Files
FabledSteward/steward/ansible/bundled/maintenance/swarm_prune/swarm_prune.yml
T
bvandeusenandClaude Opus 5.5 b2f4dde288
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 1m42s
CI / integration (push) Successful in 1m47s
CI / publish (push) Successful in 1m10s
fix(ansible): the swarm prune never runs a docker prune that touches images; it lives in its own folder (#6399)
The first applied run lost VDNT-Docker03 29 images (10.4 GB) before its
script ran. Its `docker system prune` removes dangling images, and Docker
counts an image with no tag as dangling: every image a swarm task pulled by
digest. Docker03 was left holding only its own tasks' images, as under the
old prune. Nothing went down, since a running container's image can't be
removed.

- Step 1 now prunes containers, networks, build cache and (prune_volumes)
  volumes one by one. Every image decision is left to the script, which
  keeps the images services are pinned to.
- A test guards against any image-pruning command in the playbook.
- The playbook moves to maintenance/swarm_prune/, with its script in that
  folder's files/, so a helper's name can't collide with another
  playbook's (the operator's direction for playbooks with helpers).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 11:22:41 -04:00

52 lines
2.4 KiB
YAML

---
# description: Reclaim disk on swarm nodes but keep every image a swarm service is pinned to, so each node can take over the others' services without pulling.
# steward:category: maintenance
# steward:confirm: true
# Run on the swarm's nodes; each must be a manager (only a manager can list
# services). On each node:
# 1. Stopped containers, unused networks and build cache are pruned
# (+ prune_volumes=true: unused anonymous volumes). No image is: Docker
# calls an untagged image dangling, and swarm pulls by digest, untagged.
# 2. files/swarm_prune.py: removes every image no container uses and no
# swarm service is pinned to, wherever that service's task runs. A
# node keeps the images for the services running elsewhere, so it can
# start them at once when the other node fails or is drained.
# A node that can't list the swarm's services removes no images and the run
# fails, rather than pruning blind. Check mode lists what would go.
- name: Swarm prune
hosts: all
gather_facts: false
become: true
vars:
prune_volumes: false
tasks:
# Never `docker system prune` or `docker image prune`: Docker counts an
# image with no tag as dangling, and every image a swarm task pulled by
# digest has none. Images are left to the script below.
- name: Prune stopped containers, unused networks and build cache
ansible.builtin.command:
argv: "{{ item }}"
loop: >-
{{ [['docker', 'container', 'prune', '-f'],
['docker', 'network', 'prune', '-f'],
['docker', 'builder', 'prune', '-f']]
+ ([['docker', 'volume', 'prune', '-f']] if prune_volumes | bool else []) }}
register: cleanup
changed_when: "'Total reclaimed space: 0B' not in cleanup.stdout and cleanup.stdout | trim | length > 0"
- name: Remove images no container uses and no swarm service is pinned to
ansible.builtin.script:
cmd: "swarm_prune.py{{ ' --dry-run' if ansible_check_mode else '' }}"
executable: python3
check_mode: false
register: image_prune
changed_when: >-
not ansible_check_mode and (image_prune.stdout | from_json).removed | default([]) | length > 0
- name: Report what went
ansible.builtin.debug:
msg: >-
{{ (cleanup.results | default([]) | map(attribute='stdout', default='')
| select('search', 'reclaimed') | list)
+ [image_prune.stdout | from_json] }}