Cloud/클라우드 프로그래밍

[클라우드 프로그래밍] 10주차

승주우에요 2026. 6. 16. 14:57

쿠버네티스에서 애플리케이션에 설정이 전달되는 과정

쿠버네티스에서 컨테이너에 설정값을 주입하는 데 쓰는 리소스는 **컨피그맵(ConfigMap)**과 비밀값(Secret) 두 가지이며, 두 리소스 모두 포맷 제한 없이 데이터를 보유할 수 있다.

컨피그맵과 비밀값의 데이터는 클러스터 속에서 다른 리소스와 독립적인 장소에 보관된다. 파드 정의에서 이 데이터를 읽어 오도록 할 수 있으며, 데이터가 전달되는 과정에도 다양한 설정이 가능하다. 다른 쿠버네티스 리소스와 마찬가지로 kubectl create 명령을 사용하거나 YAML 정의를 읽어 들여 생성할 수 있다.

다른 리소스와 달리 컨피그맵과 비밀값은 스스로 어떤 기능을 하지 않으며, 단지 적은 양의 데이터를 저장하는 것이 목적이다. 리소스는 파드로 전달되어 컨테이너 환경의 일부가 되고, 이 상태에서 컨테이너가 데이터를 읽을 수 있다.

# 예제 코드로 디렉터리를 이동
cd ch04

# 설정값 없이 sleep 이미지로 파드 실행
kubectl apply -f sleep/sleep.yaml

# 파드가 준비될 때까지 대기
kubectl wait --for=condition=Ready pod -l app=sleep

# 파드 속 컨테이너에 설정된 몇 가지 환경 변수의 값을 확인
kubectl exec deploy/sleep -- printenv HOSTNAME KIAMOL_CHAPTER

💡 모든 컨테이너에는 쿠버네티스 또는 컨테이너 운영체제가 설정한 환경 변수가 기본으로 존재한다.

sleep-with-env.yaml — 환경 변수가 추가된 파드 정의

spec:
  containers:
  - name: sleep
    image: kiamol/ch03-sleep
    env: # 이 아래로 환경 변수가 정의
    - name: KIAMOL_CHAPTER # 새로운 환경 변수의 이름 정의
      value: "04"           # 새로운 환경 변수의 값 정의
# 디플로이먼트를 업데이트
kubectl apply -f sleep/sleep-with-env.yaml

# 조금 전과 같은 환경 변수의 값 확인
kubectl exec deploy/sleep -- printenv HOSTNAME KIAMOL_CHAPTER

컨피그맵은 하나 또는 그 이상의 파드에 연결될 수 있는 독립된 리소스다.

sleep-with-configMap-env.yaml — 파드 정의에서 컨피그맵 읽기

env: # 컨테이너 정의의 환경 변수 부분
- name: KIAMOL_CHAPTER
  value: "04" # 환경 변수의 값
- name: KIAMOL_SECTION
  valueFrom:
    configMapKeyRef: # 이 값은 컨피그맵에서 읽어 들이라는 의미
      name: sleep-config-literal # 컨피그맵 이름
      key: kiamol.section         # 컨피그맵에서 읽어 들일 항목 이름
# 명령행 도구를 사용하여 컨피그맵 생성
kubectl create configmap sleep-config-literal --from-literal=kiamol.section='4.1'

# 컨피그맵에 들어 있는 데이터 확인
kubectl get cm sleep-config-literal

# 컨피그맵의 상세 정보를 보기 좋게 출력
kubectl describe cm sleep-config-literal

# 수정된 파드 정의 배치
kubectl apply -f sleep/sleep-with-configMap-env.yaml

# 파드 속 환경 변수가 적용되었는지 확인
kubectl exec deploy/sleep -- sh -c 'printenv | grep "^KIAMOL"'

컨피그맵에 저장한 설정 파일 사용

명령행 입력 대신 환경 파일로 컨피그맵을 만들 수도 있다.

# 환경 파일의 내용으로 컨피그맵 생성
kubectl create configmap sleep-config-env-file --from-env-file=sleep/ch04.env

# 컨피그맵의 상세 정보 확인
kubectl get cm sleep-config-env-file

# 새로운 컨피그맵의 설정을 적용하여 파드 업데이트
kubectl apply -f sleep/sleep-with-configMap-env-file.yaml

# 컨테이너에 적용된 환경 변수의 값 확인
kubectl exec deploy/sleep -- sh -c 'printenv | grep "^KIAMOL"'

한 줄에 하나씩 환경 변수를 정의하는 .env 파일 예시:

KIAMOL_CHAPTER=ch04
KIAMOL_SECTION=ch04-4.1
KIAMOL_EXERCISE=try it now

컨피그맵은 여러 개의 데이터 항목을 한 번에 파드에 전달할 수 있다.

sleep-with-configMap-env-file.yaml 파드 정의

