From 9c156df0c7ca2fd5a54d186d730e729fa9ce5c4f Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Fri, 24 Jul 2026 21:05:27 +0300 Subject: [PATCH] =?UTF-8?q?Ansible:=20=D1=80=D0=B5=D1=81=D1=82=D0=B0=D1=80?= =?UTF-8?q?=D1=82=20=D0=BA=D0=BE=D0=BD=D1=82=D0=B5=D0=B9=D0=BD=D0=B5=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B2=D0=B5=D0=B4=D1=91?= =?UTF-8?q?=D0=BD=20=D0=BD=D0=B0=20handlers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - caddyproxy, netdata, authelia, bifrost, remembos: конфиг-задачи нотифаят handler "Restart application" вместо безусловного или условного рестарта - ansible.cfg: force_handlers, иначе упавший прогон теряет notify и рестарт, применяющий правку конфига, не случится уже никогда - authelia рестартит только authelia_app, чтобы не сбрасывать сессии в redis --- ansible.cfg | 5 ++++ docs/backlog/README.md | 2 +- docs/backlog/ansible-handlers-restart.md | 20 ---------------- docs/backlog/handlers-remaining-apps.md | 30 ++++++++++++++++++++++++ playbook-authelia.yml | 11 ++++++++- playbook-bifrost.yml | 15 +++++------- playbook-caddyproxy.yml | 7 +++++- playbook-memos.yml | 1 - playbook-netdata.yml | 12 +++++++++- playbook-remembos.yml | 12 +++++----- 10 files changed, 75 insertions(+), 40 deletions(-) delete mode 100644 docs/backlog/ansible-handlers-restart.md create mode 100644 docs/backlog/handlers-remaining-apps.md diff --git a/ansible.cfg b/ansible.cfg index cd05717..6170948 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -2,3 +2,8 @@ host_key_checking = True vault_password_file = ./ansible-vault-password-file roles_path = ./galaxy.roles + +# Выполнять handlers даже если play упал: иначе упавший прогон теряет +# накопленные notify, а на следующем конфиг уже не changed — и рестарт, +# применяющий правку, не случится никогда. +force_handlers = True diff --git a/docs/backlog/README.md b/docs/backlog/README.md index 1b730ca..ebe62b2 100644 --- a/docs/backlog/README.md +++ b/docs/backlog/README.md @@ -21,7 +21,6 @@ _(sec-ревью)_ — три независимых security-ревью на м - [Убрать группу `docker` у сервисных пользователей](service-users-docker-group.md) — `docker` = root на хосте; ~все app-аккаунты фактически root, изоляция «юзер на сервис» обнуляется _(sec-ревью)_ - [Изолировать/запаролить сессионный redis Authelia](authelia-redis-isolation.md) — стор сессий SSO в общей сети без пароля; запись в него = hijack SSO всех сервисов _(sec-ревью)_ - [Алерты на проблемные контейнеры](container-alerts.md) — wakapi крутился в restart-loop несколько дней незамеченным; healthcheck в compose + алерты Netdata -- [Рестарт контейнеров через handlers](ansible-handlers-restart.md) — `state: restarted` выполняется безусловно на каждом прогоне (лишний downtime), нет ни одного handler _(ансибл-ревью)_ ## Средний @@ -36,6 +35,7 @@ _(sec-ревью)_ — три независимых security-ревью на м - [Gitea runner on-demand в Yandex Cloud](gitea-runner-on-demand.md) — раннер активен только во время сборки; webhook→Cloud Function стартует ВМ, probe/decide гасят по idle; экономия ~95% - [Синхронизация общих Ansible-ролей](shared-roles-sync.md) — `owner`/`eget`/`secrets` дублируются между репозиториями и дрейфуют; канон в ansible-shared + rsync-таски invoke - [Уведомления о размере приложений и диске](disk-usage-alerts.md) — алерты на заполнение диска и аномальный рост data-томов; сервер ресурсно ограничен _(tududi)_ +- [Handlers рестарта для оставшихся приложений](handlers-remaining-apps.md) — apprise/dashboard/transcriber/miniflux монтируют конфиг в контейнер, но рестарта нет вовсе; правка конфига не подхватывается - [Composable-роль `backup`](ansible-backup-role.md) — бэкап — самый чистый шов для extraction (одинаков у всех, различается только список targets) _(ансибл-ревью)_ - [Точечные фиксы идемпотентности и баг имени play](ansible-quick-fixes.md) — быстрые правки: имя play в wanderer, `changed_when` в netdata/eget, backup-targets через template _(ансибл-ревью)_ diff --git a/docs/backlog/ansible-handlers-restart.md b/docs/backlog/ansible-handlers-restart.md deleted file mode 100644 index 04240c7..0000000 --- a/docs/backlog/ansible-handlers-restart.md +++ /dev/null @@ -1,20 +0,0 @@ -# Рестарт контейнеров через handlers, а не безусловно - -**Приоритет:** высокий - -Ни в одном плейбуке нет `handlers:`. Вместо этого задача `state: restarted` -выполняется **всегда** — рестартит контейнер на каждом прогоне даже без -изменений (`playbook-caddyproxy.yml:106`, `playbook-netdata.yml:143`, -`playbook-authelia.yml:92`): не идемпотентно, лишний downtime. В -`playbook-gitea.yml` рестарта нет вовсе — несогласованность. Канонический -паттерн: шаблон конфига `notify`-ит handler, который делает -`docker_compose_v2: state: restarted` только при реальном изменении. Внедряется -инкрементально, по одному сервису. Заодно убрать мёртвый -`docker_compose_file_result` в `playbook-memos.yml:76` (регистрируется, нигде не -используется — задумывался под `when`/`notify`). - -Топ-приоритет ансибл-ревью. Полный контекст: -[docs/drafts/ansible-review.md](../drafts/ansible-review.md) §3. - -Связано: playbook-caddyproxy.yml, playbook-netdata.yml, playbook-authelia.yml, -playbook-gitea.yml, playbook-memos.yml. diff --git a/docs/backlog/handlers-remaining-apps.md b/docs/backlog/handlers-remaining-apps.md new file mode 100644 index 0000000..ddcef3c --- /dev/null +++ b/docs/backlog/handlers-remaining-apps.md @@ -0,0 +1,30 @@ +# Handlers рестарта для оставшихся приложений + +**Приоритет:** средний + +Паттерн `handlers` + `notify` внедрён в caddyproxy, netdata, authelia, bifrost, +remembos (см. задачу `ansible-handlers-restart`, закрыта). Остались плейбуки, где +конфиг bind-моунтится в контейнер, но рестарта нет **вообще** — правка конфига без +изменения docker-compose.yml не подхватывается до следующего пересоздания +контейнера: + +- `playbook-apprise.yml` — `server.cfg` → `config_dir:/config`. +- `playbook-dashboard.yml` — `config/*.yaml` → `config_dir:/app/config`. +- `playbook-transcriber.yml` — `config.toml` bind-моунтится **отдельным файлом** + (`{{ config_file }}:/config/config.toml:ro`). Худший случай: `template` + перезаписывает файл с новым inode, и контейнер продолжает видеть старый до + рестарта. +- `playbook-miniflux.yml` — файлы секретов роли `secrets` → `secrets_dir:/secrets:ro`, + читаются через `*_FILE` при старте. + +Заодно проверить `playbook-goaccess.yml`: конфиг там не монтируется, но +`Dockerfile`/`entrypoint.sh` меняются без изменения compose-файла — надо +убедиться, что `build: always` действительно пересоздаёт контейнер. + +Отдельный кандидат: caddy сейчас перезапускается целиком, хотя умеет +`caddy reload` — применение Caddyfile без разрыва соединений на реверс-прокси, +через который идёт весь трафик. В `playbook-caddyproxy.yml` для этого уже лежит +закомментированный блок с `docker_compose_v2_exec`. + +Связано: playbook-apprise.yml, playbook-dashboard.yml, playbook-transcriber.yml, +playbook-miniflux.yml, playbook-goaccess.yml, playbook-caddyproxy.yml. diff --git a/playbook-authelia.yml b/playbook-authelia.yml index d5a5d84..62eaca9 100644 --- a/playbook-authelia.yml +++ b/playbook-authelia.yml @@ -46,6 +46,7 @@ owner: "{{ app_user }}" group: "{{ app_user }}" mode: "0600" + notify: "Restart application" - name: "Copy configuration file" ansible.builtin.template: @@ -54,6 +55,7 @@ owner: "{{ app_user }}" group: "{{ app_user }}" mode: "0600" + notify: "Restart application" - name: "Copy gobackup config" ansible.builtin.template: @@ -87,9 +89,16 @@ tags: - run-app - - name: "Restart application with docker compose" + handlers: + # Приложение читает конфиги только при старте, а `state: present` не + # пересоздаёт контейнер, если docker-compose.yml не изменился, — поэтому + # правку конфига применяет только явный рестарт. + - name: "Restart application" community.docker.docker_compose_v2: project_src: "{{ base_dir }}" + # Только сам authelia: рестарт redis сбросил бы сессии всех сервисов. + services: + - "authelia_app" state: "restarted" tags: - run-app diff --git a/playbook-bifrost.yml b/playbook-bifrost.yml index 8f078e3..1377ee3 100644 --- a/playbook-bifrost.yml +++ b/playbook-bifrost.yml @@ -42,7 +42,7 @@ owner: "{{ app_user }}" group: "{{ app_user }}" mode: "0600" - register: config_file_result + notify: "Restart application" - name: "Copy docker compose file" ansible.builtin.template: @@ -51,7 +51,6 @@ owner: "{{ app_user }}" group: "{{ app_user }}" mode: "0640" - register: docker_compose_file_result - name: "Run application with docker compose" community.docker.docker_compose_v2: @@ -61,15 +60,13 @@ tags: - run-app - # Bifrost читает config.json только при старте. Если изменился лишь конфиг - # (а compose — нет), `state: present` не пересоздаёт контейнер, и правки - # (ротация ключа, смена модели/правил) не подхватятся — нужен явный рестарт. - - name: "Restart docker compose services if config changed but not docker-compose.yml" + handlers: + # Приложение читает конфиги только при старте, а `state: present` не + # пересоздаёт контейнер, если docker-compose.yml не изменился, — поэтому + # правку конфига применяет только явный рестарт. + - name: "Restart application" community.docker.docker_compose_v2: project_src: "{{ base_dir }}" state: "restarted" - when: - - config_file_result.changed - - not docker_compose_file_result.changed tags: - run-app diff --git a/playbook-caddyproxy.yml b/playbook-caddyproxy.yml index 55ce2df..bbdec06 100644 --- a/playbook-caddyproxy.yml +++ b/playbook-caddyproxy.yml @@ -74,6 +74,7 @@ owner: "{{ app_user }}" group: "{{ app_user }}" mode: "0640" + notify: "Restart application" - name: "Copy docker compose file" ansible.builtin.template: @@ -99,7 +100,11 @@ # tags: # - run-app - - name: "Restart application with docker compose" + handlers: + # Приложение читает конфиги только при старте, а `state: present` не + # пересоздаёт контейнер, если docker-compose.yml не изменился, — поэтому + # правку конфига применяет только явный рестарт. + - name: "Restart application" community.docker.docker_compose_v2: project_src: "{{ base_dir }}" state: "restarted" diff --git a/playbook-memos.yml b/playbook-memos.yml index 5f028c0..dcbb117 100644 --- a/playbook-memos.yml +++ b/playbook-memos.yml @@ -69,7 +69,6 @@ owner: "{{ app_user }}" group: "{{ app_user }}" mode: "0640" - register: docker_compose_file_result - name: "Run application with docker compose" community.docker.docker_compose_v2: diff --git a/playbook-netdata.yml b/playbook-netdata.yml index 97db590..68cd552 100644 --- a/playbook-netdata.yml +++ b/playbook-netdata.yml @@ -44,6 +44,7 @@ owner: "{{ app_user }}" group: "{{ app_user }}" mode: "0640" + notify: "Restart application" - name: "Find all go.d plugin config files" ansible.builtin.find: @@ -60,6 +61,7 @@ group: "{{ app_user }}" mode: "0640" loop: "{{ go_d_source_files.files }}" + notify: "Restart application" - name: "Find existing go.d config files on server" ansible.builtin.find: @@ -73,6 +75,7 @@ state: absent loop: "{{ go_d_existing_files.files }}" when: (item.path | basename) not in (go_d_source_files.files | map(attribute='path') | map('basename') | list) + notify: "Restart application" - name: "Find all health.d config files" ansible.builtin.find: @@ -89,6 +92,7 @@ group: "{{ app_user }}" mode: "0640" loop: "{{ health_d_source_files.files }}" + notify: "Restart application" - name: "Find existing health.d config files on server" ansible.builtin.find: @@ -102,6 +106,7 @@ state: absent loop: "{{ health_d_existing_files.files }}" when: (item.path | basename) not in (health_d_source_files.files | map(attribute='path') | map('basename') | list) + notify: "Restart application" - name: "Copy health alarm notify config" ansible.builtin.template: @@ -110,6 +115,7 @@ owner: "{{ app_user }}" group: "{{ app_user }}" mode: "0640" + notify: "Restart application" - name: "Grab docker group id." ansible.builtin.shell: @@ -136,7 +142,11 @@ tags: - run-app - - name: "Restart application with docker compose" + handlers: + # Приложение читает конфиги только при старте, а `state: present` не + # пересоздаёт контейнер, если docker-compose.yml не изменился, — поэтому + # правку конфига применяет только явный рестарт. + - name: "Restart application" community.docker.docker_compose_v2: project_src: "{{ base_dir }}" state: "restarted" diff --git a/playbook-remembos.yml b/playbook-remembos.yml index 4a62794..514f41d 100644 --- a/playbook-remembos.yml +++ b/playbook-remembos.yml @@ -41,7 +41,7 @@ owner: "{{ app_user }}" group: "{{ app_user }}" mode: "0640" - register: config_file_result + notify: "Restart application" - name: "Copy docker compose file" ansible.builtin.template: @@ -50,7 +50,6 @@ owner: "{{ app_user }}" group: "{{ app_user }}" mode: "0640" - register: docker_compose_file_result - name: 'Login to Yandex Container Registry' community.docker.docker_login: @@ -66,12 +65,13 @@ tags: - run-app - - name: "Restart docker compose services if config changed but not docker-compose.yml" + handlers: + # Приложение читает конфиги только при старте, а `state: present` не + # пересоздаёт контейнер, если docker-compose.yml не изменился, — поэтому + # правку конфига применяет только явный рестарт. + - name: "Restart application" community.docker.docker_compose_v2: project_src: "{{ base_dir }}" state: "restarted" - when: - - config_file_result.changed - - not docker_compose_file_result.changed tags: - run-app