Kubernetes에서 가장 기본적인 배포 단위이자 핵심 개념은 바로 Pod(파드)이다. Pod는 Kubernetes 클러스터에서 실행되는 애플리케이션의 단일 인스턴스를 나타내며, 하나 이상의 컨테이너로 구성될 수 있다
Pod의 기본 개념
Cluster 내에 여러 Node가 있고 각 Node 안에는 Pod들이 존재하며, Pod 안에는 Container들이 포함되어 있다
- 가장 작은 배포 단위: Pod는 Kubernetes에서 생성, 관리 및 배포할 수 있는 가장 작은 컴퓨팅 단위이다
- 컨테이너 묶음: Pod는 하나 이상의 컨테이너를 포함시킬 수 있다. 일반적으로는 단일 컨테이너로 구성되는 경우가 많지만, 특정 목적을 위해 여러 컨테이너를 함께 실행할 때 사용된다
- 공유 네트워크 및 스토리지: Pod 내의 모든 컨테이너는 동일한 네트워크 네임스페이스를 공유한다. 이는 즉, Pod 내의 모든 컨테이너가 동일한 IP 주소와 포트 공간을 공유하며, localhost를 통해 서로 통신할 수 있다는 의미이다. 또한, 스토리지를 공유하도록 설정할 수도 있다
- Pod는 가상 머신이 아니다: Pod 자체는 가상 머신(VM)과 같은 독립적인 운영 체제를 포함하지 않는다. Pod는 컨테이너들을 묶어주고 네트워크 및 자원을 공유하게 하는 논리적인 추상화 계층이다. 컨테이너는 각자 자신만의 운영체제(주로 Linux) 위에서 애플리케이션을 실행한다
다중 컨테이너 Pod 설계 패턴
대부분의 경우 하나의 Pod에 하나의 컨테이너만 배포하지만, 다음과 같은 시나리오에서는 여러 컨테이너를 하나의 Pod에 함께 배치하는 것이 효율적이다. 이러한 컨테이너들을 사이드카(Sidecar) 컨테이너라고 부르기도 한다
- 밀접한 통신이 필요한 경우: 두 컨테이너가 매우 빈번하고 긴밀하게 통신해야 할 때 유용하다. 동일한 Pod 내에서는 localhost를 통해 통신하므로 네트워크 지연이 거의 없어 통신 속도가 매우 빠르다
- 예시: 메인 애플리케이션 컨테이너(예: 스프링 서버)와 해당 애플리케이션의 로그를 주기적으로 수집하여 외부 시스템으로 전송하는 로그 수집기 컨테이너가 함께 실행되는 경우, 이들은 밀리초 단위로 데이터를 주고 받을 수 있어 효율적이다
- 헬스 체크 또는 모니터링: 메인 애플리케이션 컨테이너의 상태를 주기적으로 확인하거나 특정 지표를 수집하는 모니터링 컨테이너가 필요한 경우
- 설정 관리 또는 데이터 동기화: 메인 컨테이너가 실행되기 전에 필요한 설정을 준비하거나, 특정 데이터를 주기적으로 동기화하는 보조 컨테이너가 있을 수 있다
Pod 설계 시 주의 사항
Redis와 같은 서드파티 데이터베이스나 캐싱 솔루션은 일반적으로 메인 애플리케이션 컨테이너와 동일한 Pod에 묶지 않는다. 그 이유는 다음과 같다
- 확장성 및 가용성: Redis와 같은 서비스는 자체적으로 스케일링되고 고가용성을 유지해야 한다. 애플리케이션 Pod와 Redis Pod를 분리하면 각각 독립적으로 확장하거나 장애 발생 시 복구할 수 있다
- 상태 비저장성 유지: Kubernetes에서 애플리케이션 Pod는 상태 비저장(stateless)으로 설계하는 것이 권장된다. Redis가 애플리케이션 Pod 내에 있다면, Pod가 재생성될 때 Redis 데이터가 유실될 위험이 있다
- 공통 리소스: Redis는 여러 애플리케이션 Pod에서 공유하는 공통 캐시나 세션 저장소로 사용되는 경우가 많다. 이때 Redis를 별도의 Pod로 구성하고 모든 애플리케이션 Pod가 해당 Redis Pod와 통신하도록 하는 것이 일반적인 아키텍처이다. 이렇게 하면 사용자가 로드밸런서를 통해 어떤 애플리케이션 Pod로 접속하더라도 동일한 Redis 데이터를 참조할 수 있다
결론적으로, 하나의 Pod에 여러 컨테이너를 배치할지 여부는 두 컨테이너 간의 긴밀한 협력의 필요성과 자원 공유의 적합성을 기준으로 신중하게 결정해야 한다. 대부분의 상황에서는 하나의 Pod에 하나의 컨테이너를 배치하는 것이 일반적이다
Pod 생성 및 관리
Kubernetes에서 Pod를 생성하는 방법은 크게 두 가지가 있다
- 명령어 사용: kubectl run my-nginx –image=nginx –port=80 과 같은 명령어를 사용하여 즉시 Pod를 생성할 수 있다. 하지만 이 방법은 Pod의 생성 기록이 명확하게 남지 않고 복잡한 설정에 제한이 있어 잘 사용되지 않는다
- 선언적 스크립트(YAML) 사용: YAML 형식의 파일을 작성하여 Pod의 명세를 정의하고 이 파일을 kubectl apply -f [파일명] 명령어로 클러스터에 적용하여 Pod를 생성하는 방법이다. 이 방법은 Kubernetes 리소스를 관리하는 표준적인 방식으로 아래의 장점 때문에 많이 사용된다
- 버전 관리: YAML 파일을 Git과 같은 버전 관리 시스템으로 관리할 수 있어 리소스의 변경 이력을 추적하고 협업하기 용이하다
- 가독성 및 재사용성: Pod의 모든 설정을 한눈에 파악할 수 있으며, 필요에 따라 파일을 수정하여 재사용하기 편리하다
- 선언적 관리: 원하는 상태 (desired state)를 YAML 파일에 선언하면, Kubernetes 컨트롤러가 해당 상태를 유지하도록 지속적으로 감시하고 조정한다
apiVersion: v1 # Kubernetes API 버전 (Pod는 v1)
kind: Pod # 생성할 Kubernetes 리소스 종류를 Pod로 지정
metadata:
name: web-app-pod # Pod 이름
labels:
app: web-app # Pod를 분류하는 레이블
spec:
containers:
- name: web-server # 메인 애플리케이션 컨테이너
image: nginx # Nginx 웹 서버 이미지 사용
ports:
- containerPort: 80 # 웹 서버가 노출할 포트
- name: log-collector # 사이드카 컨테이너 (보조 기능 담당)
image: fluentd # 로그 수집 및 전달 담당 이미지
volumeMounts:
- name: log-volume # 공유 볼륨을 마운트
mountPath: /var/log
volumes:
- name: log-volume # 두 컨테이너가 공유할 임시 볼륨
emptyDir: {} # Pod 생명주기 동안만 유지되는 임시 디렉토리
위 예시는 메인 애플리케이션(Nginx 웹 서버)과 사이드카 컨테이너(Fluentd 로그 수집기)를 하나의 Pod 안에 배치한 사례이다.
- 메인 컨테이너(web-server): 실제 사용자를 대상으로 서비스를 제공하는 웹 서버 역할을 수행한다
- 사이드카 컨테이너(log-collector): 메인 컨테이너에서 발생하는 로그를 수집하여 외부 로그 시스템(예: Elasticsearch, Cloud Logging등)으로 전달한다
- 공유 볼륨(emptyDir): 두 컨테이너가 동일한 로그 파일에 접근할 수 있도록 하는 임시 저장 공간이다
이 구조는 서드파티 데이터베이스(Redis, MySQL 등)와 달리 메인 애플리케이션과 강하게 결합된 기능(로그, 모니터링, 프록시 등)을 함께 운영할 때 적합하다. 이렇게 하면 애플리케이션과 보조 기능을 하나의 배포 단위로 묶어 관리할 수 있고 Pod 단위로 확장하거나 스케일링하기에도 용이하다
Pod의 생명주기와 한계
Pod의 생명주기 (Pod Lifecycle)
- 일회성 특성: Pod는 영구적이지 않은 일회성(ephemeral) 리소스이다
- IP 주소 변경: Pod가 재시작되면 새로운 IP 주소를 할당받는다
- 생명주기 단계: Pending → Running → Succeeded/Failed 단계를 거친다
Pod의 한계점
- 직접적인 Pod 생성의 문제: 실제 운영 환경에서는 Pod를 직접 생성하지 않고 Deployment, ReplicaSet 등의 상위 컨트롤러를 사용한다
- 복구 불가: Pod가 삭제되면 자동으로 재성성되지 않는다 (컨트롤러 없이는)
리소스 관리
- request vs limits: 리소스 요청량과 제한량을 설정하는 것이 중요하다 (위 YAML 에는 적용되지 않았다)
- 네임스페이스: Pod는 특정 네임스페이스 내에 생성된다
네트워킹 상세
- Cluster IP: 각 Pod는 클러스터 내에서만 접근 가능한 내부 IP를 받는다
- Service의 필요성: 외부에서 Pod에 접근하려면 Service 리소스가 필요하다