centos:ansible:playbooks1

Dies ist eine alte Version des Dokuments!


Ansible - Playbookbeispiele

Bild: Ansible Logo

Nachdem wir uns bereits eingehend mit den Grundlagen und auch schon das benötigte Programmpaket auf unserer Admin-Workstation zur Orchestrierung installiert haben, werden wir uns nun ein paar Beispile ansehen, wie man sich das Leben mit Ansible-Playbooks leichter gestalten kann.

In den beiden Kapiteln Playbooks und YAML - was ist das? hatten wir uns schon eingehend mit den Hintergrundinformationen zu diesen beiden Themenblöcken beschäftigt, so dass wir uns nun direkt mit unserem ersten Playbook beschäftigen können.

In folgendem Beispiel Nummer fünf wollen wir auf unseren CentOS 8-Hosts den NTP-Deamon chrony installieren und auch entsprechend als Client konfigurieren.

Script anlegen

Das Script legen wir wie auch schon bei den anderen Beispielen zuvor im Verzeichnis ~/ansible an

 $ vim 05_chrony.yml
05_chrony.yml
---
- hosts: centos8
  become: true
  vars:
    sudoers: ansible
    config_file: /etc/chrony.conf
    # chronyd client config-options
    chrony_pool: "server time.dmz.nausch.org iburst"
    chrony_stratumweight: "stratumweight 0"
    chrony_makestep: "makestep 10 3"
 
  tasks:
    - name: Install chrony ntp Deamon
      dnf: 
      #https://docs.ansible.com/ansible/latest/modules/dnf_module.html
        name: chrony
        state: latest
 
    - name: Check if /etc/chrony.orig does exists
      stat:
      #https://docs.ansible.com/ansible/latest/modules/stat_module.html
        path: /etc/chrony.conf.orig
      register: stat_result
 
    - name: Make a copy of /etc/chrony.conf as /etc/chrony.conf.orig  
      copy:
      #https://docs.ansible.com/ansible/latest/modules/copy_module.html
        remote_src: yes
        src: /etc/chrony.conf
        dest: /etc/chrony.conf.orig
      when: stat_result.stat.exists == False
 
    - name: Copy template config-file in place
      template:
      #https://docs.ansible.com/ansible/latest/modules/template_module.html        
        src: templates/CentOS8/chrony-client.conf.j2 
        dest: "{{ config_file }}"
 
    - name: Make sure Chrony is started up
      service: 
      #https://docs.ansible.com/ansible/latest/modules/service_module.html
        name: chronyd 
        state: started 
        enabled: yes
...

Die Konfigurationsdatei unseres chrony-Daemon werden wir im Arbeitsbereich unserer ansible-Umgebung auf dem Admin-Rechner/-Server in einem eigenen Verzeichnis vorhalten. Diese Verzeichnis erstellen wir uns nun noch.

 $ mkdir -p ~/ansible/templates/CentOS8/

Ansible nutzt die Jinja2 Template Engine zum abgleich der verwendeten Variablen in einem Playbook. Wir werden also unsere Konfigurationsdatei entsprechend präparieren und dort ablegen. Als Datei-Extension verwenden wir hier .j2, um dies optisch abzutrennen. Wir könnten auch andere Datei-Extension verwenden, da Ansible selbst nur den Inhalt bzw. die Formatierung der Variablen interprätiert.

 $ vim ~/ansible/templates/CentOS8/chrony-client.conf.j2
~/ansible/templates/CentOS8/chrony-client.conf.j2
# Use public servers from the pool.ntp.org project.
# Please consider joining the pool (http://www.pool.ntp.org/join.html).
{{ chrony_pool }}
 
# Ignore stratum in source selection
{{ chrony_stratumweight }}
 
# Record the rate at which the system clock gains/losses time.
driftfile /var/lib/chrony/drift
 
# Allow the system clock to be stepped in the first three updates
# if its offset is larger than 1 second.
makestep 1.0 3
 
# Enable kernel synchronization of the real-time clock (RTC).
rtcsync
 
# In first three updates step the system clock instead of slew
# if the adjustment is larger than 10 seconds.
{{ chrony_makestep }}
 
# Enable hardware timestamping on all interfaces that support it.
#hwtimestamp *
 
