Kubernetes 개념 한판 정리 - 아키텍처부터 실전 배포까지

2023-10-29 18:48:05
#Kubernetes#K8s#DevOps#Container#Infra

왜 Kubernetes인가

Docker로 컨테이너 하나 띄우는 건 쉽다. 근데 실제 서비스는 그렇게 단순하지 않다.

  • 컨테이너가 죽으면 누가 다시 띄워주지?
  • 트래픽이 늘면 컨테이너를 어떻게 늘리지?
  • 여러 서버에 컨테이너를 어떻게 분산하지?
  • 무중단 배포는 어떻게 하지?

이런 문제를 해결하는 게 컨테이너 오케스트레이션이고, Kubernetes(K8s)가 사실상 표준이다.

기능 설명
자동 복구 (Self-healing) 컨테이너 죽으면 자동 재시작
오토스케일링 트래픽에 따라 Pod 수 자동 조절
로드밸런싱 여러 Pod에 트래픽 분산
롤링 업데이트 무중단 배포
서비스 디스커버리 DNS 기반 서비스 간 통신

Kubernetes 아키텍처

K8s 클러스터는 Control PlaneWorker Node로 구성된다.

Kubernetes Architecture Kubernetes 클러스터 아키텍처 - Control Plane과 Worker Node 구성

Control Plane 구성요소

구성요소 역할
API Server 모든 요청의 진입점. kubectl 명령이 여기로 들어옴
etcd 클러스터 상태 저장소 (Key-Value DB)
Scheduler 새 Pod를 어느 Node에 배치할지 결정
Controller Manager 원하는 상태(Desired State)를 유지. ReplicaSet, Deployment 등 관리

Worker Node 구성요소

구성요소 역할
kubelet Node의 에이전트. Pod 생성/삭제/상태 보고
kube-proxy 네트워크 프록시. Service → Pod 트래픽 라우팅
Container Runtime 실제 컨테이너 실행 (containerd, CRI-O 등)

핵심 오브젝트

K8s에서는 모든 것이 오브젝트다. YAML로 원하는 상태(Desired State)를 선언하면, K8s가 알아서 현재 상태를 맞춰준다.

Pod - 최소 배포 단위

Pod는 K8s에서 가장 작은 배포 단위다. 하나 이상의 컨테이너를 포함한다.

Pod 특징 설명
네트워크 같은 네트워크 네임스페이스 (localhost 통신)
스토리지 같은 볼륨 공유 가능
IP Pod 단위로 고유 IP 할당

Pod 특징:

  • Pod 내 컨테이너들은 localhost로 서로 통신
  • Pod마다 고유 IP 할당 (클러스터 내부 IP)
  • Pod는 일시적(Ephemeral). 죽으면 새 IP로 재생성됨
  • 그래서 직접 Pod IP로 접근하면 안 됨 → Service 사용
# pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: my-app
  labels:
    app: my-app
    env: production
spec:
  containers:
  - name: app
    image: my-app:1.0
    ports:
    - containerPort: 8080
    resources:
      requests:
        memory: "256Mi"
        cpu: "250m"
      limits:
        memory: "512Mi"
        cpu: "500m"

ReplicaSet - Pod 복제 관리

ReplicaSet은 지정한 수의 Pod를 항상 유지한다. Pod가 죽으면 자동으로 새로 생성.

ReplicaSet의 핵심은 Self-healing이다. replicas: 3으로 설정하면 항상 3개의 Pod를 유지한다. Pod 2가 죽으면 자동으로 새 Pod 4를 생성하여 3개를 맞춘다.

Deployment - 배포 관리

실무에서는 Pod나 ReplicaSet을 직접 만들지 않는다. Deployment를 사용한다.

Deployment가 ReplicaSet을 관리하고, ReplicaSet이 Pod를 관리하는 구조다.

Deployment는 ReplicaSet을 관리한다. 버전 업데이트(v1.0 → v1.1) 시 새 ReplicaSet을 만들고 기존 ReplicaSet의 Pod를 점진적으로 종료한다. 이것이 롤링 업데이트다.

