Ansible

Nützliche Hinweise

Überprüfung der Erreichbarkeit aller Hosts

root@server:/etc/ansible# ansible -i /etc/ansible/INVENTORY -m ping HOST-GRUPPE
server-01 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
server-02 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
server-03 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

Verwendung der shell

Abfragen ob ein paket installiert ist:

root@server#ansible -i /etc/ansible/INVENTORY -m shell -a "dpkg -l | grep PAKETNAME" HOST-GRUPPE

Hier mal ein Beispiel:

root@server:/etc/ansible# ansible -i /etc/ansible/INVENTORY -m shell -a "dpkg -l | grep lsb-re" linuxserver
server-01 | SUCCESS | rc=0 >>
ii  lsb-release                 9.20161125       all     Linux Standard Base version report utility

server-02 | SUCCESS | rc=0 >>
ii  lsb-release                 9.20161125       all     Linux Standard Base version reping utility

server-03 | SUCCESS | rc=0 >>
ii  lsb-release                 9.20161125       all     Linux Standard Base version reporting uity

Arbeiten mit Variablen, geschützten sensiblen Daten in Variablen usw.

Quelle: https://www.digitalocean.com/community/tutorials/how-to-use-vault-to-protect-sensitive-ansible-data-on-ubuntu-16-04

So könnt ihr Variablen in config Dateien aufrufen:

variableABC: "{{ variablexyz }}"

Ich nutze das z.B. um Passwörter zu schützen, die ich in einer sog. "Ansible Vault" verschlüsselt gespeichert habe.

Hier gehts zur Ansible Doku zum Thema Vault.

Variablen - Hostnamen aus dem Inventory in config files einbauen

Wenn man z.b. ein Cluster für Graylog hat und möchte diese Cluster Nodes mit einer einheitlichen Konfiguration für einen Telegraf Datensammler bestücken dann muss man nicht für jeden Node eine separate telegraf.conf bereitstellen.

Ich habe es so gelöst:

1. einheitliche telegraf.conf Datei:

[[inputs.graylog]]
metrics = ["...."]
password = "Graylog-Passwort"
servers = ["http://#graylog-node:9000/api/system/metrics/multiple"]
username = "graylog-user"

2. Ansible Playbook

---
- hosts: g-gl-cluster
  tasks:
    - name: copy the telegraf config file to the group g-prometheus-clients
      copy:
       src: "/etc/ansible/etc-configs/telegraf/telegraf-inputs-graylog.conf"
       dest: "/etc/telegraf/telegraf.d/telegraf-inputs-graylog.conf"
       owner: root
       group: root
       mode: 0644
       backup: yes
    - name: Replace hostname in telegraf config file
      lineinfile:
        path: '/etc/telegraf/telegraf.d/telegraf-inputs-graylog.conf'
        regexp: '^servers'
        line: 'servers = ["http://{{ inventory_hostname }}:9000/api/system/metrics/multiple"]'
        state: present
        create: yes
    - name: Restart service telegraf, in all cases
      service:
       name: telegraf
       state: restarted

Ausführung von Playbooks auf Hosts beschränken

#ansible-playbook dein-playbook.yml --limit=einzelner-host

Strukturen, Playbooks, Vaults - so habe ich es gemacht

Prolog

Ich möchte meine Erfahrungen mit Ansible mit euch teilen und werde hier mal aufführen wie ich Ansible auf meinem Lernweg aufbaue. Bedenkt aber stets, dass ich gerade erst mit Ansible anfange und vorher noch nie etwas mit zentralem Konfigurationsmanagement zu tun hatte. Ich zeige euch hier lediglich meine Überlegungen, die wahrscheinlich hier und da noch verbessert werden müssten. Ein Nachbauen macht ihr immer auf eigene Gefahr hin.

Vorraussetzungen - Ansible Eigenheiten

Was solltet ihr beachten bzw. vorausschauend planen, wenn ihr Ansible einsetzen wollt?

