Docker & Kubernetes 완전 정복 10편: Helm과 CI/CD 파이프라인
Docker & Kubernetes Complete Guide Part 10: Helm and CI/CD Pipeline
서론: 효율적인 Kubernetes 애플리케이션 배포
지금까지 Kubernetes의 다양한 리소스와 설정 방법을 배웠습니다. 하지만 실제 프로덕션 환경에서는 수십, 수백 개의 YAML 파일을 관리해야 하고, 환경별로 다른 설정을 적용해야 합니다. 이러한 복잡성을 해결하기 위해 Helm이라는 패키지 매니저가 등장했습니다.
또한 현대의 소프트웨어 개발에서는 코드 변경부터 프로덕션 배포까지의 과정을 자동화하는 CI/CD 파이프라인이 필수입니다. 이번 편에서는 Helm의 활용법과 GitOps 기반의 CI/CD 파이프라인 구축 방법을 상세히 알아보겠습니다.
1. Helm이란?
1.1 Helm 개요
Helm은 Kubernetes의 패키지 매니저입니다. Linux의 apt, yum이나 macOS의 Homebrew처럼, Helm은 Kubernetes 애플리케이션을 쉽게 설치, 업그레이드, 관리할 수 있게 해줍니다.
Helm의 핵심 개념:
- Chart: Kubernetes 애플리케이션을 정의하는 파일들의 묶음
- Release: 클러스터에 설치된 Chart의 인스턴스
- Repository: Chart를 저장하고 공유하는 저장소
- Values: Chart의 기본 설정을 커스터마이징하는 값들
1.2 Helm을 사용하는 이유
| 문제점 | Helm의 해결책 |
|---|---|
| 수많은 YAML 파일 관리 | 단일 Chart로 패키징 |
| 환경별 설정 차이 | values.yaml로 오버라이드 |
| 배포 이력 관리 | 릴리스 버전 관리 및 롤백 |
| 복잡한 의존성 | Chart 의존성 관리 |
| 재사용성 부족 | 템플릿과 공유 가능한 Chart |
2. Helm 설치와 기본 사용법
2.1 Helm 설치
# macOS
brew install helm
# Linux (스크립트)
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# Windows (Chocolatey)
choco install kubernetes-helm
# 설치 확인
helm version
2.2 Repository 관리
# 공식 Helm Chart 저장소 추가
helm repo add stable https://charts.helm.sh/stable
helm repo add bitnami https://charts.bitnami.com/bitnami
# 저장소 목록 확인
helm repo list
# 저장소 업데이트
helm repo update
# Chart 검색
helm search repo nginx
helm search repo mysql
# Hub에서 검색
helm search hub prometheus
2.3 기본 명령어
# Chart 설치
helm install my-release bitnami/nginx
# 특정 네임스페이스에 설치
helm install my-release bitnami/nginx -n my-namespace --create-namespace
# values 파일로 설치
helm install my-release bitnami/nginx -f custom-values.yaml
# 명령행에서 값 지정
helm install my-release bitnami/nginx --set replicaCount=3
# 릴리스 목록 확인
helm list
helm list -A # 모든 네임스페이스
# 릴리스 상태 확인
helm status my-release
# 릴리스 업그레이드
helm upgrade my-release bitnami/nginx --set replicaCount=5
# 릴리스 롤백
helm rollback my-release 1 # 리비전 1로 롤백
# 릴리스 이력 확인
helm history my-release
# 릴리스 삭제
helm uninstall my-release
3. Chart 구조 이해
3.1 Chart 디렉토리 구조
my-chart/
├── Chart.yaml # Chart 메타데이터
├── values.yaml # 기본 설정 값
├── charts/ # 의존성 Chart
├── templates/ # Kubernetes 매니페스트 템플릿
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── configmap.yaml
│ ├── secret.yaml
│ ├── _helpers.tpl # 템플릿 헬퍼 함수
│ ├── NOTES.txt # 설치 후 출력될 메시지
│ └── tests/ # 테스트 파일
│ └── test-connection.yaml
├── .helmignore # 패키징 시 제외할 파일
└── README.md # 문서
3.2 Chart.yaml
# Chart.yaml
apiVersion: v2
name: my-app
description: A Helm chart for my application
type: application
version: 1.0.0 # Chart 버전
appVersion: "2.0.0" # 애플리케이션 버전
# 의존성 정의
dependencies:
- name: mysql
version: "9.x.x"
repository: https://charts.bitnami.com/bitnami
condition: mysql.enabled
- name: redis
version: "17.x.x"
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled
# 키워드 및 메타데이터
keywords:
- web
- application
home: https://example.com
sources:
- https://github.com/example/my-app
maintainers:
- name: DevOps Team
email: devops@example.com
3.3 values.yaml
# values.yaml
# 기본 설정 값 정의
# 애플리케이션 설정
replicaCount: 2
image:
repository: myregistry/my-app
tag: "latest"
pullPolicy: IfNotPresent
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
# 서비스 설정
service:
type: ClusterIP
port: 80
targetPort: 8080
# Ingress 설정
ingress:
enabled: true
className: nginx
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
hosts:
- host: myapp.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: myapp-tls
hosts:
- myapp.example.com
# 리소스 제한
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
# 오토스케일링
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
# 환경 변수
env:
- name: APP_ENV
value: production
- name: LOG_LEVEL
value: info
# 의존성 활성화
mysql:
enabled: true
auth:
database: myapp
username: appuser
redis:
enabled: false
3.4 템플릿 작성
templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-app.fullname" . }}
labels:
{{- include "my-app.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "my-app.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "my-app.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.targetPort }}
protocol: TCP
{{- if .Values.env }}
env:
{{- toYaml .Values.env | nindent 12 }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 5
periodSeconds: 5
templates/_helpers.tpl
{{/*
애플리케이션 이름 생성
*/}}
{{- define "my-app.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
전체 이름 생성 (릴리스 이름 포함)
*/}}
{{- define "my-app.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
공통 라벨
*/}}
{{- define "my-app.labels" -}}
helm.sh/chart: {{ include "my-app.chart" . }}
{{ include "my-app.selectorLabels" . }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
셀렉터 라벨
*/}}
{{- define "my-app.selectorLabels" -}}
app.kubernetes.io/name: {{ include "my-app.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Chart 이름과 버전
*/}}
{{- define "my-app.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
4. values.yaml 커스터마이징
4.1 환경별 values 파일
# 환경별 values 파일 구성
values/
├── values.yaml # 기본 값
├── values-dev.yaml # 개발 환경
├── values-staging.yaml # 스테이징 환경
└── values-prod.yaml # 프로덕션 환경
values-prod.yaml
# values-prod.yaml - 프로덕션 환경 설정
replicaCount: 5
image:
tag: "1.2.3" # 특정 버전 고정
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 500m
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 5
maxReplicas: 20
targetCPUUtilizationPercentage: 70
ingress:
hosts:
- host: api.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: api-prod-tls
hosts:
- api.example.com
env:
- name: APP_ENV
value: production
- name: LOG_LEVEL
value: warn
- name: DB_HOST
valueFrom:
secretKeyRef:
name: db-credentials
key: host
# 여러 values 파일 조합
helm install my-app ./my-chart \
-f values.yaml \
-f values-prod.yaml \
--set image.tag=1.2.4
4.2 템플릿 렌더링 확인
# 렌더링된 템플릿 확인 (실제 배포 없이)
helm template my-release ./my-chart -f values-prod.yaml
# 특정 템플릿만 확인
helm template my-release ./my-chart -s templates/deployment.yaml
# 디버그 모드
helm install my-release ./my-chart --dry-run --debug
5. 유용한 Helm Charts
5.1 Prometheus 스택 설치
# prometheus-community 저장소 추가
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
# kube-prometheus-stack 설치 (Prometheus + Grafana + AlertManager)
helm install prometheus prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--create-namespace \
--set grafana.adminPassword=admin123
커스텀 values 파일
# prometheus-values.yaml
prometheus:
prometheusSpec:
retention: 15d
resources:
requests:
memory: 2Gi
cpu: 500m
limits:
memory: 4Gi
cpu: 1000m
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: standard
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 50Gi
grafana:
adminPassword: "secure-password"
persistence:
enabled: true
size: 10Gi
ingress:
enabled: true
hosts:
- grafana.example.com
tls:
- secretName: grafana-tls
hosts:
- grafana.example.com
alertmanager:
alertmanagerSpec:
storage:
volumeClaimTemplate:
spec:
storageClassName: standard
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
5.2 Grafana 단독 설치
# Grafana 저장소 추가
helm repo add grafana https://grafana.github.io/helm-charts
# Grafana 설치
helm install grafana grafana/grafana \
--namespace monitoring \
--set persistence.enabled=true \
--set adminPassword='admin123'
5.3 기타 유용한 Charts
# Nginx Ingress Controller
helm install nginx-ingress ingress-nginx/ingress-nginx \
--namespace ingress-nginx --create-namespace
# cert-manager (TLS 인증서 자동화)
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager --create-namespace \
--set installCRDs=true
# Redis
helm install redis bitnami/redis \
--set auth.password=mypassword
# PostgreSQL
helm install postgresql bitnami/postgresql \
--set auth.postgresPassword=mypassword
# Elasticsearch
helm install elasticsearch elastic/elasticsearch \
--set replicas=3
6. CI/CD 개념 복습
6.1 CI/CD란?
- CI (Continuous Integration): 코드 변경을 자주 통합하고, 자동화된 빌드와 테스트를 수행
- CD (Continuous Delivery): 프로덕션에 배포할 준비가 된 상태를 항상 유지
- CD (Continuous Deployment): 모든 변경이 자동으로 프로덕션에 배포
6.2 전통적인 CI/CD vs GitOps
| 특성 | 전통적인 CI/CD | GitOps |
|---|---|---|
| 배포 트리거 | CI 파이프라인이 push | Git 변경을 pull |
| 진실의 원천 | CI 서버 상태 | Git 저장소 |
| 롤백 | 재배포 필요 | Git revert |
| 감사 추적 | CI 로그 | Git 히스토리 |
| 보안 | CI에 클러스터 접근 권한 필요 | 클러스터 내부에서만 동작 |
7. GitOps 소개
7.1 GitOps 원칙
- 선언적 설정: 모든 인프라와 애플리케이션 상태를 선언적으로 정의
- 버전 관리: Git을 유일한 진실의 원천으로 사용
- 자동 동기화: 승인된 변경은 자동으로 클러스터에 적용
- 지속적 조정: 실제 상태와 원하는 상태의 차이를 지속적으로 감지하고 수정
7.2 ArgoCD
ArgoCD는 가장 널리 사용되는 GitOps 도구입니다.
# ArgoCD 설치
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# ArgoCD CLI 설치 (macOS)
brew install argocd
# 초기 admin 비밀번호 확인
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
# 포트 포워딩으로 접속
kubectl port-forward svc/argocd-server -n argocd 8080:443
ArgoCD Application 정의
# argocd-application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/my-app-config.git
targetRevision: main
path: kubernetes/overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true # 삭제된 리소스 자동 정리
selfHeal: true # 수동 변경 자동 복구
syncOptions:
- CreateNamespace=true
7.3 Flux CD
# Flux CLI 설치
curl -s https://fluxcd.io/install.sh | sudo bash
# Flux 부트스트랩 (GitHub 사용)
flux bootstrap github \
--owner=myorg \
--repository=fleet-infra \
--branch=main \
--path=./clusters/production \
--personal
Flux GitRepository와 Kustomization
# flux-source.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: my-app
namespace: flux-system
spec:
interval: 1m
url: https://github.com/myorg/my-app-config
ref:
branch: main
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: my-app
namespace: flux-system
spec:
interval: 10m
targetNamespace: production
sourceRef:
kind: GitRepository
name: my-app
path: ./kubernetes/overlays/production
prune: true
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: my-app
namespace: production
8. GitHub Actions로 K8s 배포 자동화
8.1 GitHub Actions 워크플로우 기본 구조
# .github/workflows/ci-cd.yaml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# 1. 테스트 및 빌드
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Run linting
run: npm run lint
# 2. Docker 이미지 빌드 및 푸시
build:
needs: test
runs-on: ubuntu-latest
outputs:
image-tag: ${{ steps.meta.outputs.tags }}
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix=
type=ref,event=branch
type=semver,pattern={{version}}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# 3. Kubernetes 배포
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: production
steps:
- uses: actions/checkout@v4
- name: Set up kubectl
uses: azure/setup-kubectl@v3
with:
version: 'latest'
- name: Configure kubeconfig
run: |
mkdir -p $HOME/.kube
echo "${{ secrets.KUBECONFIG }}" | base64 -d > $HOME/.kube/config
chmod 600 $HOME/.kube/config
- name: Set up Helm
uses: azure/setup-helm@v3
with:
version: 'latest'
- name: Deploy with Helm
run: |
helm upgrade --install my-app ./charts/my-app \
--namespace production \
--create-namespace \
--set image.tag=${{ github.sha }} \
--set image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} \
--wait \
--timeout 5m
- name: Verify deployment
run: |
kubectl rollout status deployment/my-app -n production
kubectl get pods -n production -l app.kubernetes.io/name=my-app
8.2 GitOps 방식의 GitHub Actions
# .github/workflows/gitops-ci.yaml
name: GitOps CI Pipeline
on:
push:
branches: [main]
paths:
- 'src/**'
- 'Dockerfile'
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
CONFIG_REPO: myorg/k8s-config
jobs:
build-and-update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
# GitOps: 설정 저장소 업데이트
- name: Checkout config repo
uses: actions/checkout@v4
with:
repository: ${{ env.CONFIG_REPO }}
token: ${{ secrets.CONFIG_REPO_TOKEN }}
path: config-repo
- name: Update image tag
run: |
cd config-repo
# kustomize를 사용하는 경우
cd kubernetes/overlays/production
kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
- name: Commit and push
run: |
cd config-repo
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git add .
git commit -m "Update image to ${{ github.sha }}"
git push
9. 실전 파이프라인 구축 예제
9.1 전체 프로젝트 구조
my-project/
├── .github/
│ └── workflows/
│ ├── ci.yaml # CI 파이프라인
│ ├── cd-staging.yaml # 스테이징 배포
│ └── cd-production.yaml # 프로덕션 배포
├── src/ # 애플리케이션 소스
├── tests/ # 테스트 코드
├── charts/
│ └── my-app/ # Helm Chart
│ ├── Chart.yaml
│ ├── values.yaml
│ ├── values-staging.yaml
│ ├── values-production.yaml
│ └── templates/
├── Dockerfile
└── package.json
9.2 완전한 CI/CD 파이프라인
.github/workflows/ci.yaml
name: Continuous Integration
on:
pull_request:
branches: [main, develop]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run lint
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: test
POSTGRES_DB: testdb
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm test
env:
DATABASE_URL: postgres://postgres:test@localhost:5432/testdb
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
severity: 'CRITICAL,HIGH'
build-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Build Docker image
uses: docker/build-push-action@v5
with:
context: .
push: false
tags: test-image:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
.github/workflows/cd-staging.yaml
name: Deploy to Staging
on:
push:
branches: [develop]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
deploy:
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:staging
- name: Configure kubectl
run: |
mkdir -p $HOME/.kube
echo "${{ secrets.STAGING_KUBECONFIG }}" | base64 -d > $HOME/.kube/config
- name: Deploy to Staging
run: |
helm upgrade --install my-app-staging ./charts/my-app \
--namespace staging \
--create-namespace \
-f ./charts/my-app/values-staging.yaml \
--set image.tag=${{ github.sha }} \
--wait
- name: Run smoke tests
run: |
STAGING_URL=$(kubectl get ingress my-app-staging -n staging -o jsonpath='{.spec.rules[0].host}')
curl -f https://$STAGING_URL/health || exit 1
- name: Notify Slack
if: always()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
fields: repo,message,commit,author
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
.github/workflows/cd-production.yaml
name: Deploy to Production
on:
release:
types: [published]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- name: Configure kubectl
run: |
mkdir -p $HOME/.kube
echo "${{ secrets.PRODUCTION_KUBECONFIG }}" | base64 -d > $HOME/.kube/config
- name: Deploy to Production
run: |
helm upgrade --install my-app ./charts/my-app \
--namespace production \
--create-namespace \
-f ./charts/my-app/values-production.yaml \
--set image.tag=${{ steps.version.outputs.VERSION }} \
--wait \
--timeout 10m
- name: Verify deployment
run: |
kubectl rollout status deployment/my-app -n production
kubectl get pods -n production
- name: Run integration tests
run: |
PROD_URL=$(kubectl get ingress my-app -n production -o jsonpath='{.spec.rules[0].host}')
npm run test:integration -- --url=https://$PROD_URL
- name: Create deployment record
run: |
echo "Deployed version ${{ steps.version.outputs.VERSION }} at $(date)" >> DEPLOYMENTS.md
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git add DEPLOYMENTS.md
git commit -m "Record deployment ${{ steps.version.outputs.VERSION }}" || true
git push || true
10. 결론 및 시리즈 마무리
이번 편에서는 Kubernetes 애플리케이션 배포를 효율화하는 Helm과 자동화된 CI/CD 파이프라인에 대해 알아보았습니다:
- Helm: Kubernetes 패키지 매니저로서 복잡한 애플리케이션 배포를 단순화
- Chart 구조: 템플릿, values, 헬퍼 함수를 활용한 재사용 가능한 패키지
- GitOps: Git을 진실의 원천으로 하는 현대적인 배포 방식
- ArgoCD/Flux: 대표적인 GitOps 도구
- GitHub Actions: 완전한 CI/CD 파이프라인 구축
Docker & Kubernetes 완전 정복 시리즈 요약
지금까지 10편에 걸쳐 Docker와 Kubernetes의 핵심 개념과 실전 활용법을 다루었습니다:
- 1편: Docker 기초와 컨테이너 개념
- 2편: Docker 이미지와 Dockerfile
- 3편: Docker Compose로 멀티 컨테이너 관리
- 4편: Kubernetes 기초와 아키텍처
- 5편: Pod, Deployment, Service
- 6편: Volume과 영속성
- 7편: 리소스 관리와 스케일링
- 8편: 네트워킹과 Service Mesh
- 9편: ConfigMap, Secret, Ingress
- 10편: Helm과 CI/CD 파이프라인
이 시리즈를 통해 컨테이너 기술의 기초부터 프로덕션 레벨의 Kubernetes 운영까지 전반적인 지식을 습득하셨기를 바랍니다. 컨테이너와 오케스트레이션 기술은 계속 발전하고 있으니, 공식 문서와 커뮤니티를 통해 최신 동향을 계속 파악하시기 바랍니다.
실습과 경험이 가장 좋은 선생님입니다. 배운 내용을 직접 프로젝트에 적용해 보세요!