KANS3 - Container 격리와 네트워크

컨테이너

컨테이너는 어플리케이션이 구동하는데 필요한 파일들(바이너리, 라이브러리, 환경변수 등)을 포함한 하나의 패키지입니다. 컨테이너는 VM과는 비슷하면서도 다릅니다. 더 가볍고 효율적입니다. 컨테이너는 패키징된 상태 그대로 어느 환경(CPU 아키텍쳐가 같은)에서도 동일하게 구동됩니다.

Docker

Docker는 컨테이너를 생성, 테스트, 운영하는데 도움이 되는 도구로 Poadman, LXC 등이 있습니다. 여기서는 Docker를 중점적으로 이야기합니다.

아래는 실제로 Citizen이라는 Terraform Private Registry OSS를 구동중인 서버의 프로세스 목록입니다.

> ps -ef
root         466       1  0 Jun05 ?        01:37:13 /usr/bin/containerd
root         607       1  0 Jun05 ?        00:42:01 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sockunattended-upgrade-shutdown --wait-for-signal
root        1250     607  0 Jun05 ?        00:00:13 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 3000 -container-ip 172.23.0.2 -c
root        1257     607  0 Jun05 ?        00:00:12 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 3000 -container-ip 172.23.0.2 -contai
root        1280       1  0 Jun05 ?        00:07:25 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 5cf1ca7e0871f9d1a364ac7ccb1f4276e5c13c2f
root        1301    1280  0 Jun05 ?        00:00:00 /bin/sh -c citizen server
root        1335    1301  0 Jun05 ?        00:00:18 citizen server

위에는 표시되지 않았지만, 다른 명령어(netstat -nlp)로 확인해보면 다음과 같은 소켓을 열고 사용하는걸 확인할수 있습니다.

> netstat -nlp |grep docker
....
unix    2   [ ACC ]    STREAM   LISTENING   17136   1/init      /run/docker.sock
....

이 소켓에 대해 권한을 부여하고, Jenkins, Gitlab Runner 등에 사용할수 있도록 허용한다면, Docker in Docker 형태로 컨테이너 빌드시에 스팟성으로 빌드하고, 레지스트리에 Push해서 저장하고 종료할수 있습니다.

실제로 실무에서는 Gitlab을 사용중에 있으며 컨테이너 빌드시 스팟성으로 구성하고, 비울수 있도록 dind(docker in docker) 이미지를 통해서 구성하고 있습니다.

> .gitlab-ci.yml
.docker_job_template: &dind_job
 image: docker:latest
 services:
   - docker:dind

또한, 권한을 사용할수 있도록 gitlab runner의 설정에 privileged 설정을 추가했습니다.

> config.toml
[[runners]]
  ...
  executor = "docker"
  ...
  [runners.docker]
    tls_verify = false
    image = "alpine:latest"
    privileged = true
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    volumes = ["/cache", "/certs"]
    shm_size = 0

이를 통해 gitlab runner에 대해서 socket 파일에 별도의 사용자 권한을 주지 않고 dind를 사용하고 있습니다.

컨테이너의 프로세스 격리

pivot-root / namespace / Overlay filesystem / cgroups 4가지로 이루어집니다.

pivot_root

새로운 루트파일 시스템으로 전환하는데 사용합니다. 컨테이너 환경에서 새로운 루트파일 시스템으로 설정해서 격리된 환경이 되도록 합니다.

namespace

네임스페이스는 시스템 리소스들을 격리하는데 사용합니다. PID(Process ID), NET(Network), MNT(Mount), UTS(이름/도에인 격리), IPC(프로세스간 통신), USER(사용자/그룹 분리)와 같이 6가지가 있습니다.

Overlay Filesystem

여러 개의 파일시스템을 하나의 파일시스템처럼 사용할수 있도록 합니다. 읽기 전용인 기본 이미지 영역과 쓰기 전용(컨테이너 내부)을 결합하여 동작합니다. Lowerdir: 읽기 전용, 컨테이너 이미지 Upperdir: 쓰기 전용, 컨테이너 내 변동사항 Merged: 읽기/쓰기 부분이 합쳐진 가상파일 시스템

