$dockerrun-itubuntu:latestbashroot@03d7ffae4c3c:/#uname-aLinux03d7ffae4c3c5.3.0-1032-aws#34~18.04.2-Ubuntu SMP Fri Jul 24 10:06:28 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux$dockerrun-itcentos:latestbash[root@6ec49524540c /]# uname -aLinux6ec49524540c5.3.0-1032-aws#34~18.04.2-Ubuntu SMP Fri Jul 24 10:06:28 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux# 맥OS는 리눅스가 아니므로 네이티브하게 도커를 사용할 수 없어, linuxkit 기반 경량 가상머신 위에서 도커를 실행합니다.# 이후의 실습은 리눅스에서 실행하길 권장합니다.❯dockerrun-itcentos:latestbash[root@c3fdb0cda9ac /]# uname -aLinuxc3fdb0cda9ac4.19.76-linuxkit#1 SMP Tue May 26 11:42:35 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
컨테이너는 이미지에 따라서 실행되는 환경(파일 시스템)이 달라진다.
이것이 가능한 이유는 chroot 를 활용하여 이미지(파일의 집합)를 루트 파일 시스템으로 강제로 인식시켜 프로세스를 실행하기 때문이다.
같은 호스트의 커널을 쓰고 있지만, 이미지마다 루트를 다르게 설정하여 서로 다른 파일 시스템을 경험할 수 있다.
# 둘의 파일시스템이 다른 것을 볼 수 있다. $dockerrun-itubuntu:latestbashroot@03d7ffae4c3c:/#cat/etc/*-releaseDISTRIB_ID=Ubuntu$dockerrun-itcentos:latestbash[root@6ec49524540c /]# cat /etc/*-releaseCentOSLinuxrelease8.2.2004 (Core)
## 우선, 터미널의 PID를 확인해봅니다.$echo $$3441$sudops-ef|grep3441ubuntu34413440005:46pts/200:00:00-bash## 그럼, PID 1의 Command를 확인해봅니다.$cat/proc/1/commsystemd$pstree-psystemd(1)─┬─accounts-daemon(849)─┬─{accounts-daemon}(859)...## 이제 nginx 컨테이너를 실행해봅니다.$dockerrun--namenginx-d-p80:80nginx:latest$dockerexec-itnginxbashroot@acaf67af191e:/#echo $$324## nginx 컨테이너의 PID 1 의 Command는 nginx임을 확인할 수 있습니다. $sudodockerexec-itacaf67af191ecat/proc/1/commnginxroot@acaf67af191e:/#apt-getupdateroot@acaf67af191e:/#apt-getinstallpsmiscroot@acaf67af191e:/#pstree-pnginx(1)--nginx(28)## 하지만 사실은 PID 4453 프로세스임을 확인할 수 있습니다. ## 서버에선 그저 nginx 프로세스가 하나 띄워져있다고 생각합니다.$ cat /var/run/docker/runtime-runc/moby/acaf67af191e5165b98b427b851f9b29a1450d20abd4e2e5287463ccc02c24a0/state.json | jq '.init_process_pid'
4453$ cat /var/run/docker/runtime-runc/moby/acaf67af191e5165b98b427b851f9b29a1450d20abd4e2e5287463ccc02c24a0/state.json | jq '.namespace_paths'
{"NEWCGROUP":"/proc/4453/ns/cgroup","NEWIPC":"/proc/4453/ns/ipc","NEWNET":"/proc/4453/ns/net","NEWNS":"/proc/4453/ns/mnt","NEWPID":"/proc/4453/ns/pid","NEWUSER":"/proc/4453/ns/user","NEWUTS":"/proc/4453/ns/uts"}$ls-Al/proc/1/ns|awk'{ print $9 $10 $11 }'$ps-ef|grep4453$ps-auxfg
컨테이너의 실행환경을 이해하면 옵션을 사용하기도 훨씬 수월해진다.
-i : 표준 입력을 받도록 설정하는 것. 파이프라이닝이나 키보드 입력 등을 말한다.
-t : 가상 터미널 설정이다.
-it 가 조합되면, 가상 터미널로 표준입력을 할 수 있으며, -t 하나만 쓰여서는 아무런 의미가 없다.
터미널은 있는데 입력은 안되는 상황이 되므로!
-i는 홀로 쓰여도 터미널 없이 동작하는 파이프라이닝 등으로 입력을 하는 기능에 쓰일 수 있다.
-d : -i, -t 와 함께 쓰일 수 없다. background 에서 돌아가기 때문에 -i 로 입력을 대기할수도, -t 로 가상터미널을 제공할수도 없기 때문!
도커 데몬은 크게 컨테이너, 이미지 빌드, 관리, 공유, 실행, 및 컨테이너 인스턴스 관리 등의 기능을 한다.
프로세스의 데이터가 변경되더라도 원본 프로그램 이미지를 변경할 수 없듯이, 컨테이너의 데이터가 변경되더라도 컨테이너 이미지의 내용을 변경할 수 없다.
도커 이미지는 [저장소 이름]/[이미지 이름]:[태그] 의 형태로 구성된다.
## Official 이미지는 [저장소 이름]을 붙여주지 않아도 됩니다.$dockerpullubuntu:latest
도커는 여러 레이어를 합쳐서 파일 시스템을 이룬다.
도커가 명령어로 commit, pull, push 등을 사용하는 이유를 다시 한 번 생각해본다.
## ubuntu 컨테이너를 생성할 때, 어떤 순서로 진행했는지 확인해봅니다. $dockerhistoryubuntu## ubuntu 이미지를 활용하여 commit_test라는 태그를 붙인 컨테이너를 생성합니다.$dockerrun-i-t--namecommit_testubuntu:latest## 컨테이너 내부에 파일을 하나 생성합니다.root@d7c8c6bf4aa4:/#echotest_first!>>first## 컨테이너의 변경사항을 커밋합니다.$dockercommit \-a"woowahancu"-m"my first commit " \commit_test \commit_test:first## 커밋한 컨테이너 이미지가 추가되었음을 확인할 수 있습니다.$dockerimages## 변경사항을 하나더 추가한 후 커밋합니다$dockerrun-i-t--namecommit_test2commit_test:firstroot@07640650696a:/#echotest_second!>>second$dockercommit \-a"woowahancu"-m"my second commit " \commit_test2 \commit_test:second## 각 이미지들사이에 추가된 Layer를 확인합니다.$dockerinspectubuntu:latest|jq'.[].RootFS.Layers'$dockerinspectcommit_test:first|jq'.[].RootFS.Layers'$dockerinspectcommit_test:second|jq'.[].RootFS.Layers'$dockerinspectcommit_test:first|jq'.[].GraphDriver'$dockerinspectcommit_test:second|jq'.[].GraphDriver'## 커밋 변경사항을 확인해봅니다. $cat/var/lib/docker/overlay2/[컨테이너ID]/diff/second
4. 도커 네트워크
veth interface
랜카드에 연결된 실제 네트워크 인터페이스가 아니라 가상으로 생성한 네트워크 인터페이스이다.
일반적인 네트워크와는 다르게 패킷을 전달받으면, 자신에게 연결된 다른 네트워크 인터페이스로 패킷을 보내주기 때문에 항상 쌍으로 생성해야한다.
NET namespace
리눅스의 격리 기술인 namespace 중 네트워크와 관련된 부분을 말한다.
네트워크 인터페이스를 각각 다른 namespace 에 할당함으로써 서로가 서로를 모르게끔 설정할 수 있다.
도커 네트워크 구조
위의 두 가지 개념을 이용하여 네트워크를 구성한다.
mac 이나 window 는 veth interface 가 VM 안에 감춰져있어서 확인하기가 어렵다.
컨테이너는 namespace 로 격리되고, 통신을 위한 네트워크 인터페이스 eth0을 할당받는다. (파란부분)
호스트OS 의 veth interface가 생성되고, 컨테이너 내의 eth0 과 연결된다.
host 의 veth interface 는 docker0라는 다른 veth interface 와 연결된다.
## 도커를 생성하면 3가지 형태의 network가 생김을 확인할 수 있습니다.$sudodockernetworklsNETWORKIDNAMEDRIVERSCOPE6b6ce553a425bridgebridgelocal81a18bc9cc40hosthostlocal576b0223f9cfnonenulllocal## bridge 네트워크를 확인해보면 172.17.0.0/16 대역을 할당했음을 확인할 수 있습니다.$dockernetworkinspectbridge[ {"Name": "bridge","Id": "6b6ce553a425c9392c5a65b8dcd2a57e1665289354b97f430758b745b1dc86a7", ..."Subnet": "172.17.0.0/16","Gateway": "172.17.0.1"## 그리고 172.17.0.0/16 대역은 docker0로 매핑되어 있습니다.$ ip routedefault via 192.168.0.193 dev eth0 proto dhcp src 192.168.0.207 metric 100172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1## docker0는 veth interface와 매핑된 브릿지임을 확인할 수 있습니다.$ brctl show docker0bridge name bridge id STP enabled interfacesdocker0 8000.024238d4b0f5 no vethc8e309f$ ip link ls3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default66: vethc8e309f@if65: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
docker-compose 로 띄우면, 다른 네트워크 대역을 가진다. compose 로 묶은 범위에 맞춰서 브릿지를 하나 더 생성하기 때문!
서로 공유하는 브릿지가 다르므로, docker-compose 로 띄운 컨테이너와 일반 컨테이너는 서로 통신할 수 없다.
기본적으로 컨테이너는 외부와 통신이 불가하다. 포트포워딩을 설정하여 외부에 컨테이너를 공개할 수 있다.
# 포트포워딩 설정과 함께 컨테이너를 생성합니다.$dockercontainerrun-d-p8081:80nginx16cd67c48e5721a6b666192b8960875c720168bf6c5e3ed2138fb04c492447c6$sudodockerps-aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
16cd67c48e57 nginx "/docker-entrypoint.…" 4 seconds ago Up 3 seconds 0.0.0.0:8081->80/tcp trusting_bhabha
# Host의 8081포트가 listen 임을 확인합니다.$sudonetstat-nlp|grep8081tcp600:::8081:::*LISTEN10009/docker-proxy# docker-proxy 라는 프로세스가 해당 포트를 listen 하고 있음을 볼 수 있습니다. # docker-proxy는 들어온 요청을 해당하는 컨테이너로 넘기는 역할만을 수행하는 프로세스입니다. # 컨테이너에 포트포워딩이나 expose를 설정했을 경우 같이 생성됩니다.$iptables-tnat-L-nChainDOCKER (2 references)targetprotoptsourcedestinationRETURNall--0.0.0.0/00.0.0.0/0RETURNall--0.0.0.0/00.0.0.0/0RETURNall--0.0.0.0/00.0.0.0/0RETURNall--0.0.0.0/00.0.0.0/0DNATtcp--0.0.0.0/00.0.0.0/0tcpdpt:8081to:172.17.0.2:80# 보시다시피 모든 요청을 DOCKER Chain 으로 넘기고, DOCKER Chain 에서는 DNAT를 통해 포트포워딩을 해주고 있음을 볼 수 있습니다.# 이 iptables 룰은 docker daemon이 자동으로 설정합니다.
docker container 의 네트워크 모드는 bridge, host, container, none 등 총 4개가 존재한다.