08. Kubernetes 심화

08. Kubernetes 심화

ν•™μŠ΅ λͺ©ν‘œ

  • Ingressλ₯Ό ν†΅ν•œ μ™ΈλΆ€ νŠΈλž˜ν”½ λΌμš°νŒ…
  • StatefulSet으둜 μƒνƒœ μžˆλŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 관리
  • PersistentVolume/PersistentVolumeClaim ν™œμš©
  • ConfigMapκ³Ό Secret κ³ κΈ‰ μ‚¬μš©λ²•
  • DaemonSetκ³Ό Job ν™œμš©

λͺ©μ°¨

  1. Ingress
  2. StatefulSet
  3. 영ꡬ μŠ€ν† λ¦¬μ§€
  4. ConfigMap κ³ κΈ‰
  5. DaemonSetκ³Ό Job
  6. κ³ κΈ‰ μŠ€μΌ€μ€„λ§
  7. μ—°μŠ΅ 문제

1. Ingress

1.1 Ingress κ°œλ…

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Ingress μ•„ν‚€ν…μ²˜                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                             β”‚
β”‚   인터넷                                                    β”‚
β”‚      β”‚                                                      β”‚
β”‚      β–Ό                                                      β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”             β”‚
β”‚  β”‚         Ingress Controller                 β”‚             β”‚
β”‚  β”‚    (nginx, traefik, haproxy λ“±)           β”‚             β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜             β”‚
β”‚                      β”‚                                      β”‚
β”‚        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                       β”‚
β”‚        β”‚             β”‚             β”‚                        β”‚
β”‚        β–Ό             β–Ό             β–Ό                        β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”                   β”‚
β”‚   β”‚Ingress  β”‚  β”‚Ingress  β”‚  β”‚Ingress  β”‚                   β”‚
β”‚   β”‚Resource β”‚  β”‚Resource β”‚  β”‚Resource β”‚                   β”‚
β”‚   β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜                   β”‚
β”‚        β”‚             β”‚             β”‚                        β”‚
β”‚        β–Ό             β–Ό             β–Ό                        β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”                   β”‚
β”‚   β”‚Service Aβ”‚  β”‚Service Bβ”‚  β”‚Service Cβ”‚                   β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                   β”‚
β”‚                                                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

1.2 Ingress Controller μ„€μΉ˜

# NGINX Ingress Controller μ„€μΉ˜
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/provider/cloud/deploy.yaml

# μ„€μΉ˜ 확인
kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx

# IngressClass 확인
kubectl get ingressclass

1.3 κΈ°λ³Έ Ingress

# simple-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: simple-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port:
              number: 80

---
# 호슀트 기반 λΌμš°νŒ…
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: host-based-ingress
spec:
  ingressClassName: nginx
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 8080
  - host: admin.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: admin-service
            port:
              number: 3000

1.4 경둜 기반 λΌμš°νŒ…

# path-based-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: path-based-ingress
  annotations:
    nginx.ingress.kubernetes.io/use-regex: "true"
spec:
  ingressClassName: nginx
  rules:
  - host: app.example.com
    http:
      paths:
      # /api/* β†’ api-service
      - path: /api(/|$)(.*)
        pathType: ImplementationSpecific
        backend:
          service:
            name: api-service
            port:
              number: 8080
      # /static/* β†’ static-service
      - path: /static
        pathType: Prefix
        backend:
          service:
            name: static-service
            port:
              number: 80
      # κΈ°λ³Έ β†’ frontend
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port:
              number: 80

1.5 TLS μ„€μ •

# tls-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: tls-ingress
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - secure.example.com
    secretName: tls-secret  # TLS Secret
  rules:
  - host: secure.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: secure-service
            port:
              number: 443

---
# TLS Secret 생성
apiVersion: v1
kind: Secret
metadata:
  name: tls-secret
type: kubernetes.io/tls
data:
  tls.crt: <base64-encoded-cert>
  tls.key: <base64-encoded-key>