# Increase the minimum number of selectable sources required to adjust
# the system clock.
#minsources 2
 
# Allow NTP client access from local network.
#allow 192.168.0.0/16
 
# Serve time even if not synchronized to a time source.
#local stratum 10
 
# Specify file containing keys for NTP authentication.
keyfile /etc/chrony.keys
 
# Get TAI-UTC offset and leap seconds from the system tz database.
leapsectz right/UTC
 
# Specify directory for log files.
logdir /var/log/chrony
 
# Select which information is logged.
#log measurements statistics tracking

Die drei Konfigurationsoptionen, die wir für unsere chrony-client Konfiguration später setzen und ggf. verändern wollen haben wir hier mit einer Variable belegt:

  • {{ chrony_pool }} : Server von dem bzw. denen wir später die Zeit beziehen wollen.
  • {{ chrony_stratumweight }} : Ignorieren der stratum Bewertung, da wir hier nur einen Zielhost zur Zeit befragen
  • {{ chrony_makestep }} : Definition wie bei den ersten Aktualisierungsschritten zu verfahren ist.

Script Beschreibung

Im Playbook greifen wir auf folgende Ansible-Module zurück:

  • dnf zum Installieren des Paketes
  • stat und copy zum Sichern der originalen Konfigurationsdatei.
  • template zur Konfiguration unseres Daemon
  • service zum (automatischen) Starten (beim Systemstart des Hosts)).

In unserem Playbook werden am Anfang den entsprechenden Variablen ihre werte zugewiesen. Im Anschluss daran werden fünft tasks definiert:

  1. Aufgabe: Installation des chrony NTP-Daemon
  2. Aufgabe: Überprüfen ob von der Konfigurationsdatei, die das RPM-Paket mitbrachte schon eine Sicherungskopie erstellt wurde.
  3. Aufgabe: Sofern bei der Prüfung in Aufgabe 2 noch keine Sicherungskopie erstellt wurde, wird eine Sicherungsopie erstellt.
  4. Aufgabe: Konfigurieren unseres chrony-Daemon
  5. Aufgabe: Starten des chrony-Daemon und aktivieren des automatischen Starts beim Starten des Hosts

Script ausführen

Zum Kopieren der unterschiedlichen Dateien rufen wir nun unser Playbook wie folgt auf:

 $ ansible-playbook -v 05_chrony.yml 

Using /etc/ansible/ansible.cfg as config file
BECOME password: 

