mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
fix(apps): swarm-specific deploy fixes (matomo, nextcloud, prometheus, dashboard)
matomo: force-restart the swarm service after patching config.ini.php DB creds (the compose-up handler is a no-op in swarm), extracted into 10_resolve_host_cid. nextcloud: gate the redis PONG wait on the engine being local. prometheus: config 0444 (swarm config objects are root) and gate the host-monitor binds plus privileged cadvisor/node-exporter to compose only. dashboard: probe the public icon URL in swarm. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
8000dbe947
commit
9f43364c00
16 files changed
+136
-74
No files matched your search
@@ -67,7 +67,11 @@ def resolve_simpleicons_base(simpleicons_value, web_protocol="https"):
|
||||
|
||||
|
||||
def add_simpleicon_source(
|
||||
cards, simpleicons_value, web_protocol="https", public_url_base=None
|
||||
cards,
|
||||
simpleicons_value,
|
||||
web_protocol="https",
|
||||
public_url_base=None,
|
||||
probe_public=False,
|
||||
):
|
||||
"""
|
||||
For each card in portfolio_cards, check if an icon exists in the simpleicons server.
|
||||
@@ -80,6 +84,10 @@ def add_simpleicon_source(
|
||||
:param simpleicons_value: Fully rendered URL/domain used for the HEAD reachability check
|
||||
:param web_protocol: Protocol to use (https or http) when resolving a bare domain
|
||||
:param public_url_base: Optional separate public base URL written into icon.source
|
||||
:param probe_public: Probe `public_url_base` instead of the sync URL. The sync
|
||||
URL is published on the swarm node, not on the Ansible controller that
|
||||
runs this filter, so per-card probes from the controller never reach it;
|
||||
the public URL goes through the front proxy the controller can resolve.
|
||||
:return: New list of cards with icon.source set when the icon is reachable
|
||||
"""
|
||||
probe_base = resolve_simpleicons_base(simpleicons_value, web_protocol)
|
||||
@@ -88,6 +96,8 @@ def add_simpleicon_source(
|
||||
if public_url_base
|
||||
else probe_base
|
||||
)
|
||||
if probe_public and public_url_base:
|
||||
probe_base = rewrite_base
|
||||
|
||||
enhanced = []
|
||||
for card in cards:
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
- name: "Load Dashboard Brand logos"
|
||||
set_fact:
|
||||
portfolio_cards: "{{ portfolio_cards | add_simpleicon_source(DASHBOARD_SIMPLEICONS_SYNC_URL_BASE, public_url_base=DASHBOARD_SIMPLEICONS_URL_BASE) }}"
|
||||
portfolio_cards: "{{ portfolio_cards | add_simpleicon_source(DASHBOARD_SIMPLEICONS_SYNC_URL_BASE, public_url_base=DASHBOARD_SIMPLEICONS_URL_BASE, probe_public=(DEPLOYMENT_MODE == 'swarm')) }}"
|
||||
when: DASHBOARD_SIMPLEICONS_ENABLED | bool
|
||||
changed_when: false
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
|
||||
- name: Reload docker cards once Simple Icons is reachable
|
||||
set_fact:
|
||||
portfolio_cards: "{{ portfolio_cards | add_simpleicon_source(DASHBOARD_SIMPLEICONS_SYNC_URL_BASE, public_url_base=DASHBOARD_SIMPLEICONS_URL_BASE) }}"
|
||||
portfolio_cards: "{{ portfolio_cards | add_simpleicon_source(DASHBOARD_SIMPLEICONS_SYNC_URL_BASE, public_url_base=DASHBOARD_SIMPLEICONS_URL_BASE, probe_public=(DEPLOYMENT_MODE
|
||||
== 'swarm')) }}"
|
||||
changed_when: false
|
||||
|
||||
- name: Regroup docker cards after Simple Icons refresh
|
||||
|
||||
@@ -60,6 +60,7 @@ redis:
|
||||
backup:
|
||||
disabled: true
|
||||
enabled: false
|
||||
shared: "{{ 'svc-db-redis' in group_names }}"
|
||||
# nocheck: playwright-service-flag — verified by web-svc-css provider spec
|
||||
css:
|
||||
enabled: "{{ 'web-svc-css' in group_names }}"
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
shared: true
|
||||
mariadb:
|
||||
shared: true
|
||||
redis:
|
||||
shared: true
|
||||
prometheus:
|
||||
enabled: true
|
||||
shared: true
|
||||
@@ -32,6 +34,8 @@
|
||||
shared: false
|
||||
mariadb:
|
||||
shared: false
|
||||
redis:
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -10,68 +10,8 @@
|
||||
- name: flush docker service
|
||||
meta: flush_handlers
|
||||
|
||||
- name: "Matomo install | Manager-side path (swarm)"
|
||||
when: DEPLOYMENT_MODE == 'swarm'
|
||||
block:
|
||||
- name: "Matomo install | Wait for matomo task to converge"
|
||||
ansible.builtin.shell: |
|
||||
container service ps {{ lookup('container_service', application_id, MATOMO_SERVICE) | quote }} \
|
||||
--filter desired-state=running \
|
||||
--format '{{ "{{.CurrentState}}" }}' \
|
||||
| head -1 | grep -qE '^Running'
|
||||
register: matomo_running_swarm
|
||||
changed_when: false
|
||||
retries: 600
|
||||
delay: 2
|
||||
until: matomo_running_swarm.rc == 0
|
||||
|
||||
- name: "Matomo install | Resolve hosting node"
|
||||
ansible.builtin.shell: |
|
||||
container service ps {{ lookup('container_service', application_id, MATOMO_SERVICE) | quote }} \
|
||||
--filter desired-state=running \
|
||||
--format '{{ "{{.Node}}" }}' | head -1
|
||||
register: matomo_swarm_node_query
|
||||
changed_when: false
|
||||
|
||||
- name: "Matomo install | Set MATOMO_HOST_NODE fact"
|
||||
ansible.builtin.set_fact:
|
||||
MATOMO_HOST_NODE: "{{ matomo_swarm_node_query.stdout | trim }}"
|
||||
|
||||
- name: "Matomo install | Manager-side path (compose)"
|
||||
when: DEPLOYMENT_MODE != 'swarm'
|
||||
block:
|
||||
- name: "Matomo install | Wait for matomo container to be running"
|
||||
ansible.builtin.shell: >
|
||||
container inspect -f '{{ "{{" }}.State.Running{{ "}}" }}'
|
||||
{{ lookup('container_address', application_id, MATOMO_SERVICE) }}
|
||||
register: matomo_running_compose
|
||||
changed_when: false
|
||||
retries: 150
|
||||
delay: 2
|
||||
until: >
|
||||
matomo_running_compose.rc == 0 and (matomo_running_compose.stdout | trim) == 'true'
|
||||
|
||||
- name: "Matomo install | Set MATOMO_HOST_NODE fact"
|
||||
ansible.builtin.set_fact:
|
||||
MATOMO_HOST_NODE: "{{ inventory_hostname }}"
|
||||
|
||||
- name: "Matomo install | Resolve local container ID on hosting node"
|
||||
ansible.builtin.shell: |
|
||||
{% if DEPLOYMENT_MODE == 'swarm' %}
|
||||
container ps \
|
||||
--filter "label=com.docker.swarm.service.name={{ lookup('container_service', application_id, MATOMO_SERVICE) }}" \
|
||||
--filter status=running \
|
||||
--format '{{ "{{.ID}}" }}' | head -1
|
||||
{% else %}
|
||||
echo {{ MATOMO_NAME | quote }}
|
||||
{% endif %}
|
||||
register: matomo_local_cid_query
|
||||
changed_when: false
|
||||
delegate_to: "{{ MATOMO_HOST_NODE }}"
|
||||
|
||||
- name: "Matomo install | Set MATOMO_LOCAL_CID fact"
|
||||
ansible.builtin.set_fact:
|
||||
MATOMO_LOCAL_CID: "{{ matomo_local_cid_query.stdout | trim }}"
|
||||
- name: "Matomo install | Resolve hosting node + local container ID"
|
||||
include_tasks: 10_resolve_host_cid.yml
|
||||
|
||||
- name: "Matomo install | Run downstream install path"
|
||||
include_tasks: 04_install_path.yml
|
||||
@@ -30,6 +30,22 @@
|
||||
- name: flush docker service
|
||||
meta: flush_handlers
|
||||
|
||||
# Exception: in swarm the 'compose up' handler runs `docker stack deploy`, which is a
|
||||
# no-op when only config.ini.php on the NFS bind changed; the running container keeps
|
||||
# the pre-patch creds in memory and loops on "Database access denied". Force a service
|
||||
# restart so the patched config is re-read, then re-resolve the (possibly new) task.
|
||||
- name: "Matomo install | Swarm: restart matomo so patched config is re-read"
|
||||
when: DEPLOYMENT_MODE == 'swarm'
|
||||
block:
|
||||
- name: "Matomo install | Swarm: force-restart matomo service"
|
||||
ansible.builtin.command: >-
|
||||
container service update --force
|
||||
{{ lookup('container_service', application_id, MATOMO_SERVICE) }}
|
||||
changed_when: true
|
||||
|
||||
- name: "Matomo install | Swarm: re-resolve hosting node + container ID after restart"
|
||||
include_tasks: 10_resolve_host_cid.yml
|
||||
|
||||
- name: "Matomo install | Wait for Matomo container to be healthy"
|
||||
ansible.builtin.shell: >
|
||||
container inspect -f '{{ "{{" }}.State.Health.Status{{ "}}" }}'
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
- name: "Matomo resolve | Swarm host node + CID"
|
||||
when: DEPLOYMENT_MODE == 'swarm'
|
||||
block:
|
||||
- name: "Matomo resolve | Wait for matomo task to converge"
|
||||
ansible.builtin.shell: |
|
||||
container service ps {{ lookup('container_service', application_id, MATOMO_SERVICE) | quote }} \
|
||||
--filter desired-state=running \
|
||||
--format '{{ "{{.CurrentState}}" }}' \
|
||||
| head -1 | grep -qE '^Running'
|
||||
register: matomo_running_swarm
|
||||
changed_when: false
|
||||
retries: 600
|
||||
delay: 2
|
||||
until: matomo_running_swarm.rc == 0
|
||||
|
||||
- name: "Matomo resolve | Resolve hosting node"
|
||||
ansible.builtin.shell: |
|
||||
container service ps {{ lookup('container_service', application_id, MATOMO_SERVICE) | quote }} \
|
||||
--filter desired-state=running \
|
||||
--format '{{ "{{.Node}}" }}' | head -1
|
||||
register: matomo_swarm_node_query
|
||||
changed_when: false
|
||||
|
||||
- name: "Matomo resolve | Set MATOMO_HOST_NODE fact"
|
||||
ansible.builtin.set_fact:
|
||||
MATOMO_HOST_NODE: "{{ matomo_swarm_node_query.stdout | trim }}"
|
||||
|
||||
- name: "Matomo resolve | Compose host node"
|
||||
when: DEPLOYMENT_MODE != 'swarm'
|
||||
block:
|
||||
- name: "Matomo resolve | Wait for matomo container to be running"
|
||||
ansible.builtin.shell: >
|
||||
container inspect -f '{{ "{{" }}.State.Running{{ "}}" }}'
|
||||
{{ lookup('container_address', application_id, MATOMO_SERVICE) }}
|
||||
register: matomo_running_compose
|
||||
changed_when: false
|
||||
retries: 150
|
||||
delay: 2
|
||||
until: >
|
||||
matomo_running_compose.rc == 0 and (matomo_running_compose.stdout | trim) == 'true'
|
||||
|
||||
- name: "Matomo resolve | Set MATOMO_HOST_NODE fact"
|
||||
ansible.builtin.set_fact:
|
||||
MATOMO_HOST_NODE: "{{ inventory_hostname }}"
|
||||
|
||||
- name: "Matomo resolve | Resolve local container ID on hosting node"
|
||||
ansible.builtin.shell: |
|
||||
{% if DEPLOYMENT_MODE == 'swarm' %}
|
||||
container ps \
|
||||
--filter "label=com.docker.swarm.service.name={{ lookup('container_service', application_id, MATOMO_SERVICE) }}" \
|
||||
--filter status=running \
|
||||
--format '{{ "{{.ID}}" }}' | head -1
|
||||
{% else %}
|
||||
echo {{ MATOMO_NAME | quote }}
|
||||
{% endif %}
|
||||
register: matomo_local_cid_query
|
||||
changed_when: false
|
||||
delegate_to: "{{ MATOMO_HOST_NODE }}"
|
||||
|
||||
- name: "Matomo resolve | Set MATOMO_LOCAL_CID fact"
|
||||
ansible.builtin.set_fact:
|
||||
MATOMO_LOCAL_CID: "{{ matomo_local_cid_query.stdout | trim }}"
|
||||
@@ -31,6 +31,7 @@ redis:
|
||||
backup:
|
||||
disabled: true
|
||||
enabled: true
|
||||
shared: "{{ 'svc-db-redis' in group_names }}"
|
||||
cpus: "1"
|
||||
mem_reservation: 1g
|
||||
mem_limit: 8g
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
shared: true
|
||||
mariadb:
|
||||
shared: true
|
||||
redis:
|
||||
shared: true
|
||||
matomo:
|
||||
enabled: true
|
||||
shared: true
|
||||
@@ -69,6 +71,8 @@
|
||||
shared: false
|
||||
mariadb:
|
||||
shared: false
|
||||
redis:
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
retries: 60
|
||||
delay: 2
|
||||
until: redis_ping.stdout is search('PONG')
|
||||
changed_when: false
|
||||
when: lookup('engine', 'redis', application_id, 'local')
|
||||
|
||||
- name: Update\Upgrade Nextcloud
|
||||
include_tasks: 03_upgrade.yml
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
- "{{ container_port }}"
|
||||
shm_size: 1g
|
||||
{{ lookup('container_networks') | indent(4) }}
|
||||
{{ lookup('depends_on', {'redis': 'service_healthy'}) }}
|
||||
{% if lookup('engine', 'redis', application_id, 'local') %}{{ lookup('depends_on', {'redis': 'service_healthy'}) }}{% endif %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
@@ -27,8 +27,13 @@ TRUSTED_PROXIES={{ NEXTCLOUD_TRUSTED_PROXIES | join(',') }}
|
||||
OVERWRITECLIURL={{ NEXTCLOUD_URL }}
|
||||
OVERWRITEPROTOCOL={{ NEXTCLOUD_PROTOCOL }}
|
||||
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
REDIS_HOST={{ lookup('engine', 'redis', application_id, 'host') }}
|
||||
REDIS_PORT={{ lookup('engine', 'redis', application_id, 'port') }}
|
||||
{% set _redis_user = lookup('engine', 'redis', application_id, 'username') %}
|
||||
{% if _redis_user | length > 0 %}
|
||||
REDIS_HOST_USER={{ _redis_user }}
|
||||
{% endif %}
|
||||
REDIS_HOST_PASSWORD={{ lookup('engine', 'redis', application_id, 'password') | dotenv_quote }}
|
||||
|
||||
{% if lookup('objstore', application_id, 'enabled') | bool %}
|
||||
OBJECTSTORE_S3_BUCKET= {{ lookup('objstore', application_id, 'bucket') }}
|
||||
@@ -60,7 +65,7 @@ TZ={{ HOST_TIMEZONE }}
|
||||
NEXTCLOUD_URL={{ NEXTCLOUD_URL }}
|
||||
JWT_SECRET_KEY={{ NEXTCLOUD_WHITEBOARD_JWT }}
|
||||
STORAGE_STRATEGY=redis
|
||||
REDIS_URL=redis://redis:6379/0
|
||||
REDIS_URL={{ lookup('engine', 'redis', application_id, 'url') }}
|
||||
CHROMIUM_FLAGS=--headless=new --no-sandbox --disable-gpu --disable-dev-shm-usage --use-gl=swiftshader --disable-software-rasterizer
|
||||
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
|
||||
PUPPETEER_SKIP_DOWNLOAD=true
|
||||
|
||||
@@ -32,6 +32,8 @@ javascript:
|
||||
shared: "{{ lookup('config', 'web-app-prometheus', 'services.sso.enabled') | bool }}"
|
||||
# nocheck: playwright-service-flag — self-provider
|
||||
prometheus:
|
||||
# nocheck: default-placement-manager TSDB on-disk data; node-local in swarm
|
||||
default_placement: manager
|
||||
enabled: true
|
||||
shared: true
|
||||
provides:
|
||||
|
||||
@@ -13,10 +13,13 @@ alertmanager_data:
|
||||
- service: alertmanager
|
||||
target: /alertmanager
|
||||
|
||||
# Swarm materialises config objects as root:root; the prom/* images run as
|
||||
# `nobody`, so 0440 (no "other" read bit) makes them unreadable -> exit 2.
|
||||
# 0444 keeps them read-only while letting the non-root runtime user load them.
|
||||
prometheus_config:
|
||||
type: config
|
||||
source: "{{ [lookup('container', 'web-app-prometheus', 'directories.config'), 'prometheus.yml'] | path_join }}"
|
||||
mode: "0440"
|
||||
mode: "0444"
|
||||
mounts:
|
||||
- service: prometheus
|
||||
target: /etc/prometheus/prometheus.yml
|
||||
@@ -24,7 +27,7 @@ prometheus_config:
|
||||
alert_rules:
|
||||
type: config
|
||||
source: "{{ [lookup('container', 'web-app-prometheus', 'directories.config'), 'alert_rules.yml'] | path_join }}"
|
||||
mode: "0440"
|
||||
mode: "0444"
|
||||
mounts:
|
||||
- service: prometheus
|
||||
target: /etc/prometheus/alert_rules.yml
|
||||
@@ -32,7 +35,7 @@ alert_rules:
|
||||
blackbox_config:
|
||||
type: config
|
||||
source: "{{ [lookup('container', 'web-app-prometheus', 'directories.config'), 'blackbox.yml'] | path_join }}"
|
||||
mode: "0440"
|
||||
mode: "0444"
|
||||
mounts:
|
||||
- service: blackbox-exporter
|
||||
target: /etc/blackbox_exporter/config.yml
|
||||
@@ -40,7 +43,7 @@ blackbox_config:
|
||||
alertmanager_config:
|
||||
type: config
|
||||
source: "{{ [lookup('container', 'web-app-prometheus', 'directories.config'), 'alertmanager.yml'] | path_join }}"
|
||||
mode: "0440"
|
||||
mode: "0444"
|
||||
mounts:
|
||||
- service: alertmanager
|
||||
target: /etc/alertmanager/alertmanager.yml
|
||||
@@ -81,6 +84,10 @@ host_var_lib_docker:
|
||||
- service: cadvisor
|
||||
target: /var/lib/docker
|
||||
|
||||
# /dev/disk does not exist on swarm DinD nodes, so the bind makes the swarm
|
||||
# service Reject ("bind source path does not exist"). cadvisor only uses it to
|
||||
# enrich per-disk stats, which is optional, so gate the mount to compose where
|
||||
# the host path is real.
|
||||
host_dev_disk:
|
||||
type: bind
|
||||
source: "/dev/disk/"
|
||||
@@ -88,6 +95,7 @@ host_dev_disk:
|
||||
mounts:
|
||||
- service: cadvisor
|
||||
target: /dev/disk
|
||||
when: "{{ DEPLOYMENT_MODE != 'swarm' }}"
|
||||
|
||||
host_proc:
|
||||
type: bind
|
||||
|
||||
@@ -39,15 +39,18 @@
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
{{ lookup('container_image', application_id, 'cadvisor') }}
|
||||
{{ lookup('compose_only', 'container_name', CADVISOR_CONTAINER) }}
|
||||
{% if DEPLOYMENT_MODE != 'swarm' %}
|
||||
privileged: true
|
||||
devices:
|
||||
- /dev/kmsg
|
||||
{% endif %}
|
||||
{{ lookup('container_volumes', application_id, 'cadvisor') | indent(4) }}
|
||||
|
||||
node-exporter:
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
{{ lookup('container_image', application_id, 'node-exporter') }}
|
||||
{{ lookup('compose_only', 'container_name', NODE_EXPORTER_CONTAINER) }}
|
||||
{% if DEPLOYMENT_MODE != 'swarm' %}
|
||||
# network_mode: host is required so node-exporter sees the host's real
|
||||
# network interfaces, disk mounts, and process table. Without it, interface
|
||||
# bandwidth metrics reflect the container's virtual stack only and are
|
||||
@@ -55,10 +58,12 @@
|
||||
# and memory metrics. Security trade-off: removes Docker network isolation
|
||||
# for this read-only monitoring container, which is the standard accepted
|
||||
# practice for node-exporter. Port note: node-exporter binds on host port
|
||||
# 9100; ensure nothing else on the host uses port 9100.
|
||||
# 9100; ensure nothing else on the host uses port 9100. Swarm services
|
||||
# cannot honor network_mode/pid host, so this is compose-only.
|
||||
# nocheck: network-mode-host-without-swarm-gate
|
||||
network_mode: host
|
||||
pid: host
|
||||
{% endif %}
|
||||
{{ lookup('container_volumes', application_id, 'node-exporter') | indent(4) }}
|
||||
command:
|
||||
- "--path.procfs=/host/proc"
|
||||
|
||||
Reference in new issue
Block a user