☁️ 클라우드

Kubernetes

K8s / 쿠버네티스

컨테이너 오케스트레이션 표준. Google 개발. 자동 배포, 스케일링, 복구.

📖 상세 설명

Kubernetes(쿠버네티스, K8s)는 컨테이너화된 애플리케이션의 배포, 스케일링, 관리를 자동화하는 오픈소스 플랫폼입니다. Google이 내부에서 사용하던 Borg 시스템을 기반으로 개발했으며, 현재는 CNCF(Cloud Native Computing Foundation)에서 관리합니다. 이름은 그리스어로 '조타수'를 의미합니다.

핵심 개념으로 Pod는 가장 작은 배포 단위로, 하나 이상의 컨테이너를 포함합니다. Deployment는 Pod의 선언적 업데이트를 관리하며, 롤링 업데이트와 롤백을 지원합니다. Service는 Pod 집합에 대한 네트워크 접근을 제공하고, Ingress는 외부에서 클러스터 내 서비스로의 HTTP/HTTPS 라우팅을 정의합니다.

아키텍처는 Control Plane과 Worker Node로 구성됩니다. Control Plane에는 API Server(모든 요청의 진입점), etcd(클러스터 상태 저장), Scheduler(Pod 배치 결정), Controller Manager(상태 감시 및 조정)가 있습니다. Worker Node에는 kubelet(Pod 라이프사이클 관리), kube-proxy(네트워크 규칙 관리), Container Runtime(Docker, containerd 등)이 실행됩니다.

주요 클라우드 관리형 서비스로 AWS EKS, GCP GKE, Azure AKS가 있습니다. 관리형 서비스는 Control Plane을 자동 관리해주어 운영 부담을 줄여주고, 클라우드 서비스와의 통합(IAM, 로드밸런서, 스토리지)이 용이합니다.

💻 코드 예제

# 클러스터 정보 확인
kubectl cluster-info
kubectl get nodes -o wide

# 네임스페이스 생성 및 기본 설정
kubectl create namespace production
kubectl config set-context --current --namespace=production

# Pod 생성 및 관리
kubectl run nginx --image=nginx:1.25 --port=80
kubectl get pods -o wide
kubectl describe pod nginx
kubectl logs nginx -f
kubectl exec -it nginx -- /bin/bash

# Deployment 생성 (명령형)
kubectl create deployment web --image=nginx:1.25 --replicas=3

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

# 롤링 업데이트
kubectl set image deployment/web nginx=nginx:1.26
kubectl rollout status deployment/web
kubectl rollout history deployment/web

# 롤백
kubectl rollout undo deployment/web
kubectl rollout undo deployment/web --to-revision=2

# 서비스 노출
kubectl expose deployment web --port=80 --type=LoadBalancer

# 리소스 모니터링
kubectl top nodes
kubectl top pods
kubectl get events --sort-by='.lastTimestamp'

# 디버깅
kubectl describe pod 
kubectl logs  --previous   # 이전 컨테이너 로그
kubectl debug  -it --image=busybox

# 리소스 삭제
kubectl delete deployment web
kubectl delete pod nginx --grace-period=0 --force
# deployment.yaml - 프로덕션 웹 애플리케이션
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
  namespace: production
  labels:
    app: web-app
    version: v1.0.0
spec:
  replicas: 3
  revisionHistoryLimit: 5          # 롤백용 히스토리 보관
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1                   # 업데이트 중 추가 Pod
      maxUnavailable: 0             # 항상 최소 replicas 유지
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
        version: v1.0.0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "8080"
    spec:
      terminationGracePeriodSeconds: 30
      serviceAccountName: web-app-sa

      # 토폴로지 분산 (가용영역 분산)
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: ScheduleAnyway
          labelSelector:
            matchLabels:
              app: web-app

      containers:
        - name: web
          image: myregistry/web-app:v1.0.0
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 8080
              protocol: TCP

          # 환경 변수
          env:
            - name: NODE_ENV
              value: "production"
            - name: DB_HOST
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: host
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name

          # 리소스 제한
          resources:
            requests:
              cpu: "250m"
              memory: "256Mi"
            limits:
              cpu: "1000m"
              memory: "512Mi"

          # Liveness Probe - 컨테이너 재시작 결정
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 20
            timeoutSeconds: 5
            failureThreshold: 3

          # Readiness Probe - 트래픽 수신 결정
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
            timeoutSeconds: 3
            successThreshold: 1
            failureThreshold: 3

          # Startup Probe - 시작 시간이 긴 앱용
          startupProbe:
            httpGet:
              path: /health
              port: 8080
            failureThreshold: 30
            periodSeconds: 10

          # 볼륨 마운트
          volumeMounts:
            - name: config
              mountPath: /app/config
              readOnly: true
            - name: tmp
              mountPath: /tmp

      volumes:
        - name: config
          configMap:
            name: web-app-config
        - name: tmp
          emptyDir: {}

      # 이미지 Pull Secret
      imagePullSecrets:
        - name: regcred

