From a0ebc1183c004a7a9c687fa0a972aabddf031ce8 Mon Sep 17 00:00:00 2001 From: Story Crater Bot <19826264+Riotpiaole@users.noreply.github.com> Date: Sun, 23 Aug 2026 00:01:30 -0700 Subject: [PATCH] Add K8s app deployment, Dockerfile, and CI workflow (Option A) --- .dockerignore | 28 +++++++++ .forgejo/workflows/build.yaml | 61 ++++++++++++++++++++ Dockerfile | 50 ++++++++++++++++ crates/mem-cli/src/http_server.rs | 2 +- k8s/app/deployment.yaml | 95 +++++++++++++++++++++++++++++++ k8s/app/kustomization.yaml | 8 +++ k8s/app/secrets.yaml | 12 ++++ k8s/app/service.yaml | 17 ++++++ 8 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 .forgejo/workflows/build.yaml create mode 100644 Dockerfile create mode 100644 k8s/app/deployment.yaml create mode 100644 k8s/app/kustomization.yaml create mode 100644 k8s/app/secrets.yaml create mode 100644 k8s/app/service.yaml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2607305 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,28 @@ +# Build artifacts +target/ +*.rs.bk + +# Version control +.git/ +.gitignore + +# IDE +.idea/ +.vscode/ +*.swp + +# CI +.github/ +.forgejo/ + +# Documentation +*.md +!README.md + +# Tests (keep for build cache, exclude from runtime) +tests/ +fixtures/ + +# Logs +log/ +*.log diff --git a/.forgejo/workflows/build.yaml b/.forgejo/workflows/build.yaml new file mode 100644 index 0000000..0d631ea --- /dev/null +++ b/.forgejo/workflows/build.yaml @@ -0,0 +1,61 @@ +name: Build and Push + +on: + push: + branches: [main] + pull_request: + branches: [main] + +env: + REGISTRY: forgejo.riotpiao.com + IMAGE_NAME: rock/poimen-memory + +jobs: + test: + name: Test + runs-on: rust + steps: + - uses: actions/checkout@v4 + + - name: Build + run: cargo build --workspace + + - name: Test + run: cargo test --all + + build: + name: Build Image + runs-on: rust + needs: test + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=sha,prefix= + type=raw,value=latest + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e0d4626 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,50 @@ +# Build stage +FROM rust:1.82-slim-bookworm AS builder + +WORKDIR /app + +# Install build dependencies +RUN apt-get update && apt-get install -y \ + pkg-config \ + libssl-dev \ + && rm -rf /var/lib/apt/lists/* + +# Copy manifests +COPY Cargo.toml Cargo.lock ./ +COPY crates ./crates + +# Build release binary +RUN cargo build --release --bin mem + +# Runtime stage +FROM debian:bookworm-slim + +WORKDIR /app + +# Install runtime dependencies +RUN apt-get update && apt-get install -y \ + ca-certificates \ + libssl3 \ + && rm -rf /var/lib/apt/lists/* + +# Copy binary from builder +COPY --from=builder /app/target/release/mem /usr/local/bin/mem + +# Copy templates and queries +COPY templates ./templates +COPY queries ./queries + +# Create non-root user +RUN useradd -r -u 1000 memuser +USER memuser + +# Default port +EXPOSE 8080 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:8080/health || exit 1 + +# Default command: start HTTP server +ENTRYPOINT ["mem"] +CMD ["serve", "--port", "8080"] diff --git a/crates/mem-cli/src/http_server.rs b/crates/mem-cli/src/http_server.rs index bbbf360..f236c1f 100644 --- a/crates/mem-cli/src/http_server.rs +++ b/crates/mem-cli/src/http_server.rs @@ -48,7 +48,7 @@ pub async fn start_server(port: u16, api_key: String) -> Result<()> { .route("/memory/projects", web::get().to(projects_handler)) .route("/memory/projects/{id}/status", web::get().to(project_status)) }) - .bind(("127.0.0.1", port))? + .bind(("0.0.0.0", port))? .run() .await?; diff --git a/k8s/app/deployment.yaml b/k8s/app/deployment.yaml new file mode 100644 index 0000000..23d417f --- /dev/null +++ b/k8s/app/deployment.yaml @@ -0,0 +1,95 @@ +# Poimen Memory API Server +# Serves 7 HTTP endpoints for memory ingest, query, and management. +# Connects to memory-db (pgvector) for persistent storage. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: poimen-memory + namespace: poimen + labels: + app.kubernetes.io/name: poimen-memory + app.kubernetes.io/component: api-server +spec: + replicas: 2 + selector: + matchLabels: + app.kubernetes.io/name: poimen-memory + template: + metadata: + labels: + app.kubernetes.io/name: poimen-memory + spec: + containers: + - name: memory + image: forgejo.riotpiao.com/rock/poimen-memory:latest + imagePullPolicy: Always + ports: + - containerPort: 8080 + name: http + env: + # Database connection (from CNPG auto-generated secret) + - name: DATABASE_HOST + value: "memory-db-rw.poimen.svc.cluster.local" + - name: DATABASE_PORT + value: "5432" + - name: DATABASE_NAME + value: "memory" + - name: DATABASE_USER + valueFrom: + secretKeyRef: + name: memory-db-app + key: username + - name: DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: memory-db-app + key: password + - name: DATABASE_URL + value: "postgresql://$(DATABASE_USER):$(DATABASE_PASSWORD)@$(DATABASE_HOST):$(DATABASE_PORT)/$(DATABASE_NAME)?sslmode=disable" + # LLM Gateway API key + - name: MEM_API_KEY + valueFrom: + secretKeyRef: + name: poimen-memory-secrets + key: llm-api-key + # Server config + - name: MEM_PORT + value: "8080" + - name: MEM_HOME + value: "/data" + args: + - serve + - --port + - "8080" + - --api-key + - "$(MEM_API_KEY)" + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 10 + periodSeconds: 30 + readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 5 + periodSeconds: 10 + volumeMounts: + - name: data + mountPath: /data + volumes: + - name: data + emptyDir: {} + # Tolerate control-plane nodes + tolerations: + - key: node-role.kubernetes.io/control-plane + operator: Exists + effect: NoSchedule diff --git a/k8s/app/kustomization.yaml b/k8s/app/kustomization.yaml new file mode 100644 index 0000000..3038c22 --- /dev/null +++ b/k8s/app/kustomization.yaml @@ -0,0 +1,8 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: poimen +resources: + - deployment.yaml + - service.yaml + # Secret managed separately (SealedSecret in homelab) + # - secrets.yaml diff --git a/k8s/app/secrets.yaml b/k8s/app/secrets.yaml new file mode 100644 index 0000000..4aa8b43 --- /dev/null +++ b/k8s/app/secrets.yaml @@ -0,0 +1,12 @@ +# Poimen Memory Secrets +# NOTE: In production, use SealedSecrets or ExternalSecrets. +# This is a placeholder - actual secret should be in homelab/k8s/argocd/secrets/ +apiVersion: v1 +kind: Secret +metadata: + name: poimen-memory-secrets + namespace: poimen +type: Opaque +stringData: + # LLM Gateway API key (Kong gateway) + llm-api-key: "REPLACE_WITH_ACTUAL_KEY" diff --git a/k8s/app/service.yaml b/k8s/app/service.yaml new file mode 100644 index 0000000..b05ce0c --- /dev/null +++ b/k8s/app/service.yaml @@ -0,0 +1,17 @@ +# Poimen Memory API Service +apiVersion: v1 +kind: Service +metadata: + name: poimen-memory + namespace: poimen + labels: + app.kubernetes.io/name: poimen-memory +spec: + type: ClusterIP + ports: + - port: 8080 + targetPort: http + protocol: TCP + name: http + selector: + app.kubernetes.io/name: poimen-memory