목차
- WHY — 왜 Kubernetes가 필요한가
- K8s 핵심 개념
- 로컬 클러스터 구성 (minikube)
- Pod / Deployment / ReplicaSet
- Service — 네트워크 접근
- Rolling Update / Rollback
- ConfigMap / Secret
- 전체 요약
1. WHY — 왜 Kubernetes가 필요한가
아리 앱은 4개 MSA 서비스 + Redis + MySQL + Kafka로 구성된다.
컨테이너가 수십 개로 늘어나면 Docker만으로는 관리가 불가능하다.
Docker만 사용 시
├── 컨테이너가 죽으면? → 수동으로 재시작
├── 트래픽 폭증하면? → 수동으로 컨테이너 추가
└── 배포하면? → 서비스 중단
Kubernetes 사용 시
├── 컨테이너가 죽으면? → 자동 재시작 (자가 치유)
├── 트래픽 폭증하면? → 자동 스케일링 (HPA)
└── 배포하면? → 무중단 Rolling Update
2. K8s 핵심 개념
아키텍처
Control Plane (두뇌)
├── API Server → 모든 요청의 진입점
├── etcd → 클러스터 상태 저장소
├── Scheduler → Pod를 어느 Node에 배치할지 결정
└── Controller → 원하는 상태 유지 담당
Worker Node (손발)
├── kubelet → Node에서 Pod 실행 담당
├── kube-proxy → 네트워크 규칙 관리
└── Container Runtime → 실제 컨테이너 실행
"kubectl apply하면 API Server가 받아서 Scheduler가 Pod를 Node에 배치하고 kubelet이 실제로 컨테이너를 실행한다"
핵심 오브젝트
| 항목 | 역할 |
| Pod | 컨테이너를 감싸는 최소 배포 단위 |
| ReplicaSet | Pod를 지정한 수만큼 유지 |
| Deployment | ReplicaSet 관리, Rolling Update 담당 |
| Service | Pod에 접근하는 네트워크 엔드포인트 |
| Ingress | 외부 트래픽을 서비스로 라우팅 |
| ConfigMap | 설정값 관리 |
| Secret | 민감 정보 관리 |
K8s 오브젝트 연결 방식
K8s 오브젝트들은 이름 또는 라벨로 서로 참조한다.
연결 방식
├── Service → selector.app (라벨로 Pod 선택)
├── ConfigMap → metadata.name (이름으로 참조)
└── Secret → metadata.name (이름으로 참조)
Service는 라벨 기반이라 같은 라벨을 가진 Pod면 자동으로 포함된다.
Pod가 재시작되어 IP가 바뀌어도 Service가 라벨로 자동으로 찾아준다.
minikube vs 실제 K8s
실제 K8s 환경
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Control │ │ Worker │ │ Worker │
│ Plane 서버 │ │ Node 서버 1 │ │ Node 서버 2 │
└─────────────┘ └─────────────┘ └─────────────┘
minikube (로컬)
┌──────────────────────────────┐
│ 맥북 안에서 전부 가상으로 │
│ Control Plane + Worker Node │
└──────────────────────────────┘
minikube에서 익힌 yaml은 실제 K8s 환경에서도 동일하게 동작한다. 차이는 인프라 레벨(서버 구성)에만 있다.
3. 로컬 클러스터 구성
minikube start
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# minikube Ready control-plane 4m v1.35.1
4. Pod / Deployment / ReplicaSet
Pod — 최소 배포 단위
# Pod 직접 실행 (실무에서 사용 안 함)
kubectl run nginx-test --image=nginx
kubectl get pods
Pod를 직접 띄우면 죽었을 때 자동 재생성이 안된다. ReplicaSet이 없기 때문이다.
kubectl delete pod nginx-test
kubectl get pods
# No resources found ← 그냥 사라짐
Deployment — 표준 배포 방식
# nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
kubectl apply -f nginx-deployment.yaml
kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-deployment-75fdcbbc74-hsk82 1/1 Running 0 8s
nginx-deployment-75fdcbbc74-rg6dw 1/1 Running 0 8s
Pod 이름 구조
nginx-deployment-75fdcbbc74-hsk82
│ │ │
│ │ └── Pod 고유 ID (랜덤)
│ └── ReplicaSet ID
└── Deployment 이름
자가 치유 (Self-healing) 실습
kubectl delete pod nginx-deployment-75fdcbbc74-hsk82
kubectl get pods
# NAME READY STATUS AGE
# nginx-deployment-75fdcbbc74-lph55 1/1 Running 9s ← 새로 생성
# nginx-deployment-75fdcbbc74-rg6dw 1/1 Running 65s ← 유지
ReplicaSet이 항상 2개를 유지하도록 자동으로 새 Pod를 생성한다.
Deployment / ReplicaSet / Pod 관계
kubectl get deployment
kubectl get replicaset
kubectl get pods
Deployment (nginx-deployment)
└── ReplicaSet (nginx-deployment-75fdcbbc74)
├── Pod (lph55)
└── Pod (rg6dw)
ReplicaSet은 Auto Scaling (HPA)과 같이 최소 N개 유지라는 점에서는 비슷한 점이 있음..
ReplicaSet
→ "항상 N개를 유지해라" (고정)
→ Pod가 죽으면 자동 재생성
HPA (Horizontal Pod Autoscaler)
→ "트래픽에 따라 N~M개 사이로 자동 조절해라"
→ CPU/메모리 사용률 기반으로 Pod 수 자동 증감
→ K8s의 Auto Scaling
5. Service — 네트워크 접근
Pod IP는 클러스터 내부에서만 접근 가능하고, Pod가 재시작될 때마다 바뀐다. Service는 고정된 접근 창구를 제공한다.
# nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
type: NodePort
kubectl apply -f nginx-service.yaml
kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
nginx-service NodePort 10.109.88.102 <none> 80:31887/TCP
네트워크 접근 흐름
외부에서 직접 호출 (NodePort)
curl 127.0.0.1:62068 (Mac 로컬, minikube 터널)
│
▼
192.168.49.2:31887 (minikube VM(Docker Container) IP:NodePort)
│
▼
10.244.0.x:80 (Pod 내부 IP)
클러스터 내부 통신 (ClusterIP)
application.yml → host: redis-service
│ K8s 내부 DNS
▼
10.109.88.102:80 (ClusterIP)
│
▼
10.244.0.x:80 (Pod 내부 IP)
IP 역할 정리
| IP | 용도 |
| 127.0.0.1:62068 | minikube 터널 (로컬 개발용) |
| 192.168.49.2 | minikube VM IP |
| 31887 | NodePort (외부 접근용) |
| 10.109.88.102 | ClusterIP (클러스터 내부 통신용) |
| 10.244.0.x | Pod 내부 IP |
클러스터 내부에서는 IP 대신 Service 이름으로 통신한다.
# application.yml
spring:
datasource:
url: jdbc:mysql://mysql-service:3306/aridb
redis:
host: redis-service
K8s 내부 DNS가 mysql-service → ClusterIP로 자동 변환해준다.
6. Rolling Update / Rollback
Rolling Update — 무중단 배포
kubectl set image deployment/nginx-deployment nginx=nginx:1.25
kubectl rollout status deployment/nginx-deployment
Waiting for deployment "nginx-deployment" rollout to finish: 1 out of 2 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 1 old replicas are pending termination...
deployment "nginx-deployment" successfully rolled out
한 번에 전부 교체하는 게 아니라 순차적으로 교체한다.
기존: [nginx:latest] [nginx:latest]
│
▼
중간: [nginx:latest] [nginx:1.25] ← 동시에 2개 버전 운영
│
▼
완료: [nginx:1.25] [nginx:1.25]
Rollback — 이전 버전으로 복구
잘못된 이미지 배포 시
kubectl set image deployment/nginx-deployment nginx=nginx:잘못된버전
kubectl get pods
# nginx-deployment-5fd577784b-x5wdr 1/1 Running ← 기존 Pod 유지
# nginx-deployment-5fd577784b-zrgqz 1/1 Running ← 기존 Pod 유지
# nginx-deployment-68ffc8f76d-kdvkx 0/1 InvalidImageName ← 새 Pod 실패
기존 Pod가 살아있어서 서비스는 계속 동작한다.
# 이전 버전으로 롤백
kubectl rollout undo deployment/nginx-deployment
# 특정 버전으로 롤백
kubectl rollout undo deployment/nginx-deployment --to-revision=1
배포 히스토리
kubectl rollout history deployment/nginx-deployment
REVISION CHANGE-CAUSE
1 <none>
3 <none>
4 <none>
K8s는 Deployment 변경 이력을 revision으로 관리한다. 실무에서는 변경 이유를 기록해두는 것이 좋다.
kubectl annotate deployment/nginx-deployment \
kubernetes.io/change-cause="nginx 1.25로 업데이트"
7. ConfigMap / Secret
ConfigMap — 설정값 관리
이미지와 설정값을 분리하여 관리한다.
# nginx-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
data:
APP_ENV: production
APP_NAME: ari-app
LOG_LEVEL: info
Secret — 민감 정보 관리
비밀번호, API 키 등 민감한 정보는 Secret으로 관리한다.
# nginx-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: ari-secret
type: Opaque
stringData:
DB_PASSWORD: ari1234
REDIS_PASSWORD: redis1234
JWT_SECRET: my-super-secret-key
ConfigMap vs Secret 차이
kubectl describe configmap nginx-config
# APP_ENV: production ← 값이 그대로 노출
kubectl describe secret ari-secret
# DB_PASSWORD: 7 bytes ← 값이 숨겨짐 (바이트 수만 표시)
Pod에 주입
# nginx-with-config.yaml
spec:
containers:
- name: nginx
image: nginx:1.25
envFrom:
- configMapRef:
name: nginx-config ← ConfigMap metadata.name
- secretRef:
name: ari-secret ← Secret metadata.name
kubectl exec -it <pod명> -- env | grep -E "APP_ENV|DB_PASSWORD"
# APP_ENV=production ← ConfigMap에서 주입
# DB_PASSWORD=ari1234 ← Secret에서 주입
아리 앱에서의 활용
# application.yml
spring:
datasource:
password: ${DB_PASSWORD} # Secret에서 주입
redis:
password: ${REDIS_PASSWORD} # Secret에서 주입
이미지에 비밀번호를 직접 넣지 않고 K8s가 실행 시점에 주입해준다. 이미지를 공개 Registry에 올려도 비밀번호가 노출되지 않는다.
Secret 보안 주의사항
Secret은 base64 인코딩이지 암호화가 아니다.
기본 Secret → base64만, 보안 취약
etcd 암호화 → etcd 저장 시 암호화
AWS Secrets Manager → 외부 시크릿 관리, 자동 로테이션
HashiCorp Vault → 가장 강력한 시크릿 관리 도구
최소한 Secret yaml 파일은 git에 절대 올리지 않는다.
8. 전체 요약
K8s 핵심 원칙
├── Pod: 최소 배포 단위, 직접 띄우지 않음
├── Deployment: 실무 표준, ReplicaSet + Rolling Update 관리
├── ReplicaSet: 항상 N개 Pod 유지 (자가 치유)
├── Service: Pod 접근 창구, 라벨로 Pod 선택
│ ├── ClusterIP → 클러스터 내부 통신
│ └── NodePort → 외부 접근
├── ConfigMap: 공개 설정값 (이미지와 분리)
└── Secret: 민감 정보 (base64, git 제외 필수)
오브젝트 연결 방식
├── Service → selector.app (라벨로 Pod 선택)
├── ConfigMap → metadata.name (이름으로 참조)
└── Secret → metadata.name (이름으로 참조)
'Infra & Network' 카테고리의 다른 글
| [Kubernetes | Part 2] Redis on Kubernetes (0) | 2026.05.03 |
|---|---|
| [Redis HA | Part 2] Redis Cluster — 샤딩 + HA (0) | 2026.04.30 |
| [Docker | Part 5] Docker 네트워크 원리 이해 (0) | 2026.04.30 |
| [Docker | Part 4] Volume + Network + Registry (0) | 2026.04.30 |
| [Docker | Part 3] Docker Compose (0) | 2026.04.30 |