[공부정리] Docker의 흥미로운 사실들

jeyong·2024년 11월 3일
0

공부 / 생각 정리  

목록 보기
120/121


이번 게시글에서는 Docker를 공부하며 흥미롭게 접한 내용을 정리하고자 한다. 흔히 볼 수 있는 내용보다는 흥미로운 사실들만 모아 작성했으므로, 다소 가독성이 떨어질 수 있다.

1. 아키텍처

Docker는 생각보다 복잡한 구조로 이루어져 있다. 많은 사람들이 이 사실을 모른다는 것은 그만큼 기술적으로 잘 설계되어 있다는 의미일 것이다. 이제 Docker의 구조에 대해 알아보자.

  • 간단히 말해, docker CLI로 보낸 요청을 docker daemon이 docker.sock을 통해 수신하고, 이를 기반으로 containerd를 활용해 컨테이너를 생성 및 관리한다. 즉, docker daemon이 컨테이너를 직접 생성하는 것이 아니라, containerd가 컨테이너 생성을 담당하는 구조이다.
  • containerd(고수준 런타임)는 컨테이너 런타임을 관리하는 서비스로, Docker는 이 런타임으로 runc를 사용한다. runc(저수준 런타임)는 리눅스의 cgroup과 namespace를 이용해 실제로 컨테이너를 생성하는 역할을 한다.
  • containerd와 runc가 직접적으로 통신하면 containerd 종료 시 runc도 함께 종료되는 문제가 발생할 수 있으며, 이는 실행 중인 컨테이너에 영향을 미칠 수 있다. 이를 방지하기 위해 shim이 도입되었으며, shim은 runc가 독립적으로 작동하도록 지원한다.
  • docker daemon은 노는 건가?

    당연히 아니다. 컨테이너 생성 기능을 제외하고도 container build, security, volume, networking, secrets, orchestration, distributed state와 같은 기능을 담당하고 있다. 이러한 이유로 Kubernetes에서 Docker를 컨테이너 런타임으로 사용하기엔 다소 무거운 선택일 수 있지 않을까? 라는 생각도 하고 있다.

  • cgroup? namespace?

    cgroup은 특정 프로세스가 사용할 수 있는 자원을 제한하고, namespace는 프로세스의 접근 범위를 격리하는 리눅스 커널 기술이다. 이런 기술을 개발하거나 이를 통해 가상화 기술을 발전시킨 사람들은 정말 대단하다고 생각한다.

  • 표준 준수 (OCI)?

    컨테이너 런타임은 OCI(Open Container Initiative) 표준을 준수해 다양한 도구와 호환되도록 설계된다. 이는 컨테이너 이미지가 어디에서 생성되었든 같은 방식으로 실행될 수 있게 보장한다.

  • 왜 이런 복잡한 구조를?

    하나의 프로세스가 다른 프로세스의 영향을 받지 않도록 책임을 분리한 구조이다. 실제로 docker daemon을 재시작해도 containerd와 컨테이너들은 계속 실행되고, containerd를 재시작하더라도 이미 실행 중인 runc 프로세스는 shim을 통해 독립적으로 유지된다. runc는 container 생성을 완료하면 종료되며, shim이 그 후 관리를 이어받기 때문이다.

이 외에도 Kubernetes와 docker-shim에 대한 흥미로운 내용이 많지만, Docker의 내용을 벗어나기 때문에 다음에 다루어 보겠다.

2. 네트워크

Docker 네트워크 구조는 실생활의 네트워크 구조와 비슷하다. 그래서 실생활의 네트워크 구조를 먼저 이해하는 것이 좋다.

간단하게 설명하자면, 사설 IP에서 외부 서버로 접속할 때는 NAT 기술을 이용해 공인 IP로 변환하여 접속한다. 반대로 외부 서버에서 사설 IP로 접속하고 싶을 때는 포트 포워딩 기술을 이용해 공인 IP와 사설 IP를 매핑해 전달한다.

Docker 네트워크는 외부 서버와 컨테이너의 연결을 위해 소프트웨어 기반 라우터를 구현한 것과 비슷하다.

컨테이너를 실행하면 veth라는 가상 네트워크 인터페이스가 생성되고, 이를 컨테이너 내부의 eth0에 연결한다. veth는 게이트웨이 역할을 하는 브리지 네트워크에 연결되며, 만약 브리지 네트워크를 지정하지 않았다면 기본 브리지 네트워크인 docker0에 연결된다. docker0를 실생활의 라우터와 같은 역할로 이해할 수 있다.

  • 포트 포워딩 기술은?

    NAT으로 내부에서 외부로의 연결은 가능하지만, 외부에서 컨테이너로의 접속을 위해서는 포트 포워딩이 필요하다. 이를 위해 docker0와 같은 브리지 네트워크가 iptables에 컨테이너 IP 주소를 기록하고, 외부 요청을 해당 컨테이너로 전달하는 방식으로 작동한다.

  • 컨테이너 이름으로 통신은?

    같은 브리지에 속한 네트워크라면 컨테이너 이름만으로 통신할 수 있다. 이는 Docker가 브리지 내부에 DNS를 띄워 컨테이너 이름과 주소를 기록하기 때문이다. 단, 기본 브리지 네트워크인 docker0는 DNS 기능을 지원하지 않는다.

3. 플랫폼

