도커 & 쿠버네티스

어플리케이션 배포 방식 : 컨테이너 (Docker engine : dockerD, containerD, runC)

wy-family 2025. 1. 21. 08:58

 

컨테이너 엔진(특히 Docker Engine)의 작동 방식은 컨테이너 기술의 핵심인 커널 공유 원칙에 기반을 두고 있으며, 컨테이너 생성, 관리, 운영을 체계적으로 처리하기 위해 다양한 컴포넌트가 협력합니다. 아래에서 Docker Engine의 구성요소작동 원리를 구체적으로 설명할게요.


1. Docker Engine의 구성 요소

Docker Engine은 다음 세 가지 주요 컴포넌트로 구성되어 있습니다.

(1) dockerD (Docker Daemon)

  • Docker의 핵심 프로세스이자 사용자와 Docker Engine의 나머지 컴포넌트 사이를 연결하는 중앙 관리자.
  • 사용자의 명령(Docker CLI나 API 요청)을 받아서 실행에 필요한 작업을 orchestrate(조율)합니다.
  • 주요 역할:
    • 컨테이너 생성/운영/삭제 명령 처리.
    • 사용자 명령을 containerD와 runC로 전달.
    • 이미지 관리 및 네트워크 설정.

(2) containerD

  • 컨테이너의 라이프사이클을 관리하는 데 특화된 모듈.
  • 컨테이너 생성, 시작, 중지, 삭제와 같은 작업을 수행합니다.
  • runC를 호출하여 실제 컨테이너를 생성하고, 이를 모니터링하며 관리.
  • containerD는 컨테이너 관리 작업의 중심부 역할을 합니다.

(3) runC

  • 컨테이너 실행에 필요한 가장 낮은 수준의 작업을 담당.
  • OCI(Open Container Initiative) 표준에 따라 컨테이너를 실행하는 도구.
  • HostOS의 커널 기능을 사용하여 컨테이너를 생성하고 실행.
  • 주요 역할:
    • 네임스페이스(namespaces)와 cgroups(control groups)를 통해 프로세스를 격리.
    • HostOS의 커널과 직접 통신하여 자원을 분리 및 할당.

2. Docker Engine의 작동 방식

Docker Engine은 사용자의 명령을 받아 컨테이너를 생성하고 관리하는 일련의 과정을 수행합니다. 이 과정은 크게 명령 처리 → 컨테이너 생성 → 실행 및 관리 단계로 나눌 수 있습니다.

(1) 명령 처리

  1. 사용자가 Docker CLI(Command Line Interface)나 API를 통해 명령을 입력합니다.
    • 예: docker run ubuntu 명령어는 Ubuntu 컨테이너를 생성하고 실행하라는 요청.
  2. dockerD(Docker Daemon)가 이 명령을 수신합니다.
    • 명령의 목적에 따라 이미지 다운로드, 컨테이너 생성, 삭제, 실행 등의 작업을 실행 계획으로 변환.

(2) 컨테이너 생성

  1. 이미지 확인 및 다운로드:
    • dockerD는 로컬에 요청된 이미지가 있는지 확인.
    • 이미지가 없으면 Docker Hub와 같은 레지스트리에서 다운로드.
  2. 컨테이너 생성:
    • dockerD가 containerD에 컨테이너 생성을 요청.
    • containerD는 runC를 호출하여 실제 컨테이너 생성 작업을 수행.
      • runC의 역할:
        • HostOS의 커널 기능을 사용하여 컨테이너를 실행.
        • 네임스페이스(namespaces): 프로세스를 격리하여 각 컨테이너가 독립적인 실행 환경을 가질 수 있게 만듦.
          • 예: PID, 네트워크, 파일 시스템 등을 분리.
        • cgroups(control groups): 컨테이너별 CPU, 메모리 등 자원을 제한하고 관리.

