Modern application delivery demands not just speed, but reliability. In the world of Kubernetes, achieving zero-downtime deployments is no longer a luxury; it is a baseline expectation for production-grade systems. By combining the robustness of GitLab CI for build and test processes with the declarative power of ArgoCD for deployment orchestration, you can create a GitOps-driven pipeline that ensures your services remain available during updates.
Why Combine GitLab CI and ArgoCD?
Traditional CI/CD tools often handle both building and deploying in a single, monolithic step. While effective, this approach can lead to complex state management issues. Separating concerns offers distinct advantages:
- GitLab CI: Excels at compilation, unit testing, image building, and pushing artifacts to a container registry.
- ArgoCD: Specializes in synchronizing the desired state defined in your Git repository with the live state of your Kubernetes cluster.
This separation allows you to decouple the "how to build" from the "how to deploy," providing clearer audit trails and simpler rollback mechanisms.
Setting Up the GitLab CI Pipeline
First, let's define the GitLab CI workflow. The primary goal here is to produce a container image and update the Kubernetes manifests in your Git repository. This is the core tenet of GitOps: the Git repository is the single source of truth.
stages:
- build
- push
- deploy
variables:
REGISTRY_IMAGE: $CI_REGISTRY_IMAGE
build:
stage: build
image: docker:latest
services:
- docker:dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $REGISTRY_IMAGE:$CI_COMMIT_SHA .
only:
- main
push:
stage: push
image: alpine:latest
script:
- apk add --no-cache git
- git config --global user.email "gitlab-ci-token@gl-sa-pipeline"
- git config --global user.name "GitLab CI"
- echo "$CI_REGISTRY_PASSWORD" > ~/.netrc
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker push $REGISTRY_IMAGE:$CI_COMMIT_SHA
update-manifests:
stage: deploy
image: alpine:latest
script:
- apk add --no-cache git
- git config --global user.email "gitlab-ci-token@gl-sa-pipeline"
- git config --global user.name "GitLab CI"
- cd k8s-manifests
- sed -i 's|image: .*$|image: '$REGISTRY_IMAGE':$CI_COMMIT_SHA|' deployment.yaml
- git add .
- git commit -m "Update image to $CI_COMMIT_SHA" || true
- git push origin main
only:
- main
after_script:
- echo "Manifests updated. ArgoCD will now detect the change."
Notice the update-manifests job. It does not deploy directly to Kubernetes. Instead, it updates the deployment.yaml file in the repository with the new image tag. This triggers ArgoCD to notice the change in the Git repository.
Configuring ArgoCD for GitOps
ArgoCD runs as a controller in your Kubernetes cluster. It continuously watches your Git repository. When it detects a change in the manifests (like the image tag update from GitLab CI), it automatically synchronizes the cluster with the new state.
To ensure zero downtime, your Kubernetes deployment strategy must be set to RollingUpdate. This is the default strategy, but it’s good to be explicit.
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: registry.gitlab.com/group/my-app:latest
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
Key Configuration for Zero Downtime:
maxUnavailable: 0: Ensures that no more than zero pods are unavailable during the update. This guarantees that at leastreplicas(3 in this case) pods are always running and ready to serve traffic.readinessProbe: ArgoCD and Kubernetes rely on this probe to determine if a pod is "Ready." A new pod will only be considered healthy and have its IP added to the Service's Endpoints after this probe passes. This prevents traffic from being routed to pods that are not fully initialized.
The Zero-Downtime Flow
Here is the step-by-step process when a developer merges code to the main branch:
- Trigger: GitLab CI pipeline starts.
- Build & Push: A new Docker image is built and pushed to the registry with a unique commit SHA tag.
- Manifest Update: The CI job updates the
deployment.yamlin the Git repository to reference the new image tag and commits it. - ArgoCD Sync: ArgoCD detects the Git commit change. It compares the desired state (new image) with the live state (old image).
- Rolling Update: ArgoCD applies the new manifest to Kubernetes.
- Kubernetes creates a new ReplicaSet with the new image.
- New pods start up and pass their readiness probes.
- Only after new pods are ready does Kubernetes begin terminating old pods.
- The LoadBalancer/Service continues to route traffic to the healthy pods throughout this transition.
- Completion: All new pods are running, old pods are terminated, and the application is fully updated with zero downtime.
Best Practices for Resilience
- Use Semantic Versioning for Images: While commit SHAs are precise, using semantic tags (e.g.,
v1.2.0) makes rollbacks easier in ArgoCD's UI. You can simply revert the Git commit to change the image tag back to the previous stable version. - Health Checks are Critical: Ensure your
livenessProbeandreadinessProbeare correctly configured. If a new pod fails its readiness check, ArgoCD will mark the sync as unhealthy, allowing you to intervene before the old pods are removed. - ArgoCD Sync Windows: For stricter control, configure ArgoCD sync windows to prevent automatic deployments during critical business hours.
- Monitoring: Integrate ArgoCD metrics with Prometheus/Grafana to monitor sync status, health, and deployment duration.
Conclusion
By leveraging GitLab CI for build automation and ArgoCD for declarative deployment, you create a robust, auditable, and resilient CI/CD pipeline. The key to zero downtime lies not in the tools themselves, but in the correct configuration of Kubernetes Rolling Update strategies and rigorous health probing. This GitOps approach ensures that your deployments are predictable, reversible, and always in sync with your source of truth: your Git repository.