Ansible ff@home aufsetzen

Software

  • Debian 12 (bookworm) oder Ubuntu 22.04
  • python3 und ansible

    ***** Es empfiehlt sich, eine python virtuelle Umgebung zu verwenden *****
    Die folgenden Pakete mit Root-Rechten installieren, sofern noch nicht geschehen:

    sudo apt install python3-pip python3-venv

Die weiteren Befehle alle als normaler User ausführen.

Im Basisverzeichnis (bei mir /datadisk) die Testumgebung herunterladen mit

git clone https:/...  

Für ein neues Projekt, welches noch nicht von einem git geklont werden kann, ein leeres Verzeichnis anlegen mit

mkdir ffhome  

Dann in das neue Verzeichnis ffhome wechseln und das lokale git Repository initialisieren mit

git init 

Wenn das Verzeichnis geklont wurde, den Schritt “git init” überspringen.
Nun im ffhome Verzeichnis (oder in geklonten Verzeichnis) (bei mir /datadisk/ffhome) die python virtuelle Umgebung einrichten:

python3 -m venv --prompt ffhome .venv    

Hierdurch wird in ffhome ein Unterverzeichnis .venv angelegt.

Die virtuelle Umgebung wird in /datadisk/ffhome/.venv eingerichtet und kann aktiviert werden mit

source .venv/bin/activate     

Dadurch ändert sich der prompt:

(ffhome) 18:16:41[frankb@berglap /datadisk/ffhome ]

Jetzt kann ansible in der ffhome Umgebung installiert werden:

pip3 install ansible

Damit ist ansible nur in der virtuellen Umgebung installiert und ausführbar.

Zum späteren Verlassen der Umgebung: deactivate oder Terminal Fenster schliessen

18:11:20[frankb@berglap /datadisk/ffhome 0] Die Verzeichnistruktur der Testumgebung

$ tree -L 4 /datadisk/ffhome    

    /datadisk/ffhome
    ├── ansible.cfg
    ├── inventory
    │   ├── hosts.yaml
    │   └── host_vars
    |       ├── bergdesk
    │       │   ├── vars
    │       │   └── vault
    │       ├── berghofen
    │       │   ├── vars
    │       │   └── vault
    │       ├── berglap
    │       │   ├── vars
    │       │   └── vault
    │       └── luna
    │           ├── vars
    │           └── vault
    ├── playbooks
    │   ├── resources
    │   │   └── host
    │   │       ├── tincbergdesk.gz
    │   │       └── tincberghofen.gz
    │   ├── templates
    │   │   ├── tinc-up.lan.j2
    │   │   └── tinc-up.wan.j2
    │   ├── tincbuild.yaml
    │   └── update.yaml
    

Bei den … sind Zeilen der Übersichtlichkeit halber weggelassen.

Weiter sind auch die versteckten Verzeichnisse .venv und evtl .git nicht aufgeführt.

Die verwendete ansible.cfg:

[defaults]
inventory=./inventory/
roles_path=./roles
playbook_dir=./playbooks/
interpreter_python=auto_silent
#vault_password_file=~/.vault-password
log_path=/tmp/ansible_ffhome.log
cow_selection=random
nocows=True
stdout_callback=yaml
display_args_to_stdout=True

[privilege_escalation]
become=True

[ssh_connection]
pipelining=True

Datei mit den beteiligten Hosts inventory/hosts.yaml

---
all:
  vars:
    ansible_port: 24
    ansible_user: frankb
    ansible_become: true

apus:
  hosts:
    berghofen:
      ansible_host: 192.168.178.51
      ansible_user: fb
      ansible_become_password: "{{ berghofen_password }}"

desktops:
  hosts:
    bergdesk:
      ansible_host: 192.168.178.201
      ansible_become_pass: '{{ bergdesk_password }}'

    berglap:
      ansible_host: 192.168.178.52
      ansible_become_pass: '{{ berglap_password }}'

    luna:
      ansible_host: 192.168.178.224
      ansible_become_pass: '{{ luna_password }}'

altlast:
  hosts:
    hoerde:
      ansible_host: 193.43.220.136
      ansible_become: true
      ansible_become_method: su

supernodes:
  hosts:
    31.172.33.20:
      ansible_port: 22
    snng-dus01.ffdo.de:
      ansible_port: 22
    snng-dtm01.ffdo.de:
      ansible_port: 22

Die Gruppen apus und desktops enthalten die testhosts, die Gruppen altlast und supernodes sind nicht komplett einbezogen.