env: # 기존 env 항목
- name: KIAMOL_CHAPTER
  value: "04"
- name: KIAMOL_SECTION
  valueFrom:
    configMapKeyRef:
      name: sleep-config-literal
      key: kiamol.section
envFrom: # envFrom 항목에서 컨피그맵 전체를
- configMapRef: # 환경 변수로 정의한다
    name: sleep-config-env-file
# 서비스와 함께 애플리케이션 배치
kubectl apply -f todo-list/todo-web.yaml

# 파드가 준비 상태가 될 때까지 대기
kubectl wait --for=condition=Ready pod -l app=todo-web

# 애플리케이션에 접근하기 위한 주소 출력
kubectl get svc todo-web -o jsonpath='http://{.status.loadBalancer.ingress[0].*}:8080'

# 애플리케이션 로그 확인
kubectl logs -l app=todo-web

todo-web-config-dev.yaml — 컨피그맵 정의

apiVersion: v1
kind: ConfigMap # 리소스 유형은 컨피그맵이다
metadata:
  name: todo-web-config-dev # 컨피그맵 이름을 지정한다
data:
  config.json: |- # 키-값 쌍의 키 이름이 파일 이름이 된다
    {
      "ConfigController": {
        "Enabled": true
      }
    }
# JSON이 담긴 컨피그맵 생성
kubectl apply -f todo-list/configMaps/todo-web-config-dev.yaml

# 컨피그맵을 참조하도록 애플리케이션 업데이트
kubectl apply -f todo-list/todo-web-dev.yaml
# 웹 브라우저에서 /config 페이지 새로 고침

컨피그맵에 담긴 설정값 데이터 주입

환경 변수 외에 설정값을 전달하는 또 다른 방법은 컨테이너 파일 시스템 속 파일로 설정값을 주입하는 것이다. 컨테이너 파일 시스템은 컨테이너 이미지와 그 외 출처에서 온 파일로 구성되는 가상 구조이며, 쿠버네티스는 여기에 컨피그맵도 추가할 수 있다. 컨피그맵은 디렉터리, 각 항목은 파일 형태로 컨테이너 파일 시스템에 추가된다.

todo-web-dev.yaml — 컨피그맵을 볼륨 마운트 형태로 주입

spec:
  containers:
  - name: web
    image: kiamol/ch04-todo-list
    volumeMounts: # 컨테이너에 볼륨을 마운트한다
    - name: config        # 마운트할 볼륨 이름
      mountPath: "/app/config" # 볼륨이 마운트될 경로
      readOnly: true        # 볼륨을 읽기 전용으로
  volumes: # 볼륨은 파드 수준에서 정의된다
  - name: config           # 이 이름이 볼륨 마운트의 이름과 일치해야 한다
    configMap:              # 볼륨의 원본은 컨피그맵이다
      name: todo-web-config-dev # 내용을 읽어 올 컨피그맵 이름

실제로는 이미지에 담긴 기본 설정 파일과 컨피그맵으로 주입된 설정 파일이 합쳐져 구성된다.

# 기본 설정값이 담긴 설정 파일 확인
kubectl exec deploy/todo-web -- sh -c 'ls -l /app/app*.json'

# 볼륨 마운트로 주입된 설정 파일 확인
kubectl exec deploy/todo-web -- sh -c 'ls -l /app/config/*.json'

# 볼륨 마운트가 실제로 읽기 전용인지 확인 (실패해야 정상)
kubectl exec deploy/todo-web -- sh -c 'echo ch04 >> /app/config/config.json'

todo-web-config-dev-with-logging.yaml — 두 개의 설정 파일을 담은 컨피그맵

data:
  config.json: |- # 기존 설정 파일
    {
      "ConfigController": {
        "Enabled": true
      }
    }
  logging.json: |- # 볼륨 마운트로 전달될 두 번째 설정 파일
    {
      "Logging": {
        "LogLevel": {
          "ToDoList.Pages": "Debug"
        }
      }
    }
# 애플리케이션 로그 확인
kubectl logs -l app=todo-web

# 컨피그맵 업데이트
kubectl apply -f todo-list/configMaps/todo-web-config-dev-with-logging.yaml

# 업데이트된 컨피그맵이 파드에 반영될 때까지 대기
sleep 120

# 설정 파일에 반영되었는지 확인
kubectl exec deploy/todo-web -- sh -c 'ls -l /app/config/*.json'

# 애플리케이션에 접근하여 로그 출력이 변화했는지 확인
kubectl logs -l app=todo-web

💡 컨피그맵을 업데이트하면, 파드를 재시작하지 않아도 마운트된 파일 내용이 일정 시간 내에 자동으로 갱신된다.

컨피그맵에 항목이 여러 개 있어도 items 필드로 특정 항목만 골라서 전달할 수 있다.

todo-web-dev-no-logging.yaml — 컨피그맵 데이터 중 단일 항목만 전달

