Set up Open Policy Agent Gatekeeper with ArgoCD to enforce Kubernetes admission policies through GitOps workflows. This tutorial covers installation, policy template creation, and automated policy enforcement with monitoring.
Prerequisites
- Running Kubernetes cluster with admin access
- kubectl configured
- Helm 3 installed
- Git repository access
- Basic Kubernetes and RBAC knowledge
What this solves
Open Policy Agent (OPA) Gatekeeper provides Kubernetes admission control through customizable policies, while ArgoCD enables GitOps-based continuous deployment. Integrating these tools creates a comprehensive policy-as-code system where security policies, resource quotas, and compliance rules are version-controlled and automatically enforced across your Kubernetes clusters.
This setup is essential for organizations requiring centralized policy management, compliance automation, and secure multi-tenant Kubernetes environments where policy changes must be auditable and consistently applied.
Prerequisites
- Running Kubernetes cluster with cluster-admin access
- kubectl configured and connected to your cluster
- Helm 3 installed
- Git repository for storing policy definitions
- Basic understanding of Kubernetes RBAC and admission controllers
Step-by-step installation
Install OPA Gatekeeper
Deploy Gatekeeper using the official manifest. This creates the admission webhook, constraint templates, and controller components.
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/release-3.14/deploy/gatekeeper.yaml
Verify the Gatekeeper pods are running:
kubectl get pods -n gatekeeper-system
kubectl wait --for=condition=Ready pods --all -n gatekeeper-system --timeout=300s
Install ArgoCD
Create the ArgoCD namespace and deploy the controller. This provides the GitOps engine for managing policy deployments.
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
Wait for ArgoCD components to be ready:
kubectl wait --for=condition=Ready pods --all -n argocd --timeout=600s
Configure ArgoCD access
Get the initial admin password and set up port forwarding to access the ArgoCD UI.
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d && echo
Start port forwarding in the background:
kubectl port-forward svc/argocd-server -n argocd 8080:443 &
Access ArgoCD at https://localhost:8080 with username admin and the password from the previous command.
Create policy repository structure
Set up a Git repository with the proper directory structure for Gatekeeper policies. This enables version control and GitOps workflows for policy management.
mkdir -p gatekeeper-policies/{templates,constraints,config}
cd gatekeeper-policies
Create constraint template for required labels
Define a reusable policy template that enforces required labels on Kubernetes resources. This template uses Rego language for policy logic.
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
type: object
properties:
labels:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg}] {
provided := input.review.object.metadata.labels
required := input.parameters.labels
missing := required[_]
not provided[missing]
msg := sprintf("Missing required label: %v", [missing])
}
Create resource quota constraint template
Define a template for enforcing resource limits on containers to prevent resource exhaustion attacks and ensure fair resource allocation.
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8scontainerlimits
spec:
crd:
spec:
names:
kind: K8sContainerLimits
validation:
type: object
properties:
cpu:
type: string
memory:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8scontainerlimits
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.resources.limits.cpu
msg := "Container must have CPU limits"
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.resources.limits.memory
msg := "Container must have memory limits"
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
cpu_limit := container.resources.limits.cpu
cpu_limit > input.parameters.cpu
msg := sprintf("CPU limit %v exceeds maximum %v", [cpu_limit, input.parameters.cpu])
}
Create policy constraints
Define specific constraint instances that apply the templates to namespaces. These constraints enforce the actual policies in your cluster.
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: must-have-app-labels
spec:
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment", "StatefulSet", "DaemonSet"]
excludedNamespaces: ["kube-system", "gatekeeper-system", "argocd"]
parameters:
labels: ["app", "version", "environment"]
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sContainerLimits
metadata:
name: container-must-have-limits
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
- apiGroups: ["apps"]
kinds: ["Deployment", "StatefulSet", "DaemonSet"]
excludedNamespaces: ["kube-system", "gatekeeper-system", "argocd"]
parameters:
cpu: "2000m"
memory: "4Gi"
Create Gatekeeper configuration
Configure Gatekeeper to exclude system namespaces from policy enforcement and set up audit intervals for compliance reporting.
apiVersion: config.gatekeeper.sh/v1alpha1
kind: Config
metadata:
name: config
namespace: gatekeeper-system
spec:
match:
- excludedNamespaces: ["kube-system", "gatekeeper-system", "argocd"]
processes: ["*"]
validation:
traces:
- user:
kind:
group: "*"
version: "*"
kind: "*"
audit:
auditInterval: 60s
Initialize Git repository
Initialize the policy repository and commit your policy definitions for GitOps management.
git init
git add .
git commit -m "Initial Gatekeeper policies"
git branch -M main
git remote add origin https://github.com/yourusername/gatekeeper-policies.git
git push -u origin main
Create ArgoCD application for Gatekeeper policies
Define an ArgoCD application that watches your policy repository and automatically deploys changes to the cluster.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: gatekeeper-policies
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/yourusername/gatekeeper-policies.git
targetRevision: main
path: .
destination:
server: https://kubernetes.default.svc
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
Apply the ArgoCD application:
kubectl apply -f argocd-gatekeeper-app.yaml
Configure policy monitoring
Set up monitoring for policy violations using Prometheus and Grafana. Gatekeeper exports metrics about constraint violations and audit results.
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: gatekeeper-controller-manager
namespace: gatekeeper-system
spec:
endpoints:
- interval: 30s
path: /metrics
port: http
selector:
matchLabels:
control-plane: controller-manager
gatekeeper.sh/operation: webhook
Apply the service monitor if you have Prometheus Operator installed:
kubectl apply -f gatekeeper-servicemonitor.yaml
Set up policy violation alerts
Create Prometheus alerting rules for policy violations to ensure compliance issues are immediately visible to your team.
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: gatekeeper-violations
namespace: gatekeeper-system
spec:
groups:
- name: gatekeeper.rules
rules:
- alert: GatekeeperViolations
expr: gatekeeper_violations > 0
for: 5m
labels:
severity: warning
annotations:
summary: "Gatekeeper policy violations detected"
description: "{{ $labels.violation_kind }} has {{ $value }} policy violations"
- alert: GatekeeperAuditDuration
expr: gatekeeper_audit_duration_seconds > 300
for: 10m
labels:
severity: warning
annotations:
summary: "Gatekeeper audit taking too long"
description: "Audit cycle is taking {{ $value }} seconds"
Apply the alerting rules:
kubectl apply -f gatekeeper-alerts.yaml
Configure GitOps workflow
Set up policy review process
Create a branch protection rule and review process for policy changes. This ensures all policy modifications are reviewed before deployment.
name: Validate Gatekeeper Policies
on:
pull_request:
paths:
- 'templates/**'
- 'constraints/**'
- 'config/**'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup kubectl
uses: azure/setup-kubectl@v3
with:
version: 'v1.28.0'
- name: Validate constraint templates
run: |
for template in templates/*.yaml; do
kubectl --dry-run=server apply -f "$template" || exit 1
done
- name: Validate constraints
run: |
for constraint in constraints/*.yaml; do
kubectl --dry-run=server apply -f "$constraint" || exit 1
done
Create policy testing framework
Set up automated testing for your policies using conftest to validate that policies work as expected before deployment.
curl -L https://github.com/open-policy-agent/conftest/releases/latest/download/conftest_Linux_x86_64.tar.gz | tar xz
sudo mv conftest /usr/local/bin
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-app
labels:
app: test-app
version: v1.0.0
environment: testing
spec:
replicas: 1
selector:
matchLabels:
app: test-app
template:
metadata:
labels:
app: test-app
spec:
containers:
- name: app
image: nginx:1.21
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
apiVersion: apps/v1
kind: Deployment
metadata:
name: bad-app
spec:
replicas: 1
selector:
matchLabels:
app: bad-app
template:
metadata:
labels:
app: bad-app
spec:
containers:
- name: app
image: nginx:1.21
Verify your setup
Test that your OPA Gatekeeper and ArgoCD integration is working by deploying test applications and verifying policy enforcement.
# Check Gatekeeper status
kubectl get constrainttemplates
kubectl get k8srequiredlabels
kubectl get k8scontainerlimits
Check ArgoCD application status
kubectl get applications -n argocd
Test policy enforcement with a non-compliant deployment
kubectl apply -f tests/deployment-invalid.yaml
Check constraint violations
kubectl get k8srequiredlabels must-have-app-labels -o yaml
kubectl get events --field-selector reason=ConstraintViolation
Monitor policy violations through the Gatekeeper audit system:
# Check audit results
kubectl logs -n gatekeeper-system -l control-plane=audit-controller
View policy violation metrics
kubectl port-forward -n gatekeeper-system svc/gatekeeper-controller-manager-metrics-service 8443:8443 &
curl -k https://localhost:8443/metrics | grep gatekeeper_violations
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Constraint template not found | Template not applied or invalid Rego | kubectl logs -n gatekeeper-system -l control-plane=controller-manager for errors |
| ArgoCD sync fails | Invalid YAML or RBAC issues | Check ArgoCD logs: kubectl logs -n argocd -l app.kubernetes.io/name=argocd-application-controller |
| Policies not enforcing | Webhook not configured or namespace excluded | Verify webhook: kubectl get validatingwebhookconfiguration gatekeeper-validating-webhook-configuration |
| High audit duration | Too many resources to audit | Increase audit interval in config or exclude more namespaces |
| Policy conflicts | Multiple constraints affecting same resources | Review constraint match criteria and use enforcementAction: dryrun for testing |
Production considerations
For production deployments, consider implementing these additional security and operational measures:
enforcementAction: dryrun on new policies to identify violations without blocking deployments. Monitor audit logs for a week before switching to deny mode.- Use separate Git repositories for different environments (dev/staging/prod policies)
- Implement policy signing and verification for supply chain security
- Set up automated backup of Gatekeeper configurations
- Configure resource limits for Gatekeeper controllers to prevent resource exhaustion
- Use external data sources for dynamic policy parameters
For enhanced monitoring, integrate with your existing observability stack by configuring Prometheus monitoring for ArgoCD and setting up comprehensive alerting rules for both policy violations and system health.
Consider implementing advanced Kubernetes security by combining Gatekeeper with Pod Security Standards for defense-in-depth protection.
Next steps
- Configure Istio security policies with mTLS for service mesh protection
- Implement Kubernetes network policies with Calico CNI and OPA Gatekeeper
- Set up Kubernetes RBAC with OPA Gatekeeper policy enforcement
- Configure Gatekeeper external data sources for dynamic policy parameters
- Implement policy-as-code with Gatekeeper and Terraform for infrastructure automation
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'
BLUE='\033[0;34m'
NC='\033[0m'
# Global variables
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_FILE="/tmp/opa-argocd-install.log"
CLEANUP_ITEMS=()
ARGOCD_PORT_PID=""
# Logging function
log() {
echo -e "${2:-$NC}$1${NC}" | tee -a "$LOG_FILE"
}
# Error handling and cleanup
cleanup() {
if [[ ${#CLEANUP_ITEMS[@]} -gt 0 ]]; then
log "Cleaning up due to error..." "$YELLOW"
for item in "${CLEANUP_ITEMS[@]}"; do
case "$item" in
"argocd_namespace") kubectl delete namespace argocd --ignore-not-found=true ;;
"gatekeeper") kubectl delete -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/release-3.14/deploy/gatekeeper.yaml --ignore-not-found=true ;;
"port_forward") [[ -n "$ARGOCD_PORT_PID" ]] && kill "$ARGOCD_PORT_PID" 2>/dev/null || true ;;
esac
done
fi
}
trap cleanup ERR
# Usage function
usage() {
cat << EOF
Usage: $0 [OPTIONS]
Install and configure OPA Gatekeeper with ArgoCD for GitOps policy management
Options:
-h, --help Show this help message
-p, --port PORT ArgoCD port for access (default: 8080)
-t, --timeout SEC Timeout for pod readiness (default: 600)
--skip-verify Skip final verification steps
Example:
$0
$0 --port 9090 --timeout 300
EOF
}
# Parse arguments
ARGOCD_PORT=8080
TIMEOUT=600
SKIP_VERIFY=false
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help) usage; exit 0 ;;
-p|--port) ARGOCD_PORT="$2"; shift 2 ;;
-t|--timeout) TIMEOUT="$2"; shift 2 ;;
--skip-verify) SKIP_VERIFY=true; shift ;;
*) log "Unknown option: $1" "$RED"; usage; exit 1 ;;
esac
done
# Detect distribution
if [[ ! -f /etc/os-release ]]; then
log "Cannot detect Linux distribution" "$RED"
exit 1
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf check-update || true"
;;
fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf check-update || true"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum check-update || true"
;;
*)
log "Unsupported distribution: $ID" "$RED"
exit 1
;;
esac
log "Detected distribution: $PRETTY_NAME" "$BLUE"
# Check prerequisites
log "[1/9] Checking prerequisites..." "$BLUE"
if [[ $EUID -eq 0 ]]; then
log "Running as root user" "$YELLOW"
else
if ! sudo -n true 2>/dev/null; then
log "This script requires sudo privileges" "$RED"
exit 1
fi
fi
# Check required tools
for tool in kubectl curl; do
if ! command -v "$tool" &> /dev/null; then
log "Installing $tool..." "$YELLOW"
sudo $PKG_UPDATE
case "$tool" in
kubectl)
case "$PKG_MGR" in
apt)
curl -fsSLo /etc/apt/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt update && sudo $PKG_INSTALL kubectl
;;
dnf|yum)
cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF
sudo $PKG_INSTALL kubectl
;;
esac
;;
curl) sudo $PKG_INSTALL curl ;;
esac
fi
done
# Verify kubectl connection
if ! kubectl cluster-info &>/dev/null; then
log "kubectl is not configured or cluster is not accessible" "$RED"
exit 1
fi
log "Prerequisites check completed" "$GREEN"
# Install OPA Gatekeeper
log "[2/9] Installing OPA Gatekeeper..." "$BLUE"
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/release-3.14/deploy/gatekeeper.yaml
CLEANUP_ITEMS+=("gatekeeper")
log "[3/9] Waiting for Gatekeeper pods to be ready..." "$BLUE"
kubectl wait --for=condition=Ready pods --all -n gatekeeper-system --timeout="${TIMEOUT}s"
log "Gatekeeper installation completed" "$GREEN"
# Install ArgoCD
log "[4/9] Installing ArgoCD..." "$BLUE"
kubectl create namespace argocd
CLEANUP_ITEMS+=("argocd_namespace")
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
log "[5/9] Waiting for ArgoCD pods to be ready..." "$BLUE"
kubectl wait --for=condition=Ready pods --all -n argocd --timeout="${TIMEOUT}s"
log "ArgoCD installation completed" "$GREEN"
# Configure ArgoCD access
log "[6/9] Configuring ArgoCD access..." "$BLUE"
ARGOCD_PASSWORD=""
for i in {1..30}; do
if ARGOCD_PASSWORD=$(kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" 2>/dev/null | base64 -d); then
break
fi
log "Waiting for ArgoCD initial password... ($i/30)" "$YELLOW"
sleep 2
done
if [[ -z "$ARGOCD_PASSWORD" ]]; then
log "Failed to retrieve ArgoCD initial password" "$RED"
exit 1
fi
log "ArgoCD admin password: $ARGOCD_PASSWORD" "$GREEN"
# Start port forwarding
log "[7/9] Starting ArgoCD port forwarding on port $ARGOCD_PORT..." "$BLUE"
kubectl port-forward svc/argocd-server -n argocd "$ARGOCD_PORT:443" &>/dev/null &
ARGOCD_PORT_PID=$!
CLEANUP_ITEMS+=("port_forward")
# Create policy repository structure
log "[8/9] Creating policy repository structure..." "$BLUE"
POLICY_DIR="$SCRIPT_DIR/gatekeeper-policies"
mkdir -p "$POLICY_DIR"/{templates,constraints,config}
chown -R $(id -u):$(id -g) "$POLICY_DIR"
chmod -R 755 "$POLICY_DIR"
# Create constraint template for required labels
cat > "$POLICY_DIR/templates/required-labels.yaml" << 'EOF'
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
type: object
properties:
labels:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg}] {
provided := input.review.object.metadata.labels
required := input.parameters.labels
missing := required[_]
not provided[missing]
msg := sprintf("Missing required label: %v", [missing])
}
EOF
# Create constraint template for container limits
cat > "$POLICY_DIR/templates/container-limits.yaml" << 'EOF'
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8scontainerlimits
spec:
crd:
spec:
names:
kind: K8sContainerLimits
validation:
type: object
properties:
cpu:
type: string
memory:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8scontainerlimits
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.resources.limits.cpu
msg := "Container must specify CPU limits"
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.resources.limits.memory
msg := "Container must specify memory limits"
}
EOF
# Create sample constraint
cat > "$POLICY_DIR/constraints/require-app-label.yaml" << 'EOF'
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: require-app-label
spec:
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment", "StatefulSet", "DaemonSet"]
parameters:
labels: ["app"]
EOF
chmod 644 "$POLICY_DIR"/{templates,constraints,config}/*.yaml
log "Policy repository structure created at $POLICY_DIR" "$GREEN"
# Verification
if [[ "$SKIP_VERIFY" != "true" ]]; then
log "[9/9] Performing verification..." "$BLUE"
# Check Gatekeeper
GATEKEEPER_PODS=$(kubectl get pods -n gatekeeper-system --no-headers | grep -c "Running" || echo "0")
if [[ "$GATEKEEPER_PODS" -lt 3 ]]; then
log "Warning: Expected at least 3 Gatekeeper pods running, found $GATEKEEPER_PODS" "$YELLOW"
else
log "✓ Gatekeeper pods are running ($GATEKEEPER_PODS/3+)" "$GREEN"
fi
# Check ArgoCD
ARGOCD_PODS=$(kubectl get pods -n argocd --no-headers | grep -c "Running" || echo "0")
if [[ "$ARGOCD_PODS" -lt 5 ]]; then
log "Warning: Expected at least 5 ArgoCD pods running, found $ARGOCD_PODS" "$YELLOW"
else
log "✓ ArgoCD pods are running ($ARGOCD_PODS/5+)" "$GREEN"
fi
# Check port forwarding
sleep 2
if kill -0 "$ARGOCD_PORT_PID" 2>/dev/null; then
log "✓ ArgoCD port forwarding is active (PID: $ARGOCD_PORT_PID)" "$GREEN"
else
log "Warning: ArgoCD port forwarding may have failed" "$YELLOW"
fi
log "✓ Policy templates created in $POLICY_DIR" "$GREEN"
log "Verification completed" "$GREEN"
fi
# Installation summary
log "Installation completed successfully!" "$GREEN"
echo
log "Next steps:" "$BLUE"
log "1. Access ArgoCD UI at https://localhost:$ARGOCD_PORT" "$NC"
log " Username: admin" "$NC"
log " Password: $ARGOCD_PASSWORD" "$NC"
log "2. Initialize git repository in $POLICY_DIR and push to your Git provider" "$NC"
log "3. Create ArgoCD Application to sync policies from your Git repository" "$NC"
log "4. Apply constraint templates: kubectl apply -f $POLICY_DIR/templates/" "$NC"
log "5. Apply constraints: kubectl apply -f $POLICY_DIR/constraints/" "$NC"
echo
log "Port forwarding PID: $ARGOCD_PORT_PID (kill to stop)" "$YELLOW"
log "Log file: $LOG_FILE" "$NC"
# Remove cleanup items since installation succeeded
CLEANUP_ITEMS=()
Review the script before running. Execute with: bash install.sh