From 96c573debc48d721ae5543181f6fbb111c1f5eb8 Mon Sep 17 00:00:00 2001 From: Phil Leggetter Date: Fri, 12 Dec 2025 20:39:19 +0000 Subject: [PATCH 1/4] fix(examples): replace Bitnami charts with official Docker images Replace unreliable Bitnami Helm charts with official Docker images and native Kubernetes manifests for PostgreSQL, Redis, and RabbitMQ. This change significantly improves reliability and simplifies the setup process. Key improvements: - Created dedicated Kubernetes manifests for each dependency: * PostgreSQL using postgres:16-alpine * Redis using redis:7-alpine * RabbitMQ using rabbitmq:3.13-management-alpine - Enhanced setup-dependencies.sh script with: * Robust error detection and diagnostic capabilities * Health checks for pods with proper error state detection * Improved timeout handling with periodic error checking * Clear error messages with troubleshooting instructions * Support for detecting ImagePullBackOff, CrashLoopBackOff, etc. - Fixed documentation paths: * Updated quickstart to reference examples/kubernetes directory * Renamed values.yaml to outpost.yaml for clarity - Made setup script executable (chmod +x) Breaking changes: - Bitnami Helm charts are no longer used for dependencies - Users must run setup-dependencies.sh to deploy PostgreSQL, Redis, and RabbitMQ using the new Kubernetes manifests - Configuration paths have changed in documentation This change resolves timeout issues and provides more reliable dependency deployment for local Kubernetes development. --- docs/pages/quickstarts/kubernetes.mdx | 4 +- .../kubernetes/{values.yaml => outpost.yaml} | 0 examples/kubernetes/postgresql.yaml | 72 +++++ examples/kubernetes/rabbitmq.yaml | 93 ++++++ examples/kubernetes/redis.yaml | 72 +++++ examples/kubernetes/setup-dependencies.sh | 266 +++++++++++++++--- 6 files changed, 467 insertions(+), 40 deletions(-) rename examples/kubernetes/{values.yaml => outpost.yaml} (100%) create mode 100644 examples/kubernetes/postgresql.yaml create mode 100644 examples/kubernetes/rabbitmq.yaml create mode 100644 examples/kubernetes/redis.yaml mode change 100644 => 100755 examples/kubernetes/setup-dependencies.sh diff --git a/docs/pages/quickstarts/kubernetes.mdx b/docs/pages/quickstarts/kubernetes.mdx index e76a81318..18d74cf15 100644 --- a/docs/pages/quickstarts/kubernetes.mdx +++ b/docs/pages/quickstarts/kubernetes.mdx @@ -35,7 +35,7 @@ Local Kubernetes setup for Outpost using Minikube. This setup includes: 3. Install dependencies: ```sh - cd outpost/deployments/kubernetes + cd outpost/examples/kubernetes ./setup-dependencies.sh ``` @@ -48,7 +48,7 @@ Local Kubernetes setup for Outpost using Minikube. This setup includes: ::: ```sh - helm install outpost ../../deployments/kubernetes/charts/outpost -f values.yaml + helm install outpost ../../deployments/kubernetes/charts/outpost -f outpost.yaml ``` ## Verify Installation diff --git a/examples/kubernetes/values.yaml b/examples/kubernetes/outpost.yaml similarity index 100% rename from examples/kubernetes/values.yaml rename to examples/kubernetes/outpost.yaml diff --git a/examples/kubernetes/postgresql.yaml b/examples/kubernetes/postgresql.yaml new file mode 100644 index 000000000..fcd01442b --- /dev/null +++ b/examples/kubernetes/postgresql.yaml @@ -0,0 +1,72 @@ +apiVersion: v1 +kind: Service +metadata: + name: outpost-postgresql +spec: + type: ClusterIP + ports: + - port: 5432 + targetPort: 5432 + selector: + app: outpost-postgresql +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: outpost-postgresql +spec: + serviceName: outpost-postgresql + replicas: 1 + selector: + matchLabels: + app: outpost-postgresql + template: + metadata: + labels: + app: outpost-postgresql + spec: + containers: + - name: postgresql + image: postgres:16-alpine + env: + - name: POSTGRES_USER + value: "outpost" + - name: POSTGRES_DB + value: "outpost" + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: outpost-postgresql + key: password + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + ports: + - containerPort: 5432 + name: postgresql + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + livenessProbe: + exec: + command: + - pg_isready + - -U + - outpost + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + exec: + command: + - pg_isready + - -U + - outpost + initialDelaySeconds: 5 + periodSeconds: 5 + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: [ "ReadWriteOnce" ] + resources: + requests: + storage: 8Gi \ No newline at end of file diff --git a/examples/kubernetes/rabbitmq.yaml b/examples/kubernetes/rabbitmq.yaml new file mode 100644 index 000000000..5df6a8e54 --- /dev/null +++ b/examples/kubernetes/rabbitmq.yaml @@ -0,0 +1,93 @@ +apiVersion: v1 +kind: Service +metadata: + name: outpost-rabbitmq +spec: + type: ClusterIP + ports: + - name: amqp + port: 5672 + targetPort: 5672 + - name: management + port: 15672 + targetPort: 15672 + selector: + app: outpost-rabbitmq +--- +apiVersion: v1 +kind: Service +metadata: + name: outpost-rabbitmq-headless +spec: + type: ClusterIP + clusterIP: None + ports: + - name: amqp + port: 5672 + targetPort: 5672 + selector: + app: outpost-rabbitmq +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: outpost-rabbitmq +spec: + serviceName: outpost-rabbitmq-headless + replicas: 1 + selector: + matchLabels: + app: outpost-rabbitmq + template: + metadata: + labels: + app: outpost-rabbitmq + spec: + containers: + - name: rabbitmq + image: rabbitmq:3.13-management-alpine + env: + - name: RABBITMQ_DEFAULT_USER + value: "outpost" + - name: RABBITMQ_DEFAULT_PASS + valueFrom: + secretKeyRef: + name: outpost-rabbitmq + key: rabbitmq-password + - name: RABBITMQ_ERLANG_COOKIE + valueFrom: + secretKeyRef: + name: outpost-rabbitmq + key: rabbitmq-erlang-cookie + ports: + - containerPort: 5672 + name: amqp + - containerPort: 15672 + name: management + volumeMounts: + - name: data + mountPath: /var/lib/rabbitmq + livenessProbe: + exec: + command: + - rabbitmq-diagnostics + - ping + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 10 + readinessProbe: + exec: + command: + - rabbitmq-diagnostics + - check_port_connectivity + initialDelaySeconds: 20 + periodSeconds: 10 + timeoutSeconds: 5 + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: [ "ReadWriteOnce" ] + resources: + requests: + storage: 8Gi \ No newline at end of file diff --git a/examples/kubernetes/redis.yaml b/examples/kubernetes/redis.yaml new file mode 100644 index 000000000..cf785770a --- /dev/null +++ b/examples/kubernetes/redis.yaml @@ -0,0 +1,72 @@ +apiVersion: v1 +kind: Service +metadata: + name: outpost-redis-master +spec: + type: ClusterIP + ports: + - port: 6379 + targetPort: 6379 + selector: + app: outpost-redis +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: outpost-redis +spec: + serviceName: outpost-redis-master + replicas: 1 + selector: + matchLabels: + app: outpost-redis + template: + metadata: + labels: + app: outpost-redis + spec: + containers: + - name: redis + image: redis:7-alpine + command: + - redis-server + - --requirepass + - $(REDIS_PASSWORD) + env: + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: outpost-redis + key: redis-password + ports: + - containerPort: 6379 + name: redis + volumeMounts: + - name: data + mountPath: /data + livenessProbe: + exec: + command: + - redis-cli + - --raw + - incr + - ping + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + exec: + command: + - redis-cli + - --raw + - incr + - ping + initialDelaySeconds: 5 + periodSeconds: 5 + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: [ "ReadWriteOnce" ] + resources: + requests: + storage: 8Gi \ No newline at end of file diff --git a/examples/kubernetes/setup-dependencies.sh b/examples/kubernetes/setup-dependencies.sh old mode 100644 new mode 100755 index 14a5bde27..617331ffc --- a/examples/kubernetes/setup-dependencies.sh +++ b/examples/kubernetes/setup-dependencies.sh @@ -3,60 +3,250 @@ set -e # Exit on any error echo "🚀 Setting up Outpost dependencies..." -# Helper function to check if helm release exists -helm_release_exists() { - helm list | grep -q "^$1" +# Helper function to check if pod is healthy +pod_is_healthy() { + local pod_name=$1 + if kubectl get pod "$pod_name" >/dev/null 2>&1; then + local status=$(kubectl get pod "$pod_name" -o jsonpath='{.status.phase}') + local ready=$(kubectl get pod "$pod_name" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}') + + # Check init containers for ERROR states only (not normal waiting states like "PodInitializing") + local init_container_status=$(kubectl get pod "$pod_name" -o jsonpath='{.status.initContainerStatuses[*].state.waiting.reason}' 2>/dev/null) + if [[ "$init_container_status" =~ (ImagePullBackOff|ErrImagePull|CrashLoopBackOff) ]]; then + return 1 + fi + + # Check main containers for error states + local container_status=$(kubectl get pod "$pod_name" -o jsonpath='{.status.containerStatuses[0].state}' 2>/dev/null) + if echo "$container_status" | grep -q "waiting"; then + local waiting_reason=$(kubectl get pod "$pod_name" -o jsonpath='{.status.containerStatuses[0].state.waiting.reason}') + if [[ "$waiting_reason" =~ (ImagePullBackOff|ErrImagePull|CrashLoopBackOff) ]]; then + return 1 + fi + fi + + [[ "$status" == "Running" && "$ready" == "True" ]] + else + return 1 + fi } -# Helper function to check if secret exists -secret_exists() { - kubectl get secret "$1" >/dev/null 2>&1 +# Helper function to wait for pod with periodic error checking +wait_for_pod_ready() { + local pod_name=$1 + local timeout=$2 + local elapsed=0 + local check_interval=5 + + while [ $elapsed -lt $timeout ]; do + # Check if pod is ready + if kubectl wait --for=condition=ready "pod/$pod_name" --timeout=${check_interval}s 2>/dev/null; then + return 0 + fi + + # Check for actual error states only (not just "not ready yet") + if kubectl get pod "$pod_name" >/dev/null 2>&1; then + local init_container_status=$(kubectl get pod "$pod_name" -o jsonpath='{.status.initContainerStatuses[*].state.waiting.reason}' 2>/dev/null) + if [[ "$init_container_status" =~ (ImagePullBackOff|ErrImagePull|CrashLoopBackOff) ]]; then + return 1 + fi + + local container_status=$(kubectl get pod "$pod_name" -o jsonpath='{.status.containerStatuses[0].state.waiting.reason}' 2>/dev/null) + if [[ "$container_status" =~ (ImagePullBackOff|ErrImagePull|CrashLoopBackOff) ]]; then + return 1 + fi + fi + + elapsed=$((elapsed + check_interval)) + done + + return 1 } -# Add Bitnami repo if not exists -if ! helm repo list | grep -q "bitnami"; then - echo "📦 Adding Bitnami Helm repo..." - helm repo add bitnami https://charts.bitnami.com/bitnami >/dev/null - helm repo update >/dev/null -fi +echo "🚀 Using official Docker images (Bitnami images are not available)" +echo "" + +# Get the directory where this script is located +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# Install PostgreSQL with custom config -if ! helm_release_exists "outpost-postgresql"; then - echo "🐘 Installing PostgreSQL..." - helm install outpost-postgresql bitnami/postgresql \ - --set auth.username=outpost \ - --set auth.database=outpost \ - --set auth.postgresPassword="" >/dev/null +# Install PostgreSQL using direct Kubernetes manifests +if ! kubectl get statefulset outpost-postgresql >/dev/null 2>&1; then + echo "🐘 Installing PostgreSQL (using official postgres:16-alpine image)..." + + # Generate a random password + POSTGRES_PASSWORD=$(openssl rand -hex 16) + + # Create PostgreSQL secret + kubectl create secret generic outpost-postgresql \ + --from-literal=password="$POSTGRES_PASSWORD" \ + --dry-run=client -o yaml | kubectl apply -f - >/dev/null + + # Apply PostgreSQL manifests + kubectl apply -f "$SCRIPT_DIR/postgresql.yaml" >/dev/null + + echo "⏳ Waiting for PostgreSQL to be ready (timeout: 120s)..." + if ! wait_for_pod_ready "outpost-postgresql-0" 120; then + echo "" + echo "❌ PostgreSQL pod failed to become ready!" + echo "" + echo "Pod status:" + kubectl get pod outpost-postgresql-0 + echo "" + echo "Recent events:" + kubectl describe pod outpost-postgresql-0 | grep -A 10 "Events:" || true + echo "" + echo "Container logs (last 20 lines):" + kubectl logs outpost-postgresql-0 --tail=20 2>/dev/null || echo " (no logs available)" + echo "" + echo "⚠️ To fix this issue, clean up and re-run:" + echo " kubectl delete -f $SCRIPT_DIR/postgresql.yaml" + echo " kubectl delete pvc data-outpost-postgresql-0" + echo " kubectl delete secret outpost-postgresql" + echo "" + exit 1 + fi +elif ! pod_is_healthy "outpost-postgresql-0"; then + echo "❌ PostgreSQL pod is unhealthy!" + echo "" + echo "Pod status:" + kubectl get pod outpost-postgresql-0 + echo "" + echo "Recent events:" + kubectl describe pod outpost-postgresql-0 | grep -A 10 "Events:" || true + echo "" + echo "Container logs (last 20 lines):" + kubectl logs outpost-postgresql-0 --tail=20 2>/dev/null || echo " (no logs available)" + echo "" + echo "⚠️ To fix this issue, clean up and re-run:" + echo " kubectl delete -f $SCRIPT_DIR/postgresql.yaml" + echo " kubectl delete pvc data-outpost-postgresql-0" + echo " kubectl delete secret outpost-postgresql" + echo "" + exit 1 else - echo "🐘 PostgreSQL already installed, skipping..." + echo "🐘 PostgreSQL already installed and healthy, skipping..." fi -echo "⏳ Waiting for PostgreSQL to be ready..." -kubectl wait --for=condition=ready pod/outpost-postgresql-0 --timeout=120s >/dev/null 2>&1 POSTGRES_PASSWORD=$(kubectl get secret outpost-postgresql -o jsonpath="{.data.password}" | base64 -d) POSTGRES_URL="postgresql://outpost:${POSTGRES_PASSWORD}@outpost-postgresql:5432/outpost?sslmode=disable" -# Install Redis -if ! helm_release_exists "outpost-redis"; then - echo "🔴 Installing Redis..." - helm install outpost-redis bitnami/redis >/dev/null +# Install Redis using direct Kubernetes manifests +if ! kubectl get statefulset outpost-redis >/dev/null 2>&1; then + echo "🔴 Installing Redis (using official redis:7-alpine image)..." + + # Generate a random password + REDIS_PASSWORD=$(openssl rand -hex 16) + + # Create Redis secret + kubectl create secret generic outpost-redis \ + --from-literal=redis-password="$REDIS_PASSWORD" \ + --dry-run=client -o yaml | kubectl apply -f - >/dev/null + + # Apply Redis manifests + kubectl apply -f "$SCRIPT_DIR/redis.yaml" >/dev/null + + echo "⏳ Waiting for Redis to be ready (timeout: 120s)..." + if ! wait_for_pod_ready "outpost-redis-0" 120; then + echo "" + echo "❌ Redis pod failed to become ready!" + echo "" + echo "Pod status:" + kubectl get pod outpost-redis-0 + echo "" + echo "Recent events:" + kubectl describe pod outpost-redis-0 | grep -A 10 "Events:" || true + echo "" + echo "Container logs (last 20 lines):" + kubectl logs outpost-redis-0 --tail=20 2>/dev/null || echo " (no logs available)" + echo "" + echo "⚠️ To fix this issue, clean up and re-run:" + echo " kubectl delete -f $SCRIPT_DIR/redis.yaml" + echo " kubectl delete pvc data-outpost-redis-0" + echo " kubectl delete secret outpost-redis" + echo "" + exit 1 + fi +elif ! pod_is_healthy "outpost-redis-0"; then + echo "❌ Redis pod is unhealthy!" + echo "" + echo "Pod status:" + kubectl get pod outpost-redis-0 + echo "" + echo "Recent events:" + kubectl describe pod outpost-redis-0 | grep -A 10 "Events:" || true + echo "" + echo "Container logs (last 20 lines):" + kubectl logs outpost-redis-0 --tail=20 2>/dev/null || echo " (no logs available)" + echo "" + echo "⚠️ To fix this issue, clean up and re-run:" + echo " kubectl delete -f $SCRIPT_DIR/redis.yaml" + echo " kubectl delete pvc data-outpost-redis-0" + echo " kubectl delete secret outpost-redis" + echo "" + exit 1 else - echo "🔴 Redis already installed, skipping..." + echo "🔴 Redis already installed and healthy, skipping..." fi -echo "⏳ Waiting for Redis to be ready..." -kubectl wait --for=condition=ready pod/outpost-redis-master-0 --timeout=120s >/dev/null 2>&1 REDIS_PASSWORD=$(kubectl get secret outpost-redis -o jsonpath="{.data.redis-password}" | base64 -d) -# Install RabbitMQ with custom config -if ! helm_release_exists "outpost-rabbitmq"; then - echo "🐰 Installing RabbitMQ..." - helm install outpost-rabbitmq bitnami/rabbitmq \ - --set auth.username=outpost \ - --set auth.password="" >/dev/null +# Install RabbitMQ using direct Kubernetes manifests +if ! kubectl get statefulset outpost-rabbitmq >/dev/null 2>&1; then + echo "🐰 Installing RabbitMQ (using official rabbitmq:3.13-management-alpine image)..." + + # Generate passwords + RABBITMQ_PASSWORD=$(openssl rand -hex 16) + RABBITMQ_ERLANG_COOKIE=$(openssl rand -hex 32) + + # Create RabbitMQ secret + kubectl create secret generic outpost-rabbitmq \ + --from-literal=rabbitmq-password="$RABBITMQ_PASSWORD" \ + --from-literal=rabbitmq-erlang-cookie="$RABBITMQ_ERLANG_COOKIE" \ + --dry-run=client -o yaml | kubectl apply -f - >/dev/null + + # Apply RabbitMQ manifests + kubectl apply -f "$SCRIPT_DIR/rabbitmq.yaml" >/dev/null + + echo "⏳ Waiting for RabbitMQ to be ready (timeout: 120s)..." + if ! wait_for_pod_ready "outpost-rabbitmq-0" 120; then + echo "" + echo "❌ RabbitMQ pod failed to become ready!" + echo "" + echo "Pod status:" + kubectl get pod outpost-rabbitmq-0 + echo "" + echo "Recent events:" + kubectl describe pod outpost-rabbitmq-0 | grep -A 10 "Events:" || true + echo "" + echo "Container logs (last 20 lines):" + kubectl logs outpost-rabbitmq-0 --tail=20 2>/dev/null || echo " (no logs available)" + echo "" + echo "⚠️ To fix this issue, clean up and re-run:" + echo " kubectl delete -f $SCRIPT_DIR/rabbitmq.yaml" + echo " kubectl delete pvc data-outpost-rabbitmq-0" + echo " kubectl delete secret outpost-rabbitmq" + echo "" + exit 1 + fi +elif ! pod_is_healthy "outpost-rabbitmq-0"; then + echo "❌ RabbitMQ pod is unhealthy!" + echo "" + echo "Pod status:" + kubectl get pod outpost-rabbitmq-0 + echo "" + echo "Recent events:" + kubectl describe pod outpost-rabbitmq-0 | grep -A 10 "Events:" || true + echo "" + echo "Container logs (last 20 lines):" + kubectl logs outpost-rabbitmq-0 --tail=20 2>/dev/null || echo " (no logs available)" + echo "" + echo "⚠️ To fix this issue, clean up and re-run:" + echo " kubectl delete -f $SCRIPT_DIR/rabbitmq.yaml" + echo " kubectl delete pvc data-outpost-rabbitmq-0" + echo " kubectl delete secret outpost-rabbitmq" + echo "" + exit 1 else - echo "🐰 RabbitMQ already installed, skipping..." + echo "🐰 RabbitMQ already installed and healthy, skipping..." fi -echo "⏳ Waiting for RabbitMQ to be ready..." -kubectl wait --for=condition=ready pod/outpost-rabbitmq-0 --timeout=120s >/dev/null 2>&1 RABBITMQ_PASSWORD=$(kubectl get secret outpost-rabbitmq -o jsonpath="{.data.rabbitmq-password}" | base64 -d) RABBITMQ_SERVER_URL="amqp://outpost:${RABBITMQ_PASSWORD}@outpost-rabbitmq:5672" From 844f5f7b56e839c4ba902b4d0f3de7c5142fb8b5 Mon Sep 17 00:00:00 2001 From: Phil Leggetter Date: Mon, 15 Dec 2025 09:36:39 +0000 Subject: [PATCH 2/4] chore(examples): correct Redis health check probes with authentication - Change from incorrect 'redis-cli --raw incr ping' to proper 'redis-cli ping' - Add authentication using -a flag with REDIS_PASSWORD environment variable - Use shell wrapper (sh -c) to properly expand environment variable Fixes authentication errors in Redis health checks. --- examples/kubernetes/redis.yaml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/examples/kubernetes/redis.yaml b/examples/kubernetes/redis.yaml index cf785770a..0ec221a36 100644 --- a/examples/kubernetes/redis.yaml +++ b/examples/kubernetes/redis.yaml @@ -47,19 +47,17 @@ spec: livenessProbe: exec: command: - - redis-cli - - --raw - - incr - - ping + - sh + - -c + - redis-cli -a "${REDIS_PASSWORD}" ping initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: exec: command: - - redis-cli - - --raw - - incr - - ping + - sh + - -c + - redis-cli -a "${REDIS_PASSWORD}" ping initialDelaySeconds: 5 periodSeconds: 5 volumeClaimTemplates: From 2e7d58c840ebce2cbe4c470c94fc2a34c3fe8084 Mon Sep 17 00:00:00 2001 From: Phil Leggetter Date: Mon, 15 Dec 2025 09:36:46 +0000 Subject: [PATCH 3/4] chore(examples): add headless service for PostgreSQL StatefulSet - Create separate headless service 'outpost-postgresql-headless' with clusterIP: None - Use headless service as serviceName for StatefulSet - Keep regular ClusterIP service for client connections Follows Kubernetes best practices for StatefulSet DNS records. --- examples/kubernetes/postgresql.yaml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/examples/kubernetes/postgresql.yaml b/examples/kubernetes/postgresql.yaml index fcd01442b..8c08d9f4c 100644 --- a/examples/kubernetes/postgresql.yaml +++ b/examples/kubernetes/postgresql.yaml @@ -10,12 +10,24 @@ spec: selector: app: outpost-postgresql --- +apiVersion: v1 +kind: Service +metadata: + name: outpost-postgresql-headless +spec: + clusterIP: None + ports: + - port: 5432 + targetPort: 5432 + selector: + app: outpost-postgresql +--- apiVersion: apps/v1 kind: StatefulSet metadata: name: outpost-postgresql spec: - serviceName: outpost-postgresql + serviceName: outpost-postgresql-headless replicas: 1 selector: matchLabels: From 2990f8224d74c9386a56d0f49c8ff54c0684a285 Mon Sep 17 00:00:00 2001 From: Phil Leggetter Date: Mon, 15 Dec 2025 09:36:58 +0000 Subject: [PATCH 4/4] chore(examples): improve setup script security and maintainability - Change password generation from 'openssl rand -hex' to 'openssl rand -base64' for better entropy * PostgreSQL: base64 24 bytes * Redis: base64 24 bytes * RabbitMQ: base64 24 bytes (password) and 48 bytes (erlang cookie) * Application secrets: base64 24/48 bytes - Extract duplicate diagnostic code into reusable print_pod_diagnostics() function - Update message from 'Bitnami images are not available' to 'Using official Docker images for improved reliability' Addresses PR feedback for improved security and code quality. --- examples/kubernetes/setup-dependencies.sh | 162 ++++++++-------------- 1 file changed, 55 insertions(+), 107 deletions(-) diff --git a/examples/kubernetes/setup-dependencies.sh b/examples/kubernetes/setup-dependencies.sh index 617331ffc..72dfad71e 100755 --- a/examples/kubernetes/setup-dependencies.sh +++ b/examples/kubernetes/setup-dependencies.sh @@ -3,6 +3,29 @@ set -e # Exit on any error echo "🚀 Setting up Outpost dependencies..." +# Helper function to print pod diagnostics +print_pod_diagnostics() { + local pod_name=$1 + local resource_name=$2 + local cleanup_commands=$3 + + echo "" + echo "❌ $resource_name pod failed to become ready!" + echo "" + echo "Pod status:" + kubectl get pod "$pod_name" + echo "" + echo "Recent events:" + kubectl describe pod "$pod_name" | grep -A 10 "Events:" || true + echo "" + echo "Container logs (last 20 lines):" + kubectl logs "$pod_name" --tail=20 2>/dev/null || echo " (no logs available)" + echo "" + echo "⚠️ To fix this issue, clean up and re-run:" + echo "$cleanup_commands" + echo "" +} + # Helper function to check if pod is healthy pod_is_healthy() { local pod_name=$1 @@ -63,7 +86,7 @@ wait_for_pod_ready() { return 1 } -echo "🚀 Using official Docker images (Bitnami images are not available)" +echo "🚀 Using official Docker images for improved reliability" echo "" # Get the directory where this script is located @@ -74,7 +97,7 @@ if ! kubectl get statefulset outpost-postgresql >/dev/null 2>&1; then echo "🐘 Installing PostgreSQL (using official postgres:16-alpine image)..." # Generate a random password - POSTGRES_PASSWORD=$(openssl rand -hex 16) + POSTGRES_PASSWORD=$(openssl rand -base64 24) # Create PostgreSQL secret kubectl create secret generic outpost-postgresql \ @@ -86,42 +109,17 @@ if ! kubectl get statefulset outpost-postgresql >/dev/null 2>&1; then echo "⏳ Waiting for PostgreSQL to be ready (timeout: 120s)..." if ! wait_for_pod_ready "outpost-postgresql-0" 120; then - echo "" - echo "❌ PostgreSQL pod failed to become ready!" - echo "" - echo "Pod status:" - kubectl get pod outpost-postgresql-0 - echo "" - echo "Recent events:" - kubectl describe pod outpost-postgresql-0 | grep -A 10 "Events:" || true - echo "" - echo "Container logs (last 20 lines):" - kubectl logs outpost-postgresql-0 --tail=20 2>/dev/null || echo " (no logs available)" - echo "" - echo "⚠️ To fix this issue, clean up and re-run:" - echo " kubectl delete -f $SCRIPT_DIR/postgresql.yaml" - echo " kubectl delete pvc data-outpost-postgresql-0" - echo " kubectl delete secret outpost-postgresql" - echo "" + print_pod_diagnostics "outpost-postgresql-0" "PostgreSQL" \ +" kubectl delete -f $SCRIPT_DIR/postgresql.yaml + kubectl delete pvc data-outpost-postgresql-0 + kubectl delete secret outpost-postgresql" exit 1 fi elif ! pod_is_healthy "outpost-postgresql-0"; then - echo "❌ PostgreSQL pod is unhealthy!" - echo "" - echo "Pod status:" - kubectl get pod outpost-postgresql-0 - echo "" - echo "Recent events:" - kubectl describe pod outpost-postgresql-0 | grep -A 10 "Events:" || true - echo "" - echo "Container logs (last 20 lines):" - kubectl logs outpost-postgresql-0 --tail=20 2>/dev/null || echo " (no logs available)" - echo "" - echo "⚠️ To fix this issue, clean up and re-run:" - echo " kubectl delete -f $SCRIPT_DIR/postgresql.yaml" - echo " kubectl delete pvc data-outpost-postgresql-0" - echo " kubectl delete secret outpost-postgresql" - echo "" + print_pod_diagnostics "outpost-postgresql-0" "PostgreSQL" \ +" kubectl delete -f $SCRIPT_DIR/postgresql.yaml + kubectl delete pvc data-outpost-postgresql-0 + kubectl delete secret outpost-postgresql" exit 1 else echo "🐘 PostgreSQL already installed and healthy, skipping..." @@ -134,7 +132,7 @@ if ! kubectl get statefulset outpost-redis >/dev/null 2>&1; then echo "🔴 Installing Redis (using official redis:7-alpine image)..." # Generate a random password - REDIS_PASSWORD=$(openssl rand -hex 16) + REDIS_PASSWORD=$(openssl rand -base64 24) # Create Redis secret kubectl create secret generic outpost-redis \ @@ -146,42 +144,17 @@ if ! kubectl get statefulset outpost-redis >/dev/null 2>&1; then echo "⏳ Waiting for Redis to be ready (timeout: 120s)..." if ! wait_for_pod_ready "outpost-redis-0" 120; then - echo "" - echo "❌ Redis pod failed to become ready!" - echo "" - echo "Pod status:" - kubectl get pod outpost-redis-0 - echo "" - echo "Recent events:" - kubectl describe pod outpost-redis-0 | grep -A 10 "Events:" || true - echo "" - echo "Container logs (last 20 lines):" - kubectl logs outpost-redis-0 --tail=20 2>/dev/null || echo " (no logs available)" - echo "" - echo "⚠️ To fix this issue, clean up and re-run:" - echo " kubectl delete -f $SCRIPT_DIR/redis.yaml" - echo " kubectl delete pvc data-outpost-redis-0" - echo " kubectl delete secret outpost-redis" - echo "" + print_pod_diagnostics "outpost-redis-0" "Redis" \ +" kubectl delete -f $SCRIPT_DIR/redis.yaml + kubectl delete pvc data-outpost-redis-0 + kubectl delete secret outpost-redis" exit 1 fi elif ! pod_is_healthy "outpost-redis-0"; then - echo "❌ Redis pod is unhealthy!" - echo "" - echo "Pod status:" - kubectl get pod outpost-redis-0 - echo "" - echo "Recent events:" - kubectl describe pod outpost-redis-0 | grep -A 10 "Events:" || true - echo "" - echo "Container logs (last 20 lines):" - kubectl logs outpost-redis-0 --tail=20 2>/dev/null || echo " (no logs available)" - echo "" - echo "⚠️ To fix this issue, clean up and re-run:" - echo " kubectl delete -f $SCRIPT_DIR/redis.yaml" - echo " kubectl delete pvc data-outpost-redis-0" - echo " kubectl delete secret outpost-redis" - echo "" + print_pod_diagnostics "outpost-redis-0" "Redis" \ +" kubectl delete -f $SCRIPT_DIR/redis.yaml + kubectl delete pvc data-outpost-redis-0 + kubectl delete secret outpost-redis" exit 1 else echo "🔴 Redis already installed and healthy, skipping..." @@ -193,8 +166,8 @@ if ! kubectl get statefulset outpost-rabbitmq >/dev/null 2>&1; then echo "🐰 Installing RabbitMQ (using official rabbitmq:3.13-management-alpine image)..." # Generate passwords - RABBITMQ_PASSWORD=$(openssl rand -hex 16) - RABBITMQ_ERLANG_COOKIE=$(openssl rand -hex 32) + RABBITMQ_PASSWORD=$(openssl rand -base64 24) + RABBITMQ_ERLANG_COOKIE=$(openssl rand -base64 48) # Create RabbitMQ secret kubectl create secret generic outpost-rabbitmq \ @@ -207,42 +180,17 @@ if ! kubectl get statefulset outpost-rabbitmq >/dev/null 2>&1; then echo "⏳ Waiting for RabbitMQ to be ready (timeout: 120s)..." if ! wait_for_pod_ready "outpost-rabbitmq-0" 120; then - echo "" - echo "❌ RabbitMQ pod failed to become ready!" - echo "" - echo "Pod status:" - kubectl get pod outpost-rabbitmq-0 - echo "" - echo "Recent events:" - kubectl describe pod outpost-rabbitmq-0 | grep -A 10 "Events:" || true - echo "" - echo "Container logs (last 20 lines):" - kubectl logs outpost-rabbitmq-0 --tail=20 2>/dev/null || echo " (no logs available)" - echo "" - echo "⚠️ To fix this issue, clean up and re-run:" - echo " kubectl delete -f $SCRIPT_DIR/rabbitmq.yaml" - echo " kubectl delete pvc data-outpost-rabbitmq-0" - echo " kubectl delete secret outpost-rabbitmq" - echo "" + print_pod_diagnostics "outpost-rabbitmq-0" "RabbitMQ" \ +" kubectl delete -f $SCRIPT_DIR/rabbitmq.yaml + kubectl delete pvc data-outpost-rabbitmq-0 + kubectl delete secret outpost-rabbitmq" exit 1 fi elif ! pod_is_healthy "outpost-rabbitmq-0"; then - echo "❌ RabbitMQ pod is unhealthy!" - echo "" - echo "Pod status:" - kubectl get pod outpost-rabbitmq-0 - echo "" - echo "Recent events:" - kubectl describe pod outpost-rabbitmq-0 | grep -A 10 "Events:" || true - echo "" - echo "Container logs (last 20 lines):" - kubectl logs outpost-rabbitmq-0 --tail=20 2>/dev/null || echo " (no logs available)" - echo "" - echo "⚠️ To fix this issue, clean up and re-run:" - echo " kubectl delete -f $SCRIPT_DIR/rabbitmq.yaml" - echo " kubectl delete pvc data-outpost-rabbitmq-0" - echo " kubectl delete secret outpost-rabbitmq" - echo "" + print_pod_diagnostics "outpost-rabbitmq-0" "RabbitMQ" \ +" kubectl delete -f $SCRIPT_DIR/rabbitmq.yaml + kubectl delete pvc data-outpost-rabbitmq-0 + kubectl delete secret outpost-rabbitmq" exit 1 else echo "🐰 RabbitMQ already installed and healthy, skipping..." @@ -252,9 +200,9 @@ RABBITMQ_SERVER_URL="amqp://outpost:${RABBITMQ_PASSWORD}@outpost-rabbitmq:5672" # Generate application secrets echo "🔑 Generating application secrets..." -API_KEY=$(openssl rand -hex 16) -API_JWT_SECRET=$(openssl rand -hex 32) -AES_ENCRYPTION_SECRET=$(openssl rand -hex 32) +API_KEY=$(openssl rand -base64 24) +API_JWT_SECRET=$(openssl rand -base64 48) +AES_ENCRYPTION_SECRET=$(openssl rand -base64 48) # Create or update Kubernetes secret echo "🔒 Creating/updating Kubernetes secrets..."