apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "svcforge.fullname" . }}-worker labels: {{- include "svcforge.labels" . | nindent 4 }} app.kubernetes.io/component: worker spec: # Plain replicas, no HPA. Concurrency is bounded twice over — by replicas here and by a # semaphore inside the claim loop — and the SKIP LOCKED claim makes both safe. The day # replicas: 2 stops keeping up, add an HPA. Not before. replicas: {{ .Values.worker.replicas }} selector: matchLabels: {{- include "svcforge.selectorLabels" (dict "ctx" $ "component" "worker") | nindent 6 }} template: metadata: labels: {{- include "svcforge.labels" . | nindent 8 }} {{- include "svcforge.selectorLabels" (dict "ctx" $ "component" "worker") | nindent 8 }} spec: serviceAccountName: {{ include "svcforge.serviceAccountName" (dict "ctx" $ "component" "worker") }} {{- with .Values.image.pullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} # The load-bearing one. On SIGTERM the loop stops claiming and finishes the task in # hand. Chaos experiment 2 asserts the pod exits 0 inside it. Lower this and a # rolling deploy starts orphaning tasks to lease expiry. # # 660s, and the number is derived, not chosen. The task being drained is a # `helm upgrade --install`, and this MUST exceed the longest that call can run before # it returns: # - settings.py helm_timeout_s = 300 (default) # - HelmProvisioner timeout_s = 600 (default, the ceiling) # 600s is the value to beat; 660 leaves a 60s margin for the pool to close and the # process to exit cleanly. At the old 60s the kubelet SIGKILLed the worker mid-helm — # precisely the orphaning this setting exists to prevent. If either timeout above is # raised, raise this past it first. terminationGracePeriodSeconds: 660 securityContext: {{- include "svcforge.podSecurityContext" . | nindent 8 }} containers: - name: worker image: {{ include "svcforge.image" (dict "ctx" $ "component" "worker") }} imagePullPolicy: {{ .Values.image.pullPolicy }} securityContext: {{- include "svcforge.containerSecurityContext" . | nindent 12 }} ports: - name: metrics containerPort: 9000 envFrom: - secretRef: name: {{ include "svcforge.secretName" . }} env: {{- include "svcforge.env" . | nindent 12 }} # The claim loop stamps locked_by with this. Per-pod, so a stuck lease names # the pod that holds it — runbook entry 1 depends on that. - name: SVCFORGE_WORKER_ID valueFrom: fieldRef: fieldPath: metadata.name - name: SVCFORGE_WORKER_CONCURRENCY value: {{ .Values.worker.concurrency | quote }} - name: OTEL_SERVICE_NAME value: svcforge-worker # No liveness probe. A worker mid-provision is legitimately busy for minutes; # a probe here is a way to kill a healthy provision and learn nothing. resources: {{- toYaml .Values.worker.resources | nindent 12 }} volumeMounts: - name: tmp mountPath: /tmp # helm writes cache/config/repositories under $HELM_*_HOME, which the image # points at /tmp/helm. Without this, every helm call fails on a read-only fs. - name: helm-home mountPath: /tmp/helm volumes: - name: tmp emptyDir: {} - name: helm-home emptyDir: {} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- end }}