- Add registry secret template for private container registry authentication - Fix frontend deployment to use imagePullSecrets for private registry - Enhance deploy-k8s.sh with registry authentication handling - Add PVC storage size validation to prevent storage reduction errors - Add graceful StatefulSet update error handling - Fix template variable substitution for DOCKER_IMAGE - Remove conflicting static PVC file that had unprocessed template variables - Add Kustomize structure as alternative to shell script templates: - Base configuration with common resources - Development overlay with dev-specific configurations - Support for environment-specific image tags and resource limits Registry setup requires setting REGISTRY_USERNAME, REGISTRY_PASSWORD, and optionally REGISTRY_HOST in .env file for private registry authentication.
394 lines
12 KiB
Bash
Executable File
394 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
||
|
||
# Kubernetes deployment script with environment variable substitution
|
||
# This script processes template files and applies them to Kubernetes
|
||
#
|
||
# Registry Authentication Setup:
|
||
# To pull images from a private registry, set these environment variables:
|
||
# REGISTRY_USERNAME - Username for the container registry
|
||
# REGISTRY_PASSWORD - Password/token for the container registry
|
||
# REGISTRY_HOST - Registry hostname (default: gitea-http.taildb3494.ts.net)
|
||
#
|
||
# Example in .env file:
|
||
# REGISTRY_USERNAME=your-username
|
||
# REGISTRY_PASSWORD=your-password-or-token
|
||
# REGISTRY_HOST=gitea-http.taildb3494.ts.net
|
||
|
||
set -euo pipefail
|
||
|
||
# Color codes for output
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
NC='\033[0m' # No Color
|
||
|
||
# Script directory
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||
K8S_DIR="$PROJECT_ROOT/k8s"
|
||
TEMP_DIR="/tmp/meds-k8s-deploy"
|
||
|
||
# Function to print colored output
|
||
print_info() {
|
||
echo -e "${BLUE}ℹ️ $1${NC}"
|
||
}
|
||
|
||
print_success() {
|
||
echo -e "${GREEN}✅ $1${NC}"
|
||
}
|
||
|
||
print_warning() {
|
||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||
}
|
||
|
||
print_error() {
|
||
echo -e "${RED}❌ $1${NC}"
|
||
}
|
||
|
||
# Function to load environment variables
|
||
load_env() {
|
||
local env_file="$1"
|
||
if [[ -f "$env_file" ]]; then
|
||
print_info "Loading environment from $env_file"
|
||
# Export variables from .env file
|
||
set -a
|
||
source "$env_file"
|
||
set +a
|
||
else
|
||
print_warning "Environment file $env_file not found"
|
||
fi
|
||
}
|
||
|
||
# Function to substitute environment variables in template files
|
||
substitute_templates() {
|
||
print_info "Processing template files..."
|
||
|
||
# Create temporary directory
|
||
mkdir -p "$TEMP_DIR"
|
||
|
||
# Process each template file
|
||
for template_file in "$K8S_DIR"/*.template; do
|
||
if [[ -f "$template_file" ]]; then
|
||
local filename=$(basename "$template_file" .template)
|
||
local output_file="$TEMP_DIR/$filename"
|
||
|
||
print_info "Processing template: $filename"
|
||
|
||
# Substitute environment variables
|
||
envsubst < "$template_file" > "$output_file"
|
||
|
||
print_success "Generated: $output_file"
|
||
fi
|
||
done
|
||
}
|
||
|
||
# Function to create registry authentication
|
||
create_registry_auth() {
|
||
if [[ -n "${REGISTRY_USERNAME:-}" && -n "${REGISTRY_PASSWORD:-}" ]]; then
|
||
local registry_host="${REGISTRY_HOST:-gitea-http.taildb3494.ts.net}"
|
||
local auth_string=$(echo -n "${REGISTRY_USERNAME}:${REGISTRY_PASSWORD}" | base64 -w 0)
|
||
local docker_config="{\"auths\":{\"${registry_host}\":{\"auth\":\"${auth_string}\"}}}"
|
||
export REGISTRY_AUTH_BASE64=$(echo -n "$docker_config" | base64 -w 0)
|
||
print_info "Registry authentication configured for $registry_host"
|
||
else
|
||
print_warning "Registry credentials not provided - skipping registry secret creation"
|
||
export REGISTRY_AUTH_BASE64=""
|
||
fi
|
||
}
|
||
|
||
# Function to validate required environment variables
|
||
validate_env() {
|
||
local required_vars=("INGRESS_HOST")
|
||
local missing_vars=()
|
||
|
||
for var in "${required_vars[@]}"; do
|
||
if [[ -z "${!var:-}" ]]; then
|
||
missing_vars+=("$var")
|
||
fi
|
||
done
|
||
|
||
if [[ ${#missing_vars[@]} -gt 0 ]]; then
|
||
print_error "Missing required environment variables:"
|
||
for var in "${missing_vars[@]}"; do
|
||
echo " - $var"
|
||
done
|
||
echo ""
|
||
echo "Please set these variables in your .env file or environment."
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
# Function to ensure namespace exists
|
||
ensure_namespace() {
|
||
if ! kubectl get namespace "$NAMESPACE" &> /dev/null; then
|
||
print_info "Creating namespace: $NAMESPACE"
|
||
kubectl create namespace "$NAMESPACE"
|
||
print_success "Created namespace: $NAMESPACE"
|
||
else
|
||
print_info "Using existing namespace: $NAMESPACE"
|
||
fi
|
||
}
|
||
|
||
# Function to convert Kubernetes storage units to bytes
|
||
storage_to_bytes() {
|
||
local storage="$1"
|
||
local number=$(echo "$storage" | sed 's/[^0-9]*//g')
|
||
local unit=$(echo "$storage" | sed 's/[0-9]*//g')
|
||
|
||
case "$unit" in
|
||
"Ki"|"K") echo $((number * 1024)) ;;
|
||
"Mi"|"M") echo $((number * 1024 * 1024)) ;;
|
||
"Gi"|"G") echo $((number * 1024 * 1024 * 1024)) ;;
|
||
"Ti"|"T") echo $((number * 1024 * 1024 * 1024 * 1024)) ;;
|
||
"") echo "$number" ;;
|
||
*) echo "0" ;;
|
||
esac
|
||
}
|
||
|
||
# Function to check if PVC storage can be updated
|
||
can_update_pvc_storage() {
|
||
local pvc_file="$1"
|
||
local pvc_name=$(grep "name:" "$pvc_file" | head -1 | awk '{print $2}')
|
||
local new_storage=$(grep "storage:" "$pvc_file" | awk '{print $2}')
|
||
|
||
# Check if PVC exists
|
||
if kubectl get pvc "$pvc_name" -n "$NAMESPACE" &> /dev/null; then
|
||
local current_storage=$(kubectl get pvc "$pvc_name" -n "$NAMESPACE" -o jsonpath='{.status.capacity.storage}')
|
||
|
||
# Convert storage sizes to bytes for comparison
|
||
local current_bytes=$(storage_to_bytes "$current_storage")
|
||
local new_bytes=$(storage_to_bytes "$new_storage")
|
||
|
||
if [[ "$new_bytes" -lt "$current_bytes" ]]; then
|
||
print_warning "Skipping PVC $pvc_name: cannot reduce storage from $current_storage to $new_storage"
|
||
return 1
|
||
fi
|
||
fi
|
||
return 0
|
||
}
|
||
|
||
# Function to apply Kubernetes manifests
|
||
apply_manifests() {
|
||
local manifest_dir="$1"
|
||
|
||
print_info "Applying Kubernetes manifests from $manifest_dir"
|
||
|
||
# Apply static files that don't have template counterparts
|
||
for manifest_file in "$K8S_DIR"/*.yaml; do
|
||
if [[ -f "$manifest_file" && ! "$manifest_file" =~ \.template$ ]]; then
|
||
local basename_file=$(basename "$manifest_file")
|
||
local template_file="$K8S_DIR/${basename_file}.template"
|
||
|
||
# Only apply if there's no corresponding template file
|
||
if [[ ! -f "$template_file" ]]; then
|
||
print_info "Applying static file: $basename_file"
|
||
kubectl apply -f "$manifest_file" -n "$NAMESPACE"
|
||
fi
|
||
fi
|
||
done
|
||
|
||
# Apply processed template files
|
||
if [[ -d "$TEMP_DIR" ]]; then
|
||
for manifest_file in "$TEMP_DIR"/*.yaml; do
|
||
if [[ -f "$manifest_file" ]]; then
|
||
local basename_file=$(basename "$manifest_file")
|
||
|
||
# Special handling for PVC files
|
||
if [[ "$basename_file" == *"pvc.yaml" ]] && grep -q "kind: PersistentVolumeClaim" "$manifest_file"; then
|
||
if can_update_pvc_storage "$manifest_file"; then
|
||
print_info "Applying template: $basename_file"
|
||
kubectl apply -f "$manifest_file" -n "$NAMESPACE"
|
||
fi
|
||
# Special handling for registry secret - skip if no auth provided
|
||
elif [[ "$basename_file" == *"registry-secret.yaml" ]] && [[ -z "${REGISTRY_AUTH_BASE64:-}" ]]; then
|
||
print_info "Skipping registry secret: no registry credentials provided"
|
||
else
|
||
print_info "Applying template: $basename_file"
|
||
if ! kubectl apply -f "$manifest_file" -n "$NAMESPACE" 2>/dev/null; then
|
||
# Handle StatefulSet update failures gracefully
|
||
if [[ "$basename_file" == *"statefulset.yaml" ]] && grep -q "kind: StatefulSet" "$manifest_file"; then
|
||
print_warning "StatefulSet update failed (likely due to immutable fields) - continuing deployment"
|
||
else
|
||
# Re-run the command to show the actual error for non-StatefulSet resources
|
||
kubectl apply -f "$manifest_file" -n "$NAMESPACE"
|
||
fi
|
||
fi
|
||
fi
|
||
fi
|
||
done
|
||
fi
|
||
}
|
||
|
||
# Function to cleanup temporary files
|
||
cleanup() {
|
||
if [[ -d "$TEMP_DIR" ]]; then
|
||
print_info "Cleaning up temporary files..."
|
||
rm -rf "$TEMP_DIR"
|
||
fi
|
||
}
|
||
|
||
# Function to show deployment status
|
||
show_status() {
|
||
print_info "Deployment Status:"
|
||
echo ""
|
||
|
||
print_info "Pods:"
|
||
kubectl get pods -l app=rxminder -n "$NAMESPACE"
|
||
echo ""
|
||
|
||
print_info "Services:"
|
||
kubectl get services -l app=rxminder -n "$NAMESPACE"
|
||
echo ""
|
||
|
||
print_info "Ingress:"
|
||
kubectl get ingress -l app=rxminder -n "$NAMESPACE"
|
||
echo ""
|
||
|
||
if [[ -n "${INGRESS_HOST:-}" ]]; then
|
||
print_success "Application should be available at: http://$INGRESS_HOST"
|
||
fi
|
||
}
|
||
|
||
# Function to show usage
|
||
usage() {
|
||
echo "Usage: $0 [OPTIONS]"
|
||
echo ""
|
||
echo "Options:"
|
||
echo " -e, --env FILE Environment file to load (default: .env)"
|
||
echo " -d, --dry-run Show what would be applied without applying"
|
||
echo " -s, --status Show deployment status only"
|
||
echo " -c, --cleanup Cleanup temporary files and exit"
|
||
echo " -h, --help Show this help message"
|
||
echo ""
|
||
echo "Examples:"
|
||
echo " $0 Deploy with default .env file"
|
||
echo " $0 -e .env.prod Deploy with production environment"
|
||
echo " $0 --dry-run Preview what would be deployed"
|
||
echo " $0 --status Check deployment status"
|
||
}
|
||
|
||
# Main function
|
||
main() {
|
||
local env_file=".env"
|
||
local dry_run=false
|
||
local status_only=false
|
||
local cleanup_only=false
|
||
|
||
# Parse command line arguments
|
||
while [[ $# -gt 0 ]]; do
|
||
case $1 in
|
||
-e|--env)
|
||
env_file="$2"
|
||
shift 2
|
||
;;
|
||
-d|--dry-run)
|
||
dry_run=true
|
||
shift
|
||
;;
|
||
-s|--status)
|
||
status_only=true
|
||
shift
|
||
;;
|
||
-c|--cleanup)
|
||
cleanup_only=true
|
||
shift
|
||
;;
|
||
-h|--help)
|
||
usage
|
||
exit 0
|
||
;;
|
||
*)
|
||
print_error "Unknown option: $1"
|
||
usage
|
||
exit 1
|
||
;;
|
||
esac
|
||
done
|
||
|
||
# Handle cleanup only
|
||
if [[ "$cleanup_only" == true ]]; then
|
||
cleanup
|
||
exit 0
|
||
fi
|
||
|
||
# Handle status only
|
||
if [[ "$status_only" == true ]]; then
|
||
load_env "$env_file"
|
||
NAMESPACE="${NAMESPACE:-rxminder}"
|
||
show_status
|
||
exit 0
|
||
fi
|
||
|
||
# Check if kubectl is available
|
||
if ! command -v kubectl &> /dev/null; then
|
||
print_error "kubectl is not installed or not in PATH"
|
||
exit 1
|
||
fi
|
||
|
||
# Check if we can connect to Kubernetes cluster
|
||
if ! kubectl cluster-info &> /dev/null; then
|
||
print_error "Cannot connect to Kubernetes cluster"
|
||
print_info "Make sure your kubectl is configured correctly"
|
||
exit 1
|
||
fi
|
||
|
||
print_info "🚀 Deploying Medication Reminder App to Kubernetes"
|
||
echo ""
|
||
|
||
# Load environment variables
|
||
load_env "$env_file"
|
||
|
||
# Set default values for required variables
|
||
export APP_NAME="${APP_NAME:-rxminder}"
|
||
export STORAGE_CLASS="${STORAGE_CLASS:-longhorn}"
|
||
export STORAGE_SIZE="${STORAGE_SIZE:-5Gi}"
|
||
export DOCKER_IMAGE="${DOCKER_IMAGE:-gitea-http.taildb3494.ts.net/will/rxminder:latest}"
|
||
|
||
# Set default namespace if not provided in environment
|
||
NAMESPACE="${NAMESPACE:-rxminder}"
|
||
|
||
# Create registry authentication if credentials are provided
|
||
create_registry_auth
|
||
|
||
# Validate required environment variables
|
||
validate_env
|
||
|
||
# Ensure namespace exists
|
||
ensure_namespace
|
||
|
||
# Process templates
|
||
substitute_templates
|
||
|
||
if [[ "$dry_run" == true ]]; then
|
||
print_info "Dry run mode - showing generated manifests:"
|
||
echo ""
|
||
|
||
for manifest_file in "$TEMP_DIR"/*.yaml; do
|
||
if [[ -f "$manifest_file" ]]; then
|
||
echo "=== $(basename "$manifest_file") ==="
|
||
cat "$manifest_file"
|
||
echo ""
|
||
fi
|
||
done
|
||
else
|
||
# Apply manifests
|
||
apply_manifests "$K8S_DIR"
|
||
|
||
print_success "Deployment completed!"
|
||
echo ""
|
||
|
||
# Show status
|
||
show_status
|
||
fi
|
||
|
||
# Cleanup
|
||
cleanup
|
||
}
|
||
|
||
# Trap to ensure cleanup happens
|
||
trap cleanup EXIT
|
||
|
||
# Run main function
|
||
main "$@"
|