Helm + Kustomize for ZackBlog

Kustomize is a configuration management tool for Kubernetes that allows users to customize application manifests without modifying the original YAML files, in this post I will explore Kustomize with overlays, bases, and transformers, then create simple kustomization.yaml files for different environments using Zackblog, then to practice using Kustomize’s built-in resources like configMapGenerator and secretGenerator.

Let's take Zackblog k8s deployment manifest as an example and convert it into a Kustomize setup. We will start by organizing the files and gradually exploring key Kustomize features.

Set Up Kustomize Folder Structure

The base directory contains the common configuration. The overlays directories are for environment-specific customizations (e.g., dev and prod).

  mkdir -p zackblog/base
  mkdir -p zackblog/overlays/dev
  mkdir -p zackblog/overlays/prod
  
Create the Base Kustomization

Move the original zackblog.yaml manifest to the base directory and split it into separate files for the deployment.yaml and service.yaml, then create a kustomization.yaml file in the base directory to manage these resources:

  # zackblog/base/deployment.yaml
  apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: zackweb
    labels:
      app: zackweb
  spec:
    replicas: 1
    selector:
      matchLabels:
        app: zackweb
    template:
      metadata:
        labels:
          app: zackweb
      spec:
        containers:
        - name: zackweb
          image: zackz001/gitops-jekyll:latest
          ports:
          - containerPort: 80

  # zackblog/base/service.yaml
  apiVersion: v1
  kind: Service
  metadata:
    name: zackweb-service
  spec:
    selector:
      app: zackweb
    ports:
    - protocol: TCP
      port: 80
      targetPort: 80
    type: LoadBalancer

  # zackblog/base/kustomization.yaml
  resources:
    - deployment.yaml
    - service.yaml
  
Create Environment Overlays

In Kustomize, we create environment-specific overlays by overriding or patching the base configuration.

Dev Overlay: In the dev directory, create a kustomization.yaml file to customize the base configuration for development, then create a patch file patch.yaml to modify the replicas for the dev environment.

  mkdir -p zackblog/overlays/dev/
  mkdir -p zackblog/overlays/prod

  # zackblog/overlays/dev/kustomization.yaml
  resources:
    - ../../base

  patchesStrategicMerge:
    - patch.yaml

  commonLabels:
    environment: dev
  
  # zackblog/overlays/dev/patch.yaml
  apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: zackweb
  spec:
    replicas: 2  # Increase replica count in dev
  

Similarly, for production, create another kustomization.yaml and patch.yaml in the prod directory and set replicas to 4:

  # zackblog/overlays/prod/kustomization.yaml
  resources:
    - ../../base

  patchesStrategicMerge:
    - patch.yaml

  commonLabels:
    environment: prod
  
  # zackblog/overlays/prod/patch.yaml
  apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: zackweb
  spec:
    replicas: 5  # Scale to 5 replicas in prod
  
The final Kustomize Folder tree

The final Kustomize Folder structure looks like this, then we go and apply both dev and prod deployments into 2 namespaces using Kustomize environment overlays.

  /zackblog# tree
  .
  ├── base
  │   ├── deployment.yaml
  │   ├── kustomization.yaml
  │   └── service.yaml
  └── overlays
      ├── dev
      │   ├── kustomization.yaml
      │   └── patch.yaml
      └── prod
          ├── kustomization.yaml
          └── patch.yaml

  kubectl create namespace zackblog-dev
  kubectl create namespace zackblog-prod
  namespace/zackblog-dev created
  namespace/zackblog-prod created

  kubectl apply -k ./zackblog/overlays/dev
  service/zackweb-service created
  deployment.apps/zackweb created

  kubectl apply -k ./zackblog/overlays/prod
  service/zackweb-service created
  deployment.apps/zackweb created

  root@asb:~/kustomizahelm/kustomize-z# kubectl get deployment zackweb -n zackblog-dev --show-labels
  NAME      READY   UP-TO-DATE   AVAILABLE   AGE     LABELS
  zackweb   2/2     2            2           3m20s   app=zackweb,environment=dev
  root@asb:~/kustomizahelm/kustomize-z# kubectl get deployment zackweb -n zackblog-prod --show-labels
  NAME      READY   UP-TO-DATE   AVAILABLE   AGE     LABELS
  zackweb   4/4     4            4           2m47s   app=zackweb,environment=prod
  
Generate ConfigMaps and Secrets dynamically without manually creating YAML files

