본문으로 건너뛰기

Kubernetes 배포 — Go 앱 운영 자동화

Kubernetes(K8s)는 컨테이너 오케스트레이션 플랫폼입니다. Go 앱을 K8s에 배포하고 운영하는 핵심 패턴을 알아봅니다.


기본 배포 매니페스트

# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
namespace: production
labels:
app: user-service
version: v1.0.0
spec:
replicas: 3
selector:
matchLabels:
app: user-service
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 업데이트 중 최대 추가 파드
maxUnavailable: 0 # 업데이트 중 최소 가용 유지
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: myregistry/user-service:1.0.0
ports:
- containerPort: 8080
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: user-service-secrets
key: database-url
- name: APP_ENV
value: production

# 리소스 제한 (필수)
resources:
requests:
cpu: "100m" # 0.1 CPU
memory: "64Mi"
limits:
cpu: "500m" # 0.5 CPU
memory: "128Mi"

# 헬스 체크
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 3

readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3

# 그레이스풀 셧다운
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"]

terminationGracePeriodSeconds: 60

# 파드 분산 배치 (같은 노드에 몰리지 않도록)
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: user-service

서비스 & 인그레스

# k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
name: user-service
namespace: production
spec:
selector:
app: user-service
ports:
- port: 80
targetPort: 8080
protocol: TCP
type: ClusterIP # 클러스터 내부 접근
# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: user-service
namespace: production
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- api.example.com
secretName: api-tls
rules:
- host: api.example.com
http:
paths:
- path: /users
pathType: Prefix
backend:
service:
name: user-service
port:
number: 80

설정 관리

# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: user-service-config
namespace: production
data:
APP_ENV: "production"
LOG_LEVEL: "info"
MAX_CONNECTIONS: "100"

---
# k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: user-service-secrets
namespace: production
type: Opaque
stringData:
database-url: "postgres://user:password@postgres:5432/mydb"
redis-url: "redis://:password@redis:6379"
jwt-secret: "supersecretkey"
# deployment.yaml에서 ConfigMap 사용
spec:
containers:
- name: user-service
envFrom:
- configMapRef:
name: user-service-config
- secretRef:
name: user-service-secrets

HPA — 자동 수평 스케일링

# k8s/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: user-service
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: user-service
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleUp:
stabilizationWindowSeconds: 30 # 급격한 스케일업 방지
scaleDown:
stabilizationWindowSeconds: 300 # 급격한 스케일다운 방지

데이터베이스 마이그레이션 Job

# k8s/migration-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: user-service-migration-v1-0-0
namespace: production
spec:
ttlSecondsAfterFinished: 300 # 완료 후 5분 뒤 삭제
template:
spec:
restartPolicy: Never
containers:
- name: migration
image: myregistry/user-service:1.0.0
command: ["/migrate"]
args: ["up"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: user-service-secrets
key: database-url
backoffLimit: 3

Go 앱의 K8s 시그널 처리

// cmd/server/main.go
package main

import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)

func main() {
server := &http.Server{
Addr: ":8080",
Handler: setupRouter(),
}

// 서버 시작
go func() {
log.Println("서버 시작: :8080")
if err := server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("서버 오류: %v", err)
}
}()

// K8s 시그널 처리
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT)

sig := <-quit
log.Printf("시그널 수신: %v — 그레이스풀 셧다운 시작", sig)

// K8s가 iptables 업데이트할 시간 (preStop과 함께)
time.Sleep(5 * time.Second)

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

if err := server.Shutdown(ctx); err != nil {
log.Printf("셧다운 오류: %v", err)
}

log.Println("서버 종료 완료")
}

GitHub Actions CI/CD

# .github/workflows/deploy.yml
name: Build and Deploy

on:
push:
branches: [main]

env:
IMAGE_NAME: myregistry/user-service
K8S_DEPLOYMENT: user-service
K8S_NAMESPACE: production

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- run: go test -race ./...

build-push:
needs: test
runs-on: ubuntu-latest
outputs:
image-tag: ${{ steps.meta.outputs.version }}
steps:
- uses: actions/checkout@v4

- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.IMAGE_NAME }}
tags: type=sha,prefix=

- name: Build and push
uses: docker/build-push-action@v5
with:
push: true
tags: ${{ steps.meta.outputs.tags }}
build-args: |
VERSION=${{ github.sha }}

deploy:
needs: build-push
runs-on: ubuntu-latest
steps:
- name: Deploy to K8s
run: |
kubectl set image deployment/${{ env.K8S_DEPLOYMENT }} \
${{ env.K8S_DEPLOYMENT }}=${{ env.IMAGE_NAME }}:${{ needs.build-push.outputs.image-tag }} \
-n ${{ env.K8S_NAMESPACE }}

kubectl rollout status deployment/${{ env.K8S_DEPLOYMENT }} \
-n ${{ env.K8S_NAMESPACE }} \
--timeout=5m

kubectl 주요 명령어

# 배포 상태 확인
kubectl get pods -n production -l app=user-service
kubectl get deployment user-service -n production

# 롤아웃 상태
kubectl rollout status deployment/user-service -n production
kubectl rollout history deployment/user-service -n production

# 이전 버전으로 롤백
kubectl rollout undo deployment/user-service -n production

# 파드 로그
kubectl logs -f deployment/user-service -n production
kubectl logs -f user-service-7d9fb96c4-xk2p5 -n production --previous

# 파드 내부 접속
kubectl exec -it user-service-7d9fb96c4-xk2p5 -n production -- /bin/sh

# 수동 스케일
kubectl scale deployment user-service --replicas=5 -n production

핵심 정리

리소스역할
Deployment파드 관리, 롤링 업데이트
Service파드 로드밸런싱, DNS 이름
Ingress외부 HTTP/HTTPS 라우팅
ConfigMap비민감 설정 데이터
Secret민감 데이터 (암호화 저장)
HPACPU/메모리 기반 자동 스케일링
Job일회성 작업 (마이그레이션)
  • Liveness Probe 실패 → 파드 재시작
  • Readiness Probe 실패 → 로드밸런서에서 제외 (재시작 없음)
  • resources.requestslimits 설정은 필수 (미설정 시 노드 자원 고갈 위험)