4 분 소요

컨테이너는 필요한 라이브러리와 의존성 패키지를 포함하여 애플리케이션을 간편하게 빌드하고 배포할 수 있는 기술입니다. 컨테이너 런타임이 제공되기만 하면 OS나 환경에 상관없이 단일 애플리케이션을 배포할 수 있죠. 컨테이너 기술의 등장만으로도 개발과 배포 과정은 훨씬 편리해졌습니다. 그리고 여기서 쿠버네티스라는 컨테이너 오케스트레이션 도구가 등장하고, 파드라는 개념이 등장하게 되었죠.

컨테이너만으로도 애플리케이션 생애 주기를 효율적으로 운영할 수 있는데 파드가 필요한 이유는 무엇일까요? 파드는 어떻게 동일한 IP를 갖고 내부 컨테이너들은 서로 통신할 수 있을까요? 이를 이해하기 위해서는 먼저 MSA가 무엇인지 먼저 살펴봐야 할 것 같습니다.

MSA란?Permalink

loading-ag-1083

MSA는 MicroService Architecture로 애플리케이션을 구성하는 모듈(기능)들이 각기 따로 관리 및 배포되고 서로 API를 통해서만 데이터를 주고받을 수 있는 구조를 의미합니다. 반면 Monolitic Architecture는 MSA의 반대 개념으로, 애플리케이션의 모든 기능들이 하나의 애플리케이션으로 통합되어 관리 및 배포되는 개념입니다.

더욱 이해하기 쉽게 건설업체를 서비스 애플리케이션이라 가정하고 각각의 아키텍쳐를 설명해 보겠습니다.

모놀리식 아키텍쳐Permalink

loading-ag-1862

건설업체에는 중장비 부서와 인력 부서, 회계 부서 등 다양한 부서가 존재합니다. 각 부서들과 직원들은 하나의 사무실 안에서 업무를 진행하고 서로 커뮤니케이션을 하게 되죠.

하지만 회사가 조금씩 성장함에 따라 부서별로 채용 인원이 늘어나면 어떻게 될까요? 좁은 사무실에 직원들을 모두 수용하기 어렵고 사무실 내 비품과 업무용 장비 사용량이 늘어나게 됩니다. 심지어는 필요한 물품이 부족할 수도 있죠. 이 뿐만 아니라 각 부서 내 업무 대화가 불필요한 내용까지 타 부서에 들릴 수 있고, 대화 소리가 서로 상쇄되어 부서 내 커뮤니케이션이 원활하지 않게 될 수 있습니다.

마이크로서비스 아키텍쳐Permalink

loading-ag-364

마이크로서비스 아키텍쳐는 그림과 같이 각 부서가 하나의 사무실에 격리되고, 회사가 성장함에 따라 다른 본부 건물에 배치되어 부서와 사무실을 확장할 수 있는 구조에 비유할 수 있습니다. 각 부서별로 독립된 사무실 공간에서 업무 대화가 외부로 유출되거나 타 부서의 대화 소리에 방해를 받는 일이 없어지게 되죠. 타 부서와의 협업이 필요할 경우 타 부서의 사무실에 직접 방문해야 합니다. 그리고 부서 내 인원이 늘어나면 사무실의 수를 확장할 수 있습니다. 본부 단위로 건물을 두게 되면 사무실을 더욱 쉽게 확장할 수 있고요.

이렇듯 마이크로서비스 아키텍쳐에서는 애플리케이션을 독립된 기능별로 배포하고 손쉽게 확장할 수 있다는 장점이 있습니다. 때문에 소규모 프로젝트에서는 모든 기능을 한 번에 배포하고 운영할 수 있는 모놀리식 아키텍쳐에서 시작하는 것이 좋고, 대용량 트래픽을 다루고 많은 기능을 구현하는 프로젝트는 마이크로서비스 아키텍쳐를 설계하고 운영하는 것이 좋습니다.