(3) 실행 및 관리

  1. 컨테이너 실행:
    • runC가 생성한 컨테이너를 실행.
    • HostOS 커널과의 통신은 소켓 통신을 통해 이루어짐.
      • 소켓 통신은 컨테이너와 HostOS 간의 데이터 교환을 처리하는 경량화된 방식.
  2. 라이프사이클 관리:
    • containerD가 실행 중인 컨테이너의 상태를 모니터링하고, 컨테이너 중지, 삭제 요청을 처리.
    • 컨테이너의 네트워크 설정, 저장소 관리도 담당.
  3. 사용자 피드백:
    • 실행 결과나 상태 정보는 dockerD를 통해 사용자에게 전달.

3. Docker Engine에서 커널 공유

Docker Engine의 가장 큰 특징은 HostOS 커널의 공유입니다.

  • 컨테이너는 자체 커널을 가지지 않고, HostOS의 커널을 활용하여 작동합니다.
  • 이로 인해:
    • 오버헤드가 줄어들어 실행 속도가 빠르고 효율적.
    • 네임스페이스와 cgroups을 사용해 자원을 격리함으로써 보안과 독립성을 보장.

4. Docker Engine 작동 과정 요약

사용자의 명령 → 컨테이너 실행까지의 흐름

  1. 명령 처리:
    • 사용자가 CLI로 명령을 실행.
    • dockerD가 명령을 수신하고 처리.
  2. 컨테이너 생성:
    • dockerD → containerD → runC 순으로 컨테이너 생성 요청 전달.
    • runC가 HostOS 커널을 사용해 컨테이너 실행.
  3. 라이프사이클 관리:
    • containerD가 컨테이너 실행, 중지, 삭제 등을 관리.
    • dockerD는 사용자와의 인터페이스를 유지.

5. Docker와 OS 커널 간 통신

Docker Engine은 HostOS 커널과의 통신을 통해 동작합니다.

  • 소켓 통신: Docker는 Unix 소켓이나 TCP 소켓을 사용하여 커널과 runC 간의 요청을 전달.
    • 소켓은 운영체제에서 제공하는 가볍고 빠른 데이터 전송 방식.
    • 이를 통해 runC가 커널과 상호작용하여 컨테이너를 생성하고 실행.

6. 정리

  • Docker Engine의 작동 방식은 dockerD → containerD → runC로 이어지는 계층적 구조를 가집니다.
  • 핵심 원칙은 HostOS 커널 공유를 통한 경량화와 격리된 실행 환경 제공입니다.
  • Docker Engine은 HostOS와의 통신을 소켓 기반으로 수행하며, 이를 통해 컨테이너를 효율적으로 생성, 관리, 삭제합니다.
질문 1. 커널에 있는 기술을 공유해서 쓴다는 원칙은 그대로 였다고 한다면,
그러면 애초에 커널에 기술이 다 있었다는 이야기가 되는거 아닌가?
 
맞아요! 커널에 기술이 이미 있었다는 말이 정확합니다. 컨테이너 기술은 새로운 개념이라기보다는, 커널에 이미 존재하던 기술을 조합하고 최적화하여 활용하는 방식입니다. 다만, 이 기술들을 컨테이너화라는 새로운 패러다임으로 체계적으로 정리하고 활용하게 된 것이 도커(Docker) 같은 컨테이너 엔진의 혁신입니다.

1. 커널에 이미 존재하던 기술

컨테이너가 사용하는 핵심 기술들은 대부분 운영체제 커널에서 오랫동안 존재해왔습니다. 이 기술들은 다음과 같은 기능을 제공합니다:

1) 네임스페이스(Namespaces)

  • 역할: 프로세스, 네트워크, 파일 시스템 등을 격리하여, 각 프로세스가 "독립적인 환경"에서 실행되는 것처럼 보이게 합니다.
  • 종류:
    • PID 네임스페이스: 프로세스 ID를 격리.
    • NET 네임스페이스: 네트워크 인터페이스 격리.
    • MNT 네임스페이스: 마운트된 파일 시스템 격리.
    • UTS 네임스페이스: 호스트 이름과 도메인 이름 격리.
    • IPC 네임스페이스: 프로세스 간 통신(IPC) 자원 격리.
    • USER 네임스페이스: 사용자 ID와 권한 격리.

