$ yh.log
[CKA] 2주차 - ConfigMap · Secret, 리소스, 스케줄링, DaemonSet

[CKA] 2주차 - ConfigMap · Secret, 리소스, 스케줄링, DaemonSet

KubernetesCKAConfigMapSecretSchedulingDaemonSet

작성자 : 오예환 | 작성일 : 2026-10-09 | 수정일 : 2026-10-09 | 조회수 :

CKA 2주차 - ConfigMap · Secret, 리소스, 스케줄링, DaemonSet

시리즈: 1주차 - 클러스터 구조와 Pod · Deployment · 2주차 (이번 글) · 3주차 - Service와 네트워킹

2주차의 주제는 크게 두 가지다.

  1. 앱에 설정을 어떻게 넣을까: ConfigMap, Secret
  2. Pod를 어느 노드에 둘까: requests/limits, nodeSelector, taint/toleration, node affinity, DaemonSet

출제 도메인으로는 Workloads & Scheduling(15%)에 해당한다. 이번 주 실습 YAML은 전부 k run ... --dry-run=client -o yaml로 뼈대를 만든 뒤 필요한 필드만 추가했다.


1. ConfigMap: 설정을 이미지 밖에 둔다

환경마다 달라지는 값(APP_ENV, APP_COLOR 같은 것)을 이미지 안에 넣어두면, 값을 바꿀 때마다 이미지를 다시 빌드해야 한다. ConfigMap은 이런 설정을 클러스터에 따로 저장해두고 Pod가 실행될 때 주입하는 리소스다.

k create configmap app-config \
  --from-literal=APP_ENV=study \
  --from-literal=APP_COLOR=blue
 
k get configmap app-config -o yaml

Pod 쪽 YAML은 명령형으로 뼈대를 만들었다. BusyBox 이미지는 실행할 명령이 없으면 바로 종료되기 때문에 sleep을 걸어 계속 떠 있게 했다.

k run cm-demo --image=busybox:1.36 --restart=Never \
  --dry-run=client -o yaml --command -- sleep 86400 > cm-demo.yaml

여기에 envFrom만 추가하면 ConfigMap의 모든 key가 환경변수로 들어간다.

apiVersion: v1
kind: Pod
metadata:
  name: cm-demo
spec:
  containers:
    - name: cm-demo
      image: busybox:1.36
      command: ["sleep", "86400"]
      envFrom:
        - configMapRef:
            name: app-config
  restartPolicy: Never
k apply -f cm-demo.yaml
k exec cm-demo -- env | grep APP
# APP_ENV=study
# APP_COLOR=blue

실수 1: envFrom을 metadata 아래에 넣었다

처음에는 envFrom을 metadata 아래에 적었다. 환경변수는 "이 컨테이너를 어떻게 실행할지"에 대한 설정이다. 그래서 Pod의 식별 정보인 metadata가 아니라 spec.containers[] 안에 있어야 한다. 위치를 옮기고 나서야 값이 들어갔다.

실수 2: ConfigMap을 바꿨는데 Pod 값이 그대로다

k edit configmap app-config로 APP_COLOR를 blue → green → red로 바꿨다. 그런데 exec ... env로 보면 여전히 blue였다.

환경변수는 컨테이너가 시작될 때 한 번 읽힌다. 실행 중인 Pod는 ConfigMap이 바뀐 사실을 모른다. 같은 YAML로 k apply -f cm-demo.yaml을 다시 해도 Pod 매니페스트 자체는 바뀐 게 없으니 unchanged만 나온다. Pod를 지우고 다시 만든 뒤에야 red가 보였다.

k delete pod cm-demo
k apply -f cm-demo.yaml
k exec cm-demo -- env | grep APP_COLOR   # APP_COLOR=red

Deployment로 띄운 앱이라면 k rollout restart deployment/<이름>으로 Pod를 새로 만들면 된다.

중간에 하나 더 겪은 일이 있다. 처음에는 sleep 3600으로 만들었는데, 1시간이 지나자 Pod가 Completed(Succeeded) 상태가 되면서 exec가 안 됐다. restartPolicy: Never라서 정상 종료된 컨테이너를 다시 띄우지 않은 것이다. 실습용으로는 sleep 86400으로 늘렸다.


2. Secret: base64는 암호화가 아니다

비밀번호 같은 값은 Secret에 담는다. 만드는 방법과 주입 방법은 ConfigMap과 거의 같다.

k create secret generic app-secret \
  --from-literal=DB_USER=admin \
  --from-literal=DB_PASSWORD=cka-pass
 
k get secrets
k get secret app-secret -o yaml

YAML을 보면 값이 YWRtaW4= 같은 문자열로 저장되어 있다. 처음에는 암호화된 줄 알았는데, 그냥 base64로 인코딩한 것이다. 명령 한 줄이면 원래 값이 나온다.

