name: Deploy to Kubernetes on: workflow_dispatch: inputs: mode: description: 'Deployment mode' required: true default: 'multiuser' type: choice options: - solo - multiuser - enterprise dry_run: description: 'Perform dry-run (no actual deployment)' required: false default: 'true' type: choice options: - 'true' - 'false' environment: description: 'Target environment' required: true type: choice options: - staging - production rollout_timeout: description: 'Rollout timeout in seconds' required: false default: '300' type: string concurrency: group: k8s-deployment-${{ github.ref }}-${{ inputs.environment }} cancel-in-progress: false jobs: deploy-kubernetes: name: Deploy ${{ inputs.mode || 'multiuser' }} to K8s runs-on: ubuntu-latest environment: ${{ inputs.environment || 'staging' }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Download artifacts uses: actions/download-artifact@v4 with: name: deployment-artifacts path: artifacts/ - name: Install Nushell run: | cargo install nu --locked nu --version - name: Install kubectl uses: azure/setup-kubectl@v3 with: version: 'latest' - name: Configure kubeconfig run: | mkdir -p ~/.kube echo "${{ secrets.KUBE_CONFIG_STAGING }}" | base64 -d > ~/.kube/config chmod 600 ~/.kube/config kubectl cluster-info if: ${{ inputs.environment == 'staging' }} - name: Configure kubeconfig (production) run: | mkdir -p ~/.kube echo "${{ secrets.KUBE_CONFIG_PRODUCTION }}" | base64 -d > ~/.kube/config chmod 600 ~/.kube/config kubectl cluster-info if: ${{ inputs.environment == 'production' }} - name: Create VAPORA namespace run: | kubectl create namespace vapora --dry-run=client -o yaml | kubectl apply -f - kubectl label namespace vapora environment=${{ inputs.environment }} --overwrite - name: Create deployment directory run: | mkdir -p deploy/kubernetes cp artifacts/configmap.yaml deploy/kubernetes/ cp artifacts/deployment.yaml deploy/kubernetes/ cp artifacts/vapora-${{ inputs.mode || 'multiuser' }}.yaml deploy/kubernetes/config.yaml - name: Validate Kubernetes manifests run: | kubectl apply --dry-run=client -f deploy/kubernetes/configmap.yaml kubectl apply --dry-run=client -f deploy/kubernetes/deployment.yaml echo "✓ Kubernetes manifests validated" - name: Show deployment diff (dry-run) if: ${{ inputs.dry_run == 'true' }} run: | echo "🔍 Deployment diff (dry-run):" kubectl apply --dry-run=server -f deploy/kubernetes/configmap.yaml -o yaml kubectl apply --dry-run=server -f deploy/kubernetes/deployment.yaml -o yaml - name: Deploy ConfigMap if: ${{ inputs.dry_run == 'false' }} run: | echo "📋 Deploying ConfigMap..." kubectl apply -f deploy/kubernetes/configmap.yaml sleep 5 kubectl get configmap -n vapora - name: Deploy Deployments if: ${{ inputs.dry_run == 'false' }} run: | echo "🚀 Deploying services..." kubectl apply -f deploy/kubernetes/deployment.yaml kubectl get deployments -n vapora - name: Wait for rollout (backend) if: ${{ inputs.dry_run == 'false' }} run: | echo "⏳ Waiting for backend deployment..." kubectl rollout status deployment/vapora-backend -n vapora --timeout=${{ inputs.rollout_timeout }}s - name: Wait for rollout (agents) if: ${{ inputs.dry_run == 'false' }} run: | echo "⏳ Waiting for agents deployment..." kubectl rollout status deployment/vapora-agents -n vapora --timeout=${{ inputs.rollout_timeout }}s - name: Wait for rollout (llm-router) if: ${{ inputs.dry_run == 'false' }} run: | echo "⏳ Waiting for llm-router deployment..." kubectl rollout status deployment/vapora-llm-router -n vapora --timeout=${{ inputs.rollout_timeout }}s - name: Verify pod health if: ${{ inputs.dry_run == 'false' }} run: | echo "🏥 Checking pod health..." kubectl get pods -n vapora echo "" echo "Pod details:" kubectl describe pods -n vapora | grep -A 5 "Status:" - name: Check deployment status if: always() run: | echo "📊 Deployment Status:" kubectl get deployments -n vapora -o wide echo "" echo "📋 Services:" kubectl get services -n vapora echo "" echo "🔧 ConfigMaps:" kubectl get configmaps -n vapora - name: Get service endpoints if: ${{ inputs.dry_run == 'false' }} run: | echo "🌐 Service Endpoints:" kubectl get services -n vapora -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.clusterIP}{"\n"}{end}' - name: Save deployment manifest if: success() run: | cat > deploy/kubernetes/DEPLOYMENT.md << 'EOF' # Kubernetes Deployment Details **Deployment Time**: $(date -u +'%Y-%m-%dT%H:%M:%SZ') **Mode**: ${{ inputs.mode || 'multiuser' }} **Environment**: ${{ inputs.environment || 'staging' }} **Namespace**: vapora **Commit**: ${{ github.sha }} **Workflow Run**: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} ## Deployment Status ### Deployments - **vapora-backend** - REST API server - **vapora-agents** - Agent orchestration - **vapora-llm-router** - LLM provider routing ### Configuration - **ConfigMap**: vapora-config (environment data) - **Namespace**: vapora (isolated environment) ## Kubernetes Commands ### View logs ```bash # Backend logs kubectl logs -f deployment/vapora-backend -n vapora # Agents logs kubectl logs -f deployment/vapora-agents -n vapora # All pod logs kubectl logs -f -l app=vapora -n vapora ``` ### Check deployment status ```bash kubectl get deployments -n vapora kubectl get pods -n vapora kubectl describe deployment vapora-backend -n vapora ``` ### Rollback if needed ```bash kubectl rollout undo deployment/vapora-backend -n vapora kubectl rollout history deployment/vapora-backend -n vapora ``` ### Port forwarding ```bash kubectl port-forward -n vapora svc/vapora-backend 8001:8001 kubectl port-forward -n vapora svc/vapora-frontend 3000:3000 ``` ### Scale deployment ```bash kubectl scale deployment vapora-backend --replicas=3 -n vapora ``` ## Access Services ### Internal (ClusterIP) - **Backend**: http://vapora-backend.vapora.svc.cluster.local:8001 - **Agents**: http://vapora-agents.vapora.svc.cluster.local:8002 - **LLM Router**: http://vapora-llm-router.vapora.svc.cluster.local:8003 - **Frontend**: http://vapora-frontend.vapora.svc.cluster.local:3000 ### External (requires Ingress/LoadBalancer) - Configure Ingress or LoadBalancer service - See production documentation for external access setup EOF cat deploy/kubernetes/DEPLOYMENT.md - name: Upload deployment manifests if: always() uses: actions/upload-artifact@v4 with: name: k8s-deployment-${{ inputs.environment }}-${{ github.run_id }} path: deploy/kubernetes/ retention-days: 30 - name: Create deployment annotation if: ${{ inputs.dry_run == 'false' && success() }} run: | kubectl annotate deployment vapora-backend \ -n vapora \ deployment.kubernetes.io/revision=$(date +%s) \ github.deployment.run=${{ github.run_id }} \ github.deployment.commit=${{ github.sha }} \ --overwrite - name: Post deployment summary if: always() uses: actions/github-script@v7 with: script: | const mode = '${{ inputs.mode || "multiuser" }}'; const env = '${{ inputs.environment || "staging" }}'; const isDryRun = '${{ inputs.dry_run }}' === 'true'; let message = `${isDryRun ? '🔍' : '✅'} **Kubernetes deployment ${isDryRun ? 'validated' : 'successful'}!**\n\n`; message += `**Mode**: ${mode}\n`; message += `**Environment**: ${env}\n`; message += `**Dry-run**: ${isDryRun ? 'Yes' : 'No'}\n`; message += `**Namespace**: vapora\n\n`; message += `**Deployments**:\n`; message += `- vapora-backend\n`; message += `- vapora-agents\n`; message += `- vapora-llm-router\n\n`; message += `**Useful Commands**:\n`; message += `\`\`\`bash\n`; message += `# View deployment status\n`; message += `kubectl get deployments -n vapora\n\n`; message += `# View logs\n`; message += `kubectl logs -f deployment/vapora-backend -n vapora\n\n`; message += `# Port forward\n`; message += `kubectl port-forward -n vapora svc/vapora-backend 8001:8001\n`; message += `\`\`\`\n`; // Only post to PR if it's a PR event if (context.eventName === 'pull_request' || context.payload.pull_request) { github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: message }); } - name: Notify Slack on success if: success() uses: 8398a7/action-slack@v3 with: status: ${{ job.status }} text: | VAPORA Kubernetes deployment successful! Mode: ${{ inputs.mode || 'multiuser' }} Environment: ${{ inputs.environment || 'staging' }} Namespace: vapora webhook_url: ${{ secrets.SLACK_WEBHOOK }} fields: repo,message,commit,author continue-on-error: true - name: Notify Slack on failure if: failure() uses: 8398a7/action-slack@v3 with: status: ${{ job.status }} text: | VAPORA Kubernetes deployment failed! Mode: ${{ inputs.mode || 'multiuser' }} Environment: ${{ inputs.environment || 'staging' }} webhook_url: ${{ secrets.SLACK_WEBHOOK }} fields: repo,message,commit,author continue-on-error: true