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>
52 lines
2.4 KiB
YAML
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] }}
|