Add service registry, shared roles and unified reverse proxy
Collect the facts about all 14 services -- VMID, node, address, ports,
domain, pinned images, resources, backup and monitoring participation --
into group_vars/all/services.yml. Values are taken from the existing
playbooks; gaps are marked null rather than invented.
Replace reverse-proxy-{gitea,vaultwarden,grimmory}.yml with a single
playbook iterating over registry entries that declare a domain. It keeps
every check the three had, preserves grimmory's richer Caddy block
byte-for-byte, and restarts Caddy once when any site changed instead of
up to three times. Verified with --check --diff against ru-vps: ok=6
changed=0, so it reproduces the current Caddyfile exactly.
Add two roles factoring out the skeleton duplicated across the pve-*
playbooks: lxc_docker_host (packages, /dev/fuse assertion, fuse-overlayfs
storage driver, UFW baseline) and compose_service (compose file, systemd
unit, config validation, health check). They are not wired into any
playbook yet -- migrating a live service is a separate, per-service step;
compose_service/README.md shows the Gitea example and spells out what
actually changes on the host.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTocXkGUUazHdKKd3r9k71
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
# lxc_docker_host
|
||||
|
||||
Приводит непривилегированный Proxmox LXC в состояние «здесь можно запускать
|
||||
Docker»: пакеты, проверка `/dev/fuse`, `storage-driver: fuse-overlayfs`,
|
||||
запуск демона и базовый UFW.
|
||||
|
||||
Роль вынесена из повторяющихся блоков `playbooks/pve-*.yml`
|
||||
(gitea, vaultwarden, mihomo, adguard, memoir-bot, docker-test, grimmory,
|
||||
hermes-ai) — суммарно около 350 строк копипасты.
|
||||
|
||||
## Что делает
|
||||
|
||||
1. Ставит `ca-certificates`, `curl`, `docker.io`, `fuse-overlayfs`
|
||||
(+ `lxc_docker_host_extra_packages`, `ufw` при управлении фаерволом).
|
||||
2. `stat` + `assert` на `/dev/fuse`: без символьного устройства
|
||||
fuse-overlayfs не работает, и Docker молча деградирует до `vfs`.
|
||||
Сообщение об ошибке подсказывает, какие строки добавить в
|
||||
`/etc/pve/lxc/<vmid>.conf` на узле PVE.
|
||||
3. Пишет `/etc/docker/daemon.json` со `storage-driver: fuse-overlayfs`.
|
||||
4. `systemd: docker` — `enabled: true`, `restarted` при смене daemon.json,
|
||||
иначе `started`.
|
||||
5. UFW: SSH из `homelab_lan_cidr` и `openvpn_network_cidr`, node-exporter
|
||||
9100/tcp с хоста мониторинга (192.168.1.30), произвольные порты сервиса
|
||||
из `lxc_docker_host_ufw_service_rules`, затем `policy deny incoming`.
|
||||
Разрешающие правила ставятся ДО включения политики — иначе прогон
|
||||
обрывает собственную SSH-сессию.
|
||||
6. Проверяет `docker info --format '{{.Driver}}'` и падает при расхождении.
|
||||
|
||||
## Переменные
|
||||
|
||||
Полный список с комментариями — в `defaults/main.yml`. Ключевые:
|
||||
|
||||
| Переменная | По умолчанию | Назначение |
|
||||
|---|---|---|
|
||||
| `lxc_docker_host_extra_packages` | `[]` | доп. пакеты сервиса |
|
||||
| `lxc_docker_host_require_fuse` | `true` | проверять `/dev/fuse` |
|
||||
| `lxc_docker_host_storage_driver` | `fuse-overlayfs` | драйвер хранилища |
|
||||
| `lxc_docker_host_manage_ufw` | `true` | трогать ли UFW вообще |
|
||||
| `lxc_docker_host_ssh_sources` | LAN + OpenVPN | откуда разрешён SSH |
|
||||
| `lxc_docker_host_allow_node_exporter` | `true` | 9100 с хоста мониторинга |
|
||||
| `lxc_docker_host_ufw_service_rules` | `[]` | порты сервиса |
|
||||
| `lxc_docker_host_ufw_enable` | `true` | включать `deny incoming` |
|
||||
| `lxc_docker_host_verify_storage_driver` | `true` | финальная проверка |
|
||||
|
||||
## Пример: как выглядела бы подготовка хоста Grimmory
|
||||
|
||||
```yaml
|
||||
- name: Prepare the Grimmory Docker host
|
||||
hosts: grimmory
|
||||
gather_facts: true
|
||||
vars:
|
||||
ansible_become: false
|
||||
roles:
|
||||
- role: lxc_docker_host
|
||||
lxc_docker_host_extra_packages:
|
||||
- mariadb-client
|
||||
- openssl
|
||||
lxc_docker_host_install_node_exporter: true
|
||||
lxc_docker_host_ufw_service_rules:
|
||||
- port: "{{ homelab_services.grimmory.ports[0].port }}"
|
||||
proto: tcp
|
||||
comment: Grimmory HTTP
|
||||
sources:
|
||||
- "{{ homelab_lan_cidr }}"
|
||||
- "{{ openvpn_network_cidr }}"
|
||||
```
|
||||
|
||||
Эти 12 строк заменяют 86 строк из `playbooks/pve-grimmory.yml`.
|
||||
|
||||
## Чего роль НЕ делает
|
||||
|
||||
* Не создаёт LXC и не правит `/etc/pve/lxc/<vmid>.conf` — это роль `pve_lxc`
|
||||
и соответствующий `pve-*.yml`. Роль только проверяет результат.
|
||||
* Не публикует порты Docker в обход UFW. Помните: `-p` в Docker обходит UFW,
|
||||
поэтому для публикуемых портов нужны правила в цепочке `DOCKER-USER`
|
||||
(см. `grimmory-docker-firewall` в `playbooks/pve-grimmory.yml`).
|
||||
* Не ставит node-exporter по умолчанию — этим занимается `monitoring_exporter`.
|
||||
|
||||
## Handlers
|
||||
|
||||
Их нет намеренно. Docker перезапускается прямо в задаче через
|
||||
`state: restarted if changed else started`: handler отработал бы в конце
|
||||
play, уже после того как compose-стек стартовал на старом драйвере хранилища.
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# roles/lxc_docker_host — подготовка непривилегированного LXC под Docker.
|
||||
# Все переменные документированы; значения по умолчанию соответствуют тому,
|
||||
# что фактически делают playbooks/pve-*.yml.
|
||||
# ============================================================================
|
||||
|
||||
# --- Пакеты ----------------------------------------------------------------
|
||||
# Базовый набор, общий для всех Docker-хостов HomeLab.
|
||||
lxc_docker_host_packages:
|
||||
- ca-certificates
|
||||
- curl
|
||||
- docker.io
|
||||
- fuse-overlayfs
|
||||
|
||||
# Дополнительные пакеты конкретного сервиса.
|
||||
# Примеры из существующих плейбуков:
|
||||
# gitea: [sqlite3, rsync]
|
||||
# adguard: [dnsutils]
|
||||
# mihomo: [git]
|
||||
# memoir-bot: [git, openssh-client, rsync]
|
||||
# grimmory: [mariadb-client, openssl]
|
||||
lxc_docker_host_extra_packages: []
|
||||
|
||||
# Ставить пакет docker-compose (в pve-grimmory.yml и pve-hermes-ai.yml он есть).
|
||||
# Юниты используют плагин `docker compose` из docker.io, поэтому по умолчанию
|
||||
# отдельный пакет не нужен.
|
||||
lxc_docker_host_install_compose_package: false
|
||||
|
||||
# Ставить prometheus-node-exporter локально (так делает pve-grimmory.yml).
|
||||
# Обычно экспортёром управляет роль monitoring_exporter.
|
||||
lxc_docker_host_install_node_exporter: false
|
||||
|
||||
# Обновлять кеш apt перед установкой.
|
||||
lxc_docker_host_update_cache: true
|
||||
|
||||
# --- FUSE ------------------------------------------------------------------
|
||||
# Проверять наличие символьного устройства /dev/fuse и падать, если его нет.
|
||||
# Без него fuse-overlayfs не заработает, а Docker молча деградирует.
|
||||
lxc_docker_host_require_fuse: true
|
||||
lxc_docker_host_fuse_device: /dev/fuse
|
||||
|
||||
# --- Docker daemon ---------------------------------------------------------
|
||||
lxc_docker_host_storage_driver: fuse-overlayfs
|
||||
# Итоговое содержимое /etc/docker/daemon.json. Расширяемо: можно передать
|
||||
# дополнительные ключи, storage-driver подставляется отсюда.
|
||||
lxc_docker_host_daemon_config:
|
||||
storage-driver: "{{ lxc_docker_host_storage_driver }}"
|
||||
lxc_docker_host_daemon_config_path: /etc/docker/daemon.json
|
||||
# Проверить `docker info --format {{.Driver}}` в конце и упасть при расхождении.
|
||||
lxc_docker_host_verify_storage_driver: true
|
||||
|
||||
# --- UFW -------------------------------------------------------------------
|
||||
# Управлять ли фаерволом вообще. false — роль не трогает ufw.
|
||||
lxc_docker_host_manage_ufw: true
|
||||
# Ставить пакет ufw, если управление включено.
|
||||
lxc_docker_host_install_ufw: true
|
||||
|
||||
# Источники, которым разрешён SSH.
|
||||
lxc_docker_host_ssh_port: 22
|
||||
lxc_docker_host_ssh_sources:
|
||||
- "{{ homelab_lan_cidr }}"
|
||||
- "{{ openvpn_network_cidr }}"
|
||||
|
||||
# Node exporter: скрейп разрешён только с хоста мониторинга.
|
||||
lxc_docker_host_allow_node_exporter: true
|
||||
lxc_docker_host_node_exporter_port: 9100
|
||||
lxc_docker_host_monitoring_host: "{{ homelab_monitoring_host_ip | default('192.168.1.30') }}"
|
||||
|
||||
# Дополнительные порты сервиса. Формат:
|
||||
# - port: "6060"
|
||||
# proto: tcp # необязательно, по умолчанию tcp
|
||||
# comment: "Grimmory" # необязательно
|
||||
# sources:
|
||||
# - "{{ homelab_lan_cidr }}"
|
||||
# - "{{ openvpn_network_cidr }}"
|
||||
lxc_docker_host_ufw_service_rules: []
|
||||
|
||||
# Включить ufw с политикой deny incoming. Правила выше применяются ДО включения,
|
||||
# чтобы не потерять SSH.
|
||||
lxc_docker_host_ufw_enable: true
|
||||
lxc_docker_host_ufw_policy: deny
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
# Зависимостей у роли нет: подготовка хоста и раскладка стека независимы
|
||||
# и подключаются в нужном порядке из плейбука.
|
||||
dependencies: []
|
||||
@@ -0,0 +1,113 @@
|
||||
---
|
||||
- name: Install Docker runtime packages
|
||||
ansible.builtin.apt:
|
||||
name: "{{ lxc_docker_host_all_packages }}"
|
||||
state: present
|
||||
update_cache: "{{ lxc_docker_host_update_cache }}"
|
||||
vars:
|
||||
lxc_docker_host_all_packages: >-
|
||||
{{ (lxc_docker_host_packages
|
||||
+ lxc_docker_host_extra_packages
|
||||
+ (['docker-compose'] if lxc_docker_host_install_compose_package else [])
|
||||
+ (['prometheus-node-exporter'] if lxc_docker_host_install_node_exporter else [])
|
||||
+ (['ufw'] if (lxc_docker_host_manage_ufw and lxc_docker_host_install_ufw) else []))
|
||||
| unique | sort }}
|
||||
|
||||
- name: Check the FUSE device
|
||||
ansible.builtin.stat:
|
||||
path: "{{ lxc_docker_host_fuse_device }}"
|
||||
register: lxc_docker_host_fuse
|
||||
when: lxc_docker_host_require_fuse
|
||||
|
||||
- name: Require the FUSE device
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- lxc_docker_host_fuse.stat.exists
|
||||
- lxc_docker_host_fuse.stat.ischr
|
||||
fail_msg: >-
|
||||
{{ lxc_docker_host_fuse_device }} отсутствует или не является символьным
|
||||
устройством. Добавьте в /etc/pve/lxc/<vmid>.conf на узле PVE:
|
||||
"lxc.cgroup2.devices.allow: c 10:229 rwm" и
|
||||
"lxc.mount.entry: /dev/fuse dev/fuse none bind,create=file",
|
||||
затем перезапустите контейнер.
|
||||
when: lxc_docker_host_require_fuse
|
||||
|
||||
- name: Ensure the Docker config directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ lxc_docker_host_daemon_config_path | dirname }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Configure the Docker storage driver for the unprivileged LXC
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ lxc_docker_host_daemon_config_path }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: "{{ lxc_docker_host_daemon_config | to_nice_json(indent=2) }}\n"
|
||||
register: lxc_docker_host_daemon_config_result
|
||||
|
||||
- name: Ensure the Docker service is enabled and running
|
||||
ansible.builtin.systemd:
|
||||
name: docker
|
||||
enabled: true
|
||||
state: "{{ 'restarted' if lxc_docker_host_daemon_config_result.changed else 'started' }}"
|
||||
|
||||
# --- UFW -------------------------------------------------------------------
|
||||
# Порядок важен: сначала разрешающие правила, только потом включение политики
|
||||
# deny incoming — иначе SSH обрывается прямо во время прогона.
|
||||
|
||||
- name: Allow SSH from the trusted networks
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
port: "{{ lxc_docker_host_ssh_port | string }}"
|
||||
proto: tcp
|
||||
src: "{{ item }}"
|
||||
loop: "{{ lxc_docker_host_ssh_sources }}"
|
||||
when: lxc_docker_host_manage_ufw
|
||||
|
||||
- name: Allow the declared service ports
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
port: "{{ item.0.port | string }}"
|
||||
proto: "{{ item.0.proto | default('tcp') }}"
|
||||
src: "{{ item.1 }}"
|
||||
comment: "{{ item.0.comment | default(omit) }}"
|
||||
loop: "{{ lxc_docker_host_ufw_service_rules | subelements('sources') }}"
|
||||
loop_control:
|
||||
label: "{{ item.0.port }}/{{ item.0.proto | default('tcp') }} from {{ item.1 }}"
|
||||
when: lxc_docker_host_manage_ufw
|
||||
|
||||
- name: Allow Node Exporter from the monitoring host
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
port: "{{ lxc_docker_host_node_exporter_port | string }}"
|
||||
proto: tcp
|
||||
src: "{{ lxc_docker_host_monitoring_host }}"
|
||||
when:
|
||||
- lxc_docker_host_manage_ufw
|
||||
- lxc_docker_host_allow_node_exporter
|
||||
|
||||
- name: Enable the restrictive firewall
|
||||
community.general.ufw:
|
||||
state: enabled
|
||||
policy: "{{ lxc_docker_host_ufw_policy }}"
|
||||
direction: incoming
|
||||
when:
|
||||
- lxc_docker_host_manage_ufw
|
||||
- lxc_docker_host_ufw_enable
|
||||
|
||||
# --- Verification ----------------------------------------------------------
|
||||
- name: Verify the effective Docker storage driver
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- docker
|
||||
- info
|
||||
- --format
|
||||
- "{% raw %}{{.Driver}}{% endraw %}"
|
||||
register: lxc_docker_host_driver
|
||||
changed_when: false
|
||||
failed_when: lxc_docker_host_driver.stdout != lxc_docker_host_storage_driver
|
||||
when: lxc_docker_host_verify_storage_driver
|
||||
Reference in New Issue
Block a user