파드의 원리와 목적Permalink

파드는 쿠버네티스에서 하나 이상의 컨테이너를 품고 동작시키는 최소 단위입니다. 그렇다면 왜 파드가 생기게 되었을까요? MSA 환경 특성 상 애플리케이션을 구성하는 여러 컨테이너를 규칙적으로 확장 가능한 그룹으로 묶어서 관리하는 것이 효과적입니다. 그래서 서로 의존 관계에 있는 컨테이너들을 동일한 로컬 네트워크와 시스템 자원을 공유하는 네임스페이스에 격리한 파드가 생겨난 것이죠.

Docker와 같은 기존의 컨테이너 런타임에서는 하나의 컨테이너가 하나의 네임스페이스로 격리되는 것이 일반적인데요. 그렇다면 쿠버네티스에서 파드는 어떻게 같은 네임스페이스에 두개 이상의 컨테이너를 수용할 수 있을까요? 바로 파드가 생성될 때 가장 먼저 생성되는 Pause Container가 이러한 역할을 하게 됩니다.

Pause containerPermalink

Pause Container는 pause() 시스템 콜을 호출을 통해 말 그대로 멈춰 있는 상태의 컨테이너로, 컨테이너들을 같은 네임스페이스 안에 유지하는 역할을 합니다. 때문에 Infra Container라는 이름으로 불리기도 하죠. Pause Container가 파드 내 실제 컨테이너들과 공유하는 네임스페이스는 다음 3가지가 있습니다.

  • 네트워크 네임스페이스 - 같은 파드 안의 컨테이너가 동일한 로컬 네트워크를 갖고 서로 통신할 수 있습니다.

  • IPC 네임스페이스 - Shared Memory나 메시지 큐를 통해 파드 내 컨테이너들의 프로세스 간 통신을 지원합니다.

  • UTS 네임스페이스 - 같은 파드 안의 컨테이너가 동일한 Hostname을 갖도록 합니다.

이를 잘 확인할 수 있도록 두 개의 컨테이너를 가진 sample 파드를 배치하고 살펴 보도록 하겠습니다.

spec:
  containers:
  - image: busybox
    name: showing-time
    command: ["sh", "-c", "while true; do echo $(date +%Y/%m/%d:%H:%M) > /usr/share/nginx/html/index.html; sleep 60; done"]
    volumeMounts:
      - name: data
        mountPath: /usr/share/nginx/html
  - image: nginx
    name: web
    volumeMounts:
      - name: data
        mountPath: /usr/share/nginx/html
  volumes:

위의 파드는 현재 날짜와 시간을 메인 페이지로 출력하는 busybox 컨테이너와 이를 웹으로 보여주는 nginx 컨테이너로 구성됩니다. 이를 프로세스 트리로 살펴보면

pstree -alnp

loading-ag-2380

pause 프로세스 밑에 shell와 nginx 프로세스로 분기되어 있는 것을 확인할 수 있습니다. 이들은 각각 다른 컨테이너로 분리되어 있죠. 이들의 네임스페이스 구성을 살펴보면

lsns -p [PID]

loading-ag-2384

네트워크, IPC, UTS 네임스페이스는 동일한 PID 아래 서로 공유되고 있고, pid, mnt 네임스페이스는 서로 독립적으로 취하는 것을 확인할 수 있습니다.

loading-ag-2388

신기한 점을 찾아볼 수 있는데요. cgroup과 user 네임스페이스 구성이 컨테이너나 호스트 프로세스 모두 동일한 것을 확인할 수 있습니다. cgroup은 시스템 자원을 프로세스에 할당하고 제한하는 리눅스 커널의 기능이고, user 네임스페이스는 각 프로세스 별 사용자 ID를 격리하는 네임스페이스인데요. 이는 파드 내 컨테이너들이 호스트와 동일한 사용자 ID로 실행되고 호스트와 동일한 cgroup 기능을 통해 시스템 자원 사용량이 관리됨을 의미합니다.

