목차
Docker를 실무에서도 많이 다뤄보았지만 Docker를 하면서 내가 부족한 부분을 보충하고 도커의 컨테이너 프로세스에 대한 개념을 다시 다지기 위해서 Docker에 대한 블로그를 정리해보려한다.
Docker
Docker는 애플리케이션을 컨테이너 단위로 패키징하고 실행할 수 있도록 해주는 플랫폼이다. 이러한 특성 덕분에, 각 기능을 독립적인 서비스로 분리하여 운영하는 MSA(Microservice Architecture) 환경에서 효율적으로 애플리케이션을 관리하고 배포할 수 있다.
가상화
가상화(Virtualization)는 서버, 스토리지, 네트워크, 애플리케이션 등 물리적 하드웨어 리소스를 소프트웨어적으로 추상화하여 효율적으로 사용할 수 있게 하는 기술이다. 이를 통해 기업은 자원을 더 효율적으로 활용하고, 자동화된 IT 관리와 빠른 재해 복구 등의 장점을 얻을 수 있다.
또한, 물리적 하드웨어 유지 관리 대신 가상화된 소프트웨어 레이어를 통해 제한된 리소스들을 쉽게 관리하고 유지할 수 있다.
실행 방식으로는 하이퍼바이저(Hypervisor) 기반의 가상 머신(VM, Virtual Machine)을 통해 가상화를 수행한다. 대표적인 하이퍼바이저 기반 솔루션으로는 VMware와 VirtualBox 등이 있다.
컨테이너 가상화 vs VM 가상화

가상화는 크게 컨테이너 가상화와 VM 가상화로 나눌 수 있다.
두 가지 가상화 방식 모두 애플리케이션 프로세스, 종속 요소, 소스 등을 패키지화(이미지화)하여 호스트 OS와 격리된 환경을 제공한다. 하지만 구현 방식과 성능 차이가 존재한다.
VM 가상화
별도의 Guest OS를 포함하여, 애플리케이션을 설치하고 실행하는 하드웨어 수준의 가상화를 구현한다.
각 VM은 자체 운영체제를 가지고 있어 리소스 사용이 더 크고, 시스템 부팅이나 애플리케이션 실행 속도가 느릴 수 있다.
컨테이너 가상화
VM 가상화에 비해 경량화되어 있으며, 호스트 OS의 커널을 공유하는 운영체제(OS) 수준의 가상화를 구현한다.
Guest OS 없이도 애플리케이션을 실행할 수 있어 더 빠른 속도와 적은 리소스를 사용한다
컨테이너는 애플리케이션 환경을 빠르게 번들링하고 패키징할 수 있어 배포 및 확장에 유리하다.
하지만 보통은 IDC를 자체적으로 운영하는것이 아닌 이상 컨테이너기술을 관리하기 위해선 대규모환경은 Kubernetes, ECS를 통해 관리하고, 소규모환경에서는 보통 Cloud VM에 Docker를 설치한 후 애플리케이션을 운영한다.
Docker Image

