콘텐츠로 이동

데이터베이스 사용자 동기화 유지

데이터베이스 사용자에서는 tenax 사용자를 한 번 만듭니다. 대부분의 환경에서는 그것으로 충분합니다. 이 페이지는 누군가 직접 실행하지 않아도 사용자가 계속 Secret과 일치해야 하는 GitOps 환경을 위한 것입니다.

  • 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에 들어가므로 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 자체가 거부된 로그인을 재시도하고 대상에 보고하므로, 비밀번호 교체는 늦어도 한 주기 뒤에 반영됩니다. pg_hba.conf 규칙과 binlog 설정은 계속 서버 설정으로 남습니다 (데이터베이스 사용자, 3단계). 이 CronJob은 이를 건드리지 않습니다.