Environment Management¶
This guide provides comprehensive strategies for managing multiple environments in Temporal.io deployments using GitOps patterns, ensuring consistent, scalable, and secure infrastructure across development, staging, and production environments.
Overview¶
Environment management in GitOps involves: - Declarative environment definitions - Automated promotion pipelines - Environment-specific configurations - Security boundaries and access controls - Monitoring and observability per environment - Disaster recovery and backup strategies
Architecture¶
graph TB
subgraph "Git Repositories"
INFRA[Infrastructure Repo]
CONFIG[Config Repo]
APP[Application Repo]
ENVDEF[Environment Definitions]
end
subgraph "ArgoCD"
ARGOCD[ArgoCD Controller]
APPSETS[ApplicationSets]
PROJECTS[App Projects]
end
subgraph "Development Environment"
DEV_K8S[Dev Kubernetes]
DEV_TEMPORAL[Dev Temporal Cluster]
DEV_WORKERS[Dev Workers]
DEV_DB[(Dev Database)]
end
subgraph "Staging Environment"
STAGE_K8S[Staging Kubernetes]
STAGE_TEMPORAL[Staging Temporal Cluster]
STAGE_WORKERS[Staging Workers]
STAGE_DB[(Staging Database)]
end
subgraph "Production Environment"
PROD_K8S[Production Kubernetes]
PROD_TEMPORAL[Production Temporal Cluster]
PROD_WORKERS[Production Workers]
PROD_DB[(Production Database)]
end
subgraph "Monitoring Stack"
PROMETHEUS[Prometheus]
GRAFANA[Grafana]
ALERTMANAGER[AlertManager]
end
INFRA --> ARGOCD
CONFIG --> ARGOCD
APP --> ARGOCD
ENVDEF --> ARGOCD
ARGOCD --> APPSETS
APPSETS --> PROJECTS
PROJECTS --> DEV_K8S
PROJECTS --> STAGE_K8S
PROJECTS --> PROD_K8S
DEV_K8S --> DEV_TEMPORAL
DEV_TEMPORAL --> DEV_WORKERS
DEV_TEMPORAL --> DEV_DB
STAGE_K8S --> STAGE_TEMPORAL
STAGE_TEMPORAL --> STAGE_WORKERS
STAGE_TEMPORAL --> STAGE_DB
PROD_K8S --> PROD_TEMPORAL
PROD_TEMPORAL --> PROD_WORKERS
PROD_TEMPORAL --> PROD_DB
DEV_K8S --> PROMETHEUS
STAGE_K8S --> PROMETHEUS
PROD_K8S --> PROMETHEUS
PROMETHEUS --> GRAFANA
PROMETHEUS --> ALERTMANAGER
Environment Strategy¶
Environment Types¶
Development Environment¶
- Purpose: Feature development and testing
- Characteristics:
- Shared resources
- Relaxed security policies
- Frequent deployments
- Cost-optimized configurations
- Extended logging and debugging
Staging Environment¶
- Purpose: Pre-production validation
- Characteristics:
- Production-like configuration
- Performance testing
- Security validation
- Data migration testing
- User acceptance testing
Production Environment¶
- Purpose: Live workloads
- Characteristics:
- High availability
- Security hardened
- Performance optimized
- Comprehensive monitoring
- Disaster recovery enabled
Environment Lifecycle¶
graph LR
DEV[Development] --> STAGE[Staging]
STAGE --> PROD[Production]
subgraph "Promotion Gates"
TESTS[Automated Tests]
SECURITY[Security Scans]
APPROVAL[Manual Approval]
end
DEV --> TESTS
TESTS --> STAGE
STAGE --> SECURITY
SECURITY --> APPROVAL
APPROVAL --> PROD
Repository Structure¶
Environment Configuration Repository¶
temporal-environments/
├── environments/
│ ├── dev/
│ │ ├── kustomization.yaml
│ │ ├── namespace.yaml
│ │ ├── temporal/
│ │ │ ├── values.yaml
│ │ │ ├── server-config.yaml
│ │ │ └── worker-config.yaml
│ │ ├── observability/
│ │ │ ├── prometheus.yaml
│ │ │ ├── grafana.yaml
│ │ │ └── jaeger.yaml
│ │ └── security/
│ │ ├── rbac.yaml
│ │ ├── network-policies.yaml
│ │ └── pod-security.yaml
│ ├── staging/
│ │ ├── kustomization.yaml
│ │ ├── namespace.yaml
│ │ ├── temporal/
│ │ │ ├── values.yaml
│ │ │ ├── server-config.yaml
│ │ │ └── worker-config.yaml
│ │ ├── observability/
│ │ │ ├── prometheus.yaml
│ │ │ ├── grafana.yaml
│ │ │ └── jaeger.yaml
│ │ └── security/
│ │ ├── rbac.yaml
│ │ ├── network-policies.yaml
│ │ └── pod-security.yaml
│ └── production/
│ ├── kustomization.yaml
│ ├── namespace.yaml
│ ├── temporal/
│ │ ├── values.yaml
│ │ ├── server-config.yaml
│ │ └── worker-config.yaml
│ ├── observability/
│ │ ├── prometheus.yaml
│ │ ├── grafana.yaml
│ │ └── jaeger.yaml
│ └── security/
│ ├── rbac.yaml
│ ├── network-policies.yaml
│ └── pod-security.yaml
├── base/
│ ├── temporal/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ ├── configmap.yaml
│ │ └── ingress.yaml
│ ├── observability/
│ │ ├── prometheus/
│ │ │ ├── deployment.yaml
│ │ │ ├── configmap.yaml
│ │ │ └── service.yaml
│ │ └── grafana/
│ │ ├── deployment.yaml
│ │ ├── configmap.yaml
│ │ └── service.yaml
│ └── security/
│ ├── rbac-template.yaml
│ ├── network-policy-template.yaml
│ └── pod-security-template.yaml
├── argocd/
│ ├── applications/
│ │ ├── dev-environment.yaml
│ │ ├── staging-environment.yaml
│ │ └── production-environment.yaml
│ ├── applicationsets/
│ │ └── temporal-environments.yaml
│ └── projects/
│ ├── temporal-dev.yaml
│ ├── temporal-staging.yaml
│ └── temporal-production.yaml
└── scripts/
├── promote-environment.sh
├── validate-environment.sh
└── rollback-environment.sh
Environment Definitions¶
Base Configuration Template¶
# base/temporal/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: temporal-server
labels:
app: temporal-server
component: server
spec:
selector:
matchLabels:
app: temporal-server
template:
metadata:
labels:
app: temporal-server
component: server
spec:
containers:
- name: temporal
image: temporalio/auto-setup:1.22.0
ports:
- containerPort: 7233
name: rpc
- containerPort: 7234
name: membership
- containerPort: 7235
name: history
- containerPort: 7239
name: worker
env:
- name: DB
value: "postgresql"
- name: DB_PORT
value: "5432"
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: temporal-postgres
key: username
- name: POSTGRES_PWD
valueFrom:
secretKeyRef:
name: temporal-postgres
key: password
- name: POSTGRES_SEEDS
valueFrom:
configMapKeyRef:
name: temporal-config
key: postgres-hosts
- name: DYNAMIC_CONFIG_FILE_PATH
value: config/dynamicconfig/development.yaml
volumeMounts:
- name: config
mountPath: /etc/temporal/config
- name: dynamic-config
mountPath: /etc/temporal/config/dynamicconfig
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
volumes:
- name: config
configMap:
name: temporal-config
- name: dynamic-config
configMap:
name: temporal-dynamic-config
Development Environment¶
# environments/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: temporal-dev
resources:
- ../../base/temporal
- ../../base/observability
- namespace.yaml
patchesStrategicMerge:
- temporal/values.yaml
configMapGenerator:
- name: temporal-config
files:
- temporal/server-config.yaml
- name: temporal-dynamic-config
files:
- temporal/dynamic-config.yaml
images:
- name: temporalio/auto-setup
newTag: 1.22.0-dev
replicas:
- name: temporal-server
count: 1
commonLabels:
environment: development
project: temporal
# environments/dev/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: temporal-dev
labels:
name: temporal-dev
environment: development
project: temporal
annotations:
argocd.argoproj.io/sync-wave: "0"
# environments/dev/temporal/values.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: temporal-server
spec:
replicas: 1
template:
spec:
containers:
- name: temporal
env:
- name: LOG_LEVEL
value: debug
- name: SERVICES
value: history,matching,worker,frontend
- name: TEMPORAL_CLI_ADDRESS
value: temporal-server:7233
resources:
requests:
memory: 256Mi
cpu: 100m
limits:
memory: 512Mi
cpu: 250m
Staging Environment¶
# environments/staging/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: temporal-staging
resources:
- ../../base/temporal
- ../../base/observability
- ../../base/security
- namespace.yaml
patchesStrategicMerge:
- temporal/values.yaml
- security/rbac.yaml
configMapGenerator:
- name: temporal-config
files:
- temporal/server-config.yaml
- name: temporal-dynamic-config
files:
- temporal/dynamic-config.yaml
secretGenerator:
- name: temporal-tls
files:
- temporal/tls/ca.crt
- temporal/tls/tls.crt
- temporal/tls/tls.key
images:
- name: temporalio/auto-setup
newTag: 1.22.0
replicas:
- name: temporal-server
count: 2
commonLabels:
environment: staging
project: temporal
# environments/staging/temporal/values.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: temporal-server
spec:
replicas: 2
template:
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: temporal-server
topologyKey: kubernetes.io/hostname
containers:
- name: temporal
env:
- name: LOG_LEVEL
value: info
- name: SERVICES
value: history,matching,worker,frontend
- name: TLS_ENABLED
value: "true"
- name: TLS_CERT_FILE
value: /etc/temporal/tls/tls.crt
- name: TLS_KEY_FILE
value: /etc/temporal/tls/tls.key
- name: TLS_CA_FILE
value: /etc/temporal/tls/ca.crt
volumeMounts:
- name: tls
mountPath: /etc/temporal/tls
readOnly: true
resources:
requests:
memory: 512Mi
cpu: 250m
limits:
memory: 1Gi
cpu: 500m
volumes:
- name: tls
secret:
secretName: temporal-tls
Production Environment¶
# environments/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: temporal-production
resources:
- ../../base/temporal
- ../../base/observability
- ../../base/security
- namespace.yaml
patchesStrategicMerge:
- temporal/values.yaml
- security/rbac.yaml
- security/network-policies.yaml
- security/pod-security.yaml
configMapGenerator:
- name: temporal-config
files:
- temporal/server-config.yaml
- name: temporal-dynamic-config
files:
- temporal/dynamic-config.yaml
secretGenerator:
- name: temporal-tls
files:
- temporal/tls/ca.crt
- temporal/tls/tls.crt
- temporal/tls/tls.key
images:
- name: temporalio/auto-setup
newTag: 1.22.0
replicas:
- name: temporal-server
count: 3
commonLabels:
environment: production
project: temporal
# environments/production/temporal/values.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: temporal-server
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: temporal-server
topologyKey: kubernetes.io/hostname
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-type
operator: In
values:
- temporal
tolerations:
- key: temporal
operator: Equal
value: "true"
effect: NoSchedule
containers:
- name: temporal
env:
- name: LOG_LEVEL
value: warn
- name: SERVICES
value: history,matching,worker,frontend
- name: TLS_ENABLED
value: "true"
- name: TLS_CERT_FILE
value: /etc/temporal/tls/tls.crt
- name: TLS_KEY_FILE
value: /etc/temporal/tls/tls.key
- name: TLS_CA_FILE
value: /etc/temporal/tls/ca.crt
- name: TEMPORAL_BROADCAST_ADDRESS
valueFrom:
fieldRef:
fieldPath: status.podIP
volumeMounts:
- name: tls
mountPath: /etc/temporal/tls
readOnly: true
resources:
requests:
memory: 2Gi
cpu: 1000m
limits:
memory: 4Gi
cpu: 2000m
livenessProbe:
exec:
command:
- temporal
- workflow
- list
- --namespace
- default
initialDelaySeconds: 60
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
readinessProbe:
exec:
command:
- temporal
- workflow
- list
- --namespace
- default
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
volumes:
- name: tls
secret:
secretName: temporal-tls
ArgoCD ApplicationSets¶
Environment ApplicationSet¶
# argocd/applicationsets/temporal-environments.yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: temporal-environments
namespace: argocd
spec:
generators:
- git:
repoURL: https://github.com/company/temporal-environments
revision: HEAD
directories:
- path: environments/*
- matrix:
generators:
- git:
repoURL: https://github.com/company/temporal-environments
revision: HEAD
directories:
- path: environments/*
- clusters: {}
template:
metadata:
name: '{{path.basename}}-{{cluster.name}}'
labels:
environment: '{{path.basename}}'
cluster: '{{cluster.name}}'
spec:
project: 'temporal-{{path.basename}}'
source:
repoURL: https://github.com/company/temporal-environments
targetRevision: HEAD
path: '{{path}}'
destination:
server: '{{cluster.server}}'
namespace: 'temporal-{{path.basename}}'
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ApplyOutOfSyncOnly=true
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas
syncPolicy:
preserveResourcesOnDeletion: false
Progressive Deployment ApplicationSet¶
# argocd/applicationsets/progressive-deployment.yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: temporal-progressive-deployment
namespace: argocd
spec:
generators:
- pullRequest:
github:
owner: company
repo: temporal-environments
tokenRef:
secretName: github-token
key: token
requeueAfterSeconds: 300
template:
metadata:
name: 'pr-{{number}}-{{head_sha}}'
labels:
pull-request: '{{number}}'
branch: '{{branch}}'
spec:
project: temporal-dev
source:
repoURL: '{{head_sha}}'
targetRevision: '{{head_sha}}'
path: environments/dev
destination:
server: https://kubernetes.default.svc
namespace: 'temporal-pr-{{number}}'
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
info:
- name: 'Pull Request'
value: 'https://github.com/company/temporal-environments/pull/{{number}}'
Environment-Specific Configurations¶
Security Configurations¶
Development Security¶
# environments/dev/security/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: temporal-dev
name: temporal-dev-access
rules:
- apiGroups: [""]
resources: ["pods", "services", "configmaps", "secrets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: temporal-dev-binding
namespace: temporal-dev
subjects:
- kind: Group
name: temporal-developers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: temporal-dev-access
apiGroup: rbac.authorization.k8s.io
Production Security¶
# environments/production/security/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: temporal-production
name: temporal-prod-readonly
rules:
- apiGroups: [""]
resources: ["pods", "services", "configmaps"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: temporal-production
name: temporal-prod-admin
rules:
- apiGroups: [""]
resources: ["*"]
verbs: ["*"]
- apiGroups: ["apps"]
resources: ["*"]
verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: temporal-prod-readonly-binding
namespace: temporal-production
subjects:
- kind: Group
name: temporal-users
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: temporal-prod-readonly
apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: temporal-prod-admin-binding
namespace: temporal-production
subjects:
- kind: Group
name: temporal-admins
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: temporal-prod-admin
apiGroup: rbac.authorization.k8s.io
Network Policies¶
# environments/production/security/network-policies.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: temporal-server-netpol
namespace: temporal-production
spec:
podSelector:
matchLabels:
app: temporal-server
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: temporal-production
- namespaceSelector:
matchLabels:
name: monitoring
ports:
- protocol: TCP
port: 7233
- protocol: TCP
port: 7234
egress:
- to:
- namespaceSelector:
matchLabels:
name: temporal-production
- to: []
ports:
- protocol: TCP
port: 5432 # PostgreSQL
- protocol: TCP
port: 53 # DNS
- protocol: UDP
port: 53 # DNS
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-default
namespace: temporal-production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Pod Security Standards¶
# environments/production/security/pod-security.yaml
apiVersion: v1
kind: Namespace
metadata:
name: temporal-production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
---
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: temporal-psp
namespace: temporal-production
spec:
privileged: false
allowPrivilegeEscalation: false
requiredDropCapabilities:
- ALL
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
- 'persistentVolumeClaim'
runAsUser:
rule: 'MustRunAsNonRoot'
seLinux:
rule: 'RunAsAny'
fsGroup:
rule: 'RunAsAny'
Environment Promotion¶
Promotion Workflow¶
#!/bin/bash
# scripts/promote-environment.sh
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
# Configuration
SOURCE_ENV=""
TARGET_ENV=""
DRY_RUN="false"
AUTO_APPROVE="false"
log() {
echo -e "\033[0;32m[$(date +'%Y-%m-%d %H:%M:%S')] $1\033[0m"
}
error() {
echo -e "\033[0;31m[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1\033[0m"
exit 1
}
warn() {
echo -e "\033[1;33m[$(date +'%Y-%m-%d %H:%M:%S')] WARNING: $1\033[0m"
}
usage() {
cat << EOF
Usage: $0 [OPTIONS]
Options:
-s, --source-env ENV Source environment (dev, staging)
-t, --target-env ENV Target environment (staging, production)
-d, --dry-run Perform dry run without applying changes
-a, --auto-approve Skip manual approval prompts
-h, --help Show this help message
Examples:
$0 --source-env dev --target-env staging
$0 --source-env staging --target-env production --dry-run
EOF
}
validate_environments() {
local valid_envs=("dev" "staging" "production")
if [[ ! " ${valid_envs[@]} " =~ " ${SOURCE_ENV} " ]]; then
error "Invalid source environment: $SOURCE_ENV"
fi
if [[ ! " ${valid_envs[@]} " =~ " ${TARGET_ENV} " ]]; then
error "Invalid target environment: $TARGET_ENV"
fi
if [[ "$SOURCE_ENV" == "$TARGET_ENV" ]]; then
error "Source and target environments cannot be the same"
fi
# Validate promotion path
case "$SOURCE_ENV:$TARGET_ENV" in
"dev:staging"|"staging:production")
log "✓ Valid promotion path: $SOURCE_ENV → $TARGET_ENV"
;;
*)
error "Invalid promotion path: $SOURCE_ENV → $TARGET_ENV. Valid paths: dev→staging, staging→production"
;;
esac
}
get_current_versions() {
local env="$1"
local env_file="$PROJECT_ROOT/environments/$env/kustomization.yaml"
if [[ ! -f "$env_file" ]]; then
error "Environment file not found: $env_file"
fi
# Extract image versions
yq eval '.images[] | .name + ":" + .newTag' "$env_file"
}
run_tests() {
local env="$1"
log "Running validation tests for $env environment..."
# Kustomize validation
if ! kustomize build "$PROJECT_ROOT/environments/$env" > /dev/null; then
error "Kustomize validation failed for $env environment"
fi
# Kubernetes validation
if ! kustomize build "$PROJECT_ROOT/environments/$env" | kubectl apply --dry-run=client -f -; then
error "Kubernetes validation failed for $env environment"
fi
# Security scanning
if command -v kubesec &> /dev/null; then
log "Running security scan..."
kustomize build "$PROJECT_ROOT/environments/$env" | kubesec scan -
fi
# Policy validation
if command -v conftest &> /dev/null; then
log "Running policy validation..."
kustomize build "$PROJECT_ROOT/environments/$env" | conftest verify --policy "$PROJECT_ROOT/policies/"
fi
log "✓ All validation tests passed for $env environment"
}
promote_configuration() {
local source_env="$1"
local target_env="$2"
log "Promoting configuration from $source_env to $target_env..."
# Get source versions
local source_file="$PROJECT_ROOT/environments/$source_env/kustomization.yaml"
local target_file="$PROJECT_ROOT/environments/$target_env/kustomization.yaml"
# Create backup
cp "$target_file" "$target_file.backup.$(date +%Y%m%d_%H%M%S)"
# Extract and update image versions
while IFS= read -r image_line; do
local image_name=$(echo "$image_line" | cut -d: -f1)
local new_tag=$(echo "$image_line" | cut -d: -f2)
log "Updating $image_name to $new_tag in $target_env"
# Update the target kustomization file
yq eval "(.images[] | select(.name == \"$image_name\") | .newTag) = \"$new_tag\"" -i "$target_file"
done < <(get_current_versions "$source_env")
log "✓ Configuration promoted successfully"
}
create_pull_request() {
local source_env="$1"
local target_env="$2"
# Create feature branch
local branch_name="promote-${source_env}-to-${target_env}-$(date +%Y%m%d_%H%M%S)"
git checkout -b "$branch_name"
git add .
git commit -m "Promote $source_env configuration to $target_env
- Updated image versions from $source_env environment
- Validated configurations and security policies
- Ready for $target_env deployment"
git push origin "$branch_name"
# Create PR using GitHub CLI if available
if command -v gh &> /dev/null; then
gh pr create \
--title "Promote $source_env to $target_env" \
--body "Automated promotion of configuration from $source_env to $target_env environment." \
--label "promotion" \
--label "$target_env"
else
log "GitHub CLI not available. Please create PR manually for branch: $branch_name"
fi
}
main() {
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-s|--source-env)
SOURCE_ENV="$2"
shift 2
;;
-t|--target-env)
TARGET_ENV="$2"
shift 2
;;
-d|--dry-run)
DRY_RUN="true"
shift
;;
-a|--auto-approve)
AUTO_APPROVE="true"
shift
;;
-h|--help)
usage
exit 0
;;
*)
error "Unknown option: $1"
;;
esac
done
# Validate required parameters
if [[ -z "$SOURCE_ENV" || -z "$TARGET_ENV" ]]; then
error "Source and target environments are required"
fi
validate_environments
log "Starting promotion: $SOURCE_ENV → $TARGET_ENV"
# Show current versions
log "Current versions in $SOURCE_ENV:"
get_current_versions "$SOURCE_ENV"
log "Current versions in $TARGET_ENV:"
get_current_versions "$TARGET_ENV"
# Run validation tests
run_tests "$SOURCE_ENV"
run_tests "$TARGET_ENV"
# Manual approval for production
if [[ "$TARGET_ENV" == "production" && "$AUTO_APPROVE" != "true" ]]; then
echo
warn "PRODUCTION DEPLOYMENT DETECTED"
echo "Source Environment: $SOURCE_ENV"
echo "Target Environment: $TARGET_ENV"
echo
read -p "Are you sure you want to promote to production? (yes/no): " confirm
if [[ "$confirm" != "yes" ]]; then
log "Promotion cancelled by user"
exit 0
fi
fi
if [[ "$DRY_RUN" == "true" ]]; then
log "DRY RUN: Would promote $SOURCE_ENV configuration to $TARGET_ENV"
exit 0
fi
# Perform promotion
promote_configuration "$SOURCE_ENV" "$TARGET_ENV"
# Validate promoted configuration
run_tests "$TARGET_ENV"
# Create pull request
create_pull_request "$SOURCE_ENV" "$TARGET_ENV"
log "✓ Promotion completed successfully!"
}
main "$@"
Environment Validation¶
#!/bin/bash
# scripts/validate-environment.sh
set -euo pipefail
ENVIRONMENT=""
VERBOSE="false"
log() {
echo -e "\033[0;32m[$(date +'%Y-%m-%d %H:%M:%S')] $1\033[0m"
}
error() {
echo -e "\033[0;31m[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1\033[0m"
exit 1
}
verbose() {
if [[ "$VERBOSE" == "true" ]]; then
echo -e "\033[0;36m[$(date +'%Y-%m-%d %H:%M:%S')] $1\033[0m"
fi
}
usage() {
cat << EOF
Usage: $0 [OPTIONS]
Options:
-e, --environment ENV Environment to validate (dev, staging, production)
-v, --verbose Enable verbose output
-h, --help Show this help message
Examples:
$0 --environment dev
$0 --environment production --verbose
EOF
}
validate_structure() {
local env="$1"
local env_dir="environments/$env"
log "Validating structure for $env environment..."
# Check required files
local required_files=(
"$env_dir/kustomization.yaml"
"$env_dir/namespace.yaml"
"$env_dir/temporal/values.yaml"
)
for file in "${required_files[@]}"; do
if [[ ! -f "$file" ]]; then
error "Required file missing: $file"
fi
verbose "✓ Found: $file"
done
log "✓ Structure validation passed"
}
validate_syntax() {
local env="$1"
local env_dir="environments/$env"
log "Validating YAML syntax for $env environment..."
# Validate all YAML files
while IFS= read -r -d '' file; do
if ! yq eval '.' "$file" > /dev/null 2>&1; then
error "Invalid YAML syntax in: $file"
fi
verbose "✓ Valid YAML: $file"
done < <(find "$env_dir" -name "*.yaml" -print0)
log "✓ YAML syntax validation passed"
}
validate_kustomize() {
local env="$1"
local env_dir="environments/$env"
log "Validating Kustomize build for $env environment..."
if ! kustomize build "$env_dir" > /dev/null; then
error "Kustomize build failed for $env environment"
fi
verbose "✓ Kustomize build successful"
log "✓ Kustomize validation passed"
}
validate_kubernetes() {
local env="$1"
local env_dir="environments/$env"
log "Validating Kubernetes manifests for $env environment..."
# Dry-run apply
if ! kustomize build "$env_dir" | kubectl apply --dry-run=client -f -; then
error "Kubernetes manifest validation failed for $env environment"
fi
log "✓ Kubernetes validation passed"
}
validate_security() {
local env="$1"
local env_dir="environments/$env"
log "Validating security policies for $env environment..."
# Check for security configurations in production
if [[ "$env" == "production" ]]; then
local security_files=(
"$env_dir/security/rbac.yaml"
"$env_dir/security/network-policies.yaml"
"$env_dir/security/pod-security.yaml"
)
for file in "${security_files[@]}"; do
if [[ ! -f "$file" ]]; then
error "Security file missing in production: $file"
fi
verbose "✓ Found security file: $file"
done
fi
# Validate with kubesec if available
if command -v kubesec &> /dev/null; then
verbose "Running kubesec security scan..."
local temp_file=$(mktemp)
kustomize build "$env_dir" > "$temp_file"
if ! kubesec scan "$temp_file" | jq -e '.[] | select(.score < 0)' > /dev/null; then
verbose "✓ Security scan passed"
else
warn "Security scan found issues. Review kubesec output."
fi
rm "$temp_file"
fi
log "✓ Security validation passed"
}
validate_resources() {
local env="$1"
local env_dir="environments/$env"
log "Validating resource configurations for $env environment..."
# Extract and validate resource configurations
local temp_file=$(mktemp)
kustomize build "$env_dir" > "$temp_file"
# Check that all containers have resource limits
local containers_without_limits=$(kubectl apply --dry-run=client -f "$temp_file" -o json | \
jq -r '.items[] | select(.kind == "Deployment") |
.spec.template.spec.containers[] |
select(.resources.limits == null) |
.name' || true)
if [[ -n "$containers_without_limits" ]]; then
warn "Containers without resource limits found: $containers_without_limits"
else
verbose "✓ All containers have resource limits"
fi
# Validate environment-specific resource requirements
case "$env" in
"dev")
# Check that dev has minimal resources
;;
"staging")
# Check that staging has moderate resources
;;
"production")
# Check that production has adequate resources
local min_memory="1Gi"
local min_cpu="500m"
;;
esac
rm "$temp_file"
log "✓ Resource validation passed"
}
validate_environment_specific() {
local env="$1"
log "Running environment-specific validations for $env..."
case "$env" in
"dev")
# Development-specific validations
verbose "Validating development environment specifics..."
;;
"staging")
# Staging-specific validations
verbose "Validating staging environment specifics..."
;;
"production")
# Production-specific validations
verbose "Validating production environment specifics..."
# Ensure high availability
local env_dir="environments/$env"
local replicas=$(kustomize build "$env_dir" | yq eval 'select(.kind == "Deployment") | .spec.replicas' -)
if [[ "$replicas" -lt 2 ]]; then
warn "Production deployment should have at least 2 replicas for high availability"
fi
;;
esac
log "✓ Environment-specific validation passed"
}
main() {
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-e|--environment)
ENVIRONMENT="$2"
shift 2
;;
-v|--verbose)
VERBOSE="true"
shift
;;
-h|--help)
usage
exit 0
;;
*)
error "Unknown option: $1"
;;
esac
done
# Validate required parameters
if [[ -z "$ENVIRONMENT" ]]; then
error "Environment is required"
fi
# Validate environment
local valid_envs=("dev" "staging" "production")
if [[ ! " ${valid_envs[@]} " =~ " ${ENVIRONMENT} " ]]; then
error "Invalid environment: $ENVIRONMENT. Valid options: ${valid_envs[*]}"
fi
log "Starting validation for $ENVIRONMENT environment..."
# Run all validations
validate_structure "$ENVIRONMENT"
validate_syntax "$ENVIRONMENT"
validate_kustomize "$ENVIRONMENT"
validate_kubernetes "$ENVIRONMENT"
validate_security "$ENVIRONMENT"
validate_resources "$ENVIRONMENT"
validate_environment_specific "$ENVIRONMENT"
log "✅ All validations passed for $ENVIRONMENT environment!"
}
main "$@"
Monitoring and Observability¶
Environment-Specific Monitoring¶
# environments/production/observability/prometheus.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-config
namespace: temporal-production
data:
prometheus.yml: |
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "temporal-rules.yml"
- "infrastructure-rules.yml"
scrape_configs:
- job_name: 'temporal-server'
kubernetes_sd_configs:
- role: endpoints
namespaces:
names:
- temporal-production
relabel_configs:
- source_labels: [__meta_kubernetes_service_name]
action: keep
regex: temporal-server
- source_labels: [__meta_kubernetes_endpoint_port_name]
action: keep
regex: metrics
- source_labels: [__meta_kubernetes_pod_name]
target_label: pod
- source_labels: [__meta_kubernetes_service_name]
target_label: service
- target_label: environment
replacement: production
- job_name: 'temporal-workers'
kubernetes_sd_configs:
- role: endpoints
namespaces:
names:
- temporal-production
relabel_configs:
- source_labels: [__meta_kubernetes_service_name]
action: keep
regex: temporal-worker
- target_label: environment
replacement: production
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager.monitoring.svc.cluster.local:9093
Environment Health Checks¶
# argocd/applications/environment-health.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: environment-health-checks
namespace: argocd
spec:
project: temporal-monitoring
source:
repoURL: https://github.com/company/temporal-environments
targetRevision: HEAD
path: monitoring/health-checks
destination:
server: https://kubernetes.default.svc
namespace: temporal-monitoring
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
Disaster Recovery¶
Environment Backup Strategy¶
#!/bin/bash
# scripts/backup-environment.sh
set -euo pipefail
ENVIRONMENT=""
BACKUP_LOCATION=""
backup_environment() {
local env="$1"
local backup_dir="$2/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$backup_dir"
# Backup Kubernetes resources
kubectl get all -n "temporal-$env" -o yaml > "$backup_dir/k8s-resources.yaml"
kubectl get secrets -n "temporal-$env" -o yaml > "$backup_dir/secrets.yaml"
kubectl get configmaps -n "temporal-$env" -o yaml > "$backup_dir/configmaps.yaml"
# Backup Git configuration
tar -czf "$backup_dir/git-config.tar.gz" "environments/$env"
# Backup database if applicable
if kubectl get secret temporal-postgres -n "temporal-$env" &> /dev/null; then
pg_dump "$(kubectl get secret temporal-postgres -n "temporal-$env" -o jsonpath='{.data.connection-string}' | base64 -d)" > "$backup_dir/database.sql"
fi
log "✓ Environment backup completed: $backup_dir"
}
This comprehensive environment management guide provides enterprise-grade GitOps practices for managing multiple Temporal.io environments with proper security, monitoring, and operational procedures.