K8s YAML 읽는 법 — 자주 보는 오브젝트 7가지
Summary
K8s 를 처음 들여다보면 YAML 파일이 정말 많이 나와요. Deployment, Service, Ingress, ConfigMap, Secret… 단어는 들어봤는데 각자 무슨 일을 하고 어떻게 물리는지 헷갈리죠. 다행히 현장에서 자주 보는 오브젝트는 사실 7~8개 안쪽 이에요. 이 글에서는 그중 7개를 골라서, 가상의 web 앱 하나에 다 붙여서 풀어볼게요. YAML 도 한 토막씩 같이 봅니다.
💡 이 글에서 다루는 것
- K8s 오브젝트가 뭐고 왜 YAML 이 그렇게 많은지
Namespace,Pod,Deployment,Service,Ingress,ConfigMap,Secret일곱 가지- 각 오브젝트가 어떤 문제를 푸는지 + 최소 YAML 예시
- 일곱 개가 하나의 앱에서 어떻게 같이 굴러가는지
이 글은 Docker 와 Kubernetes 의 관계: 워커와 오케스트레이션 관점에서 의 자매편이에요. 앞 글이 “K8s 가 뭐 하는 친구인가” 였다면, 이 글은 그 K8s 에게 실제로 어떻게 시키는가 입니다. 같은 시기에 올린 Airflow on K8s 시리즈 의 보조 자료로도 같이 보면 좋아요.
1. 들어가기 전: K8s 오브젝트가 뭐길래
K8s 에게 일을 시키는 방법은 거의 항상 같은 모양이에요. “이런 상태로 있어 줘” 라고 적은 YAML 한 장을 kubectl apply -f 로 던지는 것. K8s 는 그 YAML 을 오브젝트(Object) 라는 단위로 받아두고, 실제 클러스터를 그 상태에 맞추려고 계속 노력해요.
모든 오브젝트는 같은 4개의 최상위 키를 가져요.
apiVersion: <어느 API 그룹/버전인가>
kind: <어떤 종류의 오브젝트인가 — Pod, Service, ...>
metadata:
name: <이름>
namespace: <어느 네임스페이스에 속하나>
spec:
<오브젝트마다 다른 본문>
apiVersion + kind 가 이 YAML 이 뭐를 만드는지를 정하고, metadata 가 식별자, spec 이 본문이에요. 이 골격만 익혀두면 처음 보는 오브젝트도 어디를 봐야 할지 감이 와요.
✅ 헷갈리지 말 것: YAML 은 현재 상태가 아니라 원하는 상태(desired state) 를 적어요. K8s 가 그 상태를 만들기 위해 어떤 명령을 어떤 순서로 내릴지는 K8s 가 알아서 합니다.
2. Namespace — 다 담는 그릇
가장 먼저 깔리는 친구. 클러스터 안을 논리적으로 나누는 칸막이 예요. 같은 클러스터에서 팀별/환경별로 자원을 분리할 때 씁니다.
| 무슨 문제를 푸나 | 한 클러스터에서 여러 팀/환경(개발, 운영) 이 서로 자원을 침범하지 않게 |
| 자주 같이 쓰는 친구 | 거의 모든 다른 오브젝트가 metadata.namespace 로 소속됨 |
apiVersion: v1
kind: Namespace
metadata:
name: web-demo
이 한 줄짜리 오브젝트가 깔리면, 앞으로 만들 Pod / Service / Deployment 같은 친구들이 모두 web-demo 라는 네임스페이스 안에 모이게 돼요. 다른 네임스페이스에서는 따로 적지 않으면 안 보입니다.
💡 네임스페이스를 안 적으면 자동으로
default로 들어가요. 실험은 OK 지만 운영에서는 항상 명시적으로 박는 걸 추천.
3. Pod — 한 묶음 컨테이너
K8s 의 스케줄링 최소 단위. 컨테이너 1개~여러 개를 같은 노드, 같은 네트워크에 묶어서 돌리는 묶음이에요. 보통은 컨테이너 한 개짜리 Pod 가 대부분이에요.
| 무슨 문제를 푸나 | “컨테이너를 어디다 어떻게 띄울까” 를 K8s 에게 묘사 |
| 자주 같이 쓰는 친구 | Pod 를 직접 만드는 일은 드물고, 대부분 Deployment 가 대신 만들어줌 |
apiVersion: v1
kind: Pod
metadata:
name: web
namespace: web-demo
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
containerPort: 80 은 컨테이너가 안에서 듣는 포트 예요. 이걸 외부에 어떻게 노출할지는 Pod 의 일이 아니라 Service / Ingress 의 일이에요(뒤에서 봅니다). labels.app: web 은 다른 오브젝트가 이 Pod 를 찾아낼 때 쓰는 꼬리표예요.
⚠️ Pod 는 휘발성이라고 생각하는 게 안전해요. 죽으면 같은 이름의 Pod 가 자동으로 부활하지 않아요. 자동 부활은 다음 항목인
Deployment가 책임집니다.
4. Deployment — “같은 Pod 가 N개 떠 있어 줘” 선언
Pod 를 개수와 상태로 관리 하는 오브젝트. “이 모양의 Pod 가 항상 3개 있어 줘” 라고 적어두면 K8s 가 그 상태를 유지해줘요. 노드가 죽거나 Pod 가 죽으면 다른 곳에 다시 띄워요.
| 무슨 문제를 푸나 | Pod 자가복구, 스케일, 무중단 롤링 업데이트 |
| 자주 같이 쓰는 친구 | 거의 항상 Service 와 짝. Deployment 가 만든 Pod 들 앞에 Service 가 안정 주소 |
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: web-demo
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
핵심 세 가지만 보면 돼요.
replicas: 3— 같은 모양 Pod 를 3개 유지해줘selector.matchLabels— “내가 관리하는 Pod 는 이 라벨을 단 애들이야”template— 그 Pod 가 어떻게 생겼는지 (= 위 항목의 Pod spec 과 같은 구조)
이미지 태그(nginx:1.27) 만 바꿔서 kubectl apply 하면 K8s 가 알아서 롤링 업데이트 를 굴려요. Pod 를 한 번에 다 바꾸지 않고, 새 버전을 하나씩 띄우고 옛 버전을 하나씩 내려요.
✅ 운영에서 Pod 를 직접 만드는 경우는 거의 없어요. 거의 모든 워크로드는 Deployment(또는 StatefulSet / DaemonSet) 으로 감싸서 굴립니다.
5. Service — Pod 들 앞의 안정적인 주소
Pod 는 죽고 살고를 반복하니까 IP 가 계속 바뀌어요. 그래서 이름과 IP 가 안 바뀌는 안정적인 입구 가 필요해요. 그게 Service 예요.
| 무슨 문제를 푸나 | 끊임없이 바뀌는 Pod 들 앞에 한 자리에 박힌 주소를 둠 |
| 자주 같이 쓰는 친구 | Deployment (뒤에 깔리는 Pod 묶음), Ingress (Service 를 외부에 노출) |
apiVersion: v1
kind: Service
metadata:
name: web
namespace: web-demo
spec:
selector:
app: web
ports:
- port: 80 # 서비스가 받는 포트
targetPort: 80 # 뒤에 있는 Pod 의 containerPort
selector.app: web 이 Deployment 가 단 라벨과 같아서 자동으로 그 Pod 들을 묶어줘요. 클러스터 안에서 다른 Pod 가 http://web.web-demo.svc.cluster.local 로 부르면 이 Service 가 받아서 뒤의 Pod 들에 부하 분산해요.
💡 Service 의 종류(
type) 가 몇 가지 있어요(ClusterIP기본 /NodePort/LoadBalancer). 입문 단계에선 클러스터 내부용인ClusterIP만 알아도 충분. 외부 노출은 다음 친구인Ingress가 맡아요.
6. Ingress — 클러스터 밖에서 안으로 들어오는 입구
Service 는 클러스터 내부 주소예요. 클러스터 밖 에서 사용자가 브라우저로 접속하려면 한 단계 더 필요해요. 그 입구가 Ingress.
| 무슨 문제를 푸나 | HTTP/HTTPS 트래픽을 호스트/경로 기준으로 클러스터 안 Service 로 라우팅 |
| 자주 같이 쓰는 친구 | Service (뒤에서 받는 쪽), Ingress Controller(NGINX, Traefik 등 실제로 트래픽을 처리하는 데몬) |
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
namespace: web-demo
spec:
rules:
- host: web.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
web.example.com 으로 들어오는 모든 요청을 web 이라는 Service 의 80 포트로 보내요. 같은 클러스터에서 여러 호스트/경로를 한 곳에서 묶어서 관리할 수 있어요.
🚨 Ingress 오브젝트만 만든다고 트래픽이 들어오지 않아요. 실제로 트래픽을 받아주는 Ingress Controller(예:
ingress-nginx) 가 클러스터에 깔려 있어야 해요. 보통은 운영팀이 미리 깔아둠.
7. ConfigMap — 설정값을 코드/이미지와 분리
같은 컨테이너 이미지를 개발/스테이징/운영에서 똑같이 쓰되, 설정값만 환경에 맞게 바꾸고 싶어요. 그때 쓰는 게 ConfigMap.
| 무슨 문제를 푸나 | 환경설정/플래그/연결 정보 같은 민감하지 않은 키-값 을 코드 밖으로 분리 |
| 자주 같이 쓰는 친구 | Deployment (env 또는 볼륨으로 주입), Secret (비밀값은 Secret 으로 짝) |
apiVersion: v1
kind: ConfigMap
metadata:
name: web-config
namespace: web-demo
data:
GREETING: "Hello from K8s"
LOG_LEVEL: "info"
이 ConfigMap 을 만들고 나면, 컨테이너에 환경변수로 꽂아 넣을 수 있어요. Deployment 의 containers[0] 안에 다음 한 토막만 추가하면 돼요.
envFrom:
- configMapRef:
name: web-config
이러면 컨테이너 안에서 $GREETING / $LOG_LEVEL 환경변수로 바로 읽혀요. 이미지 다시 안 굽고도 설정만 갈아끼울 수 있는 게 핵심이에요.
💡 ConfigMap 은 암호화되지 않아요. 평문으로 클러스터에 박힘. 비밀번호/토큰은 절대 ConfigMap 에 넣지 말고 다음 친구로.
8. Secret — 비밀값 따로
ConfigMap 의 비밀값 전용 짝꿍. 비밀번호, API 토큰, TLS 인증서 같은 노출되면 안 되는 값 을 따로 모아둬요.
| 무슨 문제를 푸나 | 비밀값을 일반 환경설정과 분리, 권한과 감사를 따로 관리 |
| 자주 같이 쓰는 친구 | Deployment (env/볼륨으로 주입), 외부 시크릿 매니저(Vault, AWS Secrets Manager 등) |
apiVersion: v1
kind: Secret
metadata:
name: web-secret
namespace: web-demo
type: Opaque
stringData:
API_TOKEN: "<API_TOKEN>"
stringData 는 평문으로 적어두면 K8s 가 알아서 base64 인코딩해서 저장해줘요. 컨테이너에서 꺼내 쓸 때는 ConfigMap 과 똑같이 envFrom 으로 끼우면 돼요.
envFrom:
- secretRef:
name: web-secret
이러면 $API_TOKEN 으로 잡혀요.
🚨 Secret 은 기본 설정에서는 base64 인코딩일 뿐 암호화는 아니에요. etcd 가 평문에 가깝게 보관해요. 운영에서는 etcd 암호화 활성화 + Sealed Secrets / External Secrets Operator 같이 묶어 쓰는 게 표준. 다만 ConfigMap 보다는 한 단계 더 격리되니까 비밀값은 반드시 Secret 으로.
9. 같이 굴려보면 어떻게 묶이나
지금까지 본 7개를 web-demo 네임스페이스에 한꺼번에 깔면 이런 모양이에요.
[ 클라이언트 ]
│ https://web.example.com
▼
[ Ingress (networking.k8s.io/v1) ]
│ host=web.example.com → service:web
▼
[ Service web (ClusterIP, port 80) ]
│ selector: app=web
▼ Pod 들 사이에서 부하 분산
┌───────────┬───────────┬───────────┐
│ Pod 1 │ Pod 2 │ Pod 3 │ ← Deployment(replicas: 3)
│ nginx │ nginx │ nginx │ template.metadata.labels.app=web
│ :80 │ :80 │ :80 │ env:
└───────────┴───────────┴───────────┘ envFrom: [ConfigMap, Secret]
↑
web-config (GREETING / LOG_LEVEL)
web-secret (API_TOKEN)
[ 모두 Namespace: web-demo 안 ]
흐름을 말로 풀면:
- 사용자가
web.example.com으로 들어옴 - Ingress 컨트롤러가 받아서 라우팅 규칙대로 Service
web으로 넘김 - Service 가 selector 로 묶인 Pod 들 중 하나에 트래픽 분배
- Pod 안의 nginx 가 응답. 환경변수
GREETING,LOG_LEVEL,API_TOKEN은 ConfigMap/Secret 에서 받아둔 값 - Pod 하나가 죽으면 Deployment 가 자동으로 새로 띄워서 다시 3개를 채움
이 다섯 단계가 거의 모든 K8s 위 웹 서비스의 기본 골격이에요. 같이 깔 때는 보통 한 YAML 안에 --- 로 구분해서 모아두거나, 디렉토리 한 폴더 안에 파일별로 두고 kubectl apply -f ./manifests/ 로 한꺼번에 적용해요.
10. 마무리
이 7개만 익히면 K8s YAML 의 80% 정도 는 읽힙니다. 나머지는 이런 친구들이에요.
| 다음 단계 친구 | 언제 |
|---|---|
StatefulSet |
상태를 가진 워크로드(DB, 카프카) — 각 Pod 가 고유 이름/디스크 |
DaemonSet |
모든 노드에 1개씩 — 로그수집, 노드 에이전트 |
Job / CronJob |
끝나면 사라지는 일회성/주기성 잡 |
PersistentVolumeClaim |
영속 디스크가 필요할 때 (StatefulSet/Deployment 의 짝) |
ServiceAccount / Role / RoleBinding |
권한(RBAC) — 누가 무엇을 할 수 있는지 |
이 친구들도 결국 위에서 본 4개의 최상위 키(apiVersion/kind/metadata/spec) 골격을 그대로 따라요. 한 번 길이 보이면 나머지는 같은 패턴이에요.
같이 보면 좋은 글.
- Docker 와 Kubernetes 의 관계: 워커와 오케스트레이션 관점에서 — 이 글의 자매편(개념)
- (1/4) Airflow on K8s 시리즈 개요 — 위 7개 오브젝트가 실제 어떻게 묶이는지 한 도메인에서 끝까지 보여주는 예
일단 오늘은 여기까지…..
다음 글에서는 위 표의 다음 단계 친구들(StatefulSet, Job, PersistentVolumeClaim) 을 같은 결로 풀어볼게요.