2) Cgroups(Control Groups)

  • 역할: CPU, 메모리, 네트워크 대역폭 등 하드웨어 자원을 제한하고 관리.
  • 컨테이너별로 자원 사용량을 설정하여 과도한 리소스 사용을 방지.

3) Chroot

  • 역할: 프로세스의 루트 디렉토리를 변경하여 특정 디렉토리를 가상 환경의 루트로 인식하게 만듦.
  • 이 기술은 네임스페이스와 함께 파일 시스템 격리의 기반이 됨.

4) UnionFS

  • 역할: 여러 파일 시스템 계층을 하나로 결합하여, 읽기/쓰기 작업을 효율적으로 처리.
  • Docker 이미지의 **계층적 구조(layered structure)**를 구현하는 핵심 기술.

2. 그렇다면, 커널에 왜 이런 기술이 있었을까?

이 기술들은 컨테이너를 위해 새로 만들어진 것이 아니라, 운영체제의 일반적인 격리 및 자원 관리 요구를 충족하기 위해 개발되었습니다.

예시:

  1. 네임스페이스:
    • 멀티유저 환경에서 한 사용자의 프로세스가 다른 사용자 프로세스에 영향을 주지 않도록 격리하기 위해 개발.
  2. Cgroups:
    • 대형 서버에서 여러 애플리케이션이 실행될 때, 특정 애플리케이션이 자원을 독점하지 못하도록 제한하기 위해 개발.
  3. Chroot:
    • 특정 프로세스나 사용자가 제한된 디렉토리에서만 작업할 수 있도록 보안 목적에서 개발.

즉, 이 기술들은 원래 보안, 리소스 관리, 멀티태스킹 등의 목적을 위해 커널에 추가되었습니다.


3. 컨테이너 기술의 핵심은 무엇인가?

컨테이너 기술의 혁신은 커널에 이미 존재하던 기술을 조합하고, 개발자와 사용자가 쉽게 사용할 수 있도록 도구와 인터페이스를 제공한 것입니다.

Docker의 혁신:

  1. 자동화와 간소화:
    • Docker는 네임스페이스, Cgroups, Chroot 등을 직접 설정하지 않고도 사용할 수 있는 간단한 CLI(Command Line Interface)와 API를 제공.
    • 예: docker run 명령어 한 줄로 컨테이너 실행.
  2. 이미지 기반 배포:
    • 컨테이너 이미지를 사용해 일관된 실행 환경을 제공.
    • 이미지는 읽기 전용 계층읽기/쓰기 계층으로 나뉘어 있어 효율적.
  3. 확장성과 표준화:
    • Docker는 컨테이너 기술을 표준화하여 대규모 배포 및 관리가 가능하도록 설계.

4. 왜 초기에는 이런 기술이 널리 사용되지 않았을까?

  1. 복잡한 설정:
    • 초기의 네임스페이스나 Cgroups 같은 기술은 직접 설정하기 어렵고 번거로웠습니다.
    • Docker 이전에는 LXC(Linux Containers) 같은 툴이 있었지만, 사용하기 쉽지 않았습니다.
  2. 통합 부족:
    • 네임스페이스, Cgroups, Chroot 등은 서로 독립적인 기능으로 존재했기 때문에, 이들을 하나로 통합해 "컨테이너"라는 완성형 개념으로 사용하려는 노력이 부족했습니다.
  3. 개발 및 배포 요구 증가:
    • 클라우드 컴퓨팅과 DevOps 문화가 발전하면서, 일관된 실행 환경과 빠른 배포가 필수 요소로 자리 잡았고, 이로 인해 컨테이너 기술의 필요성이 커졌습니다.

