devops
Helm & GitOps
Helm chart structure, values, templating, upgrades, rollbacks, GitOps principles, and Argo CD
Helm Chart Structure
Helm is the package manager for Kubernetes. A chart is a collection of files that describe a related set of Kubernetes resources.
myapp/βββ Chart.yaml # chart metadataβββ values.yaml # default configuration valuesβββ templates/ # Kubernetes manifest templatesβ βββ deployment.yamlβ βββ service.yamlβ βββ ingress.yamlβ βββ configmap.yamlβ βββ secret.yamlβ βββ hpa.yamlβ βββ _helpers.tpl # reusable template snippetsβ βββ NOTES.txt # printed after installβββ charts/ # chart dependenciesChart.yaml
apiVersion: v2name: myappdescription: My application Helm charttype: applicationversion: 0.1.0 # chart version (semver)appVersion: "1.2.3" # application version being packageddependencies: - name: postgresql version: "~13.0" repository: "https://charts.bitnami.com/bitnami" condition: postgresql.enabledValues & Templating
values.yaml
# Default valuesreplicaCount: 2
image: repository: myrepo/myapp tag: "" # defaults to Chart.appVersion pullPolicy: IfNotPresent
service: type: ClusterIP port: 80
ingress: enabled: false className: "nginx" hosts: [] tls: []
resources: requests: memory: "256Mi" cpu: "100m" limits: memory: "512Mi" cpu: "500m"
autoscaling: enabled: false minReplicas: 2 maxReplicas: 10 targetCPUUtilizationPercentage: 70
postgresql: enabled: true auth: database: myappTemplate Syntax
apiVersion: apps/v1kind: Deploymentmetadata: name: {{ include "myapp.fullname" . }} # helper function labels: {{- include "myapp.labels" . | nindent 4 }} # include + indentspec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} {{- end }} selector: matchLabels: {{- include "myapp.selectorLabels" . | nindent 6 }} template: metadata: labels: {{- include "myapp.selectorLabels" . | nindent 8 }} spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - containerPort: {{ .Values.service.port }} resources: {{- toYaml .Values.resources | nindent 12 }} {{- if .Values.env }} env: {{- range $key, $value := .Values.env }} - name: {{ $key }} value: {{ $value | quote }} {{- end }} {{- end }}_helpers.tpl
{{/*Expand the name of the chart.*/}}{{- define "myapp.name" -}}{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}{{- end }}
{{/*Common labels*/}}{{- define "myapp.labels" -}}helm.sh/chart: {{ include "myapp.chart" . }}{{ include "myapp.selectorLabels" . }}app.kubernetes.io/managed-by: {{ .Release.Service }}{{- end }}
{{/*Selector labels*/}}{{- define "myapp.selectorLabels" -}}app.kubernetes.io/name: {{ include "myapp.name" . }}app.kubernetes.io/instance: {{ .Release.Name }}{{- end }}Helm CLI Usage
# Install a charthelm install myapp ./myapphelm install myapp ./myapp --values custom-values.yamlhelm install myapp ./myapp --set image.tag=1.2.4helm install myapp ./myapp --namespace production --create-namespace
# Install from public repohelm repo add bitnami https://charts.bitnami.com/bitnamihelm repo updatehelm install my-postgres bitnami/postgresql --version 13.2.0
# Render templates without installing (dry run)helm template myapp ./myapp --values prod.yamlhelm install myapp ./myapp --dry-run
# List releaseshelm listhelm list -n productionhelm list --all-namespaces
# Status of a releasehelm status myapp
# Get values of a releasehelm get values myapphelm get values myapp --all # includes defaultsHelm Upgrades & Rollbacks
# Upgrade a releasehelm upgrade myapp ./myapphelm upgrade myapp ./myapp --values prod.yaml --set image.tag=1.2.5
# Upgrade or install if not existshelm upgrade --install myapp ./myapp --values prod.yaml
# Rollback to previous versionhelm rollback myapp
# Rollback to specific revisionhelm history myapp # see all revisionshelm rollback myapp 3 # roll back to revision 3
# Uninstall (removes all K8s resources but keeps history)helm uninstall myapphelm uninstall myapp --keep-history # keep history for rollbackUpgrade best practices:
# Always diff before upgradinghelm diff upgrade myapp ./myapp --values prod.yaml# (requires helm-diff plugin: helm plugin install https://github.com/databus23/helm-diff)
# Check if upgrade would change anythinghelm upgrade myapp ./myapp --dry-run --values prod.yamlGitOps Principles
GitOps = operational model where Git is the single source of truth for your infrastructure and applications.
Core principles:
- Git as single source of truth β all desired state is in Git
- Declarative β describe what you want, not how to do it
- Automated sync β an operator continuously reconciles actual state with desired state
- Pull-based β the cluster pulls from Git, doesnβt get pushed to (more secure)
Developer β pushes to Git β [GitOps Operator watches repo] β applies changes to cluster (Argo CD / Flux)vs. traditional push-based CI/CD:
Developer β CI pipeline runs β pipeline pushes to cluster(pipeline needs credentials, cluster is externally accessible)Why GitOps is better for production:
- Full audit trail (every change is a Git commit)
- Easy rollback (revert the commit)
- Drift detection (operator alerts if cluster diverges from git)
- No pipeline credentials stored for cluster access
Argo CD
Argo CD is a declarative GitOps operator for Kubernetes. It watches Git repos and automatically syncs changes to the cluster.
Installation
# Install Argo CDkubectl create namespace argocdkubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Access UIkubectl port-forward svc/argocd-server -n argocd 8080:443
# Get initial admin passwordkubectl get secret argocd-initial-admin-secret -n argocd \ -o jsonpath='{.data.password}' | base64 -d
# Install argocd CLI# Then loginargocd login localhost:8080Application Manifest
apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata: name: myapp namespace: argocd finalizers: - resources-finalizer.argocd.argoproj.io # cascade deletespec: project: default
source: repoURL: https://github.com/myorg/myapp-config targetRevision: HEAD # or specific branch/tag/commit path: kubernetes/production # path within repo
# If using Helm helm: valueFiles: - values.yaml - values-production.yaml parameters: - name: image.tag value: "1.2.3"
destination: server: https://kubernetes.default.svc # in-cluster namespace: production
syncPolicy: automated: prune: true # delete resources removed from git selfHeal: true # revert manual changes to cluster syncOptions: - CreateNamespace=true - PrunePropagationPolicy=foreground# CLI operationsargocd app listargocd app get myappargocd app sync myapp # manual syncargocd app sync myapp --dry-run # preview syncargocd app history myappargocd app rollback myapp 3 # rollback to history IDargocd app diff myapp # diff between git and clusterSync, Drift Correction, Rollback via Git
# Sync β apply git state to clusterargocd app sync myapp
# Argo CD detects drift automatically when:# - Someone uses kubectl apply directly# - Cluster state changes (pod restart changes something)# With selfHeal=true, it automatically corrects drift
# Check if app is out of syncargocd app get myapp | grep -E "Status|Sync"# Sync Status: OutOfSync β someone changed the cluster directly
# Rollback (Argo CD)argocd app history myapp# ID DATE REVISION# 1 2024-01-01 abc123 (HEAD)# 2 2024-01-02 def456argocd app rollback myapp 2
# GitOps rollback (preferred) β revert the git commitgit revert HEADgit push# Argo CD automatically syncs the revertEnd-to-End CI/CD Pipeline with GitOps
Developer pushes code β βΌGitHub Actions runs: [Lint] β [Test] β [Build Docker image] β [Push to ECR with git-sha tag] β βΌ (on success, update image tag in config repo)Config Repo (separate repo for K8s manifests): values-production.yaml: image: tag: "abc1234" β updated by CI pipeline
β βΌArgo CD detects change in config repo β βΌArgo CD syncs new image to cluster β βΌDeployment rolls out β health checks pass β done# In GitHub Actions β update image tag in config repo- name: Update image tag in config repo run: | git clone https://x-access-token:${{ secrets.CONFIG_REPO_PAT }}@github.com/myorg/myapp-config cd myapp-config
# Update the tag in values file yq e '.image.tag = "${{ github.sha }}"' -i kubernetes/production/values.yaml
git config user.email "ci@myorg.com" git config user.name "CI Pipeline" git commit -am "ci: update myapp image to ${{ github.sha }}" git push