Infrastructure Automation#

Infrastructure automation brings the same rigour to infrastructure that CI/CD brings to application code: version control, peer review, automated testing, and repeatable deployments. GitOps, Terraform in pipelines, and policy-as-code are the pillars.


GitOps#

GitOps uses Git as the single source of truth for infrastructure and application state. A controller running in the cluster continuously reconciles the live state against what’s declared in Git.

Core Principles#

PrincipleMeaning
DeclarativeDesired state is described, not scripted
Versioned and immutableGit history is the audit trail
Pulled automaticallyController pulls from Git, not pushed by CI
Continuously reconciledDrift is detected and corrected automatically

Push vs Pull Deployment#

Push (traditional CI/CD):
  CI Pipeline ──push──▶ kubectl apply / helm upgrade ──▶ Cluster

Pull (GitOps):
  Developer ──commit──▶ Git Repo ◀──poll/watch── Controller ──reconcile──▶ Cluster

ArgoCD#

The most popular GitOps controller for Kubernetes:

# ArgoCD Application manifest
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/org/k8s-manifests.git
    targetRevision: main
    path: apps/my-app/overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true       # delete resources removed from Git
      selfHeal: true    # revert manual changes
    syncOptions:
      - CreateNamespace=true

Key features:

  • Web UI with real-time sync status visualisation
  • Multi-cluster support
  • SSO integration (OIDC, LDAP, SAML)
  • Rollback to any previous Git commit
  • Health checks and custom health assessments

Flux#

A lightweight, composable alternative:

# Flux GitRepository source
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: my-app
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/org/k8s-manifests.git
  ref:
    branch: main
---
# Flux Kustomization (reconciler)
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: my-app
  namespace: flux-system
spec:
  interval: 5m
  path: ./apps/my-app/production
  prune: true
  sourceRef:
    kind: GitRepository
    name: my-app

ArgoCD vs Flux#

AspectArgoCDFlux
ArchitectureSingle controller + UI serverSet of small controllers
UIRich web dashboardCLI-first (optional Weave UI)
Multi-tenancyAppProjectsNamespaced GitRepositories
Helm supportNativeVia HelmRelease CRD
Learning curveModerate (UI helps)Lower (YAML-native)
Best forTeams wanting visibility + UITeams preferring composability

IaC in Pipelines#

Terraform in CI/CD#

A standard pipeline for Terraform:

# GitLab CI
stages:
  - validate
  - plan
  - apply

validate:
  stage: validate
  script:
    - terraform init -backend=false
    - terraform fmt -check
    - terraform validate

plan:
  stage: plan
  script:
    - terraform init -backend-config=config/backend/${ENV}.tfbackend
    - terraform plan -var-file=config/variables/${ENV}.tfvars -out=plan.tfplan
  artifacts:
    paths:
      - plan.tfplan

apply:
  stage: apply
  script:
    - terraform init -backend-config=config/backend/${ENV}.tfbackend
    - terraform apply plan.tfplan
  when: manual   # require human approval for production
  only:
    - main

Pipeline Safety Rules#

RuleReason
Always plan before applyCatch unintended changes
Use saved plan files (-out=plan.tfplan)Apply exactly what was reviewed
Lock state during applyPrevent concurrent modifications
Require manual approval for prodHuman gate for destructive changes
Pin provider and module versionsReproducible builds
Never store secrets in tfvarsUse secret managers or CI variables

Environment Promotion#

Move infrastructure changes through environments with increasing confidence:

┌──────────┐     ┌──────────┐     ┌──────────┐
│   Test   │────▶│   Pre    │────▶│   Prod   │
│ (auto)   │     │ (auto)   │     │ (manual) │
└──────────┘     └──────────┘     └──────────┘
     │                │                │
     ▼                ▼                ▼
  Smoke tests    Integration       Canary/gradual
  + plan check   tests + soak      + monitoring

Promotion Strategies#

StrategyHow It WorksBest For
Branch-per-envenv/test, env/prod branchesSimple, small teams
Directory-per-envenvironments/test/, environments/prod/Monorepo IaC
Parameterised (tfvars)Same code, different variable filesTerraform projects
Promotion pipelineSame artifact flows through stagesGitOps + ArgoCD