PLAY [centos8] *************************************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************************
ok: [www7.dmz.nausch.org]
ok: [www8.dmz.nausch.org]
TASK [Install chrony ntp Deamon] ******************************************************************************************************* changed: [www8.dmz.nausch.org] => {"changed": true, "msg": "", "rc": 0, "results": ["Installed: chrony", "Installed: chrony-3.3-3.el8.x86_64"]}
TASK [Check if /etc/chrony.orig does exists] ******************************************************************************************* ok: [www8.dmz.nausch.org] => {"changed": false, "stat": {"exists": false}}
TASK [Make a copy of /etc/chrony.conf as /etc/chrony.conf.orig] ************************************************************************ changed: [www8.dmz.nausch.org] => {"changed": true, "checksum": "89175e7c294dedf12bd473a952014e2cefd5766d", "dest": "/etc/chrony.conf.orig", "gid": 0, "group": "root", "md5sum": "97078948a9e2c1b99ab3e38d26a3311d", "mode": "0644", "owner": "root", "secontext": "system_u:object_r:etc_t:s0", "size": 1085, "src": "/etc/chrony.conf", "state": "file", "uid": 0}
TASK [Copy template config-file in place] ********************************************************************************************** changed: [www8.dmz.nausch.org] => {"changed": true, "checksum": "37539ecdd11393937e5596894db41a02c6121c5f", "dest": "/etc/chrony.conf", "gid": 0, "group": "root", "md5sum": "adde7eeb1766f7f83bd3fba6cc30ec23", "mode": "0644", "owner": "root", "secontext": "system_u:object_r:etc_t:s0", "size": 1265, "src": "/home/ansible/.ansible/tmp/ansible-tmp-1578323551.4891849-132640554634531/source", "state": "file", "uid": 0}
TASK [Make sure Chrony is started up] ************************************************************************************************** changed: [www8.dmz.nausch.org] => {"changed": true, "enabled": true, "name": "chronyd", "state": "started", "status": {"ActiveEnterTimestampMonotonic": "0", "ActiveExitTimestampMonotonic": "0", "ActiveState": "inactive", "After": "sysinit.target system.slice -.mount systemd-tmpfiles-setup.service sntp.service ntpdate.service tmp.mount systemd-journald.socket ntpd.service basic.target", "AllowIsolate": "no", "AmbientCapabilities": "", "AssertResult": "no", "AssertTimestampMonotonic": "0", "Before": "multi-user.target shutdown.target", "BlockIOAccounting": "no", "BlockIOWeight": "[not set]", "CPUAccounting": "no", "CPUQuotaPerSecUSec": "infinity", "CPUSchedulingPolicy": "0", "CPUSchedulingPriority": "0", "CPUSchedulingResetOnFork": "no", "CPUShares": "[not set]", "CPUUsageNSec": "[not set]", "CPUWeight": "[not set]", "CacheDirectoryMode": "0755", "CanIsolate": "no", "CanReload": "no", "CanStart": "yes", "CanStop": "yes", "CapabilityBoundingSet": "cap_chown cap_dac_override cap_dac_read_search cap_fowner cap_fsetid cap_kill cap_setgid cap_setuid cap_setpcap cap_linux_immutable cap_net_bind_service cap_net_broadcast cap_net_admin cap_net_raw cap_ipc_lock cap_ipc_owner cap_sys_module cap_sys_rawio cap_sys_chroot cap_sys_ptrace cap_sys_pacct cap_sys_admin cap_sys_boot cap_sys_nice cap_sys_resource cap_sys_time cap_sys_tty_config cap_mknod cap_lease cap_audit_write cap_audit_control cap_setfcap cap_mac_override cap_mac_admin cap_syslog cap_wake_alarm cap_block_suspend", "CollectMode": "inactive", "ConditionResult": "no", "ConditionTimestampMonotonic": "0", "ConfigurationDirectoryMode": "0755", "Conflicts": "systemd-timesyncd.service shutdown.target ntpd.service", "ControlPID": "0", "DefaultDependencies": "yes", "Delegate": "no", "Description": "NTP client/server", "DevicePolicy": "auto", "Documentation": "man:chronyd(8) man:chrony.conf(5)", "DynamicUser": "no", "EnvironmentFiles": "/etc/sysconfig/chronyd (ignore_errors=yes)", "ExecMainCode": "0", "ExecMainExitTimestampMonotonic": "0", "ExecMainPID": "0", "ExecMainStartTimestampMonotonic": "0", "ExecMainStatus": "0", "ExecStart": "{ path=/usr/sbin/chronyd ; argv[]=/usr/sbin/chronyd $OPTIONS ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }", "ExecStartPost": "{ path=/usr/libexec/chrony-helper ; argv[]=/usr/libexec/chrony-helper update-daemon ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }", "FailureAction": "none", "FileDescriptorStoreMax": "0", "FragmentPath": "/usr/lib/systemd/system/chronyd.service", "GID": "[not set]", "GuessMainPID": "yes", "IOAccounting": "no", "IOSchedulingClass": "0", "IOSchedulingPriority": "0", "IOWeight": "[not set]", "IPAccounting": "no", "IPEgressBytes": "18446744073709551615", "IPEgressPackets": "18446744073709551615", "IPIngressBytes": "18446744073709551615", "IPIngressPackets": "18446744073709551615", "Id": "chronyd.service", "IgnoreOnIsolate": "no", "IgnoreSIGPIPE": "yes", "InactiveEnterTimestampMonotonic": "0", "InactiveExitTimestampMonotonic": "0", "JobRunningTimeoutUSec": "infinity", "JobTimeoutAction": "none", "JobTimeoutUSec": "infinity", "KeyringMode": "private", "KillMode": "control-group", "KillSignal": "15", "LimitAS": "infinity", "LimitASSoft": "infinity", "LimitCORE": "infinity", "LimitCORESoft": "infinity", "LimitCPU": "infinity", "LimitCPUSoft": "infinity", "LimitDATA": "infinity", "LimitDATASoft": "infinity", "LimitFSIZE": "infinity", "LimitFSIZESoft": "infinity", "LimitLOCKS": "infinity", "LimitLOCKSSoft": "infinity", "LimitMEMLOCK": "16777216", "LimitMEMLOCKSoft": "16777216", "LimitMSGQUEUE": "819200", "LimitMSGQUEUESoft": "819200", "LimitNICE": "0", "LimitNICESoft": "0", "LimitNOFILE": "4096", "LimitNOFILESoft": "1024", "LimitNPROC": "31132", "LimitNPROCSoft": "31132", "LimitRSS": "infinity", "LimitRSSSoft": "infinity", "LimitRTPRIO": "0", "LimitRTPRIOSoft": "0", "LimitRTTIME": "infinity", "LimitRTTIMESoft": "infinity", "LimitSIGPENDING": "31132", "LimitSIGPENDINGSoft": "31132", "LimitSTACK": "infinity", "LimitSTACKSoft": "8388608", "LoadState": "loaded", "LockPersonality": "no", "LogLevelMax": "-1", "LogsDirectoryMode": "0755", "MainPID": "0", "MemoryAccounting": "yes", "MemoryCurrent": "[not set]", "MemoryDenyWriteExecute": "no", "MemoryHigh": "infinity", "MemoryLimit": "infinity", "MemoryLow": "0", "MemoryMax": "infinity", "MemorySwapMax": "infinity", "MountAPIVFS": "no", "MountFlags": "", "NFileDescriptorStore": "0", "NRestarts": "0", "Names": "chronyd.service", "NeedDaemonReload": "no", "Nice": "0", "NoNewPrivileges": "no", "NonBlocking": "no", "NotifyAccess": "none", "OOMScoreAdjust": "0", "OnFailureJobMode": "replace", "PIDFile": "/var/run/chrony/chronyd.pid", "PermissionsStartOnly": "no", "Perpetual": "no", "PrivateDevices": "no", "PrivateMounts": "no", "PrivateNetwork": "no", "PrivateTmp": "yes", "PrivateUsers": "no", "ProtectControlGroups": "no", "ProtectHome": "yes", "ProtectKernelModules": "no", "ProtectKernelTunables": "no", "ProtectSystem": "full", "RefuseManualStart": "no", "RefuseManualStop": "no", "RemainAfterExit": "no", "RemoveIPC": "no", "Requires": "sysinit.target -.mount system.slice", "RequiresMountsFor": "/var/tmp", "Restart": "no", "RestartUSec": "100ms", "RestrictNamespaces": "no", "RestrictRealtime": "no", "Result": "success", "RootDirectoryStartOnly": "no", "RuntimeDirectoryMode": "0755", "RuntimeDirectoryPreserve": "no", "RuntimeMaxUSec": "infinity", "SameProcessGroup": "no", "SecureBits": "0", "SendSIGHUP": "no", "SendSIGKILL": "yes", "Slice": "system.slice", "StandardError": "inherit", "StandardInput": "null", "StandardInputData": "", "StandardOutput": "journal", "StartLimitAction": "none", "StartLimitBurst": "5", "StartLimitIntervalUSec": "10s", "StartupBlockIOWeight": "[not set]", "StartupCPUShares": "[not set]", "StartupCPUWeight": "[not set]", "StartupIOWeight": "[not set]", "StateChangeTimestampMonotonic": "0", "StateDirectoryMode": "0755", "StatusErrno": "0", "StopWhenUnneeded": "no", "SubState": "dead", "SuccessAction": "none", "SyslogFacility": "3", "SyslogLevel": "6", "SyslogLevelPrefix": "yes", "SyslogPriority": "30", "SystemCallErrorNumber": "0", "TTYReset": "no", "TTYVHangup": "no", "TTYVTDisallocate": "no", "TasksAccounting": "yes", "TasksCurrent": "[not set]", "TasksMax": "26213", "TimeoutStartUSec": "1min 30s", "TimeoutStopUSec": "1min 30s", "TimerSlackNSec": "50000", "Transient": "no", "Type": "forking", "UID": "[not set]", "UMask": "0022", "UnitFilePreset": "enabled", "UnitFileState": "enabled", "UtmpMode": "init", "WantedBy": "multi-user.target", "WatchdogTimestampMonotonic": "0", "WatchdogUSec": "0"}}
PLAY RECAP *************************************************************************************************************************************** www8.dmz.nausch.org : ok=6 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Ergebnis

