From 84261d47478e6fe64d6a19dff334e65e670810be Mon Sep 17 00:00:00 2001 From: Alex Mickelson Date: Fri, 6 Mar 2026 16:49:59 -0700 Subject: [PATCH] pipelines --- .gitea/workflows/pipeline.yml | 47 ++++++++++++++++++++++++ config/runtime.exs | 2 +- kubernetes/configmap.yml | 16 +++++++++ kubernetes/ingress.yml | 26 ++++++++++++++ kubernetes/services.yml | 34 ++++++++++++++++++ kubernetes/statefulset.yml | 67 +++++++++++++++++++++++++++++++++++ 6 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 .gitea/workflows/pipeline.yml create mode 100644 kubernetes/configmap.yml create mode 100644 kubernetes/ingress.yml create mode 100644 kubernetes/services.yml create mode 100644 kubernetes/statefulset.yml diff --git a/.gitea/workflows/pipeline.yml b/.gitea/workflows/pipeline.yml new file mode 100644 index 0000000..f958e8d --- /dev/null +++ b/.gitea/workflows/pipeline.yml @@ -0,0 +1,47 @@ +name: CI/CD Pipeline + +on: + push: + branches: [main] + workflow_dispatch: + +jobs: + build: + runs-on: home-server + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + clean: false + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push backend image + run: | + docker build -t alexmickelson/ai-liveview:$GITHUB_RUN_NUMBER . + docker push -q alexmickelson/ai-liveview:$GITHUB_RUN_NUMBER + + - name: Deploy to Kubernetes + env: + KUBECONFIG_CONTENT: ${{ secrets.KUBE_CONFIG_FILE }} + AI_TOKEN: ${{ secrets.AI_TOKEN }} + run: | + echo "$KUBECONFIG_CONTENT" > /tmp/elixir-kubeconfig.yml + export KUBECONFIG=/tmp/elixir-kubeconfig.yml + + kubectl create namespace ai-ha-elixir --dry-run=client -o yaml | kubectl apply -f - + + kubectl get secret ai-ha-elixir-secrets --namespace ai-ha-elixir || \ + kubectl create secret generic ai-ha-elixir-secrets \ + --namespace ai-ha-elixir \ + --from-literal=SECRET_KEY_BASE=$(openssl rand -hex 64) \ + --from-literal=AI_TOKEN="$AI_TOKEN" + + for file in kubernetes/*.yml; do + cat "$file" | envsubst | kubectl apply -f - + done diff --git a/config/runtime.exs b/config/runtime.exs index 8162a71..51114e6 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -55,7 +55,7 @@ if config_env() == :prod do k8s_dns: [ strategy: Cluster.Strategy.Kubernetes.DNS, config: [ - service: System.get_env("K8S_SERVICE_NAME") || "elixir-ai-headless", + service: System.get_env("K8S_SERVICE_NAME") || "ai-ha-elixir-headless", application_name: "elixir_ai", namespace: System.get_env("K8S_NAMESPACE") ] diff --git a/kubernetes/configmap.yml b/kubernetes/configmap.yml new file mode 100644 index 0000000..077dd82 --- /dev/null +++ b/kubernetes/configmap.yml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: ai-ha-elixir-config + namespace: ai-ha-elixir +data: + PHX_SERVER: "true" + PORT: "4000" + POOL_SIZE: "5" + K8S_NAMESPACE: ai-ha-elixir + K8S_SERVICE_NAME: ai-ha-elixir-headless + RELEASE_COOKIE: elixir_ai_cluster_cookie + PHX_HOST: ai-ha.alexmickelson.guru + DATABASE_URL: ecto://elixir_ai:elixir_ai@postgres-service.postgres/elixir_ai + AI_RESPONSES_ENDPOINT: http://ai-snow.reindeer-pinecone.ts.net:9292/v1/chat/completions + AI_MODEL: gpt-oss-120b diff --git a/kubernetes/ingress.yml b/kubernetes/ingress.yml new file mode 100644 index 0000000..acce3be --- /dev/null +++ b/kubernetes/ingress.yml @@ -0,0 +1,26 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: ai-ha-elixir + namespace: ai-ha-elixir + annotations: + # WebSocket support for LiveView + nginx.ingress.kubernetes.io/proxy-read-timeout: "86400" + nginx.ingress.kubernetes.io/proxy-send-timeout: "86400" + nginx.ingress.kubernetes.io/proxy-http-version: "1.1" + nginx.ingress.kubernetes.io/configuration-snippet: | + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; +spec: + ingressClassName: nginx + rules: + - host: ai-ha.alexmickelson.guru + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: ai-ha-elixir + port: + number: 80 diff --git a/kubernetes/services.yml b/kubernetes/services.yml new file mode 100644 index 0000000..207309d --- /dev/null +++ b/kubernetes/services.yml @@ -0,0 +1,34 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: ai-ha-elixir +--- +apiVersion: v1 +kind: Service +metadata: + name: ai-ha-elixir-headless + namespace: ai-ha-elixir +spec: + clusterIP: None + selector: + app: ai-ha-elixir + ports: + - name: http + port: 4000 + - name: epmd + port: 4369 + - name: beam + port: 9000 +--- +apiVersion: v1 +kind: Service +metadata: + name: ai-ha-elixir + namespace: ai-ha-elixir +spec: + selector: + app: ai-ha-elixir + ports: + - name: http + port: 80 + targetPort: 4000 diff --git a/kubernetes/statefulset.yml b/kubernetes/statefulset.yml new file mode 100644 index 0000000..7d18c73 --- /dev/null +++ b/kubernetes/statefulset.yml @@ -0,0 +1,67 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: ai-ha-elixir + namespace: ai-ha-elixir +spec: + serviceName: ai-ha-elixir-headless + replicas: 3 + selector: + matchLabels: + app: ai-ha-elixir + template: + metadata: + labels: + app: ai-ha-elixir + spec: + containers: + - name: ai-ha-elixir + image: alexmickelson/ai-liveview:$GITHUB_RUN_NUMBER + ports: + - containerPort: 4000 + name: http + - containerPort: 4369 + name: epmd + - containerPort: 9000 + name: beam + envFrom: + - configMapRef: + name: ai-ha-elixir-config + env: + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: RELEASE_NODE + value: "elixir_ai@$(POD_IP)" + - name: SECRET_KEY_BASE + valueFrom: + secretKeyRef: + name: ai-ha-elixir-secrets + key: SECRET_KEY_BASE + - name: AI_TOKEN + valueFrom: + secretKeyRef: + name: ai-ha-elixir-secrets + key: AI_TOKEN + readinessProbe: + httpGet: + path: / + port: 4000 + initialDelaySeconds: 10 + periodSeconds: 5 + failureThreshold: 3 + livenessProbe: + httpGet: + path: / + port: 4000 + initialDelaySeconds: 20 + periodSeconds: 10 + failureThreshold: 5 + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m"