cgroups(Control Groups)

프로세스 그룹에 대해 CPU, 메모리, Disk I/O, 네트워크 등의 시스템 리소스를 제어하고, 모니터링할수 있게 합니다. 여기에는 리소스 제한, 격리, 계층화, 모니터링이 가능하도록 합니다.

네트워크

컨테이너는 네트워크 네임스페이스로 호스트와의 격리 환경을 구성합니다. 도커를 사용하는경우 iptables를 확인해보면 다음과 같은 규칙들이 있습니다.

> iptables -t filter -S
-P INPUT ACCEPT
-P FORWARD DROP
-P OUTPUT ACCEPT
-N DOCKER
-N DOCKER-ISOLATION-STAGE-1
-N DOCKER-ISOLATION-STAGE-2
-N DOCKER-USER
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A FORWARD -o br-874bb6874a6e -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o br-874bb6874a6e -j DOCKER
-A FORWARD -i br-874bb6874a6e ! -o br-874bb6874a6e -j ACCEPT
-A FORWARD -i br-874bb6874a6e -o br-874bb6874a6e -j ACCEPT
-A DOCKER -d 172.23.0.2/32 ! -i br-874bb6874a6e -o br-874bb6874a6e -p tcp -m tcp --dport 3000 -j ACCEPT
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -i br-874bb6874a6e ! -o br-874bb6874a6e -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -o br-874bb6874a6e -j DROP
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
-A DOCKER-USER -j RETURN
---
> iptables -t nat -S
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 172.23.0.0/16 ! -o br-874bb6874a6e -j MASQUERADE
-A POSTROUTING -s 172.23.0.2/32 -d 172.23.0.2/32 -p tcp -m tcp --dport 3000 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
-A DOCKER -i br-874bb6874a6e -j RETURN
-A DOCKER ! -i br-874bb6874a6e -p tcp -m tcp --dport 3000 -j DNAT --to-destination 172.23.0.2:3000

해당 내용은 컨테이너간 통신과 외부 트래픽에 대해서는 허용되었고, docker0을 통해 나가는 트래픽은 허용합니다. 다만, 네트워크 격리규칙에 따라 일부 트래픽은 차단되도록 되어있습니다. 그 외로는 masquerrade 규칙을 통해 172 대역으로 되어있는 통신이 외부로 나갈때에는 호스트머신의 IP로 변환하도록 되어있습니다.

도커 네트워크 모드

Docker를 설치하고, Container를 실행하며 기본적으로 bridge를 기반으로 동작하게 됩니다. 아래 정보는 실제로 Citizen이라는 OSS를 구동하는 VM에서 구성된 내용입니다.

> docker network ls
NETWORK ID     NAME           DRIVER    SCOPE
e8434cf71a58   bridge         bridge    local
874bb6874a6e   data_default   bridge    local
94774c53028c   host           host      local
2ffe41df4892   none           null      local

> docker info | grep Network
Network: bridge host ipvlan macvlan null overlay

도커의 네트워크가 6개로 확인됩니다. 6개는 아래와 같이 각각의 역할을 담당하게 됩니다.

Host

호스트의 환경을 그대로 사용하며, 별도의 포워딩을 사용하지 않습니다.

None

호스트와 단절된 네트워크 입니다.

Bridge

도커의 기본 네트워크 모드로 컨테이너간의 통신을 위한 가상네트워크의 스위치입니다.

IPvlan

컨테이너가 호스트에 대해 완전히 독립적인 네트워크 인터페이스를 가지게되고, 고유한 MAC/IP를 가지게 됩니다.

Macvlan

컨테이너마다 고유한 MAC을 부여하고, 물리네트워크에서 독립적인 네트워크 장치처럼 동작하게 합니다.

NULL

컨테이너에 네트워크 인터페이스를 사용하지 않습니다.

Overlay

Docker Swarm / Kubernetes와 같은 오케스트레이션 환경에서 사용하며, K8s에서 Calico와 같은 CNI들을 의미한다고 보면 됩니다.