Docker 이미지는 Container runtime에 필요한 바이너리, 라이브러리, 설정 값 등을 포함하며, 변경되지 않는 상태(stateless)를 유지하는 특성이 있어 이미지 자체는 불변이다. 즉, 실행 중에 상태가 변경되거나 데이터를 저장하지 않고, 항상 동일한 환경에서 컨테이너를 실행할 수 있게 해준다.
docker run 명령어로 컨테이너를 생성하면, 그 위에 컨테이너 레이어(Container layer)가 추가되는데, 이 레이어는 읽기-쓰기(read-write)가 가능하다. 그래서 하나의 이미지를 기반으로 여러 개의 컨테이너를 원하는 만큼 생성할 수 있다.
예를 들어 이미지를 통하여 Docker run명령어를 통해 컨테이너를 이용하다보면 수정사항이 생길것이다. 이럴때 내가 수정된 컨테이너를 다시 이미지를 만들고 싶으면 docker commit명령어를 통해 다시 내 컨테이너를 이미지로 만들 수 있다.
Docker Registry
Docker Registry는 기본적으로 docker.io로 지정되어있다. 예를 들어 AWS,GCP등 클라우드 Registry등을 이미지 Pull할때는 다음과 같이 앞에 이미지 앞에 Registry 주소를 정의 해줘야한다.
docker pull gcr.io/google-samples/hello-app:1.0
또한, 마찬가지로 Docker를 Push할때도 로컬에서 Docker login을 통해 Registry인증을 한 후 새로 Tag처리를 해야 Registry에 Push할 수 있다.
docker image tag myweb:v1.0 djrepo/myweb:v1.0
Docker 이미지 공유
이미지를 공유하는 방법은
- Regisry에 push하여 공유
- 이미지를 생성하는 Dockerfile과 소스를 github에 올려 공유
- 이미지를 docker save를 통해 파일로 백업하여 전달 후 docker load를 통해 공유
굳이나는 save/load를 통한 공유방식을 선호하진 않는다. docker save명령으로 통해 layer로 구성된 이미지를 *.tar파일로 묶어 다른 VM에서 docker load를 통해 이미지로 등록하기도 한다.
Docker save/load
$ mkdir save_lab && cd $_
save_lab$ docker image save phpserver:1.0 > phpserver1.tar
save_lab$ ls -lh
-rw-rw-r-- 1 kdj kdj 331M 06월 15 11:49 phpserver1.tar
save_lab$ docker image save phpserver:1.0 | gzip > phpserver1.tar.gz
save_lab$ ls -lh
-rw-rw-r-- 1 kdj kdj 331M 06월 15 11:49 phpserver1.tar
-rw-rw-r-- 1 kdj kdj 102M 06월 15 11:50 phpserver1.tar.gz
save_lab$ docker image save phpserver:1.0 | bzip2 > phpserver1.tar.bz2
save_lab$ ls -lh
-rw-rw-r-- 1 kdj kdj 331M 06월 15 11:49 phpserver1.tar
-rw-rw-r-- 1 kdj kdj 102M 06월 15 11:50 phpserver1.tar.gz
-rw-rw-r-- 1 kdj kdj 93M 06월 15 11:51 phpserver1.tar.bz2
hostos1:~/$ scp phpserver1.tar.gz kevin@hostos2:/home/kevin/backup/phpserver1.tar.gz
---------------------------------------------------------------------------
hostos2:~/backup$ docker image load < phpserver1.tar.gz
Loaded image: phpserver:1.0
hostos2:~/backup$ docker images
hostos2:~/backup$ docker run -itd -p 8200:80 phpserver:1.0
hostos2:~/backup$ curl localhost:8200
vm에서 private registry 구축
vm자체에서 private registry를 구축하는 방법이다. Docker Hub에 공개되어 있는 공식 image인 "regisry"를 사용하면 된다. 주로 적은 용량의 container service로 사용하기 좋다.
kdj@hostos1:~$ docker pull registry
kdh@hostos1:~$ docker images | grep registry
registry latest 65f3b3441f04 3 weeks ago 24MB
kdj@hostos1:~$ docker run -d \
-v /home/kevin/registry_data:/var/lib/registry \
-p 5000:5000 \
--restart=always \
--name=local-registry \
registry
kdj@hostos1:~$ docker ps | grep registry
36a452a303e1 registry "/entrypoint.sh /etc…" 13 seconds ago Up 12
seconds 0.0.0.0:5000->5000/tcp, :::5000->5000/tcp local-registry
kdj@hostos1:~$ sudo netstat -nlp | grep 5000
tcp 0 0 0.0.0.0:5000 0.0.0.0:* LISTEN 58135/docker-proxy
tcp6 0 0 :::5000 :::* LISTEN 58142/docker-proxy
똑같이 image tag를 걸어주면된다. 명심할 부분이 한가지 있는데 기본적으로 docker설치시 registry가 docker.io로 되어있기 때문에 이부분에 repositry container의 경로를 따로 추가해줘야한다.
kdj@hostos1:~$ curl -X GET http://192.168.56.101:5000/v2/_catalog
{"repositories":[]}
kdj@hostos1:~$ docker image tag myweb:v1.0 192.168.56.101:5000/myweb:v1.0
kdj@hostos1:~$ docker push 192.168.56.101:5000/myweb:v1.0
The push refers to repository [192.168.56.101:5000/myweb]
Get "https://192.168.56.101:5000/v2/": http: server gave HTTP response to HTTPS client <- 에러발생
kdj@hostos1:~$ sudo vi /etc/init.d/docker
…
DOCKER_OPTS=--insecure-registry 192.168.56.101:5000
kdj@hostos1:~$ sudo vi /etc/docker/daemon.json <- 이파일은 기존에 없어서 만들어주어야한다.
{ "insecure-registries": ["192.168.56.101:5000"] }
kdj@hostos1:~$ sudo systemctl restart docker.service
# 꼭 docker를 restart해줘야한다.
kdj@hostos1:~$ docker info
…
Insecure Registries:
192.168.56.101:5000
127.0.0.0/8
kdj@hostos1:~$ docker push 192.168.56.101:5000/myweb:v1.0
The push refers to repository [192.168.56.101:5000/myweb]
0e09aaca8f2d: Pushed
…
f1417ff83b31: Pushed
v1.0: digest: sha256:1e2580f9199d54738823cda8a79d154080ecbfb93bdedf4325baf4e1dea20 size: 2198
kdj@hostos1:~$ curl -X GET http://192.168.56.101:5000/v2/_catalog
{"repositories":["myweb"]}
kdj@hostos1:~$ curl -X GET http://192.168.56.101:5000/v2/myweb/tags/list
{"name":"myweb","tags":["v1.0"]}
컨테이너 격리 기술
Docker는 컨테이너 격리 기술이라고도 말한다. docker run -it --rm name=mycontainer ubuntu:14.04 bash 명령어를 통해 직접 컨테이너에 들어가서 확인해보면 ls, df-h, hostname, ifconfig의 명령어를 통해 HostOS와 파일시스템, Hostname등이 격리 되어있는것을 확인할 수 있다.
컨테이너에서 lsns명령어를 쳐보면 다음과 같은 컨테이너 격리기술이 있는것을 확인할 수 있다.