k get secret app-secret -o jsonpath='{.data.DB_PASSWORD}' | base64 --decode
# cka-pass

Pod에는 configMapRef 대신 secretRef를 쓴다.

envFrom:
  - secretRef:
      name: app-secret
k exec secret-demo -- env | grep DB_
# DB_USER=admin
# DB_PASSWORD=cka-pass

컨테이너 안에서는 평문으로 보인다. 즉 Secret은 ConfigMap과 저장 방식이 크게 다르지 않고, 누가 읽을 수 있는지를 RBAC로 따로 제한할 수 있다는 점이 실질적인 차이다. 저장소(etcd) 수준의 암호화는 별도로 설정해야 한다. RBAC는 6주차에 다룰 예정이다.


3. requests와 limits: 스케줄러는 실사용량을 보지 않는다

컨테이너마다 CPU와 메모리를 두 가지 값으로 지정할 수 있다.

필드의미누가 쓰나
requests이만큼은 확보해달라는 예약량스케줄러가 Pod를 둘 노드를 고를 때
limits이 이상은 쓸 수 없는 상한노드의 kubelet이 실행 중에 제한할 때
resources:
  requests:
    cpu: 100m # 0.1 CPU
    memory: 64Mi
  limits:
    cpu: 200m
    memory: 128Mi

100m은 100 millicore, 즉 CPU 코어 0.1개다. 실사용량을 보려면 metrics-server가 필요해서 minikube addon을 켰다.

minikube addons enable metrics-server
k describe pod resource-demo   # Requests / Limits 값 확인
k top pod resource-demo        # 실제 사용량 확인

describe에 나오는 예약값과 top에 나오는 실사용량은 서로 다른 숫자다. nginx 하나는 실제로 CPU를 거의 쓰지 않는다.

일부러 Pending 만들기

requests가 어떻게 쓰이는지 확인하려고 CPU requests를 "100"(코어 100개)으로 적은 Pod를 만들었다.

resources:
  requests:
    cpu: "100"
    memory: 64Mi
k get pod pending-demo        # STATUS: Pending
k describe pod pending-demo   # Events 확인
# Warning  FailedScheduling  0/2 nodes are available: 2 Insufficient cpu.

노드에 CPU 100개를 예약할 자리가 없으니 Pod는 어느 노드에도 배치되지 못하고 Pending으로 남는다. 노드가 실제로 한가하든 아니든 상관없다. 스케줄러는 실사용량이 아니라 requests 합계를 보고 배치를 결정한다. Pending이 보이면 describe의 Events에서 FailedScheduling 사유를 먼저 확인한다.


4. Pod를 특정 노드에 배치하기

내 클러스터에는 노드가 2개(minikube, minikube-m02) 있다. 이번 주의 핵심은 Pod가 어느 노드로 갈지를 제어하는 방법들이다. 방향이 서로 달라서 표로 먼저 정리했다.

방법어디에 설정방향
nodeSelectorPodPod가 "이 라벨이 있는 노드로 가겠다"
node affinityPodnodeSelector보다 조건을 다양하게. 필수/선호 구분 가능
taintNode노드가 "조건 없는 Pod는 받지 않겠다"
tolerationPodPod가 "그 taint가 있어도 들어갈 수 있다"

nodeSelector: 라벨로 노드 고르기

k label node minikube-m02 disk=ssd
k get nodes --show-labels | grep disk
spec:
  nodeSelector:
    disk: ssd

Pod를 만든 뒤 k get pod selector-demo -o wide로 확인하니 NODE가 minikube-m02였다. 라벨이 disk=ssd인 노드는 minikube-m02뿐이라 Pod가 그쪽으로 간 것이다.

taint와 toleration: 노드가 Pod를 거부하기

k taint node minikube-m02 dedicated=infra:NoSchedule
  • dedicated, infra는 내가 정한 key와 value다.
  • NoSchedule은 쿠버네티스가 정의한 effect로, "toleration이 없는 Pod는 새로 배치하지 않는다"는 뜻이다.

taint를 건 뒤 확인한 결과는 이렇다.

  1. 이미 떠 있던 Pod는 그대로 유지됐다. NoSchedule은 새 배치만 막는다. (기존 Pod까지 내보내려면 NoExecute를 쓴다.)
  2. 같은 Pod를 지우고 다시 만들자 Pending이 됐다. Events에 untolerated taint {dedicated: infra}가 찍혔다.

nodeSelector는 minikube-m02를 가리키는데, 그 노드는 taint 때문에 받아주지 않으니 갈 곳이 없어진 것이다. Pod에 toleration을 추가했다.

spec:
  nodeSelector:
    disk: ssd
  tolerations:
    - key: dedicated
      operator: Equal
      value: infra
      effect: NoSchedule

다시 만들자 minikube-m02에서 Running이 됐다.

