지금까지 Kubernetes의 기본 개념부터 Pod, Deployment, Service, ConfigMap, Secret, PV/PVC, Helm까지 살펴봤다면 이제는 실제 서비스를 Kubernetes에 배포해 볼 차례다.
실제 운영 환경에서는 하나의 애플리케이션만 실행하는 경우가 많지 않다.
예를 들어 웹 서비스를 구축한다고 하면 다음과 같은 구조가 일반적이다.
사용자
|
v
[ Nginx ]
|
v
[ FastAPI ]
/ \
/ \
v v
[ PostgreSQL ] [ Redis ]
Nginx는 외부 요청을 받아 FastAPI로 전달하고, FastAPI는 PostgreSQL을 데이터베이스로 사용하며 Redis를 캐시나 세션 저장소로 사용할 수 있다.
이번 실습에서는 이 서비스를 Kubernetes에 배포하고 Helm을 이용하여 다음 환경을 구성한다.
Development
|
+-- FastAPI
+-- PostgreSQL
+-- Redis
+-- Nginx
Production
|
+-- FastAPI
+-- PostgreSQL
+-- Redis
+-- Nginx
핵심은 같은 Helm Chart를 사용하면서 환경에 따라 설정값만 변경하는 것이다.
Docker만 사용하는 환경에서는 다음과 같은 Docker Compose 파일을 작성할 수 있다.
services:
backend:
image: myapp/backend:1.0.0
postgres:
image: postgres:17
redis:
image: redis:7
nginx:
image: nginx:latest
간단한 환경에서는 충분하지만 운영 환경으로 갈수록 관리해야 할 내용이 많아진다.
예를 들어 다음과 같은 설정이 필요하다.
Deployment
Service
ConfigMap
Secret
PersistentVolumeClaim
Ingress
Resource
Health Check
Replica
Environment Variable
Storage
이 YAML 파일들을 환경마다 직접 관리하면 문제가 발생한다.
dev/backend.yaml
dev/postgres.yaml
dev/redis.yaml
prod/backend.yaml
prod/postgres.yaml
prod/redis.yaml
환경이 늘어날수록 YAML이 중복된다.
Helm은 이 문제를 해결하기 위한 도구다.
이번 실습에서는 다음과 같은 구조를 만든다.
Internet
|
v
[ Ingress ]
|
v
[ Nginx ]
|
v
[ FastAPI Service ]
|
v
[ FastAPI Pods ]
/ \
/ \
v v
PostgreSQL Redis
| |
v v
PVC Memory
Kubernetes 리소스로 표현하면 다음과 같다.
Namespace
│
├── Nginx Deployment
├── Nginx Service
│
├── FastAPI Deployment
├── FastAPI Service
│
├── PostgreSQL StatefulSet
├── PostgreSQL Service
├── PostgreSQL PVC
│
├── Redis Deployment
├── Redis Service
│
├── ConfigMap
└── Secret
여기서 중요한 점이 하나 있다.
FastAPI와 Nginx는 일반적인 Stateless 애플리케이션이므로 Deployment를 사용할 수 있다.
반면 PostgreSQL은 데이터를 저장하는 Stateful 서비스이므로 운영 환경에서는 StatefulSet 또는 외부 관리형 데이터베이스를 고려해야 한다.
먼저 Helm Chart를 생성한다.
helm create myapp
생성된 기본 파일을 정리하면 다음과 같이 구성할 수 있다.
myapp/
├── Chart.yaml
├── values.yaml
├── values-dev.yaml
├── values-prod.yaml
└── templates/
├── backend-deployment.yaml
├── backend-service.yaml
├── postgres-statefulset.yaml
├── postgres-service.yaml
├── redis-deployment.yaml
├── redis-service.yaml
├── nginx-deployment.yaml
├── nginx-service.yaml
├── configmap.yaml
├── secret.yaml
└── pvc.yaml
실제 프로젝트에서는 Chart를 서비스별로 분리할 수도 있다.
예를 들어 다음과 같이 구성할 수도 있다.
charts/
├── backend/
├── postgres/
├── redis/
└── nginx/
하지만 이번 실습에서는 하나의 Chart에서 전체 애플리케이션을 관리한다.
apiVersion: v2
name: myapp
description: FastAPI application stack
type: application
version: 1.0.0
appVersion: "1.0.0"
각 항목은 다음과 같다.
| 항목 | 의미 |
|---|---|
| apiVersion | Helm Chart API 버전 |
| name | Chart 이름 |
| description | 설명 |
| type | application/library |
| version | Chart 버전 |
| appVersion | 애플리케이션 버전 |
중요한 점은 version과 appVersion은 서로 다른 개념이라는 것이다.
예를 들어:
Chart version
1.0.0
Application version
2.3.1
처럼 서로 다를 수 있다.
Helm의 핵심은 values.yaml이다.
예를 들어 기본 설정을 다음과 같이 만든다.
backend:
replicaCount: 2
image:
repository: myregistry/myapp-backend
tag: "1.0.0"
service:
port: 8000
postgres:
image:
repository: postgres
tag: "17"
database: appdb
username: appuser
redis:
image:
repository: redis
tag: "7"
nginx:
image:
repository: nginx
tag: "1.27"
service:
port: 80
여기에는 환경에 따라 달라질 수 있는 설정을 넣는다.
개발 환경에서는 상대적으로 작은 리소스를 사용한다.
values-dev.yaml
backend:
replicaCount: 1
image:
repository: myregistry/myapp-backend
tag: "dev"
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
postgres:
storage: 5Gi
redis:
resources:
requests:
cpu: 50m
memory: 64Mi
nginx:
replicaCount: 1
개발 환경은 비용과 리소스를 최소화하는 것이 중요하다.
운영 환경에서는 안정성과 장애 대응을 고려해야 한다.
values-prod.yaml
backend:
replicaCount: 3
image:
repository: myregistry/myapp-backend
tag: "1.0.0"
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 2
memory: 2Gi
postgres:
storage: 100Gi
redis:
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 1
memory: 1Gi
nginx:
replicaCount: 2
개발과 운영의 차이는 values 파일에서 관리한다.
Helm Chart
|
+---------+---------+
| |
v v
values-dev.yaml values-prod.yaml
| |
v v
DEV PROD
이 구조가 Helm의 핵심이다.
FastAPI Deployment는 다음과 같이 작성할 수 있다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "myapp.fullname" . }}-backend
spec:
replicas: {{ .Values.backend.replicaCount }}
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: backend
image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}"
ports:
- containerPort: 8000
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: myapp-secret
key: database-url
- name: REDIS_URL
value: redis://myapp-redis:6379
여기서 중요한 부분은 다음이다.
replicas: {{ .Values.backend.replicaCount }}
개발 환경에서는:
replicaCount: 1
운영 환경에서는:
replicaCount: 3
가 된다.
같은 Deployment YAML을 사용하지만 환경에 따라 실제 Kubernetes 리소스가 달라진다.
FastAPI Pod에 직접 접근하지 않고 Service를 사용한다.
apiVersion: v1
kind: Service
metadata:
name: myapp-backend
spec:
selector:
app: backend
ports:
- port: 8000
targetPort: 8000
type: ClusterIP
이렇게 하면 Kubernetes 내부에서 다음 주소로 접근할 수 있다.
http://myapp-backend:8000
Nginx에서는 이 주소를 Backend 주소로 사용한다.
PostgreSQL은 데이터베이스이므로 데이터를 저장할 공간이 필요하다.
개발 환경:
postgres:
storage: 5Gi
운영 환경:
postgres:
storage: 100Gi
PVC 예제:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .Values.postgres.storage }}
이렇게 하면 values 파일에 따라 PVC 크기가 달라진다.
DB 비밀번호를 values.yaml에 직접 저장하면 안 된다.
잘못된 예:
postgres:
password: password123
Git 저장소에 올라가면 비밀번호가 그대로 노출될 수 있다.
Kubernetes Secret을 사용한다.
apiVersion: v1
kind: Secret
metadata:
name: myapp-secret
type: Opaque
stringData:
database-url: postgresql://appuser:password@postgres:5432/appdb
실제 운영에서는 Secret을 Git에 평문으로 저장하지 않는 것이 중요하다.
실무에서는 다음과 같은 방법을 고려한다.
Kubernetes Secret
External Secrets
Vault
Cloud Secret Manager
Sealed Secrets
Redis는 일반적으로 Stateless 애플리케이션과 비슷하게 Deployment로 구성할 수 있다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-redis
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7
ports:
- containerPort: 6379
Service:
apiVersion: v1
kind: Service
metadata:
name: myapp-redis
spec:
selector:
app: redis
ports:
- port: 6379
targetPort: 6379
FastAPI에서는:
redis://myapp-redis:6379
로 Redis에 연결할 수 있다.
Nginx는 외부 요청을 FastAPI로 전달한다.
예를 들어 nginx.conf를 다음과 같이 구성할 수 있다.
server {
listen 80;
location / {
proxy_pass http://myapp-backend:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
구조는 다음과 같다.
Client
|
| HTTP
v
Nginx :80
|
| HTTP
v
FastAPI :8000
Nginx 설정은 ConfigMap으로 관리할 수 있다.
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
data:
nginx.conf: |
server {
listen 80;
location / {
proxy_pass http://myapp-backend:8000;
}
}
그리고 Nginx Pod에서 mount한다.
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/conf.d/default.conf
subPath: nginx.conf
이렇게 하면 Docker Image를 다시 빌드하지 않고 Nginx 설정을 변경할 수 있다.
운영 환경에서 매우 중요한 부분이다.
FastAPI에 Health API를 만든다.
@app.get("/health")
def health():
return {
"status": "ok"
}
그리고 Kubernetes에서 다음과 같이 검사한다.
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 5
두 가지는 목적이 다르다.
애플리케이션이 살아있는지를 확인한다.
문제가 발생하면 Kubernetes가 컨테이너를 재시작할 수 있다.
트래픽을 받을 준비가 되었는지 확인한다.
애플리케이션이 아직 초기화 중이라면 Service가 해당 Pod로 트래픽을 보내지 않도록 할 수 있다.
개발 환경에 설치한다.
helm install myapp ./myapp \
-n dev \
--create-namespace \
-f myapp/values-dev.yaml
설치 확인:
helm list -n dev
Pod 확인:
kubectl get pods -n dev
Service 확인:
kubectl get svc -n dev
Deployment 확인:
kubectl get deployment -n dev
운영 환경은 별도의 Namespace를 사용한다.
helm install myapp ./myapp \
-n prod \
--create-namespace \
-f myapp/values-prod.yaml
확인:
kubectl get pods -n prod
결과는 대략 다음과 같은 형태가 된다.
NAME READY STATUS
myapp-backend-xxxx 1/1 Running
myapp-backend-yyyy 1/1 Running
myapp-backend-zzzz 1/1 Running
myapp-redis-xxxx 1/1 Running
myapp-postgres-0 1/1 Running
myapp-nginx-xxxx 1/1 Running
myapp-nginx-yyyy 1/1 Running
운영 환경에서는 FastAPI 3개, Nginx 2개가 실행되는 구조다.
같은 Chart를 사용하지만 설정은 다르다.
myapp Helm Chart
|
+----------+----------+
| |
v v
values-dev values-prod
| |
v v
DEV PROD
예를 들어 Backend:
| 항목 | DEV | PROD |
| Replica | 1 | 3 |
| CPU Request | 100m | 500m |
| Memory Request | 128Mi | 512Mi |
| DB Storage | 5Gi | 100Gi |
| Nginx Replica | 1 | 2 |
이 방식의 장점은 애플리케이션 배포 방법은 동일하고 설정만 환경별로 달라진다는 것이다.
실제 Kubernetes에 설치하기 전에 생성되는 YAML을 확인할 수 있다.
helm template myapp ./myapp \
-f ./myapp/values-dev.yaml
운영 환경:
helm template myapp ./myapp \
-f ./myapp/values-prod.yaml
이 기능은 매우 중요하다.
배포하기 전에 Helm Template 결과를 확인하면 잘못된 YAML이나 잘못된 values 설정을 미리 발견할 수 있다.
Chart 자체의 문제를 확인할 수도 있다.
helm lint ./myapp
정상이라면 다음과 비슷한 결과가 나온다.
1 chart(s) linted, 0 chart(s) failed
운영 배포 전에 다음 순서로 검사하는 것이 좋다.
helm lint
|
v
helm template
|
v
kubectl apply / helm upgrade
|
v
kubectl get pods
|
v
Health Check
FastAPI를 1.0.0에서 1.1.0으로 업데이트했다고 가정하자.
Docker Image:
myregistry/myapp-backend:1.1.0
values-prod.yaml:
backend:
image:
repository: myregistry/myapp-backend
tag: "1.1.0"
그리고:
helm upgrade myapp ./myapp \
-n prod \
-f ./myapp/values-prod.yaml
Kubernetes는 변경된 Deployment를 기준으로 Pod를 업데이트한다.
배포 상태 확인:
kubectl rollout status deployment/myapp-backend -n prod
Pod 확인:
kubectl get pods -n prod
Deployment 확인:
kubectl describe deployment myapp-backend -n prod
문제가 없다면 새로운 버전으로 Pod가 교체된다.
새로운 버전에 문제가 발생했다고 가정하자.
Helm은 Release history를 관리한다.
helm history myapp -n prod
예:
REVISION
1
2
3
이전 버전으로 돌아간다.
helm rollback myapp 2 -n prod
그리고 확인한다.
helm status myapp -n prod
이것이 Helm을 사용하는 중요한 이유 중 하나다.
실제 운영에서는 다음과 같은 흐름을 만들 수 있다.
Developer
|
v
Git Push
|
v
CI Build
|
v
Docker Image Build
|
v
Container Registry
|
v
Helm Chart
|
+----------+
| |
v v
DEV PROD
| |
v v
Test Approval
|
v
Deploy
예를 들어 GitHub Actions를 사용할 수 있다.
GitHub
|
v
GitHub Actions
|
+-- Docker Build
|
+-- Docker Push
|
+-- Helm Lint
|
+-- Helm Deploy DEV
|
+-- Test
|
+-- Approval
|
+-- Helm Deploy PROD
이 구조가 만들어지면 단순한 Kubernetes 사용을 넘어 CI/CD 기반의 Kubernetes 운영 환경으로 발전하게 된다.
이번 실습은 Kubernetes 구조를 이해하기 위한 것이므로 PostgreSQL을 Kubernetes 내부에 구성했지만 실제 운영 환경에서는 데이터베이스 운영 방식을 신중하게 결정해야 한다.
특히 다음 항목이 중요하다.
Database Backup
Database Replication
Storage
Persistent Volume
Monitoring
Logging
Security
Secret Management
Disaster Recovery
PostgreSQL은 단순히 Pod가 Running이라고 해서 안전한 것이 아니다.
예를 들어:
PostgreSQL Pod
|
v
Persistent Volume
|
v
Backup
|
v
Object Storage
와 같은 백업 구조까지 고려해야 한다.
대규모 운영 환경에서는 PostgreSQL을 Kubernetes 외부의 관리형 DB 서비스로 분리하는 것도 일반적인 선택이다.
다음과 같이 Namespace를 분리할 수 있다.
Kubernetes Cluster
│
├── namespace: dev
│ ├── backend
│ ├── postgres
│ ├── redis
│ └── nginx
│
└── namespace: prod
├── backend
├── postgres
├── redis
└── nginx
이렇게 하면 동일한 Kubernetes Cluster에서도 개발과 운영 리소스를 논리적으로 분리할 수 있다.
다만 Namespace 분리만으로 완전한 보안 경계가 만들어지는 것은 아니다.
운영 환경에서는 다음 기능을 함께 고려한다.
RBAC
NetworkPolicy
ResourceQuota
LimitRange
Secret Management
Pod Security
이번 실습에서 중요한 것은 YAML을 많이 작성하는 것이 아니다.
전체 구조를 이해하는 것이 중요하다.
Helm
|
+-- Chart
|
+-- values
|
+-- Template
|
v
Kubernetes YAML
|
v
Deployment
|
v
Pod
|
+------ Service
|
+------ ConfigMap
|
+------ Secret
|
+------ PVC
|
v
Application
그리고 실제 서비스는:
Internet
|
v
Ingress
|
v
Nginx
|
v
FastAPI
|
+------ PostgreSQL
|
+------ Redis
이런 형태로 연결된다.
개발 환경:
helm lint ./myapp
helm template myapp ./myapp \
-f ./myapp/values-dev.yaml
helm install myapp ./myapp \
-n dev \
--create-namespace \
-f ./myapp/values-dev.yaml
운영 환경:
helm lint ./myapp
helm template myapp ./myapp \
-f ./myapp/values-prod.yaml
helm install myapp ./myapp \
-n prod \
--create-namespace \
-f ./myapp/values-prod.yaml
업데이트:
helm upgrade myapp ./myapp \
-n prod \
-f ./myapp/values-prod.yaml
상태 확인:
helm status myapp -n prod
kubectl get pods -n prod
kubectl get svc -n prod
kubectl get pvc -n prod
Rollback:
helm history myapp -n prod
helm rollback myapp 2 -n prod
삭제:
helm uninstall myapp -n dev
이번 실습에서는 단순한 Kubernetes Pod 실행에서 한 단계 더 나아가 실제 서비스 형태를 Kubernetes에 배포했다.
전체 구조는 다음과 같다.
사용자
|
v
Ingress
|
v
Nginx
|
v
FastAPI
/ \
/ \
v v
PostgreSQL Redis
|
v
PVC
그리고 Helm을 이용하여:
Helm Chart
|
+----------+----------+
| |
v v
values-dev.yaml values-prod.yaml
| |
v v
DEV PROD
환경을 분리했다.
개발 환경은:
Replica 최소화
Storage 최소화
Resource 최소화
빠른 테스트
운영 환경은:
Replica 증가
Resource 증가
Persistent Storage
Health Check
Rollback
Monitoring
Backup
보안
을 중심으로 설계한다.
결국 Kubernetes 운영에서 중요한 것은 단순히 kubectl apply를 실행하는 것이 아니다.
애플리케이션을 어떻게 배포하고, 어떻게 확장하며, 장애가 발생했을 때 어떻게 복구하고, 개발 환경과 운영 환경을 어떻게 동일한 방식으로 관리할 것인가가 핵심이다.
Helm은 이 과정을 표준화하고 반복 가능하게 만들어 주는 중요한 도구다.