이전 글에서는 Kubernetes 프로젝트에서 가장 중요한 Pod 개념에 대해 자세히 소개했습니다. 오늘 글에서는 Pod 객체에 대한 더 많은 세부 정보를 공유하겠습니다.
Pod와 가상 머신
지금쯤이면 Kubernetes 프로젝트에서 가장 작은 오케스트레이션 단위는 컨테이너가 아니라 Pod라는 점이 분명히 이해되었을 것입니다. 이 설계는 API 객체에도 반영되어, 컨테이너는 Pod 속성 내의 일반적인 필드에 불과하게 되었습니다. 당연히 어떤 속성이 Pod 객체에 속하고 어떤 속성이 Container에 속하는지 의문이 생깁니다.
Pod는 기존 배포 환경에서 “가상 머신” 의 역할을 합니다. 이러한 설계는 기존 환경(가상 머신 환경)에서 Kubernetes(컨테이너 환경)로의 전환을 더 원활하게 하기 위한 것입니다.
Pod를 기존 환경의 “머신” 으로 보고, 컨테이너를 이 “머신” 에서 실행되는 “사용자 프로그램” 으로 생각한다면, Pod 객체 설계의 여러 측면을 훨씬 쉽게 이해할 수 있습니다.
예를 들어, 스케줄링, 네트워킹, 스토리지, 보안과 관련된 속성은 기본적으로 Pod 수준입니다. 이러한 속성들의 공통점은 “머신” 전체를 설명할 뿐 그 안에서 실행되는 “프로그램” 을 설명하지 않는다는 것입니다.
예를 들어, “머신” 의 네트워크 카드 구성(즉, Pod의 네트워크 정의), “머신” 의 디스크 구성(즉, Pod의 스토리지 정의), “머신” 의 방화벽 구성(즉, Pod의 보안 정의)이 이에 해당합니다. 이 “머신” 이 어떤 서버에서 실행될지(즉, Pod의 스케줄링)는 말할 것도 없습니다.
Kubernetes YAML
Kubernetes에서는 선언형 파일을 통해 Pod 리소스를 정의합니다.
NodeSelector: 사용자가 Pod를 Node에 바인딩할 수 있게 해주는 필드입니다. 아래와 같이 사용합니다.
apiVersion: v1
kind: Pod
...
spec:
nodeSelector:
disktype: ssd
이러한 구성은 이 Pod가 “disktype: ssd” 레이블이 있는 노드에서만 실행될 수 있음을 의미하며, 그렇지 않으면 스케줄링에 실패합니다.
NodeName: Pod의 이 필드에 값이 할당되면 Kubernetes 프로젝트는 해당 Pod가 이미 스케줄링되었다고 간주하며, 스케줄링 결과는 할당된 노드 이름입니다. 따라서 이 필드는 일반적으로 스케줄러에 의해 설정되지만, 사용자가 설정하여 스케줄러를 “속이는” 것도 가능합니다. 단, 이러한 방식은 보통 테스트나 디버깅 시에만 사용됩니다.
HostAliases: Pod의 hosts 파일(예: /etc/hosts) 내용을 정의합니다. 아래와 같이 사용합니다.
apiVersion: v1
kind: Pod
...
spec:
hostAliases:
- ip: "10.1.2.3"
hostnames:
- "foo.remote"
- "bar.remote"
...
Kubernetes 프로젝트에서 hosts 파일 내용을 설정하려면 반드시 이 방법을 사용해야 합니다. 그렇지 않고 직접 hosts 파일을 수정하면 Pod가 삭제되고 다시 생성될 때 kubelet이 수정된 내용을 자동으로 덮어씁니다.
위와 같은 “머신” 관련 구성 외에도, 컨테이너의 Linux Namespace와 관련된 모든 속성도 Pod 수준에 있다는 것을 알 수 있습니다. 이 또한 이해하기 쉽습니다. Pod 설계는 그 안의 컨테이너들이 가능한 많은 Linux Namespace를 공유하도록 하되, 필요한 격리 및 제한 기능만 유지하기 위함입니다. 이렇게 함으로써 Pod가 시뮬레이션하는 효과는 가상 머신 내 프로그램 간의 관계와 매우 유사합니다.
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
containers:
- name: lifecycle-demo-container
image: nginx
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]
preStop:
exec:
command: ["/usr/sbin/nginx","-s","quit"]
이것은 Kubernetes 공식 문서에 있는 Pod YAML 파일입니다. 매우 간단하며, nginx 이미지를 사용하는 컨테이너 하나를 정의합니다. 그러나 이 YAML 파일의 containers 섹션에서 컨테이너에 postStart와 preStop 파라미터가 설정된 것을 볼 수 있습니다.
이것이 무엇을 의미할까요?
먼저 postStart는 컨테이너가 시작된 직후에 실행되는 작업을 의미합니다. postStart에 정의된 작업은 Docker 컨테이너의 ENTRYPOINT 이후에 실행되지만, 순서가 엄격하게 보장되지는 않는다는 점을 알아야 합니다. 즉, postStart가 시작될 때 ENTRYPOINT가 아직 종료되지 않았을 수도 있습니다.
물론 postStart 실행이 시간 초과되거나 오류가 발생하면 Kubernetes는 해당 Pod의 Events에 컨테이너 시작 실패 오류 메시지를 보고하고, Pod도 실패 상태가 됩니다.
마찬가지로 preStop의 타이밍은 컨테이너가 종료되기 전(예: SIGKILL 신호를 수신할 때)입니다. 중요한 점은 preStop 작업의 실행이 동기적이라는 것입니다. 따라서 정의된 작업이 완료될 때까지 현재 컨테이너 종료 프로세스를 차단하며, 이는 postStart와 다릅니다.
이 예제에서 컨테이너가 성공적으로 시작된 후 /usr/share/message에 “인사 메시지” 를 씁니다(postStart에 정의된 작업). 그리고 이 컨테이너가 삭제되기 전에 먼저 nginx 종료 명령을 호출하여(preStop에 정의된 작업) 컨테이너의 “정상 종료(graceful shutdown)” 를 실현합니다.
Pod와 그 Container 섹션의 주요 필드에 익숙해진 후, Kubernetes에서 이러한 Pod 객체의 생명주기를 공유하겠습니다.
Pod 생명주기의 변화는 주로 Pod API 객체의 Status 부분에 반영되며, 이는 Metadata와 Spec 다음으로 세 번째 중요한 필드입니다. 그중 pod.status.phase는 Pod의 현재 상태를 나타내며, 가능한 조건은 다음과 같습니다.
-
Pending. 이 상태는 Pod의 YAML 파일이 Kubernetes에 제출되었고 API 객체가 생성되어 Etcd에 저장되었음을 의미합니다. 그러나 이 Pod의 일부 컨테이너가 어떤 이유로 원활하게 생성되지 못하고 있습니다. 예를 들어 스케줄링 실패가 있습니다.
-
Running. 이 상태에서 Pod는 성공적으로 스케줄링되어 특정 노드에 바인딩되었습니다. 포함된 모든 컨테이너가 성공적으로 생성되었으며, 현재 적어도 하나가 실행 중입니다.
-
Succeeded. 이 상태는 Pod 내 모든 컨테이너가 실행을 완료하고 종료되었음을 의미합니다. 이 상황은 일회성 작업을 실행할 때 가장 흔히 발생합니다.
-
Failed. 이 상태에서 Pod 내 적어도 하나의 컨테이너가 비정상 상태(0이 아닌 반환 코드)로 종료되었습니다. 이 상태가 나타나면 컨테이너 내 애플리케이션을 디버깅하는 방법을 파악해야 합니다. 예를 들어 Pod의 Events와 로그를 확인합니다.
-
Unknown. 이것은 비정상 상태로, kubelet이 Pod의 상태를 kube-apiserver에 계속 보고할 수 없음을 나타냅니다. 이는 마스터와 Kubelet 간의 통신 문제 때문일 가능성이 높습니다.
또한 Pod 객체의 Status 필드는 Conditions 집합으로 더 세분화될 수 있습니다. 이러한 세부 상태 값에는 PodScheduled, Ready, Initialized, Unschedulable이 포함됩니다. 이들은 주로 현재 Status의 구체적인 이유를 설명하는 데 사용됩니다.
예를 들어 Pod의 현재 Status가 Pending이고 해당 Condition이 Unschedulable이면 스케줄링에 문제가 있음을 의미합니다.
그중 Ready 하위 상태는 특히 주목할 만합니다. 이는 Pod가 정상적으로 시작되었을 뿐만 아니라(Running 상태) 외부에 서비스를 제공할 준비가 되었음을 의미합니다. 이 두 가지(Running과 Ready)는 차이가 있으므로 잘 생각해보시기 바랍니다.
Pod의 이러한 상태 메시지는 애플리케이션의 실행 상태를 판단하는 중요한 기준입니다. 특히 Pod가 “Running” 이 아닌 상태에 진입했을 때는 신속하게 대응하여 표시된 비정상 상황에 따라 추적 및 위치 파악을 시작해야 하며, 당황하여 문서를 찾아보지 않아야 합니다.
요약
오늘 글에서는 Pod API 객체를 자세히 설명하고, Pod의 핵심 사용 방법을 소개했으며, 필드 측면에서 Pod와 Container의 유사점과 차이점을 분석했습니다. 이 설명이 Pod YAML의 핵심 필드와 그 정확한 의미를 더 잘 이해하고 기억하는 데 도움이 되기를 바랍니다.
사실 Pod API 객체는 전체 Kubernetes 시스템에서 가장 핵심적인 개념이며, 앞으로 설명할 컨트롤러에서도 사용됩니다.
