{{- include "jitsu-dev.validateReverseEtl" . -}} {{- if gt (int .Values.scaling.syncctl.replicas) 0 }} {{- $reverse := .Values.reverseEtl | default dict }} apiVersion: apps/v1 kind: Deployment metadata: name: syncctl labels: {{- include "jitsu-dev.labels" . | nindent 4 }} app.kubernetes.io/name: syncctl spec: replicas: {{ .Values.scaling.syncctl.replicas }} selector: matchLabels: app.kubernetes.io/name: syncctl app.kubernetes.io/instance: {{ .Release.Name }} template: metadata: labels: app.kubernetes.io/name: syncctl app.kubernetes.io/instance: {{ .Release.Name }} spec: serviceAccountName: syncctl initContainers: - name: build image: golang:1.26-bookworm workingDir: /src/sync-controller command: - sh - -c - | echo "Copying source..." cp -r /project/bulker/* /src/ echo "Waiting for build lock (Go builds are serialized to avoid overloading the node)..." flock /go-cache/build.lock \ go build -buildvcs=false -o /build/syncctl . echo "Build complete" volumeMounts: - name: project mountPath: /project readOnly: true - name: build mountPath: /build - name: go-cache mountPath: /go-cache - name: src mountPath: /src env: - name: GOCACHE value: /go-cache/build - name: GOMODCACHE value: /go-cache/mod # Cap the build so a compile can't starve the kubelet or running # pods. Builds are additionally serialized via the flock above, so # at most one build is active — and it reuses the shared GOCACHE # warmed by whichever build ran first. Go 1.26 is container-aware: # GOMAXPROCS follows the cgroup CPU limit automatically. resources: requests: cpu: 500m memory: 512Mi limits: cpu: "3" memory: 3Gi containers: - name: syncctl image: debian:bookworm-slim command: ["/build/syncctl"] ports: - name: http containerPort: 3043 protocol: TCP - name: metrics containerPort: 9091 protocol: TCP envFrom: - secretRef: name: jitsu-secrets # Connection URLs of the in-cluster dependencies (helm-deps chart). # Listed after jitsu-secrets so its keys win over any legacy URL # keys there; explicit env entries below still take precedence. - secretRef: name: jitsu-deps-urls optional: true {{- with $reverse.controllerSecret }} - secretRef: name: {{ . | quote }} {{- end }} {{- with .Values.syncctlExtraEnvFrom }} {{- toYaml . | nindent 12 }} {{- end }} env: # Prefixed values override legacy envFrom settings, including while disabled. - name: SYNCCTL_REVERSE_ENABLED value: {{ .Values.reverseEtl.enabled | quote }} - name: SYNCCTL_REVERSE_RUNNER_IMAGE value: {{ .Values.reverseEtl.runnerImage | quote }} - name: SYNCCTL_REVERSE_RUNTIME_SECRET value: {{ .Values.reverseEtl.runtimeSecret | quote }} - name: SYNCCTL_EXTRA_CA_CONFIG_MAP value: {{ .Values.syncctlExtraCAConfigMap | default "" | quote }} - name: SYNCCTL_REVERSE_SERVICE_ACCOUNT value: {{ include "jitsu-dev.reverseServiceAccount" . | quote }} - name: SYNCCTL_REVERSE_RUNNER_RESOURCES value: {{ .Values.reverseEtl.resources | toJson | quote }} - name: SYNCCTL_REVERSE_SCRATCH_SIZE_LIMIT value: {{ .Values.reverseEtl.scratchSizeLimit | quote }} - name: KUBERNETES_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace # Sync Pods (CronJob + ad-hoc job_runner) run as `sync-pod`, a # least-privilege SA bound only to coordination.k8s.io/leases. # Separating it from syncctl's own SA keeps the pod-side blast # radius tiny (no pod create/delete, no Secret read, no log access). {{- include "jitsu-dev.env" (dict "ctx" . "service" "syncctl" "extra" (merge (dict "HTTP_PORT" "3043" "PODS_SERVICE_ACCOUNT" "sync-pod") (ternary (dict "SSL_CERT_FILE" "/etc/jitsu-ca/ca.crt") (dict) (not (empty .Values.syncctlExtraCAConfigMap)))) "exclude" (list "KUBERNETES_NAMESPACE")) | nindent 12 }} volumeMounts: - name: build mountPath: /build readOnly: true {{- if .Values.syncctlExtraCAConfigMap }} - name: extra-ca mountPath: /etc/jitsu-ca readOnly: true {{- end }} livenessProbe: httpGet: path: /health port: http initialDelaySeconds: 5 periodSeconds: 10 readinessProbe: httpGet: path: /health port: http initialDelaySeconds: 5 periodSeconds: 10 volumes: {{- if .Values.syncctlExtraCAConfigMap }} - name: extra-ca configMap: name: {{ .Values.syncctlExtraCAConfigMap | quote }} items: - key: ca.crt path: ca.crt {{- end }} - name: project hostPath: path: {{ .Values.syncctlProjectRoot | default (include "jitsu-dev.projectRoot" .) | quote }} type: Directory - name: build emptyDir: {} - name: src emptyDir: {} - name: go-cache persistentVolumeClaim: claimName: go-cache --- apiVersion: v1 kind: Service metadata: name: syncctl labels: {{- include "jitsu-dev.labels" . | nindent 4 }} app.kubernetes.io/name: syncctl spec: # Controller authentication is optional; never expose its control plane by default. type: ClusterIP ports: - port: 3043 targetPort: http protocol: TCP name: http - port: 9091 targetPort: metrics protocol: TCP name: metrics selector: app.kubernetes.io/name: syncctl app.kubernetes.io/instance: {{ .Release.Name }} --- apiVersion: v1 kind: ServiceAccount metadata: name: syncctl labels: {{- include "jitsu-dev.labels" . | nindent 4 }} --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: {{ .Release.Name }}-syncctl labels: {{- include "jitsu-dev.labels" . | nindent 4 }} rules: - apiGroups: [""] resources: ["pods", "services", "configmaps", "secrets"] verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] # JobRunner.getPodLogs (bulker/sync-controller/job_runner.go) tails source + # sidecar logs to surface them on sync failure. - apiGroups: [""] resources: ["pods/log"] verbs: ["get"] # JobRunner.getPodResUsage (bulker/sync-controller/job_runner.go) execs # `cat /sys/fs/cgroup/...` inside the running source container to sample # CPU/memory usage. Requires create on pods/exec. - apiGroups: [""] resources: ["pods/exec"] verbs: ["create"] - apiGroups: ["apps"] resources: ["deployments", "statefulsets"] verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] - apiGroups: ["batch"] resources: ["jobs", "cronjobs"] verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: {{ .Release.Name }}-syncctl labels: {{- include "jitsu-dev.labels" . | nindent 4 }} roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: {{ .Release.Name }}-syncctl subjects: - kind: ServiceAccount name: syncctl namespace: {{ .Release.Namespace }} --- # `sync-pod` is the dedicated SA used by sync runtime pods (CronJob fires + # ad-hoc job_runner pods). It only needs coordination.k8s.io/leases — the # sidecar uses one Lease per syncID as a cross-pod mutex (see # bulker/sync-sidecar/lease.go). Namespace-scoped Role: the lease lives in # syncctl's own namespace. apiVersion: v1 kind: ServiceAccount metadata: name: sync-pod labels: {{- include "jitsu-dev.labels" . | nindent 4 }} --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: {{ .Release.Name }}-sync-pod labels: {{- include "jitsu-dev.labels" . | nindent 4 }} rules: - apiGroups: ["coordination.k8s.io"] resources: ["leases"] verbs: ["get", "create", "update", "delete"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: {{ .Release.Name }}-sync-pod labels: {{- include "jitsu-dev.labels" . | nindent 4 }} roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: {{ .Release.Name }}-sync-pod subjects: - kind: ServiceAccount name: sync-pod namespace: {{ .Release.Namespace }} {{- end }}