5. 결론

  • 네, 커널에는 이미 컨테이너 기술의 기반이 되는 기능이 모두 있었습니다.
  • Docker와 같은 컨테이너 엔진은 이 기능들을 효율적이고 사용하기 쉽게 조합하여, 현대적인 컨테이너 생태계를 구축한 것입니다.
  • 이 과정에서 **"커널 기술을 공유한다"**는 원칙은 변하지 않았고, Docker는 이를 기반으로 발전했습니다.
질문 2. kernel 기술의 공유를 통해 컨테이너 생성을 지원한다고 하면,
애초에 kernel 이 컨테이너를 생성할 수 있다는 이야기인가?
 
좋은 질문이에요! 답변을 한 마디로 요약하면: 커널 자체가 직접 "컨테이너를 생성"하는 것은 아니지만, 컨테이너를 생성하고 실행하기 위한 핵심 기술(네임스페이스, cgroups 등)을 제공한다는 뜻입니다. 컨테이너는 커널의 이러한 기능을 활용하여 만들어지고 실행됩니다.

1. 커널은 컨테이너의 기반 기술을 제공한다

컨테이너는 독립된 실행 환경을 제공하기 위해 커널에 이미 존재하는 기술을 활용합니다.

  • 커널은 네임스페이스(namespaces), cgroups(control groups), Chroot, UnionFS 등 컨테이너를 구현하는 데 필요한 핵심 기능을 제공합니다.
  • 이러한 기술을 직접적으로 사용하고 조합하는 것은 Docker Engine과 같은 컨테이너 엔진(runC 포함)의 역할입니다.

2. 커널의 역할

커널은 아래와 같은 기능을 제공하여 컨테이너가 작동할 수 있도록 돕습니다:

(1) 네임스페이스 (Namespaces)

  • 커널은 프로세스, 네트워크, 파일 시스템, IPC, 사용자 등의 격리된 공간을 제공합니다.
  • 네임스페이스는 각 컨테이너가 독립된 시스템처럼 보이게 하는 핵심 기술입니다.
  • 예:
    • PID 네임스페이스: 컨테이너 내부의 프로세스는 컨테이너 외부의 프로세스를 볼 수 없음.
    • NET 네임스페이스: 컨테이너는 자체 네트워크 인터페이스를 가지며 다른 컨테이너와 분리됨.

(2) Cgroups (Control Groups)

  • 커널은 CPU, 메모리, 네트워크 대역폭 등 자원 사용량을 제한하고 관리하는 기능을 제공합니다.
  • 이를 통해 한 컨테이너가 과도한 자원을 사용하지 않도록 제어합니다.

(3) Chroot (파일 시스템 격리)

  • 프로세스의 루트 디렉토리를 특정 디렉토리로 제한하여, 컨테이너가 격리된 파일 시스템을 사용하도록 만듭니다.

(4) UnionFS (파일 계층 구조)

  • 커널은 읽기 전용 계층과 쓰기 가능한 계층을 결합하여, 컨테이너가 효율적인 파일 시스템을 사용할 수 있도록 합니다.
  • Docker 이미지의 계층적 구조가 UnionFS 기술에 기반을 둡니다.

3. 컨테이너 엔진의 역할

커널은 컨테이너의 기본 기능을 제공하지만, 실제로 컨테이너를 생성하고 관리하는 역할은 컨테이너 엔진이 수행합니다.

컨테이너 엔진의 역할:

  • Docker Engine과 같은 컨테이너 엔진은 커널 기술을 직접 호출하거나 조작할 수 있는 API를 사용하여 컨테이너를 생성합니다.
    • runC: 커널의 네임스페이스와 cgroups을 사용하여 컨테이너 생성.
    • containerD: 생성된 컨테이너의 라이프사이클 관리.
    • dockerD: 사용자 명령을 처리하고 containerD 및 runC에 작업을 전달.

컨테이너 생성 과정:

  1. Docker 명령 실행: 사용자가 CLI로 docker run 명령을 실행.
  2. Docker Daemon 작업 전달:
    • dockerD → containerD → runC로 명령이 전달.
  3. 커널 기능 호출:
    • runC는 네임스페이스, cgroups 등의 커널 기능을 호출하여 컨테이너 생성.
  4. 컨테이너 실행:
    • 커널이 프로세스를 격리하고 자원을 제한하며 컨테이너 환경을 제공합니다.

