
Kubernetes를 처음 공부하면 Pod, ReplicaSet, Deployment, Service 같은 리소스를 각각 배우게 된다.
하지만 개별 정의만 외우면 실제로 이 리소스들이 어떤 관계를 가지고 동작하는지 이해하기 어렵다.
전체 구조를 먼저 단순화하면 다음과 같다.
Namespace
│
├── Deployment
│ │
│ └── ReplicaSet
│ │
│ ├── Pod
│ ├── Pod
│ └── Pod
│
└── Service
│
└── selector
↓
Pods
애플리케이션은 Pod에서 실행되고, ReplicaSet은 필요한 Pod 수를 유지한다.
Deployment는 ReplicaSet을 관리하면서 애플리케이션의 배포와 업데이트를 담당한다.
Service는 생성과 삭제를 반복하는 Pod들 앞에 안정적인 네트워크 접근 지점을 제공하고, Namespace는 이러한 리소스를 논리적인 범위로 구분한다.
Kubernetes 공식 문서는 Pod를 Kubernetes에서 생성하고 관리할 수 있는 가장 작은 배포 가능한 컴퓨팅 단위로 설명한다. (Kubernetes)
일반적으로 하나의 Pod 안에는 하나의 애플리케이션 컨테이너가 실행되지만, 필요하다면 여러 컨테이너가 하나의 Pod를 구성할 수도 있다.
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
여기서 중요한 점은 Pod 자체를 영구적인 서버처럼 생각하면 안 된다는 것이다.
Pod는 생성되고 삭제될 수 있으며, 장애가 발생한 기존 Pod 자체가 복구되는 것이 아니라 새로운 Pod가 생성되는 방식으로 원하는 상태가 유지된다. (Kubernetes)
그래서 실제 애플리케이션 운영에서는 Pod를 직접 생성하기보다 Deployment 같은 상위 워크로드 리소스를 사용하는 경우가 일반적이다.
Pod 하나가 죽었을 때 누가 새로운 Pod를 만들어야 할까?
이 역할을 하는 리소스가 ReplicaSet이다.
Kubernetes 공식 문서에 따르면 ReplicaSet의 목적은 지정된 수의 Pod replica가 항상 실행되도록 안정적인 집합을 유지하는 것이다. (Kubernetes)
예를 들어 다음과 같이 설정했다고 생각해보자.
spec:
replicas: 3
원하는 상태(Desired State)는 다음과 같다.
Desired Pods = 3
Pod A
Pod B
Pod C
그런데 Pod B가 삭제되면 현재 상태는 2개가 된다.
Desired = 3
Current = 2
Pod A
Pod C
ReplicaSet은 이를 감지하고 새로운 Pod를 생성한다.
Desired = 3
Current = 3
Pod A
Pod C
Pod D ← 새로 생성
즉,
Desired State
↓
ReplicaSet
↓
현재 Pod 개수 확인
↓
부족하면 생성 / 많으면 삭제
라는 reconciliation 과정이 계속 일어난다.
ReplicaSet은 selector를 사용하여 자신이 관리해야 할 Pod를 식별한다.
selector:
matchLabels:
app: nginx
따라서 label과 selector의 관계를 이해하는 것이 중요하다.
다만 실제 운영에서는 ReplicaSet을 직접 생성하기보다 대부분 Deployment를 사용한다. Kubernetes 공식 문서 역시 특별한 요구사항이 없다면 ReplicaSet을 직접 사용하기보다 Deployment 사용을 권장한다. (Kubernetes)
여기까지 보면 구조는 다음과 같다.
ReplicaSet
↓
Pods
그런데 애플리케이션 버전을 업데이트해야 한다면 문제가 하나 더 생긴다.
예를 들어
nginx:1.24
에서
nginx:1.25
로 변경하고 싶다고 생각해보자.
ReplicaSet은 Pod 개수를 유지하는 역할에는 적합하지만 애플리케이션의 업데이트 전략까지 관리하기 위한 리소스는 아니다.
그래서 한 단계 위에 Deployment가 존재한다.
Kubernetes 공식 문서에서는 Deployment가 Pod와 ReplicaSet에 대한 선언적 업데이트(declarative updates)를 제공한다고 설명한다. (Kubernetes)
구조는 다음과 같다.
Deployment
↓
ReplicaSet
↓
Pods
Deployment YAML 역시 내부에 Pod template을 가지고 있다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
여기서 특히 중요한 구조는 다음이다.
Deployment
│
├── replicas
├── selector
│
└── template
│
└── Pod specification
template 아래 내용이 결국 생성될 Pod의 설계도가 된다.
Deployment의 핵심은 Rolling Update를 이해하면 보이기 시작한다.
예를 들어 기존 Deployment가 다음 상태라고 하자.
Deployment
│
└── ReplicaSet A
├── Pod v1
├── Pod v1
└── Pod v1
컨테이너 이미지를 v2로 변경한다.
kubectl set image deployment/nginx nginx=nginx:v2
Deployment의 Pod template이 변경되면 새로운 ReplicaSet이 만들어진다. (Kubernetes)
Deployment
│
├── ReplicaSet A
│ ├── Pod v1
│ ├── Pod v1
│ └── Pod v1
│
└── ReplicaSet B
└── Pod v2
이후 새 ReplicaSet은 점차 scale up되고 기존 ReplicaSet은 scale down된다.
RS A: 3 → 2 → 1 → 0
RS B: 0 → 1 → 2 → 3
최종적으로는 다음 상태가 된다.
Deployment
│
├── ReplicaSet A
│ replicas: 0
│
└── ReplicaSet B
├── Pod v2
├── Pod v2
└── Pod v2
즉 Deployment의 업데이트를 단순히
"Pod의 이미지가 변경된다"
라고 이해하기보다는,
Pod template 변경 → 새로운 ReplicaSet 생성 → 새로운 Pod 생성 → 기존 ReplicaSet 축소
라는 흐름으로 이해하는 것이 중요하다.
Deployment가 Pod를 관리하면 새로운 문제가 생긴다.
Pod는 계속 생성되고 삭제될 수 있고 각 Pod는 자신의 IP를 갖는다. 따라서 클라이언트가 특정 Pod IP를 직접 바라보도록 구성하면 Pod가 교체될 때 문제가 발생한다. (Kubernetes)
그래서 Service가 필요하다.
Service는 여러 Pod 앞에 안정적인 네트워크 접근점을 제공하는 추상화다.
Client
│
↓
Service
│
├── Pod A
├── Pod B
└── Pod C
Service 역시 어떤 Pod로 트래픽을 전달할 것인지 알아야 한다.
여기서 다시 label과 selector가 등장한다.
Pod:
metadata:
labels:
app: nginx
Service:
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
Service의 selector가
app: nginx
인 Pod들을 대상으로 삼는다. Kubernetes는 이 Service와 연결되는 엔드포인트 정보를 EndpointSlice를 통해 관리한다. (Kubernetes)
따라서 중요한 관계는 다음과 같다.
Deployment
│
↓
ReplicaSet
│
↓
Pods ←──────── Service
selector
여기서 한 가지 주의할 점은 Service가 Deployment를 직접 찾아가는 것이 아니라는 것이다.
Service는 일반적으로 label selector를 통해 대상 Pod를 결정한다.
CKA에서 Service 문제가 나오면 따라서 가장 먼저 확인해야 할 것 중 하나가
Service selector
↕
Pod labels
이 서로 일치하는지 여부다.
Service에는 대표적으로 다음과 같은 타입이 있다. (Kubernetes)
| Type | 역할 |
|---|---|
| ClusterIP | 클러스터 내부에서 Service 노출 |
| NodePort | Node의 특정 Port를 통해 Service 노출 |
| LoadBalancer | 외부 Load Balancer를 통해 Service 노출 |
| ExternalName | Service를 외부 DNS 이름에 매핑 |
기본값은 ClusterIP이다.
CKA를 준비할 때는 단순히 타입 이름을 외우는 것보다
ClusterIP
↑
NodePort
↑
LoadBalancer
처럼 외부 노출 범위가 확장되는 구조로 이해하면 편하다.
Namespace는 하나의 Kubernetes Cluster 안에서 리소스 그룹을 분리하기 위한 범위(scope)를 제공한다. (Kubernetes)
예를 들어 하나의 클러스터 안에 다음과 같이 환경을 구분할 수 있다.
Kubernetes Cluster
├── namespace: dev
│ ├── Deployment
│ ├── Service
│ └── Pods
│
├── namespace: staging
│ ├── Deployment
│ ├── Service
│ └── Pods
│
└── namespace: production
├── Deployment
├── Service
└── Pods
같은 Namespace 안에서는 리소스 이름이 고유해야 하지만 서로 다른 Namespace라면 같은 이름을 사용할 수 있다. (Kubernetes)
예를 들어 다음 두 Deployment는 동시에 존재할 수 있다.
dev/nginx
production/nginx
Namespace를 지정할 때는 다음처럼 -n 또는 --namespace를 사용한다.
kubectl get pods -n production
kubectl get deployment -n production
현재 Cluster에 존재하는 Namespace는 다음과 같이 확인한다.
kubectl get namespaces
축약하면:
kubectl get ns
또한 모든 Kubernetes 리소스가 Namespace에 속하는 것은 아니다.
Pod, Service, Deployment 등은 namespaced resource지만 Node, PersistentVolume 등은 cluster-scoped resource다. (Kubernetes)
시험 중 헷갈린다면 다음 명령으로 직접 확인할 수 있다.
kubectl api-resources --namespaced=true
kubectl api-resources --namespaced=false
kubectl get이 리소스의 현재 상태를 빠르게 확인하는 명령이라면 describe는 해당 리소스의 상세 상태와 Events를 확인할 때 유용하다.
kubectl describe pod nginx
Deployment라면:
kubectl describe deployment nginx
Service라면:
kubectl describe service nginx-service
특히 troubleshooting에서는 출력 마지막의 Events가 중요하다.
예를 들어 Pod가 정상적으로 실행되지 않는다면
kubectl get pods
로 상태를 확인하고,
kubectl describe pod <pod-name>
으로 상세 원인을 확인하는 흐름을 사용할 수 있다.
기존 리소스를 직접 수정하고 싶다면 kubectl edit을 사용할 수 있다.
kubectl edit deployment nginx
Deployment의 image 등을 변경하고 저장하면 API object가 업데이트된다.
예를 들어 Deployment의 Pod template에서 image를 변경하면:
spec:
template:
spec:
containers:
- name: nginx
image: nginx:1.25
Pod template이 변경되었기 때문에 Deployment rollout이 발생한다. (Kubernetes)
여기서 중요한 포인트는:
Deployment의 모든 변경이 rollout을 발생시키는 것은 아니다.
Deployment의 .spec.template이 변경될 때 rollout이 발생한다. 단순히 replica 수를 변경하는 scaling은 새로운 Deployment revision을 만들지 않는다. (Kubernetes)
Deployment를 변경했다면 rollout 상태를 확인할 수 있다.
kubectl rollout status deployment/nginx
revision history를 확인하려면:
kubectl rollout history deployment/nginx
문제가 있는 업데이트를 이전 revision으로 되돌리고 싶다면:
kubectl rollout undo deployment/nginx
특정 revision으로 돌아갈 수도 있다.
kubectl rollout undo deployment/nginx --to-revision=2
즉 운영 흐름을 단순화하면 다음과 같다.
Deployment 수정
↓
새 ReplicaSet 생성
↓
Rolling Update
↓
kubectl rollout status
↓
정상?
┌────┴────┐
YES NO
│ │
완료 rollout undo
Deployment가 ReplicaSet을 이용해 rollout과 rollback을 관리한다는 구조를 이해하면 명령어를 단순 암기하지 않아도 된다. (Kubernetes)
오늘 공부한 리소스들은 사실 독립적인 개념이 아니다.
Namespace
│
│ 리소스의 논리적인 범위
│
├── Deployment
│ │
│ │ 배포 / 업데이트 관리
│ ↓
│ ReplicaSet
│ │
│ │ 원하는 개수 유지
│ ↓
│ Pods
│ │
│ │ 실제 Container 실행
│ │
│ └──────────────┐
│ │
└── Service ───────────┘
│
│ selector로 Pod 선택
↓
안정적인 네트워크 접근점
결국 다음 다섯 문장으로 요약할 수 있다.
Pod는 애플리케이션이 실제로 실행되는 단위다.
ReplicaSet은 원하는 수의 Pod를 유지한다.
Deployment는 ReplicaSet을 관리하면서 애플리케이션의 배포와 업데이트를 담당한다.
Service는 변할 수 있는 Pod 집합에 안정적인 네트워크 접근점을 제공한다.
Namespace는 하나의 Cluster 안에서 리소스를 논리적인 범위로 구분한다.