spec:
  containers:
  - name: web
    image: kiamol/ch04-todo-list
    volumeMounts:
    - name: config            # 컨피그맵 볼륨 마운트
      mountPath: "/app/config"  # 마운트할 경로
      readOnly: true
  volumes:
  - name: config
    configMap:
      name: todo-web-config-dev # 컨피그맵 지정
      items: # 컨피그맵에서 전달할 데이터 항목 지정
      - key: config.json   # config.json 항목 지정
        path: config.json    # config.json 파일로 전달하도록 지정
# 변경된 정의 배치
kubectl apply -f todo-list/todo-web-dev-no-logging.yaml

# /app/config 디렉터리의 내용 확인 (logging.json은 보이지 않아야 함)
kubectl exec deploy/todo-web -- sh -c 'ls /app/config'

# 애플리케이션에서 페이지를 두어 번 새로고침한다
# 출력되는 로그 확인
kubectl logs -l app=todo-web

# 파드의 목록과 상태 확인
kubectl get pods -l app=todo-web

📝 학습정리 (1차시)

쿠버네티스에서 컨테이너에 설정값을 주입 쿠버네티스에서 컨테이너에 설정값을 주입하는 데 쓰는 리소스는 컨피그맵(ConfigMap)과 비밀값(Secret) 두 가지이며 리소스 모두 포맷 제한 없이 데이터를 보유할 수 있다. 컨피그맵과 비밀값의 데이터는 클러스터 속에서 다른 리소스와 독립적인 장소에 보관된다. 파드 정의에서 컨피그맵과 비밀값의 데이터를 읽어 오도록 할 수 있으며 이때 파드에 데이터가 전달되는 과정에도 다양한 설정이 가능하다. 컨피그맵과 비밀값 역시 다른 쿠버네티스 리소스와 마찬가지로 kubectl의 create 명령을 사용하거나 YAML 포맷으로 기재된 정의를 읽어 들여 생성할 수 있다.

컨피그맵에 담긴 설정값 데이터 주입 컨피그맵을 컨테이너 파일 시스템의 디렉터리 형태로 읽어 들일 수 있다. 환경 변수 외에 설정값을 전달하는 또 다른 방법은 컨테이너 파일 시스템 속 파일로 설정값을 주입하는 것이다. 컨테이너 파일 시스템은 컨테이너 이미지와 그 외 출처에서 온 파일로 구성되는 가상 구조다. 쿠버네티스는 컨테이너 파일 시스템 구성에 컨피그맵도 추가할 수 있다. 컨피그맵은 디렉터리, 각 항목은 파일 형태로 컨테이너 파일 시스템에 추가된다.


2차시 — 볼륨, 마운트, 클레임을 이용한 데이터 퍼시스턴시

쿠버네티스에서 컨테이너 파일 시스템이 구축되는 과정

파드 속 컨테이너의 파일 시스템은 여러 출처를 합쳐서 구성된다. 컨테이너 이미지가 파일 시스템의 초기 내용을 제공하고, 그 위에 컨테이너가 기록 가능한 레이어가 얹힌다. 이미지에 들어 있던 파일을 수정하거나 새 파일을 기록하는 작업은 바로 이 레이어에서 일어난다.

쿠버네티스에는 클러스터 전체에서 사용 가능한 스토리지를 제공하는 내장 기능이 없다. 모든 상황에 적합한 단일 스토리지 수단이 존재하지 않기 때문이다. 애플리케이션마다 스토리지 요구 사항이 다르고, 플랫폼의 스토리지 제공 능력도 천차만별이라, 쿠버네티스는 스토리지 유형을 직접 정의할 수 있게 하는 방식으로 이 문제를 해결했다.

# 이 장의 예제 코드 디렉터리로 이동
cd ch05

# sleep 파드를 배치한다
kubectl apply -f sleep/sleep.yaml

# 컨테이너 속에 파일 하나를 생성한다
kubectl exec deploy/sleep -- sh -c 'echo ch05 > /file.txt; ls /*.txt'

# 컨테이너 ID를 확인한다
kubectl get pod -l app=sleep -o jsonpath='{.items[0].status.containerStatuses[0].containerID}'

# 파드가 재시작하도록 컨테이너의 모든 프로세스를 강제 종료한다
kubectl exec -it deploy/sleep -- killall5

# 대체된 컨테이너의 ID를 확인한다 (바뀌어 있다)
kubectl get pod -l app=sleep -o jsonpath='{.items[0].status.containerStatuses[0].containerID}'