Im Verzeichnis inventory/host_vars sind Variablen für die einzelnen hosts, u.a. die passwords, in vars unverschlüsselt, in vault aes256 geschützt. Weiter sind Parameter für tinc enthalten, nur in vars, unverschlüsselt. Beispielhaft für berghofen

$ cat berghofen/vars

---
berghofen_password: "{{ vault_berghofen_password }}"
tinc_bindto: 192.168.178.51
wan_broadcast_ip: 193.43.220.191
wan_ip: 193.43.220.162/27

lan_broadcast_ip: 192.168.34.255
lan_ip: 192.168.34.1/24

$ cat berghofen/vault

$ANSIBLE_VAULT;1.2;AES256;xx
35656536383233636434636533613830303439316263636436363932333636626462616461636537
3838626266396332363236643361626134393238636133640a646333333866643161356333626564
32373735343033633666353763376230646137663639373438393537663031643562376365396337
3161646534666236350a303366373433373833373066353030363766616166666361376637393464
30613139313661643932373239333865616338653132613530393161656466326561633537383535
3631356664643139383037636565346630643036353364333866

In der vault Datei (hier Klartext)

---
vault_berghofen_password: hier das echte PW eintragen

dann die vault Datei verschlüsseln mit

$ ansible-vault encrypt vault --vault-id xxxxx@prompt

anzeigen kann man die Datei mit

$ ansible-vault view vault   

und wieder entschlüsseln mit

$ ansible-vault decrypt vault

Es gibt z.Zt. zwei playbooks: update.yaml und tincbuild.yaml

$ cat playbooks/update.yaml

---
# name: update yaml
- hosts: [desktops,apus,altlast]

  tasks:
    - name: Testausgabe
      debug: msg="Hallo von {{ ansible_hostname }} Ansible managed!"

    - name: df -h Aufruf
      command: df -h /
      changed_when: false
      register: df_cmd

    - debug:
        msg: '{{df_cmd.stdout_lines}} {{ansible_distribution }}'

    - name: ping meine hosts
      ansible.builtin.ping:
      changed_when: false

#   - name: Warte auf enter Taste
#     ansible.builtin.pause:

    - name: Ist flatpak installiert
      stat:
        path: /usr/bin/flatpak
      register: flatpak_file
      when: ansible_os_family == "Debian"

    - name: Update flatpak Pakete falls snap vh
      command: /usr/bin/flatpak update
      when: ansible_os_family == "Debian" and flatpak_file.stat.exists

    - name: Ist snap installiert
      stat:
        path: /usr/bin/snap
      register: snap_file
      when: ansible_os_family == "Debian"

    - name: Update snap Pakete falls snap vh
      command: /usr/bin/snap refresh
      when: ansible_os_family == "Debian" and snap_file.stat.exists


    - name: apt update mit upgrade und autoremove
      ansible.builtin.apt:
        update_cache: yes
        cache_valid_time: 3600
        autoremove: yes
        upgrade: 'yes'
      when:  ansible_os_family == "Debian"

    - stat:
        path: /var/run/needrestart
      register: needrestart_file

    - name: reboot falls erforderlich
      ansible.builtin.reboot:
      when:
        - ansible_os_family == "Debian" and
         ( needrestart_file.stat.exists or
           rebootreq_file.stat.exists
         )

    - name: playbook hier beenden bei ! debian
      meta: end_play
      when: ansible_os_family != "Debian"

$ cat playbooks/tincbuild.yaml

---
# name: tincbuild yaml
- hosts: [desktops, apus, altlast]
  vars:
    TINC_VERSION: 1.1pre18
    tinc_dev: /dev/net/tun
    tinc_mode: switch
    tinc_adr_family: ipv4
    tinc_max_timeout: 30

    tinc_port:
      lan: 10001
      wan: 661


  tasks:

#   - name: Show facts available on the system
#     ansible.builtin.debug:
#       var: ansible_facts


# das folgende klappt leider nicht {{tinc_port.{{ item }}}} syntaxfehler
#  geschachtelte variablen gehen nicht.
#   - name: willi
#     debug: msg="var {{ tinc_port.{{lan}}}}"

    - name: Testausgabe
      debug: msg="Hallo von {{ ansible_hostname }} Ansible managed!"

    - name: df -h Aufruf
      command: df -h /
      changed_when: false
      register: df_cmd

    - debug:
        msg: '{{df_cmd.stdout_lines}} {{ansible_distribution }}'

#    - name: ping meine hosts
#      ansible.builtin.ping:
#      changed_when: false

