chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,695 @@
|
||||
# CI/CD Pipelines & GitOps
|
||||
|
||||
## CI/CD Pipeline Stages
|
||||
|
||||
### 1. Source → 2. Build → 3. Test → 4. Deploy → 5. Monitor
|
||||
|
||||
```
|
||||
┌──────────┐ ┌───────┐ ┌──────┐ ┌────────┐ ┌─────────┐
|
||||
│ Source │──▶│ Build │──▶│ Test │──▶│ Deploy │──▶│ Monitor │
|
||||
│ (Git) │ │ │ │ │ │ │ │ │
|
||||
└──────────┘ └───────┘ └──────┘ └────────┘ └─────────┘
|
||||
```
|
||||
|
||||
## GitOps Principles
|
||||
|
||||
### Core Concepts
|
||||
1. **Git as Single Source of Truth**: All configuration in Git
|
||||
2. **Declarative**: Desired state defined, not imperative steps
|
||||
3. **Automated**: Continuous reconciliation of desired vs actual state
|
||||
4. **Auditable**: All changes tracked in Git history
|
||||
|
||||
### GitOps Workflow
|
||||
```
|
||||
Developer ──▶ Git Push ──▶ GitOps Controller ──▶ Kubernetes Cluster
|
||||
(ArgoCD/Flux)
|
||||
│
|
||||
▼
|
||||
Continuous Sync
|
||||
```
|
||||
|
||||
## ArgoCD Setup
|
||||
|
||||
### Installation
|
||||
```bash
|
||||
# Install ArgoCD
|
||||
kubectl create namespace argocd
|
||||
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
|
||||
|
||||
# Access ArgoCD UI
|
||||
kubectl port-forward svc/argocd-server -n argocd 8080:443
|
||||
|
||||
# Get initial password
|
||||
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
|
||||
```
|
||||
|
||||
### ArgoCD Application
|
||||
```yaml
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: myapp-production
|
||||
namespace: argocd
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: default
|
||||
|
||||
source:
|
||||
repoURL: https://github.com/myorg/myapp-gitops.git
|
||||
targetRevision: main
|
||||
path: kubernetes/overlays/production
|
||||
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: production
|
||||
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true # Delete resources not in Git
|
||||
selfHeal: true # Sync if cluster state drifts
|
||||
allowEmpty: false
|
||||
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- PrunePropagationPolicy=foreground
|
||||
- PruneLast=true
|
||||
|
||||
retry:
|
||||
limit: 5
|
||||
backoff:
|
||||
duration: 5s
|
||||
factor: 2
|
||||
maxDuration: 3m
|
||||
|
||||
ignoreDifferences:
|
||||
- group: apps
|
||||
kind: Deployment
|
||||
jsonPointers:
|
||||
- /spec/replicas # Ignore HPA-managed replicas
|
||||
```
|
||||
|
||||
### ArgoCD ApplicationSet (Multi-Environment)
|
||||
```yaml
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: ApplicationSet
|
||||
metadata:
|
||||
name: myapp
|
||||
namespace: argocd
|
||||
spec:
|
||||
generators:
|
||||
- list:
|
||||
elements:
|
||||
- env: dev
|
||||
cluster: https://kubernetes.default.svc
|
||||
- env: staging
|
||||
cluster: https://kubernetes.default.svc
|
||||
- env: production
|
||||
cluster: https://prod-cluster-api.example.com
|
||||
|
||||
template:
|
||||
metadata:
|
||||
name: 'myapp-{{env}}'
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/myorg/myapp-gitops.git
|
||||
targetRevision: main
|
||||
path: 'kubernetes/overlays/{{env}}'
|
||||
destination:
|
||||
server: '{{cluster}}'
|
||||
namespace: '{{env}}'
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
```
|
||||
|
||||
## Flux CD Setup
|
||||
|
||||
### Installation
|
||||
```bash
|
||||
# Install Flux CLI
|
||||
brew install fluxcd/tap/flux
|
||||
|
||||
# Check prerequisites
|
||||
flux check --pre
|
||||
|
||||
# Bootstrap Flux
|
||||
flux bootstrap github \
|
||||
--owner=myorg \
|
||||
--repository=fleet-infra \
|
||||
--branch=main \
|
||||
--path=clusters/production \
|
||||
--personal
|
||||
```
|
||||
|
||||
### Flux GitRepository
|
||||
```yaml
|
||||
apiVersion: source.toolkit.fluxcd.io/v1
|
||||
kind: GitRepository
|
||||
metadata:
|
||||
name: myapp
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 1m
|
||||
url: https://github.com/myorg/myapp-gitops
|
||||
ref:
|
||||
branch: main
|
||||
secretRef:
|
||||
name: git-credentials
|
||||
```
|
||||
|
||||
### Flux Kustomization
|
||||
```yaml
|
||||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||
kind: Kustomization
|
||||
metadata:
|
||||
name: myapp-production
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 5m
|
||||
path: ./kubernetes/overlays/production
|
||||
prune: true
|
||||
sourceRef:
|
||||
kind: GitRepository
|
||||
name: myapp
|
||||
healthChecks:
|
||||
- apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: myapp
|
||||
namespace: production
|
||||
timeout: 2m
|
||||
```
|
||||
|
||||
## Progressive Delivery
|
||||
|
||||
### Canary Deployment with Flagger
|
||||
```yaml
|
||||
apiVersion: flagger.app/v1beta1
|
||||
kind: Canary
|
||||
metadata:
|
||||
name: myapp
|
||||
namespace: production
|
||||
spec:
|
||||
targetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: myapp
|
||||
|
||||
service:
|
||||
port: 80
|
||||
targetPort: 8080
|
||||
|
||||
analysis:
|
||||
interval: 1m
|
||||
threshold: 5
|
||||
maxWeight: 50
|
||||
stepWeight: 10
|
||||
|
||||
metrics:
|
||||
- name: request-success-rate
|
||||
thresholdRange:
|
||||
min: 99
|
||||
interval: 1m
|
||||
|
||||
- name: request-duration
|
||||
thresholdRange:
|
||||
max: 500
|
||||
interval: 1m
|
||||
|
||||
webhooks:
|
||||
- name: load-test
|
||||
url: http://flagger-loadtester/
|
||||
timeout: 5s
|
||||
metadata:
|
||||
cmd: "hey -z 1m -q 10 -c 2 http://myapp-canary.production/"
|
||||
|
||||
- name: smoke-test
|
||||
url: http://flagger-loadtester/
|
||||
timeout: 5s
|
||||
metadata:
|
||||
type: smoke
|
||||
cmd: "curl -s http://myapp-canary.production/healthz | grep ok"
|
||||
```
|
||||
|
||||
### Blue/Green Deployment
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: myapp
|
||||
namespace: production
|
||||
spec:
|
||||
selector:
|
||||
app: myapp
|
||||
version: blue # Switch to 'green' for deployment
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8080
|
||||
|
||||
---
|
||||
# Blue Deployment
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myapp-blue
|
||||
namespace: production
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: myapp
|
||||
version: blue
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: myapp
|
||||
version: blue
|
||||
spec:
|
||||
containers:
|
||||
- name: myapp
|
||||
image: myapp:v1.0.0
|
||||
|
||||
---
|
||||
# Green Deployment
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myapp-green
|
||||
namespace: production
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: myapp
|
||||
version: green
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: myapp
|
||||
version: green
|
||||
spec:
|
||||
containers:
|
||||
- name: myapp
|
||||
image: myapp:v2.0.0
|
||||
```
|
||||
|
||||
## CI/CD Pipeline Examples
|
||||
|
||||
### GitHub Actions - Complete Pipeline
|
||||
See [templates.md](templates.md) for full GitHub Actions pipeline
|
||||
|
||||
### GitLab CI Pipeline
|
||||
```yaml
|
||||
variables:
|
||||
DOCKER_DRIVER: overlay2
|
||||
DOCKER_TLS_CERTDIR: "/certs"
|
||||
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
|
||||
|
||||
stages:
|
||||
- lint
|
||||
- build
|
||||
- test
|
||||
- security
|
||||
- deploy
|
||||
|
||||
# Lint stage
|
||||
lint:code:
|
||||
stage: lint
|
||||
image: node:20-alpine
|
||||
script:
|
||||
- npm ci
|
||||
- npm run lint
|
||||
cache:
|
||||
key: ${CI_COMMIT_REF_SLUG}
|
||||
paths:
|
||||
- node_modules/
|
||||
|
||||
lint:dockerfile:
|
||||
stage: lint
|
||||
image: hadolint/hadolint:latest-alpine
|
||||
script:
|
||||
- hadolint Dockerfile
|
||||
|
||||
# Build stage
|
||||
build:image:
|
||||
stage: build
|
||||
image: docker:24
|
||||
services:
|
||||
- docker:24-dind
|
||||
before_script:
|
||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||
script:
|
||||
- docker build --cache-from $CI_REGISTRY_IMAGE:latest -t $IMAGE_TAG .
|
||||
- docker tag $IMAGE_TAG $CI_REGISTRY_IMAGE:latest
|
||||
- docker push $IMAGE_TAG
|
||||
- docker push $CI_REGISTRY_IMAGE:latest
|
||||
only:
|
||||
- main
|
||||
- develop
|
||||
|
||||
# Test stage
|
||||
test:unit:
|
||||
stage: test
|
||||
image: node:20-alpine
|
||||
script:
|
||||
- npm ci
|
||||
- npm run test:unit
|
||||
coverage: '/Statements\s*:\s*(\d+\.\d+)%/'
|
||||
artifacts:
|
||||
reports:
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: coverage/cobertura-coverage.xml
|
||||
|
||||
test:integration:
|
||||
stage: test
|
||||
image: $IMAGE_TAG
|
||||
services:
|
||||
- postgres:15-alpine
|
||||
variables:
|
||||
POSTGRES_DB: testdb
|
||||
POSTGRES_USER: testuser
|
||||
POSTGRES_PASSWORD: testpass
|
||||
script:
|
||||
- npm run test:integration
|
||||
|
||||
# Security stage
|
||||
security:trivy:
|
||||
stage: security
|
||||
image: aquasec/trivy:latest
|
||||
script:
|
||||
- trivy image --exit-code 1 --severity HIGH,CRITICAL $IMAGE_TAG
|
||||
allow_failure: true
|
||||
|
||||
security:secrets:
|
||||
stage: security
|
||||
image: trufflesecurity/trufflehog:latest
|
||||
script:
|
||||
- trufflehog filesystem . --fail
|
||||
|
||||
# Deploy stage
|
||||
deploy:dev:
|
||||
stage: deploy
|
||||
image: bitnami/kubectl:latest
|
||||
script:
|
||||
- kubectl config use-context dev-cluster
|
||||
- kubectl set image deployment/myapp myapp=$IMAGE_TAG -n development
|
||||
- kubectl rollout status deployment/myapp -n development
|
||||
environment:
|
||||
name: development
|
||||
only:
|
||||
- develop
|
||||
|
||||
deploy:staging:
|
||||
stage: deploy
|
||||
image: bitnami/kubectl:latest
|
||||
script:
|
||||
- kubectl config use-context staging-cluster
|
||||
- kubectl set image deployment/myapp myapp=$IMAGE_TAG -n staging
|
||||
- kubectl rollout status deployment/myapp -n staging
|
||||
environment:
|
||||
name: staging
|
||||
only:
|
||||
- main
|
||||
|
||||
deploy:production:
|
||||
stage: deploy
|
||||
image: bitnami/kubectl:latest
|
||||
script:
|
||||
- kubectl config use-context prod-cluster
|
||||
- kubectl set image deployment/myapp myapp=$IMAGE_TAG -n production
|
||||
- kubectl rollout status deployment/myapp -n production
|
||||
environment:
|
||||
name: production
|
||||
when: manual
|
||||
only:
|
||||
- main
|
||||
```
|
||||
|
||||
### Jenkins Pipeline (Jenkinsfile)
|
||||
```groovy
|
||||
pipeline {
|
||||
agent any
|
||||
|
||||
environment {
|
||||
DOCKER_REGISTRY = 'ghcr.io'
|
||||
IMAGE_NAME = 'myorg/myapp'
|
||||
IMAGE_TAG = "${env.GIT_COMMIT.take(7)}"
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
|
||||
stage('Lint') {
|
||||
parallel {
|
||||
stage('Lint Code') {
|
||||
steps {
|
||||
sh 'npm ci'
|
||||
sh 'npm run lint'
|
||||
}
|
||||
}
|
||||
stage('Lint Dockerfile') {
|
||||
steps {
|
||||
sh 'hadolint Dockerfile'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build') {
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-credentials') {
|
||||
def app = docker.build("${IMAGE_NAME}:${IMAGE_TAG}")
|
||||
app.push()
|
||||
app.push('latest')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Test') {
|
||||
parallel {
|
||||
stage('Unit Tests') {
|
||||
steps {
|
||||
sh 'npm run test:unit'
|
||||
}
|
||||
}
|
||||
stage('Integration Tests') {
|
||||
steps {
|
||||
sh 'npm run test:integration'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Security Scan') {
|
||||
steps {
|
||||
sh "trivy image --exit-code 1 --severity HIGH,CRITICAL ${IMAGE_NAME}:${IMAGE_TAG}"
|
||||
}
|
||||
}
|
||||
|
||||
stage('Deploy to Dev') {
|
||||
when {
|
||||
branch 'develop'
|
||||
}
|
||||
steps {
|
||||
kubernetesDeploy(
|
||||
configs: 'kubernetes/overlays/dev',
|
||||
kubeconfigId: 'dev-kubeconfig'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
stage('Deploy to Production') {
|
||||
when {
|
||||
branch 'main'
|
||||
}
|
||||
steps {
|
||||
input message: 'Deploy to production?', ok: 'Deploy'
|
||||
kubernetesDeploy(
|
||||
configs: 'kubernetes/overlays/prod',
|
||||
kubeconfigId: 'prod-kubeconfig'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
success {
|
||||
slackSend(
|
||||
color: 'good',
|
||||
message: "✅ Build ${env.BUILD_NUMBER} succeeded: ${env.JOB_NAME}"
|
||||
)
|
||||
}
|
||||
failure {
|
||||
slackSend(
|
||||
color: 'danger',
|
||||
message: "❌ Build ${env.BUILD_NUMBER} failed: ${env.JOB_NAME}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Infrastructure Testing
|
||||
|
||||
### Terratest (Go)
|
||||
```go
|
||||
package test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"github.com/gruntwork-io/terratest/modules/terraform"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTerraformVPCModule(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
|
||||
TerraformDir: "../modules/vpc",
|
||||
Vars: map[string]interface{}{
|
||||
"name_prefix": "test",
|
||||
"vpc_cidr": "10.0.0.0/16",
|
||||
},
|
||||
})
|
||||
|
||||
defer terraform.Destroy(t, terraformOptions)
|
||||
|
||||
terraform.InitAndApply(t, terraformOptions)
|
||||
|
||||
vpcID := terraform.Output(t, terraformOptions, "vpc_id")
|
||||
assert.NotEmpty(t, vpcID)
|
||||
}
|
||||
```
|
||||
|
||||
### Checkov (Infrastructure Security)
|
||||
```bash
|
||||
# Scan Terraform
|
||||
checkov -d terraform/ --framework terraform
|
||||
|
||||
# Scan Kubernetes
|
||||
checkov -d kubernetes/ --framework kubernetes
|
||||
|
||||
# Output JSON
|
||||
checkov -d terraform/ --framework terraform -o json > results.json
|
||||
|
||||
# Skip specific checks
|
||||
checkov -d terraform/ --skip-check CKV_AWS_23
|
||||
```
|
||||
|
||||
### Kitchen-Terraform
|
||||
```ruby
|
||||
# kitchen.yml
|
||||
driver:
|
||||
name: terraform
|
||||
|
||||
provisioner:
|
||||
name: terraform
|
||||
|
||||
verifier:
|
||||
name: terraform
|
||||
systems:
|
||||
- name: default
|
||||
backend: aws
|
||||
controls:
|
||||
- vpc_test
|
||||
|
||||
platforms:
|
||||
- name: aws
|
||||
|
||||
suites:
|
||||
- name: default
|
||||
driver:
|
||||
variables:
|
||||
region: us-east-1
|
||||
```
|
||||
|
||||
## Deployment Strategies
|
||||
|
||||
### Rolling Update (Default in Kubernetes)
|
||||
```yaml
|
||||
spec:
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1 # Max pods above desired during update
|
||||
maxUnavailable: 0 # Zero-downtime deployment
|
||||
```
|
||||
|
||||
### Recreate (Downtime Acceptable)
|
||||
```yaml
|
||||
spec:
|
||||
strategy:
|
||||
type: Recreate # Kill all pods, then create new ones
|
||||
```
|
||||
|
||||
### Canary (Gradual Rollout)
|
||||
- Deploy new version alongside old
|
||||
- Route small % of traffic to new version
|
||||
- Monitor metrics
|
||||
- Gradually increase traffic
|
||||
- Rollback if issues detected
|
||||
|
||||
### Blue/Green (Instant Switch)
|
||||
- Deploy new version (green) alongside old (blue)
|
||||
- Test green environment
|
||||
- Switch traffic from blue to green
|
||||
- Keep blue for quick rollback
|
||||
|
||||
## Rollback Procedures
|
||||
|
||||
### Kubernetes Rollback
|
||||
```bash
|
||||
# View rollout history
|
||||
kubectl rollout history deployment/myapp -n production
|
||||
|
||||
# Rollback to previous version
|
||||
kubectl rollout undo deployment/myapp -n production
|
||||
|
||||
# Rollback to specific revision
|
||||
kubectl rollout undo deployment/myapp -n production --to-revision=3
|
||||
|
||||
# Check rollout status
|
||||
kubectl rollout status deployment/myapp -n production
|
||||
```
|
||||
|
||||
### ArgoCD Rollback
|
||||
```bash
|
||||
# Rollback application
|
||||
argocd app rollback myapp-production
|
||||
|
||||
# Rollback to specific revision
|
||||
argocd app rollback myapp-production 123
|
||||
```
|
||||
|
||||
## CI/CD Best Practices
|
||||
|
||||
1. **Automate Everything**: From code commit to production
|
||||
2. **Fast Feedback**: Fail fast, fix fast
|
||||
3. **Test in Production-like**: Staging mirrors production
|
||||
4. **Gradual Rollout**: Canary or blue/green for production
|
||||
5. **Easy Rollback**: One-click rollback capability
|
||||
6. **Security Scanning**: Every stage, every pipeline
|
||||
7. **Infrastructure as Code**: No manual changes
|
||||
8. **GitOps**: Git as single source of truth
|
||||
9. **Observability**: Monitor every deployment
|
||||
10. **Post-Deployment Tests**: Smoke tests after deploy
|
||||
|
||||
---
|
||||
|
||||
## Pipeline Performance Optimization
|
||||
|
||||
- **Caching**: Cache dependencies between runs
|
||||
- **Parallel Execution**: Run independent stages in parallel
|
||||
- **Docker Layer Caching**: Use BuildKit and layer caching
|
||||
- **Artifacts**: Share build artifacts between stages
|
||||
- **Resource Limits**: Optimize CI runner resources
|
||||
- **Skip Unnecessary**: Only run tests for changed code
|
||||
+695
@@ -0,0 +1,695 @@
|
||||
# Cloud Platforms: AWS, Azure, GCP
|
||||
|
||||
## Cloud Platform Selection
|
||||
|
||||
### AWS (Amazon Web Services)
|
||||
- **Strengths**: Market leader, most services, mature ecosystem
|
||||
- **Best for**: Enterprise, startups, wide service selection
|
||||
- **Key Services**: EC2, EKS, RDS, S3, Lambda, CloudFormation
|
||||
|
||||
### Azure (Microsoft Azure)
|
||||
- **Strengths**: Enterprise integration, hybrid cloud, Microsoft stack
|
||||
- **Best for**: Windows workloads, hybrid scenarios, Microsoft shops
|
||||
- **Key Services**: VMs, AKS, SQL Database, Blob Storage, ARM Templates
|
||||
|
||||
### GCP (Google Cloud Platform)
|
||||
- **Strengths**: Kubernetes-native, ML/AI, data analytics
|
||||
- **Best for**: Kubernetes, data processing, ML workloads
|
||||
- **Key Services**: Compute Engine, GKE, Cloud SQL, Cloud Storage, Deployment Manager
|
||||
|
||||
## AWS Architecture Patterns
|
||||
|
||||
### Multi-Tier Web Application
|
||||
```
|
||||
Internet
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ CloudFront │ CDN
|
||||
│ (Global) │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌────▼────┐
|
||||
│ ALB │ Load Balancer
|
||||
└────┬────┘
|
||||
│
|
||||
┌────▼──────────┐
|
||||
│ ECS/EKS │ Application Layer
|
||||
│ (Multi-AZ) │
|
||||
└────┬──────────┘
|
||||
│
|
||||
┌────▼────┐
|
||||
│ RDS │ Database Layer
|
||||
│ (Multi-AZ)│
|
||||
└─────────┘
|
||||
```
|
||||
|
||||
### AWS Well-Architected Framework Pillars
|
||||
|
||||
**1. Operational Excellence**
|
||||
- IaC (Terraform/CloudFormation)
|
||||
- CI/CD automation
|
||||
- Monitoring and observability
|
||||
|
||||
**2. Security**
|
||||
- IAM least privilege
|
||||
- Encryption at rest and in transit
|
||||
- Network segmentation (VPC, Security Groups)
|
||||
|
||||
**3. Reliability**
|
||||
- Multi-AZ deployment
|
||||
- Auto Scaling
|
||||
- Backup and disaster recovery
|
||||
|
||||
**4. Performance Efficiency**
|
||||
- Right-sizing instances
|
||||
- CloudFront for content delivery
|
||||
- ElastiCache for caching
|
||||
|
||||
**5. Cost Optimization**
|
||||
- Reserved Instances
|
||||
- Spot Instances
|
||||
- Auto Scaling based on demand
|
||||
|
||||
**6. Sustainability**
|
||||
- Region selection for renewable energy
|
||||
- Right-sizing to minimize waste
|
||||
|
||||
### AWS Core Services
|
||||
|
||||
#### Compute
|
||||
```hcl
|
||||
# EC2 Instance
|
||||
resource "aws_instance" "app" {
|
||||
ami = data.aws_ami.amazon_linux_2.id
|
||||
instance_type = "t3.medium"
|
||||
|
||||
vpc_security_group_ids = [aws_security_group.app.id]
|
||||
subnet_id = aws_subnet.private[0].id
|
||||
|
||||
iam_instance_profile = aws_iam_instance_profile.app.name
|
||||
|
||||
user_data = <<-EOF
|
||||
#!/bin/bash
|
||||
yum update -y
|
||||
yum install -y docker
|
||||
systemctl start docker
|
||||
EOF
|
||||
|
||||
tags = {
|
||||
Name = "${var.name_prefix}-app-server"
|
||||
}
|
||||
}
|
||||
|
||||
# ECS Fargate
|
||||
resource "aws_ecs_service" "app" {
|
||||
name = "${var.name_prefix}-service"
|
||||
cluster = aws_ecs_cluster.main.id
|
||||
task_definition = aws_ecs_task_definition.app.arn
|
||||
desired_count = 3
|
||||
|
||||
launch_type = "FARGATE"
|
||||
|
||||
network_configuration {
|
||||
subnets = aws_subnet.private[*].id
|
||||
security_groups = [aws_security_group.app.id]
|
||||
assign_public_ip = false
|
||||
}
|
||||
|
||||
load_balancer {
|
||||
target_group_arn = aws_lb_target_group.app.arn
|
||||
container_name = "app"
|
||||
container_port = 8080
|
||||
}
|
||||
}
|
||||
|
||||
# Lambda Function
|
||||
resource "aws_lambda_function" "processor" {
|
||||
filename = "lambda.zip"
|
||||
function_name = "${var.name_prefix}-processor"
|
||||
role = aws_iam_role.lambda.arn
|
||||
handler = "index.handler"
|
||||
runtime = "nodejs20.x"
|
||||
|
||||
environment {
|
||||
variables = {
|
||||
ENV = var.environment
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Storage
|
||||
```hcl
|
||||
# S3 Bucket
|
||||
resource "aws_s3_bucket" "data" {
|
||||
bucket = "${var.name_prefix}-data-bucket"
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_versioning" "data" {
|
||||
bucket = aws_s3_bucket.data.id
|
||||
|
||||
versioning_configuration {
|
||||
status = "Enabled"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
|
||||
bucket = aws_s3_bucket.data.id
|
||||
|
||||
rule {
|
||||
apply_server_side_encryption_by_default {
|
||||
sse_algorithm = "aws:kms"
|
||||
kms_master_key_id = aws_kms_key.s3.arn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# EBS Volume
|
||||
resource "aws_ebs_volume" "data" {
|
||||
availability_zone = var.availability_zone
|
||||
size = 100
|
||||
type = "gp3"
|
||||
encrypted = true
|
||||
kms_key_id = aws_kms_key.ebs.arn
|
||||
|
||||
tags = {
|
||||
Name = "${var.name_prefix}-data-volume"
|
||||
}
|
||||
}
|
||||
|
||||
# EFS File System
|
||||
resource "aws_efs_file_system" "shared" {
|
||||
encrypted = true
|
||||
kms_key_id = aws_kms_key.efs.arn
|
||||
|
||||
lifecycle_policy {
|
||||
transition_to_ia = "AFTER_30_DAYS"
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "${var.name_prefix}-efs"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Database
|
||||
```hcl
|
||||
# RDS PostgreSQL
|
||||
resource "aws_db_instance" "main" {
|
||||
identifier = "${var.name_prefix}-db"
|
||||
|
||||
engine = "postgres"
|
||||
engine_version = "15.4"
|
||||
instance_class = "db.t3.medium"
|
||||
|
||||
allocated_storage = 100
|
||||
max_allocated_storage = 1000
|
||||
storage_type = "gp3"
|
||||
storage_encrypted = true
|
||||
|
||||
multi_az = true
|
||||
db_subnet_group_name = aws_db_subnet_group.main.name
|
||||
vpc_security_group_ids = [aws_security_group.db.id]
|
||||
|
||||
backup_retention_period = 7
|
||||
backup_window = "03:00-04:00"
|
||||
maintenance_window = "sun:04:00-sun:05:00"
|
||||
|
||||
enabled_cloudwatch_logs_exports = ["postgresql"]
|
||||
|
||||
deletion_protection = var.environment == "prod"
|
||||
}
|
||||
|
||||
# DynamoDB Table
|
||||
resource "aws_dynamodb_table" "sessions" {
|
||||
name = "${var.name_prefix}-sessions"
|
||||
billing_mode = "PAY_PER_REQUEST"
|
||||
hash_key = "session_id"
|
||||
|
||||
attribute {
|
||||
name = "session_id"
|
||||
type = "S"
|
||||
}
|
||||
|
||||
ttl {
|
||||
attribute_name = "ttl"
|
||||
enabled = true
|
||||
}
|
||||
|
||||
point_in_time_recovery {
|
||||
enabled = true
|
||||
}
|
||||
|
||||
server_side_encryption {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Azure Architecture Patterns
|
||||
|
||||
### Multi-Tier Application on Azure
|
||||
```
|
||||
Internet
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Azure Front │ CDN + WAF
|
||||
│ Door │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌────▼────────┐
|
||||
│ App GW │ Load Balancer
|
||||
└────┬────────┘
|
||||
│
|
||||
┌────▼──────────┐
|
||||
│ AKS │ Application Layer
|
||||
│ (Multi-Zone) │
|
||||
└────┬──────────┘
|
||||
│
|
||||
┌────▼──────────┐
|
||||
│ Azure SQL DB │ Database Layer
|
||||
│ (Geo-Replica) │
|
||||
└───────────────┘
|
||||
```
|
||||
|
||||
### Azure Core Services
|
||||
|
||||
#### Compute
|
||||
```hcl
|
||||
# Virtual Machine
|
||||
resource "azurerm_linux_virtual_machine" "app" {
|
||||
name = "${var.name_prefix}-vm"
|
||||
resource_group_name = azurerm_resource_group.main.name
|
||||
location = azurerm_resource_group.main.location
|
||||
size = "Standard_D2s_v3"
|
||||
|
||||
network_interface_ids = [
|
||||
azurerm_network_interface.app.id,
|
||||
]
|
||||
|
||||
os_disk {
|
||||
caching = "ReadWrite"
|
||||
storage_account_type = "Premium_LRS"
|
||||
}
|
||||
|
||||
source_image_reference {
|
||||
publisher = "Canonical"
|
||||
offer = "0001-com-ubuntu-server-focal"
|
||||
sku = "20_04-lts"
|
||||
version = "latest"
|
||||
}
|
||||
|
||||
admin_username = "azureuser"
|
||||
admin_ssh_key {
|
||||
username = "azureuser"
|
||||
public_key = file("~/.ssh/id_rsa.pub")
|
||||
}
|
||||
}
|
||||
|
||||
# AKS Cluster
|
||||
resource "azurerm_kubernetes_cluster" "main" {
|
||||
name = "${var.name_prefix}-aks"
|
||||
location = azurerm_resource_group.main.location
|
||||
resource_group_name = azurerm_resource_group.main.name
|
||||
dns_prefix = var.name_prefix
|
||||
|
||||
default_node_pool {
|
||||
name = "default"
|
||||
node_count = 3
|
||||
vm_size = "Standard_D2s_v3"
|
||||
os_disk_size_gb = 100
|
||||
}
|
||||
|
||||
identity {
|
||||
type = "SystemAssigned"
|
||||
}
|
||||
|
||||
network_profile {
|
||||
network_plugin = "azure"
|
||||
network_policy = "calico"
|
||||
}
|
||||
}
|
||||
|
||||
# Azure Functions
|
||||
resource "azurerm_linux_function_app" "processor" {
|
||||
name = "${var.name_prefix}-func"
|
||||
resource_group_name = azurerm_resource_group.main.name
|
||||
location = azurerm_resource_group.main.location
|
||||
|
||||
storage_account_name = azurerm_storage_account.func.name
|
||||
storage_account_access_key = azurerm_storage_account.func.primary_access_key
|
||||
service_plan_id = azurerm_service_plan.func.id
|
||||
|
||||
site_config {
|
||||
application_stack {
|
||||
node_version = "20"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## GCP Architecture Patterns
|
||||
|
||||
### Multi-Tier Application on GCP
|
||||
```
|
||||
Internet
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Cloud CDN │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌────▼────────┐
|
||||
│ Cloud LB │ Global Load Balancer
|
||||
└────┬────────┘
|
||||
│
|
||||
┌────▼──────────┐
|
||||
│ GKE │ Application Layer
|
||||
│ (Multi-Zone) │
|
||||
└────┬──────────┘
|
||||
│
|
||||
┌────▼──────────┐
|
||||
│ Cloud SQL │ Database Layer
|
||||
│ (HA) │
|
||||
└───────────────┘
|
||||
```
|
||||
|
||||
### GCP Core Services
|
||||
|
||||
#### Compute
|
||||
```hcl
|
||||
# Compute Engine Instance
|
||||
resource "google_compute_instance" "app" {
|
||||
name = "${var.name_prefix}-vm"
|
||||
machine_type = "e2-medium"
|
||||
zone = var.zone
|
||||
|
||||
boot_disk {
|
||||
initialize_params {
|
||||
image = "ubuntu-os-cloud/ubuntu-2004-lts"
|
||||
size = 50
|
||||
type = "pd-ssd"
|
||||
}
|
||||
}
|
||||
|
||||
network_interface {
|
||||
network = google_compute_network.vpc.name
|
||||
subnetwork = google_compute_subnetwork.private.name
|
||||
|
||||
access_config {
|
||||
// Ephemeral public IP
|
||||
}
|
||||
}
|
||||
|
||||
metadata_startup_script = file("startup.sh")
|
||||
|
||||
service_account {
|
||||
scopes = ["cloud-platform"]
|
||||
}
|
||||
}
|
||||
|
||||
# GKE Cluster
|
||||
resource "google_container_cluster" "main" {
|
||||
name = "${var.name_prefix}-gke"
|
||||
location = var.region
|
||||
|
||||
# Remove default node pool
|
||||
remove_default_node_pool = true
|
||||
initial_node_count = 1
|
||||
|
||||
network = google_compute_network.vpc.name
|
||||
subnetwork = google_compute_subnetwork.private.name
|
||||
|
||||
ip_allocation_policy {
|
||||
cluster_ipv4_cidr_block = "/16"
|
||||
services_ipv4_cidr_block = "/22"
|
||||
}
|
||||
|
||||
workload_identity_config {
|
||||
workload_pool = "${var.project_id}.svc.id.goog"
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_container_node_pool" "main" {
|
||||
name = "main-pool"
|
||||
location = var.region
|
||||
cluster = google_container_cluster.main.name
|
||||
node_count = 1
|
||||
|
||||
autoscaling {
|
||||
min_node_count = 1
|
||||
max_node_count = 10
|
||||
}
|
||||
|
||||
node_config {
|
||||
machine_type = "e2-medium"
|
||||
disk_size_gb = 100
|
||||
disk_type = "pd-standard"
|
||||
|
||||
oauth_scopes = [
|
||||
"https://www.googleapis.com/auth/cloud-platform"
|
||||
]
|
||||
|
||||
workload_metadata_config {
|
||||
mode = "GKE_METADATA"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Multi-Cloud Strategy
|
||||
|
||||
### When to Use Multi-Cloud
|
||||
✓ **Avoid vendor lock-in**
|
||||
✓ **Leverage best-of-breed services**
|
||||
✓ **Geographic requirements**
|
||||
✓ **Disaster recovery**
|
||||
|
||||
### Multi-Cloud Challenges
|
||||
✗ Increased complexity
|
||||
✗ Higher operational overhead
|
||||
✗ Different APIs and tools
|
||||
✗ Data transfer costs
|
||||
|
||||
### Multi-Cloud Tools
|
||||
- **Terraform**: Unified IaC across clouds
|
||||
- **Kubernetes**: Consistent compute layer
|
||||
- **Service Mesh**: Unified networking
|
||||
- **OpenTelemetry**: Unified observability
|
||||
|
||||
## Cost Optimization Strategies
|
||||
|
||||
### AWS Cost Optimization
|
||||
```hcl
|
||||
# Savings Plans / Reserved Instances
|
||||
# Purchase via AWS Console or API
|
||||
|
||||
# Spot Instances
|
||||
resource "aws_autoscaling_group" "app" {
|
||||
mixed_instances_policy {
|
||||
instances_distribution {
|
||||
on_demand_base_capacity = 1
|
||||
on_demand_percentage_above_base_capacity = 20
|
||||
spot_allocation_strategy = "capacity-optimized"
|
||||
}
|
||||
|
||||
launch_template {
|
||||
launch_template_specification {
|
||||
launch_template_id = aws_launch_template.app.id
|
||||
}
|
||||
|
||||
override {
|
||||
instance_type = "t3.medium"
|
||||
}
|
||||
override {
|
||||
instance_type = "t3a.medium"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# S3 Lifecycle Policy
|
||||
resource "aws_s3_bucket_lifecycle_configuration" "data" {
|
||||
bucket = aws_s3_bucket.data.id
|
||||
|
||||
rule {
|
||||
id = "archive-old-data"
|
||||
status = "Enabled"
|
||||
|
||||
transition {
|
||||
days = 30
|
||||
storage_class = "STANDARD_IA"
|
||||
}
|
||||
|
||||
transition {
|
||||
days = 90
|
||||
storage_class = "GLACIER"
|
||||
}
|
||||
|
||||
expiration {
|
||||
days = 365
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Cost Monitoring
|
||||
```hcl
|
||||
# AWS Budget
|
||||
resource "aws_budgets_budget" "monthly" {
|
||||
name = "monthly-budget"
|
||||
budget_type = "COST"
|
||||
limit_amount = "1000"
|
||||
limit_unit = "USD"
|
||||
time_unit = "MONTHLY"
|
||||
|
||||
notification {
|
||||
comparison_operator = "GREATER_THAN"
|
||||
threshold = 80
|
||||
threshold_type = "PERCENTAGE"
|
||||
notification_type = "FORECASTED"
|
||||
subscriber_email_addresses = ["devops@example.com"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Disaster Recovery
|
||||
|
||||
### RTO/RPO Targets
|
||||
- **RTO (Recovery Time Objective)**: Maximum acceptable downtime
|
||||
- **RPO (Recovery Point Objective)**: Maximum acceptable data loss
|
||||
|
||||
### DR Strategies (Lowest to Highest Cost)
|
||||
|
||||
**1. Backup & Restore (RPO: hours, RTO: hours)**
|
||||
- Regular backups to cloud storage
|
||||
- Restore when needed
|
||||
|
||||
**2. Pilot Light (RPO: minutes, RTO: hours)**
|
||||
- Minimal infrastructure always running
|
||||
- Scale up when needed
|
||||
|
||||
**3. Warm Standby (RPO: seconds, RTO: minutes)**
|
||||
- Scaled-down version running
|
||||
- Scale up for failover
|
||||
|
||||
**4. Multi-Site Active/Active (RPO: none, RTO: none)**
|
||||
- Full capacity in multiple regions
|
||||
- Traffic distributed across sites
|
||||
|
||||
### Multi-Region Setup (AWS)
|
||||
```hcl
|
||||
provider "aws" {
|
||||
alias = "primary"
|
||||
region = "us-east-1"
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
alias = "dr"
|
||||
region = "us-west-2"
|
||||
}
|
||||
|
||||
# Primary region resources
|
||||
module "vpc_primary" {
|
||||
source = "./modules/vpc"
|
||||
providers = {
|
||||
aws = aws.primary
|
||||
}
|
||||
}
|
||||
|
||||
# DR region resources
|
||||
module "vpc_dr" {
|
||||
source = "./modules/vpc"
|
||||
providers = {
|
||||
aws = aws.dr
|
||||
}
|
||||
}
|
||||
|
||||
# Route53 health check and failover
|
||||
resource "aws_route53_health_check" "primary" {
|
||||
fqdn = aws_lb.primary.dns_name
|
||||
port = 443
|
||||
type = "HTTPS"
|
||||
resource_path = "/health"
|
||||
failure_threshold = "3"
|
||||
request_interval = "30"
|
||||
}
|
||||
|
||||
resource "aws_route53_record" "app" {
|
||||
zone_id = aws_route53_zone.main.id
|
||||
name = "app.example.com"
|
||||
type = "A"
|
||||
|
||||
set_identifier = "primary"
|
||||
failover_routing_policy {
|
||||
type = "PRIMARY"
|
||||
}
|
||||
|
||||
alias {
|
||||
name = aws_lb.primary.dns_name
|
||||
zone_id = aws_lb.primary.zone_id
|
||||
evaluate_target_health = true
|
||||
}
|
||||
|
||||
health_check_id = aws_route53_health_check.primary.id
|
||||
}
|
||||
|
||||
resource "aws_route53_record" "app_dr" {
|
||||
zone_id = aws_route53_zone.main.id
|
||||
name = "app.example.com"
|
||||
type = "A"
|
||||
|
||||
set_identifier = "secondary"
|
||||
failover_routing_policy {
|
||||
type = "SECONDARY"
|
||||
}
|
||||
|
||||
alias {
|
||||
name = aws_lb.dr.dns_name
|
||||
zone_id = aws_lb.dr.zone_id
|
||||
evaluate_target_health = true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices Summary
|
||||
|
||||
### AWS
|
||||
- Use IAM roles, not access keys
|
||||
- Enable CloudTrail in all regions
|
||||
- Encrypt everything (S3, EBS, RDS)
|
||||
- Use VPC for network isolation
|
||||
- Tag all resources for cost allocation
|
||||
|
||||
### Azure
|
||||
- Use Managed Identities
|
||||
- Enable Azure Policy for governance
|
||||
- Use Azure Key Vault for secrets
|
||||
- Implement RBAC
|
||||
- Use Resource Groups for organization
|
||||
|
||||
### GCP
|
||||
- Use Service Accounts with least privilege
|
||||
- Enable Cloud Audit Logs
|
||||
- Use VPC Service Controls
|
||||
- Implement Organization Policies
|
||||
- Use Labels for resource management
|
||||
|
||||
---
|
||||
|
||||
## Cloud Service Comparison
|
||||
|
||||
| Service Type | AWS | Azure | GCP |
|
||||
|--------------|-----|-------|-----|
|
||||
| Compute | EC2 | Virtual Machines | Compute Engine |
|
||||
| Containers | ECS, EKS | AKS | GKE |
|
||||
| Serverless | Lambda | Functions | Cloud Functions |
|
||||
| Storage | S3 | Blob Storage | Cloud Storage |
|
||||
| Database (SQL) | RDS | SQL Database | Cloud SQL |
|
||||
| Database (NoSQL) | DynamoDB | Cosmos DB | Firestore |
|
||||
| Networking | VPC | Virtual Network | VPC |
|
||||
| Load Balancer | ALB/NLB | App Gateway | Cloud Load Balancing |
|
||||
| CDN | CloudFront | Front Door | Cloud CDN |
|
||||
| IAM | IAM | Azure AD | Cloud IAM |
|
||||
@@ -0,0 +1,994 @@
|
||||
# Kubernetes & Container Orchestration
|
||||
|
||||
## Kubernetes Architecture Essentials
|
||||
|
||||
### Core Components
|
||||
- **Control Plane**: API Server, Scheduler, Controller Manager, etcd
|
||||
- **Worker Nodes**: Kubelet, Kube-proxy, Container Runtime
|
||||
- **Add-ons**: CoreDNS, Metrics Server, Ingress Controller
|
||||
|
||||
### Key Kubernetes Resources
|
||||
- **Workloads**: Pods, Deployments, StatefulSets, DaemonSets, Jobs, CronJobs
|
||||
- **Networking**: Services, Ingress, NetworkPolicies
|
||||
- **Configuration**: ConfigMaps, Secrets
|
||||
- **Storage**: PersistentVolumes, PersistentVolumeClaims, StorageClasses
|
||||
- **Access Control**: ServiceAccounts, Roles, RoleBindings, ClusterRoles, ClusterRoleBindings
|
||||
|
||||
## Production-Ready Deployment Pattern
|
||||
|
||||
### Deployment with Best Practices
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myapp
|
||||
namespace: production
|
||||
labels:
|
||||
app: myapp
|
||||
version: v1.0.0
|
||||
environment: production
|
||||
spec:
|
||||
replicas: 3
|
||||
revisionHistoryLimit: 10
|
||||
|
||||
# Deployment strategy
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0 # Zero-downtime deployment
|
||||
|
||||
selector:
|
||||
matchLabels:
|
||||
app: myapp
|
||||
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: myapp
|
||||
version: v1.0.0
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "8080"
|
||||
prometheus.io/path: "/metrics"
|
||||
|
||||
spec:
|
||||
# Security context at pod level
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
fsGroup: 2000
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
# Service account for pod identity
|
||||
serviceAccountName: myapp
|
||||
|
||||
# Init container for setup tasks
|
||||
initContainers:
|
||||
- name: init-config
|
||||
image: busybox:1.36
|
||||
command: ['sh', '-c', 'echo Initializing... && sleep 2']
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
containers:
|
||||
- name: myapp
|
||||
image: myapp:1.0.0
|
||||
imagePullPolicy: IfNotPresent
|
||||
|
||||
# Resource limits and requests
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
|
||||
# Container security
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
# Health checks
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 8080
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ready
|
||||
port: 8080
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
successThreshold: 1
|
||||
failureThreshold: 3
|
||||
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /startup
|
||||
port: 8080
|
||||
initialDelaySeconds: 0
|
||||
periodSeconds: 10
|
||||
failureThreshold: 30 # 5 minutes max startup time
|
||||
|
||||
# Environment variables
|
||||
env:
|
||||
- name: ENV
|
||||
value: "production"
|
||||
- name: LOG_LEVEL
|
||||
value: "info"
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
- name: POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
|
||||
# Environment from ConfigMap
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: myapp-config
|
||||
- secretRef:
|
||||
name: myapp-secrets
|
||||
|
||||
# Container ports
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
- name: metrics
|
||||
containerPort: 9090
|
||||
protocol: TCP
|
||||
|
||||
# Volume mounts
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/myapp
|
||||
readOnly: true
|
||||
- name: secrets
|
||||
mountPath: /etc/secrets
|
||||
readOnly: true
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
- name: cache
|
||||
mountPath: /var/cache
|
||||
|
||||
# Volumes
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: myapp-config
|
||||
- name: secrets
|
||||
secret:
|
||||
secretName: myapp-secrets
|
||||
defaultMode: 0400
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: cache
|
||||
emptyDir: {}
|
||||
|
||||
# Pod scheduling
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchExpressions:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- myapp
|
||||
topologyKey: kubernetes.io/hostname
|
||||
|
||||
# Tolerations for node taints
|
||||
tolerations:
|
||||
- key: "node-role.kubernetes.io/spot"
|
||||
operator: "Exists"
|
||||
effect: "NoSchedule"
|
||||
```
|
||||
|
||||
### Service Configuration
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: myapp
|
||||
namespace: production
|
||||
labels:
|
||||
app: myapp
|
||||
annotations:
|
||||
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
selector:
|
||||
app: myapp
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 8080
|
||||
protocol: TCP
|
||||
- name: https
|
||||
port: 443
|
||||
targetPort: 8443
|
||||
protocol: TCP
|
||||
|
||||
# Session affinity (optional)
|
||||
sessionAffinity: ClientIP
|
||||
sessionAffinityConfig:
|
||||
clientIP:
|
||||
timeoutSeconds: 10800
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: myapp-headless
|
||||
namespace: production
|
||||
spec:
|
||||
clusterIP: None # Headless service for StatefulSets
|
||||
selector:
|
||||
app: myapp
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
```
|
||||
|
||||
### Ingress with TLS
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: myapp
|
||||
namespace: production
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: nginx
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||
nginx.ingress.kubernetes.io/rate-limit: "100"
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- myapp.example.com
|
||||
secretName: myapp-tls
|
||||
|
||||
rules:
|
||||
- host: myapp.example.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: myapp
|
||||
port:
|
||||
number: 8080
|
||||
```
|
||||
|
||||
## Configuration Management
|
||||
|
||||
### ConfigMap
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: myapp-config
|
||||
namespace: production
|
||||
data:
|
||||
# Simple key-value pairs
|
||||
app.env: "production"
|
||||
log.level: "info"
|
||||
|
||||
# Multi-line configuration files
|
||||
application.yaml: |
|
||||
server:
|
||||
port: 8080
|
||||
host: 0.0.0.0
|
||||
database:
|
||||
max_connections: 100
|
||||
timeout: 30s
|
||||
cache:
|
||||
ttl: 3600
|
||||
max_size: 1000
|
||||
```
|
||||
|
||||
### Secrets Management
|
||||
```yaml
|
||||
# ❌ NOT RECOMMENDED - Base64 is not encryption!
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: myapp-secrets
|
||||
namespace: production
|
||||
type: Opaque
|
||||
data:
|
||||
db-password: cGFzc3dvcmQxMjM= # base64 encoded
|
||||
|
||||
---
|
||||
# ✅ BETTER - Use Sealed Secrets
|
||||
apiVersion: bitnami.com/v1alpha1
|
||||
kind: SealedSecret
|
||||
metadata:
|
||||
name: myapp-secrets
|
||||
namespace: production
|
||||
spec:
|
||||
encryptedData:
|
||||
db-password: AgBqV7zJ8... # Encrypted with controller's public key
|
||||
|
||||
---
|
||||
# ✅ BEST - Use External Secrets Operator with AWS Secrets Manager
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: myapp-secrets
|
||||
namespace: production
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
name: aws-secrets-manager
|
||||
kind: SecretStore
|
||||
|
||||
target:
|
||||
name: myapp-secrets
|
||||
creationPolicy: Owner
|
||||
|
||||
data:
|
||||
- secretKey: db-password
|
||||
remoteRef:
|
||||
key: prod/myapp/db-password
|
||||
- secretKey: api-key
|
||||
remoteRef:
|
||||
key: prod/myapp/api-key
|
||||
```
|
||||
|
||||
## StatefulSets for Stateful Applications
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: postgres
|
||||
namespace: production
|
||||
spec:
|
||||
serviceName: postgres-headless
|
||||
replicas: 3
|
||||
|
||||
selector:
|
||||
matchLabels:
|
||||
app: postgres
|
||||
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: postgres
|
||||
spec:
|
||||
securityContext:
|
||||
fsGroup: 999
|
||||
runAsUser: 999
|
||||
|
||||
containers:
|
||||
- name: postgres
|
||||
image: postgres:15-alpine
|
||||
|
||||
env:
|
||||
- name: POSTGRES_DB
|
||||
value: myapp
|
||||
- name: POSTGRES_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-secrets
|
||||
key: username
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-secrets
|
||||
key: password
|
||||
- name: PGDATA
|
||||
value: /var/lib/postgresql/data/pgdata
|
||||
|
||||
ports:
|
||||
- name: postgres
|
||||
containerPort: 5432
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "1000m"
|
||||
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- pg_isready -U $POSTGRES_USER -d $POSTGRES_DB
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- pg_isready -U $POSTGRES_USER -d $POSTGRES_DB
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /var/lib/postgresql/data
|
||||
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: data
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
storageClassName: gp3
|
||||
resources:
|
||||
requests:
|
||||
storage: 100Gi
|
||||
```
|
||||
|
||||
## Autoscaling
|
||||
|
||||
### Horizontal Pod Autoscaler (HPA)
|
||||
```yaml
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: myapp-hpa
|
||||
namespace: production
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: myapp
|
||||
|
||||
minReplicas: 3
|
||||
maxReplicas: 20
|
||||
|
||||
metrics:
|
||||
# CPU-based scaling
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
|
||||
# Memory-based scaling
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
|
||||
# Custom metrics (requires metrics adapter)
|
||||
- type: Pods
|
||||
pods:
|
||||
metric:
|
||||
name: http_requests_per_second
|
||||
target:
|
||||
type: AverageValue
|
||||
averageValue: "1000"
|
||||
|
||||
behavior:
|
||||
scaleDown:
|
||||
stabilizationWindowSeconds: 300
|
||||
policies:
|
||||
- type: Percent
|
||||
value: 50
|
||||
periodSeconds: 60
|
||||
scaleUp:
|
||||
stabilizationWindowSeconds: 0
|
||||
policies:
|
||||
- type: Percent
|
||||
value: 100
|
||||
periodSeconds: 15
|
||||
- type: Pods
|
||||
value: 4
|
||||
periodSeconds: 15
|
||||
selectPolicy: Max
|
||||
```
|
||||
|
||||
### Vertical Pod Autoscaler (VPA)
|
||||
```yaml
|
||||
apiVersion: autoscaling.k8s.io/v1
|
||||
kind: VerticalPodAutoscaler
|
||||
metadata:
|
||||
name: myapp-vpa
|
||||
namespace: production
|
||||
spec:
|
||||
targetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: myapp
|
||||
|
||||
updatePolicy:
|
||||
updateMode: "Auto" # "Off", "Initial", "Recreate", or "Auto"
|
||||
|
||||
resourcePolicy:
|
||||
containerPolicies:
|
||||
- containerName: myapp
|
||||
minAllowed:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
maxAllowed:
|
||||
cpu: 2000m
|
||||
memory: 2Gi
|
||||
controlledResources:
|
||||
- cpu
|
||||
- memory
|
||||
```
|
||||
|
||||
## RBAC (Role-Based Access Control)
|
||||
|
||||
### ServiceAccount, Role, and RoleBinding
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: myapp
|
||||
namespace: production
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: myapp-role
|
||||
namespace: production
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["configmaps", "secrets"]
|
||||
verbs: ["get", "list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: myapp-rolebinding
|
||||
namespace: production
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: myapp
|
||||
namespace: production
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: myapp-role
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
### ClusterRole for Cluster-Wide Permissions
|
||||
```yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: pod-reader
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["nodes"]
|
||||
verbs: ["get", "list"]
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: read-pods-global
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: myapp
|
||||
namespace: production
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: pod-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
## Network Policies
|
||||
|
||||
### Restrict Ingress Traffic
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: myapp-netpol
|
||||
namespace: production
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: myapp
|
||||
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
|
||||
ingress:
|
||||
# Allow traffic from nginx ingress controller
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
name: ingress-nginx
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: nginx-ingress
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
|
||||
# Allow traffic from prometheus for metrics
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
name: monitoring
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: prometheus
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 9090
|
||||
|
||||
egress:
|
||||
# Allow DNS
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
name: kube-system
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
k8s-app: kube-dns
|
||||
ports:
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
|
||||
# Allow database access
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: postgres
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5432
|
||||
|
||||
# Allow external HTTPS
|
||||
- to:
|
||||
- namespaceSelector: {}
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 443
|
||||
```
|
||||
|
||||
## Jobs and CronJobs
|
||||
|
||||
### Job for One-Time Task
|
||||
```yaml
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: database-migration
|
||||
namespace: production
|
||||
spec:
|
||||
backoffLimit: 3
|
||||
activeDeadlineSeconds: 600 # 10 minutes timeout
|
||||
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: migration
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
|
||||
containers:
|
||||
- name: migrate
|
||||
image: myapp:1.0.0
|
||||
command: ["/app/migrate"]
|
||||
args: ["--direction", "up"]
|
||||
|
||||
env:
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: myapp-secrets
|
||||
key: database-url
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
```
|
||||
|
||||
### CronJob for Scheduled Tasks
|
||||
```yaml
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: backup-database
|
||||
namespace: production
|
||||
spec:
|
||||
schedule: "0 2 * * *" # Daily at 2 AM
|
||||
timeZone: "America/New_York"
|
||||
concurrencyPolicy: Forbid # Don't allow concurrent runs
|
||||
successfulJobsHistoryLimit: 3
|
||||
failedJobsHistoryLimit: 1
|
||||
|
||||
jobTemplate:
|
||||
spec:
|
||||
backoffLimit: 2
|
||||
activeDeadlineSeconds: 3600 # 1 hour timeout
|
||||
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
|
||||
containers:
|
||||
- name: backup
|
||||
image: postgres:15-alpine
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
pg_dump -h $DB_HOST -U $DB_USER -d $DB_NAME | \
|
||||
gzip > /backup/backup-$(date +%Y%m%d-%H%M%S).sql.gz
|
||||
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: postgres-secrets
|
||||
|
||||
volumeMounts:
|
||||
- name: backup
|
||||
mountPath: /backup
|
||||
|
||||
volumes:
|
||||
- name: backup
|
||||
persistentVolumeClaim:
|
||||
claimName: backup-pvc
|
||||
```
|
||||
|
||||
## Helm Charts
|
||||
|
||||
### Chart Structure
|
||||
```
|
||||
myapp-chart/
|
||||
├── Chart.yaml
|
||||
├── values.yaml
|
||||
├── values-dev.yaml
|
||||
├── values-prod.yaml
|
||||
├── templates/
|
||||
│ ├── deployment.yaml
|
||||
│ ├── service.yaml
|
||||
│ ├── ingress.yaml
|
||||
│ ├── configmap.yaml
|
||||
│ ├── secret.yaml
|
||||
│ ├── hpa.yaml
|
||||
│ ├── serviceaccount.yaml
|
||||
│ ├── NOTES.txt
|
||||
│ └── _helpers.tpl
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### Chart.yaml
|
||||
```yaml
|
||||
apiVersion: v2
|
||||
name: myapp
|
||||
description: A Helm chart for MyApp
|
||||
type: application
|
||||
version: 1.0.0
|
||||
appVersion: "1.0.0"
|
||||
keywords:
|
||||
- myapp
|
||||
- web
|
||||
maintainers:
|
||||
- name: DevOps Team
|
||||
email: devops@example.com
|
||||
dependencies:
|
||||
- name: postgresql
|
||||
version: "12.x.x"
|
||||
repository: https://charts.bitnami.com/bitnami
|
||||
condition: postgresql.enabled
|
||||
```
|
||||
|
||||
### values.yaml
|
||||
```yaml
|
||||
replicaCount: 3
|
||||
|
||||
image:
|
||||
repository: myapp
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "" # Defaults to chart appVersion
|
||||
|
||||
imagePullSecrets: []
|
||||
|
||||
serviceAccount:
|
||||
create: true
|
||||
annotations: {}
|
||||
name: ""
|
||||
|
||||
podAnnotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "8080"
|
||||
|
||||
podSecurityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
fsGroup: 2000
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
targetPort: 8080
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
hosts:
|
||||
- host: myapp.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- secretName: myapp-tls
|
||||
hosts:
|
||||
- myapp.example.com
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 256Mi
|
||||
|
||||
autoscaling:
|
||||
enabled: true
|
||||
minReplicas: 3
|
||||
maxReplicas: 20
|
||||
targetCPUUtilizationPercentage: 70
|
||||
targetMemoryUtilizationPercentage: 80
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
||||
|
||||
postgresql:
|
||||
enabled: true
|
||||
auth:
|
||||
username: myapp
|
||||
database: myapp
|
||||
```
|
||||
|
||||
### Helm Commands
|
||||
```bash
|
||||
# Install chart
|
||||
helm install myapp ./myapp-chart -n production
|
||||
|
||||
# Install with custom values
|
||||
helm install myapp ./myapp-chart -n production -f values-prod.yaml
|
||||
|
||||
# Upgrade release
|
||||
helm upgrade myapp ./myapp-chart -n production
|
||||
|
||||
# Rollback
|
||||
helm rollback myapp 1 -n production
|
||||
|
||||
# Uninstall
|
||||
helm uninstall myapp -n production
|
||||
|
||||
# Template rendering (dry-run)
|
||||
helm template myapp ./myapp-chart -f values-prod.yaml
|
||||
|
||||
# Lint chart
|
||||
helm lint ./myapp-chart
|
||||
```
|
||||
|
||||
## kubectl Command Reference
|
||||
|
||||
```bash
|
||||
# Get resources
|
||||
kubectl get pods -n production
|
||||
kubectl get deployments -n production -o wide
|
||||
kubectl get svc -n production
|
||||
|
||||
# Describe resources
|
||||
kubectl describe pod myapp-123 -n production
|
||||
kubectl describe deployment myapp -n production
|
||||
|
||||
# Logs
|
||||
kubectl logs myapp-123 -n production
|
||||
kubectl logs -f myapp-123 -n production # Follow
|
||||
kubectl logs myapp-123 -n production --previous # Previous container
|
||||
|
||||
# Execute commands in pod
|
||||
kubectl exec -it myapp-123 -n production -- /bin/sh
|
||||
kubectl exec myapp-123 -n production -- env
|
||||
|
||||
# Port forwarding
|
||||
kubectl port-forward svc/myapp 8080:80 -n production
|
||||
|
||||
# Copy files
|
||||
kubectl cp myapp-123:/tmp/file.txt ./file.txt -n production
|
||||
|
||||
# Scale deployment
|
||||
kubectl scale deployment myapp --replicas=5 -n production
|
||||
|
||||
# Rollout management
|
||||
kubectl rollout status deployment/myapp -n production
|
||||
kubectl rollout history deployment/myapp -n production
|
||||
kubectl rollout undo deployment/myapp -n production
|
||||
|
||||
# Apply/Delete manifests
|
||||
kubectl apply -f deployment.yaml
|
||||
kubectl delete -f deployment.yaml
|
||||
|
||||
# Resource usage
|
||||
kubectl top nodes
|
||||
kubectl top pods -n production
|
||||
|
||||
# Debug
|
||||
kubectl run debug --image=busybox:1.36 -it --rm --restart=Never -- sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices Summary
|
||||
|
||||
1. **Always set resource requests and limits**
|
||||
2. **Implement proper health checks** (liveness, readiness, startup)
|
||||
3. **Use non-root containers** with security contexts
|
||||
4. **Enable RBAC** and use service accounts
|
||||
5. **Implement network policies** for zero-trust networking
|
||||
6. **Use namespaces** for isolation
|
||||
7. **Tag everything** with consistent labels
|
||||
8. **Use ConfigMaps and Secrets** (never hardcode)
|
||||
9. **Implement HPA** for auto-scaling
|
||||
10. **Use readOnlyRootFilesystem** when possible
|
||||
+559
@@ -0,0 +1,559 @@
|
||||
# Observability: Monitoring, Logging & Tracing
|
||||
|
||||
## The Three Pillars of Observability
|
||||
|
||||
### 1. Metrics (What is happening?)
|
||||
- **Definition**: Numeric measurements over time
|
||||
- **Examples**: CPU usage, request rate, error rate, latency
|
||||
- **Tools**: Prometheus, Datadog, CloudWatch, New Relic
|
||||
|
||||
### 2. Logs (Why is it happening?)
|
||||
- **Definition**: Timestamped event records
|
||||
- **Examples**: Application logs, access logs, error logs
|
||||
- **Tools**: ELK Stack, Splunk, CloudWatch Logs, Loki
|
||||
|
||||
### 3. Traces (Where is it happening?)
|
||||
- **Definition**: Request journey through distributed system
|
||||
- **Examples**: Service call chains, database queries, external API calls
|
||||
- **Tools**: Jaeger, Zipkin, AWS X-Ray, Datadog APM
|
||||
|
||||
## SLI/SLO/SLA Framework
|
||||
|
||||
### Service Level Indicators (SLIs)
|
||||
**Quantitative measurements of service quality**
|
||||
|
||||
```yaml
|
||||
# Common SLIs
|
||||
availability:
|
||||
definition: "Percentage of successful requests"
|
||||
measurement: "(successful_requests / total_requests) * 100"
|
||||
|
||||
latency:
|
||||
definition: "Time to process request"
|
||||
measurement: "p95 response time < 200ms"
|
||||
|
||||
error_rate:
|
||||
definition: "Percentage of failed requests"
|
||||
measurement: "(failed_requests / total_requests) * 100"
|
||||
|
||||
throughput:
|
||||
definition: "Requests processed per second"
|
||||
measurement: "requests_per_second"
|
||||
```
|
||||
|
||||
### Service Level Objectives (SLOs)
|
||||
**Target values for SLIs**
|
||||
|
||||
```yaml
|
||||
# Example SLOs
|
||||
availability_slo:
|
||||
target: 99.9%
|
||||
measurement_window: 30 days
|
||||
error_budget: 0.1% (43 minutes per month)
|
||||
|
||||
latency_slo:
|
||||
target: "95% of requests < 200ms"
|
||||
measurement_window: 7 days
|
||||
|
||||
error_rate_slo:
|
||||
target: "< 0.1%"
|
||||
measurement_window: 24 hours
|
||||
```
|
||||
|
||||
### Service Level Agreements (SLAs)
|
||||
**Business contracts with consequences**
|
||||
|
||||
```yaml
|
||||
# Example SLA
|
||||
web_application_sla:
|
||||
availability: 99.9%
|
||||
latency_p95: 300ms
|
||||
consequences:
|
||||
- availability < 99.9%: 10% service credit
|
||||
- availability < 99.0%: 25% service credit
|
||||
- availability < 95.0%: 50% service credit
|
||||
```
|
||||
|
||||
## Prometheus Setup
|
||||
|
||||
### Prometheus Configuration
|
||||
```yaml
|
||||
# prometheus.yml
|
||||
global:
|
||||
scrape_interval: 15s
|
||||
evaluation_interval: 15s
|
||||
external_labels:
|
||||
cluster: 'production'
|
||||
environment: 'prod'
|
||||
|
||||
# Alert manager configuration
|
||||
alerting:
|
||||
alertmanagers:
|
||||
- static_configs:
|
||||
- targets:
|
||||
- alertmanager:9093
|
||||
|
||||
# Load rules
|
||||
rule_files:
|
||||
- "/etc/prometheus/rules/*.yml"
|
||||
|
||||
# Scrape configurations
|
||||
scrape_configs:
|
||||
# Prometheus self-monitoring
|
||||
- job_name: 'prometheus'
|
||||
static_configs:
|
||||
- targets: ['localhost:9090']
|
||||
|
||||
# Kubernetes pods
|
||||
- job_name: 'kubernetes-pods'
|
||||
kubernetes_sd_configs:
|
||||
- role: pod
|
||||
relabel_configs:
|
||||
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
|
||||
action: keep
|
||||
regex: true
|
||||
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
|
||||
action: replace
|
||||
target_label: __metrics_path__
|
||||
regex: (.+)
|
||||
- source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
|
||||
action: replace
|
||||
regex: ([^:]+)(?::\d+)?;(\d+)
|
||||
replacement: $1:$2
|
||||
target_label: __address__
|
||||
- action: labelmap
|
||||
regex: __meta_kubernetes_pod_label_(.+)
|
||||
- source_labels: [__meta_kubernetes_namespace]
|
||||
action: replace
|
||||
target_label: kubernetes_namespace
|
||||
- source_labels: [__meta_kubernetes_pod_name]
|
||||
action: replace
|
||||
target_label: kubernetes_pod_name
|
||||
|
||||
# Node exporter
|
||||
- job_name: 'node-exporter'
|
||||
kubernetes_sd_configs:
|
||||
- role: node
|
||||
relabel_configs:
|
||||
- action: labelmap
|
||||
regex: __meta_kubernetes_node_label_(.+)
|
||||
```
|
||||
|
||||
### Alert Rules
|
||||
```yaml
|
||||
# alert-rules.yml
|
||||
groups:
|
||||
- name: application_alerts
|
||||
interval: 30s
|
||||
rules:
|
||||
# High error rate
|
||||
- alert: HighErrorRate
|
||||
expr: |
|
||||
rate(http_requests_total{status=~"5.."}[5m]) > 0.05
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
team: backend
|
||||
annotations:
|
||||
summary: "High error rate detected"
|
||||
description: "Error rate is {{ $value | humanizePercentage }} for {{ $labels.job }}"
|
||||
runbook: "https://wiki.example.com/runbooks/high-error-rate"
|
||||
|
||||
# High latency
|
||||
- alert: HighLatency
|
||||
expr: |
|
||||
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 0.5
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
team: backend
|
||||
annotations:
|
||||
summary: "High latency detected"
|
||||
description: "P95 latency is {{ $value | humanizeDuration }} for {{ $labels.job }}"
|
||||
|
||||
# Low availability
|
||||
- alert: ServiceDown
|
||||
expr: up == 0
|
||||
for: 2m
|
||||
labels:
|
||||
severity: critical
|
||||
team: sre
|
||||
annotations:
|
||||
summary: "Service is down"
|
||||
description: "{{ $labels.job }} has been down for more than 2 minutes"
|
||||
|
||||
- name: kubernetes_alerts
|
||||
interval: 30s
|
||||
rules:
|
||||
# Pod crash looping
|
||||
- alert: PodCrashLooping
|
||||
expr: |
|
||||
rate(kube_pod_container_status_restarts_total[15m]) > 0
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Pod crash looping"
|
||||
description: "Pod {{ $labels.namespace }}/{{ $labels.pod }} is crash looping"
|
||||
|
||||
# High memory usage
|
||||
- alert: HighMemoryUsage
|
||||
expr: |
|
||||
(container_memory_usage_bytes / container_spec_memory_limit_bytes) > 0.9
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "High memory usage"
|
||||
description: "Container {{ $labels.container }} in pod {{ $labels.pod }} is using {{ $value | humanizePercentage }} of memory"
|
||||
|
||||
# Node disk space
|
||||
- alert: NodeDiskSpaceLow
|
||||
expr: |
|
||||
(node_filesystem_avail_bytes / node_filesystem_size_bytes) < 0.1
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Node disk space low"
|
||||
description: "Node {{ $labels.node }} has less than 10% disk space available"
|
||||
```
|
||||
|
||||
## Structured Logging
|
||||
|
||||
### Best Practices
|
||||
```json
|
||||
{
|
||||
"timestamp": "2025-10-17T10:30:45.123Z",
|
||||
"level": "ERROR",
|
||||
"service": "api-gateway",
|
||||
"version": "v1.2.3",
|
||||
"trace_id": "abc123def456",
|
||||
"span_id": "789ghi012jkl",
|
||||
"user_id": "user-12345",
|
||||
"request_id": "req-67890",
|
||||
"method": "POST",
|
||||
"path": "/api/v1/orders",
|
||||
"status_code": 500,
|
||||
"duration_ms": 245,
|
||||
"error": {
|
||||
"type": "DatabaseConnectionError",
|
||||
"message": "Failed to connect to database",
|
||||
"stack_trace": "..."
|
||||
},
|
||||
"context": {
|
||||
"order_id": "order-98765",
|
||||
"customer_id": "cust-54321"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Logging Configuration (Node.js Example)
|
||||
```javascript
|
||||
const winston = require('winston');
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.json()
|
||||
),
|
||||
defaultMeta: {
|
||||
service: process.env.SERVICE_NAME,
|
||||
version: process.env.SERVICE_VERSION,
|
||||
environment: process.env.ENVIRONMENT
|
||||
},
|
||||
transports: [
|
||||
new winston.transports.Console(),
|
||||
new winston.transports.File({
|
||||
filename: 'error.log',
|
||||
level: 'error'
|
||||
}),
|
||||
new winston.transports.File({
|
||||
filename: 'combined.log'
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
// Usage with correlation ID
|
||||
app.use((req, res, next) => {
|
||||
req.id = req.headers['x-request-id'] || uuidv4();
|
||||
req.logger = logger.child({
|
||||
request_id: req.id,
|
||||
trace_id: req.headers['x-trace-id']
|
||||
});
|
||||
next();
|
||||
});
|
||||
|
||||
app.post('/api/orders', async (req, res) => {
|
||||
req.logger.info('Creating order', {
|
||||
customer_id: req.body.customer_id
|
||||
});
|
||||
|
||||
try {
|
||||
const order = await createOrder(req.body);
|
||||
req.logger.info('Order created successfully', {
|
||||
order_id: order.id
|
||||
});
|
||||
res.json(order);
|
||||
} catch (error) {
|
||||
req.logger.error('Failed to create order', {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Distributed Tracing
|
||||
|
||||
### OpenTelemetry Configuration
|
||||
```yaml
|
||||
# otel-collector-config.yaml
|
||||
receivers:
|
||||
otlp:
|
||||
protocols:
|
||||
grpc:
|
||||
endpoint: 0.0.0.0:4317
|
||||
http:
|
||||
endpoint: 0.0.0.0:4318
|
||||
|
||||
processors:
|
||||
batch:
|
||||
timeout: 10s
|
||||
send_batch_size: 1024
|
||||
|
||||
memory_limiter:
|
||||
check_interval: 1s
|
||||
limit_mib: 512
|
||||
|
||||
resource:
|
||||
attributes:
|
||||
- key: environment
|
||||
value: production
|
||||
action: insert
|
||||
|
||||
exporters:
|
||||
jaeger:
|
||||
endpoint: jaeger:14250
|
||||
tls:
|
||||
insecure: true
|
||||
|
||||
prometheus:
|
||||
endpoint: 0.0.0.0:8889
|
||||
|
||||
logging:
|
||||
loglevel: info
|
||||
|
||||
service:
|
||||
pipelines:
|
||||
traces:
|
||||
receivers: [otlp]
|
||||
processors: [memory_limiter, batch, resource]
|
||||
exporters: [jaeger, logging]
|
||||
|
||||
metrics:
|
||||
receivers: [otlp]
|
||||
processors: [memory_limiter, batch, resource]
|
||||
exporters: [prometheus, logging]
|
||||
```
|
||||
|
||||
### Application Instrumentation (Python Example)
|
||||
```python
|
||||
from opentelemetry import trace
|
||||
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
||||
from opentelemetry.sdk.trace import TracerProvider
|
||||
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
||||
from opentelemetry.instrumentation.flask import FlaskInstrumentor
|
||||
from opentelemetry.instrumentation.requests import RequestsInstrumentor
|
||||
|
||||
# Set up tracing
|
||||
trace.set_tracer_provider(TracerProvider())
|
||||
tracer = trace.get_tracer(__name__)
|
||||
|
||||
# Configure OTLP exporter
|
||||
otlp_exporter = OTLPSpanExporter(
|
||||
endpoint="otel-collector:4317",
|
||||
insecure=True
|
||||
)
|
||||
|
||||
# Add span processor
|
||||
span_processor = BatchSpanProcessor(otlp_exporter)
|
||||
trace.get_tracer_provider().add_span_processor(span_processor)
|
||||
|
||||
# Instrument Flask and requests library
|
||||
app = Flask(__name__)
|
||||
FlaskInstrumentor().instrument_app(app)
|
||||
RequestsInstrumentor().instrument()
|
||||
|
||||
# Manual span creation
|
||||
@app.route('/api/order/<order_id>')
|
||||
def get_order(order_id):
|
||||
with tracer.start_as_current_span("get_order") as span:
|
||||
span.set_attribute("order.id", order_id)
|
||||
span.set_attribute("user.id", request.headers.get('X-User-ID'))
|
||||
|
||||
# Add events
|
||||
span.add_event("Fetching order from database")
|
||||
order = fetch_order_from_db(order_id)
|
||||
|
||||
if not order:
|
||||
span.set_status(Status(StatusCode.ERROR, "Order not found"))
|
||||
return {"error": "Order not found"}, 404
|
||||
|
||||
span.add_event("Order retrieved successfully")
|
||||
return order
|
||||
```
|
||||
|
||||
## Dashboards & Visualization
|
||||
|
||||
### Grafana Dashboard JSON (Example)
|
||||
```json
|
||||
{
|
||||
"dashboard": {
|
||||
"title": "Application Performance",
|
||||
"panels": [
|
||||
{
|
||||
"title": "Request Rate",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "rate(http_requests_total[5m])",
|
||||
"legendFormat": "{{method}} {{status}}"
|
||||
}
|
||||
],
|
||||
"type": "graph"
|
||||
},
|
||||
{
|
||||
"title": "Error Rate",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "rate(http_requests_total{status=~\"5..\"}[5m]) / rate(http_requests_total[5m])",
|
||||
"legendFormat": "Error Rate"
|
||||
}
|
||||
],
|
||||
"type": "graph"
|
||||
},
|
||||
{
|
||||
"title": "P95 Latency",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))",
|
||||
"legendFormat": "P95 Latency"
|
||||
}
|
||||
],
|
||||
"type": "graph"
|
||||
},
|
||||
{
|
||||
"title": "Active Connections",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(up{job=\"myapp\"})",
|
||||
"legendFormat": "Active Instances"
|
||||
}
|
||||
],
|
||||
"type": "stat"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## On-Call & Incident Response
|
||||
|
||||
### Runbook Template
|
||||
```markdown
|
||||
# Runbook: High Error Rate Alert
|
||||
|
||||
## Alert Details
|
||||
- **Alert Name**: HighErrorRate
|
||||
- **Severity**: Critical
|
||||
- **Team**: Backend Engineering
|
||||
- **On-Call**: See PagerDuty schedule
|
||||
|
||||
## Symptoms
|
||||
- Error rate exceeds 5% for 5 minutes
|
||||
- Users experiencing 5xx errors
|
||||
- Elevated p95 latency
|
||||
|
||||
## Investigation Steps
|
||||
|
||||
1. **Check service health**
|
||||
```bash
|
||||
kubectl get pods -n production -l app=myapp
|
||||
kubectl logs -n production -l app=myapp --tail=100
|
||||
```
|
||||
|
||||
2. **Review error logs**
|
||||
- Check Grafana dashboard
|
||||
- Review application logs in Kibana
|
||||
- Check CloudWatch metrics
|
||||
|
||||
3. **Identify error patterns**
|
||||
- What endpoints are failing?
|
||||
- Are errors consistent across all pods?
|
||||
- Is there a pattern in timing?
|
||||
|
||||
4. **Check dependencies**
|
||||
- Database connectivity
|
||||
- External API availability
|
||||
- Redis/cache status
|
||||
|
||||
## Common Causes & Solutions
|
||||
|
||||
### Database Connection Issues
|
||||
- **Symptoms**: Connection timeout errors
|
||||
- **Solution**:
|
||||
```bash
|
||||
# Check database connectivity
|
||||
kubectl exec -it <pod-name> -- nc -zv database-host 5432
|
||||
|
||||
# Check connection pool
|
||||
kubectl logs <pod-name> | grep "connection pool"
|
||||
```
|
||||
|
||||
### Memory Leaks
|
||||
- **Symptoms**: Increasing memory usage, OOM kills
|
||||
- **Solution**: Restart affected pods, investigate memory usage
|
||||
|
||||
### Deployment Issues
|
||||
- **Symptoms**: Errors started after deployment
|
||||
- **Solution**: Rollback deployment
|
||||
```bash
|
||||
kubectl rollout undo deployment/myapp -n production
|
||||
```
|
||||
|
||||
## Escalation
|
||||
- If unresolved after 15 minutes, escalate to Senior Engineer
|
||||
- If service degradation > 30 minutes, notify VP Engineering
|
||||
|
||||
## Post-Incident
|
||||
- Create incident report
|
||||
- Schedule post-mortem
|
||||
- Update runbook with findings
|
||||
```
|
||||
|
||||
## Observability Best Practices
|
||||
|
||||
1. **Use consistent naming**: Follow naming conventions for metrics, logs, traces
|
||||
2. **Add context**: Include correlation IDs in logs and traces
|
||||
3. **Set meaningful alerts**: Avoid alert fatigue with actionable alerts
|
||||
4. **Define SLOs**: Measure what matters to users
|
||||
5. **Practice incident response**: Regular game days and fire drills
|
||||
6. **Automate runbooks**: Convert manual steps to automated remediation
|
||||
7. **Monitor the monitors**: Ensure observability stack is reliable
|
||||
8. **Continuous improvement**: Review and refine based on incidents
|
||||
|
||||
---
|
||||
|
||||
## Tools Comparison
|
||||
|
||||
| Feature | Prometheus | Datadog | New Relic | CloudWatch |
|
||||
|---------|-----------|---------|-----------|------------|
|
||||
| Metrics | ✓✓✓ | ✓✓✓ | ✓✓✓ | ✓✓ |
|
||||
| Logs | via Loki | ✓✓✓ | ✓✓✓ | ✓✓✓ |
|
||||
| Traces | via Tempo | ✓✓✓ | ✓✓✓ | ✓✓ |
|
||||
| Cost | Free (self-hosted) | $$$ | $$$ | $$ |
|
||||
| Learning Curve | Medium | Low | Low | Low |
|
||||
| Kubernetes Native | ✓✓✓ | ✓✓ | ✓✓ | ✓ |
|
||||
@@ -0,0 +1,661 @@
|
||||
# DevSecOps & Security Best Practices
|
||||
|
||||
## Security Principles
|
||||
|
||||
### Defense in Depth
|
||||
- **Multiple layers of security controls**
|
||||
- Network security, application security, data security
|
||||
- No single point of failure
|
||||
|
||||
### Least Privilege
|
||||
- **Minimum permissions necessary**
|
||||
- Regular access reviews
|
||||
- Time-bound elevated access
|
||||
|
||||
### Zero Trust
|
||||
- **Never trust, always verify**
|
||||
- Verify every request regardless of origin
|
||||
- Micro-segmentation and strict access controls
|
||||
|
||||
## Secrets Management
|
||||
|
||||
### ❌ Never Do This
|
||||
```yaml
|
||||
# NEVER hardcode secrets!
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: bad-example
|
||||
spec:
|
||||
containers:
|
||||
- name: app
|
||||
env:
|
||||
- name: DATABASE_PASSWORD
|
||||
value: "SuperSecret123!" # 🚨 NEVER DO THIS
|
||||
- name: API_KEY
|
||||
value: "sk-abc123def456" # 🚨 NEVER DO THIS
|
||||
```
|
||||
|
||||
### ✅ Use External Secrets Operator
|
||||
```yaml
|
||||
# External Secrets with AWS Secrets Manager
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: app-secrets
|
||||
namespace: production
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
|
||||
secretStoreRef:
|
||||
name: aws-secrets-manager
|
||||
kind: SecretStore
|
||||
|
||||
target:
|
||||
name: app-secrets
|
||||
creationPolicy: Owner
|
||||
|
||||
data:
|
||||
- secretKey: database-password
|
||||
remoteRef:
|
||||
key: prod/myapp/database-password
|
||||
|
||||
- secretKey: api-key
|
||||
remoteRef:
|
||||
key: prod/myapp/api-key
|
||||
```
|
||||
|
||||
### ✅ Use Sealed Secrets
|
||||
```bash
|
||||
# Install kubeseal
|
||||
brew install kubeseal
|
||||
|
||||
# Create sealed secret
|
||||
kubectl create secret generic myapp-secrets \
|
||||
--from-literal=db-password='secret123' \
|
||||
--dry-run=client -o yaml | \
|
||||
kubeseal -o yaml > sealed-secret.yaml
|
||||
|
||||
# Apply sealed secret (safe to commit to Git)
|
||||
kubectl apply -f sealed-secret.yaml
|
||||
```
|
||||
|
||||
### AWS Secrets Manager with IRSA
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: myapp
|
||||
namespace: production
|
||||
annotations:
|
||||
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/myapp-secrets-role
|
||||
|
||||
---
|
||||
# Application retrieves secrets using AWS SDK
|
||||
# No credentials in code or config!
|
||||
```
|
||||
|
||||
## Container Security
|
||||
|
||||
### Secure Dockerfile
|
||||
```dockerfile
|
||||
# Use specific version tags, not 'latest'
|
||||
FROM node:20.10.0-alpine3.19 AS builder
|
||||
|
||||
# Run as non-root user
|
||||
RUN addgroup -g 1000 nodejs && \
|
||||
adduser -u 1000 -G nodejs -s /bin/sh -D nodejs
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy and install dependencies
|
||||
COPY --chown=nodejs:nodejs package*.json ./
|
||||
RUN npm ci --only=production && \
|
||||
npm cache clean --force
|
||||
|
||||
# Copy application code
|
||||
COPY --chown=nodejs:nodejs . .
|
||||
|
||||
# Build application
|
||||
RUN npm run build
|
||||
|
||||
# Production stage
|
||||
FROM node:20.10.0-alpine3.19
|
||||
|
||||
# Install dumb-init for signal handling
|
||||
RUN apk add --no-cache dumb-init
|
||||
|
||||
# Create non-root user
|
||||
RUN addgroup -g 1000 nodejs && \
|
||||
adduser -u 1000 -G nodejs -s /bin/sh -D nodejs
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy from builder
|
||||
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
|
||||
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
|
||||
COPY --from=builder --chown=nodejs:nodejs /app/package*.json ./
|
||||
|
||||
# Switch to non-root user
|
||||
USER nodejs
|
||||
|
||||
# Use dumb-init
|
||||
ENTRYPOINT ["dumb-init", "--"]
|
||||
|
||||
# Run application
|
||||
CMD ["node", "dist/index.js"]
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8080
|
||||
|
||||
# Add labels
|
||||
LABEL org.opencontainers.image.source="https://github.com/myorg/myapp" \
|
||||
org.opencontainers.image.version="1.0.0" \
|
||||
org.opencontainers.image.vendor="MyOrg"
|
||||
```
|
||||
|
||||
### Security Scanning
|
||||
|
||||
#### Trivy (Container Vulnerability Scanner)
|
||||
```bash
|
||||
# Scan Docker image
|
||||
trivy image myapp:latest
|
||||
|
||||
# Scan with severity filter
|
||||
trivy image --severity HIGH,CRITICAL myapp:latest
|
||||
|
||||
# Scan filesystem
|
||||
trivy fs --security-checks vuln,config /path/to/project
|
||||
|
||||
# Output to JSON
|
||||
trivy image -f json -o results.json myapp:latest
|
||||
```
|
||||
|
||||
#### Grype (Vulnerability Scanner)
|
||||
```bash
|
||||
# Scan image
|
||||
grype myapp:latest
|
||||
|
||||
# Scan with specific severity
|
||||
grype myapp:latest --fail-on high
|
||||
|
||||
# Output SARIF for GitHub
|
||||
grype myapp:latest -o sarif > results.sarif
|
||||
```
|
||||
|
||||
### Pod Security Standards
|
||||
|
||||
```yaml
|
||||
# Pod Security Admission
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: production
|
||||
labels:
|
||||
pod-security.kubernetes.io/enforce: restricted
|
||||
pod-security.kubernetes.io/audit: restricted
|
||||
pod-security.kubernetes.io/warn: restricted
|
||||
|
||||
---
|
||||
# Secure Pod Configuration
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: secure-pod
|
||||
namespace: production
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
fsGroup: 2000
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
containers:
|
||||
- name: app
|
||||
image: myapp:1.0.0
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- NET_BIND_SERVICE # Only if needed
|
||||
|
||||
resources:
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "250m"
|
||||
|
||||
volumeMounts:
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
- name: cache
|
||||
mountPath: /var/cache
|
||||
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: cache
|
||||
emptyDir: {}
|
||||
```
|
||||
|
||||
## Network Security
|
||||
|
||||
### Network Policies
|
||||
```yaml
|
||||
# Default deny all ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: default-deny-ingress
|
||||
namespace: production
|
||||
spec:
|
||||
podSelector: {}
|
||||
policyTypes:
|
||||
- Ingress
|
||||
|
||||
---
|
||||
# Allow specific ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: app-allow-ingress
|
||||
namespace: production
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: myapp
|
||||
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
|
||||
ingress:
|
||||
# Allow from ingress controller
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
name: ingress-nginx
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
|
||||
egress:
|
||||
# Allow DNS
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
name: kube-system
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
k8s-app: kube-dns
|
||||
ports:
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
|
||||
# Allow database
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: postgres
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5432
|
||||
|
||||
# Allow HTTPS egress
|
||||
- to:
|
||||
- namespaceSelector: {}
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 443
|
||||
```
|
||||
|
||||
### AWS Security Groups (Terraform)
|
||||
```hcl
|
||||
# Application Security Group
|
||||
resource "aws_security_group" "app" {
|
||||
name = "${var.name_prefix}-app-sg"
|
||||
description = "Security group for application"
|
||||
vpc_id = var.vpc_id
|
||||
|
||||
# Allow inbound from ALB only
|
||||
ingress {
|
||||
from_port = 8080
|
||||
to_port = 8080
|
||||
protocol = "tcp"
|
||||
security_groups = [aws_security_group.alb.id]
|
||||
description = "HTTP from ALB"
|
||||
}
|
||||
|
||||
# Allow outbound to database
|
||||
egress {
|
||||
from_port = 5432
|
||||
to_port = 5432
|
||||
protocol = "tcp"
|
||||
security_groups = [aws_security_group.database.id]
|
||||
description = "PostgreSQL to database"
|
||||
}
|
||||
|
||||
# Allow HTTPS egress
|
||||
egress {
|
||||
from_port = 443
|
||||
to_port = 443
|
||||
protocol = "tcp"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
description = "HTTPS to internet"
|
||||
}
|
||||
|
||||
tags = var.tags
|
||||
}
|
||||
```
|
||||
|
||||
## IAM & RBAC
|
||||
|
||||
### Kubernetes RBAC
|
||||
```yaml
|
||||
# Least privilege service account
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: myapp
|
||||
namespace: production
|
||||
|
||||
---
|
||||
# Role with minimal permissions
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: myapp-role
|
||||
namespace: production
|
||||
rules:
|
||||
# Only read ConfigMaps and Secrets
|
||||
- apiGroups: [""]
|
||||
resources: ["configmaps", "secrets"]
|
||||
verbs: ["get", "list"]
|
||||
resourceNames: ["myapp-config", "myapp-secrets"]
|
||||
|
||||
# Read own pod information
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get"]
|
||||
|
||||
---
|
||||
# Bind role to service account
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: myapp-rolebinding
|
||||
namespace: production
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: myapp
|
||||
namespace: production
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: myapp-role
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
### AWS IAM Policy (Terraform)
|
||||
```hcl
|
||||
# Least privilege IAM policy
|
||||
data "aws_iam_policy_document" "app" {
|
||||
# Allow reading specific secrets
|
||||
statement {
|
||||
actions = [
|
||||
"secretsmanager:GetSecretValue",
|
||||
"secretsmanager:DescribeSecret",
|
||||
]
|
||||
resources = [
|
||||
"arn:aws:secretsmanager:${var.region}:${data.aws_caller_identity.current.account_id}:secret:prod/myapp/*"
|
||||
]
|
||||
}
|
||||
|
||||
# Allow writing to specific S3 bucket
|
||||
statement {
|
||||
actions = [
|
||||
"s3:PutObject",
|
||||
"s3:GetObject",
|
||||
]
|
||||
resources = [
|
||||
"${aws_s3_bucket.app.arn}/*"
|
||||
]
|
||||
}
|
||||
|
||||
# Allow publishing to specific SNS topic
|
||||
statement {
|
||||
actions = [
|
||||
"sns:Publish",
|
||||
]
|
||||
resources = [
|
||||
aws_sns_topic.app_notifications.arn
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_policy" "app" {
|
||||
name = "${var.name_prefix}-app-policy"
|
||||
policy = data.aws_iam_policy_document.app.json
|
||||
}
|
||||
```
|
||||
|
||||
## Compliance & Policy as Code
|
||||
|
||||
### OPA (Open Policy Agent)
|
||||
```rego
|
||||
# Deny pods running as root
|
||||
package kubernetes.admission
|
||||
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "Pod"
|
||||
input.request.object.spec.securityContext.runAsNonRoot != true
|
||||
msg := "Pods must not run as root"
|
||||
}
|
||||
|
||||
# Require resource limits
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "Pod"
|
||||
container := input.request.object.spec.containers[_]
|
||||
not container.resources.limits
|
||||
msg := sprintf("Container %v must specify resource limits", [container.name])
|
||||
}
|
||||
|
||||
# Deny latest tag
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "Pod"
|
||||
container := input.request.object.spec.containers[_]
|
||||
endswith(container.image, ":latest")
|
||||
msg := sprintf("Container %v uses 'latest' tag", [container.name])
|
||||
}
|
||||
|
||||
# Require specific labels
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "Pod"
|
||||
required_labels := ["app", "version", "environment"]
|
||||
label := required_labels[_]
|
||||
not input.request.object.metadata.labels[label]
|
||||
msg := sprintf("Missing required label: %v", [label])
|
||||
}
|
||||
```
|
||||
|
||||
### Terraform Sentinel Policy
|
||||
```hcl
|
||||
# sentinel.hcl
|
||||
policy "require-tags" {
|
||||
enforcement_level = "hard-mandatory"
|
||||
}
|
||||
|
||||
policy "restrict-instance-type" {
|
||||
enforcement_level = "soft-mandatory"
|
||||
}
|
||||
|
||||
# require-tags.sentinel
|
||||
import "tfplan/v2" as tfplan
|
||||
|
||||
required_tags = ["Environment", "Owner", "CostCenter"]
|
||||
|
||||
main = rule {
|
||||
all tfplan.resource_changes as _, rc {
|
||||
rc.mode is "managed" and
|
||||
rc.type in ["aws_instance", "aws_db_instance", "aws_s3_bucket"] and
|
||||
rc.change.actions contains "create"
|
||||
implies
|
||||
all required_tags as tag {
|
||||
rc.change.after.tags contains tag
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Security Scanning in CI/CD
|
||||
|
||||
### GitHub Actions Security Workflow
|
||||
```yaml
|
||||
name: Security Scan
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
secret-scan:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: TruffleHog Secret Scan
|
||||
uses: trufflesecurity/trufflehog@main
|
||||
with:
|
||||
path: ./
|
||||
base: ${{ github.event.repository.default_branch }}
|
||||
head: HEAD
|
||||
|
||||
container-scan:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Build image
|
||||
run: docker build -t myapp:${{ github.sha }} .
|
||||
|
||||
- name: Run Trivy scan
|
||||
uses: aquasecurity/trivy-action@master
|
||||
with:
|
||||
image-ref: myapp:${{ github.sha }}
|
||||
format: 'sarif'
|
||||
output: 'trivy-results.sarif'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
|
||||
- name: Upload to GitHub Security
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
with:
|
||||
sarif_file: 'trivy-results.sarif'
|
||||
|
||||
terraform-scan:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Run Checkov
|
||||
uses: bridgecrewio/checkov-action@master
|
||||
with:
|
||||
directory: terraform/
|
||||
framework: terraform
|
||||
soft_fail: false
|
||||
|
||||
sast-scan:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Run Semgrep
|
||||
uses: returntocorp/semgrep-action@v1
|
||||
with:
|
||||
config: auto
|
||||
```
|
||||
|
||||
## Encryption
|
||||
|
||||
### At Rest
|
||||
```hcl
|
||||
# S3 bucket encryption
|
||||
resource "aws_s3_bucket_server_side_encryption_configuration" "app" {
|
||||
bucket = aws_s3_bucket.app.id
|
||||
|
||||
rule {
|
||||
apply_server_side_encryption_by_default {
|
||||
sse_algorithm = "aws:kms"
|
||||
kms_master_key_id = aws_kms_key.s3.arn
|
||||
}
|
||||
bucket_key_enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
# RDS encryption
|
||||
resource "aws_db_instance" "app" {
|
||||
storage_encrypted = true
|
||||
kms_key_id = aws_kms_key.rds.arn
|
||||
# ... other configuration
|
||||
}
|
||||
|
||||
# EBS encryption
|
||||
resource "aws_ebs_volume" "app" {
|
||||
encrypted = true
|
||||
kms_key_id = aws_kms_key.ebs.arn
|
||||
# ... other configuration
|
||||
}
|
||||
```
|
||||
|
||||
### In Transit
|
||||
```yaml
|
||||
# Enforce TLS in Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: app
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- app.example.com
|
||||
secretName: app-tls
|
||||
```
|
||||
|
||||
## Security Best Practices Checklist
|
||||
|
||||
- [ ] **Never hardcode secrets** - Use secret managers
|
||||
- [ ] **Run containers as non-root** - Set securityContext
|
||||
- [ ] **Use specific image tags** - Never use 'latest'
|
||||
- [ ] **Scan images for vulnerabilities** - Use Trivy/Grype
|
||||
- [ ] **Enable Pod Security Standards** - Use restricted profile
|
||||
- [ ] **Implement network policies** - Default deny, allow specific
|
||||
- [ ] **Use RBAC** - Least privilege access
|
||||
- [ ] **Encrypt data at rest** - KMS for all data stores
|
||||
- [ ] **Enforce TLS** - All traffic encrypted in transit
|
||||
- [ ] **Scan IaC for issues** - Checkov, tfsec in CI/CD
|
||||
- [ ] **Rotate credentials regularly** - Automate rotation
|
||||
- [ ] **Audit and log everything** - CloudTrail, audit logs
|
||||
- [ ] **Implement policy as code** - OPA, Sentinel
|
||||
- [ ] **Regular security reviews** - Penetration testing, audits
|
||||
- [ ] **Keep dependencies updated** - Renovate, Dependabot
|
||||
|
||||
---
|
||||
|
||||
## Security Incident Response
|
||||
|
||||
1. **Detect**: Automated alerting on security events
|
||||
2. **Contain**: Isolate affected systems
|
||||
3. **Eradicate**: Remove threat and vulnerabilities
|
||||
4. **Recover**: Restore services securely
|
||||
5. **Learn**: Post-incident review and improvements
|
||||
@@ -0,0 +1,919 @@
|
||||
# Ready-to-Use DevOps Templates
|
||||
|
||||
## Terraform Templates
|
||||
|
||||
### AWS VPC with Multi-AZ
|
||||
See [terraform.md](terraform.md) for the complete VPC module implementation.
|
||||
|
||||
### AWS EKS Cluster
|
||||
```hcl
|
||||
module "eks" {
|
||||
source = "terraform-aws-modules/eks/aws"
|
||||
version = "~> 19.0"
|
||||
|
||||
cluster_name = "${var.name_prefix}-cluster"
|
||||
cluster_version = "1.28"
|
||||
|
||||
vpc_id = module.vpc.vpc_id
|
||||
subnet_ids = module.vpc.private_subnet_ids
|
||||
|
||||
cluster_endpoint_public_access = false
|
||||
cluster_endpoint_private_access = true
|
||||
|
||||
cluster_addons = {
|
||||
coredns = {
|
||||
most_recent = true
|
||||
}
|
||||
kube-proxy = {
|
||||
most_recent = true
|
||||
}
|
||||
vpc-cni = {
|
||||
most_recent = true
|
||||
}
|
||||
aws-ebs-csi-driver = {
|
||||
most_recent = true
|
||||
}
|
||||
}
|
||||
|
||||
eks_managed_node_groups = {
|
||||
general = {
|
||||
min_size = 2
|
||||
max_size = 10
|
||||
desired_size = 3
|
||||
|
||||
instance_types = ["t3.large"]
|
||||
capacity_type = "ON_DEMAND"
|
||||
|
||||
labels = {
|
||||
role = "general"
|
||||
}
|
||||
|
||||
tags = {
|
||||
NodeGroup = "general"
|
||||
}
|
||||
}
|
||||
|
||||
spot = {
|
||||
min_size = 0
|
||||
max_size = 10
|
||||
desired_size = 2
|
||||
|
||||
instance_types = ["t3.large", "t3a.large"]
|
||||
capacity_type = "SPOT"
|
||||
|
||||
labels = {
|
||||
role = "spot"
|
||||
}
|
||||
|
||||
taints = [{
|
||||
key = "spot"
|
||||
value = "true"
|
||||
effect = "NoSchedule"
|
||||
}]
|
||||
|
||||
tags = {
|
||||
NodeGroup = "spot"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tags = var.tags
|
||||
}
|
||||
```
|
||||
|
||||
### AWS RDS PostgreSQL
|
||||
```hcl
|
||||
resource "aws_db_subnet_group" "main" {
|
||||
name = "${var.name_prefix}-db-subnet-group"
|
||||
subnet_ids = var.private_subnet_ids
|
||||
|
||||
tags = merge(
|
||||
var.tags,
|
||||
{
|
||||
Name = "${var.name_prefix}-db-subnet-group"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
resource "aws_db_parameter_group" "postgres" {
|
||||
name = "${var.name_prefix}-postgres-params"
|
||||
family = "postgres15"
|
||||
|
||||
parameter {
|
||||
name = "log_connections"
|
||||
value = "1"
|
||||
}
|
||||
|
||||
parameter {
|
||||
name = "log_disconnections"
|
||||
value = "1"
|
||||
}
|
||||
|
||||
parameter {
|
||||
name = "log_duration"
|
||||
value = "1"
|
||||
}
|
||||
|
||||
parameter {
|
||||
name = "log_statement"
|
||||
value = "all"
|
||||
}
|
||||
|
||||
tags = var.tags
|
||||
}
|
||||
|
||||
resource "aws_db_instance" "main" {
|
||||
identifier = "${var.name_prefix}-db"
|
||||
|
||||
engine = "postgres"
|
||||
engine_version = "15.4"
|
||||
instance_class = var.instance_class
|
||||
|
||||
allocated_storage = var.allocated_storage
|
||||
max_allocated_storage = var.max_allocated_storage
|
||||
storage_type = "gp3"
|
||||
storage_encrypted = true
|
||||
kms_key_id = aws_kms_key.rds.arn
|
||||
|
||||
db_name = var.database_name
|
||||
username = var.master_username
|
||||
password = random_password.db_password.result
|
||||
|
||||
db_subnet_group_name = aws_db_subnet_group.main.name
|
||||
vpc_security_group_ids = [aws_security_group.db.id]
|
||||
parameter_group_name = aws_db_parameter_group.postgres.name
|
||||
|
||||
multi_az = var.multi_az
|
||||
backup_retention_period = var.backup_retention_period
|
||||
backup_window = "03:00-04:00"
|
||||
maintenance_window = "sun:04:00-sun:05:00"
|
||||
|
||||
enabled_cloudwatch_logs_exports = ["postgresql", "upgrade"]
|
||||
monitoring_interval = 60
|
||||
monitoring_role_arn = aws_iam_role.rds_monitoring.arn
|
||||
|
||||
deletion_protection = var.environment == "prod" ? true : false
|
||||
skip_final_snapshot = var.environment != "prod" ? true : false
|
||||
final_snapshot_identifier = var.environment == "prod" ? "${var.name_prefix}-final-snapshot-${formatdate("YYYY-MM-DD-hhmm", timestamp())}" : null
|
||||
|
||||
tags = merge(
|
||||
var.tags,
|
||||
{
|
||||
Name = "${var.name_prefix}-db"
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Kubernetes Templates
|
||||
|
||||
### Complete Application Stack
|
||||
```yaml
|
||||
---
|
||||
# Namespace
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: myapp
|
||||
labels:
|
||||
name: myapp
|
||||
environment: production
|
||||
|
||||
---
|
||||
# ConfigMap
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: myapp-config
|
||||
namespace: myapp
|
||||
data:
|
||||
APP_ENV: "production"
|
||||
LOG_LEVEL: "info"
|
||||
PORT: "8080"
|
||||
|
||||
---
|
||||
# Secret (use Sealed Secrets or External Secrets in production)
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: myapp-secrets
|
||||
namespace: myapp
|
||||
type: Opaque
|
||||
stringData:
|
||||
DATABASE_URL: "postgresql://user:pass@postgres:5432/myapp"
|
||||
API_KEY: "your-api-key-here"
|
||||
|
||||
---
|
||||
# ServiceAccount
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: myapp
|
||||
namespace: myapp
|
||||
automountServiceAccountToken: true
|
||||
|
||||
---
|
||||
# Deployment
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myapp
|
||||
namespace: myapp
|
||||
labels:
|
||||
app: myapp
|
||||
spec:
|
||||
replicas: 3
|
||||
revisionHistoryLimit: 10
|
||||
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
|
||||
selector:
|
||||
matchLabels:
|
||||
app: myapp
|
||||
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: myapp
|
||||
version: v1.0.0
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "8080"
|
||||
prometheus.io/path: "/metrics"
|
||||
|
||||
spec:
|
||||
serviceAccountName: myapp
|
||||
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
fsGroup: 2000
|
||||
|
||||
containers:
|
||||
- name: myapp
|
||||
image: myapp:1.0.0
|
||||
imagePullPolicy: IfNotPresent
|
||||
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: myapp-config
|
||||
- secretRef:
|
||||
name: myapp-secrets
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 8080
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ready
|
||||
port: 8080
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
volumeMounts:
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
- name: cache
|
||||
mountPath: /var/cache
|
||||
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: cache
|
||||
emptyDir: {}
|
||||
|
||||
---
|
||||
# Service
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: myapp
|
||||
namespace: myapp
|
||||
labels:
|
||||
app: myapp
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: myapp
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 8080
|
||||
protocol: TCP
|
||||
|
||||
---
|
||||
# Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: myapp
|
||||
namespace: myapp
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: nginx
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- myapp.example.com
|
||||
secretName: myapp-tls
|
||||
rules:
|
||||
- host: myapp.example.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: myapp
|
||||
port:
|
||||
number: 80
|
||||
|
||||
---
|
||||
# HorizontalPodAutoscaler
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: myapp
|
||||
namespace: myapp
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: myapp
|
||||
minReplicas: 3
|
||||
maxReplicas: 20
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
|
||||
---
|
||||
# PodDisruptionBudget
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: myapp
|
||||
namespace: myapp
|
||||
spec:
|
||||
minAvailable: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: myapp
|
||||
```
|
||||
|
||||
## CI/CD Pipeline Templates
|
||||
|
||||
### GitHub Actions - Docker Build and Push
|
||||
```yaml
|
||||
name: Build and Push Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha,prefix={{branch}}-
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
```
|
||||
|
||||
### GitHub Actions - Terraform Workflow
|
||||
```yaml
|
||||
name: Terraform
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'terraform/**'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'terraform/**'
|
||||
|
||||
env:
|
||||
TF_VERSION: 1.6.0
|
||||
AWS_REGION: us-east-1
|
||||
|
||||
jobs:
|
||||
terraform:
|
||||
name: Terraform Plan & Apply
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
|
||||
aws-region: ${{ env.AWS_REGION }}
|
||||
|
||||
- name: Setup Terraform
|
||||
uses: hashicorp/setup-terraform@v3
|
||||
with:
|
||||
terraform_version: ${{ env.TF_VERSION }}
|
||||
|
||||
- name: Terraform Format Check
|
||||
run: terraform fmt -check -recursive
|
||||
working-directory: ./terraform
|
||||
|
||||
- name: Terraform Init
|
||||
run: terraform init
|
||||
working-directory: ./terraform/environments/prod
|
||||
|
||||
- name: Terraform Validate
|
||||
run: terraform validate
|
||||
working-directory: ./terraform/environments/prod
|
||||
|
||||
- name: Terraform Plan
|
||||
id: plan
|
||||
run: |
|
||||
terraform plan -no-color -out=tfplan
|
||||
working-directory: ./terraform/environments/prod
|
||||
continue-on-error: true
|
||||
|
||||
- name: Post Plan to PR
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const output = `#### Terraform Plan 📖
|
||||
|
||||
<details><summary>Show Plan</summary>
|
||||
|
||||
\`\`\`terraform
|
||||
${{ steps.plan.outputs.stdout }}
|
||||
\`\`\`
|
||||
|
||||
</details>`;
|
||||
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: output
|
||||
});
|
||||
|
||||
- name: Terraform Apply
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
run: terraform apply -auto-approve tfplan
|
||||
working-directory: ./terraform/environments/prod
|
||||
```
|
||||
|
||||
### GitLab CI - Complete Pipeline
|
||||
```yaml
|
||||
stages:
|
||||
- lint
|
||||
- build
|
||||
- test
|
||||
- deploy
|
||||
|
||||
variables:
|
||||
DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
|
||||
KUBE_NAMESPACE: production
|
||||
|
||||
# Lint stage
|
||||
lint:terraform:
|
||||
stage: lint
|
||||
image: hashicorp/terraform:1.6
|
||||
script:
|
||||
- cd terraform
|
||||
- terraform fmt -check -recursive
|
||||
- terraform init -backend=false
|
||||
- terraform validate
|
||||
only:
|
||||
changes:
|
||||
- terraform/**/*
|
||||
|
||||
lint:kubernetes:
|
||||
stage: lint
|
||||
image: cytopia/kubeval:latest
|
||||
script:
|
||||
- kubeval --strict kubernetes/*.yaml
|
||||
only:
|
||||
changes:
|
||||
- kubernetes/**/*
|
||||
|
||||
# Build stage
|
||||
build:
|
||||
stage: build
|
||||
image: docker:24
|
||||
services:
|
||||
- docker:24-dind
|
||||
before_script:
|
||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||
script:
|
||||
- docker build -t $DOCKER_IMAGE .
|
||||
- docker push $DOCKER_IMAGE
|
||||
only:
|
||||
- main
|
||||
- develop
|
||||
|
||||
# Test stage
|
||||
test:unit:
|
||||
stage: test
|
||||
image: $DOCKER_IMAGE
|
||||
script:
|
||||
- npm test
|
||||
coverage: '/Statements\s*:\s*(\d+\.\d+)%/'
|
||||
only:
|
||||
- main
|
||||
- develop
|
||||
|
||||
test:security:
|
||||
stage: test
|
||||
image: aquasec/trivy:latest
|
||||
script:
|
||||
- trivy image --severity HIGH,CRITICAL $DOCKER_IMAGE
|
||||
only:
|
||||
- main
|
||||
- develop
|
||||
|
||||
# Deploy stage
|
||||
deploy:production:
|
||||
stage: deploy
|
||||
image: bitnami/kubectl:latest
|
||||
before_script:
|
||||
- kubectl config use-context production
|
||||
script:
|
||||
- kubectl set image deployment/myapp myapp=$DOCKER_IMAGE -n $KUBE_NAMESPACE
|
||||
- kubectl rollout status deployment/myapp -n $KUBE_NAMESPACE
|
||||
environment:
|
||||
name: production
|
||||
url: https://myapp.example.com
|
||||
only:
|
||||
- main
|
||||
when: manual
|
||||
```
|
||||
|
||||
## ArgoCD Application Template
|
||||
|
||||
```yaml
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: myapp
|
||||
namespace: argocd
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: default
|
||||
|
||||
source:
|
||||
repoURL: https://github.com/myorg/myapp.git
|
||||
targetRevision: HEAD
|
||||
path: kubernetes/overlays/production
|
||||
|
||||
# For Helm charts
|
||||
# helm:
|
||||
# valueFiles:
|
||||
# - values-production.yaml
|
||||
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: production
|
||||
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
allowEmpty: false
|
||||
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- PrunePropagationPolicy=foreground
|
||||
- PruneLast=true
|
||||
|
||||
retry:
|
||||
limit: 5
|
||||
backoff:
|
||||
duration: 5s
|
||||
factor: 2
|
||||
maxDuration: 3m
|
||||
|
||||
ignoreDifferences:
|
||||
- group: apps
|
||||
kind: Deployment
|
||||
jsonPointers:
|
||||
- /spec/replicas
|
||||
```
|
||||
|
||||
## Monitoring & Alerting Templates
|
||||
|
||||
### Prometheus ServiceMonitor
|
||||
```yaml
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: myapp
|
||||
namespace: production
|
||||
labels:
|
||||
app: myapp
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: myapp
|
||||
endpoints:
|
||||
- port: http
|
||||
path: /metrics
|
||||
interval: 30s
|
||||
scrapeTimeout: 10s
|
||||
```
|
||||
|
||||
### Prometheus Alert Rules
|
||||
```yaml
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: PrometheusRule
|
||||
metadata:
|
||||
name: myapp-alerts
|
||||
namespace: production
|
||||
spec:
|
||||
groups:
|
||||
- name: myapp
|
||||
interval: 30s
|
||||
rules:
|
||||
- alert: HighErrorRate
|
||||
expr: |
|
||||
rate(http_requests_total{status=~"5.."}[5m]) > 0.05
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "High error rate detected"
|
||||
description: "Error rate is {{ $value | humanizePercentage }}"
|
||||
|
||||
- alert: PodCrashLooping
|
||||
expr: |
|
||||
rate(kube_pod_container_status_restarts_total[15m]) > 0
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Pod {{ $labels.pod }} is crash looping"
|
||||
description: "Pod has restarted {{ $value }} times in 15 minutes"
|
||||
|
||||
- alert: HighMemoryUsage
|
||||
expr: |
|
||||
container_memory_usage_bytes{pod=~"myapp-.*"} /
|
||||
container_spec_memory_limit_bytes{pod=~"myapp-.*"} > 0.9
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "High memory usage"
|
||||
description: "Memory usage is {{ $value | humanizePercentage }}"
|
||||
```
|
||||
|
||||
## Docker Templates
|
||||
|
||||
### Multi-stage Dockerfile (Node.js)
|
||||
```dockerfile
|
||||
# Build stage
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files
|
||||
COPY package*.json ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm ci --only=production && \
|
||||
npm cache clean --force
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Build application
|
||||
RUN npm run build
|
||||
|
||||
# Production stage
|
||||
FROM node:20-alpine
|
||||
|
||||
# Install dumb-init for proper signal handling
|
||||
RUN apk add --no-cache dumb-init
|
||||
|
||||
# Create non-root user
|
||||
RUN addgroup -g 1000 nodejs && \
|
||||
adduser -u 1000 -G nodejs -s /bin/sh -D nodejs
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy dependencies and built app from builder
|
||||
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
|
||||
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
|
||||
COPY --from=builder --chown=nodejs:nodejs /app/package*.json ./
|
||||
|
||||
# Switch to non-root user
|
||||
USER nodejs
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8080
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
|
||||
CMD node healthcheck.js
|
||||
|
||||
# Use dumb-init to handle signals properly
|
||||
ENTRYPOINT ["dumb-init", "--"]
|
||||
|
||||
# Start application
|
||||
CMD ["node", "dist/index.js"]
|
||||
```
|
||||
|
||||
### .dockerignore
|
||||
```
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
# CI/CD
|
||||
.github
|
||||
.gitlab-ci.yml
|
||||
Jenkinsfile
|
||||
|
||||
# Documentation
|
||||
README.md
|
||||
docs/
|
||||
*.md
|
||||
|
||||
# Dependencies
|
||||
node_modules
|
||||
npm-debug.log
|
||||
|
||||
# Testing
|
||||
coverage/
|
||||
.nyc_output
|
||||
test/
|
||||
*.test.js
|
||||
|
||||
# IDE
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Build artifacts
|
||||
dist/
|
||||
build/
|
||||
*.log
|
||||
```
|
||||
|
||||
## Makefile Template
|
||||
|
||||
```makefile
|
||||
.PHONY: help
|
||||
help: ## Show this help
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
.PHONY: tf-init
|
||||
tf-init: ## Initialize Terraform
|
||||
cd terraform/environments/$(ENV) && terraform init
|
||||
|
||||
.PHONY: tf-plan
|
||||
tf-plan: ## Plan Terraform changes
|
||||
cd terraform/environments/$(ENV) && terraform plan -out=tfplan
|
||||
|
||||
.PHONY: tf-apply
|
||||
tf-apply: ## Apply Terraform changes
|
||||
cd terraform/environments/$(ENV) && terraform apply tfplan
|
||||
|
||||
.PHONY: k8s-apply
|
||||
k8s-apply: ## Apply Kubernetes manifests
|
||||
kubectl apply -f kubernetes/ -n $(NAMESPACE)
|
||||
|
||||
.PHONY: k8s-delete
|
||||
k8s-delete: ## Delete Kubernetes resources
|
||||
kubectl delete -f kubernetes/ -n $(NAMESPACE)
|
||||
|
||||
.PHONY: docker-build
|
||||
docker-build: ## Build Docker image
|
||||
docker build -t $(IMAGE_NAME):$(TAG) .
|
||||
|
||||
.PHONY: docker-push
|
||||
docker-push: ## Push Docker image
|
||||
docker push $(IMAGE_NAME):$(TAG)
|
||||
|
||||
.PHONY: lint
|
||||
lint: ## Run linters
|
||||
terraform fmt -check -recursive
|
||||
kubeval --strict kubernetes/*.yaml
|
||||
|
||||
.PHONY: test
|
||||
test: ## Run tests
|
||||
go test ./... -v -cover
|
||||
|
||||
.PHONY: clean
|
||||
clean: ## Clean build artifacts
|
||||
rm -rf dist/ build/ *.log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Template Usage Tips
|
||||
|
||||
1. **Always customize** - Don't use templates as-is, adapt to your needs
|
||||
2. **Security first** - Never commit secrets, use proper secret management
|
||||
3. **Test in dev** - Validate templates in development before production
|
||||
4. **Version control** - Track all infrastructure code in Git
|
||||
5. **Documentation** - Add README files explaining template usage
|
||||
6. **Automation** - Use CI/CD to validate and deploy templates
|
||||
@@ -0,0 +1,725 @@
|
||||
# Terraform Best Practices & Patterns
|
||||
|
||||
## Terraform Project Structure
|
||||
|
||||
### Standard Module Structure
|
||||
```
|
||||
terraform/
|
||||
├── environments/
|
||||
│ ├── dev/
|
||||
│ │ ├── main.tf
|
||||
│ │ ├── variables.tf
|
||||
│ │ ├── outputs.tf
|
||||
│ │ ├── terraform.tfvars
|
||||
│ │ └── backend.tf
|
||||
│ ├── staging/
|
||||
│ └── prod/
|
||||
├── modules/
|
||||
│ ├── vpc/
|
||||
│ │ ├── main.tf
|
||||
│ │ ├── variables.tf
|
||||
│ │ ├── outputs.tf
|
||||
│ │ └── README.md
|
||||
│ ├── eks/
|
||||
│ ├── rds/
|
||||
│ └── s3-bucket/
|
||||
└── README.md
|
||||
```
|
||||
|
||||
**Why this structure?**
|
||||
- Separates environment configurations for isolation
|
||||
- Promotes module reusability across environments
|
||||
- Makes state management cleaner (one state per environment)
|
||||
- Enables environment-specific variable overrides
|
||||
|
||||
### Module Development Template
|
||||
|
||||
**modules/vpc/main.tf:**
|
||||
```hcl
|
||||
# VPC
|
||||
resource "aws_vpc" "main" {
|
||||
cidr_block = var.vpc_cidr
|
||||
enable_dns_hostnames = var.enable_dns_hostnames
|
||||
enable_dns_support = var.enable_dns_support
|
||||
|
||||
tags = merge(
|
||||
var.tags,
|
||||
{
|
||||
Name = "${var.name_prefix}-vpc"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
# Public Subnets
|
||||
resource "aws_subnet" "public" {
|
||||
count = length(var.public_subnet_cidrs)
|
||||
vpc_id = aws_vpc.main.id
|
||||
cidr_block = var.public_subnet_cidrs[count.index]
|
||||
availability_zone = var.availability_zones[count.index]
|
||||
map_public_ip_on_launch = true
|
||||
|
||||
tags = merge(
|
||||
var.tags,
|
||||
{
|
||||
Name = "${var.name_prefix}-public-${var.availability_zones[count.index]}"
|
||||
Type = "public"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
# Private Subnets
|
||||
resource "aws_subnet" "private" {
|
||||
count = length(var.private_subnet_cidrs)
|
||||
vpc_id = aws_vpc.main.id
|
||||
cidr_block = var.private_subnet_cidrs[count.index]
|
||||
availability_zone = var.availability_zones[count.index]
|
||||
|
||||
tags = merge(
|
||||
var.tags,
|
||||
{
|
||||
Name = "${var.name_prefix}-private-${var.availability_zones[count.index]}"
|
||||
Type = "private"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
# Internet Gateway
|
||||
resource "aws_internet_gateway" "main" {
|
||||
count = length(var.public_subnet_cidrs) > 0 ? 1 : 0
|
||||
vpc_id = aws_vpc.main.id
|
||||
|
||||
tags = merge(
|
||||
var.tags,
|
||||
{
|
||||
Name = "${var.name_prefix}-igw"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
# NAT Gateways
|
||||
resource "aws_eip" "nat" {
|
||||
count = var.enable_nat_gateway ? var.single_nat_gateway ? 1 : length(var.availability_zones) : 0
|
||||
domain = "vpc"
|
||||
|
||||
tags = merge(
|
||||
var.tags,
|
||||
{
|
||||
Name = "${var.name_prefix}-nat-eip-${count.index + 1}"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
resource "aws_nat_gateway" "main" {
|
||||
count = var.enable_nat_gateway ? var.single_nat_gateway ? 1 : length(var.availability_zones) : 0
|
||||
allocation_id = aws_eip.nat[count.index].id
|
||||
subnet_id = aws_subnet.public[count.index].id
|
||||
|
||||
tags = merge(
|
||||
var.tags,
|
||||
{
|
||||
Name = "${var.name_prefix}-nat-${count.index + 1}"
|
||||
}
|
||||
)
|
||||
|
||||
depends_on = [aws_internet_gateway.main]
|
||||
}
|
||||
```
|
||||
|
||||
**modules/vpc/variables.tf:**
|
||||
```hcl
|
||||
variable "name_prefix" {
|
||||
description = "Prefix for resource names"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "vpc_cidr" {
|
||||
description = "CIDR block for VPC"
|
||||
type = string
|
||||
validation {
|
||||
condition = can(cidrhost(var.vpc_cidr, 0))
|
||||
error_message = "Must be a valid IPv4 CIDR block."
|
||||
}
|
||||
}
|
||||
|
||||
variable "availability_zones" {
|
||||
description = "List of availability zones"
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
variable "public_subnet_cidrs" {
|
||||
description = "CIDR blocks for public subnets"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "private_subnet_cidrs" {
|
||||
description = "CIDR blocks for private subnets"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "enable_nat_gateway" {
|
||||
description = "Enable NAT Gateway for private subnets"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "single_nat_gateway" {
|
||||
description = "Use single NAT gateway for all AZs (cost optimization)"
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "enable_dns_hostnames" {
|
||||
description = "Enable DNS hostnames in VPC"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "enable_dns_support" {
|
||||
description = "Enable DNS support in VPC"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
description = "Tags to apply to all resources"
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
```
|
||||
|
||||
**modules/vpc/outputs.tf:**
|
||||
```hcl
|
||||
output "vpc_id" {
|
||||
description = "ID of the VPC"
|
||||
value = aws_vpc.main.id
|
||||
}
|
||||
|
||||
output "vpc_cidr" {
|
||||
description = "CIDR block of the VPC"
|
||||
value = aws_vpc.main.cidr_block
|
||||
}
|
||||
|
||||
output "public_subnet_ids" {
|
||||
description = "IDs of public subnets"
|
||||
value = aws_subnet.public[*].id
|
||||
}
|
||||
|
||||
output "private_subnet_ids" {
|
||||
description = "IDs of private subnets"
|
||||
value = aws_subnet.private[*].id
|
||||
}
|
||||
|
||||
output "nat_gateway_ids" {
|
||||
description = "IDs of NAT Gateways"
|
||||
value = aws_nat_gateway.main[*].id
|
||||
}
|
||||
```
|
||||
|
||||
## State Management Best Practices
|
||||
|
||||
### Remote State Configuration (S3 + DynamoDB)
|
||||
|
||||
**environments/prod/backend.tf:**
|
||||
```hcl
|
||||
terraform {
|
||||
backend "s3" {
|
||||
bucket = "company-terraform-state"
|
||||
key = "prod/terraform.tfstate"
|
||||
region = "us-east-1"
|
||||
encrypt = true
|
||||
dynamodb_table = "terraform-state-lock"
|
||||
|
||||
# Enable versioning on the S3 bucket for state history
|
||||
# Enable server-side encryption
|
||||
# Use DynamoDB for state locking
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### State Locking Setup
|
||||
```bash
|
||||
# Create S3 bucket for state
|
||||
aws s3api create-bucket \
|
||||
--bucket company-terraform-state \
|
||||
--region us-east-1
|
||||
|
||||
# Enable versioning
|
||||
aws s3api put-bucket-versioning \
|
||||
--bucket company-terraform-state \
|
||||
--versioning-configuration Status=Enabled
|
||||
|
||||
# Enable encryption
|
||||
aws s3api put-bucket-encryption \
|
||||
--bucket company-terraform-state \
|
||||
--server-side-encryption-configuration '{
|
||||
"Rules": [{
|
||||
"ApplyServerSideEncryptionByDefault": {
|
||||
"SSEAlgorithm": "AES256"
|
||||
}
|
||||
}]
|
||||
}'
|
||||
|
||||
# Create DynamoDB table for locking
|
||||
aws dynamodb create-table \
|
||||
--table-name terraform-state-lock \
|
||||
--attribute-definitions AttributeName=LockID,AttributeType=S \
|
||||
--key-schema AttributeName=LockID,KeyType=HASH \
|
||||
--billing-mode PAY_PER_REQUEST \
|
||||
--region us-east-1
|
||||
```
|
||||
|
||||
## Advanced Terraform Patterns
|
||||
|
||||
### Data Sources for Dynamic Configuration
|
||||
```hcl
|
||||
# Get latest Amazon Linux 2 AMI
|
||||
data "aws_ami" "amazon_linux_2" {
|
||||
most_recent = true
|
||||
owners = ["amazon"]
|
||||
|
||||
filter {
|
||||
name = "name"
|
||||
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
|
||||
}
|
||||
|
||||
filter {
|
||||
name = "virtualization-type"
|
||||
values = ["hvm"]
|
||||
}
|
||||
}
|
||||
|
||||
# Get current AWS account ID
|
||||
data "aws_caller_identity" "current" {}
|
||||
|
||||
# Get current AWS region
|
||||
data "aws_region" "current" {}
|
||||
|
||||
# Get available availability zones
|
||||
data "aws_availability_zones" "available" {
|
||||
state = "available"
|
||||
}
|
||||
```
|
||||
|
||||
### Dynamic Blocks
|
||||
```hcl
|
||||
resource "aws_security_group" "app" {
|
||||
name = "app-sg"
|
||||
description = "Security group for application"
|
||||
vpc_id = var.vpc_id
|
||||
|
||||
dynamic "ingress" {
|
||||
for_each = var.ingress_rules
|
||||
content {
|
||||
from_port = ingress.value.from_port
|
||||
to_port = ingress.value.to_port
|
||||
protocol = ingress.value.protocol
|
||||
cidr_blocks = ingress.value.cidr_blocks
|
||||
description = ingress.value.description
|
||||
}
|
||||
}
|
||||
|
||||
egress {
|
||||
from_port = 0
|
||||
to_port = 0
|
||||
protocol = "-1"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
}
|
||||
|
||||
# Variable definition
|
||||
variable "ingress_rules" {
|
||||
description = "List of ingress rules"
|
||||
type = list(object({
|
||||
from_port = number
|
||||
to_port = number
|
||||
protocol = string
|
||||
cidr_blocks = list(string)
|
||||
description = string
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
### Conditional Resource Creation
|
||||
```hcl
|
||||
# Create resource only in production
|
||||
resource "aws_db_instance" "replica" {
|
||||
count = var.environment == "prod" ? var.replica_count : 0
|
||||
|
||||
identifier = "${var.name_prefix}-replica-${count.index + 1}"
|
||||
replicate_source_db = aws_db_instance.primary.identifier
|
||||
instance_class = var.replica_instance_class
|
||||
publicly_accessible = false
|
||||
skip_final_snapshot = true
|
||||
}
|
||||
```
|
||||
|
||||
### Lifecycle Management
|
||||
```hcl
|
||||
resource "aws_instance" "web" {
|
||||
ami = data.aws_ami.amazon_linux_2.id
|
||||
instance_type = var.instance_type
|
||||
|
||||
lifecycle {
|
||||
# Prevent accidental deletion of critical resources
|
||||
prevent_destroy = true
|
||||
|
||||
# Create new resource before destroying old one
|
||||
create_before_destroy = true
|
||||
|
||||
# Ignore changes to specific attributes
|
||||
ignore_changes = [
|
||||
ami, # Allow AMI updates outside Terraform
|
||||
tags["LastModified"],
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### Never Hardcode Credentials
|
||||
```hcl
|
||||
# ❌ BAD - Hardcoded credentials
|
||||
resource "aws_db_instance" "bad" {
|
||||
username = "admin"
|
||||
password = "SuperSecret123!" # Never do this!
|
||||
}
|
||||
|
||||
# ✅ GOOD - Use AWS Secrets Manager
|
||||
data "aws_secretsmanager_secret_version" "db_password" {
|
||||
secret_id = "prod/db/master-password"
|
||||
}
|
||||
|
||||
resource "aws_db_instance" "good" {
|
||||
username = "admin"
|
||||
password = jsondecode(data.aws_secretsmanager_secret_version.db_password.secret_string)["password"]
|
||||
}
|
||||
|
||||
# ✅ BETTER - Use random password and store in Secrets Manager
|
||||
resource "random_password" "db_password" {
|
||||
length = 32
|
||||
special = true
|
||||
}
|
||||
|
||||
resource "aws_secretsmanager_secret" "db_password" {
|
||||
name = "${var.name_prefix}-db-password"
|
||||
}
|
||||
|
||||
resource "aws_secretsmanager_secret_version" "db_password" {
|
||||
secret_id = aws_secretsmanager_secret.db_password.id
|
||||
secret_string = random_password.db_password.result
|
||||
}
|
||||
|
||||
resource "aws_db_instance" "best" {
|
||||
username = "admin"
|
||||
password = random_password.db_password.result
|
||||
}
|
||||
```
|
||||
|
||||
### Encryption Best Practices
|
||||
```hcl
|
||||
# S3 Bucket with encryption
|
||||
resource "aws_s3_bucket" "data" {
|
||||
bucket = "${var.name_prefix}-data-bucket"
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
|
||||
bucket = aws_s3_bucket.data.id
|
||||
|
||||
rule {
|
||||
apply_server_side_encryption_by_default {
|
||||
sse_algorithm = "aws:kms"
|
||||
kms_master_key_id = aws_kms_key.s3.arn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# EBS encryption
|
||||
resource "aws_ebs_volume" "data" {
|
||||
availability_zone = var.availability_zone
|
||||
size = var.volume_size
|
||||
encrypted = true
|
||||
kms_key_id = aws_kms_key.ebs.arn
|
||||
}
|
||||
|
||||
# RDS encryption
|
||||
resource "aws_db_instance" "main" {
|
||||
allocated_storage = var.allocated_storage
|
||||
storage_encrypted = true
|
||||
kms_key_id = aws_kms_key.rds.arn
|
||||
}
|
||||
```
|
||||
|
||||
## Cost Optimization Patterns
|
||||
|
||||
### Spot Instances with Auto Scaling
|
||||
```hcl
|
||||
resource "aws_launch_template" "app" {
|
||||
name_prefix = "${var.name_prefix}-"
|
||||
image_id = data.aws_ami.amazon_linux_2.id
|
||||
instance_type = var.instance_type
|
||||
|
||||
instance_market_options {
|
||||
market_type = "spot"
|
||||
spot_options {
|
||||
max_price = var.spot_max_price
|
||||
spot_instance_type = "one-time"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_autoscaling_group" "app" {
|
||||
name = "${var.name_prefix}-asg"
|
||||
vpc_zone_identifier = var.private_subnet_ids
|
||||
min_size = var.min_size
|
||||
max_size = var.max_size
|
||||
desired_capacity = var.desired_capacity
|
||||
|
||||
mixed_instances_policy {
|
||||
instances_distribution {
|
||||
on_demand_base_capacity = 1
|
||||
on_demand_percentage_above_base_capacity = 20
|
||||
spot_allocation_strategy = "capacity-optimized"
|
||||
}
|
||||
|
||||
launch_template {
|
||||
launch_template_specification {
|
||||
launch_template_id = aws_launch_template.app.id
|
||||
version = "$Latest"
|
||||
}
|
||||
|
||||
override {
|
||||
instance_type = "t3.medium"
|
||||
}
|
||||
|
||||
override {
|
||||
instance_type = "t3a.medium"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Resource Tagging for Cost Allocation
|
||||
```hcl
|
||||
locals {
|
||||
common_tags = {
|
||||
Environment = var.environment
|
||||
Project = var.project_name
|
||||
ManagedBy = "Terraform"
|
||||
CostCenter = var.cost_center
|
||||
Owner = var.owner
|
||||
Application = var.application_name
|
||||
CreatedDate = timestamp()
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_instance" "web" {
|
||||
ami = data.aws_ami.amazon_linux_2.id
|
||||
instance_type = var.instance_type
|
||||
|
||||
tags = merge(
|
||||
local.common_tags,
|
||||
{
|
||||
Name = "${var.name_prefix}-web-server"
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Testing and Validation
|
||||
|
||||
### Input Validation
|
||||
```hcl
|
||||
variable "environment" {
|
||||
description = "Environment name"
|
||||
type = string
|
||||
validation {
|
||||
condition = contains(["dev", "staging", "prod"], var.environment)
|
||||
error_message = "Environment must be dev, staging, or prod."
|
||||
}
|
||||
}
|
||||
|
||||
variable "instance_type" {
|
||||
description = "EC2 instance type"
|
||||
type = string
|
||||
validation {
|
||||
condition = can(regex("^[tm][3-6]\\.(nano|micro|small|medium|large|xlarge|2xlarge)$", var.instance_type))
|
||||
error_message = "Instance type must be a valid t3-t6 or m3-m6 type."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pre-commit Hooks
|
||||
```yaml
|
||||
# .pre-commit-config.yaml
|
||||
repos:
|
||||
- repo: https://github.com/antonbabenko/pre-commit-terraform
|
||||
rev: v1.77.0
|
||||
hooks:
|
||||
- id: terraform_fmt
|
||||
- id: terraform_validate
|
||||
- id: terraform_docs
|
||||
- id: terraform_tflint
|
||||
- id: terraform_checkov
|
||||
```
|
||||
|
||||
### CI/CD Pipeline Validation
|
||||
```yaml
|
||||
# .github/workflows/terraform.yml
|
||||
name: Terraform CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'terraform/**'
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Terraform
|
||||
uses: hashicorp/setup-terraform@v2
|
||||
with:
|
||||
terraform_version: 1.6.0
|
||||
|
||||
- name: Terraform Format Check
|
||||
run: terraform fmt -check -recursive
|
||||
|
||||
- name: Terraform Init
|
||||
run: terraform init -backend=false
|
||||
working-directory: ./terraform/environments/dev
|
||||
|
||||
- name: Terraform Validate
|
||||
run: terraform validate
|
||||
working-directory: ./terraform/environments/dev
|
||||
|
||||
- name: Run Checkov
|
||||
uses: bridgecrewio/checkov-action@master
|
||||
with:
|
||||
directory: terraform/
|
||||
framework: terraform
|
||||
```
|
||||
|
||||
## Common Anti-Patterns to Avoid
|
||||
|
||||
### ❌ Don't: Manage state manually
|
||||
- Never edit `.tfstate` files directly
|
||||
- Never commit state files to Git
|
||||
- Always use remote state with locking
|
||||
|
||||
### ❌ Don't: Use count for stateful resources
|
||||
```hcl
|
||||
# Bad - Changes in list order cause resource recreation
|
||||
resource "aws_instance" "web" {
|
||||
count = length(var.instance_names)
|
||||
# ...
|
||||
}
|
||||
|
||||
# Good - Use for_each for stable resource addressing
|
||||
resource "aws_instance" "web" {
|
||||
for_each = toset(var.instance_names)
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ Don't: Create monolithic configurations
|
||||
- Split large configurations into modules
|
||||
- Separate concerns (networking, compute, data)
|
||||
- Use module composition
|
||||
|
||||
### ❌ Don't: Ignore drift detection
|
||||
```bash
|
||||
# Run regularly to detect drift
|
||||
terraform plan -out=tfplan
|
||||
terraform show -json tfplan | jq '.resource_changes[] | select(.change.actions != ["no-op"])'
|
||||
```
|
||||
|
||||
## Terraform Commands Cheat Sheet
|
||||
|
||||
```bash
|
||||
# Initialize and download providers
|
||||
terraform init
|
||||
|
||||
# Upgrade providers to latest versions
|
||||
terraform init -upgrade
|
||||
|
||||
# Format code
|
||||
terraform fmt -recursive
|
||||
|
||||
# Validate configuration
|
||||
terraform validate
|
||||
|
||||
# Plan changes
|
||||
terraform plan -out=tfplan
|
||||
|
||||
# Apply changes
|
||||
terraform apply tfplan
|
||||
|
||||
# Destroy infrastructure
|
||||
terraform destroy
|
||||
|
||||
# Import existing resource
|
||||
terraform import aws_instance.example i-1234567890abcdef0
|
||||
|
||||
# Show current state
|
||||
terraform show
|
||||
|
||||
# List resources in state
|
||||
terraform state list
|
||||
|
||||
# Remove resource from state (without destroying)
|
||||
terraform state rm aws_instance.example
|
||||
|
||||
# Refresh state
|
||||
terraform refresh
|
||||
|
||||
# Output values
|
||||
terraform output
|
||||
|
||||
# Workspace commands
|
||||
terraform workspace new prod
|
||||
terraform workspace select prod
|
||||
terraform workspace list
|
||||
```
|
||||
|
||||
## Advanced Topics
|
||||
|
||||
### Terraform Cloud/Enterprise
|
||||
- Remote execution
|
||||
- Policy as code with Sentinel
|
||||
- Private module registry
|
||||
- Cost estimation
|
||||
- VCS integration
|
||||
|
||||
### Multi-Environment Strategy
|
||||
1. **Workspaces**: Simple, same code, different state
|
||||
2. **Directories**: More isolation, easier to understand
|
||||
3. **Branches**: Git-based separation (not recommended)
|
||||
|
||||
**Recommended: Directory-based with shared modules**
|
||||
|
||||
### Dependency Management
|
||||
```hcl
|
||||
# Implicit dependency (Terraform detects automatically)
|
||||
resource "aws_instance" "web" {
|
||||
subnet_id = aws_subnet.public.id
|
||||
}
|
||||
|
||||
# Explicit dependency (when needed)
|
||||
resource "aws_instance" "web" {
|
||||
# ...
|
||||
depends_on = [aws_iam_role_policy_attachment.example]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
- [Terraform Registry](https://registry.terraform.io/)
|
||||
- [Terraform AWS Provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs)
|
||||
- [Terraform Best Practices](https://www.terraform-best-practices.com/)
|
||||
Reference in New Issue
Block a user