跳轉到

保持資料庫使用者同步

資料庫使用者一次性建立 tenax 使用者,這對大多數環境已經足夠。本頁面向這樣的 GitOps 環境: 使用者必須始終與其 Secret 保持一致,而且無需任何人手動執行任何操作:

  • Secret 中的密碼會被輪替;
  • 資料庫被重建,或從較舊的傾印還原,導致使用者遺失;
  • 有人手動修改了使用者(撤銷某項授權、修改密碼);
  • 資料庫在描述其使用者的清單之後才建立(全新叢集)。

一次性 Job 處理不了上述任何情況:它只執行一次,而 Secret 變化時 Kubernetes 不會重新執行已完成的 Job。 定期重新執行同一段冪等 SQL 的 CronJob 則可以:在一個週期之內,使用者就會重新與 Secret 保持一致。

工作原理

每個資料庫一個 CronJob,位於資料庫所在的名稱空間。每次執行:

  1. 等待資料庫可以回應(以 Job 的截止時間為上限);
  2. 使用資料庫自身所用 Secret 中的憑證,以管理員身分登入;
  3. 如果 tenax 使用者不存在則建立它,並根據 Tenax Secret 設定其密碼和授權。

每條語句都是冪等的,因此當一切都已就緒時,執行不會做任何更改。密碼以來自 Secret 的環境變數形式傳入容器; 它們從不出現在命令列或日誌中。

PostgreSQL

替換標有 # ← 的四個值:名稱空間、資料庫的 Service、資料庫,以及儲存管理員密碼的 Secret 及其鍵。

apiVersion: batch/v1
kind: CronJob
metadata:
  name: tenax-user-postgres
  namespace: team-a                                   # ←
spec:
  schedule: "*/30 * * * *"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 1
  failedJobsHistoryLimit: 3
  jobTemplate:
    spec:
      backoffLimit: 2
      activeDeadlineSeconds: 300
      template:
        spec:
          restartPolicy: OnFailure
          automountServiceAccountToken: false
          securityContext: { runAsNonRoot: true, runAsUser: 999, runAsGroup: 999 }
          containers:
            - name: psql
              image: docker.io/library/postgres:16
              resources: { requests: { cpu: 10m, memory: 32Mi }, limits: { memory: 128Mi } }
              securityContext: { allowPrivilegeEscalation: false, capabilities: { drop: [ALL] } }
              env:
                - { name: PGHOST, value: postgres }                     # ← the database's Service
                - { name: PGDATABASE, value: app }                      # ← any database on the server
                - { name: PGUSER, value: postgres }
                - { name: PGCONNECT_TIMEOUT, value: "10" }
                - name: PGPASSWORD
                  valueFrom: { secretKeyRef: { name: postgres-admin, key: password } }   # ←
                - name: TENAX_PASSWORD
                  valueFrom: { secretKeyRef: { name: tenax-postgres, key: password } }
              command: [sh, -ec]
              args:
                - |
                  until pg_isready -q; do echo "waiting for $PGHOST"; sleep 5; done
                  psql -v ON_ERROR_STOP=1 -X <<'SQL'
                  \getenv tenax_password TENAX_PASSWORD
                  SELECT 'CREATE ROLE tenax LOGIN REPLICATION' WHERE NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'tenax') \gexec
                  ALTER ROLE tenax WITH LOGIN REPLICATION NOSUPERUSER NOCREATEDB NOCREATEROLE PASSWORD :'tenax_password';
                  GRANT pg_read_all_data, pg_monitor TO tenax;
                  ALTER ROLE tenax SET idle_in_transaction_session_timeout = '10min';
                  SQL
                  echo "tenax user in sync on $PGHOST"

\getenv 要求映像中的 psql 為 15 或更高版本;伺服器可以是任意受支援的版本。max_slot_wal_keep_size 請在伺服器設定中設定(例如 -c max_slot_wal_keep_size=2GB),而不是在這裡:它是伺服器設定,不屬於使用者。

MySQL

apiVersion: batch/v1
kind: CronJob
metadata:
  name: tenax-user-mysql
  namespace: team-a                                   # ←
spec:
  schedule: "*/30 * * * *"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 1
  failedJobsHistoryLimit: 3
  jobTemplate:
    spec:
      backoffLimit: 2
      activeDeadlineSeconds: 300
      template:
        spec:
          restartPolicy: OnFailure
          automountServiceAccountToken: false
          securityContext: { runAsNonRoot: true, runAsUser: 999, runAsGroup: 999 }
          containers:
            - name: mysql
              image: docker.io/library/mysql:8.4
              resources: { requests: { cpu: 10m, memory: 64Mi }, limits: { memory: 256Mi } }
              securityContext: { allowPrivilegeEscalation: false, capabilities: { drop: [ALL] } }
              env:
                - { name: MYSQL_HOST, value: mysql }                    # ← the database's Service
                - name: ROOT_PASSWORD
                  valueFrom: { secretKeyRef: { name: mysql-admin, key: root-password } }  # ←
                - name: TENAX_PASSWORD
                  valueFrom: { secretKeyRef: { name: tenax-mysql, key: password } }
              command: [bash, -ec]
              args:
                - |
                  umask 077
                  printf '[client]\nuser=root\npassword=%s\nhost=%s\nconnect-timeout=10\n' "$ROOT_PASSWORD" "$MYSQL_HOST" > /tmp/root.cnf
                  until mysqladmin --defaults-extra-file=/tmp/root.cnf ping --silent; do echo "waiting for $MYSQL_HOST"; sleep 5; done
                  mysql --defaults-extra-file=/tmp/root.cnf <<SQL
                  CREATE USER IF NOT EXISTS 'tenax'@'%' IDENTIFIED BY '${TENAX_PASSWORD}';
                  ALTER USER 'tenax'@'%' IDENTIFIED BY '${TENAX_PASSWORD}';
                  GRANT SELECT, SHOW VIEW, TRIGGER, EVENT, LOCK TABLES, PROCESS, RELOAD, SHOW_ROUTINE,
                        REPLICATION CLIENT, REPLICATION SLAVE ON *.* TO 'tenax'@'%';
                  SQL
                  rm -f /tmp/root.cnf
                  echo "tenax user in sync on $MYSQL_HOST"

密碼直接嵌入 SQL 的字串字面量中,除此之外不做任何轉義,因此請用十六進位產生密碼(openssl rand -hex 24)。

立即執行

kubectl -n team-a create job --from=cronjob/tenax-user-postgres tenax-user-now
kubectl -n team-a logs -f job/tenax-user-now

每 30 分鐘一次已經足夠:Tenax 自身會重試被拒絕的登入並在目標上報告,因此密碼輪替最多在一個週期後就會生效。 pg_hba.conf 規則和 binlog 設定仍屬於伺服器設定(資料庫使用者第 3 步);此 CronJob 不會改動它們。