- chroot: 프로세스가 특정 디렉터리 내에서만 작동하도록 제한한다. 컨테이너 내에서 보이는 파일 시스템은 호스트와 격리된 상태이다.
- pivot_root: 현재 루트 파일 시스템을 다른 파일 시스템으로 교체하여, 컨테이너가 독립된 파일 시스템을 사용할 수 있게 한다.
- Mount Namespace: 컨테이너가 사용하는 파일 시스템의 마운트 상태를 격리하여, 독립적인 파일 시스템 트리를 제공한다.
- UTS Namespace: 컨테이너가 자체 호스트 이름과 도메인 이름을 설정할 수 있도록 하여, 호스트와의 식별자를 분리한다.
- PID Namespace: 컨테이너 내의 프로세스들이 호스트와 겹치지 않는 독립적인 PID를 사용하게 한다.
- Network Namespace: 네트워크 설정을 격리하여, 컨테이너가 독립적인 네트워크 인터페이스와 IP 주소를 가지게 한다.
- IPC Namespace: 프로세스 간 통신 자원을 격리하여, 컨테이너가 독립적인 IPC 자원을 사용합니다.
여기서 의문이었던점은 Docker는 실제로 호스트 OS의 디스크를 사용하지만 이게 왜 격리된거지 하고 궁금점이 있어 찾아본 결과 Docker는 Overlay Filesystem을 사용하여 컨테이너의 루트 파일 시스템을 구성한다고 한다. 이 기술은 호스트 시스템의 파일 시스템 위에 컨테이너의 읽기 전용 이미지 레이어와 쓰기 가능한 레이어를 겹쳐서 보여준다. 실제 디스크는 호스트의 것이지만, 컨테이너는 독립적인 파일 시스템처럼 작동하기때문에 격리되었다고 표한한다고 한다.
아래는 그에 대한 실습자료이다.
컨테이너 안에서 txt파일을 하나 만들어주고 HostOS 볼륨에서 확인해본결과 HostOS서도 볼 수 있었다.
kdj@hostos1:~/LABs/ch05$ docker run -it --name=mycontainer ubuntu:14.04 bash
root@c7539527c2a6:/# echo 'fastcampus!' > mycontainer.txt
root@c7539527c2a6:/# cat mycontainer.txt
fastcampus!
root@c7539527c2a6:/# (접속 중..)
# 다른 터미널을 열어 linux ps 명령으로 해당 컨테이너를 확인한다.
# 컨테이너는 동적 프로세스와 같다.
kdj@hostos1:~$ ps -ef | grep mycontainer
kdj 61081 54837 0 00:09 pts/0 00:00:00 docker run -it --
name=mycontainer centos:7 bash
kdj@hostos1:~$ sudo su -
root@hostos1:~# find /var/lib/docker -name mycontainer.txt
/var/lib/docker/overlay2/6a712b6af2c166aa982467957c573259d5d22e5dd9edbcca766b10c8cb7385de/
diff/mycontainer.txt
/var/lib/docker/overlay2/6a712b6af2c166aa982467957c573259d5d22e5dd9edbcca766b10c8cb7385de/
merged/mycontainer.txt
root@hostos1:~# cd
/var/lib/docker/overlay2/6a712b6af2c166aa982467957c573259d5d22e5dd9edbcca766b1
0c8cb7385de/merged
root@hostos1:/var/lib/docker/overlay2/6a712b6af2c166aa982467957c573259d5d22e5d
d9edbcca766b10c8cb7385de/merged# ls
anaconda-post.log dev home lib64 mnt opt root sbin sys usr
bin etc lib media mycontainer.txt proc run srv tmp var
7385de/merged# cat mycontainer.txt
fastcampus!
Docker container lifecycle