여기서 헷갈렸던 점이 있다. toleration은 "그 노드로 가라"는 뜻이 아니라 "그 taint를 통과할 자격이 있다"는 뜻이다. toleration만 있으면 taint가 없는 다른 노드로도 갈 수 있다. 특정 노드에 확실히 보내려면 이번 실습처럼 nodeSelector나 node affinity를 함께 써야 한다.

실습 후 taint는 끝에 -를 붙여서 제거한다: k taint node minikube-m02 dedicated=infra:NoSchedule-

node affinity: 필수 조건과 선호 조건

node affinity는 nodeSelector와 하는 일이 같지만, In, NotIn, Exists 같은 연산자를 쓸 수 있고 반드시 지켜야 하는 조건과 가능하면 지키는 조건을 나눌 수 있다. 필드 이름이 길어서 시험에서는 kubernetes.io 문서에서 예제를 복사해 쓰는 편이 안전하다.

필수 조건 (required)

spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: disk
                operator: In
                values: ["ssd"]
  • disk=ssd 노드(minikube-m02)에 배치됐다.
  • 배치 후 노드 라벨을 지워도 이미 떠 있는 Pod는 그대로였다. 이름 뒷부분 IgnoredDuringExecution이 바로 "실행 중에는 조건을 다시 검사하지 않는다"는 뜻이다.
  • Pod를 새로 만들면 조건에 맞는 노드가 없어 Pending이 됐다. 라벨을 다시 붙이자 자동으로 배치됐다.

선호 조건 (preferred)

spec:
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 100
          preference:
            matchExpressions:
              - key: disk
                operator: In
                values: ["ssd"]
  • disk=ssd 노드가 있을 때는 그 노드로 갔다.
  • 라벨을 지우고 다시 만들어도 Pending이 되지 않고 다른 노드에서 Running이 됐다. 선호 조건은 점수를 더해줄 뿐 배치를 막지는 않는다. weight(1~100)는 여러 선호 조건 사이의 가중치다.

5. DaemonSet: 노드마다 Pod 하나씩

로그 수집기나 모니터링 에이전트처럼 모든 노드에 하나씩 떠야 하는 Pod가 있다. 이때 쓰는 것이 DaemonSet이다. 3주차에 본 kube-proxy도 DaemonSet으로 각 노드에 떠 있다.

kubectl create daemonset 명령은 없다. 그래서 Deployment 뼈대를 뽑아서 고쳤다.

k create deployment node-agent --image=busybox:1.36 \
  --dry-run=client -o yaml -- sleep 86400 > daemonset.yaml

고친 내용은 세 가지다.

  1. kind: Deployment → kind: DaemonSet
  2. spec.replicas 삭제 (노드 수만큼 자동으로 만들어지므로 개수를 지정하지 않는다)
  3. strategy 삭제
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-agent
spec:
  selector:
    matchLabels:
      app: node-agent
  template:
    metadata:
      labels:
        app: node-agent
    spec:
      containers:
        - name: busybox
          image: busybox:1.36
          command: ["sleep", "86400"]
k apply -f daemonset.yaml
k get pod -l app=node-agent -o wide

노드 2개에 Pod가 하나씩 떴다. Pod 하나를 지우자 같은 노드에 새 이름의 Pod가 바로 다시 생겼다. Deployment는 "전체 개수"를 맞추고, DaemonSet은 "노드마다 하나"를 맞춘다는 차이를 확인했다.


6. 2주차 명령어 정리

목적명령
ConfigMap 생성k create configmap <이름> --from-literal=K=V
Secret 생성k create secret generic <이름> --from-literal=K=V
Secret 값 확인k get secret <이름> -o jsonpath='{.data.KEY}' | base64 --decode
실사용량k top pod · k top node
노드 라벨k label node <노드> disk=ssd · 제거: k label node <노드> disk-
taintk taint node <노드> key=value:NoSchedule · 제거: 끝에 -
라벨 확인k get nodes --show-labels
스케줄 실패 원인k describe pod <이름> → Events의 FailedScheduling

정리

  • 환경변수로 주입한 ConfigMap · Secret 값은 컨테이너가 시작될 때 한 번 읽힌다. 값을 바꿨다면 Pod를 다시 만들어야 반영된다.
  • Secret의 base64는 인코딩일 뿐이다. 접근 제한은 RBAC로 한다.
  • 스케줄러는 requests를 기준으로 노드를 고른다. Pending이면 describe의 Events부터 본다.
  • nodeSelector · affinity는 Pod가 노드를 고르는 방법이고, taint는 노드가 Pod를 거부하는 방법이다. toleration은 거부를 통과할 자격일 뿐이라 특정 노드로 보내지는 않는다.

다음 글에서는 계속 바뀌는 Pod IP 앞에 고정된 주소를 만들어주는 Service와, 그 뒤에서 동작하는 EndpointSlice · kube-proxy · CoreDNS를 정리한다.