1.6 κ³ κΈ‰ Ingress μ„€μ •

# advanced-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: advanced-ingress
  annotations:
    # κΈ°λ³Έ μ„€μ •
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "60"

    # Rate Limiting
    nginx.ingress.kubernetes.io/limit-rps: "100"
    nginx.ingress.kubernetes.io/limit-connections: "50"

    # CORS
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/cors-allow-origin: "https://frontend.example.com"
    nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS"

    # 인증
    nginx.ingress.kubernetes.io/auth-type: basic
    nginx.ingress.kubernetes.io/auth-secret: basic-auth
    nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"

    # μ»€μŠ€ν…€ 헀더
    nginx.ingress.kubernetes.io/configuration-snippet: |
      add_header X-Frame-Options "SAMEORIGIN";
      add_header X-Content-Type-Options "nosniff";

spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - api.example.com
    secretName: api-tls
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 8080

2. StatefulSet

2.1 StatefulSet κ°œλ…

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            StatefulSet vs Deployment                         β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                             β”‚
β”‚  Deployment (Stateless)                                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”                             β”‚
β”‚  β”‚pod-xyzβ”‚ β”‚pod-abcβ”‚ β”‚pod-123β”‚  랜덀 이름, ꡐ체 κ°€λŠ₯        β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”˜                             β”‚
β”‚                                                             β”‚
β”‚  StatefulSet (Stateful)                                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”                             β”‚
β”‚  β”‚web-0  β”‚ β”‚web-1  β”‚ β”‚web-2  β”‚  μˆœμ„œ 보μž₯, 고유 ID         β”‚
β”‚  β”‚  ↓    β”‚ β”‚  ↓    β”‚ β”‚  ↓    β”‚                             β”‚
β”‚  β”‚pvc-0  β”‚ β”‚pvc-1  β”‚ β”‚pvc-2  β”‚  각자 μ „μš© μŠ€ν† λ¦¬μ§€         β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”˜                             β”‚
β”‚                                                             β”‚
β”‚  νŠΉμ§•:                                                      β”‚
β”‚  β€’ μˆœμ„œλŒ€λ‘œ 생성/μ‚­μ œ (0 β†’ 1 β†’ 2)                          β”‚
β”‚  β€’ κ³ μ •λœ λ„€νŠΈμ›Œν¬ ID (pod-name.service-name)              β”‚
β”‚  β€’ 영ꡬ μŠ€ν† λ¦¬μ§€ μ—°κ²°                                       β”‚
β”‚                                                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2.2 StatefulSet μ •μ˜

# statefulset-example.yaml
apiVersion: v1
kind: Service
metadata:
  name: web-headless
  labels:
    app: web
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None  # Headless Service
  selector:
    app: web

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "web-headless"  # Headless Service μ—°κ²°
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: nginx
        image: nginx:1.25
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html

  # λ³Όλ₯¨ ν΄λ ˆμž„ ν…œν”Œλ¦Ώ
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: standard
      resources:
        requests:
          storage: 1Gi

  # μ—…λ°μ΄νŠΈ μ „λž΅
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 0  # 이 번호 μ΄μƒμ˜ Pod만 μ—…λ°μ΄νŠΈ

  # Pod 관리 μ •μ±…
  podManagementPolicy: OrderedReady  # λ˜λŠ” Parallel

2.3 λ°μ΄ν„°λ² μ΄μŠ€ StatefulSet

# mysql-statefulset.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-config
data:
  my.cnf: |
    [mysqld]
    bind-address = 0.0.0.0
    default_authentication_plugin = mysql_native_password

---
apiVersion: v1
kind: Secret
metadata:
  name: mysql-secret
type: Opaque
stringData:
  root-password: "rootpass123"
  user-password: "userpass123"

---
apiVersion: v1
kind: Service
metadata:
  name: mysql-headless
spec:
  clusterIP: None
  selector:
    app: mysql
  ports:
  - port: 3306