#   - name: Warte auf enter Taste
#     ansible.builtin.pause:


    - name: playbook hier beenden bei ! debian
      meta: end_play
      when: ansible_os_family != "Debian"


    - name: Ist tinc Konfigurationsverzeichnis vorhanden
      stat:
        path: /etc/tinc
      register: tinckonf

    - name: Gibt es Sicherung von tinc Konfiguration
      # ueberlebt keinen reboot
      stat:
        path: /tmp/tinc{{ ansible_hostname }}.gz
      register: tincgz

    - name: Konfiguration packen wenn noetig
      community.general.archive:
        path: /etc/tinc
        dest: /tmp/tinc{{ ansible_hostname }}.gz
      when: tinckonf.stat.exists

    - name: Gesicherte Konfiguration auf Controlnode kopieren
      ansible.builtin.fetch:
        src: /tmp/tinc{{ ansible_hostname }}.gz
        dest: resources/host/
        flat: true
      when: tinckonf.stat.exists

      #    - name: Gesicherte Konfiguration auf targetnode loeschen
      #      file:
      #        name: /tmp/tinc{{ ansible_hostname }}.gz
      #        state: absent
      #      when: tincgz.stat.exists

    - name: Abhängigkeiten fuer tinc installieren
      apt:
        pkg: "{{ item }}"
        state: present
      with_items:
        - build-essential
        - libncurses-dev
        - libreadline-dev
        - pkg-config
        - zlib1g-dev
        - liblzo2-dev
        - libssl-dev
        - texinfo

    - name: create directory for tinc
      file:
        name: /opt/tinc
        state: directory

    - name: Download tinc source
      get_url:
        url: "https://www.tinc-vpn.org/packages/tinc-{{TINC_VERSION}}.tar.gz"
        dest: /opt/tinc/tinc-{{TINC_VERSION}}.tar.gz
      register: gettinc

    - name: tinc-Quellen entpacken
      unarchive:
        src: /opt/tinc/tinc-{{TINC_VERSION}}.tar.gz
        dest: /usr/src
        remote_src: true
      when:
        - gettinc.changed

    - name: Pruefen tinc programm vh
      stat:
        path: /usr/sbin/tinc
      register: tinc_bin

    - name: tinc kompilieren und installieren
      shell: "cd /usr/src/tinc-{{TINC_VERSION}}
        &&    ./configure --prefix=/usr --sysconfdir=/etc --runstatedir=/run
              --localstatedir=/var --with-systemd
        &&    make
        &&    make install"
      when:
        - tinc_bin.stat.exists == False or gettinc.changed

    - name: Gibt es schon eine (alte) tinc Konfiguration
      stat:
        path: /etc/tinc
      register: tinc_etc

    - name: tinc Konfigurationsreste beseitigen
      file:
        path: /etc/tinc
        state: absent
      when:
        - tinc_etc.stat.exists

    - name: tinc lan/wan vorkonfigurieren in mehreren Schritten
      shell: "tinc --net={{ item }} init {{ansible_hostname}}"
      with_items:
        - lan
        - wan