Here we will add a ConfigMap by updating the Base kustomization.yaml, then update deployment.yaml to merge and use the ConfigMap:

  # zackblog/base/kustomization.yaml
  resources:
    - deployment.yaml
    - service.yaml

  configMapGenerator:
    - name: zackweb-config
      literals:
        - LOG_LEVEL=debug

  # zackblog/base/deployment.yaml
  spec:
    containers:
    - name: zackweb
      image: zackz001/gitops-jekyll:latest
      ports:
      - containerPort: 80
      envFrom:   # add configmap
        - configMapRef:
            name: zackweb-config

  # zackblog/overlays/dev/kustomization.yaml
  configMapGenerator:
    - name: zackweb-config
      behavior: merge  # Merge with the base ConfigMap
      literals:
        - LOG_LEVEL=debug  # Dev-specific log level

  # zackblog/overlays/prod/kustomization.yaml
  configMapGenerator:
    - name: zackweb-config
      behavior: merge  # Merge with the base ConfigMap
      literals:
        - LOG_LEVEL=info  # Prod-specific log level
  
  root@asb:~/kustomizahelm/kustomize-z# kubectl apply -k zackblog/base
  configmap/zackweb-config-47668c6k28 created
  service/zackweb-service created
  deployment.apps/zackweb created

  root@asb:~/kustomizahelm/kustomize-z# kubectl apply -k zackblog/overlays/dev
  configmap/zackweb-config-hf678c7m2b created
  service/zackweb-service unchanged
  deployment.apps/zackweb configured

  root@asb:~/kustomizahelm/kustomize-z# kubectl apply -k zackblog/overlays/prod
  configmap/zackweb-config-hf678c7m2b created
  service/zackweb-service unchanged
  deployment.apps/zackweb configured
  
Using Transformers for Advanced Customizations

Next, we will create a Transformer File to add Resource Limits for deployment in dev

  # zackblog/overlays/dev/resource-limits.yaml
  apiVersion: builtin
  kind: PatchTransformer
  metadata:
    name: add-resource-limits
  patch: |
    - op: add
      path: /spec/template/spec/containers/0/resources
      value:
        limits:
          cpu: "500m"
          memory: "256Mi"
        requests:
          cpu: "250m"
          memory: "128Mi"
  target:
    kind: Deployment
    name: zackweb

  # zackblog/overlays/dev/kustomization.yaml
  namespace: zackblog-dev

  transformers:
    - resource-limits.yaml
  
  root@asb:~/kustomizahelm/kustomize-z# kubectl apply -k zackblog/overlays/dev
  configmap/zackweb-config-hf678c7m2b unchanged
  service/zackweb-service unchanged
  deployment.apps/zackweb configured

  root@asb:~/kustomizahelm/kustomize-z# kubectl describe deployments.apps -n zackblog-dev

  Pod Template:
    Labels:  app=zackweb
            environment=dev
    Containers:
      zackweb:
        Image:      zackz001/gitops-jekyll:latest
        Port:       80/TCP
        Host Port:  0/TCP
        Limits:
          cpu:     500m
          memory:  256Mi
        Requests:
          cpu:     250m
          memory:  128Mi
        Environment Variables from:
          zackweb-config-hf678c7m2b  ConfigMap  Optional: false
  
Override container image tags for different environments.

Lastly, we can update image tags in Dev to use zackz001/gitops-jekyll:v222 by updating overlay kustomization.yaml

  # zackblog/overlays/dev/kustomization.yaml
  namespace: zackblog-dev

  images:
    - name: zackz001/gitops-jekyll
      newTag: dev

  root@asb:~/kustomizahelm/kustomize-z# kubectl describe deployments.apps -n zackblog-dev | grep Image
  Image:      zackz001/gitops-jekyll:v222
  
Summary about Kustomize

We had done the following practice using Kustomize:

  • Namespace Isolation: Use separate namespaces (zackblog-dev and zackblog-prod) to isolate environments.
  • Environment-Specific Customizations: Utilize overlays to manage environment-specific configurations, such as replica counts, resource limits, and image tags.
  • Maintainable Structure: Keep a clear base and overlay structure to manage configurations efficiently.
  • Leverage Kustomize Features: Explore generators, transformers, and image overrides to maximize Kustomize's capabilities.
  • Version Control: Keep Kustomize configurations under version control to track changes and collaborate effectively.

Next stage I want to explore:

  • Strategic Merge vs. JSON Patches: Understand the differences and use cases for each patch type.
  • Custom Transformers: Create custom transformers for complex modifications.
  • Integration with GitOps Tools: Integrate Kustomize with ArgoCD or Flux for automated, Git-driven deployments.
  • Managing Secrets Securely: Use tools like sealed-secrets or SOPS with Kustomize to manage sensitive information.
About Helm

Helm is another popular package manager for Kubernetes application deployment, not new to me as I had tried many charts previously with Kafka, and Redis helm charts installation, today I am going to explore how to build my helm chart for this zack blog, and deep dive into the chart development for advanced templating, together with helm release management and version control, finally integrate my chart with CI/CD for GitOps.

