🌳
pt0/deployF/k8sF/jobTypeF.mts
1import * as _ from 'lodash-es'
3import { envHToA } from "./envHToAF.mts"
8import type { V1Volume, V1VolumeMount, V1HostAlias, V1SecurityContext, V1PodSecurityContext, V1Toleration, V1EnvVarSource } from '@kubernetes/client-node'
10/** Pod-level options shared by all workload kinds */
11export type PodSpecOpts = {
12 securityContext?: V1SecurityContext // container-level (privileged/capabilities/runAs*)
13 podSecurityContext?: V1PodSecurityContext
14 tolerations?: V1Toleration[]
15 hostNetwork?: boolean
16 hostPID?: boolean
17 hostIPC?: boolean
18 nodeSelector?: Record<string, string>
19 serviceAccountName?: string
22export type DeploymentJob = PodSpecOpts & {
23 kind: 'Deployment'
24 replicas?: number
25 strategy?: object
27export type DaemonSetJob = PodSpecOpts & {
28 kind: 'DaemonSet'
29 updateStrategy?: object
31export type SingleJob = PodSpecOpts & {
32 kind: 'Job'
33 backoffLimit?: number
34 ttlSecondsAfterFinished?: number
36export type CronJobJob = PodSpecOpts & {
37 kind: 'CronJob'
38 schedule: string
39 concurrencyPolicy?: string
40 backoffLimit?: number
41 activeDeadlineSec?: number
44export type JobType = DeploymentJob | DaemonSetJob | SingleJob | CronJobJob
46/** DRY defaults for periodic idempotent sync cronjobs: replace any stuck run each tick, kill stuck runs well under the 5-min interval so a degraded cluster can't pile up unbounded Pending pods. */
47export const syncCronJobPolicy = {concurrencyPolicy: 'Replace' as const, activeDeadlineSec: 240}
49type ResourceForJobTypeProps = {
50 jobType: JobType
51 name: string
52 cluster_name: string
53 dockreg_host?: string
54 image: string
55 taskCmd: string
56 volumes?: V1Volume[]
57 volumeMounts?: V1VolumeMount[]
58 git_sha: string
59 action?: string
60 hasPvc?: boolean
61 colocateWithPod?: string
62 envLocal: Record<string, string | V1EnvVarSource>
65export const hostAliasesCtx = genContext<{hostAliases?: V1HostAlias[]}>()
67export const resourceForJobType = async ({jobType, name, cluster_name, dockreg_host, image, taskCmd, volumes, volumeMounts, git_sha, action, hasPvc, colocateWithPod, envLocal}: ResourceForJobTypeProps) => {
69 const {kind, securityContext, podSecurityContext, tolerations, hostNetwork, hostPID, hostIPC, nodeSelector, serviceAccountName} = jobType
71 const {hostAliases} = hostAliasesCtx.getStore() || {}
73 assertDefined(envLocal)
75 const env = envHToA(envLocal)
77 // PodSpec shared by all kinds. PVC-colocation affinity is Deployment-only (meaningless for DaemonSet one-per-node), applied in the Deployment branch.
78 const spec: Record<string, unknown> = {
79 volumes,
80 imagePullSecrets: await getImagePullSecrets(),
81 hostAliases,
82 ...(podSecurityContext && {securityContext: podSecurityContext}),
83 ...(tolerations && {tolerations}),
84 ...(hostNetwork && {hostNetwork}),
85 ...(hostPID && {hostPID}),
86 ...(hostIPC && {hostIPC}),
87 ...(nodeSelector && {nodeSelector}),
88 ...(serviceAccountName && {serviceAccountName}),
89 containers: [
90 {
91 env,
92 name, image,
93 volumeMounts,
94 ...(securityContext && {securityContext}),
95 command: [ '/bin/sh', '-c' ],
96 args: [
97 taskCmd
98 ],
99 }
100 ]
101 }
103 if (kind === 'Deployment') {
104 const {replicas, strategy} = jobType
105 if (hasPvc) {
106 spec.affinity = {
107 podAffinity: {
108 requiredDuringSchedulingIgnoredDuringExecution: [
109 { // ensures pods on same node for ReadWriteOnce
110 // https://stackoverflow.com/questions/65313780/kubernetes-how-to-config-a-group-of-pods-to-be-deployed-on-the-same-node
111 topologyKey: 'kubernetes.io/hostname',
112 labelSelector: {
113 matchExpressions: [
114 {
115 key: 'name',
116 operator: 'In',
117 values: [colocateWithPod || name]
118 }
119 ]
120 }
121 }
122 ]
123 }
124 }
125 }
127 name, spec, git_sha, jobType: {replicas}, strategy: hasPvc ? {type: 'Recreate', rollingUpdate: null} : strategy
128 })
129 }
131 if (kind === 'DaemonSet') {
132 const {updateStrategy} = jobType
134 name, spec, git_sha, updateStrategy
135 })
136 }
138 Object.assign(spec, {
139 restartPolicy: 'Never',
140 })
142 if (kind == 'Job') {
143 const {backoffLimit=0, ttlSecondsAfterFinished} = jobType
144 return {
145 apiVersion: 'batch/v1',
146 kind: 'Job',
147 metadata: {
148 name,
149 },
150 spec: {
151 ttlSecondsAfterFinished: ttlSecondsAfterFinished ?? 60 * 60 * 24 * 7,
152 backoffLimit,
153 template: {
154 metadata: {
155 labels: { name, git_sha }
156 },
157 spec
158 }
159 }
160 }
161 }
163 if (kind == 'CronJob') {
164 const {schedule, concurrencyPolicy, backoffLimit=0, activeDeadlineSec} = jobType
165 return {
166 apiVersion: 'batch/v1',
167 kind: 'CronJob',
168 metadata: {
169 name,
170 },
171 spec: {
172 schedule, concurrencyPolicy,
173 successfulJobsHistoryLimit: 3,
174 failedJobsHistoryLimit: 3,
175 jobTemplate: {
176 spec: {
177 ttlSecondsAfterFinished: 60 * 60 * 2,
178 backoffLimit,
179 activeDeadlineSeconds: activeDeadlineSec,
180 template: {
181 metadata: {
182 labels: { name, git_sha }
183 },
184 spec
185 }
186 }
187 }
188 }
189 }
190 }
191 throwDebugH({jobType})