본문으로 건너뛰기
yutils

Kubernetes YAML 완벽 정리 — 매니페스트 구조·리소스 종류·연결 방식

모든 Kubernetes 매니페스트의 공통 구조 (apiVersion / kind / metadata / spec), 실무에서 쓰는 리소스 종류, Service 가 label 로 Pod 를 찾는 방식, 입문자가 자주 빠지는 매니페스트 함정.

약 11분 읽기

Kubernetes 처음 접하면 YAML 파일 50 개가 어디서 어디로 흐르는지 그림이 안 그려진다. Helm chart 의 render 결과를 받아도 막막. 이 가이드는 Kubernetes 매니페스트의 공통 구조 4 가지 (apiVersion / kind / metadata / spec), 실무에서 자주 만나는 리소스 종류, Service 가 Pod 를 어떻게 찾는지, 그리고 입문자가 자주 빠지는 함정을 정리한다.

모든 매니페스트의 공통 구조

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: production
  labels:
    app: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: nginx:1.27

네 가지 최상위 필드:

  • apiVersion — 리소스 정의를 누가 책임지는가 (API group/version). 예: v1 (core), apps/v1 (Deployment 등), networking.k8s.io/v1 (Ingress).
  • kind — 리소스 타입. 예: Deployment, Service, ConfigMap.
  • metadata — 식별 정보. name, namespace, labels, annotations.
  • spec — desired state. 이 안에 진짜 설정이 들어감. kind 별로 구조가 완전히 다름.

status 라는 5 번째 필드도 있지만 클러스터가 채우는 것이라 매니페스트엔 안 박는다.

왜 YAML 인가

Kubernetes API 자체는 JSON 으로 통신. kubectl apply -f 가 YAML 을 JSON 으로 변환해 서버로 보낸다. YAML 을 쓰는 이유:

  • 주석 가능 — 운영 노하우를 매니페스트 옆에 박을 수 있음.
  • 긴 문자열 친화 — YAML 의 multi-line literal (|, >) 이 cert 나 script 박기 편함.
  • 따옴표 적음 — JSON 보다 시각적 노이즈 적음.

