🌳
pt0/deployF/harvF/setupLonghornBackupF.mts
9import { CustomObjectsApi } from '@kubernetes/client-node'
11export const k8sLonghornBackup = async ({bucket_name}: {bucket_name: string}) => {
12 const {cluster_name} = getKlusterCtx()
13 const action = getAction()
14 const {endpoint, secretAccessKey, accessKeyId} = getS3Cfg({bucket_name})
16 const backupTargetSecName = `longhorn-backuptarget-${bucket_name}`
17 const longhornUrl = `s3://${bucket_name}@dummyregion/`
20 action, cluster_name,
21 resources: [
23 namespace: 'longhorn-system',
24 name: backupTargetSecName,
25 secretsH: {
26 AWS_ACCESS_KEY_ID: accessKeyId,
27 AWS_SECRET_ACCESS_KEY: secretAccessKey,
28 AWS_ENDPOINTS: endpoint,
29 }
30 }),
31 {
32 apiVersion: 'longhorn.io/v1beta2',
33 kind: 'BackupTarget',
34 metadata: {
35 name: 'default',
36 namespace: 'longhorn-system',
37 },
38 spec: {
39 backupTargetURL: longhornUrl,
40 credentialSecret: backupTargetSecName,
41 pollInterval: '5m0s',
42 },
43 },
44 ]
45 })
48type LonghornSetting = {
49 name: string
50 value: string
53const longhornSettingTmpl = ({name, value}: LonghornSetting) => ({
54 apiVersion: 'longhorn.io/v1beta2',
55 kind: 'Setting',
56 metadata: {
57 name,
58 namespace: 'longhorn-system',
59 },
60 value,
61})
63export const k8sLonghornSettings = async (settings: LonghornSetting[]) => {
64 const {cluster_name} = getKlusterCtx()
65 const action = getAction()
67 action, cluster_name,
68 resources: settings.map(longhornSettingTmpl),
69 })
72type VolumeReplicaSpec = {
73 pvcName: string
74 desiredReplicaCnt: number
77export const k8sLonghornVolumeReconcile = async (specs: VolumeReplicaSpec[]) => {
78 const {cluster_name} = getKlusterCtx()
79 const {kubeConfig} = await getKubeApis({cluster_name})
80 const custObjApi = kubeConfig.makeApiClient(CustomObjectsApi)
82 for (const {pvcName, desiredReplicaCnt} of specs) {
83 const pvc = await read2Resource({
84 resource: {apiVersion: 'v1', kind: 'PersistentVolumeClaim', metadata: {name: pvcName}},
85 cluster_name,
86 }) as any
87 if (!pvc?.spec?.volumeName) {
88 betLog({pvcName, skipped: 'PVC not found or not bound'})
89 continue
90 }
92 const pvName = pvc.spec.volumeName
93 const lhVol = await read2Resource({
94 resource: {apiVersion: 'longhorn.io/v1beta2', kind: 'Volume', metadata: {name: pvName, namespace: 'longhorn-system'}},
95 cluster_name,
96 }) as any
97 if (!lhVol) {
98 betLog({pvcName, skipped: 'no Longhorn volume'})
99 continue
100 }
102 const currentReplicas = lhVol.spec?.numberOfReplicas
103 const robustness = lhVol.status?.robustness
104 if (currentReplicas === desiredReplicaCnt && robustness === 'healthy') {
105 betLog({pvcName, pvName, replicas: currentReplicas, robustness, skipped: 'already correct'})
106 continue
107 }
109 if (robustness !== 'healthy' && currentReplicas <= desiredReplicaCnt) {
110 betLog({pvcName, pvName, replicas: currentReplicas, robustness, skipped: 'volume not healthy, not bumping replicas'})
111 continue
112 }
114 if (robustness !== 'healthy' && currentReplicas > desiredReplicaCnt) {
115 betLog({pvcName, pvName, replicas: `${currentReplicas} -> ${desiredReplicaCnt}`, robustness, action: 'self-heal down (sick + over-provisioned)'})
116 await custObjApi.patchNamespacedCustomObject({
117 group: 'longhorn.io', version: 'v1beta2',
118 namespace: 'longhorn-system', plural: 'volumes', name: pvName,
119 body: [{op: 'replace', path: '/spec/numberOfReplicas', value: desiredReplicaCnt}],
120 })
121 continue
122 }
124 betLog({pvcName, pvName, replicas: `${currentReplicas} -> ${desiredReplicaCnt}`, robustness})
125 await custObjApi.patchNamespacedCustomObject({
126 group: 'longhorn.io', version: 'v1beta2',
127 namespace: 'longhorn-system', plural: 'volumes', name: pvName,
128 body: [{op: 'replace', path: '/spec/numberOfReplicas', value: desiredReplicaCnt}],
129 })
130 }