Benutzer für zentrale Verwaltungsaufgaben

Das Arbeiten mit Ansible wird euch viel einfacher fallen, wenn ihr einen einzigen Benutzer auf allen euren Hosts angelegt habt und dieser SUDO Rechte hat. Dann könnt ihr euer Ansible Management einheitlich aufbauen und habt nicht zig verschiedene User, die ihr berücksichtigen müsst.

Ja, das ist sicherlich Geschmackssache und in manchen Situationen im Bezug auf Sicherheit sollte man das abwandeln und auf mehrere spezifische Benutzer zurückgreifen.

 

Ordnerstruktur

Ansible Docs: https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html#directory-layout

Von Ansible wird keine Struktur vorgegeben. Man hat also die freie Entscheidungsgewalt. So habe ich also das hier angefangen zu strukturieren:

root@server:/etc/ansible# tree
.
├── ansible.cfg
├── archive
│   ├── azure.bak
│   ├── configure-linuxserver-rsyslog-to-graylog.yml.alt
│   ├── hosts.bak
│   └── install-prometheus-node-exporter.yml.bak
├── azure
├── group_vars
│   ├── production
│   │   ├── vars
│   │   └── vault
│   ├── init-linuxserver
│   │   ├── vars
│   │   └── vault
│   └── linuxserver
│       ├── vars
│       └── vault
├── playbooks
│   ├── packages
│   │   ├── install-production-prometheus-node-exporter.yml
│   │   ├── install-prometheus-node-exporter.yml
│   │   └── install-systemtools.yml
│   ├── services
│   │   └── connect-graylog.yml
│   ├── systemsettings
│   │   ├── set-timezone-europe-berlin.yml
│   │   └── update-hosts-file.yml
│   └── users
│       └── create-user-webdata.yml
└── vault_pwd

Variablen

So sieht eine vars Datei für die Gruppe linuxserver aus:

root@server:/etc/ansible# cat group_vars/linuxserver/vars
---
ansible_connection: ssh
ansible_user: ansibleadmin
ansible_ssh_private_key_file: /home/ansibleadmin/.ssh/id_rsa
ansible_ssh_common_args: '-o StrictHostKeyChecking=no'
ansible_become: yes
ansible_become_method: sudo
ansible_become_user: root
ansible_become_pass: "{{ vault_ansible_become_pass }}"

Inventory

Ansible Docs: https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html

Meine Inventory Datei heißt azure, weil ich Ansible im Zuge einer Azure Evaluierung einsetze.

root@vm-azure-manager:/etc/ansible# cat azure
#Here i was experimenting where do set this option for being applied everywhere.
#ansible_ssh_common_args='-o StrictHostKeyChecking=no'

[prometheus]
prometheus-01

[grafana]
grafana-01

[graylog]
graylog-01

[es-nodes]
es-node-01

[init-linuxserver:children]
prometheus
grafana
graylog
es-nodes

[linuxserver:children]
prometheus
grafana
graylog
es-nodes

[graylogclients:children]
grafana
prometheus
es-nodes

Bisher habe ich folgendes gemeistert:

Durch die Verschachtelung von Gruppen kann ich meine Hosts kategorisieren und habe eine ähnliche Vorangehensweise wie beim Administrieren von Benutzerrechten, d.h. bei Änderungen den Aufwand zu minimieren, weil man z.B. einen neuen Host nur noch einer Gruppe zuweist oder einen alten aus einer Gruppe löschen muss.

Ansible Vault

Ansible Docs: https://docs.ansible.com/ansible/latest/user_guide/vault.html

Ich habe am Anfang die Passwörter für meine User, die Ansible benutzen sollte, klar lesbar in meine Konfig-Dateien geschrieben. Da ich davon aber absolut kein Freund bin, habe ich mich also zeitnah darum gekümmert und herausgefunden, dass es die sog. Ansible-Vault gibt.  Vault heißt Tresor. Also ein verschlüsselter Container, der eure Passwörter und sonstige Einstellungen beinhalten kann. Ich habe aktuell nur ein Passwort darin gespeichert.

