Practical Applications and Examples
The real test of configuration management comes when you’re deploying actual applications. I’ve configured everything from simple web services to complex microservice architectures, and each taught me something new about what works in practice versus what looks good in documentation.
The most valuable lesson I’ve learned: start simple and add complexity only when you need it. I’ve seen teams over-engineer configuration systems that became harder to debug than the applications they were meant to support.
Microservices Configuration Architecture
Managing configuration for a microservices architecture requires coordination across multiple services. Here’s how I structure configuration for a typical e-commerce platform with user service, product service, and order service.
I start with shared configuration that all services need:
apiVersion: v1
kind: ConfigMap
metadata:
name: platform-config
namespace: ecommerce
data:
platform.yaml: |
cluster_name: production-east
region: us-east-1
environment: production
observability:
jaeger_endpoint: http://jaeger-collector:14268/api/traces
metrics_port: 9090
log_format: json
security:
cors_origins:
- https://app.example.com
- https://admin.example.com
rate_limit: 1000
Each service gets its own specific configuration:
# User Service Configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: user-service-config
namespace: ecommerce
data:
service.yaml: |
service_name: user-service
port: 8080
database:
host: postgres-users.internal
port: 5432
database: users
max_connections: 20
cache:
redis_url: redis://redis-users:6379
ttl: 3600
auth:
jwt_secret_key: user-jwt-secret
token_expiry: 24h
---
# Product Service Configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: product-service-config
namespace: ecommerce
data:
service.yaml: |
service_name: product-service
port: 8080
database:
host: postgres-products.internal
port: 5432
database: products
max_connections: 50
search:
elasticsearch_url: http://elasticsearch:9200
index_name: products
image_storage:
s3_bucket: product-images-prod
cdn_url: https://cdn.example.com
The deployment pattern I use injects both shared and service-specific configuration:
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
namespace: ecommerce
spec:
template:
spec:
containers:
- name: user-service
image: user-service:v1.2.0
env:
- name: CONFIG_PATH
value: /etc/config
volumeMounts:
- name: platform-config
mountPath: /etc/config/platform
- name: service-config
mountPath: /etc/config/service
- name: secrets
mountPath: /etc/secrets
volumes:
- name: platform-config
configMap:
name: platform-config
- name: service-config
configMap:
name: user-service-config
- name: secrets
secret:
secretName: user-service-secrets
This pattern scales well because each service gets exactly the configuration it needs while sharing common platform settings.
Database Configuration Patterns
Database configuration is where I’ve made the most painful mistakes. Connection strings, credentials, and connection pooling settings need careful management across environments.
Here’s my standard database configuration pattern:
apiVersion: v1
kind: ConfigMap
metadata:
name: database-config
namespace: production
data:
database.yaml: |
primary:
host: postgres-primary.internal
port: 5432
database: myapp
sslmode: require
pool:
min_size: 5
max_size: 20
max_lifetime: 1h
idle_timeout: 10m
timeouts:
connect: 30s
query: 60s
idle: 300s
replica:
host: postgres-replica.internal
port: 5432
database: myapp
sslmode: require
pool:
min_size: 2
max_size: 10
max_lifetime: 1h
idle_timeout: 10m
---
apiVersion: v1
kind: Secret
metadata:
name: database-credentials
namespace: production
type: Opaque
data:
username: bXlhcHA= # myapp
password: c3VwZXJzZWNyZXRwYXNzd29yZA== # supersecretpassword
# Connection strings for different use cases
primary_url: cG9zdGdyZXM6Ly9teWFwcDpzdXBlcnNlY3JldHBhc3N3b3JkQHBvc3RncmVzLXByaW1hcnkuaW50ZXJuYWw6NTQzMi9teWFwcD9zc2xtb2RlPXJlcXVpcmU=
replica_url: cG9zdGdyZXM6Ly9teWFwcDpzdXBlcnNlY3JldHBhc3N3b3JkQHBvc3RncmVzLXJlcGxpY2EuaW50ZXJuYWw6NTQzMi9teWFwcD9zc2xtb2RlPXJlcXVpcmU=
Applications can use either the structured configuration or the pre-built connection strings depending on their database libraries.
Web Application Configuration
Web applications need configuration for frontend assets, API endpoints, and feature flags. I structure this configuration to support both server-side and client-side needs:
apiVersion: v1
kind: ConfigMap
metadata:
name: webapp-config
namespace: production
data:
# Server-side configuration
server.yaml: |
server:
port: 8080
host: 0.0.0.0
session:
secret_key: webapp-session-secret
cookie_name: webapp_session
max_age: 86400
secure: true
static_files:
path: /static
cache_duration: 3600
# Client-side configuration (injected into HTML)
client.json: |
{
"api_base_url": "https://api.example.com",
"websocket_url": "wss://ws.example.com",
"features": {
"new_dashboard": true,
"beta_features": false,
"analytics_enabled": true
},
"third_party": {
"google_analytics_id": "GA-XXXXXXXXX",
"stripe_public_key": "pk_live_..."
}
}
# Nginx configuration for serving the app
nginx.conf: |
server {
listen 80;
server_name example.com;
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://backend-service:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /static/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
The deployment injects client configuration into the HTML at build time, ensuring the frontend gets the right API endpoints for each environment.
Configuration Testing and Validation
I’ve learned to test configuration as rigorously as application code. Here’s my testing approach:
#!/bin/bash
# config-test.sh
set -e
echo "Testing Kubernetes configurations..."
# Test YAML syntax
echo "Validating YAML syntax..."
for file in *.yaml; do
if ! yq eval . "$file" >/dev/null 2>&1; then
echo "ERROR: Invalid YAML in $file"
exit 1
fi
done
# Test ConfigMap required keys
echo "Validating ConfigMap required keys..."
required_keys=("database.host" "database.port" "log.level")
for configmap in $(kubectl get configmaps -o name); do
for key in "${required_keys[@]}"; do
if ! kubectl get "$configmap" -o jsonpath="{.data['$key']}" >/dev/null 2>&1; then
echo "WARNING: Key '$key' missing in $configmap"
fi
done
done
# Test Secret references
echo "Validating Secret references..."
secret_refs=$(grep -r "secretKeyRef" . | grep -o "name: [a-zA-Z0-9-]*" | cut -d' ' -f2 | sort -u)
for secret in $secret_refs; do
if ! kubectl get secret "$secret" >/dev/null 2>&1; then
echo "ERROR: Referenced secret '$secret' does not exist"
exit 1
fi
done
echo "Configuration tests passed!"
I run this script in CI/CD pipelines before deploying configuration changes. It catches most configuration errors before they reach production.
Configuration Monitoring and Alerting
Monitoring configuration changes helps catch issues early. I use this monitoring setup:
apiVersion: v1
kind: ConfigMap
metadata:
name: config-monitor
data:
monitor.py: |
#!/usr/bin/env python3
import time
import hashlib
from kubernetes import client, config, watch
def monitor_config_changes():
config.load_incluster_config()
v1 = client.CoreV1Api()
print("Starting configuration monitor...")
w = watch.Watch()
for event in w.stream(v1.list_config_map_for_all_namespaces):
config_map = event['object']
event_type = event['type']
if event_type in ['ADDED', 'MODIFIED']:
print(f"ConfigMap {config_map.metadata.name} {event_type}")
# Calculate configuration hash
config_hash = hashlib.md5(
str(config_map.data).encode()
).hexdigest()
# Log change for audit
print(f"Configuration hash: {config_hash}")
# Alert on production changes
if config_map.metadata.namespace == 'production':
send_alert(config_map.metadata.name, event_type)
def send_alert(config_name, event_type):
# Send alert to monitoring system
print(f"ALERT: Production config {config_name} {event_type}")
if __name__ == "__main__":
monitor_config_changes()
This monitor tracks all configuration changes and alerts when production configuration is modified.
Configuration Drift Detection
Configuration drift happens when running configuration differs from what’s in version control. I use this drift detection system:
#!/usr/bin/env python3
import yaml
import hashlib
from kubernetes import client, config
class ConfigDriftDetector:
def __init__(self):
config.load_incluster_config()
self.v1 = client.CoreV1Api()
def detect_drift(self):
print("Detecting configuration drift...")
# Load baseline configuration from git
baseline = self.load_baseline_config()
# Get current cluster configuration
current = self.get_cluster_config()
# Compare configurations
drift_detected = False
for name, baseline_config in baseline.items():
if name not in current:
print(f"DRIFT: ConfigMap {name} missing from cluster")
drift_detected = True
continue
current_config = current[name]
if self.config_hash(baseline_config) != self.config_hash(current_config):
print(f"DRIFT: ConfigMap {name} differs from baseline")
self.show_diff(name, baseline_config, current_config)
drift_detected = True
return drift_detected
def config_hash(self, config_data):
return hashlib.md5(str(config_data).encode()).hexdigest()
def show_diff(self, name, baseline, current):
print(f"Differences in {name}:")
# Implementation would show actual differences
pass
I run drift detection daily to ensure cluster configuration matches the intended state in version control.
These practical patterns have evolved from managing configuration in real production environments. They handle the complexity that emerges when you move beyond simple examples to actual applications serving real users.
Next, we’ll explore advanced techniques including custom operators, policy-driven configuration, and enterprise-grade configuration management patterns.