Set up GitLab CI/CD pipelines with Kubernetes runners for automated application deployments. Configure RBAC, implement rolling updates, and establish production-grade deployment strategies.
Prerequisites
- Kubernetes cluster with admin access
- GitLab instance or GitLab.com account
- Docker registry access
- kubectl and helm installed
What this solves
GitLab CI/CD integration with Kubernetes automates your application deployments, eliminating manual deployment steps and reducing human error. This setup enables automated testing, building, and deployment of applications directly to Kubernetes clusters using GitLab runners with proper RBAC controls and deployment strategies.
Step-by-step configuration
Install and configure GitLab Runner
Install GitLab Runner on your system to handle CI/CD jobs with Kubernetes executor support.
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
sudo apt-get install gitlab-runner
Install kubectl and helm
Install Kubernetes command-line tools needed for cluster management and application deployment.
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt update && sudo apt install helm
Create GitLab service account and RBAC
Set up proper Kubernetes RBAC permissions for GitLab Runner to deploy applications securely.
apiVersion: v1
kind: ServiceAccount
metadata:
name: gitlab-runner
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: gitlab-runner
rules:
- apiGroups: [""]
resources: ["pods", "services", "configmaps", "secrets"]
verbs: ["get", "list", "create", "update", "delete"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "create", "update", "delete"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: gitlab-runner
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: gitlab-runner
subjects:
- kind: ServiceAccount
name: gitlab-runner
namespace: default
kubectl apply -f /home/gitlab-runner/gitlab-service-account.yaml
Get service account token
Extract the service account token needed for GitLab Runner authentication with the Kubernetes cluster.
kubectl create secret generic gitlab-runner-token --from-literal=token=$(kubectl create token gitlab-runner --duration=8760h)
kubectl get secret gitlab-runner-token -o jsonpath='{.data.token}' | base64 -d > /tmp/gitlab-token
Register GitLab Runner with Kubernetes executor
Register the runner with your GitLab instance using Kubernetes executor for containerized builds and deployments.
sudo gitlab-runner register \
--url "https://gitlab.example.com/" \
--registration-token "YOUR_REGISTRATION_TOKEN" \
--executor "kubernetes" \
--description "kubernetes-runner" \
--kubernetes-host "https://YOUR_KUBERNETES_API_SERVER:6443" \
--kubernetes-ca-file "/etc/kubernetes/pki/ca.crt" \
--kubernetes-token-file "/tmp/gitlab-token" \
--kubernetes-namespace "default" \
--kubernetes-service-account "gitlab-runner" \
--kubernetes-image "ubuntu:22.04" \
--kubernetes-cpu-request "100m" \
--kubernetes-memory-request "128Mi" \
--kubernetes-cpu-limit "1000m" \
--kubernetes-memory-limit "1Gi"
Configure GitLab Runner Kubernetes settings
Fine-tune the runner configuration for optimal performance and security in Kubernetes environments.
concurrent = 4
check_interval = 0
[session_server]
session_timeout = 1800
[[runners]]
name = "kubernetes-runner"
url = "https://gitlab.example.com/"
token = "YOUR_RUNNER_TOKEN"
executor = "kubernetes"
[runners.kubernetes]
host = "https://YOUR_KUBERNETES_API_SERVER:6443"
ca_file = "/etc/kubernetes/pki/ca.crt"
token_file = "/tmp/gitlab-token"
namespace = "default"
service_account = "gitlab-runner"
image = "ubuntu:22.04"
cpu_request = "100m"
cpu_limit = "1000m"
memory_request = "128Mi"
memory_limit = "1Gi"
pull_policy = "if-not-present"
[runners.kubernetes.node_selector]
kubernetes.io/os = "linux"
[runners.kubernetes.pod_security_context]
run_as_non_root = true
run_as_user = 1000
fs_group = 2000
Create deployment namespace and resources
Set up a dedicated namespace for your application deployments with proper resource quotas.
apiVersion: v1
kind: Namespace
metadata:
name: myapp-production
labels:
environment: production
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: app-quota
namespace: myapp-production
spec:
hard:
requests.cpu: "4"
requests.memory: 8Gi
limits.cpu: "8"
limits.memory: 16Gi
pods: "10"
services: "5"
---
apiVersion: v1
kind: LimitRange
metadata:
name: app-limits
namespace: myapp-production
spec:
limits:
- default:
cpu: 500m
memory: 512Mi
defaultRequest:
cpu: 100m
memory: 128Mi
type: Container
kubectl apply -f /home/gitlab-runner/app-namespace.yaml
Create application deployment template
Set up a Kubernetes deployment template for your application with rolling update strategy.
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: myapp-production
labels:
app: myapp
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
version: "__VERSION__"
spec:
serviceAccountName: gitlab-runner
containers:
- name: myapp
image: "__IMAGE__"
ports:
- containerPort: 8080
env:
- name: ENV
value: "production"
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: myapp-service
namespace: myapp-production
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 8080
type: ClusterIP
Configure GitLab CI/CD pipeline
Create a comprehensive CI/CD pipeline that builds, tests, and deploys your application to Kubernetes.
stages:
- build
- test
- deploy
variables:
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: "/certs"
KUBERNETES_NAMESPACE: myapp-production
APP_NAME: myapp
build:
stage: build
image: docker:24.0.5
services:
- docker:24.0.5-dind
before_script:
- echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
only:
- main
- develop
test:
stage: test
image: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
script:
- echo "Running application tests"
- npm test
only:
- main
- develop
deploy_staging:
stage: deploy
image: alpine/k8s:1.28.2
before_script:
- kubectl config set-cluster k8s --server="$KUBE_URL" --certificate-authority="$KUBE_CA_PEM_FILE"
- kubectl config set-credentials gitlab --token="$KUBE_TOKEN"
- kubectl config set-context default --cluster=k8s --user=gitlab
- kubectl config use-context default
script:
- sed -i "s|__IMAGE__|$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA|g" k8s/deployment.yaml
- sed -i "s|__VERSION__|$CI_COMMIT_SHORT_SHA|g" k8s/deployment.yaml
- kubectl apply -f k8s/deployment.yaml
- kubectl rollout status deployment/$APP_NAME -n $KUBERNETES_NAMESPACE --timeout=300s
- kubectl get pods -n $KUBERNETES_NAMESPACE -l app=$APP_NAME
environment:
name: staging
url: https://staging.example.com
only:
- develop
deploy_production:
stage: deploy
image: alpine/k8s:1.28.2
before_script:
- kubectl config set-cluster k8s --server="$KUBE_URL" --certificate-authority="$KUBE_CA_PEM_FILE"
- kubectl config set-credentials gitlab --token="$KUBE_TOKEN"
- kubectl config set-context default --cluster=k8s --user=gitlab
- kubectl config use-context default
script:
- sed -i "s|__IMAGE__|$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA|g" k8s/deployment.yaml
- sed -i "s|__VERSION__|$CI_COMMIT_SHORT_SHA|g" k8s/deployment.yaml
- kubectl apply -f k8s/deployment.yaml
- kubectl rollout status deployment/$APP_NAME -n $KUBERNETES_NAMESPACE --timeout=300s
- kubectl get pods -n $KUBERNETES_NAMESPACE -l app=$APP_NAME
environment:
name: production
url: https://example.com
when: manual
only:
- main
Set up GitLab CI/CD variables
Configure environment variables in GitLab for secure cluster access and deployment configuration.
kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.server}'
kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.certificate-authority-data}' | base64 -d > ca.crt
cat /tmp/gitlab-token
Configure advanced deployment strategies
Implement blue-green deployment strategy for zero-downtime deployments with automatic rollback capabilities.
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: myapp-rollout
namespace: myapp-production
spec:
replicas: 5
strategy:
blueGreen:
activeService: myapp-active
previewService: myapp-preview
autoPromotionEnabled: false
scaleDownDelaySeconds: 30
prePromotionAnalysis:
templates:
- templateName: success-rate
args:
- name: service-name
value: myapp-preview
postPromotionAnalysis:
templates:
- templateName: success-rate
args:
- name: service-name
value: myapp-active
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: "__IMAGE__"
ports:
- containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: myapp-active
namespace: myapp-production
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: myapp-preview
namespace: myapp-production
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 8080
Install ArgoCD for advanced GitOps
Deploy ArgoCD for GitOps-based deployment management with automated sync and rollback capabilities.
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
Wait for ArgoCD to be ready
kubectl wait --for=condition=available --timeout=300s deployment/argocd-server -n argocd
Get initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
Configure monitoring and alerts
Set up monitoring for your deployments with Prometheus metrics and Grafana dashboards.
apiVersion: v1
kind: ServiceMonitor
metadata:
name: myapp-monitor
namespace: myapp-production
labels:
app: myapp
spec:
selector:
matchLabels:
app: myapp
endpoints:
- port: metrics
path: /metrics
interval: 30s
---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: myapp-alerts
namespace: myapp-production
spec:
groups:
- name: myapp.rules
rules:
- alert: MyAppDown
expr: up{job="myapp"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "MyApp is down"
description: "MyApp has been down for more than 1 minute"
- alert: MyAppHighErrorRate
expr: rate(http_requests_total{job="myapp",code=~"5.."}[5m]) > 0.1
for: 5m
labels:
severity: warning
annotations:
summary: "High error rate in MyApp"
description: "Error rate is above 10% for 5 minutes"
kubectl apply -f k8s/monitoring.yaml
Verify your setup
Test the complete CI/CD pipeline and verify deployments are working correctly.
# Check GitLab Runner status
sudo gitlab-runner status
Verify Kubernetes connectivity
kubectl get nodes
kubectl get serviceaccount gitlab-runner -o yaml
Check namespace and resources
kubectl get all -n myapp-production
kubectl describe deployment myapp -n myapp-production
Monitor deployment rollout
kubectl rollout status deployment/myapp -n myapp-production
kubectl get pods -n myapp-production -l app=myapp
Check service connectivity
kubectl get svc -n myapp-production
kubectl port-forward svc/myapp-service 8080:80 -n myapp-production
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Runner registration fails | Invalid registration token or GitLab URL | Verify token in GitLab CI/CD settings and check URL accessibility |
| Kubernetes authentication error | Service account token expired or invalid | Recreate service account token: kubectl create token gitlab-runner --duration=8760h |
| Pod creation fails | Insufficient RBAC permissions | Check ClusterRole permissions and verify service account binding |
| Image pull fails | Registry authentication or network issues | Verify CI_REGISTRY_PASSWORD variable and network connectivity |
| Deployment timeout | Resource constraints or health check failures | Check pod logs: kubectl logs -l app=myapp -n myapp-production |
| Service not accessible | Label selector mismatch | Verify service selector matches deployment labels |
Next steps
- Configure OpenTelemetry for comprehensive application tracing
- Integrate Vault for secure secrets management
- Set up Ingress NGINX with automated SSL certificates
- Implement network policies for enhanced security
- Configure horizontal pod autoscaler for automatic scaling
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Usage function
usage() {
echo "Usage: $0 <gitlab-url> <registration-token> <kubernetes-api-server>"
echo "Example: $0 https://gitlab.example.com glrt-xxxxxxxxxxxx https://k8s.example.com:6443"
exit 1
}
# Error handler
cleanup() {
echo -e "${RED}[ERROR] Script failed. Cleaning up...${NC}"
rm -f /tmp/gitlab-token /tmp/gitlab-service-account.yaml
exit 1
}
trap cleanup ERR
# Check arguments
if [ $# -ne 3 ]; then
usage
fi
GITLAB_URL="$1"
REGISTRATION_TOKEN="$2"
K8S_API_SERVER="$3"
# Check if running as root
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}Please run as root or with sudo${NC}"
exit 1
fi
# Auto-detect distribution
echo -e "${YELLOW}[1/10] Detecting operating system...${NC}"
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
REPO_SCRIPT="script.deb.sh"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
REPO_SCRIPT="script.rpm.sh"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
REPO_SCRIPT="script.rpm.sh"
;;
*)
echo -e "${RED}Unsupported distribution: $ID${NC}"
exit 1
;;
esac
echo -e "${GREEN}Detected: $PRETTY_NAME${NC}"
else
echo -e "${RED}Cannot detect OS. /etc/os-release not found${NC}"
exit 1
fi
# Update package repositories
echo -e "${YELLOW}[2/10] Updating package repositories...${NC}"
$PKG_UPDATE
# Install required packages
echo -e "${YELLOW}[3/10] Installing prerequisite packages...${NC}"
if [ "$PKG_MGR" = "apt" ]; then
$PKG_INSTALL curl wget gpg software-properties-common apt-transport-https
else
$PKG_INSTALL curl wget gnupg2 which
fi
# Install GitLab Runner
echo -e "${YELLOW}[4/10] Installing GitLab Runner...${NC}"
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/${REPO_SCRIPT}" | bash
$PKG_INSTALL gitlab-runner
# Install kubectl
echo -e "${YELLOW}[5/10] Installing kubectl...${NC}"
KUBECTL_VERSION=$(curl -L -s https://dl.k8s.io/release/stable.txt)
curl -LO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl"
install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
rm kubectl
# Install Helm
echo -e "${YELLOW}[6/10] Installing Helm...${NC}"
if [ "$PKG_MGR" = "apt" ]; then
curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | tee /usr/share/keyrings/helm.gpg > /dev/null
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | tee /etc/apt/sources.list.d/helm-stable-debian.list
apt update
$PKG_INSTALL helm
else
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh
rm get_helm.sh
fi
# Create GitLab service account RBAC
echo -e "${YELLOW}[7/10] Creating GitLab service account and RBAC...${NC}"
cat > /tmp/gitlab-service-account.yaml << 'EOF'
apiVersion: v1
kind: ServiceAccount
metadata:
name: gitlab-runner
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: gitlab-runner
rules:
- apiGroups: [""]
resources: ["pods", "services", "configmaps", "secrets"]
verbs: ["get", "list", "create", "update", "delete"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "create", "update", "delete"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: gitlab-runner
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: gitlab-runner
subjects:
- kind: ServiceAccount
name: gitlab-runner
namespace: default
EOF
kubectl apply -f /tmp/gitlab-service-account.yaml
# Generate and extract service account token
echo -e "${YELLOW}[8/10] Generating service account token...${NC}"
kubectl create token gitlab-runner --duration=8760h > /tmp/gitlab-token
chmod 600 /tmp/gitlab-token
# Determine Kubernetes CA file location
echo -e "${YELLOW}[9/10] Locating Kubernetes CA certificate...${NC}"
K8S_CA_FILE="/etc/kubernetes/pki/ca.crt"
if [ ! -f "$K8S_CA_FILE" ]; then
# Try alternative locations
for ca_path in "/etc/ssl/certs/ca-certificates.crt" "/etc/pki/tls/certs/ca-bundle.crt" "/etc/ssl/ca-bundle.pem"; do
if [ -f "$ca_path" ]; then
K8S_CA_FILE="$ca_path"
break
fi
done
fi
if [ ! -f "$K8S_CA_FILE" ]; then
echo -e "${YELLOW}Warning: Kubernetes CA file not found. Using system CA bundle.${NC}"
K8S_CA_FILE="/etc/ssl/certs/ca-certificates.crt"
fi
# Register GitLab Runner
echo -e "${YELLOW}[10/10] Registering GitLab Runner...${NC}"
gitlab-runner register \
--non-interactive \
--url "$GITLAB_URL" \
--registration-token "$REGISTRATION_TOKEN" \
--executor "kubernetes" \
--description "kubernetes-runner" \
--kubernetes-host "$K8S_API_SERVER" \
--kubernetes-ca-file "$K8S_CA_FILE" \
--kubernetes-bearer-token-file "/tmp/gitlab-token" \
--kubernetes-namespace "default" \
--kubernetes-service-account "gitlab-runner" \
--kubernetes-image "ubuntu:22.04" \
--kubernetes-cpu-request "100m" \
--kubernetes-memory-request "128Mi" \
--kubernetes-cpu-limit "1000m" \
--kubernetes-memory-limit "1Gi"
# Configure runner service
systemctl enable gitlab-runner
systemctl start gitlab-runner
# Verification checks
echo -e "${YELLOW}Performing verification checks...${NC}"
# Check GitLab Runner status
if systemctl is-active --quiet gitlab-runner; then
echo -e "${GREEN}✓ GitLab Runner service is running${NC}"
else
echo -e "${RED}✗ GitLab Runner service is not running${NC}"
fi
# Check kubectl connectivity
if kubectl cluster-info &>/dev/null; then
echo -e "${GREEN}✓ kubectl can connect to Kubernetes cluster${NC}"
else
echo -e "${RED}✗ kubectl cannot connect to Kubernetes cluster${NC}"
fi
# Check Helm installation
if command -v helm &>/dev/null; then
echo -e "${GREEN}✓ Helm is installed (version: $(helm version --short))${NC}"
else
echo -e "${RED}✗ Helm is not installed${NC}"
fi
# Check service account
if kubectl get serviceaccount gitlab-runner &>/dev/null; then
echo -e "${GREEN}✓ GitLab service account exists${NC}"
else
echo -e "${RED}✗ GitLab service account not found${NC}"
fi
# Cleanup temporary files
rm -f /tmp/gitlab-token /tmp/gitlab-service-account.yaml
echo -e "${GREEN}GitLab CI/CD integration with Kubernetes completed successfully!${NC}"
echo -e "${YELLOW}Next steps:${NC}"
echo "1. Verify the runner appears in your GitLab project's CI/CD settings"
echo "2. Create a .gitlab-ci.yml file in your repository"
echo "3. Configure your Kubernetes deployment manifests"
Review the script before running. Execute with: bash install.sh