@leverege/build-tools 2.65.0 → 2.66.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/Utils.mjs +5 -4
- package/src/clone-cnpg-from-snapshot.mjs +132 -48
- package/src/helm-charts/cnpg-operator/cnpg-cleanup-cronjob.yaml.ovh +67 -0
- package/src/helm-charts/cnpg-operator/cnpg-cleanup-script.sh +73 -0
- package/src/helm-charts/cnpg-operator/gitignore +1 -0
- package/src/helm-charts/cnpg-operator/helmup-1.26.0-rc1.plugin +28 -0
- package/src/helm-charts/cnpg-operator/helmup.plugin +2 -3
- package/src/helm-charts/elasticsearch8/helmup.plugin +1 -1
- package/src/helm-charts/prom-operator/helmup.plugin +1 -1
- package/src/helm-charts/prom-operator/stackdriver-exporter.yaml.ovh +21 -1
- package/src/helm-charts/redis/helmup.plugin +1 -1
- package/src/helm-charts/traefik/helmup.plugin +1 -1
- package/src/helm-charts/velero/helmup.plugin +1 -1
- package/src/helm-charts/velero/{velero-local.yaml → values-local.yaml} +1 -1
- package/src/helmup.sh +42 -9
- package/src/templates/cloudBuildSteps.hbs +6 -2
- package/src/templates/dockerfileTemplatePy.hbs +1 -3
- package/src/helm-charts/cnpg-operator/cnpg-cleanup-snapshot-cronjob.yaml +0 -73
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leverege/build-tools",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.66.0",
|
|
4
4
|
"description": "A collection of build / support tools for Leverege developers",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -83,10 +83,10 @@
|
|
|
83
83
|
"semver": "^7.7.1",
|
|
84
84
|
"simple-git": "^3.27.0",
|
|
85
85
|
"toml": "^3.0.0",
|
|
86
|
-
"zx": "^8.5.
|
|
86
|
+
"zx": "^8.5.2"
|
|
87
87
|
},
|
|
88
88
|
"devDependencies": {
|
|
89
89
|
"@leverege/eslint-config-leverege": "^5.0.1",
|
|
90
|
-
"npm": "^11.
|
|
90
|
+
"npm": "^11.3.0"
|
|
91
91
|
}
|
|
92
92
|
}
|
package/src/Utils.mjs
CHANGED
|
@@ -90,11 +90,12 @@ export const shellCmd = async ( cmdstr, opts = {} ) => {
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
export const mkdirSafe =
|
|
93
|
+
export const mkdirSafe = ( dir ) => {
|
|
94
94
|
try {
|
|
95
|
-
|
|
96
|
-
} catch (
|
|
97
|
-
|
|
95
|
+
fs.mkdirSync( dir, { recursive : true } ) // eslint-disable-line security/detect-non-literal-fs-filename
|
|
96
|
+
} catch ( error ) {
|
|
97
|
+
condir( { error }, '<==fs.mkdir failed?' )
|
|
98
|
+
warning( { error }, `Could not create output dir: ${dir}` )
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
101
|
|
|
@@ -25,8 +25,7 @@ program
|
|
|
25
25
|
.requiredOption( '--source-project <project>', 'Source GCP project ID' )
|
|
26
26
|
.requiredOption( '--dest-project <project>', 'Destination GCP project ID' )
|
|
27
27
|
.requiredOption( '--dest-region <region>', 'Destination region, e.g. us-east4' )
|
|
28
|
-
.option( '--only
|
|
29
|
-
.option( '--emit-yaml-dir <dir>', 'Directory to emit VolumeSnapshot YAMLs', './out' )
|
|
28
|
+
.option( '--artifacts-only', 'Only generate the volume snapshot YAML, script and readme' )
|
|
30
29
|
.option( '--dry-run', 'Show commands but don\'t run them' )
|
|
31
30
|
.option( '--execute', 'Perform real actions (required to run gcloud commands)' )
|
|
32
31
|
.option( '--cleanup', 'Remove temporary GCP resources after snapshot creation' )
|
|
@@ -42,7 +41,6 @@ const {
|
|
|
42
41
|
destProject,
|
|
43
42
|
destRegion,
|
|
44
43
|
dryRun,
|
|
45
|
-
emitYamlDir,
|
|
46
44
|
snapshot,
|
|
47
45
|
sourceProject,
|
|
48
46
|
} = options
|
|
@@ -55,6 +53,10 @@ const destSnapshot = `snapshot-${sourceProject}-${snapshot}`
|
|
|
55
53
|
const tempDiskFromImage = `temp-from-img-${snapshot}`
|
|
56
54
|
const destZone = `${destRegion}-a` // infer from region - implies "-a" zone always available
|
|
57
55
|
|
|
56
|
+
// directory for receiving the YAML necessary for defining the snapshot class,
|
|
57
|
+
// content and volume
|
|
58
|
+
const emitYamlDir = `cnpg-clone-${snapshot}`
|
|
59
|
+
|
|
58
60
|
let sourceZone // set and used in main(), also needed by cleanupResouces
|
|
59
61
|
|
|
60
62
|
const getPrettyTimeNow = () => {
|
|
@@ -71,6 +73,61 @@ const getElapsedTime = ( startMs ) => {
|
|
|
71
73
|
return `${minutes}:${seconds}`
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
// equivalent to Unix touch command to create an empty file
|
|
77
|
+
const touchFile = async ( filename ) => {
|
|
78
|
+
if ( filename ) {
|
|
79
|
+
const handle = await fs.open( filename, 'w' ) // eslint-disable-line security/detect-non-literal-fs-filename
|
|
80
|
+
await handle.close()
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// generates an executable script to use for defining the volume on k8s
|
|
85
|
+
const writeApplyScript = async ( dir ) => {
|
|
86
|
+
const applyScript = `#!/usr/bin/env bash
|
|
87
|
+
#
|
|
88
|
+
# Apply VolumeSnapshot resources prior to deploying the CNPG cluster
|
|
89
|
+
#
|
|
90
|
+
kubectl apply -f ${dir}/volumesnapshotclass.yaml
|
|
91
|
+
kubectl apply -f ${dir}/volumesnapshotcontent.yaml
|
|
92
|
+
kubectl apply -f ${dir}/volumesnapshot.yaml
|
|
93
|
+
`
|
|
94
|
+
|
|
95
|
+
const scriptPath = path.join( dir, 'apply-volumesnapshots.sh' )
|
|
96
|
+
await fs.writeFile( scriptPath, applyScript, { mode : 0o755 } ) // eslint-disable-line security/detect-non-literal-fs-filename
|
|
97
|
+
log( chalk.green( ' [✓] Helper script written:' ), scriptPath )
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const writeReadme = async ( dir ) => {
|
|
101
|
+
const readmePath = path.join( dir, 'readme.txt' )
|
|
102
|
+
const content = `This directory contains the Kubernetes manifests needed to bootstrap a new CNPG cluster
|
|
103
|
+
using a cloned volume snapshot from another GCP project.
|
|
104
|
+
|
|
105
|
+
Files:
|
|
106
|
+
- volumesnapshotclass.yaml (optional, use if the class doesn't already exist)
|
|
107
|
+
- volumesnapshotcontent.yaml (refers to the GCE snapshot copied into this project)
|
|
108
|
+
- volumesnapshot.yaml (used by CNPG to bootstrap from the volume snapshot)
|
|
109
|
+
- apply-volumesnapshots.sh (helper script to apply these manifests in correct order)
|
|
110
|
+
- .nohelm (marker file for overwhelm to skip values.yaml generation)
|
|
111
|
+
|
|
112
|
+
To deploy the volume snapshots to your Kubernetes cluster:
|
|
113
|
+
|
|
114
|
+
$ ./apply-volumesnapshots.sh
|
|
115
|
+
|
|
116
|
+
Once applied, reference the following VolumeSnapshot in your CNPG cluster bootstrap config:
|
|
117
|
+
|
|
118
|
+
spec.bootstrap.recovery.volumeSnapshot.volumeSnapshotName: ${k8sSnapshotName}
|
|
119
|
+
|
|
120
|
+
Then deploy your CNPG cluster normally. Validate the cluster startup:
|
|
121
|
+
|
|
122
|
+
$ kubectl get pods -n cnpg-operands
|
|
123
|
+
|
|
124
|
+
Enjoy your cloned cluster ✨
|
|
125
|
+
`
|
|
126
|
+
|
|
127
|
+
await fs.writeFile( readmePath, content ) // eslint-disable-line security/detect-non-literal-fs-filename
|
|
128
|
+
log( chalk.green( ' [✓] README written:' ), readmePath )
|
|
129
|
+
}
|
|
130
|
+
|
|
74
131
|
const writeYaml = async ( filename, data ) => {
|
|
75
132
|
const fullPath = path.join( emitYamlDir, filename )
|
|
76
133
|
const yaml = YAML.dump( data )
|
|
@@ -98,7 +155,9 @@ const getVolumeHandle = async ( vscName ) => {
|
|
|
98
155
|
return handle
|
|
99
156
|
}
|
|
100
157
|
|
|
101
|
-
|
|
158
|
+
// this function is responsible for emitting all of the artifacts for the creation
|
|
159
|
+
// of the k8s volume that will then be used to bootstrap the cnpg cluster
|
|
160
|
+
const emitCloningArtifacts = async () => {
|
|
102
161
|
try {
|
|
103
162
|
const snapshotYamlRaw = await shellCmd(
|
|
104
163
|
`kubectl get volumesnapshot ${snapshot} -n cnpg-operands -o json`
|
|
@@ -120,6 +179,14 @@ const generateK8sYaml = async () => {
|
|
|
120
179
|
)
|
|
121
180
|
)
|
|
122
181
|
|
|
182
|
+
const snapshotClass = {
|
|
183
|
+
apiVersion : 'snapshot.storage.k8s.io/v1',
|
|
184
|
+
kind : 'VolumeSnapshotClass',
|
|
185
|
+
metadata : { name : 'cnpg-preprovisioned' },
|
|
186
|
+
driver : 'pd.csi.storage.gke.io',
|
|
187
|
+
deletionPolicy : 'Retain'
|
|
188
|
+
}
|
|
189
|
+
|
|
123
190
|
const snapshotContent = {
|
|
124
191
|
apiVersion : 'snapshot.storage.k8s.io/v1',
|
|
125
192
|
kind : 'VolumeSnapshotContent',
|
|
@@ -155,16 +222,18 @@ const generateK8sYaml = async () => {
|
|
|
155
222
|
},
|
|
156
223
|
}
|
|
157
224
|
|
|
158
|
-
|
|
225
|
+
mkdirSafe( emitYamlDir )
|
|
226
|
+
|
|
227
|
+
await writeYaml( 'volumesnapshotclass.yaml', snapshotClass )
|
|
159
228
|
await writeYaml( 'volumesnapshotcontent.yaml', snapshotContent )
|
|
160
229
|
await writeYaml( 'volumesnapshot.yaml', snapshotYaml )
|
|
230
|
+
await touchFile( path.join( emitYamlDir, '.nohelm' ) )
|
|
231
|
+
await writeApplyScript( emitYamlDir )
|
|
232
|
+
await writeReadme( emitYamlDir )
|
|
233
|
+
|
|
161
234
|
} catch ( err ) {
|
|
162
235
|
errorExit( `Unable to generate VolumeSnapshot YAML: ${err.message}` )
|
|
163
236
|
}
|
|
164
|
-
|
|
165
|
-
if ( options.onlyK8sYaml ) {
|
|
166
|
-
process.exit( 0 )
|
|
167
|
-
}
|
|
168
237
|
}
|
|
169
238
|
|
|
170
239
|
// runs a CLI command with a silly little spinner in an attempt to convince
|
|
@@ -246,7 +315,11 @@ const main = async () => {
|
|
|
246
315
|
log( ` from handle: ${chalk.cyan( volumeHandle )}` )
|
|
247
316
|
log( ` in zone: ${chalk.cyan( sourceZone )}\n` )
|
|
248
317
|
|
|
249
|
-
|
|
318
|
+
// if all we want are the artifacts
|
|
319
|
+
if ( options.artifactsOnly ) {
|
|
320
|
+
await emitCloningArtifacts()
|
|
321
|
+
process.exit( 0 )
|
|
322
|
+
}
|
|
250
323
|
|
|
251
324
|
log( `\n[STEP 2] 📸 Creating GCE snapshot from source disk [${diskName}]` )
|
|
252
325
|
await runCommand( [
|
|
@@ -309,50 +382,59 @@ const main = async () => {
|
|
|
309
382
|
`--project=${destProject}`,
|
|
310
383
|
] )
|
|
311
384
|
|
|
312
|
-
|
|
385
|
+
await emitCloningArtifacts()
|
|
386
|
+
|
|
387
|
+
log( `
|
|
388
|
+
✅ Done. Snapshot: ${chalk.cyan( destSnapshot )}
|
|
389
|
+
Recovery YAMLs written to: ${chalk.cyan( emitYamlDir )}
|
|
390
|
+
Run '${chalk.yellow( './apply-volumesnapshots.sh' )}' to prep the cluster for recovery.
|
|
391
|
+
` )
|
|
313
392
|
}
|
|
314
393
|
|
|
315
394
|
const cleanupResources = async () => {
|
|
316
395
|
log( '\n[STEP 9] 🧹 Cleaning up temporary resources (unwinding the stack)...' )
|
|
317
396
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
397
|
+
const commands = [
|
|
398
|
+
[ // STEP 7: Remove temp disk from image in destination project
|
|
399
|
+
`gcloud compute disks delete ${tempDiskFromImage}`,
|
|
400
|
+
`--zone=${destZone}`,
|
|
401
|
+
`--project=${destProject}`,
|
|
402
|
+
'--quiet'
|
|
403
|
+
],
|
|
404
|
+
|
|
405
|
+
[ // STEP 6: Remove copied image from destination project
|
|
406
|
+
`gcloud compute images delete ${tempImage}`,
|
|
407
|
+
`--project=${destProject}`,
|
|
408
|
+
'--quiet'
|
|
409
|
+
],
|
|
410
|
+
|
|
411
|
+
[ // STEP 4: Remove image from source project
|
|
412
|
+
`gcloud compute images delete ${tempImage}`,
|
|
413
|
+
`--project=${sourceProject}`,
|
|
414
|
+
'--quiet'
|
|
415
|
+
],
|
|
416
|
+
|
|
417
|
+
[ // STEP 3: Remove temp disk from source project
|
|
418
|
+
`gcloud compute disks delete ${tempDisk}`,
|
|
419
|
+
`--zone=${sourceZone}`,
|
|
420
|
+
`--project=${sourceProject}`,
|
|
421
|
+
'--quiet'
|
|
422
|
+
],
|
|
423
|
+
|
|
424
|
+
[ // STEP 2: Remove source snapshot
|
|
425
|
+
`gcloud compute snapshots delete ${intermediateSnapshot}`,
|
|
426
|
+
`--project=${sourceProject}`,
|
|
427
|
+
'--quiet'
|
|
428
|
+
],
|
|
429
|
+
]
|
|
430
|
+
|
|
431
|
+
await Promise.all(
|
|
432
|
+
commands.map( cmdParts => runCommand( cmdParts ).catch( ( err ) => {
|
|
433
|
+
warning( `Non-fatal error during cleanup: ${err.message}` )
|
|
434
|
+
} ) )
|
|
435
|
+
)
|
|
354
436
|
|
|
355
|
-
log( chalk.green( ' [✓]
|
|
437
|
+
log( chalk.green( '\n [✓] Parallel cleanup complete - final GCE snapshot retained for CNPG bootstrapping\n' ) )
|
|
356
438
|
}
|
|
357
439
|
|
|
358
440
|
if ( cleanupOnly ) {
|
|
@@ -365,3 +447,5 @@ await main().catch( err => errorExit( err.message || err ) )
|
|
|
365
447
|
if ( cleanup ) {
|
|
366
448
|
await cleanupResources()
|
|
367
449
|
}
|
|
450
|
+
|
|
451
|
+
log( chalk.green( ` [✓] Cloning complete - recovery image name is => ${chalk.bold.yellow( k8sSnapshotName )}\n` ) )
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
apiVersion: v1
|
|
2
|
+
kind: ServiceAccount
|
|
3
|
+
metadata:
|
|
4
|
+
name: cnpg-cleanup-sa
|
|
5
|
+
namespace: cnpg-operands
|
|
6
|
+
annotations:
|
|
7
|
+
# wire up this KSA to the GSA to hook into IAM for workload identity
|
|
8
|
+
iam.gke.io/gcp-service-account: cnpg-cleanup-sa@${PROJECT_ID}.iam.gserviceaccount.com
|
|
9
|
+
---
|
|
10
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
11
|
+
kind: Role
|
|
12
|
+
metadata:
|
|
13
|
+
name: cnpg-cleanup-role
|
|
14
|
+
namespace: cnpg-operands
|
|
15
|
+
rules:
|
|
16
|
+
- apiGroups: ["postgresql.cnpg.io"]
|
|
17
|
+
resources: ["backups"]
|
|
18
|
+
verbs: ["get", "list", "delete"]
|
|
19
|
+
- apiGroups: ["snapshot.storage.k8s.io"]
|
|
20
|
+
resources: ["volumesnapshots"]
|
|
21
|
+
verbs: ["get", "list", "delete"]
|
|
22
|
+
---
|
|
23
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
24
|
+
kind: RoleBinding
|
|
25
|
+
metadata:
|
|
26
|
+
name: cnpg-cleanup-rolebinding
|
|
27
|
+
namespace: cnpg-operands
|
|
28
|
+
subjects:
|
|
29
|
+
- kind: ServiceAccount
|
|
30
|
+
name: cnpg-cleanup-sa
|
|
31
|
+
namespace: cnpg-operands
|
|
32
|
+
roleRef:
|
|
33
|
+
kind: Role
|
|
34
|
+
name: cnpg-cleanup-role
|
|
35
|
+
apiGroup: rbac.authorization.k8s.io
|
|
36
|
+
---
|
|
37
|
+
apiVersion: batch/v1
|
|
38
|
+
kind: CronJob
|
|
39
|
+
metadata:
|
|
40
|
+
name: cnpg-cleanup-snapshot
|
|
41
|
+
namespace: cnpg-operands
|
|
42
|
+
spec:
|
|
43
|
+
schedule: "0 11 * * *" # 11 UTC is ~7AM EST
|
|
44
|
+
# schedule: "*/5 * * * *" # every 5 minutes for testing
|
|
45
|
+
successfulJobsHistoryLimit: 1
|
|
46
|
+
jobTemplate:
|
|
47
|
+
spec:
|
|
48
|
+
template:
|
|
49
|
+
spec:
|
|
50
|
+
restartPolicy: Never
|
|
51
|
+
serviceAccountName: cnpg-cleanup-sa
|
|
52
|
+
containers:
|
|
53
|
+
- name: cnpg-cleanup
|
|
54
|
+
image: gcr.io/google.com/cloudsdktool/cloud-sdk:latest
|
|
55
|
+
env:
|
|
56
|
+
- name: DRY_RUN
|
|
57
|
+
value: "false"
|
|
58
|
+
- name: RETENTION_DAYS
|
|
59
|
+
value: "21"
|
|
60
|
+
command:
|
|
61
|
+
- /bin/bash
|
|
62
|
+
- -c
|
|
63
|
+
- |
|
|
64
|
+
echo "🚀 Fetching cleanup script from GCS..."
|
|
65
|
+
apt-get update -qq
|
|
66
|
+
apt-get install -y jq libjq1 libonig5 --no-install-recommends -o=Dpkg::Use-Pty=0 -o=APT::Get::Assume-Yes=1 -o=Debug::pkgProblemResolver=1
|
|
67
|
+
gsutil cat gs://${PROJECT_ID}-k8s-cronjobs/cnpg-cleanup-script.sh | bash
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
KUBECTL_DELETE_ARGS=""
|
|
5
|
+
[[ "$DRY_RUN" == "true" ]] && KUBECTL_DELETE_ARGS="--dry-run=server"
|
|
6
|
+
|
|
7
|
+
# volumesnapshots namespace can be set here but if we need more than just the
|
|
8
|
+
# cnpg-operands then we'll need to the Role with a ClusterRole and bind it
|
|
9
|
+
# differently as well.
|
|
10
|
+
VSNAPS_NS="-n cnpg-operands"
|
|
11
|
+
|
|
12
|
+
# Default retention period (can override with env)
|
|
13
|
+
RETENTION_DAYS="${RETENTION_DAYS:-21}"
|
|
14
|
+
CURRENT_DATE=$(date +%s)
|
|
15
|
+
|
|
16
|
+
log() {
|
|
17
|
+
echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] $*"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
should_delete() {
|
|
21
|
+
local creation_ts="$1"
|
|
22
|
+
local age_days
|
|
23
|
+
local created_secs
|
|
24
|
+
created_secs=$(date -d "$creation_ts" +%s)
|
|
25
|
+
age_days=$(( (CURRENT_DATE - created_secs) / 86400 ))
|
|
26
|
+
if (( age_days > RETENTION_DAYS )); then
|
|
27
|
+
echo "$age_days"
|
|
28
|
+
return 0
|
|
29
|
+
fi
|
|
30
|
+
return 1
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
cleanup_cnpg_backups() {
|
|
34
|
+
log "🔍 Cleaning CNPG backups older than $RETENTION_DAYS days"
|
|
35
|
+
kubectl get backups.postgresql.cnpg.io -n cnpg-operands -o json | jq -c '.items[]' | while read -r item; do
|
|
36
|
+
name=$(echo "$item" | jq -r '.metadata.name')
|
|
37
|
+
created=$(echo "$item" | jq -r '.metadata.creationTimestamp')
|
|
38
|
+
[[ "$name" != *"-backup-"* ]] && log "Skipping manual backup: $name" && continue
|
|
39
|
+
|
|
40
|
+
if age=$(should_delete "$created"); then
|
|
41
|
+
log "🗑 Deleting CNPG backup: $name (Age: $age days)"
|
|
42
|
+
kubectl delete backups.postgresql.cnpg.io "$name" -n cnpg-operands --ignore-not-found --wait=false $KUBECTL_DELETE_ARGS
|
|
43
|
+
fi
|
|
44
|
+
done
|
|
45
|
+
log "✅ CNPG backup cleanup complete"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
cleanup_volume_snapshots() {
|
|
49
|
+
log "🔍 Cleaning VolumeSnapshots older than $RETENTION_DAYS days"
|
|
50
|
+
kubectl get volumesnapshots $VSNAPS_NS -o json | jq -c '.items[]' | while read -r item; do
|
|
51
|
+
ns=$(echo "$item" | jq -r '.metadata.namespace')
|
|
52
|
+
name=$(echo "$item" | jq -r '.metadata.name')
|
|
53
|
+
created=$(echo "$item" | jq -r '.metadata.creationTimestamp')
|
|
54
|
+
ready=$(echo "$item" | jq -r '.status.readyToUse // false')
|
|
55
|
+
|
|
56
|
+
[[ "$ready" != "true" ]] && continue
|
|
57
|
+
|
|
58
|
+
if age=$(should_delete "$created"); then
|
|
59
|
+
log "🗑 Deleting VolumeSnapshot: $ns/$name (Age: $age days)"
|
|
60
|
+
kubectl delete volumesnapshot -n "$ns" "$name" --ignore-not-found --wait=false $KUBECTL_DELETE_ARGS
|
|
61
|
+
fi
|
|
62
|
+
done
|
|
63
|
+
log "✅ VolumeSnapshot cleanup complete"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
main() {
|
|
67
|
+
log "🚀 Starting cleanup job (retention: $RETENTION_DAYS days)"
|
|
68
|
+
cleanup_cnpg_backups
|
|
69
|
+
cleanup_volume_snapshots
|
|
70
|
+
log "🏁 All cleanup tasks complete"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
main
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cnpg-cleanup-cronjob.yaml
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# See => https://cloudnative-pg.io/documentation/current/installation_upgrade/
|
|
4
|
+
#
|
|
5
|
+
# OPVER="1.25.1"
|
|
6
|
+
OPVER="1.26.0-rc1" # 03/28/2025
|
|
7
|
+
|
|
8
|
+
createNamespaceIfNeeded cnpg-system
|
|
9
|
+
|
|
10
|
+
kubectl apply --server-side -f \
|
|
11
|
+
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/main/releases/cnpg-${OPVER}.yaml $K8S_WHAT
|
|
12
|
+
|
|
13
|
+
# Legacy terraformed clusters (like sandbox) may need a special firewall rule
|
|
14
|
+
# added to the network layer on k8s. It should look like this:
|
|
15
|
+
#
|
|
16
|
+
# Name : cnpg-operator
|
|
17
|
+
# Ports : 8000,9443 <= kubectl cnpg status and webhooks
|
|
18
|
+
# Filters: 172.16.0.0/28 <= k8s control plane
|
|
19
|
+
#
|
|
20
|
+
# Old approach used the helm chart.
|
|
21
|
+
#
|
|
22
|
+
#addHelmRepo cnpg https://cloudnative-pg.github.io/charts
|
|
23
|
+
#
|
|
24
|
+
#helm upgrade --install cnpg cnpg/cloudnative-pg \
|
|
25
|
+
# --namespace cnpg-system --create-namespace \
|
|
26
|
+
# --set webhook.port="10250" $HELM_WHAT
|
|
27
|
+
#
|
|
28
|
+
#removeHelmRepo cnpg
|
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
#
|
|
3
3
|
# See => https://cloudnative-pg.io/documentation/current/installation_upgrade/
|
|
4
4
|
#
|
|
5
|
-
|
|
6
|
-
OPVER="1.26.0-rc1" # 03/28/2025
|
|
5
|
+
OPVER="1.25.1"
|
|
7
6
|
|
|
8
7
|
createNamespaceIfNeeded cnpg-system
|
|
9
8
|
|
|
10
9
|
kubectl apply --server-side -f \
|
|
11
|
-
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/
|
|
10
|
+
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-${OPVER%.*}/releases/cnpg-${OPVER}.yaml $K8S_WHAT
|
|
12
11
|
|
|
13
12
|
# Legacy terraformed clusters (like sandbox) may need a special firewall rule
|
|
14
13
|
# added to the network layer on k8s. It should look like this:
|
|
@@ -4,7 +4,7 @@ showInstalling "The Prometheus Operator and Components"
|
|
|
4
4
|
addHelmRepo prometheus-community https://prometheus-community.github.io/helm-charts
|
|
5
5
|
|
|
6
6
|
showInstalling "The Prometheus Operator (kube-prometheus-stack)"
|
|
7
|
-
[ -z "$PROMETHEUS_STACK_CHART_VERSION" ] && PROMETHEUS_STACK_CHART_VERSION="
|
|
7
|
+
[ -z "$PROMETHEUS_STACK_CHART_VERSION" ] && PROMETHEUS_STACK_CHART_VERSION="70"
|
|
8
8
|
|
|
9
9
|
NS="--namespace prometheus"
|
|
10
10
|
|
|
@@ -7,7 +7,25 @@ fullnameOverride: "prometheus-stack-stackdriver-metrics"
|
|
|
7
7
|
stackdriver:
|
|
8
8
|
projectId: "${PROJECT_NAME}"
|
|
9
9
|
metrics:
|
|
10
|
-
typePrefixes:
|
|
10
|
+
typePrefixes: >
|
|
11
|
+
pubsub.googleapis.com/subscription/oldest_unacked_message_age,
|
|
12
|
+
pubsub.googleapis.com/subscription/num_undelivered_messages,
|
|
13
|
+
pubsub.googleapis.com/subscription/pull_ack_request_count,
|
|
14
|
+
pubsub.googleapis.com/subscription/streaming_pull_ack_request_count,
|
|
15
|
+
pubsub.googleapis.com/topic/message_sizes,
|
|
16
|
+
pubsub.googleapis.com/topic/send_request_count,
|
|
17
|
+
firebasedatabase.googleapis.com/io/database_load,
|
|
18
|
+
firebasedatabase.googleapis.com/network/sent_bytes_count,
|
|
19
|
+
firebasedatabase.googleapis.com/network/active_connections,
|
|
20
|
+
container.googleapis.com/accelerator/duty_cycle,
|
|
21
|
+
container.googleapis.com/accelerator/memory_usage,
|
|
22
|
+
container.googleapis.com/accelerator/request_count,
|
|
23
|
+
agent.googleapis.com/memory/bytes_used,
|
|
24
|
+
agent.googleapis.com/cpu/utilization,
|
|
25
|
+
storage.googleapis.com/network/received_bytes_count,
|
|
26
|
+
storage.googleapis.com/network/sent_bytes_count,
|
|
27
|
+
monitoring.googleapis.com/uptime_check/check_passed,
|
|
28
|
+
quota.googleapis.com/allocation/usage
|
|
11
29
|
|
|
12
30
|
# https://artifacthub.io/packages/helm/prometheus-community/prometheus-stackdriver-exporter?modal=values&path=serviceMonitor
|
|
13
31
|
serviceMonitor:
|
|
@@ -15,6 +33,8 @@ serviceMonitor:
|
|
|
15
33
|
namespace: prometheus
|
|
16
34
|
interval: "30s"
|
|
17
35
|
relabelings:
|
|
36
|
+
# Legacy labelmap used to preserve compatibility with pre-operator Grafana dashboards
|
|
37
|
+
# Can be removed once legacy Prometheus stack is deprecated
|
|
18
38
|
- action: labelmap
|
|
19
39
|
regex: __meta_kubernetes_pod_label_(.+)
|
|
20
40
|
- action: labelmap
|
|
@@ -4,7 +4,7 @@ showInstalling "Redis"
|
|
|
4
4
|
|
|
5
5
|
OCI_CHART="oci://registry-1.docker.io/bitnamicharts/redis"
|
|
6
6
|
|
|
7
|
-
[ -z "$REDIS_CHART_VERSION" ] && REDIS_CHART_VERSION="20.11.
|
|
7
|
+
[ -z "$REDIS_CHART_VERSION" ] && REDIS_CHART_VERSION="20.11.5"
|
|
8
8
|
|
|
9
9
|
helm upgrade --install redis $OCI_CHART \
|
|
10
10
|
--values redis/values.yaml \
|
|
@@ -17,7 +17,7 @@ TRAEFIK_NAMESPACE="traefik"
|
|
|
17
17
|
|
|
18
18
|
addHelmRepo traefik https://helm.traefik.io/traefik
|
|
19
19
|
|
|
20
|
-
[ -z "$TRAEFIK_CHART_VERSION" ] && TRAEFIK_CHART_VERSION="
|
|
20
|
+
[ -z "$TRAEFIK_CHART_VERSION" ] && TRAEFIK_CHART_VERSION="35"
|
|
21
21
|
helm upgrade --install traefik traefik/traefik \
|
|
22
22
|
--namespace $TRAEFIK_NAMESPACE --create-namespace \
|
|
23
23
|
--values traefik/values.yaml \
|
|
@@ -25,7 +25,7 @@ velero schedule delete --all --confirm &> /dev/null
|
|
|
25
25
|
helm upgrade \
|
|
26
26
|
--install velero vmware-tanzu/velero \
|
|
27
27
|
--namespace velero --create-namespace \
|
|
28
|
-
--values velero/
|
|
28
|
+
--values velero/values-local.yaml \
|
|
29
29
|
--version $VELERO_CHART_VERSION $HELM_WHAT \
|
|
30
30
|
--set configuration.backupStorageLocation[0].bucket="$GCE_BUCKET" \
|
|
31
31
|
--set configuration.backupStorageLocation[0].config.serviceAccount="$GCE_SA_EMAIL" \
|
package/src/helmup.sh
CHANGED
|
@@ -702,7 +702,11 @@ EOSNAPSC
|
|
|
702
702
|
sleep 2 # hold up processing for a moment to allow IAM mods to propagate
|
|
703
703
|
}
|
|
704
704
|
|
|
705
|
-
function
|
|
705
|
+
# this function is a little convoluted in the way it attempts to install the
|
|
706
|
+
# scripts and yaml necessary for the CNPG cleanup activities - the idea is to
|
|
707
|
+
# be able to update the scripts without touching anything else since the target
|
|
708
|
+
# directory is the cnpg-operator setup
|
|
709
|
+
function installCnpgCleanupCronjob() {
|
|
706
710
|
CNPG_OPERATOR_DIR="cnpg-operator"
|
|
707
711
|
if [ ! -d "$CNPG_OPERATOR_DIR" ];
|
|
708
712
|
then
|
|
@@ -713,23 +717,52 @@ CNPG_OPERATOR_MISSING
|
|
|
713
717
|
exit 1
|
|
714
718
|
fi
|
|
715
719
|
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
720
|
+
# create the destination bucket for k8s cronjob scripts
|
|
721
|
+
BUCKET_SUFFIX="k8s-cronjobs"
|
|
722
|
+
makeUniformBucketWithCors $BUCKET_SUFFIX
|
|
723
|
+
|
|
724
|
+
CNPG_CLEANUP_CRONJOB_OVH="cnpg-cleanup-cronjob.yaml.ovh"
|
|
725
|
+
CNPG_CLEANUP_CRONJOB="$CNPG_OPERATOR_DIR/$CNPG_CLEANUP_CRONJOB_OVH"
|
|
726
|
+
CNPG_CLEANUP_SCRIPT_BASH="cnpg-cleanup-script.sh"
|
|
727
|
+
CNPG_CLEANUP_SCRIPT="$CNPG_OPERATOR_DIR/$CNPG_CLEANUP_SCRIPT_BASH"
|
|
728
|
+
if [ ! -f "$CNPG_CLEANUP_SCRIPT" ];
|
|
719
729
|
then
|
|
720
730
|
cat<<CNPG_FETCH_CLEANUP_CRONJOB_YAML
|
|
721
|
-
`color y "Missing $
|
|
731
|
+
`color y "Missing $CNPG_CLEANUP_SCRIPT_BASH - updating local cron setup scripts"`
|
|
722
732
|
|
|
723
733
|
CNPG_FETCH_CLEANUP_CRONJOB_YAML
|
|
724
734
|
CNPG_OP_ROOT="$(build-tools --reporoot)/src/helm-charts/cnpg-operator"
|
|
725
|
-
cp $CNPG_OP_ROOT/$
|
|
735
|
+
cp $CNPG_OP_ROOT/$CNPG_CLEANUP_CRONJOB_OVH $CNPG_CLEANUP_CRONJOB
|
|
736
|
+
cp $CNPG_OP_ROOT/$CNPG_CLEANUP_SCRIPT_BASH $CNPG_CLEANUP_SCRIPT
|
|
726
737
|
fi
|
|
727
738
|
|
|
739
|
+
# copy / update the script in the bucket referenced in the cronjob
|
|
740
|
+
printf "\ngsutil cp $CNPG_CLEANUP_SCRIPT gs://$GCP_PROJECT_ID-$BUCKET_SUFFIX\n"
|
|
741
|
+
gsutil cp $CNPG_CLEANUP_SCRIPT gs://$GCP_PROJECT_ID-$BUCKET_SUFFIX
|
|
742
|
+
|
|
743
|
+
local CNPG_NAMESPACE="cnpg-operands"
|
|
744
|
+
local CNPG_CLEANUP_SA="cnpg-cleanup-sa"
|
|
745
|
+
|
|
746
|
+
gcloud iam service-accounts create "$CNPG_CLEANUP_SA" \
|
|
747
|
+
--project="$GCP_PROJECT_ID" \
|
|
748
|
+
--description="Used for cronjob cleanup with Workload Identity" \
|
|
749
|
+
--display-name="CNPG Cleanup SA" &> $DEVNULL
|
|
750
|
+
warnOnError $? "the GCP SA $CNPG_CLEANUP_SA may already exist"
|
|
751
|
+
|
|
752
|
+
gcloud projects add-iam-policy-binding "$GCP_PROJECT_ID" \
|
|
753
|
+
--project="$GCP_PROJECT_ID" \
|
|
754
|
+
--member="serviceAccount:$CNPG_CLEANUP_SA@$GCP_PROJECT_ID.iam.gserviceaccount.com" \
|
|
755
|
+
--role="roles/storage.objectViewer"
|
|
756
|
+
|
|
757
|
+
bindWorkloadIdentity "$CNPG_CLEANUP_SA" "$CNPG_NAMESPACE"
|
|
758
|
+
|
|
759
|
+
CNPG_CLEANUP_CRONJOB_YAML="$CNPG_OPERATOR_DIR/$(basename $CNPG_CLEANUP_CRONJOB_OVH .ovh)"
|
|
760
|
+
overwhelm -g # allow ovh -> yaml to occur
|
|
728
761
|
cat<<CNPG_CLEANUP_CRONJOB_APPLY
|
|
729
|
-
`color g "Creating cronjob =>"``color y " kubectl apply -f $
|
|
762
|
+
`color g "Creating cronjob =>"``color y " kubectl apply -f $CNPG_CLEANUP_CRONJOB_YAML"`
|
|
730
763
|
|
|
731
764
|
CNPG_CLEANUP_CRONJOB_APPLY
|
|
732
|
-
kubectl apply -f $
|
|
765
|
+
kubectl apply -f $CNPG_CLEANUP_CRONJOB_YAML
|
|
733
766
|
}
|
|
734
767
|
|
|
735
768
|
function ensureCnpgSecret() {
|
|
@@ -1001,7 +1034,7 @@ CATBACKUP
|
|
|
1001
1034
|
;;
|
|
1002
1035
|
|
|
1003
1036
|
"cnpg-cronjob")
|
|
1004
|
-
|
|
1037
|
+
installCnpgCleanupCronjob
|
|
1005
1038
|
;;
|
|
1006
1039
|
|
|
1007
1040
|
"cronZombieKiller"|"zombie-killer")
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
steps:
|
|
2
|
+
- name: 'gcr.io/cloud-builders/docker'
|
|
3
|
+
entrypoint: 'bash'
|
|
4
|
+
args: ['-c', 'docker pull {{imageName}}:latest || exit 0']
|
|
5
|
+
|
|
2
6
|
- name: 'gcr.io/cloud-builders/docker'
|
|
3
7
|
args:
|
|
4
8
|
- 'build'
|
|
@@ -6,8 +10,8 @@ steps:
|
|
|
6
10
|
- '-t'
|
|
7
11
|
- '{{imageName}}:{{imageVersion}}'
|
|
8
12
|
- '--cache-from'
|
|
9
|
-
- '{{imageName}}:
|
|
13
|
+
- '{{imageName}}:latest'
|
|
10
14
|
- '.'
|
|
11
15
|
|
|
12
16
|
images:
|
|
13
|
-
- '{{imageName}}:{{imageVersion}}'
|
|
17
|
+
- '{{imageName}}:{{imageVersion}}'
|
|
@@ -9,13 +9,11 @@ RUN useradd -ms /bin/bash {{runuser}} && \
|
|
|
9
9
|
mkdir -p /usr/src/app && \
|
|
10
10
|
chown {{runuser}} /usr/src/app && \
|
|
11
11
|
apt-get update && apt-get install -y \
|
|
12
|
-
wget git \
|
|
12
|
+
wget git libgl1 libglib2.0-0 \
|
|
13
13
|
&& rm -rf /var/lib/apt/lists/*
|
|
14
14
|
|
|
15
15
|
# gcc \
|
|
16
16
|
# git \
|
|
17
|
-
# libgl1 \
|
|
18
|
-
# libglib2.0-0 \
|
|
19
17
|
# && rm -rf /var/lib/apt/lists/*
|
|
20
18
|
|
|
21
19
|
# Copy uv binary from the official distroless Docker image.
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
apiVersion: v1
|
|
2
|
-
kind: ServiceAccount
|
|
3
|
-
metadata:
|
|
4
|
-
name: cnpg-cleanup-sa
|
|
5
|
-
namespace: cnpg-operands
|
|
6
|
-
---
|
|
7
|
-
apiVersion: rbac.authorization.k8s.io/v1
|
|
8
|
-
kind: Role
|
|
9
|
-
metadata:
|
|
10
|
-
name: cnpg-cleanup-role
|
|
11
|
-
namespace: cnpg-operands
|
|
12
|
-
rules:
|
|
13
|
-
- apiGroups: ["postgresql.cnpg.io"]
|
|
14
|
-
resources: ["backups"]
|
|
15
|
-
verbs: ["get", "list", "delete"]
|
|
16
|
-
---
|
|
17
|
-
apiVersion: rbac.authorization.k8s.io/v1
|
|
18
|
-
kind: RoleBinding
|
|
19
|
-
metadata:
|
|
20
|
-
name: cnpg-cleanup-rolebinding
|
|
21
|
-
namespace: cnpg-operands
|
|
22
|
-
subjects:
|
|
23
|
-
- kind: ServiceAccount
|
|
24
|
-
name: cnpg-cleanup-sa
|
|
25
|
-
namespace: cnpg-operands
|
|
26
|
-
roleRef:
|
|
27
|
-
kind: Role
|
|
28
|
-
name: cnpg-cleanup-role
|
|
29
|
-
apiGroup: rbac.authorization.k8s.io
|
|
30
|
-
---
|
|
31
|
-
apiVersion: batch/v1
|
|
32
|
-
kind: CronJob
|
|
33
|
-
metadata:
|
|
34
|
-
name: cnpg-cleanup-snapshot
|
|
35
|
-
namespace: cnpg-operands
|
|
36
|
-
spec:
|
|
37
|
-
schedule: "0 13 * * *" # 13 UTC is ~8AM EST
|
|
38
|
-
successfulJobsHistoryLimit: 2
|
|
39
|
-
jobTemplate:
|
|
40
|
-
spec:
|
|
41
|
-
template:
|
|
42
|
-
spec:
|
|
43
|
-
serviceAccountName: cnpg-cleanup-sa
|
|
44
|
-
restartPolicy: Never
|
|
45
|
-
containers:
|
|
46
|
-
- name: cnpg-cleanup-snapshots
|
|
47
|
-
image: bitnami/kubectl:latest
|
|
48
|
-
command:
|
|
49
|
-
- "/bin/bash"
|
|
50
|
-
- "-c"
|
|
51
|
-
- |
|
|
52
|
-
echo "Starting CNPG Cleanup"
|
|
53
|
-
CURRENT_DATE=$(date +%s)
|
|
54
|
-
RETENTION_DAYS=21
|
|
55
|
-
SNAPSHOTS_JSON=$(kubectl get backups.postgresql.cnpg.io -n cnpg-operands -o json)
|
|
56
|
-
echo "$SNAPSHOTS_JSON" | jq -c '.items[]' | while read -r snapshot
|
|
57
|
-
do
|
|
58
|
-
NAME=$(echo "$snapshot" | jq -r '.metadata.name')
|
|
59
|
-
CREATION_TIMESTAMP=$(echo "$snapshot" | jq -r '.metadata.creationTimestamp')
|
|
60
|
-
if [[ "$NAME" != *"-backup-"* ]];
|
|
61
|
-
then
|
|
62
|
-
echo "Skipping manual snapshot: $NAME"
|
|
63
|
-
continue
|
|
64
|
-
fi
|
|
65
|
-
SNAPSHOT_DATE=$(date -d "$CREATION_TIMESTAMP" +%s)
|
|
66
|
-
AGE=$(( (CURRENT_DATE - SNAPSHOT_DATE) / 86400 ))
|
|
67
|
-
if [ "$AGE" -gt "$RETENTION_DAYS" ];
|
|
68
|
-
then
|
|
69
|
-
echo "Deleting snapshot: $NAME (Age: $AGE days)"
|
|
70
|
-
kubectl delete backups.postgresql.cnpg.io "$NAME" -n cnpg-operands 2> /dev/null
|
|
71
|
-
fi
|
|
72
|
-
done
|
|
73
|
-
echo "Cleanup complete."
|