CI/CD with Kubernetes
Introduction
Welcome to our guide on Continuous Integration and Continuous Deployment (CI/CD) with Kubernetes! If you're looking to streamline your application deployment process, automate testing, and ensure reliable releases, you're in the right place.
In today's fast-paced development environment, manually deploying applications is time-consuming and error-prone. CI/CD practices help teams deliver code changes more frequently and reliably, while Kubernetes provides a powerful platform for container orchestration. When combined, they create a robust foundation for modern application deployment.
In this tutorial, we'll explore how to implement CI/CD pipelines that deploy applications to Kubernetes clusters, making your development workflow more efficient and reliable.
What is CI/CD?
Before diving into Kubernetes specifics, let's clarify what CI/CD means:
- Continuous Integration (CI): The practice of frequently merging code changes into a shared repository, followed by automated building and testing.
- Continuous Deployment (CD): The practice of automatically deploying all code changes to production after passing through the CI pipeline.
Together, these practices form a pipeline that takes code from development to production with minimal manual intervention.
What is Kubernetes?
Kubernetes (often abbreviated as K8s) is an open-source container orchestration platform that automates the deployment, scaling, and management of containerized applications. It provides:
- Scalability: Easily scale your applications up or down based on demand
- Self-healing: Automatically replaces and reschedules containers when nodes fail
- Service discovery: Containers can find and communicate with each other
- Load balancing: Distributes network traffic to ensure high availability
- Automated rollouts and rollbacks: Change application state gradually or revert to previous states
Why Combine CI/CD with Kubernetes?
Combining CI/CD practices with Kubernetes offers several benefits:
- Infrastructure as Code: Define your infrastructure declaratively
- Immutable deployments: Each deployment creates new containers rather than modifying existing ones
- Easy rollbacks: Quickly revert to previous versions if issues arise
- Consistent environments: Development, staging, and production environments remain consistent
- Resource efficiency: Optimize resource utilization in your cluster
Setting Up a Basic CI/CD Pipeline for Kubernetes
Let's build a simple CI/CD pipeline for deploying an application to Kubernetes. We'll use GitHub Actions as our CI/CD tool, but the concepts apply to other tools like Jenkins, GitLab CI, or CircleCI.
Prerequisites
Before we begin, you'll need:
- A GitHub repository with your application code
- A Kubernetes cluster (local like Minikube, or cloud-based)
- Docker installed for building container images
kubectl
configured to access your cluster
Step 1: Containerize Your Application
First, create a Dockerfile
in your project root:
FROM node:14-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
This Dockerfile builds a container image for a Node.js application.
Step 2: Create Kubernetes Manifests
Next, create a directory called kubernetes
and add a deployment manifest:
# kubernetes/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: ${DOCKER_USERNAME}/my-app:${VERSION}
ports:
- containerPort: 3000
resources:
limits:
memory: "128Mi"
cpu: "500m"
And a service manifest:
# kubernetes/service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-app-service
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 3000
type: LoadBalancer
Step 3: Set Up GitHub Actions Workflow
Create a workflow file in .github/workflows/ci-cd.yaml
:
name: CI/CD Pipeline
on:
push:
branches: [ main ]
env:
DOCKER_USERNAME: ${{ github.repository_owner }}
VERSION: ${{ github.sha }}
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ${{ env.DOCKER_USERNAME }}/my-app:${{ env.VERSION }}
- name: Set up kubectl
uses: azure/setup-kubectl@v1
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Update kubeconfig
run: aws eks update-kubeconfig --name my-cluster --region us-east-1
- name: Deploy to Kubernetes
run: |
# Replace placeholders in deployment.yaml
sed -i "s|\${DOCKER_USERNAME}|$DOCKER_USERNAME|g" kubernetes/deployment.yaml
sed -i "s|\${VERSION}|$VERSION|g" kubernetes/deployment.yaml
# Apply Kubernetes manifests
kubectl apply -f kubernetes/deployment.yaml
kubectl apply -f kubernetes/service.yaml
# Wait for deployment to complete
kubectl rollout status deployment/my-app
This workflow:
- Builds a Docker image from your application
- Pushes the image to Docker Hub
- Deploys the application to your Kubernetes cluster
Step 4: Add Required Secrets to GitHub
In your GitHub repository, go to Settings > Secrets and add:
DOCKER_USERNAME
: Your Docker Hub usernameDOCKER_PASSWORD
: Your Docker Hub password or tokenAWS_ACCESS_KEY_ID
: Your AWS access key (if using EKS)AWS_SECRET_ACCESS_KEY
: Your AWS secret key (if using EKS)
Advanced CI/CD Techniques for Kubernetes
Once you have a basic pipeline working, you might want to explore these advanced techniques:
Implementing Canary Deployments
Canary deployments involve routing a small percentage of traffic to a new version of your application to test it with real users before full deployment.
# kubernetes/canary-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-canary
spec:
replicas: 1
selector:
matchLabels:
app: my-app
version: canary
template:
metadata:
labels:
app: my-app
version: canary
spec:
containers:
- name: my-app
image: ${DOCKER_USERNAME}/my-app:${VERSION}
ports:
- containerPort: 3000
In your CI/CD pipeline, you could add:
- name: Deploy canary
run: |
kubectl apply -f kubernetes/canary-deployment.yaml
# Wait and monitor for issues
sleep 300
# If successful, update the main deployment
kubectl apply -f kubernetes/deployment.yaml
# Then remove the canary
kubectl delete -f kubernetes/canary-deployment.yaml
Using Helm for Package Management
Helm is a package manager for Kubernetes that simplifies deployment of complex applications.
First, create a Helm chart:
helm create my-app
This creates a directory structure with templates for your Kubernetes resources. You can then use Helm in your CI/CD pipeline:
- name: Install Helm
uses: azure/setup-helm@v1
- name: Deploy with Helm
run: |
# Update the image tag in values.yaml
sed -i "s|tag:.*|tag: $VERSION|g" my-app/values.yaml
# Deploy or upgrade the release
helm upgrade --install my-app ./my-app
Implementing GitOps with ArgoCD
GitOps is an approach where Git repositories serve as the source of truth for your infrastructure. ArgoCD is a popular tool for implementing GitOps with Kubernetes.
- Install ArgoCD in your cluster:
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
- Create an application manifest:
# argocd-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/yourusername/your-repo.git
targetRevision: HEAD
path: kubernetes
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
- Apply it to your cluster:
kubectl apply -f argocd-app.yaml
Now, whenever you push changes to your Kubernetes manifests in the Git repository, ArgoCD will automatically sync them to your cluster.
Best Practices for CI/CD with Kubernetes
To make the most of your CI/CD pipeline with Kubernetes, follow these best practices:
1. Use Namespaces to Separate Environments
Create separate namespaces for different environments:
kubectl create namespace development
kubectl create namespace staging
kubectl create namespace production
Then specify the namespace in your deployment commands:
kubectl apply -f kubernetes/deployment.yaml -n staging
2. Implement Environment-Specific Configurations
Use ConfigMaps and Secrets to manage environment-specific configurations:
# kubernetes/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: my-app-config
data:
API_URL: "https://api.example.com"
LOG_LEVEL: "info"
Reference these in your deployments:
env:
- name: API_URL
valueFrom:
configMapKeyRef:
name: my-app-config
key: API_URL
3. Implement Automated Testing in the Pipeline
Include automated testing in your CI/CD pipeline:
- name: Run tests
run: npm test
- name: Deploy to staging
if: success()
run: kubectl apply -f kubernetes/deployment.yaml -n staging
- name: Run integration tests
run: npm run test:integration
- name: Deploy to production
if: success()
run: kubectl apply -f kubernetes/deployment.yaml -n production
4. Add Health Checks to Your Deployments
Implement liveness and readiness probes:
containers:
- name: my-app
image: ${DOCKER_USERNAME}/my-app:${VERSION}
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
Common CI/CD Pipeline Tools for Kubernetes
Several tools can help you build CI/CD pipelines for Kubernetes:
- Jenkins X: A CI/CD solution designed specifically for Kubernetes
- GitLab CI/CD: Integrated CI/CD with Kubernetes support
- GitHub Actions: Workflow automation with Kubernetes support
- CircleCI: Cloud-based CI/CD with Kubernetes integration
- ArgoCD: GitOps continuous delivery for Kubernetes
- Flux: GitOps operator for Kubernetes
- Tekton: Kubernetes-native CI/CD framework
- Spinnaker: Multi-cloud continuous delivery platform
Example: Complete CI/CD Workflow with Testing
Let's look at a more complete example that includes testing:
And here's a GitHub Actions workflow implementing this pipeline:
name: Complete CI/CD Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
DOCKER_USERNAME: ${{ github.repository_owner }}
VERSION: ${{ github.sha }}
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
build-and-push:
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ${{ env.DOCKER_USERNAME }}/my-app:${{ env.VERSION }}
deploy-dev:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up kubectl
uses: azure/setup-kubectl@v1
- name: Configure kubeconfig
run: aws eks update-kubeconfig --name my-cluster --region us-east-1
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Deploy to dev environment
run: |
sed -i "s|\${DOCKER_USERNAME}|$DOCKER_USERNAME|g" kubernetes/deployment.yaml
sed -i "s|\${VERSION}|$VERSION|g" kubernetes/deployment.yaml
kubectl apply -f kubernetes/deployment.yaml -n development
- name: Run integration tests
run: |
# Wait for deployment to complete
kubectl rollout status deployment/my-app -n development
npm run test:integration
deploy-staging:
needs: deploy-dev
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up kubectl
uses: azure/setup-kubectl@v1
- name: Configure kubeconfig
run: aws eks update-kubeconfig --name my-cluster --region us-east-1
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Deploy to staging environment
run: |
sed -i "s|\${DOCKER_USERNAME}|$DOCKER_USERNAME|g" kubernetes/deployment.yaml
sed -i "s|\${VERSION}|$VERSION|g" kubernetes/deployment.yaml
kubectl apply -f kubernetes/deployment.yaml -n staging
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production # Requires manual approval
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up kubectl
uses: azure/setup-kubectl@v1
- name: Configure kubeconfig
run: aws eks update-kubeconfig --name my-cluster --region us-east-1
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Deploy to production environment
run: |
sed -i "s|\${DOCKER_USERNAME}|$DOCKER_USERNAME|g" kubernetes/deployment.yaml
sed -i "s|\${VERSION}|$VERSION|g" kubernetes/deployment.yaml
kubectl apply -f kubernetes/deployment.yaml -n production
Troubleshooting Common Issues
Issue: Deployment Fails with ImagePullBackOff
This usually means Kubernetes can't pull your Docker image.
Solution:
- Check that you've pushed the image to the registry
- Verify the image name and tag are correct
- If using a private registry, ensure you've set up the imagePullSecret:
spec:
containers:
- name: my-app
image: ${DOCKER_USERNAME}/my-app:${VERSION}
imagePullSecrets:
- name: regcred
To create the secret:
kubectl create secret docker-registry regcred \
--docker-server=https://index.docker.io/v1/ \
--docker-username=<your-username> \
--docker-password=<your-password>
Issue: Container Crashes After Deployment
Solution:
- Check container logs:
kubectl logs <pod-name>
- Add proper health checks to catch issues before deployment
- Implement proper error handling in your application
Issue: Pipeline Fails on Kubernetes Configuration
Solution:
- Validate your Kubernetes manifests:
kubectl apply --validate=true --dry-run=client -f kubernetes/deployment.yaml
- Use a linter for Kubernetes manifests such as
kubeval
Summary
In this tutorial, we've covered:
- The basics of CI/CD and Kubernetes
- Setting up a CI/CD pipeline for Kubernetes deployments
- Advanced techniques like canary deployments and GitOps
- Best practices for CI/CD with Kubernetes
- Common tools and troubleshooting tips
CI/CD with Kubernetes is a powerful combination that can significantly improve your development workflow. By automating the building, testing, and deployment processes, you can deliver changes more quickly and with greater confidence.
Additional Resources
To continue your learning journey:
-
Official Documentation:
-
Books:
- "Kubernetes: Up and Running" by Brendan Burns, Joe Beda, and Kelsey Hightower
- "Continuous Delivery with Kubernetes" by Mauricio Salatino
-
Online Courses:
- Kubernetes Certified Administrator (CKA) course
- CI/CD with Kubernetes on Udemy or Pluralsight
Practice Exercises
- Set up a basic CI/CD pipeline for a simple application using GitHub Actions and deploy it to Minikube.
- Implement a canary deployment strategy for your application.
- Create a multi-environment pipeline with development, staging, and production environments.
- Implement automatic rollbacks if deployment metrics indicate issues.
- Set up ArgoCD or Flux to implement GitOps for your Kubernetes deployments.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)