Common Helm command
  • helm list -A # list releases across all namespaces
  • helm pull bitnami/postgresql-ha --untar # untar the chart after pull online chart
  • helm repo add bitnami https://charts.bitnami.com/bitnami # add a repo
  • helm create zackblog-helm # create a new chart
  • helm install zackblog-helm ~/zackblog-helm -n NAMESPACE -f dev-values.yaml # define ns and override with a new value file
  • helm upgrade zackblog-helm ~/zackblog-helm --set image.repository= --set image.tag= # --set to upgrade chart with override a new value
  • helm lint ~/zackblog-helm # lint syntax
  • helm rollback zackblog-helm 2 # rollback to revision 2 of a release
  • helm uninstall zackblog-helm -n Production # uninstall a chart from a ns
Start with own chart

- create a new helm chart

  [root@freeipa-server ~]# helm create zackblog-helm
  Creating zackblog-helm

  # modify values.yaml
  [root@freeipa-server zackblog]# vim values.yaml

  replicaCount: 3

  image:
    repository: zackz001/gitops-jekyll
    pullPolicy: IfNotPresent
    # Overrides the image tag.
    tag: "latest"

  service:
    type: NodePort
    port: 80
  

- Lint chart syntax before install

  # lint syntax
  [root@freeipa-server ~]# helm lint zackblog-helm/
  ==> Linting zackblog-helm/
  [INFO] Chart.yaml: icon is recommended

  1 chart(s) linted, 0 chart(s) failed

  # install own chart

  [root@freeipa-server ~]# helm install zackblog-helm zackblog-helm
  NAME: zackblog-helm
  LAST DEPLOYED: Mon May 13 21:27:14 2024
  NAMESPACE: default
  STATUS: deployed
  REVISION: 1
  NOTES:
  1. Get the application URL by running these commands:
  export NODE_PORT=$(kubectl get --namespace default -o jsonpath="{.spec.ports[0].nodePort}" services zackblog-helm)
  export NODE_IP=$(kubectl get nodes --namespace default -o jsonpath="{.items[0].status.addresses[0].address}")
  echo http://$NODE_IP:$NODE_PORT
  

- Customize value.yaml by changing replica and image tag

  # modify value.yaml to scale down and change image to v138
  [root@freeipa-server ~]# vim zackblog-helm/values.yaml
  replicaCount: 1

  image:
    repository: zackz001/gitops-jekyll
    pullPolicy: IfNotPresent
    # Overrides the image tag.
    tag: "v139"
  

- Override values.yaml by -f and deploy the same chart to different environments

  # create a dev ns then deploy and override with dev-values.yaml
  [root@freeipa-server ~]# vim zackblog-helm/dev-values.yaml
  image:
    repository: zackz001/gitops-jekyll
    tag: v140
  replicaCount: 2
  service:
    type: NodePort
    port: 80

  [root@freeipa-server ~]# kubectl create ns dev
  namespace/dev created
  [root@freeipa-server ~]# helm install dev-zackblog-helm zackblog-helm -f zackblog-helm/dev-values.yaml -n dev
  NAME: dev-zackblog-helm
  LAST DEPLOYED: Mon May 13 21:36:39 2024
  NAMESPACE: dev
  STATUS: deployed
  REVISION: 1
  
Advanced templating to add PVC into chart
  # add templates/pvc.yaml
  [root@freeipa-server ~]# vim zackblog-helm/templates/pvc.yaml
  apiVersion: v1
  kind: PersistentVolumeClaim
  metadata:
    name: longhron-pvc
  spec:
    accessModes:
    - ReadWriteOnce
    storageClassName: longhorn
    resources:
      requests:
        storage: 1Gi

  # add pvc in values.yaml
  [root@freeipa-server ~]# vim zackblog-helm/values.yaml
  pvc:
    enabled: true
    templateFiles:
    - pvc.yaml

  # add persistentVolumeClaim in templates/deployment.yaml
  [root@freeipa-server ~]# vim zackblog-helm/templates/deployment.yaml
  ...
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: longhron-pvc
  

- Integrate chart deployment with ArgoCD

  # argoCD application manifest
  project: default
  source:
    repoURL: 'https://github.com/ZackZhouHB/zack-gitops-project.git'
    path: argo-helm-zackblog
    targetRevision: editing
    helm:
      valueFiles:
        - values.yaml
  destination:
    server: 'https://kubernetes.default.svc'
    namespace: helm
  syncPolicy:
    automated: {}
    syncOptions:
      - CreateNamespace=true
  
image tooltip here Conclusion

Finally, I had a chance to go over helm, it makes package management easier and more convenient, through charts, k8s deployment can be more flexible with values and templates that can be deployed and reusable into different environments, it provides versioning and rollbacks, also allow customization of the template. However using online charts can also be risky in a production environment with quality, dependency, and security risks.

Next stage for Helm advanced templating:

  • Helmfile for Multi-Release Management
  • Use Helm’s built-in test hooks to create automated tests for your deployments
  • Master dependencies and subcharts for modular Helm chart creation
  • Helm Chart private Repositories and Version Control
  • Helm + Kustomize Hybrid Approaches

Welcome to Zack's Blog

Join me for fun journey about ##AWS ##DevOps ##Kubenetes ##MLOps

  • Latest Posts