Wie ihr schon anhand meiner Ordnerstruktur gesehen habt, gibt es jeweils in jedem Unterordner einer Gruppe eine Datei namens "vault". Das sind meine geschützten Bereiche für die jeweilige Gruppe.

So hier habe ich es gemacht:

  1. Eine Schlüsseldatei "vault_pwd" erstellt und in Klartext den Schlüssel eingetragen, der global zum Ver- und Entschlüsseln benutzt werden soll.

    Man muss keinen globalen Schlüssel verwenden. Das kann man so einstellen wie man es möchte.

    1. root@server:# nano vault_pwd
    2. Schlüssel eintragen
  2. Globale Schlüsseldatei in der ansbile.cfg aktiviert
    1. vault_password_file = /etc/ansible/vault_pwd
  3. zu verschlüsselnde Variablen mit einem Editor in die jeweilige vault Datei eintragen
  4. vault Datei verschlüsseln: ansible-vault encrypt vault
    1. Da wir einen globalen Schlüssel aktiviert haben, wird dieser automatisch zum Verschlüsseln benutzt, sodass ihr keinen Schlüssel an der Stelle eingeben müsst.

    2. So habe ich z.B. das Passwort hinterlegt, dass benutzt wird um SUDO Rechte zu aktivieren:
      1. vault_ansible_become_pass: DAS-PASSWORT

        1. Ansible empfiehlt als Variablennamen der zu verschlüsselnden Variable ein "vault_" als Präfix zu verwenden. Ich finde das gut, da man so gleich den Bezug der Verwendung herstellen kann.
  5. Passwort in unverschlüsselter Variable aus verschlüsselter Variable aufrufen
    1. So sieht der Aufruf dann in meiner "vars" Datei aus: ansible_become_pass: "{{ vault_ansible_become_pass }}"
    2. Die Syntax nennt sich jinja2

Alles was ich jetzt mit Ansible mache, funktioniert mit verschlüsselten Passwörtern.

Playbooks

Ansible Docs: https://docs.ansible.com/ansible/latest/user_guide/playbooks.html

Ansible-Systembenutzer bereitstellen

Funktionen

Probleme

Man benötigt natürlich auf allen Hosts für das erstmalige Bereitstellen mit Ansible einen Systemuser mit SUDO Rechten. Da ich für Ansible Arbeiten einen eigenen Systemuser nutzen möchte, habe ich als erstes einen neuen user auf allen Hosts angelegt. Dafür habe ich den User benutzt, den man mit der Installation von Debian anlegen muss.

Playbook "create-user-ansibleadmin.yml

root@server:/etc/ansible# cat playbooks/users/create-user-ansibleadmin.yml
---
- hosts: init-linuxserver
  remote_user: initial-user-from-fresh-install-with-sudo-rights
  become: yes
  become_method: sudo
  become_user: root

  tasks:
    - name: Ansible create user ansibleadmin with home directory
      user:
       name: ansibleadmin
       createhome: yes

    - name: create directory .ssh
      file:
       path: /home/ansibleadmin/.ssh
       state: directory
       owner: ansibleadmin
       group: ansibleadmin
       mode: 0700

    - name: Ansible copy the ssh public key of the user ansibleadmin to the remote host
      copy:
       src: "/home/ansibleadmin/.ssh/id_rsa.pub"
       dest: "/home/ansibleadmin/.ssh/authorized_keys"
       owner: ansibleadmin
       group: ansibleadmin

    - name: Add user ansibleadmin to sudo group and grant sudo rights
      user:
       name: ansibleadmin
       groups: sudo

Debian Paket mit apt installieren

Playbook: "install-systemtools.yml"

