Mantener sincronizado el usuario de base de datos¶
Usuarios de base de datos crea el usuario tenax una sola vez. Eso basta en la mayoría de los
casos. Esta página es para entornos GitOps en los que el usuario debe seguir coincidiendo con su Secret sin que nadie
ejecute nada:
- la contraseña del Secret se rota;
- la base de datos se reconstruye o se restaura desde un volcado antiguo, y el usuario desaparece;
- alguien cambia el usuario a mano (quita un permiso, cambia la contraseña);
- la base de datos se crea después de los manifiestos que describen su usuario (un clúster nuevo).
Un Job de un solo uso no resuelve ninguno de estos casos: se ejecuta una vez, y Kubernetes no vuelve a ejecutar un Job terminado cuando cambia el Secret. Un CronJob que vuelve a aplicar el mismo SQL idempotente sí: en un periodo como máximo, el usuario vuelve a coincidir con el Secret.
Cómo funciona¶
Un CronJob por base de datos, en el namespace de la base de datos. En cada ejecución:
- espera a que la base de datos responda (con el límite del deadline del Job);
- inicia sesión como administrador, con el Secret que usa la propia base de datos;
- crea el usuario
tenaxsi falta y le asigna la contraseña y los permisos a partir del Secret de Tenax.
Cada sentencia es idempotente, así que una ejecución que lo encuentra todo en su sitio no cambia nada. Las contraseñas llegan al contenedor como variables de entorno desde Secrets; nunca aparecen en una línea de comandos ni en un log.
PostgreSQL¶
Sustituye los cuatro valores marcados con # ←: el namespace, el Service de la base de datos, la base de datos, y el
Secret y la clave que contienen la contraseña de administrador.
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 requiere psql 15 o posterior en la imagen; el servidor puede ser cualquier versión soportada. Define
max_slot_wal_keep_size en la configuración del servidor (por ejemplo -c max_slot_wal_keep_size=2GB), no aquí: es
un ajuste del servidor, no forma parte del usuario.
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"
La contraseña entra en el SQL sin más escapado que el del literal de cadena, así que genérala en hexadecimal
(openssl rand -hex 24).
Ejecutarlo ahora¶
kubectl -n team-a create job --from=cronjob/tenax-user-postgres tenax-user-now
kubectl -n team-a logs -f job/tenax-user-now
Cada 30 minutos es más que suficiente: el propio Tenax reintenta un inicio de sesión rechazado y lo informa en el
objetivo, así que una rotación se recoge como mucho un periodo después. La regla de pg_hba.conf y los ajustes del
binlog siguen siendo configuración del servidor (Usuarios de base de datos, paso 3); este CronJob
no los toca.