コンテンツにスキップ

データベースユーザーを同期させる

データベースユーザー では tenax ユーザーを一度だけ作成します。ほとんどの構成ではそれで十分です。 このページは、誰も何も実行しなくてもユーザーが Secret と一致し続ける必要がある GitOps 構成向けです。

  • Secret のパスワードが ローテーション される。
  • データベースが 再構築 されたり古いダンプから復元されたりして、ユーザーが消える。
  • 誰かが ユーザーを手作業で変更 する (権限を剥奪する、パスワードを変える)。
  • データベースが、そのユーザーを記述するマニフェストより 後に 作成される (新しいクラスター)。

一度きりの Job ではどれにも対応できません。Job は一度だけ実行され、Secret が変わっても Kubernetes は完了した Job を 再実行しないからです。同じ冪等な SQL を再適用する CronJob なら対応できます。1 周期のうちに、ユーザーは再び Secret と一致します。

仕組み

データベースごとに CronJob をひとつ、データベースの Namespace に置きます。各実行では次を行います。

  1. データベースが応答するまで待つ (Job の期限内で)。
  2. データベース自身が使う Secret から、管理者としてログインする。
  3. tenax ユーザーがなければ作成し、Tenax の Secret に基づいてパスワードと権限を設定する。

どの文も冪等なので、すべてが揃っている状態の実行では何も変わりません。パスワードは Secret から環境変数としてコンテナーに 渡され、コマンドラインやログに出ることはありません。

PostgreSQL

# ← の付いた 4 つの値を置き換えます: Namespace、データベースの 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 に入るため、16 進数で生成してください (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 自身も拒否されたログインを再試行してターゲットに報告するので、ローテーションは遅くとも 1 周期後には 反映されます。pg_hba.conf のルールと binlog の設定は引き続きサーバーの設定です (データベースユーザー の手順 3)。 この CronJob はそれらには触れません。