종종 Docker 이미지가 어디서나 작동할 것이라고 오해할 수 있는데, 이는 사실이 아니다. Docker 컨테이너도 하나의 프로세스이기 때문에 이를 실행하는 하드웨어에 종속적이다.

도커 허브에서는 이미지 아래에 작동 가능한 아키텍처를 명시한다. 개발 환경과 배포 환경의 아키텍처가 다를 경우 빌드 시 --platform 옵션을 지정해야 한다. 다행히도 이미지를 pull 받을 때는 플랫폼 환경에 따라 적절한 이미지를 자동으로 지정해준다.

이런 불편함을 해결하기 위해 Docker는 최근 buildx라는 멀티플랫폼 빌드를 지원하기 시작했다.

Docker buildx는 한 번의 명령어로 여러 플랫폼을 지원하는 이미지를 생성하는 것이다.

4. 레이어

개인적으로 Docker의 핵심은 레이어라고 생각한다. 프로세스들 간에도 중복이 발생할 수 있다는 점을 인식하고, 이를 블록처럼 쌓아올리는 구조로 구성한 것이 참 놀랍다. 이러한 개념을 생각해내는 것도 놀랍지만, 이를 실제로 구현한 점이 더욱 인상적이다.

Docker 이미지는 여러 레이어로 구성되며, 각 레이어는 이전 레이어의 변경 사항을 포함한다. 이 구조 덕분에 기존 레이어를 그대로 유지하면서, 업데이트된 내용만 새로운 레이어로 추가하여 효율적으로 관리할 수 있다.
재미있는 점은 컨테이너 생성 시 이미지 레이어(읽기 전용) 위에 컨테이너 레이어(읽기/쓰기 전용)가 추가된다는 것이다. 이미지 레이어는 불변성을 유지하고, 컨테이너가 애플리케이션 실행 중에 생성하는 모든 파일이나 변경 사항은 컨테이너 레이어에 저장된다. 이를 통해 여러 컨테이너를 실행하더라도 이미지는 변경되지 않는다.

  • 컨테이너 레이어는 왜 쓰기 속성을 가지고 있을까?

    컨테이너는 결국 프로세스이기 때문에 작업 중 생성된 데이터(파일, 디렉터리 등)를 저장해야 한다. 이를 위해 컨테이너 레이어는 쓰기 속성을 가진다. 하지만 컨테이너가 종료되면 컨테이너 레이어는 삭제되므로 작업한 데이터는 유지되지 않는다.

  • 레이어 어디에 저장되는데?

    레이어가 저장 되는 구조는 생각보다 복잡하다. 이유는 레이어 메타정보와 레이어 데이터를 따로 관리하기 때문인데, 메타 정보는 /var/lib/image/overlay2/layerdb/sha256에 저장하고 데이터는 /var/lib/docker/overlay2에 저장한다.
    재미있는 사실은 컨테이너가 실행될 때도 레이어가 생성되기 때문에 해당 폴더에 새로운 레이어가 생성된다. 참고로 이렇게 생성된 컨테이너 레이어는 쓰기 속성을 가졌기 때문에 수정 가능하다.

  • 레이어는 어떻게 생성될까?

    Dockerfile을 빌드하면 각 명령어마다 이미지 레이어가 생성된다. 당연하게도 이 과정에서 Docker는 프로세스의 힘을 빌려야한다.
    명령어가 실행될 때마다 intermediate 컨테이너라는 임시 프로세스가 잠깐 실행되었다가 종료되며, 해당 컨테이너가 명령어를 수행하고 레이어를 생성한다. 이렇게 생성된 레이어는 이미지 형태로 저장되므로, docker images -a 명령어를 통해 확인할 수 있다. 참고로 이미지 레이어, 컨테이너 레이어, 최종 레이어는 각각 lowerdir, upperdir, merged 폴더에 저장된다.

  • 그래서 컨테이너는 어떻게 실행될까?

    레이어들은 결국 컨테이너라는 하나의 프로세스를 구성하는 요소다. 레이어는 파일 단위로 관리되며, 이 파일들을 union file system을 통해 하나로 합쳐 실행한다. 이미지 레이어와 컨테이너 레이어 간의 중복이 발생할 수 있는데, 이를 해결하기 위해 컨테이너 레이어가 우선권을 갖는다.

  • 주의할 점은?

    Dockerfile에서 각 명령어는 하나의 레이어를 생성하며, 이 레이어들 간에는 상하관계가 있다. 한 명령어를 수정하면 해당 명령어 이후 모든 자식 레이어들이 다시 빌드된다.
    따라서 실행 시간이 오래 걸리는 명령어는 최대한 부모 레이어로 올려 캐시를 활용할 수 있도록 작성하는 것이 중요하다.

5. Off The Record

Docker가 출시되면서 정말 많은 것이 바뀌었다. 도커 첫 시연 영상을 보면 알 수 있듯이, 정말 충격적인 기술이다.

하지만 그렇다고 VM 기술이 나쁜 것은 아니다. 하이퍼바이저 기반의 VM 기술은 가상화에 비해 무겁지만, Docker는 프로세스를 분리하고, VM은 운영체제를 분리하기 때문에 서로 다른 환경을 완벽히 분리한다.

따라서 둘 다 장단이 있는 기술이고, 상황에 맞게 사용할 수 있도록 편협한 시각을 가지지 않는 것이 중요하다고 생각한다.

profile
노를 젓다 보면 언젠가는 물이 들어오겠지.

0개의 댓글

관련 채용 정보