---
# HorizontalPodAutoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-app-hpa
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-app
  minReplicas: 3
  maxReplicas: 50
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 10
          periodSeconds: 60
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
        - type: Pods
          value: 4
          periodSeconds: 15
# ClusterIP Service (내부 통신용)
apiVersion: v1
kind: Service
metadata:
  name: web-app
  namespace: production
spec:
  type: ClusterIP
  selector:
    app: web-app
  ports:
    - name: http
      port: 80
      targetPort: 8080
      protocol: TCP

---
# LoadBalancer Service (외부 노출)
apiVersion: v1
kind: Service
metadata:
  name: web-app-lb
  namespace: production
  annotations:
    # AWS NLB 설정
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
    service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
spec:
  type: LoadBalancer
  selector:
    app: web-app
  ports:
    - name: http
      port: 80
      targetPort: 8080
    - name: https
      port: 443
      targetPort: 8080

---
# Ingress (HTTP 라우팅)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web-app-ingress
  namespace: production
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  tls:
    - hosts:
        - api.example.com
        - www.example.com
      secretName: example-tls
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web-app
                port:
                  number: 80
    - host: www.example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api-backend
                port:
                  number: 80
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend
                port:
                  number: 80

---
# NetworkPolicy (네트워크 보안)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: web-app-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: web-app
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              name: ingress-nginx
        - podSelector:
            matchLabels:
              app: frontend
      ports:
        - protocol: TCP
          port: 8080
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: database
      ports:
        - protocol: TCP
          port: 5432
    - to:
        - namespaceSelector: {}
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53

💬 실무에서 이렇게 씁니다

아키텍처 설계 회의

PM: "신규 프로젝트를 Kubernetes에서 운영하려고 하는데, 직접 클러스터를 구축할지 관리형 서비스를 쓸지 고민입니다."

DevOps: "프로덕션 환경이라면 관리형 서비스를 추천드립니다. AWS EKS나 GKE를 쓰면 Control Plane 고가용성, etcd 백업, 버전 업그레이드를 자동으로 처리해줍니다. 직접 구축하면 초기 비용은 절감되지만, 운영 인력과 장애 대응 부담이 큽니다. 저희 팀 규모에서는 관리형으로 시작하고, 트래픽 10만 DAU 넘어가면 멀티 클러스터 전략을 검토하는 게 좋을 것 같습니다."

면접 질문

면접관: "Kubernetes에서 무중단 배포를 어떻게 구현하시나요?"

지원자: "Deployment의 RollingUpdate 전략을 사용합니다. maxSurge를 1, maxUnavailable을 0으로 설정하면 항상 기존 replica 수가 유지됩니다. 핵심은 readinessProbe 설정인데요, 새 Pod가 실제로 트래픽을 처리할 준비가 됐을 때만 Service에 추가되도록 합니다. 또한 preStop hook에 sleep을 넣어 기존 연결이 정리될 시간을 주고, terminationGracePeriodSeconds를 충분히 설정합니다. Blue-Green이 필요하면 Argo Rollouts를 사용하기도 합니다."

장애 대응

개발자: "Pod가 계속 CrashLoopBackOff 상태로 재시작돼요. 무슨 문제일까요?"

SRE: "먼저 kubectl describe pod로 Events를 확인하세요. OOMKilled면 메모리 limit 부족이고, livenessProbe 실패면 앱 상태 확인이 필요해요. kubectl logs --previous로 이전 컨테이너 로그 보시고, 시작 시간이 오래 걸리는 앱이면 startupProbe를 추가하거나 initialDelaySeconds를 늘려보세요. 최근 배포 후 발생했다면 kubectl rollout undo로 롤백하고 원인 분석하는 게 빠릅니다."

⚠️ 주의사항

리소스 요청/제한 미설정

resources.requests와 limits를 설정하지 않으면 노드 리소스를 과다 사용하거나, 스케줄링 실패가 발생합니다. 특히 메모리 limit 없이 운영하면 OOM으로 다른 Pod까지 영향받을 수 있습니다. LimitRange와 ResourceQuota로 네임스페이스별 기본값을 설정하세요.

latest 태그 이미지 사용

이미지 태그에 :latest를 사용하면 어떤 버전이 배포됐는지 추적이 불가능합니다. 롤백도 어렵고, imagePullPolicy: Always와 함께 사용하면 동일 Deployment에서 다른 버전의 Pod가 실행될 수 있습니다. 반드시 고정 버전 태그(v1.2.3 또는 git commit SHA)를 사용하세요.

Pod Disruption Budget으로 가용성 보장

노드 업그레이드나 스케일 다운 시 PDB(PodDisruptionBudget)를 설정하면 최소 가용 Pod 수를 보장합니다. minAvailable이나 maxUnavailable을 설정해 클러스터 유지보수 중에도 서비스가 중단되지 않도록 하세요.

🔗 관련 용어

📚 더 배우기