---
apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  selector:
    app: mysql
    statefulset.kubernetes.io/pod-name: mysql-0  # Primary만
  ports:
  - port: 3306

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql-headless
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      initContainers:
      - name: init-mysql
        image: mysql:8.0
        command:
        - bash
        - "-c"
        - |
          set -ex
          # Pod μΈλ±μŠ€μ—μ„œ server-id 생성
          [[ `hostname` =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          echo [mysqld] > /mnt/conf.d/server-id.cnf
          echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
        volumeMounts:
        - name: conf
          mountPath: /mnt/conf.d

      containers:
      - name: mysql
        image: mysql:8.0
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: root-password
        ports:
        - containerPort: 3306
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
        - name: config
          mountPath: /etc/mysql/my.cnf
          subPath: my.cnf
        resources:
          requests:
            cpu: 500m
            memory: 1Gi
        livenessProbe:
          exec:
            command: ["mysqladmin", "ping"]
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          exec:
            command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"]
          initialDelaySeconds: 5
          periodSeconds: 2

      volumes:
      - name: conf
        emptyDir: {}
      - name: config
        configMap:
          name: mysql-config

  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: fast
      resources:
        requests:
          storage: 10Gi

2.4 StatefulSet 관리

# StatefulSet 쑰회
kubectl get statefulset
kubectl describe statefulset web

# Pod 확인 (μˆœμ„œλŒ€λ‘œ 이름 λΆ€μ—¬)
kubectl get pods -l app=web
# NAME    READY   STATUS
# web-0   1/1     Running
# web-1   1/1     Running
# web-2   1/1     Running

# DNS 확인
# 각 Pod: web-0.web-headless.default.svc.cluster.local
kubectl run -it --rm debug --image=busybox -- nslookup web-0.web-headless

# μŠ€μΌ€μΌλ§
kubectl scale statefulset web --replicas=5

# 둀링 μ—…λ°μ΄νŠΈ
kubectl set image statefulset/web nginx=nginx:1.26

# νŠΉμ • Pod만 μ—…λ°μ΄νŠΈ (partition μ‚¬μš©)
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":2}}}}'

# μ‚­μ œ (PVCλŠ” μœ μ§€λ¨)
kubectl delete statefulset web
kubectl delete pvc -l app=web  # PVC μ‚­μ œ

3. 영ꡬ μŠ€ν† λ¦¬μ§€

3.1 μŠ€ν† λ¦¬μ§€ 계측 ꡬ쑰

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    μŠ€ν† λ¦¬μ§€ 계측 ꡬ쑰                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”               β”‚
β”‚  β”‚              Pod                         β”‚               β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚               β”‚
β”‚  β”‚  β”‚     Volume Mount                 β”‚   β”‚               β”‚
β”‚  β”‚  β”‚     /data                        β”‚   β”‚               β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚               β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜               β”‚
β”‚                    β”‚                                        β”‚
β”‚                    β–Ό                                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”               β”‚
β”‚  β”‚     PersistentVolumeClaim (PVC)         β”‚               β”‚
β”‚  β”‚     β€’ μŠ€ν† λ¦¬μ§€ μš”μ²­                      β”‚               β”‚
β”‚  β”‚     β€’ λ„€μž„μŠ€νŽ˜μ΄μŠ€ λ²”μœ„                  β”‚               β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜               β”‚
β”‚                    β”‚ 바인딩                                 β”‚
β”‚                    β–Ό                                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”               β”‚
β”‚  β”‚     PersistentVolume (PV)               β”‚               β”‚
β”‚  β”‚     β€’ μ‹€μ œ μŠ€ν† λ¦¬μ§€                      β”‚               β”‚
β”‚  β”‚     β€’ ν΄λŸ¬μŠ€ν„° λ²”μœ„                      β”‚               β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜               β”‚
β”‚                    β”‚                                        β”‚
β”‚                    β–Ό                                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”               β”‚
β”‚  β”‚     StorageClass                        β”‚               β”‚
β”‚  β”‚     β€’ 동적 ν”„λ‘œλΉ„μ €λ‹                    β”‚               β”‚
β”‚  β”‚     β€’ μŠ€ν† λ¦¬μ§€ μœ ν˜• μ •μ˜                 β”‚               β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜               β”‚
β”‚                                                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

3.2 StorageClass

# storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: kubernetes.io/gce-pd  # ν΄λΌμš°λ“œμ— 따라 닀름
parameters:
  type: pd-ssd
reclaimPolicy: Delete  # Delete, Retain
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer  # Immediate

---
# AWS EBS StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: aws-fast
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
reclaimPolicy: Delete
allowVolumeExpansion: true

---
# 둜컬 StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer

3.3 PersistentVolume (정적 ν”„λ‘œλΉ„μ €λ‹)

# pv-static.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-manual
  labels:
    type: local
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: manual
  hostPath:
    path: /mnt/data

---
# NFS PV
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-nfs
spec:
  capacity:
    storage: 100Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: nfs
  nfs:
    server: nfs-server.example.com
    path: /exports/data

---
# AWS EBS PV
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-aws-ebs
spec:
  capacity:
    storage: 50Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: aws-fast
  awsElasticBlockStore:
    volumeID: vol-0123456789abcdef
    fsType: ext4

3.4 PersistentVolumeClaim

# pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-data-pvc
  namespace: production
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  storageClassName: fast  # 동적 ν”„λ‘œλΉ„μ €λ‹
  # selector:             # 정적 바인딩 μ‹œ μ‚¬μš©
  #   matchLabels:
  #     type: local

---
# Podμ—μ„œ PVC μ‚¬μš©
apiVersion: v1
kind: Pod
metadata:
  name: app-with-storage
spec:
  containers:
  - name: app
    image: myapp:latest
    volumeMounts:
    - name: data
      mountPath: /app/data
  volumes:
  - name: data
    persistentVolumeClaim:
      claimName: app-data-pvc

3.5 λ³Όλ₯¨ ν™•μž₯ 및 μŠ€λƒ…μƒ·

# PVC ν™•μž₯ (allowVolumeExpansion: true ν•„μš”)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: expandable-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi  # 5Giμ—μ„œ ν™•μž₯
  storageClassName: fast

---
# VolumeSnapshot (CSI λ“œλΌμ΄λ²„ ν•„μš”)
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: data-snapshot
spec:
  volumeSnapshotClassName: csi-snapclass
  source:
    persistentVolumeClaimName: app-data-pvc

---
# μŠ€λƒ…μƒ·μ—μ„œ PVC 볡원
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: restored-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  storageClassName: fast
  dataSource:
    name: data-snapshot
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io

4. ConfigMap κ³ κΈ‰

4.1 ConfigMap 생성 방법

# λ¦¬ν„°λŸ΄λ‘œ 생성
kubectl create configmap app-config \
  --from-literal=LOG_LEVEL=info \
  --from-literal=MAX_CONNECTIONS=100

# 파일둜 생성
kubectl create configmap nginx-config \
  --from-file=nginx.conf

# λ””λ ‰ν† λ¦¬λ‘œ 생성
kubectl create configmap app-configs \
  --from-file=config/
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  # λ‹¨μˆœ ν‚€-κ°’
  LOG_LEVEL: "info"
  DATABASE_HOST: "db.example.com"

  # 파일 ν˜•νƒœ
  app.properties: |
    server.port=8080
    server.host=0.0.0.0
    logging.level=INFO

  nginx.conf: |
    server {
        listen 80;
        server_name localhost;

        location / {
            root /usr/share/nginx/html;
            index index.html;
        }

        location /api {
            proxy_pass http://backend:8080;
        }
    }

4.2 ConfigMap μ‚¬μš© 방법

# configmap-usage.yaml
apiVersion: v1
kind: Pod
metadata:
  name: app-pod
spec:
  containers:
  - name: app
    image: myapp:latest

    # ν™˜κ²½ λ³€μˆ˜λ‘œ μ£Όμž…
    env:
    - name: LOG_LEVEL
      valueFrom:
        configMapKeyRef:
          name: app-config
          key: LOG_LEVEL

    # 전체 ConfigMap을 ν™˜κ²½ λ³€μˆ˜λ‘œ
    envFrom:
    - configMapRef:
        name: app-config
      prefix: APP_  # 선택적 접두사

    volumeMounts:
    # 파일둜 마운트
    - name: config-volume
      mountPath: /etc/app
    # νŠΉμ • ν‚€λ§Œ 마운트
    - name: nginx-volume
      mountPath: /etc/nginx/conf.d

  volumes:
  - name: config-volume
    configMap:
      name: app-config
      # 전체 ν•­λͺ©
  - name: nginx-volume
    configMap:
      name: app-config
      items:
      - key: nginx.conf
        path: default.conf
        mode: 0644

4.3 ConfigMap λ³€κ²½ 감지

# μžλ™ λ¦¬λ‘œλ“œ (Reloader μ‚¬μš©)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-deployment
  annotations:
    # stakater/Reloader μ–΄λ…Έν…Œμ΄μ…˜
    reloader.stakater.com/auto: "true"
    # λ˜λŠ” νŠΉμ • ConfigMap만
    configmap.reloader.stakater.com/reload: "app-config"
spec:
  template:
    spec:
      containers:
      - name: app
        image: myapp:latest
        volumeMounts:
        - name: config
          mountPath: /etc/app
      volumes:
      - name: config
        configMap:
          name: app-config

---
# Sidecar둜 λ³€κ²½ 감지
apiVersion: v1
kind: Pod
metadata:
  name: app-with-reloader
spec:
  containers:
  - name: app
    image: myapp:latest
    volumeMounts:
    - name: config
      mountPath: /etc/app

  - name: config-reloader
    image: jimmidyson/configmap-reload:v0.5.0
    args:
    - --volume-dir=/etc/app
    - --webhook-url=http://localhost:8080/-/reload
    volumeMounts:
    - name: config
      mountPath: /etc/app
      readOnly: true

  volumes:
  - name: config
    configMap:
      name: app-config

5. DaemonSetκ³Ό Job

5.1 DaemonSet

# daemonset.yaml
# λͺ¨λ“  λ…Έλ“œμ— Pod 배포
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-exporter
  labels:
    app: node-exporter
spec:
  selector:
    matchLabels:
      app: node-exporter
  template:
    metadata:
      labels:
        app: node-exporter
    spec:
      # νŠΉμ • λ…Έλ“œμ—λ§Œ 배포
      nodeSelector:
        monitoring: "true"

      tolerations:
      # λ§ˆμŠ€ν„° λ…Έλ“œμ—λ„ 배포
      - key: node-role.kubernetes.io/control-plane
        operator: Exists
        effect: NoSchedule

      containers:
      - name: node-exporter
        image: prom/node-exporter:v1.6.1
        ports:
        - containerPort: 9100
          hostPort: 9100
        volumeMounts:
        - name: proc
          mountPath: /host/proc
          readOnly: true
        - name: sys
          mountPath: /host/sys
          readOnly: true
        resources:
          limits:
            cpu: 200m
            memory: 100Mi
          requests:
            cpu: 100m
            memory: 50Mi

      hostNetwork: true
      hostPID: true

      volumes:
      - name: proc
        hostPath:
          path: /proc
      - name: sys
        hostPath:
          path: /sys

  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1

5.2 Job

# job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: data-migration
spec:
  # μ™„λ£Œ 쑰건
  completions: 1        # 성곡해야 ν•  Pod 수
  parallelism: 1        # λ™μ‹œ μ‹€ν–‰ Pod 수
  backoffLimit: 3       # μ‹€νŒ¨ μ‹œ μž¬μ‹œλ„ 횟수
  activeDeadlineSeconds: 600  # μ΅œλŒ€ μ‹€ν–‰ μ‹œκ°„

  # μ™„λ£Œ ν›„ μ‚­μ œ
  ttlSecondsAfterFinished: 3600

  template:
    spec:
      restartPolicy: Never  # OnFailure λ˜λŠ” Never
      containers:
      - name: migrator
        image: myapp/migrator:latest
        command: ["python", "migrate.py"]
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: url

---
# 병렬 Job
apiVersion: batch/v1
kind: Job
metadata:
  name: parallel-processing
spec:
  completions: 10     # 총 10개 μ™„λ£Œ
  parallelism: 3      # λ™μ‹œμ— 3κ°œμ”©
  template:
    spec:
      restartPolicy: OnFailure
      containers:
      - name: worker
        image: myapp/worker:latest

5.3 CronJob

# cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: db-backup
spec:
  schedule: "0 2 * * *"  # 맀일 μƒˆλ²½ 2μ‹œ
  timeZone: "Asia/Seoul"

  # λ™μ‹œ μ‹€ν–‰ μ •μ±…
  concurrencyPolicy: Forbid  # Allow, Forbid, Replace

  # μ‹œμž‘ λ°λ“œλΌμΈ
  startingDeadlineSeconds: 300

  # 성곡/μ‹€νŒ¨ νžˆμŠ€ν† λ¦¬
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 1

  # μΌμ‹œ 쀑지
  suspend: false

  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
          - name: backup
            image: postgres:15
            command:
            - /bin/sh
            - -c
            - |
              pg_dump -h $DB_HOST -U $DB_USER -d $DB_NAME > /backup/db_$(date +%Y%m%d).sql
              aws s3 cp /backup/db_$(date +%Y%m%d).sql s3://backups/
            env:
            - name: DB_HOST
              value: "postgres"
            - name: DB_USER
              valueFrom:
                secretKeyRef:
                  name: db-secret
                  key: username
            - name: PGPASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-secret
                  key: password
            volumeMounts:
            - name: backup
              mountPath: /backup
          volumes:
          - name: backup
            emptyDir: {}

6. κ³ κΈ‰ μŠ€μΌ€μ€„λ§

6.1 Node Affinity

# node-affinity.yaml
apiVersion: v1
kind: Pod
metadata:
  name: gpu-pod
spec:
  affinity:
    nodeAffinity:
      # ν•„μˆ˜ 쑰건
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: gpu
            operator: In
            values:
            - "true"
          - key: kubernetes.io/arch
            operator: In
            values:
            - amd64

      # μ„ ν˜Έ 쑰건
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        preference:
          matchExpressions:
          - key: gpu-type
            operator: In
            values:
            - nvidia-a100
      - weight: 50
        preference:
          matchExpressions:
          - key: gpu-type
            operator: In
            values:
            - nvidia-v100

  containers:
  - name: gpu-app
    image: nvidia/cuda:12.0-base
    resources:
      limits:
        nvidia.com/gpu: 1

6.2 Pod Affinity/Anti-Affinity

# pod-affinity.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      affinity:
        # μΊμ‹œ Pod와 같은 λ…Έλ“œ μ„ ν˜Έ
        podAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - cache
              topologyKey: kubernetes.io/hostname

        # 같은 μ•±μ˜ λ‹€λ₯Έ Pod와 λ‹€λ₯Έ λ…Έλ“œμ— 배치
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - web
            topologyKey: kubernetes.io/hostname

      containers:
      - name: web
        image: nginx:latest

6.3 Taints와 Tolerations

# λ…Έλ“œμ— Taint μΆ”κ°€
kubectl taint nodes node1 dedicated=gpu:NoSchedule
kubectl taint nodes node2 special=true:PreferNoSchedule
kubectl taint nodes node3 critical=true:NoExecute

# Taint 제거
kubectl taint nodes node1 dedicated=gpu:NoSchedule-
# tolerations.yaml
apiVersion: v1
kind: Pod
metadata:
  name: gpu-pod
spec:
  tolerations:
  # μ •ν™•νžˆ 일치
  - key: "dedicated"
    operator: "Equal"
    value: "gpu"
    effect: "NoSchedule"

  # ν‚€λ§Œ μ‘΄μž¬ν•˜λ©΄ 됨
  - key: "special"
    operator: "Exists"
    effect: "PreferNoSchedule"

  # NoExecute + tolerationSeconds
  - key: "critical"
    operator: "Equal"
    value: "true"
    effect: "NoExecute"
    tolerationSeconds: 3600  # 1μ‹œκ°„ ν›„ μΆ•μΆœ

  containers:
  - name: app
    image: myapp:latest

6.4 Topology Spread Constraints

# topology-spread.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: distributed-app
spec:
  replicas: 6
  selector:
    matchLabels:
      app: distributed
  template:
    metadata:
      labels:
        app: distributed
    spec:
      topologySpreadConstraints:
      # Zone κ°„ κ· λ“± λΆ„λ°°
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: distributed

      # λ…Έλ“œ κ°„ κ· λ“± λΆ„λ°°
      - maxSkew: 1
        topologyKey: kubernetes.io/hostname
        whenUnsatisfiable: ScheduleAnyway
        labelSelector:
          matchLabels:
            app: distributed

      containers:
      - name: app
        image: myapp:latest

7. μ—°μŠ΅ 문제

μ—°μŠ΅ 1: λ§ˆμ΄ν¬λ‘œμ„œλΉ„μŠ€ Ingress

# μš”κ΅¬μ‚¬ν•­:
# - api.example.com/v1/* β†’ api-v1-service
# - api.example.com/v2/* β†’ api-v2-service
# - TLS 적용, HTTPβ†’HTTPS λ¦¬λ‹€μ΄λ ‰νŠΈ
# - Rate limiting 적용

# Ingress μž‘μ„±

μ—°μŠ΅ 2: Redis Cluster StatefulSet

# μš”κ΅¬μ‚¬ν•­:
# - 3개 λ…Έλ“œ Redis Cluster
# - 각 λ…Έλ“œμ— 1Gi PVC
# - Headless Service둜 λ…Έλ“œ κ°„ 톡신
# - μ μ ˆν•œ λ¦¬μ†ŒμŠ€ μ œν•œ

# StatefulSet μž‘μ„±

μ—°μŠ΅ 3: 둜그 μˆ˜μ§‘ DaemonSet

# μš”κ΅¬μ‚¬ν•­:
# - λͺ¨λ“  λ…Έλ“œμ—μ„œ /var/log μˆ˜μ§‘
# - Elasticsearch둜 전솑
# - ConfigMap으둜 μ„€μ • 관리
# - λ§ˆμŠ€ν„° λ…Έλ“œμ—λ„ 배포

# DaemonSet μž‘μ„±

μ—°μŠ΅ 4: 배치 데이터 처리 Job

# μš”κ΅¬μ‚¬ν•­:
# - 100개 데이터 처리 (completions: 100)
# - 10κ°œμ”© 병렬 처리 (parallelism: 10)
# - μ‹€νŒ¨ μ‹œ 3회 μž¬μ‹œλ„
# - 2μ‹œκ°„ λ‚΄ μ™„λ£Œ ν•„μˆ˜
# - μ™„λ£Œ ν›„ 24μ‹œκ°„ λ’€ μ‚­μ œ

# Job μž‘μ„±

λ‹€μŒ 단계

참고 자료


← 이전: Kubernetes λ³΄μ•ˆ | λ‹€μŒ: Helm νŒ¨ν‚€μ§€κ΄€λ¦¬ β†’ | λͺ©μ°¨

to navigate between lessons