Dies ist eine alte Version des Dokuments!
Ansible - Erweiterte Konfigurationsbeispiele
Artikel befindet sich aktuell in der Überarbeitung!
Konfigurationsbeispiel
In folgendem Konfigurationsbeispiel wollen wir uns zu folgendem Szenario eine handelbare Lösung genauer betrachten. Wir gehen dabei von folgendem Einsatzszenario aus:
Aufgabenstellung
Es ist ein Webserver mit Hilfe von Ansible zu erstellen. Am Host soll sich der verantwortliche WEB-Admin ruben anmelden können und der Host soll sich des NTP-Servers im der eigenen Zone bedienen. Dies sind Standardkonfigurationsaufgaben, die auf jedem Server in der betreffenden Sicherheitszonezutreffen. Nach der Installation des Webserver-Daemon apache soll dieser konfiguriert, die initiale Homepage Hello World installiert unde zum Schluß der Webserver gestartet werden.
Lösung
Als erstes zerlegen wir die Aufgabenstellung in einzelne Arbeitspakete und unterteilen diese Pakete in einzelne Aufgaben, als da wären:
- Basiskonfiguration
- Gruppe für den Webentwickler ruben anlegen (unter CentOS ist dabei die Gruppe gleich dem Benutzernamen).
- User(konto) für den Webentwickler ruben anlegen.
- Initiales Passwort für den Webentwickler ruben setzen.
- NTP-Client
- Installation des NTP-clients chrony
- Sichern der originalen chrony-Konfigurationsdatei
- Konfiguration von chrony
- Starten des NTP-Clients/-Daemon und Sicherstellen dass dieser auch bei einem Systemneustart gestartet wird.
- Host-/Aufgabenspezifische Installation: Apache-Webserver
- Installation des http-Daemon
- Sichern der originalen Apache-Konfigurationsdatei
- Konfigurieren des Apache Webservers
- Kopieren der initialen Webseite nach
DOCUMENT_ROOT
des Apache-Webservers - Starten des Webserver/-Daemon und sicherstellen dass dieser auch bei einem Systemneustart gestartet wird.
Um möglichst für die Zukunft flexibel zu sein, werden wir unser Ansible Playbook oder bessere gesagt den darunter liogenden Code, möglichst flexibel halten. Das bedeutet, dass wir sowohl für die Grundkonfiguration (User/Gruppe anlegen, wie installation des Chrony-Daemon) wie auch für die spätere Host-/Aufgabenspezifische Installation des Webservers (Apache) eigene Rollen defionieren werden. So können wir später die Rollen mühelos erweitern, wenn wir z.B. weitere Dienste wie z.B. einen PHP-Interpreter oder die zum Apachen zugehörige TLS-Konfiguration vornehmen werden.
Folgende Struktur soll definiert werden:
site.yml
- beinhaltet alle Site-spezifischen Playbooks, wie z.B.
- Datenbank
db.yml
(spätere erweiterte Konfiguration) - Mailserver
postfix.yml
(spätere erweiterte Konfiguration) - …
- Webserver
web.yml
- sein zugehöriges Playbook
web.yml
bedient sich der Rollen:base
über die die Benutzeranlage erfolgtchrony
mit Hilfe deren die NTP-Client-Konfiguration erlediggt wirdapache
Installation und Konfiguration des Apache Webservers mit Hilfe vontasks
,files
undhandlers
Systemweite Host/Playbook-Definition : site.yml
Zunächst befüllen/editieren wir die Konfigurationsdatei site.yml
, die wir automatisiert bei der Grundkonfiguration von Ansibles Directory Layout erzeugt hatten. Diese Datei wird später alle Definitionen unserer Systemumgebung enthalten, also nicht nur die für den Apache-Webserver, sondern auch für den Datenbank-, Mail- und sonstige Server. Die spezisfischen Playbooks werden dann hier nur noch includiert bzw. importiert.
$ vim ~/ansible/site.yml
- ~/ansible/site.yml
--- # Start des systemweiten Playbooks site.yml - import_playbook: web.yml # Playbook zum Konfigurieren unseres Webservers einbinden ... # Ende unseres systemweiten Playbooks
Webserver Host/Playbook-Definition : web.yml
Für die Konfiguration des Webservers an sich verwenden wir dann die Konfigurationsdatei web.yml
; die wir in der zuvor angelegten systemweiten Konfigurationsdatei site.yml
includiert hatten. Da die Konfiguration auf allen WEB-Hosts erfolgen soll, geben wir beim Parametewr hosts
den Namen der Hostgruppe aus unserer Inventory-Definition an. Bei der Definition der Rollen geben wir die drei roles
an, die wir in unseren zuvor angestellten Überlegungen gewählt hatten.
$ vim ~/ansible/web.yml
- web.yml
--- # Start des Playbooks für den Web-Server - hosts: www roles: - base # Basiskonfiguration (User anlegen) - chrony # Installation und Konfiguration NTP-Client - www # Installation und Konfiguration Apache Webserver ... # Start des Playbooks für den Web-Server
Unser Kopiervorlage common
für die Rollen kopieren wir nun für die gewählten Arbeitspakete (roles), als erstes also für die Rolle base
, bei der wir den verantwortlichen Web-Admin anlegen werden.
$ cd ~/ansible/roles $ cp -avr common/ base/
Unser Playbook-Beispiel 01 passen wir nun an und speichern dieses im Verzeichnis ~/ansible/roles/base/tasks/
unter den Namen main.yml
$ vim ~/ansible/roles/base/tasks/main.yml
- ~/ansible/roles/base/tasks/main.yml
--- # Grundlegende Konfiguration für alle Hosts - name: "***base*** : Gruppe für (WEB-Entwickler) '{{ createuser }}' erstellen" group: # https://docs.ansible.com/ansible/latest/modules/group_module.html name: '{{ createuser }}' gid: '{{ createguid }}' state: present - name: "***base*** : WEB-Admin Nutzerkonto für den User '{{ createuser }}' mit frn zugehörigen UID '{{ createguid }}' anlegen un der Gruppe '{{ createuser }}' zuordnen." user: # https://docs.ansible.com/ansible/latest/modules/user_module.html name: '{{ createuser }}' comment: '{{ createusername }}' uid: '{{ createguid }}' group: '{{ createuser }}' state: present - name: "***base*** : Initiales Passwort für den WEB-Admin '{{ createuser }}' hinterlegen" shell: # https://docs.ansible.com/ansible/latest/modules/shell_module.html cmd: usermod -p $(echo '{{ createpassword }}' | openssl passwd -1 -stdin) {{ createuser }}
Wie wir sehen, beinhaltet diese Datei nur noch die Definition der tasks aber keine Sitespezifischen Variablen, da wir diese in eine separate Date auslagern werden. Wir könnten als jederzeit diese Date auf andere Installationsumgebungen portieren oder an interessierte Adminkollegen weitergeben!
Natürlich benötigen wir nun die Definition der Rolenspezifischen Variablen, die je Rolle hier ~/ansible/roles/base/vars/main.yml
abzulegen sind.
$ vim ~/ansible/roles/base/vars/main.yml
- ~/ansible/roles/base/vars/main.yml
--- # Definition der rollenspezifische Variablen createguid : '1010' # GID/UID des Benutzers00 createuser : 'ruben' # Username createusername: 'Ruben Nausch' # Vollständiger Name createpassword: 'M31nP4p4157d3r4113r83573!' # Initialpasswort (ungecrypted!)
Die nächste Rolle, die wir konfigurieren müssen, ist die für den NTP-Daemon chrony
. Auch hier kopieren wir zunächst das Default-role-template common.
$ cd ~/ansible/roles $ cp -avr common/ chrony/
Auch hier greifen wir wieder auf das bereits bekannte Playbook-Beispiel 05 zurück, passen dieses an und speichern es im Verzeichnis ~/ansible/roles/chrony/tasks/
unter den Namen main.yml
. Bei diesem Konfigurationsbeispiel besteht das playbook aber lediglich aus vier tasks, die wir als eigenständige Dateien auslagern und hier nur includieren werden.
$ vim ~/ansible/roles/chrony/tasks/main.yml
- ~/ansible/roles/chrony/tasks/main.yml
--- # Hauptinstallations-/Konfigurationsdatei für den Dienst chrony - include: install.yml # Installation - include: config-backup.yml # original-Konfig sichern - include: configure.yml # Konfiguration - include: service.yml # Service starten
Die vier tasks hinterlegen wire dann anschließend in der zugehörigen YML-Datei.
$ vim ~/ansible/roles/chrony/tasks/install.yml
- ~/ansible/roles/chrony/tasks/install.yml
--- - name: "***chrony*** : Installation des Deamon '{{ daemon_name }}' (in der aktuellsten Version)" dnf: #https://docs.ansible.com/ansible/latest/modules/dnf_module.html name: '{{ daemon_name }}' state: latest
$ vim ~/ansible/roles/chrony/tasks/config-backup.yml
- ~/ansible/roles/chrony/tasks/config-backup.yml
--- - name: "***chrony*** : 1) Überprüfen ob das Backup der Konfigurationsdatei '{{ config_file }}' bereits existiert" stat: # https://docs.ansible.com/ansible/latest/modules/stat_module.html path: /etc/chrony.conf.orig register: stat_result - name: "***chrony*** : 2) Von der bestehenden originalen Konfigurationsdatei '{{ config_file }}' ein Backup '{{ config_file }}'.orig erstellen" copy: # https://docs.ansible.com/ansible/latest/modules/copy_module.html remote_src: yes src: '{{ config_file }}' dest: /etc/chrony.conf.orig when: stat_result.stat.exists == False
$ vim ~/ansible/roles/chrony/tasks/configure.yml
- ~/ansible/roles/chrony/tasks/configure.yml
--- - name: "***chrony*** : Template Konfigurationsdatei an Ort und Stelle kopieren und Variablen setzen" template: # https://docs.ansible.com/ansible/latest/modules/template_module.html src: templates/chrony-client.conf.j2 dest: "{{ config_file }}"
Das zugehörige Template für die Konfigurationsdatei legen wir im zugehörigen Verzeichnis ~/ansible/roles/chrony/templates
ab.
$ vim ~/ansible/roles/chrony/templates/chrony-client.conf.j2
- ~/ansible/roles/chrony/templates/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
Der task zum Starten des Daemon legen wir im Verzeichnis task
ab.
$ vim ~/ansible/roles/chrony/tasks/service.yml
- ~/ansible/roles/chrony/tasks/service.yml
--- - name: "***chrony*** : Sicherstellen dass der Daemon '{{ daemon_name }}' (beim Systemstart) gestartet wird und läuft" service: # https://docs.ansible.com/ansible/latest/modules/service_module.html name: chronyd state: started enabled: yes
Zu guter letzt benötigen wir auch hier eine zur Rolle gehörige Parameter-Datei mit den Definitionen der Variablen.
$ vim ~/ansible/roles/chrony/vars/main.yml
- ~/ansible/roles/chrony/vars/main.yml
--- # Definition der rollenspezifische Variablen zum Dienst chrony daemon_name : chrony 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"
Alle bisher getroffenen Konfigurationseinstellungen gelten für alle Applikations-Hosts und wir müssen lediglich die beiden Rollen base
und chrony
bei den entsprechenden Playbook-Dateien includieren!
Nun folgt der Webserverspezifische Teil unseres Ansible-Musterkonfiguration eines Webserversmit der Definition der Role www
. Zunächst benötigen wir natürlich auch für diese Rolle die altbekannte Verzeichnisstruktur.
$ cd ~/ansible/roles $ cp -avr common/ www/
Angelehnt an die Rolle chrony
definieren wir auch hier als erstes das playbook mit fünf einzelnen tasks, die wir als eigenständige Dateien auslagern und hier nur includieren werden. In dieses Falle sindes fünf tasks, also einer mehr wie bei der Rolle chrony
, da wir ja hier unsere initialen Webseite nach DOCUMENT_ROOT
des Apache-Webservers noch kopieren wollen.
$ vim ~/ansible/roles/www/tasks/main.yml
- ~/ansible/roles/www/tasks/main.yml
--- # Hauptinstallations-/Konfigurationsdatei für den Apache-Webserver - include: install.yml # Installation - include: config-backup.yml # original-Konfig sichern - include: configure.yml # Konfiguration - include: content.yml # Initiale Webseite befüllen - include: service.yml # Service starten
Da wir das Rad ja bekanntlicher Weise nicht 2x erfinden müssen, greifen wir bei der Konfiguration der einzelnen tasks der Rolle www
auf altbewährtes, nämlich der Konfigurationsvorlage der zuvor erstellten Rolle chrony
zurück. Wir kopieren uns die Datei und verändern diese entsprechend.
$ cp ~/ansible/roles/chrony/tasks/install.yml ~/ansible/roles/www/tasks/install.yml $ vim ~/ansible/roles/www/tasks/install.yml
- ~/ansible/roles/www/tasks/install.yml
--- - name: "***www*** : Installation des Deamon '{{ daemon_name }}' (in der aktuellsten Version)" dnf: #https://docs.ansible.com/ansible/latest/modules/dnf_module.html name: '{{ daemon_name }}' state: latest
Ähnlich verfahren wir mit dem task zum Sichern der originalen Konfigurationsdatei config-backup.yml
sowie zum Konfigurieren des Daemon configure.yml
.
$ cp ~/ansible/roles/chrony/tasks/config-backup.yml ~/ansible/roles/www/tasks/config-backup.yml $ vim ~/ansible/roles/www/tasks/config-backup.yml
- ~/ansible/roles/www/tasks/config-backup.yml
--- - name: "***www*** : 1) Überprüfen ob das Backup der Konfigurationsdatei '{{ config_file }}' bereits existiert" stat: # https://docs.ansible.com/ansible/latest/modules/stat_module.html path: '{{ backup_file }}' register: stat_result - name: "***www*** : 2) Von der bestehenden originalen Konfigurationsdatei '{{ config_file }}' ein Backup '{{ backup_file }}' erstellen" copy: # https://docs.ansible.com/ansible/latest/modules/copy_module.html remote_src: yes src: '{{ config_file }}' dest: '{{ backup_file }}' when: stat_result.stat.exists == False
$ cp ~/ansible/roles/chrony/tasks/configure.yml ~/ansible/roles/www/tasks/configure.yml $ vim ~/ansible/roles/www/tasks/configure.yml
- ~/ansible/roles/www/tasks/configure.yml
--- - name: "***www*** : Template Konfigurationsdatei an Ort und Stelle kopieren und Variablen setzen" template: # https://docs.ansible.com/ansible/latest/modules/template_module.html src: templates/httpd-server.conf.j2 dest: "{{ config_file }}"
Auch hier benötigen wir eine Datei, in der die Variablen für den Task www vorgehalten werden.
$ vim ~/ansible/roles/www/vars/main.yml
- ~/ansible/roles/www/vars/main.yml
--- # Definition der rollenspezifische Variablen zum Apache-Webserver httpd daemon_name : httpd config_file : /etc/httpd/conf/httpd.conf backup_file : /etc/httpd/conf/httpd.conf.orig # httpd-Server config-options httpd_server_admin : "ServerAdmin webmaster@nausch.org" httpd_server_http_header : "ServerTokens Prod\nServerSignature Off\nHeader always unset \"X-Powered-By\"\nHeader unset \"X-Powered-By\"" httpd_server_extendedstatus: "ExtendedStatus On" httpd_server_traceenable : "TraceEnable off"
Zu guter letzt müssen wir natürlich auch noch Sorge Tragen dass der HTTP-Daemon läuft und beim Systemstart auch geladen wird. Auch hier kopieren wir uns der Einfachheit halber das passende Gegenstück aus unserem Erfahrungsschatz.
$ cp ~/ansible/roles/chrony/tasks/service.yml ~/ansible/roles/www/tasks/service.yml $ vim ~/ansible/roles/www/tasks/service.yml
- ~/ansible/roles/www/tasks/service.yml
--- - name: "***www*** : Sicherstellen dass der Daemon '{{ daemon_name }}' (beim Systemstart) gestartet wird und läuft" service: # https://docs.ansible.com/ansible/latest/modules/service_module.html name: httpd state: started enabled: yes - name: "***www*** : Paketfilter anpassen und Port 80 (HTTP) öffnen" firewalld: # https://docs.ansible.com/ansible/latest/modules/firewalld_module.html service: http permanent: yes state: enabled
Wir haben also ein modular aufgebautes Installationsplaybook für unseren Webserver, mit den drei Rollen base, chrony und www und jeweils abgetrennte lokale individuelle Konfigurationsparameter, sowie das Playbook web.yml
und der übergeordneten Site-Konfiguration site.yml
.
/home/ansible/ansible/ ├── filter_plugins ├── inventories │ ├── production │ │ ├── group_vars │ │ ├── hosts.yml │ │ └── host_vars │ └── staging │ ├── group_vars │ ├── hosts.yml │ └── host_vars ├── library ├── module_utils ├── roles │ ├── base │ │ ├── defaults │ │ │ └── main.yml │ │ ├── files │ │ │ └── main.yml │ │ ├── handlers │ │ │ └── main.yml │ │ ├── library │ │ ├── lookup_plugin │ │ ├── meta │ │ │ └── main.yml │ │ ├── module_utils │ │ ├── tasks │ │ │ └── main.yml │ │ ├── templates │ │ │ └── main.yml │ │ └── vars │ │ └── main.yml │ ├── chrony │ │ ├── defaults │ │ │ └── main.yml │ │ ├── files │ │ │ └── main.yml │ │ ├── handlers │ │ │ └── main.yml │ │ ├── library │ │ ├── lookup_plugin │ │ ├── meta │ │ │ └── main.yml │ │ ├── module_utils │ │ ├── tasks │ │ │ ├── config-backup.yml │ │ │ ├── configure.yml │ │ │ ├── install.yml │ │ │ ├── main.yml │ │ │ └── service.yml │ │ ├── templates │ │ │ ├── chrony-client.conf.j2 │ │ │ └── main.yml │ │ └── vars │ │ └── main.yml │ ├── common │ │ ├── defaults │ │ │ └── main.yml │ │ ├── files │ │ │ └── main.yml │ │ ├── handlers │ │ │ └── main.yml │ │ ├── library │ │ ├── lookup_plugin │ │ ├── meta │ │ │ └── main.yml │ │ ├── module_utils │ │ ├── tasks │ │ │ └── main.yml │ │ ├── templates │ │ │ └── main.yml │ │ └── vars │ │ └── main.yml │ └── www │ ├── defaults │ │ └── main.yml │ ├── files │ │ └── main.yml │ ├── handlers │ │ └── main.yml │ ├── library │ ├── lookup_plugin │ ├── meta │ │ └── main.yml │ ├── module_utils │ ├── tasks │ │ ├── config-backup.yml │ │ ├── configure.yml │ │ ├── content.yml │ │ ├── install.yml │ │ ├── main.yml │ │ └── service.yml │ ├── templates │ │ ├── homepage.conf.j2 │ │ ├── httpd-server.conf.j2 │ │ └── main.yml │ └── vars │ └── main.yml ├── site.yml └── web.yml
Abschließend kann man sich nun berechtigter Weise die Frage stellen, warum man nun mehrere Dateien erstellt haben,um den Code und die Variablen dazu, der die Pakete installiert und die Dienste verwaltet, separat zu speichern? Wir sind so sehr leicht in der Lage zum, Beispiel Dienste in mehreren Phasen bereitstellen. In einer ersten Phase können wir so Anwendungen lediglich installieren und konfigurieren und erst in der zweiten Phase dann die Dienste dann starten. Natürlich haben es wir so auch leichter Teiel des Codes wiederzu verwenden, als wenn man eine große Datei vernweden würde in der alle Einzelschritte selektiv aufgeführt sind!
Script ausführen
Zum Schluss rufen wir unser erstes role-based Playbook auf uns installieren und konfigurieren unseren Webserver.
$ ansible-playbook site.yml
BECOME password: PLAY [demo] ************************************************************************************************************************* TASK [Gathering Facts] ************************************************************************************************************** ok: [demo]
TASK [***base*** : Gruppe für (WEB-Entwickler) 'ruben' erstellen] ******************************************************************* changed: [demo]
TASK [***base*** : WEB-Admin Nutzerkonto für den User 'ruben' mit frn zugehörigen UID '1010' anlegen un der Gruppe 'ruben' zuordnen.] changed: [demo]
TASK [***base*** : Initiales Passwort für den WEB-Admin 'ruben' hinterlegen] ******************************************************** changed: [demo]
TASK [***chrony*** : Installation des Deamon 'chrony' (in der aktuellsten Version)] ************************************************* ok: [demo]
TASK [***chrony*** : 1) Überprüfen ob das Backup der Konfigurationsdatei '/etc/chrony.conf' bereits existiert] ********************** ok: [demo]
TASK [***chrony*** : 2) Von der bestehenden originalen Konfigurationsdatei '/etc/chrony.conf' ein Backup '/etc/chrony.conf'.orig erstellen] ***************************************************************************************************************************** changed: [demo]
TASK [***chrony*** : Template Konfigurationsdatei an Ort und Stelle kopieren und Variablen setzen] ********************************** changed: [demo]
TASK [***chrony*** : Sicherstellen dass der Daemon 'chrony' (beim Systemstart) gestartet wird und läuft] *************************** ok: [demo]
TASK [***www*** : Installation des Deamon 'httpd' (in der aktuellsten Version)] ***************************************************** changed: [demo]
TASK [***www*** : 1) Überprüfen ob das Backup der Konfigurationsdatei '/etc/httpd/conf/httpd.conf' bereits existiert] *************** ok: [demo]
TASK [***www*** : 2) Von der bestehenden originalen Konfigurationsdatei '/etc/httpd/conf/httpd.conf' ein Backup '/etc/httpd/conf/httpd.conf.orig' erstellen] ************************************************************************************************************* changed: [demo]
TASK [***www*** : Template Konfigurationsdatei an Ort und Stelle kopieren und Variablen setzen] ************************************* changed: [demo]
TASK [***www*** : default-Homepage an Ort und Stelle kopieren] ********************************************************************** changed: [demo]
TASK [***www*** : Sicherstellen dass der Daemon 'httpd' (beim Systemstart) gestartet wird und läuft] ******************************* changed: [demo]
TASK [***www*** : Paketfilter anpassen und Port 80 (HTTP) öffnen] ******************************************************************* changed: [demo]
PLAY RECAP ************************************************************************************************************************************* demo : ok=16 changed=11 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0