Docker & Kubernetes Complete Guide Part 10: Helm and CI/CD Pipeline
Kubernetes Package Manager and Automated Deployment
Introduction: Efficient Kubernetes Application Deployment
So far, we've learned various Kubernetes resources and configuration methods. However, in real production environments, you need to manage dozens or hundreds of YAML files and apply different configurations for each environment. Helm, a package manager, emerged to solve this complexity.
Also, in modern software development, CI/CD pipelines that automate the process from code changes to production deployment are essential. In this part, we'll explore how to use Helm and build GitOps-based CI/CD pipelines in detail.
1. What is Helm?
1.1 Helm Overview
Helm is the package manager for Kubernetes. Like apt or yum on Linux, or Homebrew on macOS, Helm makes it easy to install, upgrade, and manage Kubernetes applications.
Core Helm concepts:
- Chart: A collection of files that define a Kubernetes application
- Release: An instance of a Chart installed in the cluster
- Repository: A storage location for sharing and storing Charts
- Values: Values that customize a Chart's default settings
1.2 Why Use Helm?
| Problem | Helm Solution |
|---|---|
| Managing numerous YAML files | Package into a single Chart |
| Environment-specific configuration differences | Override with values.yaml |
| Deployment history management | Release version management and rollback |
| Complex dependencies | Chart dependency management |
| Lack of reusability | Templates and shareable Charts |
2. Helm Installation and Basic Usage
2.1 Installing Helm
# macOS
brew install helm
# Linux (script)
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# Windows (Chocolatey)
choco install kubernetes-helm
# Verify installation
helm version
2.2 Repository Management
# Add official Helm Chart repositories
helm repo add stable https://charts.helm.sh/stable
helm repo add bitnami https://charts.bitnami.com/bitnami
# List repositories
helm repo list
# Update repositories
helm repo update
# Search for Charts
helm search repo nginx
helm search repo mysql
# Search Hub
helm search hub prometheus
2.3 Basic Commands
# Install Chart
helm install my-release bitnami/nginx
# Install to specific namespace
helm install my-release bitnami/nginx -n my-namespace --create-namespace
# Install with values file
helm install my-release bitnami/nginx -f custom-values.yaml
# Specify values on command line
helm install my-release bitnami/nginx --set replicaCount=3
# List releases
helm list
helm list -A # All namespaces
# Check release status
helm status my-release
# Upgrade release
helm upgrade my-release bitnami/nginx --set replicaCount=5
# Rollback release
helm rollback my-release 1 # Rollback to revision 1
# View release history
helm history my-release
# Delete release
helm uninstall my-release
3. Understanding Chart Structure
3.1 Chart Directory Structure
my-chart/
├── Chart.yaml # Chart metadata
├── values.yaml # Default configuration values
├── charts/ # Dependency Charts
├── templates/ # Kubernetes manifest templates
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── configmap.yaml
│ ├── secret.yaml
│ ├── _helpers.tpl # Template helper functions
│ ├── NOTES.txt # Message displayed after installation
│ └── tests/ # Test files
│ └── test-connection.yaml
├── .helmignore # Files to exclude when packaging
└── README.md # Documentation
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 version
appVersion: "2.0.0" # Application version
# Dependencies definition
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 and metadata
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
# Default configuration values
# Application settings
replicaCount: 2
image:
repository: myregistry/my-app
tag: "latest"
pullPolicy: IfNotPresent
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
# Service settings
service:
type: ClusterIP
port: 80
targetPort: 8080
# Ingress settings
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
# Resource limits
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
# Autoscaling
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
# Environment variables
env:
- name: APP_ENV
value: production
- name: LOG_LEVEL
value: info
# Dependency enablement
mysql:
enabled: true
auth:
database: myapp
username: appuser
redis:
enabled: false
3.4 Writing Templates
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
4. values.yaml Customization
4.1 Environment-specific values Files
# Environment-specific values files structure
values/
├── values.yaml # Default values
├── values-dev.yaml # Development environment
├── values-staging.yaml # Staging environment
└── values-prod.yaml # Production environment
values-prod.yaml
# values-prod.yaml - Production environment settings
replicaCount: 5
image:
tag: "1.2.3" # Fixed specific version
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
# Combine multiple values files
helm install my-app ./my-chart \
-f values.yaml \
-f values-prod.yaml \
--set image.tag=1.2.4
4.2 Verifying Template Rendering
# Check rendered templates (without actual deployment)
helm template my-release ./my-chart -f values-prod.yaml
# Check specific template only
helm template my-release ./my-chart -s templates/deployment.yaml
# Debug mode
helm install my-release ./my-chart --dry-run --debug
5. Useful Helm Charts
5.1 Installing Prometheus Stack
# Add prometheus-community repository
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
# Install kube-prometheus-stack (Prometheus + Grafana + AlertManager)
helm install prometheus prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--create-namespace \
--set grafana.adminPassword=admin123
5.2 Installing Grafana Standalone
# Add Grafana repository
helm repo add grafana https://grafana.github.io/helm-charts
# Install Grafana
helm install grafana grafana/grafana \
--namespace monitoring \
--set persistence.enabled=true \
--set adminPassword='admin123'
5.3 Other Useful Charts
# Nginx Ingress Controller
helm install nginx-ingress ingress-nginx/ingress-nginx \
--namespace ingress-nginx --create-namespace
# cert-manager (TLS certificate automation)
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 Concept Review
6.1 What is CI/CD?
- CI (Continuous Integration): Frequently integrate code changes with automated builds and tests
- CD (Continuous Delivery): Always maintain a state ready to deploy to production
- CD (Continuous Deployment): All changes are automatically deployed to production
6.2 Traditional CI/CD vs GitOps
| Feature | Traditional CI/CD | GitOps |
|---|---|---|
| Deployment trigger | CI pipeline pushes | Pulls Git changes |
| Source of truth | CI server state | Git repository |
| Rollback | Requires redeployment | Git revert |
| Audit trail | CI logs | Git history |
| Security | CI needs cluster access | Operates only within cluster |
7. Introduction to GitOps
7.1 GitOps Principles
- Declarative configuration: Define all infrastructure and application state declaratively
- Version control: Use Git as the single source of truth
- Automatic synchronization: Approved changes are automatically applied to the cluster
- Continuous reconciliation: Continuously detect and correct differences between actual and desired state
7.2 ArgoCD
ArgoCD is the most widely used GitOps tool.
# Install ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Install ArgoCD CLI (macOS)
brew install argocd
# Get initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
# Access via port forwarding
kubectl port-forward svc/argocd-server -n argocd 8080:443
ArgoCD Application Definition
# 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 # Automatically clean deleted resources
selfHeal: true # Auto-recover manual changes
syncOptions:
- CreateNamespace=true
7.3 Flux CD
# Install Flux CLI
curl -s https://fluxcd.io/install.sh | sudo bash
# Bootstrap Flux (using GitHub)
flux bootstrap github \
--owner=myorg \
--repository=fleet-infra \
--branch=main \
--path=./clusters/production \
--personal
8. Automating K8s Deployment with GitHub Actions
8.1 GitHub Actions Workflow Basic Structure
# .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 and Build
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. Build and Push Docker Image
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 Deployment
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-style 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: Update config repository
- 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
# If using 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. Practical Pipeline Building Example
9.1 Complete Project Structure
my-project/
├── .github/
│ └── workflows/
│ ├── ci.yaml # CI pipeline
│ ├── cd-staging.yaml # Staging deployment
│ └── cd-production.yaml # Production deployment
├── src/ # Application source
├── tests/ # Test code
├── charts/
│ └── my-app/ # Helm Chart
│ ├── Chart.yaml
│ ├── values.yaml
│ ├── values-staging.yaml
│ ├── values-production.yaml
│ └── templates/
├── Dockerfile
└── package.json
10. Conclusion and Series Wrap-up
In this part, we explored Helm for streamlining Kubernetes application deployment and automated CI/CD pipelines:
- Helm: Kubernetes package manager that simplifies complex application deployment
- Chart structure: Reusable packages utilizing templates, values, and helper functions
- GitOps: Modern deployment approach using Git as the source of truth
- ArgoCD/Flux: Representative GitOps tools
- GitHub Actions: Building complete CI/CD pipelines
Docker & Kubernetes Complete Guide Series Summary
Throughout 10 parts, we've covered the core concepts and practical applications of Docker and Kubernetes:
- Part 1: Docker basics and container concepts
- Part 2: Docker images and Dockerfile
- Part 3: Multi-container management with Docker Compose
- Part 4: Kubernetes basics and architecture
- Part 5: Pod, Deployment, Service
- Part 6: Kubernetes Basics - Concepts and Architecture
- Part 7: Kubernetes Installation and Cluster Configuration
- Part 8: Pod, Deployment, Service Deep Dive
- Part 9: ConfigMap, Secret, Ingress
- Part 10: Helm and CI/CD Pipeline
We hope this series has given you comprehensive knowledge from container technology basics to production-level Kubernetes operations. Container and orchestration technologies continue to evolve, so keep following official documentation and the community for the latest developments.
Practice and experience are the best teachers. Apply what you've learned to your own projects!