#     when:
#       - tinc_bin.stat.exists == False or gettinc.changed

    - name: 2048er priv und pub keys löschen
      shell: "rm /etc/tinc/{{ item }}/*.priv
        &&    rm /etc/tinc/{{ item }}/hosts/*"
      with_items:
        - lan
        - wan
#     when:
#       - tinc_bin.stat.exists == False or gettinc.changed

    - name: 4096er keys generieren
      shell: "tinc --net={{ item }} -b generate-keys 4096"
      with_items:
        - lan
        - wan
#     when:
#       - tinc_bin.stat.exists == False or gettinc.changed

    - name: tinc.conf einrichten
      shell: "tinc --net={{ item }} set Device {{tinc_dev}}
        &&    tinc --net={{ item }} set Mode {{tinc_mode}}
        &&    tinc --net={{ item }} set AddressFamily {{tinc_adr_family}}
        &&    tinc --net={{ item }} set MaxTimeout {{tinc_max_timeout}}
        &&    tinc --net={{ item }} set BindToAddress {{tinc_bindto}}"
      with_items:
        - lan
        - wan
#     when:
#       - tinc_bin.stat.exists == False or gettinc.changed

    - name: lan/wan ports setzen
      # das geht leider nicht in loop, da Variablen nicht geschachtelt
      shell: "tinc --net=lan set Port {{ tinc_port.lan }}
        &&    tinc --net=wan set Port {{ tinc_port.wan }}"

    - name: tinc-up anpassen
      ansible.builtin.template:
        src: templates/tinc-up.{{ item }}.j2
        dest: /etc/tinc/{{ item }}/tinc-up
      with_items:
        - lan
        - wan
#     when:
#       - tinc_bin.stat.exists == False or gettinc.changed

    - meta: end_play

Für tinc-up werden folgende templates verwendet

$ cat playbooks/templates/tinc-up.lan.j2

#!/bin/sh

ip addr add {{ lan_ip }} brd {{ lan_broadcast_ip }} dev $INTERFACE
ip link set $INTERFACE mtu 1504 up

$ cat playbooks/templates/tinc-up.wan.j2

#!/bin/sh

ip addr add {{ wan_ip }} brd {{ wan_broadcast_ip }} dev $INTERFACE
ip link set $INTERFACE mtu 1504 up

Aufruf der beiden playbooks mit

$ ansible-playbook -b  playbooks/update.yaml -i inventory/hosts.yaml --ask-vault-pass 
$ ansible-playbook -b  playbooks/tincbuild.yaml -i inventory/hosts.yaml --ask-vault-pass

Gekürzte Ausgabe:

(ffhome) 20:55:59[frankb@berglap /datadisk/ffhome 4] ansible-playbook -b  playbooks/update.yaml -i inventory/hosts.yaml --ask-vault-pass 
Vault password: 

PLAY [desktops,apus,altlast] ***************************************************************
TASK [Gathering Facts] *********************************************************************
[WARNING]: Platform linux on host berglap is using the discovered Python interpreter at
/usr/bin/python3.10, but future installation of another Python interpreter could change the
meaning of that path. See https://docs.ansible.com/ansible-
core/2.17/reference_appendices/interpreter_discovery.html for more information.
ok: [berglap]
[WARNING]: Platform linux on host bergdesk is using the discovered Python interpreter at
/usr/bin/python3.11, but future installation of another Python interpreter could change the
meaning of that path. See https://docs.ansible.com/ansible-
core/2.17/reference_appendices/interpreter_discovery.html for more information.
ok: [bergdesk]
fatal: [luna]: UNREACHABLE! => changed=false 
  msg: 'Failed to connect to the host via ssh: ssh: connect to host 192.168.178.224 port 24: No route to host'
  unreachable: true
[WARNING]: Platform linux on host berghofen is using the discovered Python interpreter at
/usr/bin/python3.11, but future installation of another Python interpreter could change the
meaning of that path. See https://docs.ansible.com/ansible-
core/2.17/reference_appendices/interpreter_discovery.html for more information.
ok: [berghofen]
[WARNING]: Platform freebsd on host hoerde is using the discovered Python interpreter at
/usr/local/bin/python3.9, but future installation of another Python interpreter could
change the meaning of that path. See https://docs.ansible.com/ansible-
core/2.17/reference_appendices/interpreter_discovery.html for more information.
ok: [hoerde]

TASK [Testausgabe] *************************************************************************
ok: [bergdesk] => 
  msg: Hallo von bergdesk Ansible managed!
ok: [berglap] => 
  msg: Hallo von berglap Ansible managed!
ok: [berghofen] => 
  msg: Hallo von berghofen Ansible managed!
ok: [hoerde] => 
  msg: Hallo von hoerde Ansible managed!

TASK [df -h Aufruf] ************************************************************************
ok: [berglap]
ok: [bergdesk]
ok: [berghofen]
ok: [hoerde]

TASK [debug] *******************************************************************************
ok: [bergdesk] => 
  msg: '[''Dateisystem    Größe Benutzt Verf. Verw% Eingehängt auf'', ''/dev/sdb1       439G     98G  319G   24% /''] Debian'
ok: [berglap] => 
  msg: '[''Dateisystem             Größe Benutzt Verf. Verw% Eingehängt auf'', ''/dev/mapper/system-root  444G    298G  124G   71% /''] Ubuntu'
ok: [berghofen] => 
  msg: '[''Dateisystem    Größe Benutzt Verf. Verw% Eingehängt auf'', ''/dev/sda6        18G    5,1G   12G   30% /''] Debian'
ok: [hoerde] => 
  msg: '[''Filesystem                       Size    Used   Avail Capacity  Mounted on'', ''s3pool25/jail/hoerde.ffdo.net    3.9G    891M    3.0G    22%    /''] FreeBSD'

TASK [ping meine hosts] ********************************************************************
ok: [berglap]
ok: [bergdesk]
ok: [berghofen]
ok: [hoerde]

...

PLAY RECAP *********************************************************************************
bergdesk                   : ok=6    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
berghofen                  : ok=6    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
berglap                    : ok=6    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
hoerde                     : ok=5    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   
luna                       : ok=0    changed=0    unreachable=1    failed=0    skipped=0    rescued=0    ignored=0