Deployment의 장점:

  • 롤링 업데이트: 무중단 배포
  • 롤백: 문제 생기면 이전 버전으로 복구
  • 스케일링: kubectl scale 명령으로 Pod 수 조절
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1        # 업데이트 중 최대 추가 Pod 수
      maxUnavailable: 0  # 업데이트 중 최소 가용 Pod 수 보장
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: app
        image: my-app:1.0
        ports:
        - containerPort: 8080

Service - 네트워크 추상화

Pod는 일시적이고 IP가 바뀐다. Service는 고정된 엔드포인트를 제공한다.

Service는 selector로 매칭되는 Pod들에게 트래픽을 분산한다. Service IP(예: 10.96.0.100)는 고정이므로, Pod가 죽고 새로 생겨도 클라이언트는 같은 IP로 접근할 수 있다.

Kube-Proxy Workflow kube-proxy가 Service와 Pod 간 트래픽 라우팅을 담당한다

Service 타입:

타입 설명 접근 범위
ClusterIP 클러스터 내부 IP (기본값) 클러스터 내부만
NodePort 모든 Node의 특정 포트로 노출 외부 접근 가능
LoadBalancer 클라우드 LB 연동 외부 접근 가능
# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-app-service
spec:
  type: ClusterIP
  selector:
    app: my-app
  ports:
  - port: 80          # Service 포트
    targetPort: 8080  # Pod 포트

Ingress - HTTP 라우팅

Ingress는 외부 HTTP(S) 트래픽을 클러스터 내부 Service로 라우팅한다. 도메인 기반, 경로 기반 라우팅이 가능하다.

Ingress Controller(nginx, traefik 등)가 외부 HTTP 트래픽을 받아 도메인/경로 규칙에 따라 내부 Service로 라우팅한다.

