文章

如何避免 EKS 上 的 cronjob 執行失敗導致 Worker Node 自動擴展失敗

如何避免 EKS 上 的 cronjob 執行失敗導致 Worker Node 自動擴展失敗. Practical notes and implementation steps.

最近在維運 EKS 時

發生一件事情

因為大量的 cronjob 執行失敗

導致 Worker Node 無法自動擴展而無法建立新的佈署

這個問題有多個層面的可能性

  1. 解決 Worker Node 自動擴展策略
  2. Cronjob 執行失敗導致大量 Pod 重啟影響到 API Server 和 kube-scheduler 的效能

以下是撰寫有問題的 cronjob

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
apiVersion: batch/v1
kind: CronJob
metadata:
  name: database-backup
  namespace: operating
spec:
  schedule: "*/10 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: database-backup-sa
          containers:
            - name: database-backup
              image: postgres:17-alpine
              imagePullPolicy: IfNotPresent
              command:
              - /bin/sh
              - -c
              - |
                export HOST=$(cat /mnt/secrets-store/db-host)
                export PORT=$(cat /mnt/secrets-store/db-port)
                export USERNAME=$(cat /mnt/secrets-store/db-user)
                export PASSWORD=$(cat /mnt/secrets-store/db-password)
                DATABASE="dummy-biz"
                apk add --no-cache aws-cli
                BACKUP_FILE="/tmp/database-backup_$(date +%Y%m%d_%H%M%S).sql"
                PGPASSWORD=$PASSWORD pg_dump -h $HOST -p $PORT -U $USERNAME -d $DATABASE -f $BACKUP_FILE
                aws s3 cp $BACKUP_FILE s3://database-backup/staging/
              resources:
                limits:
                  cpu: "100m"
                  memory: "512Mi"
                requests:
                  cpu: "50m"
                  memory: "64Mi"
              volumeMounts:
              - name: secrets-store
                mountPath: /mnt/secrets-store
                readOnly: true
          volumes:
          - name: secrets-store
            csi:
              driver: secrets-store.csi.k8s.io
              readOnly: true
              volumeAttributes:
                secretProviderClass: database-secret
          restartPolicy: OnFailure

先不論 10 秒鐘備份一次資料庫

程式是否可以執行的完

不過 10 秒鐘累積一個錯誤的 Pod

預計不到一小時 EKS Cluster 就有可能被一個錯誤的 Pod 打掛

解決 Worker Node 自動擴展策略也許是個方法

不過無限制的增長 Worker Node

也會讓一個 Cronjob 的錯誤設定

就讓整個維運支出大幅增加

以上的錯誤設定應該增加兩項設定彌補

  1. 新增 concurrencyPolicy 設定 錯誤的 Pod 持續累積 又允許並行的話 將持續累積錯誤的 Pod
    1
    2
    
    spec:
      concurrencyPolicy: Forbid
    
  2. 強化重試政策的設定 如果是程式上的問題 一旦出錯就只會繼續錯下去 直到程式修正為止 於此應該加個重試次數和超時設定
    1
    2
    3
    
    spec:
      backoffLimit: 2 # 失敗兩次就放棄,別在那邊一直試
      activeDeadlineSeconds: 600 # 不管成功失敗,超過十分鐘就把它殺掉
    
  3. 增加成功和錯誤歷史紀錄次數設定 這設定不是強制 不過最終 Log 會轉發到 Log Server 因此 Server 上只需要保留較新的幾筆紀錄即可
    1
    2
    3
    
    spec:
      failedJobsHistoryLimit: 1
      successfulJobsHistoryLimit: 3
    

全部加上去後大致如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
apiVersion: batch/v1
kind: CronJob
metadata:
  name: database-backup
  namespace: operating
spec:
  schedule: "*/10 * * * *"
  concurrencyPolicy: Forbid
  failedJobsHistoryLimit: 1
  successfulJobsHistoryLimit: 3
  jobTemplate:
    spec:
      activeDeadlineSeconds: 600 # 不管成功失敗,超過十分鐘就把它殺掉
      backoffLimit: 2 # 失敗兩次就放棄,別在那邊一直試
      template:
        spec:
          serviceAccountName: database-backup-sa
          containers:
            - name: database-backup
              image: postgres:17-alpine
              imagePullPolicy: IfNotPresent
              command:
              - /bin/sh
              - -c
              - |
                export HOST=$(cat /mnt/secrets-store/db-host)
                export PORT=$(cat /mnt/secrets-store/db-port)
                export USERNAME=$(cat /mnt/secrets-store/db-user)
                export PASSWORD=$(cat /mnt/secrets-store/db-password)
                DATABASE="dummy-biz"
                apk add --no-cache aws-cli
                BACKUP_FILE="/tmp/database-backup_$(date +%Y%m%d_%H%M%S).sql"
                PGPASSWORD=$PASSWORD pg_dump -h $HOST -p $PORT -U $USERNAME -d $DATABASE -f $BACKUP_FILE
                aws s3 cp $BACKUP_FILE s3://database-backup/staging/
              resources:
                limits:
                  cpu: "100m"
                  memory: "512Mi"
                requests:
                  cpu: "50m"
                  memory: "64Mi"
              volumeMounts:
              - name: secrets-store
                mountPath: /mnt/secrets-store
                readOnly: true
          volumes:
          - name: secrets-store
            csi:
              driver: secrets-store.csi.k8s.io
              readOnly: true
              volumeAttributes:
                secretProviderClass: database-secret
          restartPolicy: OnFailure

Container 執行中加入 apk add --no-cache aws-cli 不是個好想法, 至少會希望放到 initContainer 區塊, 在準備階段的時候就將需要的 Package 裝好, 當然最好的方式還是自己打包製作一份 Container 存起來

這份範例和改善方向不是最好的做法

最源頭還是希望修正有錯誤的程式

至於改善方向也非唯一

不過這不在這次的討論範疇

也羅列以下方式,未來會再寫一篇描述

  • 語法與 Schema 驗證
    1
    
    kubeconform -summary -strict my-cronjob.yaml
    
  • 使用 SAST 工具檢查,如:kube-score 或 Kube-Linter
  • 模擬執行 Dry Run
    1
    
    kubectl apply -f my-cronjob.yaml --dry-run=server
    
本文章以 CC BY 4.0 授權