189 lines
6.9 KiB
YAML
189 lines
6.9 KiB
YAML
---
|
|
# playbooks/docker/swarm_baseline.yml
|
|
# Idempotent Ubuntu/Docker Swarm node baseline enforcement.
|
|
#
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# PURPOSE: Ongoing drift enforcement — safe to run any time, safe to schedule.
|
|
# Does NOT upgrade packages. Does NOT reboot.
|
|
# For rolling OS updates: use playbooks/docker/swarm_update.yml
|
|
# For cross-node consistency audit: use playbooks/docker/swarm_audit.yml
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
#
|
|
# What this enforces (all idempotent):
|
|
# 0. Identity: Operational user, SSH key, passwordless sudo, docker group
|
|
# 1. Packages: Required packages present (docker-ce, nfs-common, curl, htop)
|
|
# 2. Storage: Swap disabled (swapoff -a + fstab commented)
|
|
# 3. Sysctl: vm.swappiness=0, bridge netfilter, ip_forward (Docker requirements)
|
|
# 4. Docker: /etc/docker/daemon.json with log rotation
|
|
#
|
|
# Usage:
|
|
# # All swarm nodes:
|
|
# ansible-playbook -i inventory/hosts.ini playbooks/docker/swarm_baseline.yml
|
|
#
|
|
# # Single node:
|
|
# ansible-playbook -i inventory/hosts.ini playbooks/docker/swarm_baseline.yml --limit swarm-manager-1
|
|
#
|
|
# # Dry-run:
|
|
# ansible-playbook -i inventory/hosts.ini playbooks/docker/swarm_baseline.yml --check --diff
|
|
#
|
|
# # Target a specific section only:
|
|
# ansible-playbook -i inventory/hosts.ini playbooks/docker/swarm_baseline.yml --tags sysctl
|
|
# ansible-playbook -i inventory/hosts.ini playbooks/docker/swarm_baseline.yml --tags docker
|
|
|
|
- name: Swarm node baseline enforcement
|
|
hosts: swarm_hosts
|
|
become: true
|
|
|
|
vars:
|
|
lab_user: "{{ lab_ansible_user | default('chester') }}"
|
|
controller_ssh_pubkey_candidates:
|
|
- "{{ lookup('env', 'HOME') }}/.ssh/id_ed25519_homelab.pub"
|
|
- "{{ lookup('env', 'HOME') }}/.ssh/id_ed25519.pub"
|
|
|
|
handlers:
|
|
- name: Restart Docker
|
|
ansible.builtin.service:
|
|
name: docker
|
|
state: restarted
|
|
|
|
tasks:
|
|
- name: "0. Identity: ensure user '{{ lab_user }}' is configured"
|
|
tags: [identity, baseline]
|
|
block:
|
|
- name: "Ensure group '{{ lab_user }}' exists"
|
|
ansible.builtin.group:
|
|
name: "{{ lab_user }}"
|
|
state: present
|
|
|
|
- name: "Ensure user '{{ lab_user }}' exists with sudo and docker access"
|
|
ansible.builtin.user:
|
|
name: "{{ lab_user }}"
|
|
group: "{{ lab_user }}"
|
|
groups:
|
|
- sudo
|
|
- docker
|
|
append: true
|
|
shell: /bin/bash
|
|
password: '!'
|
|
password_lock: true
|
|
|
|
- name: Locate SSH public key on control machine
|
|
ansible.builtin.set_fact:
|
|
controller_ssh_pubkey_path: >-
|
|
{{ lookup('ansible.builtin.first_found', {'files': controller_ssh_pubkey_candidates, 'skip': true}) }}
|
|
delegate_to: localhost
|
|
become: false
|
|
|
|
- name: Fail early if SSH public key is missing
|
|
ansible.builtin.fail:
|
|
msg: >-
|
|
SSH public key not found on the control machine.
|
|
Checked: {{ controller_ssh_pubkey_candidates | join(', ') }}
|
|
Generate one with: ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519
|
|
when: controller_ssh_pubkey_path | default('') | length == 0
|
|
|
|
- name: "Deploy SSH key to {{ lab_user }}"
|
|
ansible.posix.authorized_key:
|
|
user: "{{ lab_user }}"
|
|
state: present
|
|
key: "{{ lookup('file', controller_ssh_pubkey_path) }}"
|
|
|
|
- name: "Grant '{{ lab_user }}' passwordless sudo"
|
|
ansible.builtin.copy:
|
|
dest: "/etc/sudoers.d/{{ lab_user }}"
|
|
content: "{{ lab_user }} ALL=(ALL) NOPASSWD: ALL\n"
|
|
mode: '0440'
|
|
owner: root
|
|
group: root
|
|
validate: '/usr/sbin/visudo -cf %s'
|
|
|
|
- name: "1. Packages: ensure required packages are present"
|
|
tags: [packages, baseline]
|
|
block:
|
|
- name: Update apt cache
|
|
ansible.builtin.apt:
|
|
update_cache: true
|
|
cache_valid_time: 3600
|
|
|
|
- name: Ensure required packages present
|
|
ansible.builtin.apt:
|
|
name:
|
|
- docker-ce
|
|
- docker-ce-cli
|
|
- containerd.io
|
|
- nfs-common
|
|
- curl
|
|
- htop
|
|
- ca-certificates
|
|
state: present
|
|
|
|
- name: "2. Storage: disable swap"
|
|
tags: [storage, baseline]
|
|
block:
|
|
- name: Disable swap immediately
|
|
ansible.builtin.command: swapoff -a
|
|
when: ansible_swaptotal_mb > 0
|
|
changed_when: ansible_swaptotal_mb > 0
|
|
|
|
- name: Comment out swap entries in /etc/fstab
|
|
ansible.builtin.replace:
|
|
path: /etc/fstab
|
|
regexp: '^([^#].*\s+swap\s+.*)$'
|
|
replace: '# \1'
|
|
|
|
- name: "3. Sysctl: apply Docker Swarm networking parameters"
|
|
tags: [sysctl, baseline]
|
|
block:
|
|
- name: Ensure br_netfilter module is loaded
|
|
community.general.modprobe:
|
|
name: br_netfilter
|
|
state: present
|
|
|
|
- name: Persist br_netfilter module load at boot
|
|
ansible.builtin.copy:
|
|
dest: /etc/modules-load.d/br_netfilter.conf
|
|
content: "br_netfilter\n"
|
|
owner: root
|
|
group: root
|
|
mode: '0644'
|
|
|
|
- name: Apply and persist sysctl parameters for swarm
|
|
ansible.posix.sysctl:
|
|
name: "{{ item.key }}"
|
|
value: "{{ item.value }}"
|
|
sysctl_file: /etc/sysctl.d/90-swarm.conf
|
|
state: present
|
|
reload: true
|
|
loop:
|
|
- { key: vm.swappiness, value: "0" }
|
|
- { key: net.bridge.bridge-nf-call-iptables, value: "1" }
|
|
- { key: net.bridge.bridge-nf-call-ip6tables, value: "1" }
|
|
- { key: net.ipv4.ip_forward, value: "1" }
|
|
|
|
- name: "4. Docker: daemon configuration and log rotation"
|
|
tags: [docker, baseline]
|
|
block:
|
|
- name: Ensure /etc/docker directory exists
|
|
ansible.builtin.file:
|
|
path: /etc/docker
|
|
state: directory
|
|
owner: root
|
|
group: root
|
|
mode: '0755'
|
|
|
|
- name: Deploy Docker daemon.json with log rotation
|
|
ansible.builtin.copy:
|
|
dest: /etc/docker/daemon.json
|
|
owner: root
|
|
group: root
|
|
mode: '0644'
|
|
content: |
|
|
{
|
|
"log-driver": "json-file",
|
|
"log-opts": {
|
|
"max-size": "10m",
|
|
"max-file": "3"
|
|
}
|
|
}
|
|
notify: Restart Docker
|