이번 장에서는 컨테이너를 다루는 CLI를 다루는 장입니다. 도커는 결국 이미지를 사용해서 컨테이너를 만들어서 사용하는게 목적이므로 빠질수 없는 부분이죠.
컨테이너 격리 기술
docker run -it --name=myubuntu16-2 ubuntu:16.04 ls df -h hostname ps -ef
위의 커맨드를 실행하면 컨테이너 내부로 들어 갑니다. 거기서 내부 구조를 ls로 확인해보면 ubuntu구조가 보입니다.
이처럼 컨테이너는 우분투 구조를 호스와 별개로 격리되게 구성하고 있습니다.
sudo apt update sudo apt install -y ifconfig ifconfig
컨테이너에서 ifconfig그로 네트워크 정보를 확인하려고 했는데 프로그램이 설치 되어있지 않네요. apt를 업데이트하고 설치해줍시다.
네트워크 정보를 보면 172.17.0.6으로 잡혀있는데 이는 별도의 네트워크 설정을 하지 않아서 docker0로 설정이 되어서 그렇습니다.
별도의 네트워크 주소를 가지고 있어서 컨테이너와 호스트 혹은 다른 컨테이너가 서로 통신을 하지 못하는 격리된 상황입니다.
통신 하는 법은 네트워크 편에서 다루겠습니다.
docker 컨테이너 lifecycle
컨테이너는 생성, 시작, 중단, 삭제라는 생명주기를 가집니다.
실습을 하면서 주로 docker run를 사용해서 진행했었는데. 이건 docker create, docker start를 합친 명령어입니다.
create: Created
start: Up
stop: Exited
rm: 삭제
위에 정리한 것은 각 커맨드에 대응하는 컨테이너의 상태입니다.
실습
ubuntu:14.04버전의 mycontainer라는 컨테이너를 만들어서 텍스트 저장하고 다른 터미널에서 호스트에 접근해서 확인해보자.
docker run -it --name=mycontainer ubuntu:14.04 bash echo 'fastcampus!' > mycontainer.txt
새로운 컨테이너를 만들고 mycontainer.txt라는 파일을 만들었다.
터미널을 열어서 ssh kevin@192.169.68.101로 호스트에 접속했다.
ps -ef | grep mycontainer sudo su - find /var/lib/docker -name mycontainer.txt cd /var/lib/docker/~/merged touch mycontainer2.txt find /var/lib/docker -name mycontainer docker exec -it mycontainer bash ls
먼저 ps -ef | grep mycontainer로 컨테이너가 잘 떠있는지 확인하자.
그런 다음 컨테이너에서 만든 파일을 찾기 위해서 관리자 권한으로 유저를 변경하자. sudo su -를 실행하자.
find /var/lib/docker -name mycontainer.txt를 실행해서 컨테이너에서 만들 파일의 위치를 찾아서 이동하자.
mycontainer2를 새로 만들고 find /var/lib/docker -name mycontainer로 확인해봅시다.
그리고 컨테이너에 접속해서 확인해보니 호스트에서 만든 파일이 보입니다.
컨테이너 관리를 위한 docker CLI(1)
위의 그림은 이번장에서 사용할 CLI 명령어들을 요약한 자료이다.
docker image를 만들기 위한 방법으로 2가지 방법이 있다. dockerfile로 부터 build, container를 commit명령어로 이미지로 빌드하는것이다.
로컬의 image는 push명령어로 서버에 저장하고 pull명령어로 다운로드 받을수 있다.
image는 run, create로 container를 생성할수 있고 exec, attach로 컨테이너에서 작업을 실행할수 있다.
stop, pause, unpause, restart, kill, rm으로 컨테이너의 lifecycle를 조정할수있다.
inspect, ps, logs, top, stats로 컨테이너의 상태를 확인할수 있다.
이는 대략적인 내용이고 아래의 글에서 자세히 실습을 하면서 살펴보자.
CLI 연습을 위한 이미지를 만들어 봅시다.
cd ~ cd fastcampus/ch05 vi runapp.js
먼저 지난번에 다운받았던 fastcampus/ch05로 이동하자. 그리고 runapp.js를 살펴보자.
스크립트를 보면 http를 사용하여 백앤드 서버를 구성하고 있다. 연결이 되면 “Your Connected.”를 로그로 남기고, 요청을 받으면 “Your request arrived.”를 로그로 남긴다.
응답으로는 “Hostname: ~”를 리턴 해준다.
이미지를 만들기 위한 Dockerfile을 살펴보자. node:20-alpine3.17 기반으로 이미지를 만드는 것을 알 수 있다. tini, curl을 설치하고 작업폴더를 /app으로 설정하고 app폴더로 이동합니다.
runapp.js를 파일을 app폴더로 이동합니다. 노출 포틑 6060으로 설정합니다.
컨테이너 실행되면 tini(찾아보니 좀비 프로세스를 제거하는 역활로 사용한다고 합니다. pid 1 으로 동작하며 이게 죽으면 컨테이너 죽습니다.)를 실행합니다. 그리고 runapp.js를 실행합니다.
도커파일을 사용해서 이미지를 만들어야 합니다.
docker build -t noderun:1.0 .
실행하면 noderun:1.0이라는 image가 생성됩니다.
docker images | grep noderun으로 생성됨을 확인 하였습니다.
docker image history 명령어는 이미지의 dockerfile을 확인할수 있는 명령어입니다. dockerfile에 어떤 정보가 기록되어있는지 간단히 살펴보기에 좋습니다. 하지만 위의 이미지처럼 잘리는 부분이 있기에 간단한 정보만 확인하고 자세한 정보는 hub.docker.com에서 살펴보는게 좋습니다.
docker run -itd -p 6060:6060 --name=node-run -h node-run noderun:1.0
이미지를 만들었으니 컨테이너를 만들어야 줘. 위의 명령어를 실행하면 node-run이라는 컨테이너가 생성됩니다.
-it: 컨테이너와 상호 작용하는 옵션. 컨테이너에 접속하여 입력을 해야할 경우 주어야 합니다.
-d: 백그라운드 옵션입니다. 이걸 안주었다면 컨테이너 접속했을 겁니다.
-p: 호스트와 컨테이너의 연결 포트를 설정해줍니다. 포트포워딩과 비슷한 개념입니다. 호스트의 6060 과 컨테이너의 6060을 연결합니다.
–name: 컨테이너의 이름을 정합니다.
-h: 컨테이너도 하나의 격리된 우분트 서버이기에 호스트네임이 존재합니다. 이건 그 이름을 정해주는 옵션입니다.
실제로 잘 작동하는 curl locahost:6060으로 호출합니다. 브라우저에서 확인할 경우는 192.168.68.101:6060으로 호출하면 됩니다. 이 경우는 “Hostname: ~”이런 정보가 응답 될 것입니다.
docker top 명령어는 리눅스 명령어와 동일하다고 보면 됩니다. 컨테이너의 상태를 자세히 확인할 때 사용하시면 됩니다.
포트를 확인 하는 명령어들을 실습한 내용입니다.
docker port node-run으로 node-run의 포트가 호스트와 어떻게 연결되어있는지 확인 할 수 있습니다. 0.0.0.0:6060이 호스트의 ipadress4로 접근하면 컨테이너의 6060으로 연결 해준다는 의미입니다.
[::]:6060은 ipaddress6를 의미합니다. 마찬가지로 6060으로 접근하면 컨테이너의 6060으로 연결해줍니다.
컨테이너의 상태를 확인 할 수 있는 또 하나의 명령어인stats을 사용해 보았습니다.
cadvisor
docker stats와 유사한 통계, Metric(시각화 정보)를 수집하는 패키지입니다. 저는 m1 pro를 사용하고 있는데 아키텍처가 arm64여서 강사님이 진행하는 버전과는 다른 버전을 사용해서 진행했습니다. 여기서는 간단히 설치하고 node-run이 실행될때 어떤 metric정보를 수집하는지 살펴봅시다.
docker run --restart=always --volume=/:/rootfs:ro --volume=/var/run:/var/run:rw --volume=/sys/fs/cgroup:/sys/fs/cgroup:ro --volume=/var/lib/docker/:/var/lib/docker:ro --volume=/dev/disk/:/dev/disk:ro --publish=9559:8080 --detach=true --name=cadvisor --privileged --device=/dev/kmsg gcr.io/cadvisor/cadvisor-arm64:v0.47.2
실행하면 cadvisor가 설치됩니다.
–restart=always: 컨테이너가 죽으면 다시 시작하는 옵션입니다.
–volume: 호스트의 파일 위치와 컨테이너의 파일위치를 연결하는 옵션입니다. 뒤에 volume을 다룰 때 자세히 정리해보겠습니다.
–publish: 위에서 설명한 -p옵션입니다. 호스트의 9559와 컨테이너의 8080을 연결합니다.
–detach: -d옵션입니다. 백그라운드로 작동하도록 하는 옵션입니다.
–name: 컨테이너의 이름
–privileged: 컨테이너가 호스트 시스템의 모든 권한을 가지도록 하는 옵션입니다. 모니터링을 하기 위해서 주는 걸로 보입니다.
–device: 호스트 시스템의 기능을 컨테이너에게 부여하는 옵션입니다. 주로 –device=/dev/nvidia0 처럼 그래픽 카드와 같은 하드웨어 장치를 사용하도록 부여해줍니다. 여기서는 /dev/kmsg를 사용하는 권한을 주었는데 시스템 로그 및 커널 메시지를 읽는 권한을 준것입니다.
gcr.io/cadvisor/cadvisor-arm64:v0.47.2 이미지를 사용했습니다.
https://console.cloud.google.com/gcr/images/cadvisor/global/cadvisor-arm64?authuser=1에서 실습당시 가장 최신의 버전을 선택했습니다.
192.168.68.101:9559로 접근하면 위와 같은 화면이 나옵니다. 특이사항으로는 Usage per Core가 비활성화 되어있는데 이건 arm쪽 버전의 버그(?)가 아닐까 합니다. ㅎㅎ
docker logs
while true; do curl 192.168.56.101:6060; sleep 3; done docker logs -f node-run
3초마다 192.168.56.101:6060을 호출하도록 합니다. docker logs -f node-run 실행하면 로그가 남는것을 알수있습니다.
이러한 로그의 양이 큰 경우, disk full error의 원인이 되기에 로그의 양은 관리 대상입니다.
로그의 데이터를 삭제 하는 방법을 살펴봅시다.
sudo ls -l /var/lib/docker/containers sudo ls -l /var/lib/docker/containers/3a8e69fe3abc56e7c8bbab77fa1122b3e9743f2896f28b974e5ac39a6ed sudo truncate -s 0 /var/lib/docker/containers/3a8e69fe3abc56e7c8bbab77fa1122b3e9743f2896f28b974e5ac39a6ed/3a8e69fe3abc56e7c8bbab77fa1122b3e9743f2896f28b974e5ac39a6ed-json.log sudo ls -l
/var/lib/docker/containers에서 해당 로그를 찾고 sudo truncate -s 0 [로그위치] 실행해서 지워주면서 관리를 할수 있습니다. 하지만 매번 이렇게 로그 데이터를 확인하고 지워 주는 것은 번거롭습니다. 로그 사이즈를 제한해서 쌓게 하면 이러한 어려움이 해결되는데요.
sudo vi /etc/docker/daemon.json # 추가 "log-driver": "json-file", "log-opts": { "max-size": "30m", "max-file": "10" } restart docker.service status docker.service
위의 명령어를 실행하면 도커의 로그가 30mb씩 10개가 쌓이게됩니다. 이 방법은 모든 컨테이너들에 적용되는 방법입니다. 컨네이 개별로 적용하는 방법이 있는데요.
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
–log-driver json-file –log-opt max-size=30m –log-opt max-file=10
위의 옵션을 적용해서 컨테이너를 만들면 됩니다.
이번에는 docker log를 보는 방법을 알아볼게요.
mysql은 MYSQL_ROOT_PASSWORD 옵션을 필수로 넣어주어야 하는데 빼고 컨테이너를 올려서 에러를 발생시키고 로그를 보겠습니다.
docker run -itd --name=mydb mysql:5.7-debian docker ps -a docker logs mydb
이렇게 컨테이너 정상적으로 올라가지 않으면 docker logs 명령어로 로그를 살펴볼수 있습니다.
docker container inspect node-run
docker container inspect [컨테이너이름]으로 컨테이너의 상세 정보를 확인할수 있다.
컨테이너 관리를 위한 docker CLI(2)
docker stop | start | pause | unpause
while true; do curl 192.168.68.101:6060; sleep 3; done # 다른 터미널 docker events docker stop node-run docker ps –a docker start node-run docker pause node-run docker unpause node-run docker ps –a
3초마다 콜하도록 실행하고 다른 터미널에서 docker events를 싱행해서 라이프사이클을 확인해보자.
docker kill
docker kill이라는 명령어로 컨테이너를 강제로 죽일수 있습니다. 그러나 이러한 방법은 데이터 손실이 발생할수 있어서 개인적으로는 좋은 방법이 아닌거 같습니다. docker stop명령어를 사용하는게 더 낳은 방법이라고 생각합니다.
docker attach | exec
attach를 사용해서 컨테이너를 사용할수 있는데. 이 명령어는 exec이랑 유사합니다. 차이점으로는 attach는 컨테이너를 사용해서 로그나 메트릭정보를 볼때 사용한다는 점입니다. exec은 직접 들어가서 컨테이너를 조작 할때 사용하는 명령어입니다.
docker run -d --name top-container ubuntu:22.04 /usr/bin/top -b docker attach top-container
우분트에 top 명령어를 실행하는 컨테이너를 만들고 attach로 로그를 출력하였습니다.
docker run -itd --name=my_container alpine sh docker exec -d my_container touch /tmp/exec_test docker exec -it my_container sh ls cd /tmp ls
exec를 사용해보았습니다. 많이 사용해 왔던거라서 아실테지만 컨테이너명 다음에 실행할 명령어를 입력하면실행이 됩니다. bash, sh을 입력하면 내부 터미널로 접근하는 걸 알수 있습니다.
docker diff
exec으로 컨테이너에서 수정 작업등로 많이 변경점이 발생했을때 어떤것이 변경이 되었는지 확인 하는 방법이 있습니다. diff 명령어가 바로 그것입니다.
docker exec -it node-run sh touch file1 rm file1 adduser kevin docker diff node-run docker diff node-run | wc -l
컨테이너의 변경사항을 diff 명령어로 확인해 보았습니다.
명령어를 실행하면 A, D, C가 나오는데 아래의 의미를 나타냅니다.
A: 파일, 폴더 추가
D: 파일, 폴더 삭제
C: 파일, 폴더 변경
docker commit
이렇게 컨테이너를 수정하고 그걸 이미지로 만드는 방법이 있습니다. commit 명령어입니다.
docker commit node-run noderun:2.0 docker images | grep noderun while true; do curl 192.168.68.101:6063; sleep 1; done docker logs -f node-run3
commit으로 이미지를 만들고 그 이미지를 사용해서 새로운 컨테이너를 만들었습니다.
그리고 그 컨테이너를 1초마다 콜을 하면서 로그를 살펴보았습니다.
docker export | import
docker export node-run > node-run.tar ls -lh tar tvf node-run. tar
export 명령어로 컨테이너를 tar로 압축할수 있다. 이건 도커 이미지가 아니기에 이미지 처럼 여러개의 레이어로 구성되지 않고 하나의 레이어로 압축된다는 점이 image와의 차이이다. 아래의 tvf를 싱행하면 해당 파일의 목록을 알수 있다.
이 파일을 hostos2에 전송하여 그곳에서 node-run.tar를 임포트 해보자.
sudo scp node-run.tar kevin@192.168.68.102:/home/kevin cat node-run.tar | docker import - node-run:3.0 docker images | grep node docker run -itd --name=node-run3 -p 6064:6060 noderun:3.0
scp를 사용해서 hostos2로 node-run.tar를 전송하고 cat node-run.tar | docker import – node-run:3.0를 실행하여 이미지로 임포트하였습니다.
그 이미지를 컨테이너화 할려고 했는데 오류가 발생합니다. 이건 원본 이미지의 dockerfile처럼 cmd 명령이 필요해서 그렇습니다.
docker rmi node-run:3.0 docker import --change 'CMD ["node", "/app/runapp.js"]' node-run.tar node-run:3.0 docker images | grep node docker run -itd --name=node-run3 -p 6064:6060 node-run:3.0 docker ps -a | grep node curl localhost:6064
docker import –change ‘CMD [“node”, “/app/runapp.js”]’ node-run.tar node-run:3.0 이 명령어를 실행해서 cmd 명령어를 실행하게 합니다.
컨테이너가 정상적으로 올라가는걸 알수있습니다.
또다른 방법으로 import를 할수 있는데 cmd명령어를 dockerfile을 새로 만들어서 실행하는 것이다.
cat node-run.tar | docker import - node-run:4.0 vi Dockerfile_noderun4 # dockerfile FROM node-run:4.0 CMD["node", "/app/runapp.js"] docker build -t node-run:5.0 -f Dockerfile_noderun4 . docker images | grep node docker run -itd --name=node-run5 -p 6065:6060 node-run:5.0 docker ps | grep node-run5 curl localhost:6065
도커파일을 통해서 다시 이미지를 만들고 그걸로 컨테이너를 만들어 보았다. 개인적으로는 두번째 방법이 좀더 직관적인거 같아서 이 방법을 사용할거 같다.
도커 컨테이너를 다루는 방법을 정리해보았다. 사실 강의는 좀더 진행했는데 블로그로 정리 하는 부분이 좀 밀리고 있다. 시간을 내서 빨리 따라 가도록 해보겠습니다.