IMG-LOGO
공지사항 :

Kubernetes 애플리케이션 배포와 운영 실습

lmkfox - 2026-09-06 06:55:56 9 Views 0 Comment

Kubernetes 애플리케이션 배포와 운영 실습

FastAPI + PostgreSQL + Redis + Nginx를 Helm Chart로 패키징하고 Dev/Prod 환경 분리하기

지금까지 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를 사용하면서 환경에 따라 설정값만 변경하는 것이다.


1. 왜 Kubernetes에서 애플리케이션을 패키징해야 하는가?

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은 이 문제를 해결하기 위한 도구다.


2. 최종 Kubernetes 구조

이번 실습에서는 다음과 같은 구조를 만든다.

                         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 또는 외부 관리형 데이터베이스를 고려해야 한다.


3. 프로젝트 디렉터리 구성

먼저 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에서 전체 애플리케이션을 관리한다.


4. Chart.yaml 작성

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 애플리케이션 버전

중요한 점은 versionappVersion은 서로 다른 개념이라는 것이다.

예를 들어:

Chart version
1.0.0

Application version
2.3.1

처럼 서로 다를 수 있다.


5. 기본 values.yaml

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

여기에는 환경에 따라 달라질 수 있는 설정을 넣는다.


6. 개발 환경 설정

개발 환경에서는 상대적으로 작은 리소스를 사용한다.

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

개발 환경은 비용과 리소스를 최소화하는 것이 중요하다.


7. 운영 환경 설정

운영 환경에서는 안정성과 장애 대응을 고려해야 한다.

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의 핵심이다.


8. FastAPI Deployment

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 리소스가 달라진다.


9. FastAPI Service

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 주소로 사용한다.


10. PostgreSQL 구성

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 크기가 달라진다.


11. PostgreSQL Secret

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

12. Redis 구성

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에 연결할 수 있다.


13. Nginx 구성

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

14. ConfigMap으로 Nginx 설정 관리

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 설정을 변경할 수 있다.


15. Health Check

운영 환경에서 매우 중요한 부분이다.

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

두 가지는 목적이 다르다.

Liveness Probe

애플리케이션이 살아있는지를 확인한다.

문제가 발생하면 Kubernetes가 컨테이너를 재시작할 수 있다.

Readiness Probe

트래픽을 받을 준비가 되었는지 확인한다.

애플리케이션이 아직 초기화 중이라면 Service가 해당 Pod로 트래픽을 보내지 않도록 할 수 있다.


16. Helm Chart 설치

개발 환경에 설치한다.

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

17. 운영 환경 배포

운영 환경은 별도의 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개가 실행되는 구조다.


18. Dev와 Prod의 가장 중요한 차이

같은 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

이 방식의 장점은 애플리케이션 배포 방법은 동일하고 설정만 환경별로 달라진다는 것이다.


19. Helm Template 검증

실제 Kubernetes에 설치하기 전에 생성되는 YAML을 확인할 수 있다.

helm template myapp ./myapp \
  -f ./myapp/values-dev.yaml

운영 환경:

helm template myapp ./myapp \
  -f ./myapp/values-prod.yaml

이 기능은 매우 중요하다.

배포하기 전에 Helm Template 결과를 확인하면 잘못된 YAML이나 잘못된 values 설정을 미리 발견할 수 있다.


20. Helm Lint

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

21. 운영 환경 업데이트

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를 업데이트한다.


22. Rollout 확인

배포 상태 확인:

kubectl rollout status deployment/myapp-backend -n prod

Pod 확인:

kubectl get pods -n prod

Deployment 확인:

kubectl describe deployment myapp-backend -n prod

문제가 없다면 새로운 버전으로 Pod가 교체된다.


23. 장애가 발생하면 Rollback

새로운 버전에 문제가 발생했다고 가정하자.

Helm은 Release history를 관리한다.

helm history myapp -n prod

예:

REVISION
1
2
3

이전 버전으로 돌아간다.

helm rollback myapp 2 -n prod

그리고 확인한다.

helm status myapp -n prod

이것이 Helm을 사용하는 중요한 이유 중 하나다.


24. 개발 → 운영 배포 프로세스

실제 운영에서는 다음과 같은 흐름을 만들 수 있다.

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 운영 환경으로 발전하게 된다.


25. 운영 환경에서 반드시 고려해야 할 것

이번 실습은 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 서비스로 분리하는 것도 일반적인 선택이다.


26. 개발과 운영을 Namespace로 분리하는 이유

다음과 같이 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

27. 시스템 엔지니어가 반드시 이해해야 하는 부분

이번 실습에서 중요한 것은 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

이런 형태로 연결된다.


28. 전체 배포 명령어 정리

개발 환경:

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

29. 최종 정리

이번 실습에서는 단순한 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은 이 과정을 표준화하고 반복 가능하게 만들어 주는 중요한 도구다.


댓글