대신 indent 민감, 탭/공백 혼용 사고, anchor (&name /*name) 의도치 않은 사용 등 함정이 있다. YAML ↔ JSON 변환 으로 양방향 변환해 보면 YAML 이 실제 어떤 JSON 구조로 풀리는지 직관적.

핵심 리소스 — 14 종 분류

워크로드 (Workload) — 컨테이너를 실행

  • Deployment — stateless 서비스. 가장 흔함. replica 수 + pod template + 롤링 업데이트.
  • StatefulSet — stateful (DB, 큐). 안정된 pod 이름 (web-0, web-1) + PVC 영속.
  • DaemonSet — 모든 노드에 1 개씩 (로깅 agent, network plugin).
  • Job — 1 회 실행 후 종료 (마이그레이션, batch).
  • CronJob — 스케줄링된 Job. Linux cron 과 같은 표현식.
  • Pod — 최소 단위. 컨테이너 1+. 직접 만드는 일은 거의 없음 — 위 워크로드가 자동 생성.

네트워크

  • Service — pod 집합에 안정된 virtual IP + DNS. 세 가지 타입: ClusterIP (내부), NodePort (노드 포트), LoadBalancer (cloud LB).
  • Ingress — HTTP/HTTPS 라우팅 layer. 도메인 + path → Service 매핑.
  • NetworkPolicy — pod 간 통신 방화벽 규칙.

설정·시크릿·스토리지

  • ConfigMap — key-value 설정 (env 변수, 파일).
  • Secret — 시크릿 (base64 인코딩, 암호화 X 가 기본 — 별도 etcd encryption 필요).
  • PersistentVolumeClaim (PVC) — 영속 디스크 요청. StorageClass 가 실제 PV provision.

스케일링·접근 제어

  • HorizontalPodAutoscaler (HPA) — CPU/메모리/커스텀 메트릭으로 replica 자동 조정.
  • ServiceAccount — pod 가 API 호출할 때의 identity.
  • Role / RoleBinding — RBAC. ServiceAccount 의 권한 정의.
  • Namespace — 논리적 분리 (dev/staging/prod, 팀별).

이 모든 종류를 한눈에 보려면 매니페스트를 Kubernetes YAML 시각화 에 붙여넣으면 리소스 + 관계 그래프가 즉시 나온다.

관계의 핵심 — 어떻게 연결되나

K8s 의 가장 헷갈리는 부분이 "이 Service 는 어떤 Pod 와 연결?", "이 Deployment 는 어떤 ConfigMap 을 사용?" 같은 관계. 이름으로 직접 가리키 는 게 아니라 label selector 라는 간접 방식이 핵심.

Service → Pod: label selector

# Pod template 의 labels
apiVersion: apps/v1
kind: Deployment
metadata: {name: web}
spec:
  selector:
    matchLabels: {app: web, tier: frontend}
  template:
    metadata:
      labels: {app: web, tier: frontend}  # ← 이 labels
    spec: {containers: [...]}

---
# Service 가 selector 로 위 labels 매칭
apiVersion: v1
kind: Service
metadata: {name: web}
spec:
  selector: {app: web, tier: frontend}  # ← 모든 key=value 일치하는 Pod
  ports: [{port: 80, targetPort: 8080}]

주의: Service 의 selector 와 Deployment 의 spec.selector.matchLabels 는 다른 역할.

  • Deployment.spec.selector.matchLabels — Deployment 가 어떤 pod 을 자기 것으로 인식할지.
  • Deployment.spec.template.metadata.labels — 실제 생성되는 pod 의 labels.
  • Service.spec.selector — Service 가 어떤 pod 으로 트래픽 보낼지.

Deployment 의 두 selector 는 일치해야 함 (안 그러면 deploy 실패). Service 의 selector 는 위 labels 의 부분집합이면 매칭.

Ingress → Service: 직접 이름 참조

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata: {name: web}
spec:
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web        # ← Service 이름 직접 참조
                port: {number: 80}

Deployment → ConfigMap / Secret: 3 가지 방법

Pod 가 ConfigMap / Secret 을 사용하는 방식:

spec:
  containers:
    - name: web
      # 방법 1: envFrom — 모든 key 를 env 변수로
      envFrom:
        - configMapRef: {name: web-config}
        - secretRef: {name: web-secrets}

      # 방법 2: env.valueFrom — 특정 key 만 골라서
      env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: web-secrets
              key: database-url

      # 방법 3: volumeMounts — 파일로 마운트
      volumeMounts:
        - name: config
          mountPath: /etc/web

  volumes:
    - name: config
      configMap: {name: web-config}

Deployment → PVC: volumes

spec:
  containers:
    - name: db
      volumeMounts:
        - {name: data, mountPath: /var/lib/db}
  volumes:
    - name: data
      persistentVolumeClaim: {claimName: db-data}

HPA → Deployment: scaleTargetRef

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata: {name: web-hpa}
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  minReplicas: 3
  maxReplicas: 10

multi-doc YAML — --- 의 의미

한 파일에 여러 리소스를 박을 때 --- 로 구분. Kubernetes 의 관례.

apiVersion: apps/v1
kind: Deployment
metadata: {name: web}
spec: {...}
---
apiVersion: v1
kind: Service
metadata: {name: web}
spec: {...}
---
apiVersion: v1
kind: ConfigMap
metadata: {name: web-config}
data: {...}

kubectl apply -f all.yaml 가 위 3 개를 모두 적용. Helm / Kustomize render 결과도 보통 multi-doc YAML.

namespace 의 의미

metadata.namespace 가 같은 리소스끼리만 자동 연결. 다른 namespace 의 Service 를 부르려면 FQDN: web.production.svc.cluster.local.

namespace 명시 안 하면 default. 운영 환경은 명확히 분리 권장 — production, staging, 팀별 등.

흔히 빠지는 함정

1. selector 와 labels 불일치

Service 의 selector 와 pod 의 labels 한 글자 차이로 트래픽 0. "왜 503 만 나오지?" 의 가장 흔한 원인. Kubernetes YAML 시각화 는 selector ↔ labels 매칭 검증해 시각화 — 연결 안 되면 화살표 안 나옴.

2. Deployment 의 두 selector 불일치

spec.selector.matchLabels spec.template.metadata.labels 가 일치 안 하면 kubectl apply 가 거부. 처음엔 헷갈림.

3. ConfigMap / Secret 변경 후 pod 재시작 안 됨

ConfigMap / Secret 만 update 해도 pod 가 자동 재시작 X (envFrom 케이스). kubectl rollout restart 또는 매니페스트의 annotation 에 checksum 박는 패턴 사용.

4. Secret 의 base64 = 암호화 아님

data: 값이 base64 인코딩이라 "이거 암호화구나" 오해 흔함. 실제로는 단순 인코딩. 시크릿 보호는 별도 (etcd encryption at rest, external secrets, Sealed Secrets 등).

5. namespace 누락

prod 매니페스트인데 namespace 안 박아서 default 에 배포. metadata 에 명시 또는 kubectl apply -n prod -f 강제.

6. apiVersion 잘못

extensions/v1beta1 같은 deprecated 버전 — K8s 업그레이드 시 사라진다. networking.k8s.io/v1 같은 stable 버전 사용.

7. 동일 리소스 중복 정의

같은 kind + namespace + name 이 여러 번 — 마지막 것이 이긴다. Kustomize patch 의도가 아니면 사고.

8. resource limits 누락

resources.limits 없으면 한 pod 가 노드 전체 메모리 소비 가능. requests + limits 박는 게 표준.

실전 워크플로우

  1. 매니페스트 파일 작성 또는 Helm chart render (helm template).
  2. Kubernetes YAML 시각화 에 붙여넣어 관계 시각 확인 — 의도한 대로 연결되는지.
  3. kubectl apply --dry-run=server -f 로 schema 검증.
  4. kubectl apply -f 적용.
  5. kubectl get all -n <ns> 로 적용 결과 확인.

Helm vs Kustomize — 큰 그림

  • Helm — chart (템플릿 + values) → render → 매니페스트. 버저닝·릴리스 관리. 가장 흔한 배포 도구.
  • Kustomize — base 매니페스트 + overlay (patch). 환경 별 (dev/staging/prod) 차이 명시. kubectl apply -k 가 내장.
  • raw YAML — 작은 프로젝트, 학습용. 환경 차이 관리 어려움.

어느 방식이든 결국 매니페스트 YAML 로 풀린다. 그 결과를 이해하는 게 핵심.

요약

  • 모든 매니페스트 = apiVersion + kind + metadata + spec.
  • 핵심 관계는 label selector — Service 가 selector 로 pod labels 매칭. 이름 직접 참조는 Ingress→Service, HPA→Workload, volume claim 등.
  • ConfigMap / Secret 은 envFrom · env.valueFrom · volumes 3 가지 방식 으로 pod 에 주입.
  • multi-doc (---) 로 여러 리소스 한 파일에. namespace 가 격리 단위.
  • Secret 의 base64 ≠ 암호화. 별도 etcd encryption 또는 external secrets.
  • 매니페스트 받았을 때 Kubernetes YAML 시각화 에 붙여넣어 관계 시각 확인 → 5 분 안에 구조 파악.
가이드 목록으로