CI/CD, GitOps & Production Clusters
Building a CI/CD Pipeline for Kubernetes — GitHub Actions
Automating Docker builds, registry pushes, and deployment rollouts.
Introduction
Modern application development requires continuous deployment workflows. A standard Continuous Integration and Continuous Deployment (CI/CD) pipeline for Kubernetes handles building source code into container images, scanning for vulnerabilities, pushing to registries, and triggering updates in active clusters.
Using GitHub Actions, we declare workflows as YAML configurations, running pipelines on virtual machines triggered by repository events such as code commits or pull requests.
Why It Matters
Without automated CI/CD, developers must compile Docker images locally, push them using manual keys, and execute kubectl commands directly. This introduces version mismatch risk, exposes admin keys, and blocks fast recovery cycles.
Real-World Analogy
Think of a CI/CD pipeline like **an automated shipping port loader**. When a factory finishes manufacturing products (code commit), they place them on a conveyor belt (CI/CD). Robotic scanners test the items (linting/unit tests), box them in standardized crates (Docker build), tag them with shipping barcodes (image registry push), and automatically crane them onto the cargo ships (deploy to cluster). The crew does not carry crates or drive cranes manually.
How It Works
A standard Kubernetes CI/CD pipeline executed via GitHub Actions flows through these phases:
- Trigger: A developer pushes code or opens a Pull Request on the main branch.
- Lint & Test: The runner checks syntax, runs unit tests, and validates Dockerfiles.
- Build & Tag: GitHub Actions uses Docker Buildx to build the image, utilizing cache layers to reduce compile times, and tags it with the Git Commit SHA.
- Registry Push: The runner logs into GitHub Container Registry (GHCR) or Docker Hub using encrypted repository secrets and pushes the image.
- Deploy: The runner authenticates with the target Kubernetes cluster and updates the Deployment manifest image version, triggering a rolling update.
Practical Example
Here is a complete, production-grade GitHub Actions workflow file (.github/workflows/deploy.yml) that builds an image, pushes to GHCR, and updates a cluster deployment:
Quick Quiz
Q1: What is the primary benefit of using Docker Buildx cache-from/cache-to=type=gha in a pipeline?
A) It encrypts container layers before pushing to registries.
B) It utilizes GitHub Actions cache storage to preserve unchanged layer build histories, drastically reducing build times.
C) It automatically restarts the cluster pods on compile failures.
D) It runs unit tests concurrently inside the container namespace.
Answer: B — Standard runners start fresh. Using the GitHub Actions cache provider allows Buildx to download unchanged layers from previous run states, avoiding redundant compilation.
Q2: Why is mapping image tags to the short Git Commit SHA preferred over using the latest tag in production?
A) The latest tag is blocked by Kubernetes resource quotas.
B) The latest tag makes deployment files too large to parse.
C) Explicit SHA tags guarantee deterministic deployments, permit rollbacks to specific commits, and ensure Kubernetes triggers a rolling update.
D) Git SHA tags compress the container registry size.
Answer: C — If you reuse the latest tag, Kubernetes will not trigger a rolling update unless the ImagePullPolicy is set to Always, which increases load on registry connections. Unique tags ensure predictability.
Scenario-Based Challenge
Production Scenario:
Your deployment workflow uses kubectl set image in GitHub Actions. During a build push, the deployment succeeds in the CI log but the pod status crashes in the cluster with an ImagePullBackOff error. The pipeline continues reporting success because the deploy step finished. How do you adjust the pipeline to fail-fast when a container fails to start?
Add the kubectl rollout status command directly after setting the image, and configure a timeout. If the rollout fails to reach a ready state within the threshold (e.g. because of crash loops or image pull errors), the rollout command returns a non-zero exit code, immediately breaking the GitHub Action job and alerting the team:
Interview Questions
1. Conceptual: What are the main differences between Push-based and Pull-based deployments?
In a Push-based model (like traditional GitHub Actions), the pipeline runner holds credentials to connect to the cluster and pushes updates imperatively. In a Pull-based model (like GitOps/ArgoCD), an operator agent runs inside the cluster, monitors the Git repository, and pulls/applies configurations, keeping secrets isolated inside the cluster boundary.
2. Technical: How do you handle secrets securely inside a GitHub Actions workflow?
Store secrets in the GitHub repository configuration (Repository Secrets) and refer to them inside the workflow YAML using the secrets context: ${{ secrets.SECRET_NAME }}. GitHub automatically masks these secrets in logs, preventing accidental exposure of API keys or kubeconfigs.
Production Considerations
Avoid using personal access keys inside production pipelines. Instead, utilize OpenID Connect (OIDC) authentication or short-lived credentials. Configure repository permissions strictly, restricting push permissions on the main branch to pull request approvals only.