apiVersion: apps/v1 kind: Deployment metadata: name: jarvis-web namespace: jarvis spec: replicas: 2 selector: matchLabels: app.kubernetes.io/name: jarvis app.kubernetes.io/component: web strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 0 maxSurge: 1 template: metadata: labels: app.kubernetes.io/name: jarvis app.kubernetes.io/component: web spec: # Nothing here holds a long-lived connection of its own — the websockets go to the API — so # this is only about letting in-flight responses finish. terminationGracePeriodSeconds: 30 # Required by the `restricted` Pod Security Standard that 00-namespace.yaml enforces. securityContext: seccompProfile: type: RuntimeDefault # Same reasoning as 04-api.yaml: the scheduler will happily put both replicas on one node, # and a pair that shares a node is a pair that shares a failure. Cheaper to satisfy here — # nothing about serving static files pins a pod anywhere. topologySpreadConstraints: - maxSkew: 1 topologyKey: kubernetes.io/hostname whenUnsatisfiable: ScheduleAnyway labelSelector: matchLabels: app.kubernetes.io/name: jarvis app.kubernetes.io/component: web containers: - name: web # By digest, like the API. It matters more here than it looks: the bundle's JavaScript # chunks are content-hashed PER BUILD and answer 404 rather than falling through to # index.html, so during a rollout a browser holding the previous shell can fail a lazy # import. Two pods on two different builds widen that window; two pods on one digest do # not have it at all. image: git.luxit.be/luxit/jarvis-web@sha256:REPLACE_ME ports: - name: http containerPort: 8080 lifecycle: preStop: exec: command: ["/bin/sh", "-c", "sleep 5"] envFrom: - configMapRef: name: jarvis-web-config # Is this nginx answering? Nothing more. Restarting the web container because the API is # down would take out the one thing still able to show an error page. livenessProbe: httpGet: path: /healthz port: http periodSeconds: 10 failureThreshold: 3 # Can THIS pod reach the API? It proxies /api/health/live, not /ready, on purpose: the # question is whether this pod's route works — a broken DNS name, a NetworkPolicy — and # not whether the API's dependencies are healthy. Pointed at /ready, one Postgres blip # would fail every web pod at once and take the console offline for a fault that has # nothing to do with serving a bundle. readinessProbe: httpGet: path: /readyz port: http periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 3 resources: requests: cpu: 50m memory: 64Mi limits: memory: 256Mi securityContext: # uid 101 is the `nginx` user the image already ships and the Dockerfile switches to. runAsNonRoot: true runAsUser: 101 allowPrivilegeEscalation: false capabilities: drop: ["ALL"] readOnlyRootFilesystem: true volumeMounts: # With a read-only root filesystem, the three paths this container writes at runtime # have to be given to it. All three are ephemeral by nature — the substituted config is # regenerated at every start from the template and the ConfigMap. - name: nginx-conf mountPath: /etc/nginx/conf.d - name: nginx-cache mountPath: /var/cache/nginx - name: tmp # The pid file lives here rather than in /var/run, which the nginx user does not own. mountPath: /tmp volumes: - name: nginx-conf emptyDir: {} - name: nginx-cache emptyDir: {} - name: tmp emptyDir: {} --- apiVersion: v1 kind: Service metadata: name: jarvis-web namespace: jarvis spec: type: ClusterIP selector: app.kubernetes.io/name: jarvis app.kubernetes.io/component: web ports: - name: http port: 8080 targetPort: http --- # Web scales on CPU; the API deliberately does not. # # Every API scale-in kills a pod holding agent websockets, live assistant runs and open shells — # the fleet reconnects, runs are interrupted and resumed, terminals die — and CPU is a poor proxy # for a load made almost entirely of long-lived connections. Raise `jarvis-api` deliberately # instead. If it ever does get an HPA, give it a scale-down stabilization window of ten minutes or # more. apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: jarvis-web namespace: jarvis spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: jarvis-web minReplicas: 2 maxReplicas: 5 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70