4. 왜 커널이 직접 컨테이너를 생성하지 않을까?

커널이 직접 컨테이너를 생성하지 않는 이유는 다음과 같습니다:

(1) 역할 분리

  • 커널은 저수준의 자원 관리와 격리 기술만을 제공하고, 고수준의 응용 프로그램 관리(컨테이너 생성, 삭제, 업데이트 등)는 컨테이너 엔진의 역할입니다.
  • 이 분리는 커널의 복잡성을 줄이고, 컨테이너 엔진이 다양한 요구사항을 유연하게 처리할 수 있도록 만듭니다.

(2) 사용자 편의성

  • 커널은 프로그래머가 직접 호출하기에는 너무 복잡합니다.
  • Docker Engine은 CLI(Command Line Interface)와 API를 제공하여 사용자가 커널의 저수준 기술을 신경 쓰지 않고 간편하게 컨테이너를 생성할 수 있게 해줍니다.

(3) 확장성과 생태계

  • 커널이 모든 기능을 처리하면 유연성이 떨어집니다.
    반면 Docker와 같은 컨테이너 엔진은 다양한 운영체제와 환경에서 동작하며, 추가 기능(네트워크, 볼륨 관리 등)을 쉽게 통합할 수 있습니다.

5. 결론

커널은 컨테이너를 구현하기 위한 핵심 기술(네임스페이스, cgroups 등)을 제공하지만, 컨테이너를 생성하는 직접적인 역할은 하지 않습니다.
컨테이너를 생성하고 관리하는 구체적인 작업은 Docker와 같은 컨테이너 엔진이 수행하며, 이는 커널 기능을 활용해 사용자가 쉽게 컨테이너를 생성할 수 있도록 합니다.

추가 질문이 있다면 언제든지 물어보세요! 😊

 
질문 3. 커널과의 통신을 할 때, 소켓 통신에 의해서 이루어진다고 했는데, 소켓 통신이 뭘까,
그리고 어떤 내용을 주고 받는걸까? 어떤 내용을 담고 있는거야?
그리고 그건 어떤 방식으로 담겨져있는거야?
 
소켓 통신두 컴퓨터 프로세스 간의 데이터 교환을 위한 기술입니다. 로컬 머신 내의 프로세스 간 통신(IPC: Inter-Process Communication)이나 네트워크를 통한 원격 통신에서도 사용됩니다. Docker Engine이 HostOS의 커널과 통신할 때도 이 소켓 통신이 사용됩니다.

1. 소켓 통신이란?

  • 소켓(Socket)은 **통신을 위한 끝점(Endpoint)**을 의미하며, 데이터를 주고받는 인터페이스를 제공합니다.
  • 두 프로세스가 데이터를 교환하려면 각각 소켓을 생성하고 서로 연결해야 합니다.
  • 소켓 통신의 특징:
    • 양방향: 데이터를 송수신할 수 있음.
    • 추상화: 로컬이나 네트워크 상에서 동일한 방식으로 동작.

2. Docker에서 소켓 통신의 역할

Docker에서 소켓 통신은 다음과 같은 상황에서 사용됩니다:

  1. Docker CLI ↔ Docker Daemon:
    • Docker CLI 명령어(docker run 등)가 소켓을 통해 Docker Daemon(dockerD)에 전달됩니다.
    • 기본적으로 Docker는 Unix 도메인 소켓(/var/run/docker.sock)을 사용합니다.
  2. Docker Daemon ↔ HostOS 커널:
    • Docker Daemon은 containerD와 runC를 통해 커널의 기능(네임스페이스, cgroups 등)을 요청.
    • 이때 커널과의 상호작용도 소켓 통신으로 이루어집니다.

3. 어떤 내용을 주고받는가?

Docker에서 소켓 통신으로 주고받는 데이터는 명령, 상태 정보, 요청 및 응답입니다.

