K8s YAML 생성
KIOPS의 [서비스 관리] 페이지에서는 Kubernetes 배포 YAML을 직접 작성할 필요가 없습니다. 폼에서 옵션을 선택하면 표준 YAML이 자동 생성되며, 미리보기에서 확인하거나 직접 편집할 수 있습니다.
이 가이드는 가장 단순한 "Hello World" 배포부터 시작하여 5단계에 걸쳐 점진적으로 기능을 추가합니다. 각 단계는 이전 단계의 YAML을 그대로 유지한 채 새로운 리소스 1개씩만 더하는 방식이므로, 처음 Kubernetes를 접하는 분도 부담 없이 따라할 수 있습니다.
왜 이 가이드를 읽어야 하는가
KIOPS가 YAML을 자동 생성해 주더라도, 미리보기에 표시되는 내용을 이해해야 잘못된 설정을 배포 전에 발견할 수 있습니다. 또한 특수한 요구사항이 생기면 자동 생성된 YAML을 직접 편집해야 하는 경우도 있습니다. 이 가이드는 폼에서 선택한 옵션이 YAML의 어떤 부분으로 만들어지는지 매핑해 주는 학습 자료입니다.
YAML 생성 페이지 진입
- [서비스 관리] 페이지에서 서비스를 선택합니다.
- 파이프라인의 Deploy 단계를 클릭합니다.
- 배포 환경 설정에서 K8s 배포를 선택하면 YAML 생성 화면이 표시됩니다.
- Step 1~5는 처음부터 순서대로 읽으세요. 단순한 정적 사이트에서 시작해 데이터베이스와 일회성 작업까지 확장합니다.
- 부록: Kind별 참고는 옵션 사전입니다. 워크스루 중간이나 이후에 필요한 항목만 찾아보세요.
- 모든 YAML 예시에는
# [필수]또는# [선택]주석이 있어 어떤 값을 꼭 채워야 하는지 한눈에 보입니다.
시작하기 전에: 알아둘 10가지 용어
본문에 들어가기 전에 자주 등장하는 용어를 한 줄씩 정리합니다. 각 Step에서 처음 등장하는 시점에 "(용어 설명: 시작하기 전에 참고)"로 다시 짚어 드립니다.
| 용어 | 정의 + 일상 비유 |
|---|---|
| 컨테이너 | 앱 하나를 격리해서 실행하는 격리 박스. 도시락 한 칸처럼, 안의 음식(앱)은 옆 칸과 섞이지 않습니다. |
| 이미지 | 컨테이너를 만들기 위한 스냅샷(틀). 붕어빵 틀과 같습니다. 같은 틀로 여러 개를 찍어낼 수 있습니다. |
| Pod | 컨테이너를 묶어 클러스터에서 실행하는 최소 실행 단위. 아파트 한 호(號)와 같습니다. 같은 호 안의 컨테이너들은 주소와 자원을 공유합니다. |
| 네임스페이스 | 같은 클러스터 안에서 리소스를 구분하는 칸막이(폴더 같은 개념). 회사 안의 부서처럼 같은 건물(클러스터)을 부서별로 나눠 씁니다. |
| 클러스터 | 여러 노드(서버)를 묶어 Kubernetes가 관리하는 한 덩어리. 여러 동의 빌딩(서버)을 묶은 단지(團地)와 비슷합니다. |
| YAML 들여쓰기 | 들여쓰기는 공백 2칸, 탭(tab) 키 금지. 마치 글머리표가 어긋난 보고서처럼, 한 칸이라도 어긋나면 전체가 인식되지 않습니다. |
| 레이블(Label) | Pod/리소스에 붙이는 이름표. 예: app: hello-world. 이름표가 같은 Pod들을 한 묶음으로 다룰 때 사용합니다. |
| Deployment | Pod를 정해진 개수만큼 살려두는 관리자. 빌딩 관리소처럼 비어 있는 호(Pod)가 생기면 새로 채워줍니다. |
| Service | Pod의 변하지 않는 우편번호. Pod가 바뀌어도 같은 주소로 들어가면 살아있는 Pod에 자동 연결됩니다. |
| Secret | ConfigMap의 금고 버전. 비밀번호, API 키 같은 민감 정보를 담습니다. |
Step 1: Hello World 배포 (Deployment + Service)
전제: 없음 (시작 단계)
이 단계에서 배우는 것: Pod를 띄우고 클러스터 내부에서 접근 가능한 가장 단순한 형태.
왜 필요한가
컨테이너를 직접 실행하면 장애 발생 시 자동 복구되지 않고, Pod가 재시작되면 IP가 바뀌어 다른 곳에서 접근할 수 없습니다.
- Deployment가 Pod 개수를 유지하고 장애 시 자동 재시작합니다.
- Service가 변하지 않는 고정 주소를 제공하여 Pod IP가 바뀌어도 접근 가능하게 합니다.
만약 Service 없이 Deployment만 만들면 같은 클러스터 안의 다른 Pod조차 이 Pod를 안정적으로 호출할 방법이 없습니다.
(컨테이너 / Pod / 네임스페이스 / 클러스터는 용어 사전 참고)
- 이름 (
metadata.name): 리소스 식별자. Deployment와 Service 각각 필요합니다. - 이미지 (
spec.containers[].image): 컨테이너 이미지 주소. 빌드 목록에서 선택하거나 직접 입력합니다. - 레이블 매칭 (
selector.matchLabels↔template.metadata.labels):labels는 Pod에 붙은 이름표,selector.matchLabels는 "이 이름표가 붙은 애들을 내가 관리할게"라는 선언입니다. 두 값이 다르면 관리자가 다른 사람을 찾고 있는 셈이라 Pod가 방치됩니다. - 포트 연결 (
containerPort, Service의port/targetPort): targetPort가 컨테이너의 containerPort와 같아야 트래픽이 도달합니다.
옵션을 더 알고 싶다면 → 부록: Deployment, 부록: Service
- selector와 labels 불일치:
matchLabels의app: hello-world와template.metadata.labels의 값을 다르게 적으면, Pod는 떠 있는데 Service의 Endpoint가 비어서 502 에러나 연결 거부가 발생합니다. 두 값을 항상 같이 수정하세요. - port와 targetPort 혼동:
port는 Service 외부에서 받는 번호,targetPort는 컨테이너 내부에서 듣는 번호입니다. 컨테이너가 8080을 듣는데targetPort: 80으로 적으면 "Connection refused"가 떨어집니다. 컨테이너 이미지의 EXPOSE 포트를 확인하고 맞추세요.
KIOPS 설정 요약
| 항목 | 값 | YAML 필드 |
|---|---|---|
| Deployment 이름 | hello-world | metadata.name (Deployment) |
| 네임스페이스 | my-app | metadata.namespace |
| 레플리카 | 1 | spec.replicas |
| 이미지 | nginx:1.25 | spec.template.spec.containers[0].image |
| 컨테이너 포트 | 80 | containers[0].ports[0].containerPort |
| Service 이름 | hello-world-svc | metadata.name (Service) |
| Service 타입 | ClusterIP | spec.type (Service) |
생성되는 YAML 확인
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world # [필수] 이름 (위 핵심 옵션 참고)
namespace: my-app # [선택] 기본값: default
labels:
app: hello-world # [선택] 리소스 분류용 레이블
spec:
replicas: 1 # [선택] 기본값: 1, 동시에 띄울 Pod 복제본 수
selector:
matchLabels:
app: hello-world # [필수] 레이블 매칭 - template.metadata.labels와 일치해야 함
template:
metadata:
labels:
app: hello-world # [필수] 레이블 매칭 - selector.matchLabels와 매칭
spec:
containers:
- name: web # [필수] 컨테이너 이름
image: nginx:1.25 # [필수] 이미지 (위 핵심 옵션 참고)
ports:
- containerPort: 80 # [선택] 포트 연결 - 컨테이너가 수신하는 포트
---
apiVersion: v1
kind: Service
metadata:
name: hello-world-svc # [필수] Service 이름
namespace: my-app # [선택] 기본값: default
spec:
type: ClusterIP # [선택] 기본값: ClusterIP
selector:
app: hello-world # [필수] 레이블 매칭 - Deployment의 Pod 레이블과 일치
ports:
- name: http # [선택] 포트 식별자
protocol: TCP # [선택] 기본값: TCP
port: 80 # [필수] 포트 연결 - Service가 수신하는 포트
targetPort: 80 # [필수] 포트 연결 - 트래픽을 전달할 컨테이너 포트
다음 단계 미리보기
Step 1의 Service는 클러스터 내부에서만 접근 가능합니다. Step 2에서는 Ingress를 추가해 외부 도메인으로 접근하게 만듭니다.
Step 2: 외부에서 도메인으로 접근 (Ingress 추가)
전제: Step 1 YAML 완성
이 단계에서 배우는 것: app.example.com 같은 도메인으로 외부 사용자가 접근할 수 있게 하는 방법.
(클러스터, 네임스페이스는 용어 사전 참고)
왜 필요한가
Step 1의 ClusterIP Service는 클러스터 내부 전용입니다. 외부 사용자가 브라우저로 접속하려면 다음 중 하나가 필요합니다.
- Ingress (이번 단계, 권장): 도메인 기반 라우팅과 TLS 인증서를 한곳에서 관리할 수 있습니다.
- NodePort/LoadBalancer Service: 도메인 처리가 어렵고 HTTPS 설정이 번거롭습니다.
Ingress 없이 운영하면 도메인마다 다른 IP를 외워야 하고, HTTPS 인증서도 애플리케이션이 직접 처리해야 합니다.
- 호스트 (
spec.rules[].host): 외부 사용자가 입력할 도메인. DNS가 클러스터의 Ingress Controller IP를 가리키도록 설정되어 있어야 합니다. 이 작업은 IT 담당자 또는 운영팀에 요청하세요. - 경로와 백엔드 Service (
paths[].backend.service.name/port.number): 어떤 URL을 어떤 Service로 보낼지 결정합니다. Service 이름과 port 번호는 Step 1에서 만든 값과 정확히 일치해야 합니다. - pathType:
Prefix로 설정하면/api,/api/users,/api/v1/data모두 매칭됩니다.Exact는 정확히/api만 매칭. 대부분Prefix를 씁니다. - Ingress Class (
spec.ingressClassName): 어떤 Ingress Controller가 이 Ingress를 처리할지 지정합니다. KIOPS는traefik을 기본 권장합니다.
옵션을 더 알고 싶다면 → 부록: Ingress
- 경로 순서:
paths에/를 먼저,/api를 나중에 쓰면/api요청도/로 매칭됩니다. 더 구체적인 경로(/api)를 위에 두세요. - DNS 설정 누락: 도메인이 클러스터의 Ingress Controller IP를 가리키도록 DNS 설정이 필요합니다. 이 작업은 IT 담당자 또는 운영팀에 요청하세요.
KIOPS 설정 요약
Step 1 YAML은 그대로 유지하고 Ingress 1개를 추가합니다.
| 항목 | 값 | YAML 필드 |
|---|---|---|
| Ingress 이름 | hello-world-ingress | metadata.name |
| 네임스페이스 | my-app | metadata.namespace |
| Ingress Class | traefik | spec.ingressClassName |
| 호스트 | app.example.com | spec.rules[0].host |
| 경로 | / → hello-world-svc:80 | spec.rules[0].http.paths[0].path + backend.service |
추가되는 Ingress YAML 확인
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hello-world-ingress # [필수] Ingress 이름
namespace: my-app # [선택] 기본값: default
spec:
ingressClassName: traefik # [선택] Ingress Class (위 핵심 옵션 참고)
rules:
- host: app.example.com # [필수] 호스트 (위 핵심 옵션 참고)
http:
paths:
- path: / # [필수] 경로와 백엔드 Service - 매칭할 URL 경로
pathType: Prefix # [필수] Prefix(접두사 매칭) 또는 Exact
backend:
service:
name: hello-world-svc # [필수] 경로와 백엔드 Service - 트래픽을 전달할 Service 이름
port:
number: 80 # [필수] 경로와 백엔드 Service - Service의 port 값
HTTPS를 사용하려면 spec.tls 항목에 secretName과 hosts를 추가하고, 해당 도메인의 인증서가 담긴 Secret을 미리 생성해야 합니다. 자세한 내용은 도메인/SSL 설정 가이드를 참고하세요.
다음 단계 미리보기
Step 2까지는 nginx 이미지를 그대로 사용했습니다. Step 3에서는 애플리케이션에 환경 변수를 주입하기 위해 ConfigMap을 추가합니다.
Step 3: 설정값 분리 (ConfigMap 추가)
전제: Step 2 YAML 완성
이 단계에서 배우는 것: 환경 변수를 ConfigMap으로 분리하여 YAML 본문에서 빼내는 방법.
(환경 변수 의미가 흐릿하다면 IT 담당자 또는 운영팀에 문의하세요. Secret은 용어 사전 참고)
왜 필요한가
환경 변수를 Deployment YAML에 직접 적으면 다음과 같은 문제가 생깁니다.
- 개발/운영 환경마다 Deployment를 따로 관리해야 합니다.
- 여러 Deployment가 같은 설정을 공유할 수 없습니다.
- 설정만 바꾸려 해도 Deployment 전체를 수정해야 합니다.
ConfigMap으로 설정값을 분리하면 한 곳에서 관리할 수 있고, envFrom으로 일괄 주입하면 변수 이름을 일일이 매핑할 필요가 없습니다.
ConfigMap은 평문으로 저장됩니다. 비밀번호, API 키, 토큰 등 민감 정보는 ConfigMap이 아닌 Secret에 저장하고 동일한 방식(envFrom: secretRef)으로 주입하세요.
- ConfigMap 데이터 (
data): key-value 형태. 값은 모두 문자열이며, 숫자나 boolean도 따옴표로 감싸야 합니다. - envFrom + configMapRef: ConfigMap의 모든 key가 환경 변수 이름이 되고 value가 환경 변수 값이 됩니다. 개별 키를 하나씩 매핑하지 않아도 됩니다.
- 이름 일치: Deployment의
configMapRef.name은 ConfigMap의metadata.name과 정확히 같아야 하며, 같은 네임스페이스에 있어야 합니다.
옵션을 더 알고 싶다면 → 부록: ConfigMap
- 네임스페이스 불일치: ConfigMap을 못 찾는다는 에러가 난다면 Deployment와 ConfigMap의
namespace가 같은지 확인하세요. 네임스페이스를 넘어선 참조는 불가능합니다. - 민감 정보 ConfigMap에 저장: 비밀번호나 API 키를 ConfigMap에 넣으면 YAML 파일에 평문으로 노출됩니다. 반드시 Secret을 사용하세요.
KIOPS 설정 요약
Step 2 YAML에 ConfigMap 1개를 추가하고, Deployment에 envFrom을 추가합니다.
| 항목 | 값 | YAML 필드 |
|---|---|---|
| ConfigMap 이름 | app-config | metadata.name (ConfigMap) |
| 데이터 | WELCOME_MESSAGE, LOG_LEVEL, FEATURE_FLAG | data |
| Deployment 변경 | envFrom으로 app-config 전체 주입 | containers[0].envFrom[0].configMapRef.name |
추가/변경되는 YAML 확인
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config # [필수] ConfigMap 이름
namespace: my-app # [선택] 기본값: default
data: # [필수] ConfigMap 데이터 - 한 개 이상의 key-value 쌍
WELCOME_MESSAGE: "Hello from KIOPS"
LOG_LEVEL: info
FEATURE_FLAG: "true"
Deployment의 containers 부분에 다음을 추가합니다.
containers:
- name: web
image: nginx:1.25
ports:
- containerPort: 80
envFrom: # [선택] envFrom + configMapRef - ConfigMap 전체를 환경 변수로 주입
- configMapRef:
name: app-config # [필수] 이름 일치 - 참조할 ConfigMap 이름
다음 단계 미리보기
Step 3에서는 설정을 분리했지만, 아직 데이터를 저장할 곳이 없습니다. Step 4에서는 PVC를 추가하여 Pod가 재시작되어도 데이터가 사라지지 않도록 합니다.
Step 4: 데이터 영구 저장 (PVC + 데이터베이스 추가)
전제: Step 3 YAML 완성
이 단계에서 배우는 것: 데이터베이스를 PVC와 함께 배포하여 Pod 재시작 시에도 데이터가 유지되도록 하는 방법.
(Secret 의미는 용어 사전 참고, Secret 생성을 미리 IT 담당자 또는 운영팀에 요청하세요)
왜 필요한가
Pod의 파일 시스템은 일시적입니다. Pod가 재시작되거나 다른 노드로 이동하면 컨테이너 내부의 모든 파일이 사라집니다. 이는 nginx 같은 정적 서버에는 문제가 없지만 데이터베이스에는 치명적입니다.
**PVC (PersistentVolumeClaim)**는 Pod와 별개로 존재하는 외부 스토리지를 요청하는 리소스입니다. Deployment에서 이 PVC를 마운트하면 데이터가 Pod 라이프사이클과 무관하게 보존됩니다.
또한 데이터베이스 Deployment는 다음 두 가지가 일반 애플리케이션과 다릅니다.
- replicas: 1: 동시에 여러 Pod가 같은 볼륨에 쓰면 데이터가 손상될 수 있습니다.
- strategy: Recreate: 새 Pod를 먼저 띄우는 기본 RollingUpdate는 두 Pod가 동시에 같은 PVC를 잡으려고 시도하므로 실패합니다. 기존 Pod를 먼저 종료한 뒤 새 Pod를 생성하는
Recreate를 사용합니다.
이 Step은 db-credentials라는 이름의 Secret이 미리 생성되어 있다고 가정합니다. Secret 생성은 IT 담당자 또는 운영팀에 요청하세요. 또한 이 Step에서는 PVC, Recreate 전략, volumeMounts/volumes 연결, Secret 참조(secretKeyRef) 등 4가지 새로운 개념이 한꺼번에 도입됩니다.
- PVC 용량 (
spec.resources.requests.storage):10Gi,100Gi등 크기 단위 포함. 생성 후 늘리는 것은 스토리지 클래스에 따라 제한됩니다. - 접근 모드 (
accessModes): 대부분ReadWriteOnce(한 서버에서만 쓰고 읽기, DB 대부분이 이걸 씁니다). 다중 노드 공유가 필요할 때만ReadWriteMany를 사용합니다. - volumes ↔ volumeMounts 연결:
volumes[].name과volumeMounts[].name이 같아야 하고,volumes[].persistentVolumeClaim.claimName은 PVC의metadata.name과 같아야 합니다. - Recreate 전략: 단일 PVC를 사용하는 Pod 한 개짜리 DB는
Recreate로 설정해야 새 Pod 배포 시 충돌이 발생하지 않습니다.
옵션을 더 알고 싶다면 → 부록: PersistentVolumeClaim (PVC), 부록: Deployment의 볼륨 부분
- PVC 함부로 삭제: Deployment를 지우면 Pod는 사라지지만 PVC는 남습니다. 하지만
kubectl delete -f로 YAML 전체를 지우면 PVC도 같이 삭제되어 데이터가 영구 손실됩니다. 삭제 전 백업 + 의도적으로kubectl delete deployment만 사용하는 습관을 들이세요. - Recreate 트레이드오프:
Recreate는 기존 Pod를 완전히 종료한 뒤 새 Pod를 띄우므로 수십 초~수 분 다운타임이 발생합니다. DB처럼 한 Pod만 띄워야 하는 경우엔 어쩔 수 없지만, 일반 웹 앱에는 절대 적용하지 마세요(Step 1처럼RollingUpdate기본값 사용).
KIOPS 설정 요약
Step 3 YAML에 PVC 1개, Deployment(postgres) 1개, Service(postgres-svc) 1개를 추가합니다.
| 항목 | 값 | YAML 필드 |
|---|---|---|
| PVC 이름 | postgres-data | metadata.name (PVC) |
| 용량 | 10Gi | spec.resources.requests.storage |
| 스토리지 클래스 | nfs-client | spec.storageClassName |
| 접근 모드 | ReadWriteOnce | spec.accessModes[0] |
| DB Deployment 이름 | postgres | metadata.name (Deployment) |
| 이미지 | postgres:15 | spec.template.spec.containers[0].image |
| 전략 | Recreate | spec.strategy.type |
| 볼륨 마운트 | /var/lib/postgresql/data | containers[0].volumeMounts[0].mountPath |
추가되는 PVC YAML 확인
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data # [필수] PVC 이름
namespace: my-app # [선택] 기본값: default
spec:
accessModes:
- ReadWriteOnce # [필수] 접근 모드 (위 핵심 옵션 참고)
storageClassName: nfs-client # [선택] 클러스터 기본 스토리지 클래스 사용 시 생략 가능
resources:
requests:
storage: 10Gi # [필수] PVC 용량 (위 핵심 옵션 참고)
추가되는 Postgres Deployment + Service YAML 확인
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres # [필수] Deployment 이름
namespace: my-app # [선택] 기본값: default
spec:
replicas: 1 # [선택] DB는 1로 고정 권장
strategy:
type: Recreate # [선택] Recreate 전략 (위 핵심 옵션 참고)
selector:
matchLabels:
app: postgres # [필수]
template:
metadata:
labels:
app: postgres # [필수]
spec:
containers:
- name: postgres # [필수]
image: postgres:15 # [필수]
ports:
- containerPort: 5432
# env: 평범한 값(POSTGRES_DB)은 직접 적고, 민감한 값(POSTGRES_PASSWORD)은 valueFrom.secretKeyRef로 Secret에서 꺼냅니다.
env:
- name: POSTGRES_DB
value: myapp
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials # [필수] 사전에 생성된 Secret 이름 (사전 준비 참고)
key: password # [필수] Secret 내 key 이름
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: postgres-storage # [필수] volumes ↔ volumeMounts 연결 - 아래 volumes의 name과 일치
mountPath: /var/lib/postgresql/data # [필수] 컨테이너 내부 마운트 경로
volumes:
- name: postgres-storage # [필수] volumes ↔ volumeMounts 연결 - volumeMounts.name과 일치
persistentVolumeClaim:
claimName: postgres-data # [필수] volumes ↔ volumeMounts 연결 - PVC의 metadata.name과 일치
---
apiVersion: v1
kind: Service
metadata:
name: postgres-svc # [필수]
namespace: my-app # [선택] 기본값: default
spec:
type: ClusterIP
selector:
app: postgres # [필수] Postgres Pod 레이블과 일치
ports:
- port: 5432
targetPort: 5432
PVC를 삭제하면 연결된 데이터가 영구 손실될 수 있습니다. 운영 환경에서 PVC 삭제 전에는 반드시 백업 여부를 확인하세요.
다음 단계 미리보기
Step 4까지는 모든 리소스가 계속 실행되는 형태였습니다. Step 5에서는 한 번만 실행하고 종료되는 Job을 추가하여 DB 마이그레이션을 자동화합니다.
Step 5: 일회성 작업 (Job 추가)
전제: Step 4 YAML 완성
이 단계에서 배우는 것: 배포 전 한 번만 실행되어야 하는 DB 마이그레이션을 Job으로 구성하는 방법.
왜 필요한가
데이터베이스 스키마 변경, 초기 데이터 삽입, 일회성 정리 스크립트 등은 다음과 같은 특징이 있습니다.
- 한 번만 실행되면 끝납니다 (계속 떠 있을 필요 없음).
- 실패하면 재시도해야 합니다 (DB 연결 일시 장애 등).
- 성공 여부를 명확히 확인해야 합니다 (Pod의 종료 코드).
Deployment로 만들면 작업이 끝난 뒤에도 Pod가 계속 살아있으려 하고, 종료되면 자동으로 재시작됩니다. Job은 작업 완료 후 Pod를 종료된 상태로 남기고, 실패 시 정해진 횟수만큼 재시도합니다.
- command와 args: 이미지의 기본 ENTRYPOINT를 덮어쓰고 마이그레이션 명령만 실행합니다. command가 실행 파일, args가 그 인자입니다.
- restartPolicy (
OnFailure/Never): Job은Always를 사용할 수 없습니다.OnFailure는 같은 Pod 안에서 컨테이너만 재시작하고,Never는 매번 새 Pod를 생성합니다. - backoffLimit: 재시도 한계. 초과하면 Job이 실패 상태로 기록됩니다. 너무 적으면(0
1) 일시 장애에 곧바로 실패 처리되고, 너무 많으면(10+) 코드 오류가 있을 때 무한히 재시도하면서 클러스터 리소스를 잡아먹습니다. 35가 적절합니다. - envFrom 재사용: Step 3에서 만든
app-configConfigMap을 그대로 사용합니다. 동일한 DB 접속 정보를 마이그레이션과 애플리케이션이 공유합니다.
옵션을 더 알고 싶다면 → 부록: Job
- Job과 Deployment 동시 시작 주의: 마이그레이션 순서 보장이 필요합니다 - 아래
Job 실행 순서박스 참고. backoffLimit는 3~5 권장: 자세한 이유는 위tip박스 참고.
KIOPS 설정 요약
Step 4 YAML에 Job 1개를 추가합니다.
| 항목 | 값 | YAML 필드 |
|---|---|---|
| Job 이름 | db-migrate | metadata.name |
| 이미지 | harbor.example.com/my-app/migrator:v1.0 | spec.template.spec.containers[0].image |
command | ["python", "manage.py", "migrate"] | containers[0].command + args |
envFrom | app-config + db-credentials | containers[0].envFrom[] |
backoffLimit | 3 | spec.backoffLimit |
restartPolicy | OnFailure | spec.template.spec.restartPolicy |
추가되는 Job YAML 확인
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate # [필수] Job 이름
namespace: my-app # [선택] 기본값: default
spec:
backoffLimit: 3 # [선택] backoffLimit (위 핵심 옵션 참고) - 기본값: 6
template:
spec:
restartPolicy: OnFailure # [필수] restartPolicy (위 핵심 옵션 참고)
containers:
- name: migrator # [필수] 컨테이너 이름
image: harbor.example.com/my-app/migrator:v1.0 # [필수] 이미지
command: # [선택] command와 args - ENTRYPOINT 덮어쓰기
- python
args: # [선택] command와 args - 명령어 인자
- manage.py
- migrate
envFrom: # [선택] envFrom 재사용 - ConfigMap/Secret 일괄 주입
- configMapRef:
name: app-config
- secretRef:
name: db-credentials
Job은 Deployment와 동시에 생성되므로, 마이그레이션이 끝나기 전에 애플리케이션이 시작될 수 있습니다. 다음 중 하나로 대응하세요.
- 애플리케이션에 DB 연결 재시도 로직을 둡니다.
- 애플리케이션 Pod의 initContainers에서 마이그레이션 완료를 대기합니다.
다음 단계 미리보기
5단계를 마쳤습니다. 이제 정적 사이트, 외부 도메인, 설정 분리, 영구 데이터, 일회성 작업까지 갖춘 완전한 배포 구성을 이해할 수 있습니다. 더 깊이 들어가려면 아래 부록에서 각 Kind의 옵션을 자세히 살펴보세요.
부록: Kind별 옵션 참고
워크스루에서 다루지 않은 세부 옵션을 정리한 참고용 섹션입니다. 필요한 항목만 찾아보세요.
Deployment
(Step 1에서 처음 등장)
| 옵션 | 필수/선택 | 기본값 | 설명 |
|---|---|---|---|
metadata.name | 필수 | - | Deployment 리소스 이름 |
metadata.namespace | 선택 | default | 배포 대상 네임스페이스 |
spec.replicas | 선택 | 1 | Pod 개수. 운영 환경은 3 이상 권장 |
spec.selector.matchLabels | 필수 | - | 관리 대상 Pod의 레이블 |
spec.template.metadata.labels | 필수 | - | Pod에 부여되는 레이블 |
spec.template.spec.containers[].name | 필수 | - | 컨테이너 식별자 |
spec.template.spec.containers[].image | 필수 | - | 컨테이너 이미지 |
spec.template.spec.containers[].ports[].containerPort | 선택 | - | 컨테이너 수신 포트 |
spec.template.spec.containers[].imagePullPolicy | 선택 | Always | Always, IfNotPresent, Never |
spec.strategy.type | 선택 | RollingUpdate | RollingUpdate 또는 Recreate |
spec.strategy.rollingUpdate.maxSurge | 선택 | 1 | 추가로 생성할 수 있는 Pod 수 |
spec.strategy.rollingUpdate.maxUnavailable | 선택 | 0 | 동시에 종료 가능한 Pod 수 |
리소스 제한 (spec.template.spec.containers[].resources)
requests는 "최소 이만큼은 받아야 일할 수 있어요"(예약), limits는 "이만큼 넘으면 잘라주세요"(상한)입니다. 호텔 객실 최소 보장 평수 vs 최대 평수와 비슷합니다.
| 옵션 | 필수/선택 | 설명 |
|---|---|---|
requests.cpu / requests.memory | 선택 | 최소 보장량. 스케줄링 기준 (예: 100m, 128Mi) |
limits.cpu / limits.memory | 선택 | 최대 상한. 초과 시 CPU는 쓰로틀, 메모리는 OOM 종료 (예: 500m, 512Mi) |
헬스체크 (livenessProbe, readinessProbe)
| 옵션 | 필수/선택 | 설명 |
|---|---|---|
httpGet.path | 필수 | 헬스체크 경로 (예: /health) |
httpGet.port | 필수 | 헬스체크 포트 |
initialDelaySeconds | 선택 | Pod 시작 후 첫 검사까지 대기 시간 |
periodSeconds | 선택 | 검사 주기 |
timeoutSeconds | 선택 | 응답 대기 시간 |
failureThreshold | 선택 | 연속 실패 허용 횟수 |
배포 전략은 RollingUpdate(무중단 점진 교체)와 Recreate(전체 종료 후 재생성) 두 가지입니다. Blue-Green 전략은 KIOPS에서 직접 지원하지 않습니다.
노드 배치제어 (spec.template.spec)
특정 노드에만 Pod를 배치하고 싶을 때 사용합니다. KIOPS 배포 폼의 노드 배치 섹션에서 클러스터 노드의 Label/Taint를 자동 수집해 입력 후보로 제안하며, 선택한 값이 아래 YAML 필드로 생성됩니다.
| 옵션 | 필수/선택 | 설명 |
|---|---|---|
nodeSelector | 선택 | 지정한 Label과 정확히 일치하는 노드에만 배치 (예: disktype: ssd) |
affinity.nodeAffinity | 선택 | Label 조건식(In/NotIn/Exists/DoesNotExist)으로 배치. nodeSelector보다 유연 |
tolerations | 선택 | 노드의 Taint를 견뎌 배치를 허용. key/operator/value/effect(NoSchedule/PreferNoSchedule/NoExecute) |
Taint가 걸린 노드에 Pod를 배치하려면 그 Taint를 견디는 Toleration이 반드시 있어야 합니다. Toleration만으로는 해당 노드에 "배치할 수 있다"는 허용일 뿐, 강제 배치하려면 nodeSelector 또는 nodeAffinity를 함께 지정합니다.
Service
(Step 1에서 처음 등장)
| 옵션 | 필수/선택 | 기본값 | 설명 |
|---|---|---|---|
metadata.name | 필수 | - | Service 이름 |
spec.type | 선택 | ClusterIP | ClusterIP, NodePort, LoadBalancer |
spec.selector | 필수 | - | 트래픽을 전달할 Pod 레이블 |
spec.ports[].port | 필수 | - | Service가 수신하는 포트 |
spec.ports[].targetPort | 필수 | - | 트래픽을 전달할 컨테이너 포트 |
spec.ports[].protocol | 선택 | TCP | TCP, UDP, SCTP |
spec.ports[].nodePort | 선택 | 자동할당 | NodePort 타입일 때 노드에서 열리는 포트 |
타입별 사용 시나리오
| 타입 | 사용 시나리오 |
|---|---|
ClusterIP | 클러스터 내부 통신, Ingress와 함께 사용 (가장 일반적) |
NodePort | 외부 리버스 프록시(외부 Nginx 등)에서 노드 포트로 접근 |
LoadBalancer | 클라우드 환경에서 외부 IP를 직접 할당받음 |
Ingress
(Step 2에서 처음 등장)
| 옵션 | 필수/선택 | 기본값 | 설명 |
|---|---|---|---|
metadata.name | 필수 | - | Ingress 이름 |
spec.ingressClassName | 선택 | 클러스터 기본 | 처리할 Ingress Controller 지정 |
spec.rules[].host | 필수 | - | 라우팅 대상 도메인 |
spec.rules[].http.paths[].path | 필수 | - | 매칭 경로 |
spec.rules[].http.paths[].pathType | 필수 | - | Prefix 또는 Exact |
spec.rules[].http.paths[].backend.service.name | 필수 | - | 대상 Service 이름 |
spec.rules[].http.paths[].backend.service.port.number | 필수 | - | 대상 Service 포트 |
spec.tls[].secretName | 선택 | - | HTTPS 인증서를 담은 Secret 이름 |
spec.tls[].hosts | 선택 | - | TLS가 적용될 도메인 목록 |
Ingress Class 선택지 (6종)
| Class | 설명 |
|---|---|
traefik | 자동 인증서, 미들웨어 체인 지원. KIOPS 기본 권장 |
nginx | 커뮤니티 프로젝트. 2026년 3월 EOL 예정으로 마이그레이션 권장 |
haproxy | 고성능 로드밸런싱. 엔터프라이즈 환경에 적합 |
kong | API Gateway 통합, 플러그인 확장 지원 |
contour | Envoy 프록시 기반 CNCF 프로젝트 |
alb | AWS Application Load Balancer. AWS 환경 전용 |
여러 경로가 매칭될 수 있을 때는 paths 배열에서 먼저 정의된 항목이 우선합니다. /api를 /보다 먼저 두면 API 요청이 정적 페이지로 가지 않습니다.
ConfigMap
(Step 3에서 처음 등장)
| 옵션 | 필수/선택 | 기본값 | 설명 |
|---|---|---|---|
metadata.name | 필수 | - | ConfigMap 이름 |
metadata.namespace | 선택 | default | 배포 대상 네임스페이스 |
data | 필수 | - | key-value 쌍. 모든 값은 문자열 |
Deployment에서 참조하는 방법
envFrom: configMapRef: 모든 key를 환경 변수로 일괄 주입 (가장 간편)env[].valueFrom.configMapKeyRef: 특정 key만 골라서 다른 이름의 환경 변수로 주입volumes[].configMap: 파일로 마운트 (설정 파일 형태로 사용할 때)
민감 정보는 ConfigMap 대신 Secret을 사용하고, 사용 방식은 동일합니다 (secretRef, secretKeyRef).
PersistentVolumeClaim (PVC)
(Step 4에서 처음 등장)
| 옵션 | 필수/선택 | 기본값 | 설명 |
|---|---|---|---|
metadata.name | 필수 | - | PVC 이름 |
spec.accessModes | 필수 | - | 접근 모드 배열 |
spec.resources.requests.storage | 필수 | - | 요청 용량 (예: 10Gi) |
spec.storageClassName | 선택 | 클러스터 기본 | 스토리지 프로비저너 지정 |
스토리지 클래스 예시: nfs-client, local-path, longhorn, ceph-rbd, default 등 클러스터에 설치된 것 중 선택합니다.
접근 모드 (3종)
| 모드 | 설명 | 사용 시나리오 |
|---|---|---|
ReadWriteOnce | 단일 노드에서 읽기/쓰기 | 대부분의 DB와 일반 애플리케이션 (기본) |
ReadWriteMany | 다중 노드에서 동시 읽기/쓰기 | NFS 등 공유 스토리지 필요 |
ReadOnlyMany | 다중 노드에서 읽기 전용 | 정적 설정 파일 공유 |
Job
(Step 5에서 처음 등장)
| 옵션 | 필수/선택 | 기본값 | 설명 |
|---|---|---|---|
metadata.name | 필수 | - | Job 이름 |
spec.template.spec.containers[].image | 필수 | - | 실행할 이미지 |
spec.template.spec.containers[].command | 선택 | - | ENTRYPOINT 덮어쓰기 |
spec.template.spec.containers[].args | 선택 | - | command 인자 |
spec.template.spec.restartPolicy | 필수 | - | OnFailure 또는 Never |
spec.backoffLimit | 선택 | 6 | 실패 재시도 횟수 |
spec.activeDeadlineSeconds | 선택 | - | Job 전체 최대 실행 시간(초) |
환경 변수 주입 방식
| 방식 | 설명 |
|---|---|
env (개별) | 변수 하나씩 이름/값 지정. ConfigMap/Secret 일부만 참조 가능 |
envFrom: configMapRef | ConfigMap의 모든 key를 환경 변수로 일괄 주입 |
envFrom: secretRef | Secret의 모든 key를 환경 변수로 일괄 주입 |
Job은 DB 마이그레이션, 시드 데이터 삽입, 일회성 정리 스크립트 등 한 번 실행하고 끝나는 작업에 사용합니다. 주기적으로 실행되는 작업은 CronJob을 사용하세요.
다음에 할 일
가이드를 다 읽었다면 다음 순서로 진행하세요.
- 실제 배포 실행 → K8s 배포
- 트래픽 증가 시 자동 확장 → HPA 자동 확장
- HTTPS 활성화 → 도메인/SSL 설정
- 문제 발생 시 → 롤백
관련 가이드
- K8s 배포 - 생성된 YAML로 실제 배포 실행
- 롤백 - 문제 발생 시 이전 버전으로 복원
- 도메인/SSL 설정 - 커스텀 도메인 및 HTTPS 설정
- HPA 자동 확장 - 트래픽에 따른 Pod 자동 확장