Docker container를 올리려면 원래는 Create로 컨테이너 이미지에 대한 스냅샷을 생성하고 Start명령으로 통해 프로세스를 올려줘야하는데 실제로는 그냥 run명령어로 한번에 해결한다. 심지어 이미지가 없어도 run명령어를 통해 자동으로 이미지까지 끌어와서 컨테이너를 올릴 수 있다.
Docker 모니터링/로깅 CLI
- Docker top
주로 컨테이너에서 실행 중인 프로세스를 확인할때 사용 - Docker stats
컨테이너 리소스 사용 통계에 대한 실시간 스트림을 확인할때 사용 - Docker logs
컨테이너 log를 확인할때 사용 대부분 Error를 찾을때 사용한다.
그 외에 Cadvisor를 이용하는 방법도 있는데 실제로 나는 실무에서 프로메테우스와 Cadvisor와 Promethus를 연동한 후 메트릭을 수집하여 사용한다. 따로 컨테이너를 올려서 사용해도 무관하다.
Docker DIsk 정리
Docker logs를 사용할때 주의할점은 Log에 대한 디스크 정리를 해줘야하는 것이다. 방법에는 여러가지 있다.
- 수동으로 그냥 지운다.
- docker log size를 처음부터 제한해준다.
- cronjob을 통해 주기적으로 지우는 작업을 자동화한다.
그냥 일시적으로 지우고 싶을 경우에는 /var/lib/docker/containers 경로에 들어가서 해당 로그의 json.log파일을 다음과 같이 지워주면 된다.
sudo truncate -s 0
3a8e69fe3abc56e7c8bbab77fa1122b3e9743f2896f28b974e5ac39a6ed-json.log
log size로 제한하고 싶으면 /etc/docker/daemon.json에 사이즈를 지정해주면된다. 특정 컨테이너만 하고싶으면 Dockerfile에 명시해 주거나 run명령어에 옵션을 더해주면된다.
kdj@hostos1:~/fastcampus/ch05$ sudo vi /etc/docker/daemon.json { "insecure-registries": ["192.168.56.101:5000"],
"log-driver": "json-file",
"log-opts": {
"max-size": "30m",
"max-file": "10"
} }
또는,
kdj@hostos1:~/fastcampus/ch05$ docker run -itd -p 6062:6060 --name=node-run2 \
> -h node-run --log-driver json-file --log-opt max-size=30m --log-opt max-file=10 \ > noderun:1.0
Docker CLI
1. 이미지 빌드
docker build -t myapp:latest .
2. 이미지 실행
docker run -d -p 8080:80 myapp:latest
3. 이미지 태그 설정
docker tag myapp:latest myapp:v1.0
4. 이미지 푸시
docker push myapp:v1.0
5. 이미지 삭제
docker rmi myapp:v1.0
6. 이미지 Pull
docker pull ubuntu:latest
7. 컨테이너 중지 및 삭제
docker stop [컨테이너ID 또는 이름]
docker rm [컨테이너ID 또는 이름]
8. 이미지 구조 확인
docker images inspect를 통해 내부 구조정보를 json형태로 볼 수 있다.
docker image inspect httpd:2.4
docker image history는 주로 Dockerfile과 많이 연관되어있다. Dockerfile을 통해 주로 어떻게 빌드했는지 확인하는데 사용한다.
docker image history httpd:2.4
9. Docker cp
Container 내의 파일을 Host로 복사, HostOS에 있는 파일을 을 컨테이너로 복사할때 주로 사용하는 명령어인데 보통 그냥 볼륨을 따로빼놔서 많이 사용하진 않는다. 복사후 Restart명령어를 꼭 쳐줘야한다.
그외에도 docker stop/start/pause/kill/attach/diff 명령어가 있다. 리눅스에서 쓰는 명령어를 그대로 Docker에서 사용한다고 보면된다.
10. docker attach
주로 컨테이너 내부에서 실행 중인 프로세스가 터미널에서의 상호작용을 필요로 하는 경우, docker attach를 사용해 해당 프로세스와 직접 상호작용한다.
예를 들어 run명령어를 통해 top명령을 같이 입력해서 attach로 컨테이너바로 확인하거나 이런 경우 사용한다.
11 .docker diff
실행 중인 컨테이너에 변경사항을 출력할때 사용한다.
12. docker export/import
docker export는 실행 중인 컨테이너의 파일 시스템을 tar arcguve로 내보내는 방법이다. exprot는 image save와 다르게 이미지의 layer에 대한 내용은 포함되지 않고, export로 추출한 컨테이너 내의 정보는 하나의 레이로 통합된다.
- export로 추출한 후 scp명령어를 통해 Host OS2로 넘겨준다음 import를 통해 이미지를 받는다.
kdj@hostos1:~/fastcampus/ch05$ docker export node-run > node-run.tar
kdj@hostos1:~/fastcampus/ch05$ ls
Dockerfile node-run.tar notepad05.txt runapp.js
kdj@hostos1:~/fastcampus/ch05$ tar tvf node-run.tar
PORTS
Up 6 seconds
NAMES
kdj@hostos1:~/fastcampus/ch05$ sudo scp node-run.tar hostos2:/home/kdj
kdj@hostos2:~$ cat node-run.tar | docker import - node-run:3.0
kdj@hostos2:~$ docker images | grep node
noderun 3.0 82247440d849 6 minutes ago 183MB
이미지는 단순히 컨테이너의 파일시스템을 아카이빙해서 만든 이미지이기 때문에 컨테이너 run 후 실행할 명령(CMD)이 요구되기 때문에 import 시 CMD를 추가하는 방법과 Dockerfile에 import된 이미지와 CMD를 추가하여 생성한다 .
- run명령어에 옵션추가
kdj@hostos2:~$ docker import --change 'CMD ["node", "/app/runapp.js"]' node-run.tar node-run:3.0
kdj@hostos2:~$ docker images | grep node
node-run 3.0 6bc8c5576e5b 5 seconds ago 182MB
kdj@hostos2:~$ docker run -itd --name=node-run3 -p 6064:6060 node-run:3.0
- Dockerfile을 이용해 기존이미지를 명시해도 CMD 명령어 추가
kdj@hostos2:~$ cat node-run.tar | docker import - node-run:4.0
kdj@hostos2:~$ vi Dockerfile_noderun4
FROM node-run:4.0
CMD ["node", "/app/runapp.js"]
kdj@hostos2:~$ docker build -t node-run:5.0 -f Dockerfile_noderun4 .
kdj@hostos2:~$ docker images | grep node
node-run 4.0 38e1235ef46c About a minute ago node-run 5.0 6f3d769c419c About a minute ago
kdj@hostos2:~$ docker run -itd --name=node-run5 -p 6065:6060 node-run:5.0
기존에 Docker를 어느정도 다뤄봐서 거의 아는 내용이었지만 나는 주로 클라우드 환경에서 Docker를 다뤄봤기 때문에 위 내용은 주로 IDC센터에서 사용될것 같다는 생각을 했다.
'Docker' 카테고리의 다른 글
| [Docker] Docker Swarm (0) | 2024.10.24 |
|---|---|
| [Docker] Dockerfile & Build & Docker-Compose (0) | 2024.10.23 |
| [Docker] Docker Resource Limit & Volume (0) | 2024.10.23 |
| [Docker] Docker Networking & Proxy (0) | 2024.10.21 |