Auf dem Zielhost finden wir nun die Sicherungskopie der originalen chrony.conf aus dem RPM-Paket.

 # ll /etc/chrony.conf*
-rw-r--r--. 1 root root 1265 Jan  6 16:12 /etc/chrony.conf
-rw-r--r--. 1 root root 1085 Apr  4  2018 /etc/chrony.conf.orig

Der Dienst chrony.conf wurde entsprechend gestartet und läuft:

 # systemctl status chronyd.service

chronyd.service - NTP client/server
   Loaded: loaded (/usr/lib/systemd/system/chronyd.service; enabled; vendor preset: enabled)
   Active: active (running) since Mon 2020-01-06 16:12:34 CET; 47s ago
     Docs: man:chronyd(8)
           man:chrony.conf(5)
  Process: 10697 ExecStartPost=/usr/libexec/chrony-helper update-daemon (code=exited, status=0/SUCCESS)
  Process: 10693 ExecStart=/usr/sbin/chronyd $OPTIONS (code=exited, status=0/SUCCESS)
 Main PID: 10695 (chronyd)
    Tasks: 1 (limit: 26213)
   Memory: 1.1M
   CGroup: /system.slice/chronyd.service
           └─10695 /usr/sbin/chronyd

Jan 06 16:12:34 vml000090.dmz.nausch.org systemd[1]: Starting NTP client/server...
Jan 06 16:12:34 vml000090.dmz.nausch.org chronyd[10695]: chronyd version 3.3 starting (+CMDMON +NTP +REFCLOCK +RTC +PRIVDROP +SCFILTER +SIGND +ASYNCDNS +SECHASH +IPV6 +DEBUG)
Jan 06 16:12:34 vml000090.dmz.nausch.org chronyd[10695]: Initial frequency 10.454 ppm
Jan 06 16:12:34 vml000090.dmz.nausch.org chronyd[10695]: Using right/UTC timezone to obtain leap second data