# 조금 전 생성했던 파일이 사라졌다
kubectl exec deploy/sleep -- ls /*.txt

⚠️ 컨테이너의 기록 가능 레이어는 컨테이너의 생애 주기를 따른다. 컨테이너가 대체되면 그 안의 데이터는 사라진다.

이 문제를 해결하려면 컨테이너의 가상 파일 시스템에 외부 스토리지를 참조하는 볼륨을 포함하면 된다.

sleep-with-emptyDir.yaml — 간단한 볼륨 정의

spec:
  containers:
  - name: sleep
    image: kiamol/ch03-sleep
    volumeMounts:
    - name: data        # 이름이 data인 볼륨을 마운트
      mountPath: /data    # 이 볼륨을 경로 /data에 마운트
  volumes:
  - name: data           # 볼륨 data의 정의
    emptyDir: {}          # 이 볼륨의 유형은 공디렉터리
# 공디렉터리 볼륨을 사용하도록 sleep 파드 업데이트
kubectl apply -f sleep/sleep-with-emptyDir.yaml

# 빈 디렉터리에 파일 하나 생성
kubectl exec deploy/sleep -- sh -c 'echo ch05 > /data/file.txt; ls /data'

# 컨테이너 프로세스 강제 종료
kubectl exec deploy/sleep -- killall5

# 대체 컨테이너의 ID가 바뀌었는지 확인
kubectl get pod -l app=sleep -o jsonpath='{.items[0].status.containerStatuses[0].containerID}'

# 볼륨이 마운트된 경로의 파일 내용 확인 (컨테이너가 대체돼도 파일이 남아 있음)
kubectl exec deploy/sleep -- cat /data/file.txt

💡 emptyDir은 하나의 빈 디렉터리일 뿐인 간단한 볼륨이지만, 컨테이너가 대체되더라도 데이터를 공유할 수 있다. (단, 파드 자체가 삭제되면 emptyDir의 데이터도 함께 사라진다.)

볼륨과 마운트로 노드에 데이터 저장

컨테이너의 가상 파일 시스템에서 데이터를 특정 노드에 고정시킬지 말지 결정해야 한다. 데이터가 특정 노드에 고정된다는 것은, 대체 파드가 이전 파드와 동일한 노드에만 배치되어야 한다는 의미다. 가장 간단한 선택지는 노드의 특정 디렉터리를 가리키는 볼륨이다.

# 파이 애플리케이션을 배치한다
kubectl apply -f pi/v1/

# 파드가 준비 상태가 될 때까지 대기한다
kubectl wait --for=condition=Ready pod -l app=pi-web

# 로드밸런서 서비스의 URL을 출력한다
kubectl get svc pi-proxy -o jsonpath='http://{.status.loadBalancer.ingress[0].*}:8080/?dp=30000'

# 프록시에 저장된 캐시를 확인한다
kubectl exec deploy/pi-proxy -- ls -l /data/nginx/cache

공디렉터리 볼륨에 캐시된 파일은 파드가 재시작(컨테이너만 교체)되더라도 유지되지만, 파드 자체가 삭제되면 사라진다.

# 프록시 파드를 삭제한다
kubectl delete pod -l app=pi-proxy

# 새로 생성된 대체 파드의 캐시 디렉터리 내용을 확인 (비어 있음)
kubectl exec deploy/pi-proxy -- ls -l /data/nginx/cache

emptyDir의 한계를 해결하려면 호스트경로(hostPath) 볼륨을 사용하면 된다.

nginx-with-hostPath.yaml — 호스트경로 볼륨의 사용

spec: # 파드 정의 중 일부 발췌
  containers:
  - image: nginx:1.17-alpine
    name: nginx
    ports:
    - containerPort: 80
    volumeMounts:
    - name: cache-volume
      mountPath: /data/nginx/cache # 프록시의 캐시 저장 경로
  volumes:
  - name: cache-volume
    hostPath: # 노드의 디렉터리를 사용함
      path: /volumes/nginx/cache # 사용할 노드의 디렉터리
      type: DirectoryOrCreate     # 디렉터리가 없으면 생성할 것
# 호스트경로 볼륨을 사용하도록 프록시 파드 업데이트
kubectl apply -f pi/nginx-with-hostPath.yaml

# 프록시 파드를 강제로 삭제
kubectl delete pod -l app=pi-proxy

# 새로 만들어진 프록시 파드의 캐시 디렉터리 내용을 확인 (이번엔 유지됨!)
kubectl exec deploy/pi-proxy -- ls -l /data/nginx/cache

⚠️ 모든 파드가 같은 노드에서 실행되는 환경에서만 호스트경로 볼륨이 안전하게 동작한다. 노드 파일 시스템 전체에 접근하도록 잘못 설정하면 보안 문제가 될 수 있다.

sleep-with-hostPath.yaml — 노드의 파일 시스템 전체에 접근할 수 있는 파드 (위험한 예시)

spec:
  containers:
  - name: sleep
    image: kiamol/ch03-sleep
    volumeMounts:
    - name: node-root
      mountPath: /node-root
  volumes:
  - name: node-root
    hostPath:
      path: /            # 노드 파일 시스템의 루트 디렉터리
      type: Directory      # 경로에 디렉터리가 존재해야 한다

노출 범위를 줄이려면 subPath로 노드 파일 시스템의 특정 하위 경로만 마운트할 수 있다.

sleep-with-hostPath-subPath.yaml — 호스트경로 볼륨 정의

spec:
  containers:
  - name: sleep
    image: kiamol/ch03-sleep
    volumeMounts:
    - name: node-root       # 마운트할 볼륨 이름
      mountPath: /pod-logs    # 마운트 대상 컨테이너 경로
      subPath: var/log/pods    # 마운트 대상 볼륨 내 경로
    - name: node-root
      mountPath: /container-logs
      subPath: var/log/containers
  volumes:
  - name: node-root
    hostPath:
      path: /
      type: Directory

볼륨의 하위 디렉터리를 마운트하면 노드 파일 시스템의 불필요한 노출을 최소화할 수 있다.

전체에서 접근 가능하도록 데이터 저장하기: 영구볼륨과 클레임

쿠버네티스 클러스터는 리소스가 가득 담긴 수영장과 같다. 여러 노드가 각각 CPU와 메모리 용량을 클러스터에 제공하고, 이를 이용해 애플리케이션을 실행한다. 스토리지는 애플리케이션에 제공할 수 있는 또 다른 유형의 리소스다. 노드가 분산 스토리지 시스템에 접속 가능하다면 클러스터 전체에서 사용 가능한 스토리지가 제공된다.

persistentVolume-nfs.yaml — NFS 스토리지를 사용하는 볼륨의 정의

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv01 # 볼륨 이름
spec:
  capacity:
    storage: 50Mi # 볼륨 용량
  accessModes: # 파드의 접근 유형
  - ReadWriteOnce # 파드 하나에서만 사용 가능
  nfs: # NFS 스토리지를 사용하는 볼륨
    server: nfs.my.network # NFS 서버의 도메인 네임
    path: "/kubernetes-volumes" # 스토리지 경로
# 클러스터의 첫 번째 노드에 레이블을 부여
kubectl label node $(kubectl get nodes -o jsonpath='{.items[0].metadata.name}') kiamol=ch05

# 레이블 셀렉터로 노드의 존재 확인
kubectl get nodes -l kiamol=ch05

# 레이블이 부여된 노드의 로컬 볼륨을 사용하는 영구볼륨을 배치
kubectl apply -f todo-list/persistentVolume.yaml

# 영구볼륨의 상세 정보 확인
kubectl get pv

💡 실습 환경에는 분산 스토리지가 없으므로, 로컬 볼륨에 영구볼륨(PV)을 만들어 대신 사용한다.

postgres-persistentVolumeClaim.yaml — 영구볼륨클레임 정의

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-pvc # 애플리케이션은 영구볼륨클레임을 통해 영구볼륨을 사용한다
spec:
  accessModes: # 접근 유형은 필수 설정이다
  - ReadWriteOnce
  resources:
    requests:
      storage: 40Mi # 요청하는 스토리지 용량
  storageClassName: "" # 스토리지 유형을 지정하지 않음
# 영구볼륨과 연결될 영구볼륨클레임을 생성
kubectl apply -f todo-list/postgres-persistentVolumeClaim.yaml

# 영구볼륨클레임의 목록 확인
kubectl get pvc

# 영구볼륨의 목록 확인
kubectl get pv

⚠️ 조건(용량 등)이 맞지 않는 PVC는 어떤 PV에도 연결되지 못하고 대기(Pending) 상태로 유지된다.

todo-db.yaml — 영구볼륨클레임을 사용하는 파드의 정의

spec:
  containers:
  - name: db
    image: postgres:11.6-alpine
    volumeMounts:
    - name: data
      mountPath: /var/lib/postgresql/data
  volumes:
  - name: data
    persistentVolumeClaim: # 영구볼륨클레임을 볼륨으로 사용
      claimName: postgres-pvc # 사용할 영구볼륨클레임의 이름
# 데이터베이스 파드를 배치
kubectl apply -f todo-list/postgres/

# 데이터베이스 파일이 초기화될 때까지 대기
sleep 30

# 애플리케이션의 웹 파드를 배치
kubectl apply -f todo-list/web/

# 데이터베이스 파드를 강제 삭제
kubectl delete pod -l app=todo-db

# 웹 애플리케이션에서 조금 전 추가한 할 일이 그대로 남아 있는지 확인

영구볼륨에 데이터를 저장하면, 첫 데이터베이스 파드가 기록한 내용을 새로 생성된 두 번째 데이터베이스 파드에서도 그대로 읽을 수 있다.

📝 학습정리 (2차시)

쿠버네티스에서 컨테이너 파일 시스템이 구축되는 과정 파드 속 컨테이너의 파일 시스템은 여러 가지의 출처를 합쳐 구성된다. 컨테이너 이미지가 파일 시스템의 초기 내용을 제공하고, 이 위에 컨테이너가 기록 가능한 레이어가 얹혀진다. 이미지에 들어 있던 파일을 수정한다거나 새로운 파일을 기록하는 작업이 바로 이 레이어에서 일어난다. 쿠버네티스에는 클러스터 전체에서 사용 가능한 스토리지를 제공하는 내장 기능이 없다. 모든 상황에서 적합한 스토리지를 제공할 수 있는 단일 수단이란 존재하지 않기 때문이다.

볼륨과 마운트로 노드에 데이터 저장 컨테이너의 가상 파일 시스템 구성에 외부 스토리지를 참조하는 볼륨을 포함할 수 있다. 컨테이너의 가상 파일에서 데이터를 특정 노드에 고정시킬지 말지를 결정해야 한다. 데이터가 특정 노드에 고정된다는 것은 대체 파드가 이전 파드와 동일한 노드에만 배치되도록 해야 한다는 의미다. 선택지 중 가장 간단한 것은 노드의 특정 디렉터리를 가리키는 볼륨이다.

전체에서 접근 가능하도록 데이터 저장하기: 영구볼륨과 클레임 쿠버네티스 클러스터는 리소스가 가득 담긴 수영장과 같다. 여러 개의 노드가 클러스터에 CPU 및 메모리 용량을 제공하고, 스토리지는 애플리케이션에 제공할 수 있는 또 다른 유형의 리소스다. 노드가 분산 스토리지 시스템에 접속 가능하다면 클러스터 전체에서 사용 가능한 스토리지가 제공된다.


3차시 — 컨트롤러 리소스를 이용한 애플리케이션의 스케일링

컨트롤러 리소스를 이용한 애플리케이션의 스케일링

파드는 쿠버네티스에서 컴퓨팅의 단위다. 컨트롤러 리소스 정의는 파드의 템플릿을 포함하고, 컨트롤러는 이 템플릿으로 파드를 생성·대체하며 똑같은 파드의 레플리카를 여러 개 만들 수도 있다.

쿠버네티스에서 가장 많이 사용하는 리소스는 디플로이먼트지만, 디플로이먼트는 사실 직접 파드를 관리하지 않는다. 파드를 직접 관리하는 역할은 **레플리카셋(ReplicaSet)**의 몫이다. 애플리케이션 스케일링의 기본 아이디어는 간단하다 — 파드를 늘리는 것이다. 쿠버네티스에서는 동일한 애플리케이션이 돌아가는 파드를 **레플리카(replica)**라고 부른다.

whoami.yaml — 레플리카셋 직접 생성

apiVersion: apps/v1
kind: ReplicaSet # 디플로이먼트와 정의 내용이 거의 같다
metadata:
  name: whoami-web
spec:
  replicas: 1
  selector: # 관리 대상 파드를 찾기 위한 셀렉터
    matchLabels:
      app: whoami-web
  template: # 일반적인 파드의 정의가 이 뒤로 이어진다
cd ch06
# 레플리카셋과 서비스를 배치
kubectl apply -f whoami/

# 배치된 리소스를 확인
kubectl get replicaset whoami-web

# 파드를 모두 삭제
kubectl delete pods -l app=whoami-web

# 레플리카셋의 정보를 확인 (대체 파드가 곧바로 생성됨)
kubectl describe rs whoami-web

레플리카셋도 디플로이먼트와 마찬가지로 파드를 관리한다는 점은 동일하다.

레플리카 수를 늘리면 서비스가 늘어난 모든 파드에 요청을 고르게 분배해 준다.

# 레플리카 수가 변경된 정의를 배치
kubectl apply -f whoami/update/whoami-replicas-3.yaml

# 모든 파드를 삭제
kubectl delete pods -l app=whoami-web

# sleep 파드를 하나 실행
kubectl apply -f sleep.yaml

# whoami-web 서비스로 HTTP 요청 (반복할수록 응답하는 파드가 바뀜)
kubectl exec deploy/sleep -- sh -c 'for i in 1 2 3; do curl -w \n -s http://whoami-web:8088; done;'

💡 클러스터 내 트래픽도 서비스를 거치며 로드밸런싱이 적용된다.

디플로이먼트와 레플리카셋을 이용한 부하 스케일링

레플리카셋을 이용하면 애플리케이션 스케일링이 매우 쉬워진다. 레플리카 수만 조절하면 수 초 안에 파드 수를 원하는 대로 늘리고 줄일 수 있다. 경량 컨테이너에서 동작하는 작은 규모의 무상태(stateless) 컴포넌트에 최적의 환경이다. 디플로이먼트는 이 레플리카셋 위에 유용한 관리 계층을 추가한다.

디플로이먼트와 레플리카셋이 어떻게 함께 동작하는지 이해했다면, 이제 레플리카셋을 직접 실행할 필요는 없다. 애플리케이션을 정의하는 수단으로는 디플로이먼트를 우선적으로 선택해야 한다.

web.yaml — 여러 개의 레플리카를 배치하는 디플로이먼트

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pi-web
spec:
  replicas: 2 # 이 필드는 필수가 아니고 생략할 경우 1이 적용된다
  selector:
    matchLabels:
      app: pi-web
  template: # 이 뒤로 일반적인 파드의 정의가 이어진다
# 원주율 웹 애플리케이션을 배치
kubectl apply -f pi/web/

# 레플리카셋의 상태를 확인
kubectl get rs -l app=pi-web

# 레플리카를 늘려 스케일링을 적용
kubectl apply -f pi/web/update/web-replicas-3.yaml

# 로그 설정이 추가된 새로운 파드의 정의를 적용
kubectl apply -f pi/web/update/web-logging-level.yaml

디플로이먼트는 레플리카셋이 파드의 적정 수를 유지하며 업데이트를 진행하도록 한다. 업데이트마다 새로운 레플리카셋이 생성되고, 이전 레플리카셋의 레플리카 수는 0으로 줄어든다.

# 원주율 애플리케이션을 신속하게 스케일링해야 한다
kubectl scale --replicas=4 deploy/pi-web

# 어떤 레플리카셋이 변경되는지 확인한다
kubectl get rs -l app=pi-web

# 로그 수준을 원래대로 되돌려도 된다
kubectl apply -f pi/web/update/web-replicas-3.yaml
# 하지만 이 과정에서 수동 스케일링도 함께 원복된다(주의!)

⚠️ kubectl scale로 수동 스케일링한 뒤, 다른 매니페스트로 다시 apply하면 매니페스트에 적힌 레플리카 수로 덮어써지면서 수동 설정이 사라질 수 있다.

# 레플리카셋과 레이블 확인 (템플릿 해시값 포함)
kubectl get rs -l app=pi-web --show-labels

# 파드와 레이블 확인
kubectl get po -l app=pi-web --show-labels

데몬셋을 이용한 스케일링으로 고가용성 확보

**데몬셋(DaemonSet)**은 리눅스 백그라운드에서 단일 인스턴스로 동작하며 시스템 기능을 제공하는 프로세스를 가리키는 "데몬(daemon)"에서 따온 이름이다. 쿠버네티스의 데몬셋은 클러스터 내 모든 노드 또는 셀렉터와 일치하는 일부 노드에서, 단일 레플리카(파드)로 동작하는 리소스를 의미한다.

데몬셋은 각 노드에서 정보를 수집해 중앙 모듈로 전달하는 등 인프라 수준의 관심사에 많이 쓰인다. 노드마다 파드가 하나씩 동작하므로 리소스 충돌을 걱정할 필요가 없다.

nginx-ds.yaml — 프록시 용도로 사용할 데몬셋

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: pi-proxy
spec:
  selector:
    matchLabels: # 데몬셋에도 레이블 셀렉터가 있음
      app: pi-proxy # 데몬셋의 관리 대상인 파드를 결정하는 기준
  template:
    metadata:
      labels:
        app: pi-proxy # 레이블 셀렉터와 레이블이 일치해야 함
    spec:
      # 이 뒤로 파드 정의가 이어짐
# 데몬셋을 배치
kubectl apply -f pi/proxy/daemonset/nginx-ds.yaml

# 프록시 서비스에 등록된 엔드포인트를 확인
kubectl get endpoints pi-proxy

# 디플로이먼트를 삭제 (애플리케이션은 그대로 동작)
kubectl delete deploy pi-proxy

# 데몬셋의 상세 정보를 확인
kubectl get daemonset pi-proxy

💡 애플리케이션을 망가뜨리지 않으면서 디플로이먼트를 데몬셋으로 교체할 수 있다. 다만 끊김 없는 전환을 위해서는 배치 순서에 신경 써야 한다.

# 프록시 파드를 수동으로 삭제 (데몬셋이 곧바로 대체 파드를 실행함)
kubectl delete po -l app=pi-proxy

# 파드의 목록을 확인
kubectl get po -l app=pi-proxy

nodeSelector를 사용하면 특정 노드에서만 데몬셋 파드를 실행할 수 있다.

# 데몬셋의 template 필드 속 파드 정의
spec:
  containers:
  # ...
  volumes:
  # ...
  nodeSelector: # 특정 노드에서만 파드를 실행한다
    kiamol: ch06 # kiamol=ch06 레이블이 부여된 노드만 대상이다
# 데몬셋을 업데이트
kubectl apply -f pi/proxy/daemonset/nginx-ds-nodeSelector.yaml

# 셀렉터와 일치하는 레이블을 노드에 부여
kubectl label node $(kubectl get nodes -o jsonpath='{.items[0].metadata.name}') kiamol=ch06 --overwrite

# 파드의 상태를 다시 확인
kubectl get ds pi-proxy

데몬셋은 제어 루프에서 대상 파드와 대상 노드를 함께 관리한다.

kubectl delete에는 cascade 옵션이 있어, 컨트롤러만 지우고 관리 대상 파드는 남길 수도 있다.

# 관리 대상 파드는 남겨 두고 데몬셋을 삭제
kubectl delete ds pi-proxy --cascade=false

# 파드의 상태를 확인 (파드는 여전히 남아 있음)
kubectl get po -l app=pi-proxy

# 데몬셋을 다시 생성
kubectl apply -f pi/proxy/daemonset/nginx-ds-nodeSelector.yaml

# cascade 옵션 없이 데몬셋을 삭제 (이번엔 파드도 함께 사라짐)
kubectl delete ds pi-proxy
kubectl get po -l app=pi-proxy

⚠️ 컨트롤러 리소스의 관리에서 벗어난 파드는 더 이상 고가용성에 기여하지 못한다.

📝 학습정리 (3차시)

애플리케이션의 스케일링 파드는 쿠버네티스에서 컴퓨팅의 단위다. 컨트롤러 리소스 정의는 파드의 템플릿을 포함한다. 컨트롤러 리소스는 파드를 생성하고 대체하는 데 이 템플릿을 사용한다. 쿠버네티스에서 가장 많이 사용하게 될 리소스는 디플로이먼트일 것이다. 디플로이먼트는 사실 직접 파드를 관리하지 않는다. 파드를 직접 관리하는 역할은 레플리카셋(ReplicaSet)의 몫이다. 애플리케이션 스케일링의 기본 아이디어는 간단하다. 파드를 늘리는 것이다. 쿠버네티스에서는 이렇게 동일한 애플리케이션이 돌아가는 파드를 레플리카(replica)라고 한다.

레플리카셋을 이용한 부하 스케일링 레플리카셋을 이용하면 애플리케이션 스케일링이 매우 쉬워진다. 레플리카셋 정의의 레플리카 수만 조절하면 수 초 안에 파드 수를 원하는 대로 늘리고 줄일 수 있다. 경량 컨테이너에서 동작하는 작은 규모의 무상태 컴포넌트에는 최적의 환경이다. 디플로이먼트는 이 레플리카셋 위에 유용한 관리 계층을 추가한다. 애플리케이션을 정의하는 수단으로는 디플로이먼트를 우선적으로 선택해야 한다.

데몬셋을 이용한 스케일링 데몬셋(DaemonSet)은 리눅스의 백그라운드에서 단일 인스턴스로 동작하며 시스템 관련 기능을 제공하는 프로세스를 가리키는 말인 데몬(daemon)(윈도우 서비스와 같은 의미)에서 따온 이름이다. 쿠버네티스의 데몬셋은 클러스터 내 모든 노드 또는 셀렉터와 일치하는 일부 노드에서 단일 레플리카 또는 파드로 동작하는 리소스를 의미한다. 데몬셋은 각 노드에서 정보를 수집하여 중앙의 수집 모듈에 전달하거나 하는 인프라 수준의 관심사와 관련된 목적으로 많이 쓰인다.


🔑 10주차 핵심 키워드 정리

키워드 한 줄 정리

컨피그맵(ConfigMap) 일반 설정값을 코드 수정 없이 외부에서 주입하는 리소스
비밀값(Secret) 패스워드 등 민감 정보를 저장하는 리소스 (컨피그맵과 사용법 동일)
env / valueFrom 환경 변수로 직접 값을 정의하거나 컨피그맵에서 읽어오는 방식
envFrom 컨피그맵/비밀값 전체를 한 번에 환경 변수로 주입
볼륨 마운트 컨피그맵·비밀값을 파일 형태로 컨테이너 파일 시스템에 주입
emptyDir 파드 생성 시 빈 디렉터리 생성, 컨테이너 교체엔 유지, 파드 삭제 시 소멸
hostPath 노드의 실제 디렉터리를 마운트, 파드가 삭제돼도 노드에 데이터 유지
subPath 호스트경로 볼륨의 특정 하위 디렉터리만 마운트해 노출 최소화
PV (PersistentVolume) 클러스터에 미리 등록해 둔 실제 물리 스토리지 자원
PVC (PersistentVolumeClaim) 개발자가 제출하는 스토리지 요청서, PV와 자동 연결
레플리카셋(ReplicaSet) 파드를 실제로 생성·관리하는 컨트롤러, 디플로이먼트가 그 위에 있음
레플리카(replica) 동일한 애플리케이션이 돌아가는 파드
데몬셋(DaemonSet) 클러스터의 모든(또는 일부) 노드마다 파드를 정확히 하나씩 실행
nodeSelector 특정 레이블이 붙은 노드에서만 파드를 실행하도록 제한
cascade 옵션 컨트롤러 삭제 시 관리 대상 파드까지 함께 삭제할지 여부 결정