요청 라우팅 대상
api.example.com/* api-service:80
web.example.com/* web-service:80
example.com/api/* api-service:80
example.com/admin/* admin-service:80
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 80
  - host: web.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web-service
            port:
              number: 80

Namespace - 논리적 분리

하나의 클러스터를 여러 팀이나 환경으로 분리할 때 사용한다.

Namespace 용도 ResourceQuota 예시
dev 개발 환경 CPU 4 cores, Memory 8Gi
staging 스테이징 환경 CPU 8 cores, Memory 16Gi
prod 프로덕션 환경 CPU 32 cores, Memory 64Gi
# Namespace 생성
kubectl create namespace dev

# 특정 Namespace에 리소스 생성
kubectl apply -f deployment.yaml -n dev

# Namespace별 리소스 확인
kubectl get pods -n dev
kubectl get all -n prod

ConfigMap & Secret

환경별로 다른 설정값을 관리한다.

구분 ConfigMap Secret
용도 일반 설정값 민감한 정보 (비밀번호, API 키)
저장 방식 평문 Base64 인코딩
예시 DB 호스트, 로그 레벨 DB 비밀번호, JWT 시크릿
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  DATABASE_HOST: "mysql-service"
  LOG_LEVEL: "INFO"
  SPRING_PROFILES_ACTIVE: "production"

---
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: app-secret
type: Opaque
data:
  DATABASE_PASSWORD: cGFzc3dvcmQxMjM=  # base64 encoded
  JWT_SECRET: c2VjcmV0LWtleS0xMjM0NTY=

Pod에서 사용하는 방법:

spec:
  containers:
  - name: app
    image: my-app:1.0
    envFrom:
    - configMapRef:
        name: app-config
    - secretRef:
        name: app-secret
    # 또는 개별 환경변수로
    env:
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: app-secret
          key: DATABASE_PASSWORD

Volume & PersistentVolume

Pod는 일시적이다. 데이터를 영구 저장하려면 Volume을 사용해야 한다.

Volume 종류 설명 수명
emptyDir Pod 내 컨테이너간 임시 공유 Pod 삭제 시 소멸
hostPath Node의 파일시스템 마운트 Node에 종속적
PersistentVolume 클러스터 레벨의 영구 스토리지 독립적 (권장)

PV / PVC 구조

PV(PersistentVolume)는 인프라 관리자가 프로비저닝하고, PVC(PersistentVolumeClaim)는 개발자가 요청한다.

구성요소 역할 관리 주체
Pod volumeMounts로 PVC 연결 개발자
PVC 스토리지 요청 (예: 10Gi) 개발자
PV 실제 스토리지 (AWS EBS, NFS 등) 인프라 관리자
# pv.yaml - 인프라 관리자가 생성
apiVersion: v1
kind: PersistentVolume
metadata:
  name: my-pv
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: standard
  hostPath:
    path: /data/my-pv

---
# pvc.yaml - 개발자가 생성
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  storageClassName: standard

---
# Pod에서 PVC 사용
spec:
  containers:
  - name: app
    volumeMounts:
    - name: data
      mountPath: /app/data
  volumes:
  - name: data
    persistentVolumeClaim:
      claimName: my-pvc

오토스케일링

HPA (Horizontal Pod Autoscaler)

CPU/메모리 사용량에 따라 Pod 수를 자동 조절한다.

HPA는 CPU/메모리 사용률을 모니터링하여 Pod 수를 자동 조절한다.

CPU 사용률 동작 Pod 수
50% 이하 Scale Down 1개로 축소
50% 목표 유지 3개 유지
80% 이상 Scale Up 5개로 확장
# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 70

실전: Spring Boot 앱 배포

전체 구성을 한 번에 보자.

# 1. Namespace
apiVersion: v1
kind: Namespace
metadata:
  name: my-app

---
# 2. ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: my-app
data:
  SPRING_PROFILES_ACTIVE: "production"
  SERVER_PORT: "8080"

---
# 3. Secret
apiVersion: v1
kind: Secret
metadata:
  name: app-secret
  namespace: my-app
type: Opaque
data:
  DB_PASSWORD: cGFzc3dvcmQxMjM=

---
# 4. Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: app
        image: my-app:1.0.0
        ports:
        - containerPort: 8080
        envFrom:
        - configMapRef:
            name: app-config
        - secretRef:
            name: app-secret
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5

---
# 5. Service
apiVersion: v1
kind: Service
metadata:
  name: my-app-service
  namespace: my-app
spec:
  selector:
    app: my-app
  ports:
  - port: 80
    targetPort: 8080

---
# 6. Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  namespace: my-app
spec:
  ingressClassName: nginx
  rules:
  - host: my-app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-app-service
            port:
              number: 80

---
# 7. HPA
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-app-hpa
  namespace: my-app
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50

kubectl 명령어 정리

# 리소스 조회
kubectl get pods                     # Pod 목록
kubectl get pods -o wide             # 상세 정보 (Node, IP)
kubectl get all                      # 모든 리소스
kubectl get all -n my-namespace      # 특정 Namespace

# 리소스 상세 정보
kubectl describe pod my-pod
kubectl describe deployment my-app

# 로그 확인
kubectl logs my-pod                  # Pod 로그
kubectl logs -f my-pod               # 실시간 로그
kubectl logs my-pod -c my-container  # 특정 컨테이너 로그

# 리소스 생성/수정/삭제
kubectl apply -f deployment.yaml     # 생성 또는 수정
kubectl delete -f deployment.yaml    # 삭제
kubectl delete pod my-pod            # Pod 삭제

# 스케일링
kubectl scale deployment my-app --replicas=5

# 롤아웃 (배포)
kubectl rollout status deployment my-app   # 배포 상태
kubectl rollout history deployment my-app  # 배포 이력
kubectl rollout undo deployment my-app     # 롤백

# Pod 접속
kubectl exec -it my-pod -- /bin/bash

# 포트 포워딩 (로컬 테스트)
kubectl port-forward pod/my-pod 8080:8080
kubectl port-forward svc/my-service 8080:80

자주 묻는 질문

Pod가 계속 CrashLoopBackOff 상태인데요?

컨테이너가 시작 후 바로 종료되는 상황이다.

# 로그 확인
kubectl logs my-pod --previous

# 이벤트 확인
kubectl describe pod my-pod

흔한 원인:

  • 애플리케이션 에러 (설정 누락, 포트 충돌)
  • 리소스 부족 (메모리 OOM)
  • liveness probe 실패

ImagePullBackOff는 뭔가요?

이미지를 Pull 받지 못하는 상태다.

확인 사항:

  • 이미지 이름/태그가 정확한지
  • Private 레지스트리면 imagePullSecrets 설정했는지
  • 네트워크 문제는 없는지

Pod가 Pending 상태에서 안 넘어가요

Scheduler가 Pod를 배치할 Node를 찾지 못한 상태다.

kubectl describe pod my-pod
# Events 섹션에서 원인 확인

흔한 원인:

  • 리소스 부족 (CPU, Memory 요청량 > 가용량)
  • nodeSelector가 매칭되는 Node가 없음
  • PVC가 바인딩되지 않음

Service로 Pod에 접근이 안 돼요

# Endpoints 확인 (연결된 Pod IP 목록)
kubectl get endpoints my-service

# 없으면 selector가 Pod label과 일치하는지 확인
kubectl get pods --show-labels

StatefulSet은 뭔가요? Deployment랑 뭐가 다른가요?

StatefulSet은 상태를 가진 애플리케이션용이다. DB, 메시지 큐 같은 것들.

구분 Deployment StatefulSet
Pod 이름 랜덤 (my-app-abc123) 순차 (my-app-0, my-app-1)
스토리지 공유 또는 없음 Pod마다 고유 PVC
생성/삭제 순서 동시 순차 (0→1→2, 2→1→0)
용도 Stateless 앱 (API 서버) Stateful 앱 (DB, Kafka)

대부분의 Spring Boot 앱은 Deployment로 충분하다. StatefulSet은 DB 클러스터 같은 특수한 경우에만 쓴다.

로컬에서 K8s 테스트하려면 어떻게 하나요?

여러 옵션이 있다.

도구 특징 추천
Docker Desktop 가장 쉬움. 설정에서 K8s 활성화 입문자
minikube 단일 노드 클러스터. 다양한 드라이버 지원 학습용
kind Docker 컨테이너로 클러스터 구성. 가벼움 CI/CD
k3s 경량 K8s. ARM 지원 라즈베리파이, Edge
# minikube 예시
minikube start
kubectl get nodes
minikube dashboard  # 웹 대시보드

# kind 예시
kind create cluster --name my-cluster
kubectl cluster-info

Helm이 뭔가요?

K8s용 패키지 매니저다. 여러 YAML 파일을 하나의 Chart로 묶어서 관리한다.

# nginx 설치 예시
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install my-nginx bitnami/nginx

# 값 커스텀
helm install my-nginx bitnami/nginx --set replicaCount=3

ConfigMap, Secret, Deployment, Service 등을 일일이 만들 필요 없이 Chart 하나로 설치 가능. 버전 관리, 롤백도 쉽다.

정리

Kubernetes 핵심 개념:

  • Pod: 최소 배포 단위, 일시적(Ephemeral)
  • Deployment: Pod 배포 관리, 롤링 업데이트/롤백
  • Service: Pod에 대한 고정 엔드포인트, 로드밸런싱
  • Ingress: HTTP 라우팅, 도메인/경로 기반
  • ConfigMap/Secret: 설정값 외부화
  • PV/PVC: 영구 스토리지
  • HPA: CPU/메모리 기반 오토스케일링
  • Namespace: 클러스터 논리적 분리

선언형(Declarative) 방식으로 원하는 상태를 YAML에 기술하면, K8s가 알아서 맞춰주는 게 핵심이다.

참고문헌

프로필 이미지
@chani
바둑, 스타크래프트 등 고전 게임을 좋아하는 내향인 개발자입니다

댓글