Drift Detection#

Drift occurs when live infrastructure diverges from what’s declared in code (manual console changes, out-of-band scripts).

Detection Methods#

# Terraform: compare state to reality
terraform plan -detailed-exitcode
# Exit code 2 = drift detected

# Schedule periodic drift checks in CI
drift-check:
  script:
    - terraform plan -detailed-exitcode -var-file=config/variables/prod.tfvars
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"

Drift Response Strategies#

StrategyActionWhen to Use
Alert onlyNotify team, don’t auto-fixProduction resources with manual exceptions
Auto-remediateRe-apply desired stateNon-critical resources, GitOps clusters
Import + update codeBring manual change into IaCLegitimate out-of-band change
Ignore (lifecycle)ignore_changes in TerraformAuto-scaling counts, external tags

Policy as Code#

Enforce governance rules automatically, before resources are deployed.

Open Policy Agent (OPA) / Rego#

General-purpose policy engine:

# policy/terraform.rego
package terraform

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_s3_bucket"
  not resource.change.after.server_side_encryption_configuration
  msg := sprintf("S3 bucket '%s' must have encryption enabled", [resource.address])
}

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_security_group_rule"
  resource.change.after.cidr_blocks[_] == "0.0.0.0/0"
  resource.change.after.type == "ingress"
  msg := sprintf("Security group '%s' must not allow 0.0.0.0/0 ingress", [resource.address])
}

Integrate with Terraform:

# Generate plan JSON
terraform plan -out=plan.tfplan
terraform show -json plan.tfplan > plan.json

# Evaluate policies
opa eval --data policy/ --input plan.json "data.terraform.deny"

HashiCorp Sentinel#

Policy-as-code for Terraform Cloud/Enterprise:

# Sentinel policy
import "tfplan/v2" as tfplan

s3_buckets = filter tfplan.resource_changes as _, rc {
  rc.type is "aws_s3_bucket" and rc.mode is "managed"
}

main = rule {
  all s3_buckets as _, bucket {
    bucket.change.after.server_side_encryption_configuration is not null
  }
}

Comparison#

ToolIntegrationLanguageBest For
OPA / ConftestAny CI, Kubernetes (Gatekeeper)RegoMulti-platform, K8s admission
SentinelTerraform Cloud/Enterprise onlySentinel (Python-like)Terraform-native governance
CheckovCLI, CI pluginsPython (YAML rules)IaC scanning (Terraform, CloudFormation, K8s)
tfsec / TrivyCLI, CI pluginsGo (built-in rules)Quick Terraform security checks

Pipeline Integration#

policy-check:
  stage: validate
  script:
    - terraform plan -out=plan.tfplan
    - terraform show -json plan.tfplan > plan.json
    - conftest test plan.json --policy policy/
  allow_failure: false

Repository Patterns#

App Repo vs Config Repo (GitOps)#

PatternStructureProsCons
MonorepoApp code + K8s manifests togetherSimple, atomic changesImage build triggers manifest redeploy
Split reposApp repo + separate config/infra repoClean separation, independent lifecyclesCoordination overhead

Recommended split for GitOps:

  • App repo → builds image, pushes to registry, updates image tag in config repo
  • Config repo → K8s manifests / Helm values, watched by ArgoCD/Flux

Key Takeaways#

  1. GitOps makes Git the source of truth for cluster state — changes are auditable, reversible, and reconciled automatically.
  2. ArgoCD suits teams wanting a UI and multi-cluster visibility; Flux suits teams preferring lightweight composability.
  3. Terraform in CI/CD must always plan before apply, use saved plan files, and gate production with manual approval.
  4. Environment promotion should flow test → pre → prod with increasing gate strictness.
  5. Drift detection on a schedule catches console cowboys — decide whether to alert or auto-remediate based on the resource’s criticality.
  6. Policy as code (OPA, Sentinel, Checkov) prevents misconfigurations from ever reaching infrastructure — enforce encryption, restrict public access, and mandate tagging automatically.