(And yes! This is part of the "DevOps Tooling Masterclass" series. If you're new here, go check out the previous blog in the series first)
Hey Dev Community!
Welcome!
Part 1 β Executive summary
YAML is the de facto configuration language across modern DevOps and cloud-native tooling: Kubernetes manifests, Helm charts, GitHub Actions, GitLab CI, Ansible playbooks, Docker Compose, and many operator/CRD definitions. This masterclass explains why YAML matters, common pitfalls, practical tooling, validation and testing strategies, and end-to-end examples you can run and adapt. By the end you will have a reproducible workflow for authoring, validating, testing, and deploying YAML-driven infrastructure safely.
Part 2 β Why YAML is central to DevOps
- Human readable and declarative. YAML expresses nested configuration in a compact, readable form that maps well to declarative APIs.
- Ecosystem adoption. Major platforms standardized on YAML early; the ecosystem (linters, validators, editors) grew around it.
- Control plane language. Declarative infrastructure (Kubernetes, Helm, Ansible) expects structured manifests; YAML is the lingua franca.
- Automation-friendly. YAML is easy to generate, templatize, and validate in CI pipelines.
Implication: mastering YAML is not optional for modern DevOps engineers. Small mistakes in YAML can cause outages, silent misconfigurations, or security leaks. Treat YAML as code: lint, validate, test, and review.
Part 3 β YAML fundamentals and common gotchas
Core constructs
- Scalars: strings, numbers, booleans.
- Sequences: lists using - item.
- Mappings: key: value pairs.
- Anchors & aliases: reuse blocks with &anchor and *anchor.
- Block styles: | (literal) and > (folded).
Common pitfalls
- Indentation sensitivity: YAML uses spaces only; tabs break parsers.
- Type coercion: unquoted yes, no, on, off, null, ~ may be parsed as booleans or nulls by some parsers.
- Numeric ambiguity: 0123 or 1e3 may be interpreted as numbers; quote if you need strings.
- Anchors misuse: reusing anchors can silently propagate unwanted values.
- Trailing commas: YAML does not allow trailing commas like JSON.
- Large inline structures: reduce readability and increase error risk.
Practical rules
- Always use spaces (configure editor to convert tabs to spaces).
- Quote ambiguous scalars: "no", "0123".
- Prefer block style for long text.
- Use anchors sparingly and document them.
- Run linters and schema validators in CI.
Part 4 β Tooling and validation
Essential tools
- Linters: yamllint (style and common errors).
- Formatters: prettier (YAML plugin), ruamel.yaml for round-trip editing.
- Kubernetes validators: kubeval, kubeconform.
- Policy as code: conftest (Rego), OPA Gatekeeper.
- Template testing: helm lint, helm template, ct (chart-testing).
- Ansible testing: ansible-lint, molecule.
- Editor integrations: VS Code YAML extension with JSON Schema support.
CI integration pattern
- Lint YAML (yamllint).
- Render templates (helm template, kustomize build).
- Schema validate (kubeval, conftest).
- Unit tests for templates (small scripts asserting fields).
- Integration tests in ephemeral clusters.
- Progressive rollout (canary/blue-green) with automated checks.
Part 5 β Hands-on examples (runnable)
All examples are minimal but practical. Replace placeholders with your real values.
Example A β GitHub Actions CI: lint, test, build
.github/workflows/ci.yml
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
lint-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install tools
run: |
python -m pip install --upgrade pip
pip install yamllint pytest
- name: Lint YAML
run: yamllint -c .yamllint.yml .
- name: Run unit tests
run: pytest -q
build-image:
runs-on: ubuntu-latest
needs: lint-and-test
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build Docker image
run: |
docker build -t myregistry.example.com/webapp:${{ github.sha }} .
Notes
- Add .yamllint.yml to enforce rules.
- Use secrets for registry credentials.
Example B β Kubernetes deployment manifest (production-ready)
k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
labels:
app: webapp
spec:
replicas: 3
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
spec:
containers:
- name: webapp
image: myregistry.example.com/webapp:1.2.3
ports:
- containerPort: 8080
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "1000m"
memory: "1Gi"
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 30
periodSeconds: 20
failureThreshold: 5
Best practices shown
- Separate readiness and liveness.
- Resource requests and limits for scheduler stability.
- Prometheus annotations for scraping.
Example C β Helm chart template snippet
charts/webapp/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "webapp.fullname" . }}
labels:
app: {{ include "webapp.name" . }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ include "webapp.name" . }}
template:
metadata:
labels:
app: {{ include "webapp.name" . }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: {{ .Values.service.port }}
env:
- name: ENVIRONMENT
value: "{{ .Values.environment }}"
Helm tips
- Use helm lint and helm template in CI.
- Keep values.yaml documented and small.
Example D β Ansible playbook
ansible/playbook.yml
- name: Deploy webapp service
hosts: webservers
become: true
vars:
app_user: webapp
app_dir: /opt/webapp
tasks:
- name: Ensure app user exists
user:
name: "{{ app_user }}"
state: present
- name: Create app directory
file:
path: "{{ app_dir }}"
state: directory
owner: "{{ app_user }}"
mode: '0755'
- name: Deploy application files
copy:
src: ./dist/
dest: "{{ app_dir }}/"
owner: "{{ app_user }}"
mode: '0644'
- name: Ensure systemd service is present
template:
src: webapp.service.j2
dest: /etc/systemd/system/webapp.service
notify:
- restart webapp
handlers:
- name: restart webapp
systemd:
name: webapp
state: restarted
Ansible best practices
- Use ansible-lint and molecule for role testing.
- Keep playbooks idempotent.
Part 6 β Policy, validation, and CI examples
Conftest (Rego) policy example
policy.rego
package kubernetes.admission
deny[msg] {
input.kind == "Deployment"
not input.spec.template.spec.containers[_].resources.requests
msg = "All containers must set resource requests"
}
CI snippet to validate manifests
.github/workflows/validate.yml snippet
- name: Validate Kubernetes manifests
run: |
kubeval k8s/*.yaml
conftest test k8s/*.yaml
Why this matters
- Prevents misconfigurations from merging.
- Enforces organizational guardrails.
Part 7 β Testing, rollout strategies, and safety
Testing pyramid
- Unit tests: template rendering and small assertions.
- Integration tests: ephemeral clusters (Kind, KinD, ephemeral namespaces).
- Load tests: k6, wrk, locust.
- Chaos tests: simulate node failures, network partitions.
Deployment strategies
- Blue/Green: full environment switch; instant rollback.
- Canary: route small percentage to new version; monitor metrics and ramp.
- Progressive delivery: automated ramp with rollback triggers.
Canary automation example (pseudo)
deploy canary
kubectl apply -f k8s/deployment-canary.yaml
monitor script (simplified)
if ./scripts/check_canary.sh; then
promote canary
kubectl apply -f k8s/deployment-promote.yaml
else
kubectl rollout undo deployment/webapp
fi
Monitoring signals
- p95/p99 latency
- error rate (4xx/5xx)
- request saturation and backend health
- business metrics (conversion, checkout success)
Part 8 β Secrets, immutability, and security
- Never store secrets in plain YAML in VCS. Use sealed secrets, HashiCorp Vault, or cloud secret managers.
- Immutable images. Build artifacts once and deploy by replacing pods, not mutating them.
- Policy as code. Enforce with OPA Gatekeeper or Conftest.
- Edge protections. WAF, rate limiting, and DDoS mitigation at the edge.
Part 9 β Observability and runbooks
Essential metrics
- httprequeststotal
- httprequestduration_seconds (histogram)
- backend_up (gauge)
- backendactiveconnections
Tracing
- Instrument with OpenTelemetry to correlate requests across LB β service β DB.
Runbooks
- Document rollback steps, escalation contacts, and playbooks for common failures (failed rollout, DB migration failure, certificate expiry).
Part 10 β Checklist before production
- YAML linting enabled in CI (yamllint).
- Template rendering and schema validation (helm lint, kubeval, conftest).
- Unit and integration tests for templates.
- Canary or blue/green deployment configured.
- Observability: dashboards, alerts, traces.
- Secrets externalized and encrypted.
- Load and chaos tests passed.
- Rollback automation and runbooks in place.
Appendix A β Example repo layout
βββ .github
β βββ workflows
β βββ ci.yml
β βββ validate.yml
βββ ansible
β βββ playbook.yml
β βββ roles
βββ charts
β βββ webapp
βββ k8s
β βββ deployment.yaml
β βββ service.yaml
βββ scripts
β βββ check_canary.sh
βββ tools
β βββ conftest
βββ README.md
Appendix B β Quick commands
lint YAML
yamllint .
validate k8s manifests
kubeval k8s/*.yaml
helm lint
helm lint charts/webapp
run a simple load test
wrk -t4 -c100 -d30s http://lb.example.com/api
Closing notes
YAML is not merely a file format β it is the control plane for modern DevOps. Treat YAML as code: lint it, validate it, test it, and guard it with policy. Invest in editor tooling, CI validation, and progressive rollout automation. With these practices you turn YAML from a liability into a powerful enabler for safe, repeatable, and auditable infrastructure delivery.
Have nice times!
Top comments (0)