Reference
이 시리즈는 ‘쿠버네티스 교과서’를 보고 정리한 내용
TL;DR
- 파드: k8s의 컴퓨팅 기본 단위, 가상 IP를 가지며 컨테이너를 감싸는 래퍼
- 컨트롤러 객체: desired state와 current state의 차이를 감지하고 자동 보정
- 디플로이먼트: 파드를 관리하는 대표적 컨트롤러, 레이블 셀렉터로 관리 대상 결정
- YAML 매니페스트로 리소스 정의,
kubectl apply로 클러스터에 적용
1. 쿠버네티스란
왜 k8s가 필요한가
Docker 같은 컨테이너 기술로 애플리케이션을 패키징하고 실행할 수 있다. 하지만 컨테이너 수가 늘어나고 여러 서버에 분산 배포해야 하는 상황이 되면 컨테이너만으로는 해결하기 어려운 문제들이 생긴다.
- 서버 한 대가 죽으면 그 위의 컨테이너도 전부 죽는다 — 누가 다른 서버에서 다시 띄워줄 것인가?
- 컨테이너가 수백 개가 되면 어떤 서버에 어떤 컨테이너를 배치할지 누가 결정하는가?
- 새 버전을 배포할 때 기존 컨테이너를 하나씩 교체하면서 무중단으로 운영할 수 있는가?
이런 문제를 해결하기 위해 컨테이너 오케스트레이션 플랫폼이 필요하다. 쿠버네티스(k8s)가 바로 그 역할을 한다.
k8s의 핵심 개념: API + 클러스터
k8s의 핵심은 API와 클러스터 두 가지다.
클러스터는 컨테이너 런타임이 동작하는 여러 대의 서버(노드)가 모여 하나의 논리적 단위를 구성한 것이다. 클러스터의 노드는 크게 두 종류로 나뉜다.
- Control Plane (마스터 노드): k8s API 서버, 스케줄러, 컨트롤러 매니저 등이 동작한다. 클러스터 전체를 관장하는 두뇌 역할이다
- Worker Node: 실제 애플리케이션 컨테이너가 실행되는 노드다. 각 노드에는 kubelet, kube-proxy, 컨테이너 런타임이 설치되어 있다
더 자세한 구조
Control Plane의 구성요소(kube-api-server, etcd, kube-scheduler, kube-controller-manager)와 Worker Node의 구성요소(kubelet, kube-proxy, CRI)에 대한 상세 설명은 k8s가 필요한 이유 참고.
API는 클러스터와 소통하는 유일한 창구다. 사용자는 YAML로 원하는 상태를 정의하고 API에 전달한다. k8s는 이 정의를 받아 필요한 컨테이너를 추가하거나 제거하면서 운영한다.
# kubectl은 k8s API와 통신하는 CLI 도구다
kubectl get nodes # 클러스터에 속한 노드 목록 확인선언적 관리와 Self-healing
k8s의 핵심 철학은 선언적 관리다. “컨테이너 3개를 실행해라”가 아니라 “이 애플리케이션은 항상 3개의 복제본이 실행 중이어야 한다”고 선언한다. k8s는 현재 상태가 이 선언과 다르면 자동으로 바로잡는다.
이것이 self-healing의 기반이다.
- 노드가 고장나면 해당 노드의 컨테이너는 다른 노드에서 대체 실행된다
- 컨테이너에 문제가 생기면 자동 재시작된다
- 부하가 높아지면 해당 컴포넌트의 컨테이너를 추가 실행한다
클러스터가 제공하는 것
클러스터는 애플리케이션 운영에 필요한 인프라를 내장하고 있다.
- 분산 데이터베이스(etcd): 앱 구성 정보, API 키, DB 접속 비밀번호 등을 저장
- 스토리지: 컨테이너 외부에 데이터를 저장하고 고가용성을 확보
- 네트워크/트래픽 관리: 파드 간 통신, 외부 트래픽 라우팅, 로드밸런싱
애플리케이션 매니페스트
YAML 파일로 작성하는 애플리케이션 정의를 매니페스트라고 부른다. 매니페스트는 여러 k8s 리소스로 구성된다.
- Pod: 컨테이너를 감싸는 최소 실행 단위
- Deployment: 파드의 생성과 업데이트를 관리
- ReplicaSet: 파드의 복제본 수를 유지
- Service: 파드에 고정된 네트워크 접점 제공
- ConfigMap / Secret: 설정값과 민감 정보 관리
- Volume: 데이터 영속성 제공
2. 파드(Pod)
파드는 k8s에서 컴퓨팅의 기본 단위다. 클러스터를 이루는 노드 중 하나에서 실행된다.
파드란 무엇인가
파드는 하나 이상의 컨테이너를 감싸는 래퍼다.
- 파드는 k8s가 부여한 자신만의 가상 IP 주소를 가진다
- 이 IP로 가상 네트워크에 존재하는 다른 파드, 심지어 다른 노드의 파드와도 통신할 수 있다
- 하나의 파드에 여러 개의 컨테이너를 포함할 수 있고, 같은 파드 내 컨테이너는 네트워크를 공유하여
localhost로 통신한다 - 고급 옵션을 건드리지 않으면 보통 파드 하나에 컨테이너 하나가 실행된다
kubectl run으로 파드 실행해보기
간단한 파드는 YAML 없이 kubectl 명령어로 바로 실행할 수 있다.
# 파드 하나 실행
kubectl run hello-kiamol --image=kiamol/ch02-hello-kiamol
# 파드 목록 확인
kubectl get pods
# 파드의 상세 정보 확인 — 어느 노드에 배정됐는지, 현재 상태 등
kubectl describe pod hello-kiamol
# 파드가 어느 노드에서 실행 중인지 확인
kubectl get pods -o wide파드와 컨테이너의 관계
k8s는 직접 컨테이너를 실행하지 않는다.
- 해당 노드에 설치된 컨테이너 런타임(Docker, containerd 등)에 실행을 맡긴다
- k8s는 파드를 관리하고, 컨테이너의 실제 실행은 k8s 외부의 런타임이 담당한다
파드는 생성 시 한 노드에 배정되고, 해당 노드가 파드를 관리한다. 이때 **CRI(Container Runtime Interface)**라는 공통 API를 통해 컨테이너 생성, 삭제, 정보 확인 등을 표준화한다.
CRI가 왜 필요한가
k8s 초기에는 Docker만 지원했지만, 이후 containerd, CRI-O 등 다양한 런타임이 등장했다. CRI는 k8s가 어떤 런타임이든 동일한 방식으로 컨테이너를 관리할 수 있게 해주는 표준 인터페이스다. 자세한 내용은 k8s가 필요한 이유 참고.
Docker 명령어로 파드 내 컨테이너를 삭제하면 곧바로 다시 생성된다. 이것이 self-healing의 첫 단계다.
파드만으로는 부족한 이유
파드는 너무 단순한 객체다. 직접 파드를 실행하면 다음 문제가 있다.
- 각 파드는 서로 다른 노드에 배정되는데, 노드가 고장나면 파드가 유실된다
- k8s는 단독 파드를 새로 대체하지 않는다 — 파드 자체에는 복구 메커니즘이 없다
- 여러 파드를 실행해 고가용성을 확보하려 해도 노드 배치를 직접 관리해야 한다
그래서 일반적으로 파드를 직접 실행할 일은 없다. 파드를 관리할 컨트롤러 객체를 따로 만든다.
3. 디플로이먼트(Deployment)와 컨트롤러 객체
컨트롤러 객체: desired state vs current state
컨트롤러 객체는 다른 리소스를 관리하는 k8s 리소스다. 컨트롤러의 핵심 동작 원리는 바람직한 상태(desired state)와 현재 상태(current state)를 비교하고, 차이가 있으면 바로잡는 것이다.
이 패턴은 k8s 전체를 관통하는 핵심 개념이다.
- “파드 3개가 실행 중이어야 한다” (desired state)
- “현재 파드가 2개뿐이다” (current state)
- → 컨트롤러가 파드 1개를 새로 생성한다 (reconciliation)
컨트롤러는 k8s API와 연동하여 이 제어 루프를 끊임없이 반복한다.
디플로이먼트의 역할
디플로이먼트는 파드를 주로 관리하는 대표적인 컨트롤러 객체다. 디플로이먼트만 생성해도 정의한 정보대로 파드를 자동으로 만들어준다.
# 디플로이먼트 생성 — 정의한 정보대로 파드가 자동 생성된다
kubectl create deployment hello-kiamol-2 --image=kiamol/ch02-hello-kiamol
# 생성된 디플로이먼트 확인
kubectl get deploy
# 디플로이먼트가 만든 파드 확인
kubectl get pods레이블 셀렉터
디플로이먼트는 파드와 직접적으로 관계를 갖지 않는다. 대신 레이블 셀렉터와 일치하는 파드가 있으면 된다.
모든 k8s 리소스는 key-value 형태의 레이블을 가지고, 이를 통해 리소스 간 관계를 연결한다.
# 디플로이먼트가 파드에 자동으로 부여한 레이블 확인
kubectl get deploy hello-kiamol-2 -o jsonpath='{.spec.template.metadata.labels}'
# 출력: {"app": "hello-kiamol-2"}
# 해당 레이블로 파드 조회
kubectl get pods -l app=hello-kiamol-2레이블 조작을 활용한 디버깅
레이블을 강제로 수정하면 디플로이먼트가 해당 파드를 더 이상 인지하지 못한다. 이 특성을 디버깅에 활용할 수 있다.
# 1. 레이블 변경 — 디플로이먼트 관리 대상에서 이탈
kubectl label pods -l app=hello-kiamol-2 --overwrite app=hello-kiamol-x
# 2. 파드 목록 확인 — 기존 파드(이탈) + 새로 생성된 파드
kubectl get pods -l app -o custom-columns=NAME:metadata.name,LABELS:metadata.labels무슨 일이 벌어지는지 정리하면:
- 레이블을 바꾸면 디플로이먼트는 “관리 대상 파드가 0개”라고 판단한다
- desired state는 “파드 1개”이므로 새 파드를 생성한다
- 기존 파드는 레이블이 바뀌었을 뿐 여전히 실행 중이다 — 이 파드에 직접 접속해서 문제를 확인할 수 있다
- 다시 원래 레이블로 복원하면 파드가 2개가 되어 디플로이먼트가 1개를 삭제한다
# 3. 이탈된 파드에 직접 접속해서 디버깅
kubectl exec -it <이탈된-파드-이름> -- sh
# 4. 원래 레이블로 복원 — 파드가 2개가 되어 하나 삭제됨
kubectl label pods -l app=hello-kiamol-x --overwrite app=hello-kiamol-24. YAML 매니페스트로 배포 정의하기
kubectl run, kubectl create deployment 같은 명령어만으로는 간단한 앱밖에 배포할 수 없다. 본격적인 배포에는 YAML 매니페스트가 필요하다.
Pod YAML 구조
# 정의하려는 k8s API 버전과 리소스 유형
apiVersion: v1
kind: Pod
# 리소스의 메타데이터 — 이름(필수)과 레이블
metadata:
name: hello-kiamol-3
# 스펙 — 리소스의 실제 정의 내용
# 파드의 경우 실행할 컨테이너를 정의한다
spec:
containers:
- name: web
image: kiamol/ch02-hello-kiamolYAML은 크게 4개 블록으로 구성된다.
- apiVersion: 이 리소스가 속한 k8s API 버전
- kind: 리소스 유형 (Pod, Deployment, Service 등)
- metadata: 리소스의 이름(필수)과 레이블
- spec: 리소스의 실제 정의 내용
Deployment YAML 구조
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-kiamol-4
spec:
# 디플로이먼트가 관리 대상을 결정하는 레이블 셀렉터
selector:
matchLabels:
app: hello-kiamol-4
# 파드를 만들 때 사용하는 템플릿
template:
# 디플로이먼트 속 파드는 이름이 없고, 레이블 셀렉터와 일치하는 레이블을 지정
metadata:
labels:
app: hello-kiamol-4
spec:
containers:
- name: web
image: kiamol/ch02-hello-kiamolselector와 template.labels를 분리하는 이유
- selector: 디플로이먼트가 관리할 파드를 고르는 조건. 안정적이고 잘 바뀌지 않는 최소 레이블만 지정한다
- template.metadata.labels: 새로 생성할 파드에 실제로 붙일 레이블 집합. selector 조건을 만족하면서 추가 레이블도 붙일 수 있다
selector 조건 ⊆ template.labels
추가 레이블의 예시:
- 고정적:
team=platform,app.kubernetes.io/part-of=kiamol - 가변적:
version=v2,release=canary
실무에서는 selector에는 안정적인 최소 레이블만, template.labels에는 그 레이블 + 운영/분류용 추가 레이블을 넣는다.
kubectl apply로 적용하기
YAML 파일을 작성했으면 kubectl apply 명령어로 클러스터에 적용한다.
# YAML 파일로 리소스 생성/업데이트
kubectl apply -f pod.yaml
kubectl apply -f deployment.yaml
# 디렉터리 내 모든 YAML 적용
kubectl apply -f ./manifests/kubectl create vs kubectl apply
kubectl create: 리소스를 새로 생성만 한다. 이미 존재하면 에러가 난다kubectl apply: 리소스가 없으면 생성하고, 이미 있으면 변경된 부분만 업데이트한다- 실무에서는 **
kubectl apply**를 주로 사용한다. 선언적 관리에 적합하기 때문이다
5. 파드 접근과 리소스 관리
파드에서 실행 중인 앱에 접근하기
# 컨테이너에 원격 접속
kubectl exec -it hello-kiamol -- sh
# 로그 확인
kubectl logs hello-kiamol
# 레이블 셀렉터로 디플로이먼트가 관리하는 파드의 로그 확인
kubectl logs -l app=hello-kiamol-4
# 파드의 상세 정보 확인 — 이벤트, 상태, 조건 등 디버깅에 필수
kubectl describe pod hello-kiamol
# 파드의 파일을 로컬로 복사
kubectl cp hello-kiamol:/usr/share/nginx/html/index.html /tmp/kiamol/ch02/index.htmlPort-forwarding
파드는 클러스터 내부의 가상 네트워크에 존재하기 때문에 호스트에서 직접 접근할 수 없다. kubectl port-forward로 로컬 포트와 파드의 포트를 연결해야 한다.
# 로컬 8080 포트를 파드의 80 포트로 연결
kubectl port-forward pod/hello-kiamol 8080:80
# 디플로이먼트 대상으로도 가능 — 관리 중인 파드 중 하나를 자동 선택
kubectl port-forward deployment/hello-kiamol-2 8080:80이후 브라우저에서 http://localhost:8080으로 접근하면 파드 내 애플리케이션을 확인할 수 있다.
대상별 동작 차이
- pod/…: 해당 파드 하나에 고정
- deployment/…: 디플로이먼트의 파드 중 하나를 골라서 연결
- service/…: 서비스가 고른 파드 하나에 연결
어떤 대상을 지정하든 최종적으로 파드 하나에 연결되는 것이지, 로드밸런서처럼 분산되지 않는다. 선택된 파드가 종료되면 port-forward 세션도 끝난다.
멀티 컨테이너 파드에서의 port-forward
파드 안의 컨테이너들은 IP와 포트 공간을 공유한다. port-forward는 컨테이너를 고르는 게 아니라 파드의 네트워크 포트로 연결되고, 해당 포트를 리슨하는 프로세스가 응답한다.
# 앱 컨테이너가 80, 사이드카가 15000을 리슨하는 경우
kubectl port-forward pod/mypod 8080:80 # 앱 쪽
kubectl port-forward pod/mypod 15000:15000 # 사이드카 쪽kubectl port-forward에 컨테이너 이름을 지정하는 옵션은 없다. 보통 한 파드 안에서 같은 포트를 여러 컨테이너가 동시에 쓸 수 없으므로 포트 번호로 구분한다.
리소스 삭제 시 동작
컨트롤러 객체가 관리하는 리소스를 직접 삭제하면 이를 대체하는 새로운 리소스가 자동 생성된다. 이것이 desired state를 유지하는 컨트롤러의 동작이다.
# 파드 목록 확인
kubectl get pods
# 파드 전체 삭제
kubectl delete pods --all
# → 디플로이먼트로 생성한 파드(이름 뒤에 hash가 있는)는 바로 재생성된다
# → kubectl run으로 직접 만든 파드는 재생성되지 않는다
# 재생성 확인
kubectl get pods파드를 완전히 제거하려면 해당 파드를 관리하는 컨트롤러 객체를 삭제해야 한다.
# 디플로이먼트 확인
kubectl get deploy
# 디플로이먼트 삭제 — 관리하던 파드도 함께 삭제된다
kubectl delete deploy --all정리
kubectl run으로 만든 파드: 삭제하면 영구 삭제. 아무도 재생성하지 않는다- 디플로이먼트가 만든 파드: 삭제해도 디플로이먼트가 즉시 재생성한다. 파드를 없애려면 디플로이먼트를 삭제해야 한다