Beim Starten des Servers/Hosts wird der chronyd-Daemon auch automatisch gestartet. Dies können wir wie folgt überprüfen:

 # systemctl is-enabled chronyd.service
enabled

In diesem Konfigurationsbeispiel befassen wir uns nun mit einer Besonderheit in Sachen Zielhosts. Nicht immer hat man auf einem Zielsystem Python zur Verfügung, wie z.B. bei Freifunk-Hardware, die auf gluon und OpenWrt basieren. Aber auch hier gibt es eine Lösungsmöglichkeit, in dem man für diesen speziellen Fall auf das Modul raw zurückgreift.

Script anlegen

Auch dieses Beispiel-Script speichern wir im Verzeichnis ~/ansible ab.

 $ vim 06_change_contact.yml
06_change_contact.yml
---
- hosts: ffmuc_gluon
  gather_facts: False
 
  tasks:
    - name: "Update new contact-address on own ffmuc-nodes"
      #https://docs.ansible.com/ansible/latest/modules/raw_module.html
      raw: uci set gluon-node-info.@owner[0].contact=' Django [BOfH] django@nausch.org | chat -> @django' ; uci commit
...

Script Beschreibung

Dieses Playbook besteht nur aus einer Aufgabe, nämlich dem Ändern der Kontakt-Adresse unserer Freifunk-WLAN-Knoten, die allesamt auf Gluon/OpenWRT basieren. Daher macht es auch keinen Sinn, dass Ansible versuchen wird, die Facts der Zielhosts zu ermitteln, da dort Python nicht zur Verfügung steht, welches für die Ermittlung der systemdaten benötigt wird.

Wir deaktivieren also mit gather_facts: False diese Funktion zu Beginn.

Zum Ausführen der Kommandos auf den Freifunk-Knoten verwenden wird das Ansible Modul raw. Zum Ändern der Kontaktdaten benutzen wir die beiden Befehle:

uci set gluon-node-info.@owner[0].contact='Django [BOfH] django@nausch.org | chat -> @django'
uci commit

