diff --git a/ansible/docs/edge-host.md b/ansible/docs/edge-host.md index 5c946cfc..7fd93251 100644 --- a/ansible/docs/edge-host.md +++ b/ansible/docs/edge-host.md @@ -164,7 +164,9 @@ role keeps logs off local flash and monitors wear: - **Docker logs to journald** (`docker-daemon.json`, `log-driver: journald`) instead of unbounded json files under `/var/lib/docker/containers`. Both compose services inherit it; `docker logs` still works. -- **Grafana Alloy ships the journal to Loki** (`edge-alloy.service`). Its +- **Grafana Alloy ships the journal to Loki** (`edge-alloy.service`, which + runs the upstream `grafana/alloy` image with `/run/log/journal` mounted + read-only; Arch's `grafana-alloy` package has no `loki.source.journal`). Its WAL/positions sit on `/run/alloy` (tmpfs), so the agent writes nothing to the eMMC either. Net effect: no log writes hit local flash — the trade-off is that logs are RAM-only and lost on reboot unless already shipped. diff --git a/ansible/roles/edge-log-shipper/defaults/main.yml b/ansible/roles/edge-log-shipper/defaults/main.yml index c6885a57..1debfcf0 100644 --- a/ansible/roles/edge-log-shipper/defaults/main.yml +++ b/ansible/roles/edge-log-shipper/defaults/main.yml @@ -7,7 +7,9 @@ edge_alloy_config_file: /etc/alloy/edge.alloy edge_alloy_http_port: 12345 -edge_alloy_bin: /usr/bin/grafana-alloy # Arch `grafana-alloy` pkg names the binary this, not `alloy` +# Upstream image: Arch's grafana-alloy 1.21 is built without +# loki.source.journal. Renovate-tracked (customManagers in renovate.json). +edge_alloy_image: grafana/alloy:v1.21.3@sha256:8ab871206bd2d8f89277f53abffee5f452a293980788d52f85fa183a87a505a4 edge_loki_push_url: "https://loki.renner.uno/loki/api/v1/push" edge_loki_username: edge-1 edge_alloy_labels: diff --git a/ansible/roles/edge-log-shipper/tasks/main.yml b/ansible/roles/edge-log-shipper/tasks/main.yml index 5ea98f3b..3d4c973e 100644 --- a/ansible/roles/edge-log-shipper/tasks/main.yml +++ b/ansible/roles/edge-log-shipper/tasks/main.yml @@ -6,15 +6,18 @@ # directly, so one journal source covers everything. Alloy s storage/WAL sits # on /run (tmpfs) so it does not write to flash either. -- name: Install grafana-alloy +# Alloy runs from the upstream image (see edge-alloy.service); the Arch package +# lacks journal support. +- name: Remove the grafana-alloy package ansible.builtin.package: name: grafana-alloy - state: present + state: absent - name: Create alloy system group ansible.builtin.group: name: alloy system: true + register: edge_alloy_group - name: Create alloy system user ansible.builtin.user: @@ -24,6 +27,25 @@ system: true create_home: false shell: /usr/bin/nologin + register: edge_alloy_user + +- name: Look up the systemd-journal group + ansible.builtin.getent: + database: group + key: systemd-journal + +- name: Set the container identity + ansible.builtin.set_fact: + edge_alloy_uid: "{{ edge_alloy_user.uid }}" + edge_alloy_gid: "{{ edge_alloy_group.gid }}" + edge_alloy_journal_gid: "{{ ansible_facts.getent_group['systemd-journal'][1] }}" + +- name: Pull the alloy image + ansible.builtin.command: + argv: [docker, pull, "{{ edge_alloy_image }}"] + register: edge_alloy_pull + changed_when: "'Downloaded newer image' in edge_alloy_pull.stdout" + notify: Restart alloy - name: Create alloy config directory ansible.builtin.file: diff --git a/ansible/roles/edge-log-shipper/templates/edge-alloy.service.j2 b/ansible/roles/edge-log-shipper/templates/edge-alloy.service.j2 index 1eb79aa3..94ee051d 100644 --- a/ansible/roles/edge-log-shipper/templates/edge-alloy.service.j2 +++ b/ansible/roles/edge-log-shipper/templates/edge-alloy.service.j2 @@ -1,32 +1,35 @@ [Unit] -Description=Grafana Alloy (edge-1 log shipper -> Loki) +Description=Grafana Alloy (journal -> Loki) Documentation=https://grafana.com/docs/alloy/ -After=network-online.target +After=docker.service network-online.target +Requires=docker.service Wants=network-online.target [Service] Type=simple -User=alloy -Group=alloy -# Read the (volatile) systemd journal. -SupplementaryGroups=systemd-journal -# WAL + positions on tmpfs so the agent never writes to the eMMC. %t is the -# runtime dir root (/run); RuntimeDirectory creates /run/alloy owned by us. -RuntimeDirectory=alloy -ExecStart={{ edge_alloy_bin }} run \ +# WAL + positions on tmpfs so the agent never writes to the eMMC. +ExecStartPre=/usr/bin/install -d -o alloy -g alloy -m 0750 /run/alloy +ExecStartPre=-/usr/bin/docker rm -f edge-alloy +# Attached run with --log-driver none: the container's output reaches the +# journal once, through this unit. +ExecStart=/usr/bin/docker run --rm --name edge-alloy \ + --log-driver none \ + --network host \ + --user {{ edge_alloy_uid }}:{{ edge_alloy_gid }} \ + --group-add {{ edge_alloy_journal_gid }} \ + --read-only --tmpfs /tmp \ + --cap-drop ALL --security-opt no-new-privileges \ + -v /run/log/journal:/run/log/journal:ro \ + -v /etc/machine-id:/etc/machine-id:ro \ + -v {{ edge_alloy_config_file }}:/etc/alloy/config.alloy:ro \ + -v /run/alloy:/var/lib/alloy \ + {{ edge_alloy_image }} run \ --server.http.listen-addr=127.0.0.1:{{ edge_alloy_http_port }} \ - --storage.path=%t/alloy \ - {{ edge_alloy_config_file }} -ExecReload=/bin/kill -HUP $MAINPID -Restart=on-failure + --storage.path=/var/lib/alloy \ + /etc/alloy/config.alloy +ExecStop=/usr/bin/docker stop edge-alloy +Restart=always RestartSec=10s -LimitNOFILE=65536 - -# Hardening (bare metal, no LXC namespace limits here). -NoNewPrivileges=yes -ProtectSystem=strict -ProtectHome=yes -PrivateTmp=yes [Install] WantedBy=multi-user.target