(1) Docker CLI ↔ Docker Daemon:

  • CLI 명령어가 전달되는 경우:
    • 사용자가 실행한 명령(docker run, docker stop 등).
    • 예: docker run ubuntu → Docker Daemon은 이 명령을 해석해 컨테이너 생성 과정을 시작.

(2) Docker Daemon ↔ containerD ↔ runC:

  • 컨테이너 생성 요청:
    • 특정 컨테이너 이미지, 실행할 명령, 리소스 제한 정보 등이 포함됨.
    • 예: "Ubuntu 이미지를 기반으로 컨테이너를 생성하고 CPU 사용량을 제한하라."
  • 상태 정보 전달:
    • 현재 컨테이너 상태(실행 중, 중지됨 등).
  • 리소스 사용량 보고:
    • 각 컨테이너의 메모리, CPU, 네트워크 사용량 통계.

(3) Docker Daemon ↔ HostOS 커널:

  • 커널에 직접 요청하는 작업:
    • 네임스페이스 생성: 프로세스 격리 요청.
    • cgroups 설정: 특정 컨테이너의 CPU와 메모리 제한 설정.
    • 네트워크 설정: 가상 네트워크 인터페이스 생성 및 연결.

4. 데이터가 어떤 방식으로 담겨있는가?

소켓 통신에서 주고받는 데이터는 일반적으로 JSON 형식 또는 바이너리 데이터로 인코딩됩니다.

(1) JSON 형식:

  • Docker Daemon은 사용자의 명령을 JSON 데이터로 변환해 내부로 전달.
  • JSON은 사람이 읽을 수 있는 텍스트 형식으로, 구조화된 데이터를 표현하는 데 적합.
  • 예:
{
  "action": "run",
  "image": "ubuntu",
  "cpu_limit": "2 cores",
  "memory_limit": "512MB"
}

(2) 바이너리 데이터:

  • 성능이 중요한 상황에서는 JSON 대신 바이너리 데이터를 사용해 더 빠른 데이터 전송과 처리를 수행.
  • 컨테이너의 네트워크 패킷 데이터 등은 바이너리로 처리됨.

5. 소켓 통신의 작동 과정

Docker에서 소켓 통신의 흐름은 다음과 같습니다:

예: docker run ubuntu 명령 실행 시

  1. Docker CLI:
    • 명령이 JSON 형태로 변환되어 /var/run/docker.sock 소켓을 통해 Docker Daemon에 전달.
  2. Docker Daemon:
    • 명령을 해석하고, 컨테이너 생성 작업을 수행하기 위해 containerD와 runC를 호출.
    • containerD와 runC는 커널과 소켓 통신을 통해 필요한 작업을 요청.
  3. HostOS 커널:
    • 요청받은 작업(네임스페이스 생성, cgroups 설정 등)을 수행하고 결과를 반환.
  4. 응답 전달:
    • 커널 → runC → containerD → Docker Daemon → Docker CLI로 결과가 반환되어 사용자에게 표시.

6. Docker 소켓 통신에서의 보안

소켓 통신은 강력하지만, 보안 위험도 있습니다.

  • Docker가 사용하는 /var/run/docker.sock은 Unix 도메인 소켓으로, 이 파일에 접근할 수 있는 사용자는 Docker Daemon에 대한 완전한 권한을 가집니다.
  • 이를 악용하면 시스템 전체에 악영향을 미칠 수 있으므로, 적절한 권한 관리가 중요합니다.

7. 정리

  • 소켓 통신은 Docker Engine과 HostOS 커널, 또는 사용자와 Docker Daemon 간의 데이터 교환을 가능하게 하는 핵심 메커니즘입니다.
  • 주고받는 데이터는 주로 명령, 상태 정보, 요청 및 응답으로 구성되며, JSON 또는 바이너리 형식으로 전달됩니다.
  • Docker 소켓 통신은 효율적이고 가벼운 방식으로, Docker 생태계를 효과적으로 지원합니다.