PID 네임스페이스 공유Permalink

spec:
  shareProcessNamespace: true
  containers:

이번에는 다른 설정 하나를 추가하여 파드를 실행해 보겠습니다. shareProcessNamespace: true 설정을 추가하면 Pause Container를 포함한 파드 내 컨테이너들이 동일한 PID 네임스페이스를 공유하고 서로의 프로세스를 확인할 수 있습니다. 이러한 설정으로 파드를 실행하면

loading-ag-3112

loading-ag-3118

PID 네임스페이스도 동일한 PID 아래 공유하는 것을 확인할 수 있고 한 쪽 컨테이너에서 다른 컨테이너의 프로세스까지 모두 확인할 수 있습니다.

PID 네임스페이스를 공유하도록 하는 설정은 보안 관점에서 권장되지 않는 설정입니다. 파드 내 컨테이너가 서로의 PID를 참조해야 하거나 하는 상황에서만 사용하고 SecurityContext를 적절하게 설정하여 서로의 프로세스를 함부로 kill할 수 없도록 해야 합니다.

컨테이너 런타임(CRI)의 역할Permalink

파드의 Pause Container가 공유 네임스페이스를 유지하는 역할을 한다고 했는데요. 파드 내 컨테이너가 공유하는 네임스페이스를 생성하는 역할을 바로 컨테이너 런타임(CRI)이 수행합니다. API 서버로부터 파드 실행 명령을 받은 kubelet이 CRI를 호출하고, CRI는 sandbox를 생성하는 함수를 통해 네트워크 네임스페이스를 생성하고 CNI 플러그인을 사용해 IP를 할당합니다. 그리고 비로소 애플리케이션 컨테이너가 네임스페이스 안에서 실행되게 되죠.

loading-ag-3237

sudo crictl ps | grep web

loading-ag-3241

파드를 실행하고 containerd의 systemd journal을 살펴보면 sandbox내에 컨테이너를 생성한다는 메시지를 확인할 수 있는데요. 바로 이 sandbox가 네임스페이스 기반으로 격리된 파드라는 공간을 지칭하는 말이 되겠네요. 실제로 뒤에 나오는 sandbox ID는 컨테이너 런타임에서 확인한 파드 ID와 동일한 것을 확인할 수 있습니다.

loading-ag-3454

그리고 초기 클러스터를 구성하면 워커 노드의 kubelet은 --pod-infra-container-image설정을 통해 Pause Container 이미지를 pull할 레지스트리가 설정되어 있는 것을 확인할 수 있습니다. 직접 구성한 쿠버네티스 클러스터에는 k8s 자체 레지스트리가 설정되어 있고, AWS EKS와 같은 퍼블릭 클라우드 관리형 쿠버네티스 클러스터에는 CSP 자체 레지스트리가 설정되어 있습니다.

마치며Permalink

이렇게 파드가 여러 개의 컨테이너를 담고 MSA 환경에 활용될 수 있는 원리를 살펴 봤는데요. 정리하자면 파드 실행을 명령하는 API 서버부터 명령을 받고 파드를 실행하는 kubelet, sandbox를 생성하여 공유 네임스페이스에 컨테이너를 담는 CRI, 컨테이너 프로세스의 시스템 자원 할당을 관리하는 cgroup, 네트워크 네임스페이스에 IP를 부여하는 CNI 플러그인, 네트워크/IPC/UTS 네임스페이스를 유지하는 Pause Container에 이르기까지, 파드 하나를 실행하는데 정말 많은 컴포넌트들이 열심히 기여하는 것을 볼 수 있습니다. 항상 아무렇지 않게 배포하고 scale out하는 파드에 대해 이렇게 깊게 파헤쳐보니 쿠버네티스라는 기술에 대해 다시 한 번 경이로움을 느끼게 되는 것 같습니다.

댓글남기기