Ansible: рестарт контейнеров переведён на handlers
- caddyproxy, netdata, authelia, bifrost, remembos: конфиг-задачи нотифаят handler "Restart application" вместо безусловного или условного рестарта - ansible.cfg: force_handlers, иначе упавший прогон теряет notify и рестарт, применяющий правку конфига, не случится уже никогда - authelia рестартит только authelia_app, чтобы не сбрасывать сессии в redis
This commit is contained in:
@@ -2,3 +2,8 @@
|
|||||||
host_key_checking = True
|
host_key_checking = True
|
||||||
vault_password_file = ./ansible-vault-password-file
|
vault_password_file = ./ansible-vault-password-file
|
||||||
roles_path = ./galaxy.roles
|
roles_path = ./galaxy.roles
|
||||||
|
|
||||||
|
# Выполнять handlers даже если play упал: иначе упавший прогон теряет
|
||||||
|
# накопленные notify, а на следующем конфиг уже не changed — и рестарт,
|
||||||
|
# применяющий правку, не случится никогда.
|
||||||
|
force_handlers = True
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ _(sec-ревью)_ — три независимых security-ревью на м
|
|||||||
- [Убрать группу `docker` у сервисных пользователей](service-users-docker-group.md) — `docker` = root на хосте; ~все app-аккаунты фактически root, изоляция «юзер на сервис» обнуляется _(sec-ревью)_
|
- [Убрать группу `docker` у сервисных пользователей](service-users-docker-group.md) — `docker` = root на хосте; ~все app-аккаунты фактически root, изоляция «юзер на сервис» обнуляется _(sec-ревью)_
|
||||||
- [Изолировать/запаролить сессионный redis Authelia](authelia-redis-isolation.md) — стор сессий SSO в общей сети без пароля; запись в него = hijack SSO всех сервисов _(sec-ревью)_
|
- [Изолировать/запаролить сессионный redis Authelia](authelia-redis-isolation.md) — стор сессий SSO в общей сети без пароля; запись в него = hijack SSO всех сервисов _(sec-ревью)_
|
||||||
- [Алерты на проблемные контейнеры](container-alerts.md) — wakapi крутился в restart-loop несколько дней незамеченным; healthcheck в compose + алерты Netdata
|
- [Алерты на проблемные контейнеры](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%
|
- [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
|
- [Синхронизация общих Ansible-ролей](shared-roles-sync.md) — `owner`/`eget`/`secrets` дублируются между репозиториями и дрейфуют; канон в ansible-shared + rsync-таски invoke
|
||||||
- [Уведомления о размере приложений и диске](disk-usage-alerts.md) — алерты на заполнение диска и аномальный рост data-томов; сервер ресурсно ограничен _(tududi)_
|
- [Уведомления о размере приложений и диске](disk-usage-alerts.md) — алерты на заполнение диска и аномальный рост data-томов; сервер ресурсно ограничен _(tududi)_
|
||||||
|
- [Handlers рестарта для оставшихся приложений](handlers-remaining-apps.md) — apprise/dashboard/transcriber/miniflux монтируют конфиг в контейнер, но рестарта нет вовсе; правка конфига не подхватывается
|
||||||
- [Composable-роль `backup`](ansible-backup-role.md) — бэкап — самый чистый шов для extraction (одинаков у всех, различается только список targets) _(ансибл-ревью)_
|
- [Composable-роль `backup`](ansible-backup-role.md) — бэкап — самый чистый шов для extraction (одинаков у всех, различается только список targets) _(ансибл-ревью)_
|
||||||
- [Точечные фиксы идемпотентности и баг имени play](ansible-quick-fixes.md) — быстрые правки: имя play в wanderer, `changed_when` в netdata/eget, backup-targets через template _(ансибл-ревью)_
|
- [Точечные фиксы идемпотентности и баг имени play](ansible-quick-fixes.md) — быстрые правки: имя play в wanderer, `changed_when` в netdata/eget, backup-targets через template _(ансибл-ревью)_
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
|
||||||
@@ -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.
|
||||||
+10
-1
@@ -46,6 +46,7 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0600"
|
mode: "0600"
|
||||||
|
notify: "Restart application"
|
||||||
|
|
||||||
- name: "Copy configuration file"
|
- name: "Copy configuration file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -54,6 +55,7 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0600"
|
mode: "0600"
|
||||||
|
notify: "Restart application"
|
||||||
|
|
||||||
- name: "Copy gobackup config"
|
- name: "Copy gobackup config"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -87,9 +89,16 @@
|
|||||||
tags:
|
tags:
|
||||||
- run-app
|
- run-app
|
||||||
|
|
||||||
- name: "Restart application with docker compose"
|
handlers:
|
||||||
|
# Приложение читает конфиги только при старте, а `state: present` не
|
||||||
|
# пересоздаёт контейнер, если docker-compose.yml не изменился, — поэтому
|
||||||
|
# правку конфига применяет только явный рестарт.
|
||||||
|
- name: "Restart application"
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
project_src: "{{ base_dir }}"
|
project_src: "{{ base_dir }}"
|
||||||
|
# Только сам authelia: рестарт redis сбросил бы сессии всех сервисов.
|
||||||
|
services:
|
||||||
|
- "authelia_app"
|
||||||
state: "restarted"
|
state: "restarted"
|
||||||
tags:
|
tags:
|
||||||
- run-app
|
- run-app
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0600"
|
mode: "0600"
|
||||||
register: config_file_result
|
notify: "Restart application"
|
||||||
|
|
||||||
- name: "Copy docker compose file"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -51,7 +51,6 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
register: docker_compose_file_result
|
|
||||||
|
|
||||||
- name: "Run application with docker compose"
|
- name: "Run application with docker compose"
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
@@ -61,15 +60,13 @@
|
|||||||
tags:
|
tags:
|
||||||
- run-app
|
- run-app
|
||||||
|
|
||||||
# Bifrost читает config.json только при старте. Если изменился лишь конфиг
|
handlers:
|
||||||
# (а compose — нет), `state: present` не пересоздаёт контейнер, и правки
|
# Приложение читает конфиги только при старте, а `state: present` не
|
||||||
# (ротация ключа, смена модели/правил) не подхватятся — нужен явный рестарт.
|
# пересоздаёт контейнер, если docker-compose.yml не изменился, — поэтому
|
||||||
- name: "Restart docker compose services if config changed but not docker-compose.yml"
|
# правку конфига применяет только явный рестарт.
|
||||||
|
- name: "Restart application"
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
project_src: "{{ base_dir }}"
|
project_src: "{{ base_dir }}"
|
||||||
state: "restarted"
|
state: "restarted"
|
||||||
when:
|
|
||||||
- config_file_result.changed
|
|
||||||
- not docker_compose_file_result.changed
|
|
||||||
tags:
|
tags:
|
||||||
- run-app
|
- run-app
|
||||||
|
|||||||
@@ -74,6 +74,7 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
|
notify: "Restart application"
|
||||||
|
|
||||||
- name: "Copy docker compose file"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -99,7 +100,11 @@
|
|||||||
# tags:
|
# tags:
|
||||||
# - run-app
|
# - run-app
|
||||||
|
|
||||||
- name: "Restart application with docker compose"
|
handlers:
|
||||||
|
# Приложение читает конфиги только при старте, а `state: present` не
|
||||||
|
# пересоздаёт контейнер, если docker-compose.yml не изменился, — поэтому
|
||||||
|
# правку конфига применяет только явный рестарт.
|
||||||
|
- name: "Restart application"
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
project_src: "{{ base_dir }}"
|
project_src: "{{ base_dir }}"
|
||||||
state: "restarted"
|
state: "restarted"
|
||||||
|
|||||||
@@ -69,7 +69,6 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
register: docker_compose_file_result
|
|
||||||
|
|
||||||
- name: "Run application with docker compose"
|
- name: "Run application with docker compose"
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
|
|||||||
+11
-1
@@ -44,6 +44,7 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
|
notify: "Restart application"
|
||||||
|
|
||||||
- name: "Find all go.d plugin config files"
|
- name: "Find all go.d plugin config files"
|
||||||
ansible.builtin.find:
|
ansible.builtin.find:
|
||||||
@@ -60,6 +61,7 @@
|
|||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
loop: "{{ go_d_source_files.files }}"
|
loop: "{{ go_d_source_files.files }}"
|
||||||
|
notify: "Restart application"
|
||||||
|
|
||||||
- name: "Find existing go.d config files on server"
|
- name: "Find existing go.d config files on server"
|
||||||
ansible.builtin.find:
|
ansible.builtin.find:
|
||||||
@@ -73,6 +75,7 @@
|
|||||||
state: absent
|
state: absent
|
||||||
loop: "{{ go_d_existing_files.files }}"
|
loop: "{{ go_d_existing_files.files }}"
|
||||||
when: (item.path | basename) not in (go_d_source_files.files | map(attribute='path') | map('basename') | list)
|
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"
|
- name: "Find all health.d config files"
|
||||||
ansible.builtin.find:
|
ansible.builtin.find:
|
||||||
@@ -89,6 +92,7 @@
|
|||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
loop: "{{ health_d_source_files.files }}"
|
loop: "{{ health_d_source_files.files }}"
|
||||||
|
notify: "Restart application"
|
||||||
|
|
||||||
- name: "Find existing health.d config files on server"
|
- name: "Find existing health.d config files on server"
|
||||||
ansible.builtin.find:
|
ansible.builtin.find:
|
||||||
@@ -102,6 +106,7 @@
|
|||||||
state: absent
|
state: absent
|
||||||
loop: "{{ health_d_existing_files.files }}"
|
loop: "{{ health_d_existing_files.files }}"
|
||||||
when: (item.path | basename) not in (health_d_source_files.files | map(attribute='path') | map('basename') | list)
|
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"
|
- name: "Copy health alarm notify config"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -110,6 +115,7 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
|
notify: "Restart application"
|
||||||
|
|
||||||
- name: "Grab docker group id."
|
- name: "Grab docker group id."
|
||||||
ansible.builtin.shell:
|
ansible.builtin.shell:
|
||||||
@@ -136,7 +142,11 @@
|
|||||||
tags:
|
tags:
|
||||||
- run-app
|
- run-app
|
||||||
|
|
||||||
- name: "Restart application with docker compose"
|
handlers:
|
||||||
|
# Приложение читает конфиги только при старте, а `state: present` не
|
||||||
|
# пересоздаёт контейнер, если docker-compose.yml не изменился, — поэтому
|
||||||
|
# правку конфига применяет только явный рестарт.
|
||||||
|
- name: "Restart application"
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
project_src: "{{ base_dir }}"
|
project_src: "{{ base_dir }}"
|
||||||
state: "restarted"
|
state: "restarted"
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
register: config_file_result
|
notify: "Restart application"
|
||||||
|
|
||||||
- name: "Copy docker compose file"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -50,7 +50,6 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
register: docker_compose_file_result
|
|
||||||
|
|
||||||
- name: 'Login to Yandex Container Registry'
|
- name: 'Login to Yandex Container Registry'
|
||||||
community.docker.docker_login:
|
community.docker.docker_login:
|
||||||
@@ -66,12 +65,13 @@
|
|||||||
tags:
|
tags:
|
||||||
- run-app
|
- 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:
|
community.docker.docker_compose_v2:
|
||||||
project_src: "{{ base_dir }}"
|
project_src: "{{ base_dir }}"
|
||||||
state: "restarted"
|
state: "restarted"
|
||||||
when:
|
|
||||||
- config_file_result.changed
|
|
||||||
- not docker_compose_file_result.changed
|
|
||||||
tags:
|
tags:
|
||||||
- run-app
|
- run-app
|
||||||
|
|||||||
Reference in New Issue
Block a user