Infra & Network

[Kubernetes | Part 1] Kubernetes 기초

hrbds 2026. 5. 1. 22:10

목차

  • 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 (이름으로 참조)