#!/bin/bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Logging functions
log() { echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $*"; }
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
# Configuration
APP_NAME="${APP_NAME:-web-app}"
ENVIRONMENT="${ENVIRONMENT:-staging}"
IMAGE_TAG="${IMAGE_TAG:-latest}"
NAMESPACE="${NAMESPACE:-$ENVIRONMENT}"
TIMEOUT="${TIMEOUT:-300}"
HEALTH_URL="${HEALTH_URL:-https://$ENVIRONMENT.example.com/health}"
# Parse arguments
usage() {
cat <<EOF
Usage: $(basename "$0") [OPTIONS]
Deploy application to Kubernetes
Options:
-e, --environment Target environment (default: staging)
-t, --tag Docker image tag (default: latest)
-n, --namespace Kubernetes namespace
-d, --dry-run Show what would be done
-h, --help Show this help
EOF
}
DRY_RUN=false
while [[ $# -gt 0 ]]; do
case "$1" in
-e|--environment) ENVIRONMENT="$2"; shift 2 ;;
-t|--tag) IMAGE_TAG="$2"; shift 2 ;;
-n|--namespace) NAMESPACE="$2"; shift 2 ;;
-d|--dry-run) DRY_RUN=true; shift ;;
-h|--help) usage; exit 0 ;;
*) error "Unknown option: $1"; usage; exit 1 ;;
esac
done
# Cleanup on exit
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT
# Pre-flight checks
preflight() {
log "Running pre-flight checks..."
command -v kubectl >/dev/null 2>&1 || { error "kubectl not found"; exit 1; }
command -v jq >/dev/null 2>&1 || { error "jq not found"; exit 1; }
# Check cluster connection
if ! kubectl cluster-info &>/dev/null; then
error "Cannot connect to Kubernetes cluster"
exit 1
fi
# Check namespace exists
if ! kubectl get namespace "$NAMESPACE" &>/dev/null; then
error "Namespace $NAMESPACE does not exist"
exit 1
fi
# Check image exists
if ! docker manifest inspect "registry.example.com/$APP_NAME:$IMAGE_TAG" &>/dev/null; then
error "Image $APP_NAME:$IMAGE_TAG not found in registry"
exit 1
fi
info "Pre-flight checks passed"
}
# Deploy
deploy() {
log "Deploying $APP_NAME:$IMAGE_TAG to $ENVIRONMENT..."
if [[ "$DRY_RUN" == true ]]; then
warn "DRY RUN - no changes will be made"
kubectl set image "deployment/$APP_NAME" "$APP_NAME=registry.example.com/$APP_NAME:$IMAGE_TAG" -n "$NAMESPACE" --dry-run=client
return
fi
# Record current revision for rollback
CURRENT_REV=$(kubectl rollout history "deployment/$APP_NAME" -n "$NAMESPACE" -o json | jq '.metadata.generation')
info "Current revision: $CURRENT_REV"
# Update image
kubectl set image "deployment/$APP_NAME" "$APP_NAME=registry.example.com/$APP_NAME:$IMAGE_TAG" -n "$NAMESPACE"
kubectl annotate "deployment/$APP_NAME" "kubernetes.io/change-cause=Deploy $IMAGE_TAG via deploy.sh" -n "$NAMESPACE" --overwrite
info "Image updated, waiting for rollout..."
}
# Wait for rollout
wait_for_rollout() {
if [[ "$DRY_RUN" == true ]]; then return; fi
if ! kubectl rollout status "deployment/$APP_NAME" -n "$NAMESPACE" --timeout="${TIMEOUT}s"; then
error "Rollout failed! Rolling back..."
kubectl rollout undo "deployment/$APP_NAME" -n "$NAMESPACE"
kubectl rollout status "deployment/$APP_NAME" -n "$NAMESPACE" --timeout="${TIMEOUT}s"
error "Rolled back to previous version"
exit 1
fi
info "Rollout completed successfully"
}
# Health check
health_check() {
if [[ "$DRY_RUN" == true ]]; then return; fi
log "Running health checks..."
local retries=10
local delay=5
for ((i=1; i<=retries; i++)); do
HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' "$HEALTH_URL" || true)
if [[ "$HTTP_CODE" == "200" ]]; then
info "Health check passed (HTTP $HTTP_CODE)"
return 0
fi
warn "Attempt $i/$retries: HTTP $HTTP_CODE (retrying in ${delay}s)"
sleep "$delay"
done
error "Health check failed after $retries attempts"
return 1
}
# Main
main() {
log "=== Deployment: $APP_NAME → $ENVIRONMENT ==="
preflight
deploy
wait_for_rollout
health_check
info "=== Deployment complete! ==="
}
main
Shell scripts automate repetitive DevOps tasks like deployments, backups, and health checks. I use #!/bin/bash with set -euo pipefail for strict error handling—-e exits on error, -u errors on undefined variables, -o pipefail catches pipe failures. Functions organize reusable logic. trap handles cleanup on exit or signals. Color output with ANSI codes improves readability. getopts parses command-line arguments. Conditional execution with && and || chains commands. curl checks HTTP endpoints for health monitoring. jq processes JSON from APIs. Logging with timestamps aids debugging. mktemp creates safe temporary files. Scripts should be idempotent—safe to run multiple times without side effects.