여기서는 쿠버네티스의 팟(파드, pod)에 대해서 이야기해 보자.

Kubernetes 최소 작업 단위

컨테이너를 실행시키는 Docker에 대해서 생각해 보자. 도커는 컨테이너를 관리하는 툴이며 컨테이너는 하나의 프로세스를 실행시키는 격리된 하나의 공간이다. 따라서 도커에서는 컨테이너가 가장 작은 최소 작업 단위가 되며, 컨테이너의 실행-종료를 통해 원하는 작업을 컨트롤할 수 있다.

컨테이너에서는 프로세스가 몇 개가 실행되어야 하는가? 강제하진 않지만 우리는 컨테이너 하나에서는 프로세스 하나가 실행되기를 기대한다. 그 이유는 하나의 기능만이 온전히 컨테이너에서 실행되는 것이 여러모로 유리하기 때문이다. 예를 들어 어떤 서비스를 제공하기 위해 APM(apache, php, mysql)이 모두 설치된 컨테이너를 만든 경우, 이 컨테이너를 어떻게 서비스 수평 확장을 시킬 것인가가 문제가 된다. 이 외에도 여러 가지 이유가 있지만 어쨌든 그래서 컨테이너 하나에서는 온전히 하나의 프로세스, 기능만을 제공하도록 작성하게 된다.

쿠버네티스의 의의는 결국 컨테이너를 더 잘 관리해 보자이므로, 쿠버네티스 위의 어플리케이션도 결국 컨테이너 위에서 돌아갈 것이다. 하지만 문제는 우리는 쿠버네티스 위에서 어떤 어플리케이션을 배포하려고 하는 것이지 프로세스를 배포하려고 하는 것이 아니기 때문에 컨테이너의 개념을 그대로 적용하는 경우 많은 어려움이 발생할 것이다.

팟은 이런 단점을 해결하기 위해 만들어진 쿠버네티스 위의 최소 작업 단위이다. 위 문제점을 어떻게 멋지게 해결했는지 확인해 보자.

팟의 구조

팟은 컨테이너 하나를 관리하는 것이 아닌 여러 개의 컨테이너를 관리하는 개념이다. 이게 도커에서의 최소 작업 단위와는 가장 결정적인 차이점인데, 일반적으로 쿠버네티스를 사용할 경우 팟은 컨테이너 하나만을 컨트롤하므로 이 이야기가 언뜻 와닿지 않을 수 있다. 작업을 실행하는 최소 단위가 팟인데, 이 팟에 컨테이너가 한 개 이상일 수도 있다고?

쿠버네티스에서 소개하는 이런 예를 보면 다음과 같다. 웹 상에서 유저에게 컨텐츠를 제공하는 팟이 있을 경우, 이 팟 내의 다른 컨테이너가 팟의 볼륨에 컨텐츠를 업데이트 할 수 있는 케이스가 바로 이런 것이다.

Pod

팟의 관점에서 본다면 이 컨테이너 두 개는 서로 같은 볼륨을 마운트하고 있다. 이건 도커에서도 있을 수 있는 모델이니까 괜찮을 수 있지만, 특이한 점은 이 두 컨테이너가 같은 IP를 가진다는 것이다. 더 자세히 말하면, 팟 안의 A, B 컨테이너는 같은 localhost를 사용해서 서로에게 통신할 수 있다는 것이다. 따라서 도커 컨테이너처럼 기능 단위로 컨테이너를 분리하지만, 쿠버네티스 입장에서는 여전히 이게 최소 작업 단위가 되는 것이다.

Template: Pod

팟은 다음과 같은 YAML 포맷 파일을 사용하여 쿠버네티스 위에 전개할 수 있다.

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
spec:
  containers:
  - name: myapp-container
    image: busybox
    command: ['sh', '-c', 'echo 안녕하세요 쿠버네티스! && sleep 3600']

템플릿의 형태를 외우는 것은 매우 불편하지만 하나 눈여겨볼 만한 것이 있는데, spec.containers이다. 팟은 컨테이너가 하나가 아니므로 spec.container가 아니라 spec.containers인 것.

팟의 의미

결국 팟이란 것은 컨테이너 여러 개를 편리하게 묶은 단위라고 볼 수 있다. 컨테이너는 프로세스 한 개를 배포하는 단위이고 팟 입장에서 보면 자신의 공간 내에서 여러 프로세스가 배포된 상태인 것이다.

그런데 잘 생각해보면 이게 결국 우리가 사용하는 일반적인 서버의 개념과 같다. 운영체제를 빼고 본다면, 서버에 필요한 어플리케이션이 배포된 상태는 팟의 구성과 같다. 서버 한 개에 apache2, php, mysql을 설치해서 운영중인 것과 팟 안에 해당 컨테이너 3개가 떠있는 것은 프로세스 관점에서 보면 정확히 같다는 것이다.

그래서 결국 팟이란 것은 서버 하나를 나타낸다고 봐도 될 것 같다. 운영체제가 없으니 조금 더 경량화됐다고 해야할까? 위에서도 잠깐 적었지만 같은 팟 안에 있는 컨테이너끼리는 localhost로 통신할 수 있다.

# pod.yaml

  containers:
  - image: nginx
    imagePullPolicy: Always
    name: 1st
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: html
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: default-token-5g2zn
      readOnly: true
  - image: nginx
    imagePullPolicy: Always
    name: 2nd
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: html
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: default-token-5g2zn
      readOnly: true

같은 팟 안에서 같은 nginx 이미지를 사용해서 컨테이너를 생성한다면? 디폴트 포트 80을 한쪽에서 미리 차지하면 다른 컨테이너가 차지하지 못하므로 이 팟은 무한히 에러가 발생하게 된다.

$ kubectl logs -f mc2 2nd
...
...
...
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
2020/12/09 03:35:21 [emerg] 1#1: bind() to [::]:80 failed (98: Address already in use)
nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
2020/12/09 03:35:21 [emerg] 1#1: bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
2020/12/09 03:35:21 [emerg] 1#1: bind() to [::]:80 failed (98: Address already in use)
nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
...
...

localhost로 통신할 수 있다는 의미는 서로 같은 네트워크에 있다는 이야기가 되고, 그러므로 두 개의 프로세스가 같은 포트를 가져갈 수 없다는 것이다.