10.4 Handlers και οι 3 διανομές 🐧
Το πρόβλημα
Το πιο συχνό πρόβλημα με handlers σε multi-distro περιβάλλον είναι ότι τα services έχουν διαφορετικά ονόματα:
┌─────────────────────────────────────────────────────────┐
│ Service │ Debian/Ubuntu │ RedHat/Fedora │ openSUSE │
├─────────────┼───────────────┼───────────────┼───────────┤
│ SSH │ ssh │ sshd │ sshd │
│ Cron │ cron │ crond │ cron │
│ Nginx │ nginx │ nginx │ nginx │
│ Apache │ apache2 │ httpd │ apache2 │
│ MySQL │ mysql │ mysqld │ mysql │
│ PostgreSQL │ postgresql │ postgresql │ postgresql│
│ Redis │ redis │ redis │ redis │
└─────────────┴───────────────┴───────────────┴───────────┘
Λύση 1: group_vars Variables (Βέλτιστη πρακτική ✅)
Ορίζουμε τα ονόματα των services στα group_vars:
# inventory/group_vars/debian_based.yml
---
service_ssh: ssh
service_cron: cron
service_apache: apache2
service_mysql: mysql
# inventory/group_vars/redhat_based.yml
---
service_ssh: sshd
service_cron: crond
service_apache: httpd
service_mysql: mysqld
# inventory/group_vars/opensuse_based.yml
---
service_ssh: sshd
service_cron: cron
service_apache: apache2
service_mysql: mysql
# Χρήση στο playbook — ένας handler για όλες τις distros!
handlers:
- name: Restart SSH
ansible.builtin.service:
name: "{{ service_ssh }}" # ← από group_vars
state: restarted
- name: Restart cron
ansible.builtin.service:
name: "{{ service_cron }}" # ← από group_vars
state: restarted
- name: Restart Apache
ansible.builtin.service:
name: "{{ service_apache }}" # ← από group_vars
state: restarted
Λύση 2: Inline Conditional (για απλές περιπτώσεις)
{% raw %}
handlers:
# SSH — διαφορετικό όνομα σε Debian
- name: Restart SSH
ansible.builtin.service:
name: >-
{{ 'ssh'
if ansible_facts['os_family'] == 'Debian'
else 'sshd' }}
state: restarted
# Apache — διαφορετικό όνομα σε RedHat
- name: Restart Apache
ansible.builtin.service:
name: >-
{{ 'apache2'
if ansible_facts['os_family'] != 'RedHat'
else 'httpd' }}
state: restarted
# Cron — διαφορετικό όνομα σε RedHat
- name: Restart cron
ansible.builtin.service:
name: >-
{{ 'cron'
if ansible_facts['os_family'] == 'Debian'
else 'crond' }}
state: restarted
Λύση 3: Ξεχωριστοί Handlers ανά Distro
handlers:
# ── SSH handlers ──────────────────────────
- name: Restart SSH Debian
ansible.builtin.service:
name: ssh
state: restarted
when: ansible_facts['os_family'] == "Debian"
- name: Restart SSH RedHat
ansible.builtin.service:
name: sshd
state: restarted
when: ansible_facts['os_family'] == "RedHat"
- name: Restart SSH openSUSE
ansible.builtin.service:
name: sshd
state: restarted
when: ansible_facts['os_family'] == "Suse"
# Tasks που τους καλούν
tasks:
- name: SSH config
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?MaxAuthTries'
line: 'MaxAuthTries 3'
notify:
- Restart SSH Debian
- Restart SSH RedHat
- Restart SSH openSUSE
# ↑ Μόνο ο σωστός θα τρέξει λόγω when
📝 Ποια λύση να διαλέξεις:
Λύση 1 (group_vars) → βέλτιστη πρακτική ✅ καθαρό, επαναχρησιμοποιήσιμο Λύση 2 (inline) → για απλές δυαδικές επιλογές Λύση 3 (ξεχωριστοί) → για πολύπλοκη λογική ανά distro
Πλήρες παράδειγμα — Multi-distro Handlers
---
# multi-distro-handlers.yml
# Handlers που δουλεύουν και στις 3 διανομές
- name: Multi-distro Service Configuration
hosts: all
become: true
gather_facts: true
vars:
ssh_port: 2022
configure_firewall: true
configure_fail2ban: true
tasks:
# ══════════════════════════════════════════
# SSH CONFIGURATION
# ══════════════════════════════════════════
- name: SSH - αλλαγή port
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?Port '
line: "Port {{ ssh_port }}"
backup: true
notify: Restart SSH
- name: SSH - απενεργοποίηση root login
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PermitRootLogin'
line: 'PermitRootLogin no'
notify: Restart SSH
- name: SSH - μόνο key auth
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PasswordAuthentication'
line: 'PasswordAuthentication no'
notify: Restart SSH
# ══════════════════════════════════════════
# FIREWALL CONFIGURATION
# ══════════════════════════════════════════
# ── ferm (Debian) ─────────────────────────
- name: Εγκατάσταση ferm (Debian)
ansible.builtin.apt:
name: ferm
state: present
when:
- configure_firewall
- ansible_facts['os_family'] == "Debian"
- name: Ρύθμιση ferm (Debian)
ansible.builtin.copy:
dest: /etc/ferm/ferm.conf
mode: '0640'
backup: true
content: |
table filter {
chain INPUT {
policy DROP;
mod state state (ESTABLISHED RELATED) ACCEPT;
interface lo ACCEPT;
proto icmp ACCEPT;
proto tcp dport {{ ssh_port }} ACCEPT;
proto tcp dport (80 443) ACCEPT;
}
chain OUTPUT { policy ACCEPT; }
chain FORWARD { policy DROP; }
}
when:
- configure_firewall
- ansible_facts['os_family'] == "Debian"
notify: Restart ferm
# ── firewalld (RedHat/openSUSE) ───────────
- name: Εγκατάσταση firewalld (RedHat/openSUSE)
ansible.builtin.package:
name: firewalld
state: present
when:
- configure_firewall
- ansible_facts['os_family'] in ["RedHat", "Suse"]
- name: Εκκίνηση firewalld
ansible.builtin.service:
name: firewalld
state: started
enabled: true
when:
- configure_firewall
- ansible_facts['os_family'] in ["RedHat", "Suse"]
- name: Άνοιγμα SSH port (firewalld)
ansible.posix.firewalld:
port: "{{ ssh_port }}/tcp"
permanent: true
state: enabled
immediate: true
when:
- configure_firewall
- ansible_facts['os_family'] in ["RedHat", "Suse"]
notify: Restart firewalld
- name: Άνοιγμα HTTP (firewalld)
ansible.posix.firewalld:
service: http
permanent: true
state: enabled
immediate: true
when:
- configure_firewall
- ansible_facts['os_family'] in ["RedHat", "Suse"]
notify: Restart firewalld
# ══════════════════════════════════════════
# FAIL2BAN CONFIGURATION
# ══════════════════════════════════════════
- name: Εγκατάσταση fail2ban
ansible.builtin.package:
name: fail2ban
state: present
when: configure_fail2ban
- name: Ρύθμιση fail2ban
ansible.builtin.copy:
dest: /etc/fail2ban/jail.local
mode: '0644'
backup: true
content: |
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
backend = systemd
[sshd]
enabled = true
port = {{ ssh_port }}
logpath = %(sshd_log)s
maxretry = 3
when: configure_fail2ban
notify: Restart fail2ban
# ══════════════════════════════════════════
# FLUSH & VERIFY
# ══════════════════════════════════════════
- name: Εφαρμογή pending handlers
ansible.builtin.meta: flush_handlers
- name: Έλεγχος SSH service
ansible.builtin.service_facts:
- name: SSH status
ansible.builtin.debug:
msg: >
SSH ({{ service_ssh }}):
{{ ansible_facts.services[service_ssh + '.service'].state
| default('unknown') }}
- name: Fail2ban status
ansible.builtin.debug:
msg: >
fail2ban:
{{ ansible_facts.services['fail2ban.service'].state
| default('unknown') }}
when: configure_fail2ban
# ══════════════════════════════════════════
# HANDLERS
# ══════════════════════════════════════════
handlers:
# ── SSH ───────────────────────────────────
- name: Restart SSH
ansible.builtin.service:
name: "{{ service_ssh }}" # ← από group_vars!
state: restarted
ignore_errors: "{{ ansible_check_mode }}"
# ── ferm ──────────────────────────────────
- name: Restart ferm
ansible.builtin.service:
name: ferm
state: restarted
when: ansible_facts['os_family'] == "Debian"
ignore_errors: "{{ ansible_check_mode }}"
# ── firewalld ─────────────────────────────
- name: Restart firewalld
ansible.builtin.service:
name: firewalld
state: restarted
when: ansible_facts['os_family'] in ["RedHat", "Suse"]
ignore_errors: "{{ ansible_check_mode }}"
# ── fail2ban ──────────────────────────────
- name: Restart fail2ban
ansible.builtin.service:
name: fail2ban
state: restarted
ignore_errors: "{{ ansible_check_mode }}"
# Syntax check
ansible-playbook playbooks/multi-distro-handlers.yml \
--syntax-check
# Dry run
ansible-playbook playbooks/multi-distro-handlers.yml \
--check --diff \
--limit nextcloud
# Εκτέλεση
ansible-playbook playbooks/multi-distro-handlers.yml \
--limit nextcloud -v
Σύνοψη 10.4
Handlers και 3 Διανομές
├── Πρόβλημα: διαφορετικά service names
│ ├── ssh vs sshd
│ ├── cron vs crond
│ └── httpd vs apache2
│
├── Λύσεις:
│ ├── group_vars variables ✅ (βέλτιστο)
│ │ └── service_ssh, service_cron κλπ
│ ├── Inline conditional
│ │ └── {{ 'ssh' if Debian else 'sshd' }}
│ └── Ξεχωριστοί handlers με when
│
└── Βέλτιστη πρακτική:
├── group_vars/debian_based.yml → service_ssh: ssh
├── group_vars/redhat_based.yml → service_ssh: sshd
├── group_vars/opensuse_based.yml→ service_ssh: sshd
└── handler: name: "{{ service_ssh }}"