Script ausführen

Nun wollen wir unser ersten Playbook ausführen, um auf den Freifunk-Knoten die Kontaktdaten zu aktualisieren; hierzu rufen wir unser Script wie folgt auf:

 $ ansible-playbook -v 06_change_contact.yml

Using /etc/ansible/ansible.cfg as config file
BECOME password: 

PLAY [ffmuc_gluon] *********************************************************************************************************************

TASK [Update new contact-address on own ffmuc-nodes] ***********************************************************************************
changed: [ff_pliening_gbw_ug] => {"changed": true, "rc": 0, "stderr": "Shared connection to 2001:608:a01:102:32b5:c2ff:fe56:62b1 closed.\r\n", "stderr_lines": ["Shared connection to 2001:608:a01:102:32b5:c2ff:fe56:62b1 closed."], "stdout": "", "stdout_lines": []}
changed: [ff_pliening_gbw_egod] => {"changed": true, "rc": 0, "stderr": "Shared connection to 2001:608:a01:102:1ae8:29ff:fea9:22ed closed.\r\n", "stderr_lines": ["Shared connection to 2001:608:a01:102:1ae8:29ff:fea9:22ed closed."], "stdout": "", "stdout_lines": []}
changed: [ff_pliening_gbw_ogod] => {"changed": true, "rc": 0, "stderr": "Shared connection to 2001:608:a01:102:1ae8:29ff:fec0:aaae closed.\r\n", "stderr_lines": ["Shared connection to 2001:608:a01:102:1ae8:29ff:fec0:aaae closed."], "stdout": "", "stdout_lines": []}
changed: [ff_pliening_gbw_dgod] => {"changed": true, "rc": 0, "stderr": "Shared connection to 2001:608:a01:102:1ae8:29ff:fec6:c8eb closed.\r\n", "stderr_lines": ["Shared connection to 2001:608:a01:102:1ae8:29ff:fec6:c8eb closed."], "stdout": "", "stdout_lines": []}
changed: [ff_pliening_gbw_cpod] => {"changed": true, "rc": 0, "stderr": "Shared connection to 2001:608:a01:102:1ae8:29ff:fec6:c8dd closed.\r\n", "stderr_lines": ["Shared connection to 2001:608:a01:102:1ae8:29ff:fec6:c8dd closed."], "stdout": "", "stdout_lines": []}
changed: [ff_pliening_gbw_kvm_ol] => {"changed": true, "rc": 0, "stderr": "Shared connection to 2001:608:a01:102:5054:ff:fe9e:b358 closed.\r\n", "stderr_lines": ["Shared connection to 2001:608:a01:102:5054:ff:fe9e:b358 closed."], "stdout": "", "stdout_lines": []}
PLAY RECAP ***************************************************************************************************************************** ff_pliening_gbw_cpod : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ff_pliening_gbw_dgod : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ff_pliening_gbw_egod : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ff_pliening_gbw_kvm_ol : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ff_pliening_gbw_ogod : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ff_pliening_gbw_ug : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Ergebnis

Alle unsere eigenen definierten Freifunk Knoten haben nun auf der Freifunk München Karte unseren aktualisierte Kontaktdaten.

In diesem Konfigurationsbeispiel wollen wir möglichst einfach und schnell einen Offloader für Freifunk München auf Basis eines Raspberry 4B befassen. Dabei gehen wir auf unterschiedliche Konfigurations-Optionen ein und wollen dennoch die Einstiegshürden für den ungeübteren Ansible und Linux-User möglichst tief ansetzen.

Die detaillierte Beschreibung hierzu ist im Kapitel Bau eines Freifunk-Offloaders auf Basis eines Raspberry 4B zu finden.

Links

Diese Website verwendet Cookies. Durch die Nutzung der Website stimmen Sie dem Speichern von Cookies auf Ihrem Computer zu. Außerdem bestätigen Sie, dass Sie unsere Datenschutzbestimmungen gelesen und verstanden haben. Wenn Sie nicht einverstanden sind, verlassen Sie die Website.Weitere Information
  • centos/ansible/playbooks1.1663164755.txt.gz
  • Zuletzt geändert: 14.09.2022 14:12.
  • von django