root@server:/etc/ansible# cat playbooks/packages/install-systemtools.yml
---
- hosts: linuxserver
  tasks:
    - name: install lsb-release
      apt: name=lsb-release state=present

    - name: install lsof
      apt: name=lsof state=present

 Playbook: Azure CLI installieren

---
- hosts: azure-master
  tasks:
    - name: Install apt-transport-https
      apt: name=apt-transport-https state=present

    - name: Add Azure Repository to sources.list.d
      apt_repository:
       repo: deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ stretch main
       state: present
       filename: azure-cli

    - name: Add Microsoft´s signature key
      apt_key:
       url: https://packages.microsoft.com/keys/microsoft.asc
       state: present

    - name: make an apt-update before be able to find azure-cli package
      apt:
       update_cache: yes

    - name: Install Azure CLI
      apt: name=azure-cli state=present

Playbook: Docker CE installieren

---
- hosts: docker-vm
  tasks:
    - name: Add Azure Repository to sources.list.d
      apt_repository:
       repo: deb [arch=amd64] https://download.docker.com/linux/debian stretch stable
       state: present
       filename: docker-ce

    - name: Add Docker CE signature key
      apt_key:
       url: https://download.docker.com/linux/debian/gpg
       state: present

    - name: make an apt-update before be able to find azure-cli package
      apt:
       update_cache: yes

    - name: Install Docker CE
      apt: name=docker-ce state=present

Datei kopieren - hosts Datei bereitstellen

Playbook "update-hosts-file.yml"

root@server:/etc/ansible# cat playbooks/systemsettings/update-hosts-file.yml
---
- hosts: linuxserver
# steht alles in den "Vars" Dateien
#  remote_user: ansibleadmin
#  become: yes
#  become_method: sudo
#  become_user: root

  tasks:
    - name: copy the hosts file from host "server" to the group linuxserver
      copy:
       src: "/etc/hosts"
       dest: "/etc/hosts"
       owner: root
       group: root
Hintergrund

Ich arbeite in meiner testumgebung mit den Hosts Dateien, weil ich auf die Schnelle kein DNS aufbauen konnte. Zum Einen bin ich kein Experte für DNS (erst einmal damit rumprobiert) und Zweitens wollte ich so schnell wie möglich alle Hosts mit Namen anstelle von IP Adressen ansprechen.

Zeitzone einstellen

Playbook "set-timezone-europe-berlin.yml"

root@server:/etc/ansible# cat playbooks/systemsettings/set-timezone-europe-berlin.yml
---
- hosts: linuxserver
  tasks:
    - name: set timezone to Europe/Berlin
      timezone:
       name: Europe/Berlin
Hintergrund

Da ich die Debian VMs in Azure über den RessourcenManager von Azure bereitstellen lassen habe, gab es nachträglich Einstellungen, die angepasst werden mussten. Dazu gehörte u.a. die Zeitzone.

Playbook - install Java 8 JDK

# cat /etc/ansible/playbooks/apt/i-java8jdk.yml                                                      ---
- hosts: g-java8jdk
  tasks:
    - name: Add Java 8 JDK Repository to sources.list.d
      apt_repository:
       repo: deb http://ppa.launchpad.net/webupd8team/java/ubuntu xenial main
       state: present
       filename: webupd8team-java8jdk

    - name: Add webupd8team signature key
      apt_key:
       keyserver: keyserver.ubuntu.com
       id: EEA14886

    - name: make an apt-update before be able to find java packages
      apt:
       update_cache: yes

    - name: set licence selected
      shell: /bin/echo debconf shared/accepted-oracle-license-v1-1 select true | /usr/bin/debconf-set-selections
      sudo: yes

    - name: set licence seen
      shell: /bin/echo debconf shared/accepted-oracle-license-v1-1 seen true | /usr/bin/debconf-set-selections
      sudo: yes

    - name: Install oracle-java8-installer
      apt: name={{item}} state=present
      with_items:
      - java-common
      - oracle-java8-installer
      - oracle-java8-set-default