Создание администратора платформы перенесено в под backend
Учётка создавалась задачей ansible через kubectl exec — по явной команде оператора и в обход GitOps. Теперь это делает сам под при каждом старте: стартовый скрипт подхватывает креды из Vault и вызывает штатную createsuperuser --noinput. Идемпотентность обеспечена ветвлением, а не подавлением ошибки: на уже существующем логине Django возвращает "That username is already taken" с ненулевым кодом, что под set -e из command базы уронило бы под на втором запуске. Ошибка ожидаема и гасится веткой else. Заодно в стартовый скрипт добавлен migrate. В entrypoint.sh образа он уже есть, но запущен без set -e — его падение не мешает uwsgi стартовать. Именно так контур несколько часов отдавал 200 на /admin/ с пустой схемой: backend не мог аутентифицироваться в СУБД, миграции падали, приложение работало. Здесь команда идёт под set -e, поэтому отказ виден сразу. Ansible остаётся владельцем значений: генерирует логин и пароль (теперь безусловно, а не по флагу — их ждёт vault-agent) и кладёт в secrets/apps/django/superuser. Задача poe superuser печатает креды. Убраны временные диагностические задачи с ignore_errors, добавленные при разборе отказа createsuperuser.
This commit is contained in:
parent
ee1881e516
commit
bddcc955fe
@ -44,7 +44,8 @@ platform = "ansible-playbook deploy.yml -e aero_platform_up=true"
|
|||||||
gen-env = "ansible-playbook deploy.yml --tags env"
|
gen-env = "ansible-playbook deploy.yml --tags env"
|
||||||
# docker login в приватный реестр по ключу json_key
|
# docker login в приватный реестр по ключу json_key
|
||||||
login = "ansible-playbook deploy.yml --tags login"
|
login = "ansible-playbook deploy.yml --tags login"
|
||||||
# Сгенерировать логин+пароль и создать Django-суперпользователя в backend
|
# Показать логин+пароль администратора платформы. Саму учётку создаёт под
|
||||||
|
# backend при старте (apps/django/aero), эта задача только печатает креды.
|
||||||
superuser = "ansible-playbook deploy.yml -e sarex_create_superuser=true --tags superuser"
|
superuser = "ansible-playbook deploy.yml -e sarex_create_superuser=true --tags superuser"
|
||||||
lint = "ansible-lint"
|
lint = "ansible-lint"
|
||||||
|
|
||||||
|
|||||||
@ -263,6 +263,15 @@ vault_app_secrets:
|
|||||||
secret_key: "{{ sarex_secrets.SAREX_MINIO_ROOT_PASSWORD }}"
|
secret_key: "{{ sarex_secrets.SAREX_MINIO_ROOT_PASSWORD }}"
|
||||||
buckets:
|
buckets:
|
||||||
- name: "{{ sarex_django_s3_bucket }}"
|
- name: "{{ sarex_django_s3_bucket }}"
|
||||||
|
# Учётка администратора платформы. Её разбирает не приложение, а стартовый
|
||||||
|
# скрипт пода backend: он экспортирует ключи как DJANGO_SUPERUSER_* — ровно
|
||||||
|
# те имена, которые ждёт штатная manage.py createsuperuser --noinput.
|
||||||
|
# См. apps/django/aero/kustomization.yaml.
|
||||||
|
- path: secrets/apps/django/superuser
|
||||||
|
data:
|
||||||
|
username: "{{ superuser_name }}"
|
||||||
|
password: "{{ superuser_password }}"
|
||||||
|
email: "{{ superuser_name }}@{{ platform_domain }}"
|
||||||
# JWT-ключи RS512. Те же самые, что роль генерирует для compose-стека, —
|
# JWT-ключи RS512. Те же самые, что роль генерирует для compose-стека, —
|
||||||
# контур переезжает, и токены должны остаться совместимыми.
|
# контур переезжает, и токены должны остаться совместимыми.
|
||||||
- path: secrets/vault/common/rsa_keys
|
- path: secrets/vault/common/rsa_keys
|
||||||
|
|||||||
@ -92,6 +92,28 @@
|
|||||||
no_log: true
|
no_log: true
|
||||||
tags: [env]
|
tags: [env]
|
||||||
|
|
||||||
|
# Учётка администратора платформы. Живёт отдельно от общего цикла выше по двум
|
||||||
|
# причинам: у логина свой алфавит (Django не примет username со спецсимволами)
|
||||||
|
# и оба значения не должны попасть в .env — compose-стек их не использует,
|
||||||
|
# потребитель один, и это Vault.
|
||||||
|
#
|
||||||
|
# Генерируется всегда, а не по флагу sarex_create_superuser: значения уезжают
|
||||||
|
# в secrets/apps/django/superuser (см. vault_app_secrets), откуда их забирает
|
||||||
|
# стартовый скрипт пода backend. Без них vault-agent не отрендерит шаблон и
|
||||||
|
# под встанет в Init:Error. lookup('password') идемпотентен — на повторных
|
||||||
|
# прогонах возвращает уже сохранённое значение, а не генерирует новое.
|
||||||
|
- name: Сгенерировать/загрузить логин администратора платформы
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
superuser_name: "{{ lookup('password', secrets_store ~ '/' ~ inventory_hostname ~ '/SAREX_SUPERUSER_NAME chars=ascii_lowercase,digits length=10') }}"
|
||||||
|
no_log: true
|
||||||
|
tags: [env, superuser]
|
||||||
|
|
||||||
|
- name: Сгенерировать/загрузить пароль администратора платформы
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
superuser_password: "{{ lookup('password', secrets_store ~ '/' ~ inventory_hostname ~ '/SAREX_SUPERUSER_PASSWORD chars=ascii_letters,digits length=' ~ secret_length) }}"
|
||||||
|
no_log: true
|
||||||
|
tags: [env, superuser]
|
||||||
|
|
||||||
# RSA-ключи JWT (RS512): генерируем на control-node, persist в secrets_store.
|
# RSA-ключи JWT (RS512): генерируем на control-node, persist в secrets_store.
|
||||||
- name: Сгенерировать приватный RSA-ключ JWT
|
- name: Сгенерировать приватный RSA-ключ JWT
|
||||||
ansible.builtin.command: "openssl genrsa -out {{ jwt_private_key_path }} 4096"
|
ansible.builtin.command: "openssl genrsa -out {{ jwt_private_key_path }} 4096"
|
||||||
@ -181,89 +203,16 @@
|
|||||||
changed_when: true
|
changed_when: true
|
||||||
tags: [up]
|
tags: [up]
|
||||||
|
|
||||||
# --- Django-суперпользователь: генерация логина+пароля, создание -----------
|
# --- Django-суперпользователь ----------------------------------------------
|
||||||
# Только по явной команде (uv run poe superuser передаёт sarex_create_superuser=true),
|
# Самого создания здесь больше нет — его делает под backend при каждом старте
|
||||||
# чтобы обычный deploy не дёргал backend. Логин/пароль персистятся в secrets_store
|
# (apps/django/aero/kustomization.yaml, args стартового скрипта). Так учётка
|
||||||
# (переиспользуются при повторе — идемпотентно).
|
# появляется вместе с приложением, а не отдельной командой оператора, и
|
||||||
- name: Сгенерировать логин суперпользователя
|
# переживает пересоздание пода без ручного вмешательства.
|
||||||
ansible.builtin.set_fact:
|
|
||||||
superuser_name: "{{ lookup('password', secrets_store ~ '/' ~ inventory_hostname ~ '/SAREX_SUPERUSER_NAME chars=ascii_lowercase,digits length=10') }}"
|
|
||||||
when: sarex_create_superuser | default(false) | bool
|
|
||||||
no_log: true
|
|
||||||
tags: [superuser]
|
|
||||||
|
|
||||||
- name: Сгенерировать пароль суперпользователя
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
superuser_password: "{{ lookup('password', secrets_store ~ '/' ~ inventory_hostname ~ '/SAREX_SUPERUSER_PASSWORD chars=ascii_letters,digits length=' ~ secret_length) }}"
|
|
||||||
when: sarex_create_superuser | default(false) | bool
|
|
||||||
no_log: true
|
|
||||||
tags: [superuser]
|
|
||||||
|
|
||||||
# Backend теперь живёт в k3s, а не в compose, поэтому идём через kubectl exec
|
|
||||||
# внутри контейнера k3s-server, а не через docker compose exec.
|
|
||||||
#
|
#
|
||||||
# Пароль передаётся переменной окружения (env в kubectl exec), а не аргументом
|
# Ansible остаётся владельцем самих значений: генерирует их выше (тег env) и
|
||||||
# командной строки: аргументы видны в ps любому, кто имеет доступ к ноде.
|
# кладёт в Vault. Здесь — только показать логин по явному запросу
|
||||||
# Метка именно app=backend: universal-chart не проставляет
|
# (uv run poe superuser передаёт sarex_create_superuser=true).
|
||||||
# app.kubernetes.io/instance, и селектор по нему возвращает пустой список.
|
- name: Показать учётные данные администратора платформы
|
||||||
- name: Найти под backend в k3s
|
|
||||||
when: sarex_create_superuser | default(false) | bool
|
|
||||||
ansible.builtin.command:
|
|
||||||
cmd: >-
|
|
||||||
{{ compose_cmd }} exec -T k3s-server
|
|
||||||
kubectl -n {{ django_namespace }} get pods -l app=backend
|
|
||||||
--field-selector=status.phase=Running
|
|
||||||
-o jsonpath={.items[0].metadata.name}
|
|
||||||
chdir: "{{ deploy_dir }}"
|
|
||||||
register: backend_pod
|
|
||||||
changed_when: false
|
|
||||||
tags: [superuser]
|
|
||||||
|
|
||||||
# argv, а не shell: аргументы передаются процессу напрямую, без разбора
|
|
||||||
# шеллом. Иначе python-однострочник попадает внутрь двойных кавычек внутри
|
|
||||||
# sh -ec '...' и разваливается на вложенном экранировании.
|
|
||||||
#
|
|
||||||
# Пароль идёт переменной окружения, а не аргументом: аргументы видны в ps
|
|
||||||
# любому, у кого есть доступ к ноде.
|
|
||||||
- name: Создать/обновить Django-суперпользователя в backend (k3s)
|
|
||||||
when: sarex_create_superuser | default(false) | bool
|
|
||||||
ansible.builtin.command:
|
|
||||||
argv:
|
|
||||||
- "{{ compose_cmd.split()[0] }}"
|
|
||||||
- compose
|
|
||||||
- exec
|
|
||||||
- -T
|
|
||||||
- k3s-server
|
|
||||||
- kubectl
|
|
||||||
- -n
|
|
||||||
- "{{ django_namespace }}"
|
|
||||||
- exec
|
|
||||||
- "{{ backend_pod.stdout | trim }}"
|
|
||||||
- -c
|
|
||||||
- backend
|
|
||||||
- --
|
|
||||||
- env
|
|
||||||
- "SU_NAME={{ superuser_name }}"
|
|
||||||
- "SU_PASS={{ superuser_password }}"
|
|
||||||
- "SU_EMAIL={{ superuser_name }}@{{ platform_domain }}"
|
|
||||||
- python
|
|
||||||
- manage.py
|
|
||||||
- shell
|
|
||||||
- -c
|
|
||||||
- >-
|
|
||||||
import os;
|
|
||||||
from django.contrib.auth import get_user_model;
|
|
||||||
U = get_user_model();
|
|
||||||
u, c = U.objects.get_or_create(username=os.environ['SU_NAME'], defaults={'email': os.environ['SU_EMAIL']});
|
|
||||||
u.is_staff = True; u.is_superuser = True; u.set_password(os.environ['SU_PASS']); u.save();
|
|
||||||
print('created' if c else 'updated')
|
|
||||||
chdir: "{{ deploy_dir }}"
|
|
||||||
register: superuser_result
|
|
||||||
changed_when: true
|
|
||||||
no_log: true
|
|
||||||
tags: [superuser]
|
|
||||||
|
|
||||||
- name: Показать учётные данные суперпользователя
|
|
||||||
ansible.builtin.debug:
|
ansible.builtin.debug:
|
||||||
msg: "Django superuser → login: {{ superuser_name }} password: {{ superuser_password }}"
|
msg: "Django superuser → login: {{ superuser_name }} password: {{ superuser_password }}"
|
||||||
when: sarex_create_superuser | default(false) | bool
|
when: sarex_create_superuser | default(false) | bool
|
||||||
|
|||||||
@ -83,3 +83,61 @@ patches:
|
|||||||
- op: replace
|
- op: replace
|
||||||
path: /spec/values/services/backend/envs/2/value/_default
|
path: /spec/values/services/backend/envs/2/value/_default
|
||||||
value: "False"
|
value: "False"
|
||||||
|
|
||||||
|
# --- Администратор платформы --------------------------------------------
|
||||||
|
# Учётка приезжает из Vault под именами, которые ждёт штатная
|
||||||
|
# createsuperuser --noinput. Секрет кладёт ansible
|
||||||
|
# (vault_app_secrets, secrets/apps/django/superuser).
|
||||||
|
- op: add
|
||||||
|
path: /spec/values/services/backend/podAnnotations/_default/vault.hashicorp.com~1agent-inject-secret-django-superuser
|
||||||
|
value: secrets/data/apps/django/superuser
|
||||||
|
- op: add
|
||||||
|
path: /spec/values/services/backend/podAnnotations/_default/vault.hashicorp.com~1agent-inject-template-django-superuser
|
||||||
|
value: |-
|
||||||
|
{{- with secret "secrets/data/apps/django/superuser" -}}
|
||||||
|
DJANGO_SUPERUSER_USERNAME={{ index .Data.data "username" }}
|
||||||
|
DJANGO_SUPERUSER_EMAIL={{ index .Data.data "email" }}
|
||||||
|
DJANGO_SUPERUSER_PASSWORD={{ index .Data.data "password" }}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
# Стартовый скрипт целиком, а не точечная правка: JSON6902 умеет заменять
|
||||||
|
# только элемент списка, а вся логика запуска — один shell-блок args[0].
|
||||||
|
# Отличия от base:
|
||||||
|
# * не подключается django-kafka — этого секрета в контуре нет;
|
||||||
|
# * добавлены migrate и создание администратора.
|
||||||
|
#
|
||||||
|
# Почему migrate здесь, хотя его же делает /opt/sarex/entrypoint.sh:
|
||||||
|
# там он запущен без set -e, и его падение НИКАК не мешает uwsgi
|
||||||
|
# стартовать. Именно так контур несколько часов отдавал 200 на /admin/
|
||||||
|
# с пустой схемой БД, пока backend не мог аутентифицироваться. Здесь
|
||||||
|
# команда выполняется под set -e из command базы (/bin/sh -ec), поэтому
|
||||||
|
# неудача валит под в CrashLoopBackOff — то есть становится видимой.
|
||||||
|
# Повторный прогон внутри entrypoint.sh при уже накатанной схеме
|
||||||
|
# отрабатывает вхолостую за секунды.
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/deployment/args/_default/0
|
||||||
|
value: |
|
||||||
|
set -a
|
||||||
|
[ -f /vault/secrets/django-postgresql ] && . /vault/secrets/django-postgresql
|
||||||
|
[ -f /vault/secrets/django-rabbitmq ] && . /vault/secrets/django-rabbitmq
|
||||||
|
[ -f /vault/secrets/django-s3 ] && . /vault/secrets/django-s3
|
||||||
|
[ -f /vault/secrets/django-superuser ] && . /vault/secrets/django-superuser
|
||||||
|
[ -f /vault/secrets/django-jwt-private ] && export JWT_PRIVATE_KEY="$(cat /vault/secrets/django-jwt-private)"
|
||||||
|
[ -f /vault/secrets/django-jwt-public ] && export JWT_PUBLIC_KEY="$(cat /vault/secrets/django-jwt-public)"
|
||||||
|
set +a
|
||||||
|
|
||||||
|
python manage.py migrate --noinput
|
||||||
|
|
||||||
|
# Идемпотентно по построению: команда отрабатывает при КАЖДОМ старте
|
||||||
|
# пода, а на уже существующем логине штатно возвращает
|
||||||
|
# "That username is already taken" с ненулевым кодом. Ошибка ожидаема
|
||||||
|
# и гасится веткой else, иначе set -e ронял бы под на втором запуске.
|
||||||
|
if [ -n "${DJANGO_SUPERUSER_USERNAME:-}" ]; then
|
||||||
|
if python manage.py createsuperuser --noinput; then
|
||||||
|
echo "entrypoint: администратор создан"
|
||||||
|
else
|
||||||
|
echo "entrypoint: администратор уже существует, пропускаем"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec /opt/sarex/entrypoint.sh
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user