Select Page

NETWAYS Blog

Ansible – Testing roles with Molecule

Ansible is a widely used and a powerful open-source configuration and deployment management tool. It can be used for simple repetitive daily tasks or complex application deployments, therefore Ansible is able to cover mostly any situation.

If used in complex or heterogene environments it is necessary to test the code to reduce time to fix code in production. To test Ansible code it is suggested to use Molecule.

Molecule is a useful tool to run automated tests on Ansible roles or collections. It helps with unit tests to ensure properly working code on different systems. Whether using the role internally or provide it to the public, it is useful to test many cases your role can be used. In addition Molecule is easily integrated into known CI/CD tools, like Github Actions or Gitlab CI/CD.

In this short introduction I’ll try get your first Molecule tests configured and running!

Please make sure you installed Molecule beforehand. On most distributions it’s easily installed via PIP.
The fastest and most common way to test roles would be in container. Due to a version problem with systemd currently it’s not possible to start services over systemd in containers. For this reason you can easily start with a vagrant instance and later migrate to docker or podman easily.


pip install molecule molecule-vagrant

If you have a role you can change into the role directory and create a default scenario.


cd ~/Documents/netways/git/thilo.my_config/
molecule init scenario -r thilo.my_config default
INFO     Initializing new scenario default...
INFO     Initialized scenario in /Users/thilo/Documents/netways/git/thilo.my_config/molecule/default successfully.

Below the molecule folder all scenarios are listed. Edit the default/molecule.yml to add the vagrant options.

Add a dependency file with your collections as with newer Ansible versions only the core is available. If needed you can add sudo privileges to your tests.

molecule/default/molecule.yml


---
dependency:
  name: galaxy
  options:
    requirements-file: collections.yml
driver:
  name: vagrant
platforms:
  - name: instance
    box: bento/centos-7
provisioner:
  name: ansible
verifier:
  name: testinfra
  options:
    sudo: true

The converge.yml is basically the playbook to run on your instance. In the playbook you define which variables should be used or if some pre-tasks should be run.

molecule/default/converge.yml


---
- name: Converge
  hosts: all
  become: true
  tasks:
    - name: "Include thilo.my_config"
      include_role:
        name: "thilo.my_config"

Now you can run your playbook with molecule. If you want to deploy and not delete your instance use converge. Otherwise you can use test, then the instance will be created, tested and destroyed afterwards.


python3 -m molecule converge -s default
or 
python3 -m molecule test -s default

Finally we can define some tests, the right tool is testinfra. Testinfra provides different modules to gather informations and check them if they have specific attributes.

Your scenario creates a tests folder with the following file: molecule/default/tests/test_default.py

In this example I’ll test the resources my role should create.


"""Role testing files using testinfra."""


def test_user(host):
    """Validate created user"""
    u = host.user("thilo")

    assert u.exists

def test_authorized_keys(host):
    """Validate pub key deployment"""
    f = host.file("/home/thilo/.ssh/authorized_keys")

    assert f.exists
    assert f.content_string == "ssh-rsa AAAA[...] \n"

And if we already converged our instance, we can verify these definitions against our deployment.


python3 -m molecule verify
INFO     default scenario test matrix: verify
INFO     Performing prerun with role_name_check=0...
[...]
INFO     Running default > verify
INFO     Executing Testinfra tests found in /Users/thilo/Documents/netways/git/thilo.my_config/molecule/default/tests/...
============================= test session starts ==============================
platform darwin -- Python 3.9.12, pytest-6.2.5, py-1.11.0, pluggy-0.13.1
rootdir: /
plugins: testinfra-6.4.0
collected 2 items

molecule/default/tests/test_default.py ..                                [100%]

============================== 2 passed in 1.79s ===============================
INFO     Verifier completed successfully.

With those easy steps you can easily test your roles for any scenario and your deployments can run without any hassle or at least you will be more relaxed during it 😉

Check out our Blog for more awesome posts and if you need help with Ansible send us a message or sign up for one of our trainings!

Thilo Wening
Thilo Wening
Manager Consulting

Thilo hat bei NETWAYS mit der Ausbildung zum Fachinformatiker, Schwerpunkt Systemadministration begonnen und unterstützt nun nach erfolgreich bestandener Prüfung tatkräftig die Kollegen im Consulting. In seiner Freizeit ist er athletisch in der Senkrechten unterwegs und stählt seine Muskeln beim Bouldern. Als richtiger Profi macht er das natürlich am liebsten in der Natur und geht nur noch in Ausnahmefällen in die Kletterhalle.

Ansible – How to create reusable tasks

Ansible is known for its simplicity, lightweight footprint and flexibility to configure nearly any device in your infrastructure. Therefore it’s used in large scale environments shared between teams or departments. Often tasks could be used in multiple playbooks to combine update routines, setting downtimes at an API or update data at the central asset management.

To use external tasks in Ansible we use the include_task module. This module dynamically includes the tasks from the given file. When used in a specific plays we would assign play specific variables to avoid confusion. For example:


vim tasks/get_ldap_user.yml

- name: get user from ldap
  register: users
  community.general.ldap_search:
    bind_pw: "{{ myplay_ad_bind_pw }}"
    bind_dn: "{{ myplay_ad_bind_dn }}"
    server_uri: "{{ myplay_ad_server }}"
    dn: "{{ myplay_ad_user_dn }}"
    filter: "(&(ObjectClass=user)(objectCategory=person)(mail={{ myplay_usermail }}))"
    scope: children
    attrs:
      - cn
      - mail
      - memberOf
      - distinguishedName

If this task should be used in another playbook to reduce the amount of code or is used again with other conditions or values. Therefore the variables need to be overwritten or if it is another playbook the variables are named wrong.

The solve this problem change the variables to unused generic variables. And assign your own variables in the include_task statement.


vim tasks/get_ldap_user.yml

- name: get user from ldap
  register: users
  community.general.ldap_search:
    bind_pw: "{{ _ad_bind_pw }}"
    bind_dn: "{{ _ad_bind_dn }}"
    server_uri: "{{ _ad_server }}"
    dn: "{{ _ad_user_dn }}"
    filter: "(&(ObjectClass=user)(objectCategory=person)(mail={{ _ad_usermail }}))"
    scope: children
    attrs:
      - cn
      - mail
      - memberOf
      - distinguishedName

The include_task vars parameter provides own variables to the tasks.


vim plays/user_management.yml
[...]
- name: check if user exists in ldap
  include_tasks:
    file: tasks/get_ldap_user.yml
  vars: 
    _ad_bind_pw: "{{ play_ad_pw }}"
    _ad_bind_dn: "{{ play_ad_user }}"
    _ad_server: "{{ play_ad_server }}"
    _ad_user_dn: "OU=users,DC=example,DC=de"
    _ad_usermail: "{{ play_usermail }}"

This can be easily combined with loops, to enhance the reusability of your tasks even more! Checkout this blogpost about looping multiple tasks. Ansible – Loop over multiple tasks

Check out our Blog for more awesome posts and if you need help with Ansible send us a message or sign up for one of our trainings!

Thilo Wening
Thilo Wening
Manager Consulting

Thilo hat bei NETWAYS mit der Ausbildung zum Fachinformatiker, Schwerpunkt Systemadministration begonnen und unterstützt nun nach erfolgreich bestandener Prüfung tatkräftig die Kollegen im Consulting. In seiner Freizeit ist er athletisch in der Senkrechten unterwegs und stählt seine Muskeln beim Bouldern. Als richtiger Profi macht er das natürlich am liebsten in der Natur und geht nur noch in Ausnahmefällen in die Kletterhalle.

Ansible – check your data

The automation and deployment tool Ansible is able to configure applications in multiple environments. This is possible due to variables used in Roles.

Variables are key to make roles reusable, but without proper documentation or descriptions they can get complicated.
If you want to provide different scenarios which are defined by specific variables, you can provide a good documentation and hope it will be seen and understood, otherwise users will get failed runs.

But instead of failed runs with cryptic messages which show that the used variables are wrong, we can easily check beforehand if the variables are set correctly.

The Ansible module ansible.builtin.assert can provide the solution to this problem.

With Ansible when expressions you can define rules and check variables. If the conditions aren’t true the module will fail with a custom message or if true, reward the user with a custom “OK” message.


- assert:
    that: role_enable_feature is true and role_feature_settings is defined 
    success_msg: The feature is configured correctly and will be managed. 
    fail_msg: To use the feature please configure the variable role_feature_settings, please look at the documentation section X.

With this module it’s easy to guide users when they forgot to use a specific variable or if they use multiple variables which shouldn’t be used together.

If you want to learn more about Ansible, checkout our Ansible Trainings or read more on our blog.

Thilo Wening
Thilo Wening
Manager Consulting

Thilo hat bei NETWAYS mit der Ausbildung zum Fachinformatiker, Schwerpunkt Systemadministration begonnen und unterstützt nun nach erfolgreich bestandener Prüfung tatkräftig die Kollegen im Consulting. In seiner Freizeit ist er athletisch in der Senkrechten unterwegs und stählt seine Muskeln beim Bouldern. Als richtiger Profi macht er das natürlich am liebsten in der Natur und geht nur noch in Ausnahmefällen in die Kletterhalle.

Icinga Plugins in Golang

Golang ist an sich noch eine relativ junge Programmiersprache, ist jedoch bei vielen Entwicklern und Firmen gut angekommen und ist die Basis von vielen modernen Software Tools, von Docker bis Kubernetes.

Für die Entwicklung von Icinga Plugins bringt die Sprache einige hilfreiche Konzepte mit. Golang baut fertige Binaries, Plugins können also zentral kompiliert und ohne große Abhängigkeiten verteilt werden. Alle Abhängigkeiten werden im Rahmen vom Bauprozess abgedeckt, die einzige Unterscheidung liegt in der Ziel Architektur, also Linux, Windows, Mac oder ähnliches, sowie ob 32 oder 64 bit.

Viele Funktionen und Packages (vergleichbar mit Libraries) kommen entweder direkt mit Golang mit oder können leicht aus der Open Source Community verwendet werden. Mit dem Package go-check von uns haben wir eine kleine Basis geschaffen, um leichter Plugins schreiben zu können, ohne sich zu sehr im Code wiederholen zu müssen.

Als ganz einfaches Go Plugin hier ein Beispiel eine “main.go” Datei:

package main

import (
	"github.com/NETWAYS/go-check"
)

func main() {
	config := check.NewConfig()
	config.Name = "check_test"
	config.Readme = `Test Plugin`
	config.Version = "1.0.0"

	_ = config.FlagSet.StringP("hostname", "H", "localhost", "Hostname to check")

	config.ParseArguments()

	// Some checking should be done here, when --help is not passed

	check.Exitf(check.OK, "Everything is fine - answer=%d", 42)
}

Alles was man noch tun muss, ist das Plugin zu kompilieren oder direkt auszuführen:

go build -o check_test main.go && ./check_test --help
go run main.go

Ein guter Einstieg in Go findet man über die Dokumentation, die Tour und vor allem in dem man sich umschaut, was die Community an Packages zu bieten hat.

Natürlich bleibt die Frage, wie überwache ich das Ding was mir wichtig ist, wofür es aber noch kein Plugin gibt. Gerade dort helfen wir von NETWAYS mit unseren Consulting und Entwicklungsleistungen.  Beispiele unserer Go Plugins findet man auf GitHub unter der NETWAYS Organisation.

 

Variablen mergen mit Ansible

Ansible kann Variable standardmäßig nicht mergen (miteinander vereinen, ineinander übergeben), sondern nur an verschiedenen Stellen überschreiben. Dies ist  bei manchen Anwendungsfällen unpraktisch, unübersichtlich und fehleranfällig wie z. B. beim ausrollen von SSH-Keys, Usern oder Firewall-Regeln. Angenommen wir wollen sowohl allgemeine SSH-Keys auf alle Server ausrollen, als auch extra Schlüssel zu bestimmten Servern zuordnen. In diesem Fall können wir unter group_vars/all  die allgemeinen Keys in einem Array oder in einem Dictonary definieren und unter group_vars/group_name oder host_vars/hostname die vorherigen Keys plus die Extras eintragen. Eine andere Möglichkeit wäre, dass wir die SSH-Keys (allgemeine und spezifische) in unterschiedlichen Variablen packen, die jeweils mit einem Task behandelt werden können. Die oben erwähnte Möglichkeiten machen besonders bei großen Umgebungen kein Spaß und ergibt am Ende entweder unübersichtliche Variablen-Definition oder statische Rollen.

In diesem Zuge wäre hiera im Kombination mit dem hiera lookup plugin eine gute Lösung. hiera ist ein hierarchisch durchsuchbarer key => value Store. In diesem Blogpost werde ich anhand eines Beispiels demonstrieren, wie hiera mit Ansible eingesetzt werden kann. Dieses Beispiel befasst die Verteilung der Konfigurationen für Gruppen, Benutzers und SSH-Keys auf zwei Server.  Bei allen Linux-Distrubtionen gibt es in unserem Fall keine Unterschiede. Alle Schritte erfolgen auf dem Ansible Controller.  Ich fange mal mit der hiera Installation an.

# yum install ruby -y
# gem install hiera

Unter /etc/hiera.yaml liegt die hiera-Konfiguration.

:backends:
- yaml
:hierarchy:
- "%{groups}"
- "%{hosts}"
- common

:yaml:
:datadir: /etc/hieradata

:merge_behavior: deeper

Unter hierarchy definieren wir die Stufen aufsteigend/absteigend, bei denen hiera nach einem Schlüssel suchen soll. hiera fängt mit groups an zu suchen und endet mit common. common trifft immer zu und gilt in unserem Fall für alle Server. groups und hosts sind Facts, die wir später dem Ansible playbook übergeben wollen. Der Pfad zu suchenden Daten bzw. Schlüsseln kann man beliebig ändern. Außerdem kann man bei :merge_behavior: eine der drei folgenden Optionen einstellen: native, deep, deeper. Um die Unterschiede zu erfahren, könnt ihr das hier nachlesen.

In der Ansible-Inentory stehen zwei Hosts jweils unter einer Gruppe.

[webserver]
web1.shared
web2.shared
web3.shared

[monitoring]
icinga-master1.shared

Unter /etc/hieradata/common.yaml liegen die SSH-Keys, die auf alle Server ausgerollt werden müssen, da common.yaml als unser Default für alle Hosts gilt, wie schon vorher erwähnt wurde.

---
ssh_users:
  - name: 'sergio'
    comment: 'sergio@example.com'
    key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDjuyGPTdo17ATAu69v2Bqui/f38e1uqOLvObiMw+uS9+Raxy5sO12pagVJeN9BTQaL09Sp9Dw+ozozg67WXfgTo+yoC5wy5a3d3Ihl4XJ/68SFUmEO2qo8Zg3914teL+FQMw5BE52LyqJjjBulGQ+jloGaldqxBfvjrmGMnz3mafCLtix+/UO1W+51gkea925XAOQ+KR1u1WFbEM3E6TfAoMv5Ev41e3jIcS+O+dnBqiZLsSIF3h72ni4eGr6h6x51itoFGQhN1jRcK9J5QfJG4O8OciReUCU5gETLi8RgFuBlQqDztZT1jGPbpkH2/swt3WGBUGOv+GizQg8iK9F"
    authorize_to: 'root'
    state: present

  - name: 'david'
    key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDRsAZ3DKxisCchDBd1dqtM9kPEgq+8MXbkClYVAoAlidx6qLz8Z6zBeSZ8ob+ae0dEegoN31mXv9ITaFhIdgIUxP0s54Mb4kgc9FE4njNRlb7xkTwsU3C6G7JUN9pJ64ucEqLI2iJ1JLI+zh0u5sWZS0tMoyEy73ZaAE6O32XyywIw5X+Uw7ISwqeeFsnWc4faKoWVgjt/s1MsuEOGsD1ocUxVPG7PKMEy8LQtQ7WL+RybV/TYH56hxBMi5H4BfC61Wm6Fn7pck5pCMzZOwstYikHoTWhJ16UYoJ6MubddoAOq/JyTimC/559U3lYgdHBXGJyCyW2xcGF7blmh0U97"
    authorize_to: 'root'
    state: absent

Unter /etc/hieradata/monitoring.yaml steht extra Konfiguration für die Hosts, die der monitoring-Gruppe zugeordnet sind.

user_groups:
  - name: 'nagios'
    state: 'present'

users:
  - name: 'icinga'
    shell: '/bin/bash'
    groups: 'nagios'
    append: yes
    state: 'present'

ssh_users:
  - name: 'alex'
    comment: 'sergio@example.com'
    key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCu5vAfoePg8Z5Tc3uOfua+RvxpA/WyDNKtHJze/pogssAtA+RZ6wdmvwuBGVFILgVKrpHItsUWZwWwsrNF0rjXV4MsPZzS8TbUNISoV7sETOk6pM1vg8ldy5XEszCokFsHmYLfovlqaICneKwiZ4SfQo21WjgzNXeVSND/1W5BZNAptobH6HC9oASRgeidwbDjDEux8//zvjjcIQlj9HXGmsKT5/r2+LR5Wo8rRkhdC+sbDJcDfN9OFg2Wo+PmwX6ArMfZ3oqh4xEGS3hN4Yo8lAVK8APPKNojtKv53qnFNTkfNrHRw9MbiXpPiYMFmKtGZztc9vPNKB6kBF1DFL/'
    authorize_to: 'icinga'
    state: present

Die Konfigurationen unter /etc/hieradata/web1.shared.yaml werden nur dem web1.shared angeordnet.

---
user_groups:
  - name: 'web-group'
    state: 'present'

  - name: 'admins'
    state: 'present'

users:
  - name: 'web-user'
    shell: '/bin/bash'
    groups: 'web-group, admins'
    append: yes
    state: 'present'

ssh_users:
  - name: 'mueller'
    comment: 'web-user@example.com'
    key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCg6fvX0UIN22fPHHbSxPZQCyeqa8j2AEeqF7TlX3TSkJ17Q7Rz3g4B1RG8L+WfI+eb11bmESPTQ5Pym27qmNvfYl6dJynSdrRihnNj4Id1fKEzoW6J835rtRmaKD0V7KJ1JJvip0m24oIlFJIYSfShWSrOuvUgP3TpYUsU3KA2AQhoNtXWhgUM+wwFfCF3qZ4AX5zhQFy+bdqyYudmW99DasuZjaCq3djoMh9Ocm9lRPdDTpGS/Yb1yAqKU7rizrwlS3EmNcbQddRd2ZXFQnustfSX+ejo7PcEpimAPpcC2jbbS38pFbYdIvdm7BXsRMpsOXEuEZLrrbZMsPpEQ8FJ'
    authorize_to: 'web-user'
    state: present

Unser playbook kann so aussehen.

---
- hosts: all
  remote_user: root
  become: true
  tasks:
    - name: Set facts
      set_fact:
        ssh_users: "{{ lookup('hiera', 'ssh_users --array hosts={{ inventory_hostname }}  groups={{ item }} --format yaml')|from_yaml }}"
        user_groups: "{{ lookup('hiera', 'user_groups --array hosts={{ inventory_hostname }}  groups={{ item }} --format yaml')|from_yaml }}"
        users: "{{ lookup('hiera', 'users --array hosts={{ inventory_hostname }}  groups={{ item }} --format yaml')|from_yaml }}"
      loop: "{{ group_names }}"
 
    - name: Create groups
      group:
        name: "{{ item.name }}"
        state: "{{ item.state }}"
      loop: "{{ user_groups | default ([]) }}"
  - name: Create users
      user:
        name: "{{ item.name }}"
        shell: "{{ item.shell | default (omit) }}"
        state: "{{ item.state }}"
        append: "{{ item.append }}"
      loop: "{{ users | default ([]) }}"

    - name: Set ssh-keys
      authorized_key:
        user: "{{ item.authorize_to }}"
        state: "{{ item.state }}"
        key: "{{ item.key }}"
      loop: "{{ ssh_users | default ([]) }}"

Das lookup-Plugin nutzt hiera um auf Daten bzw. Schlüssel zugreifen zu können. Wir geben die zu suchenden Schlüssel mit, z. B. ssh-users. Wir übermitteln auch die Facts die hiera erwartet, nämlich hosts und groups. Man kann anstatt “inventory_hostname” “ansible_hostname” oder “ansible_fqdn” verwenden. Allerdings muss man die Datein unter /etc/hieradata entsprechend benennen. Groupe-Fact geben wir durch einen Loop über den Ansible-Fact group_names an die hiera weiter, in dem alle Gruppen der Inventory aufgelistet sind. Die Rückgabe ist in unserem Fall ein array, man kann auch einen hash auswählen. Die standardmäßige Format bei hiera is ruby, allerdings stellen wir das auf yaml um, damit Ansible die Format verarbeiten kann.

# ansible-playbook hiera-playbook.yml -i inventory

PLAY [all] ****************************************************************************************************************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************************************************************************************************************
ok: [web1.shared]
ok: [icinga-master1.shared]

TASK [Set facts] **********************************************************************************************************************************************************************************************************************
ok: [icinga-master1.shared] => (item=monitoring)
ok: [web1.shared] => (item=webserver)

TASK [Create groups] ******************************************************************************************************************************************************************************************************************
ok: [icinga-master1.shared] => (item={u'state': u'present', u'name': u'nagios'})
ok: [web1.shared] => (item={u'state': u'present', u'name': u'web-group'})
ok: [web1.shared] => (item={u'state': u'present', u'name': u'admins'})

TASK [Create users] *******************************************************************************************************************************************************************************************************************
ok: [web1.shared] => (item={u'groups': u'web-group, admins', u'state': u'present', u'shell': u'/bin/bash', u'name': u'web-user', u'append': True})
ok: [icinga-master1.shared] => (item={u'groups': u'nagios', u'state': u'present', u'shell': u'/bin/bash', u'name': u'icinga', u'append': True})

TASK [Set ssh-keys] *******************************************************************************************************************************************************************************************************************
ok: [icinga-master1.shared] => (item={u'comment': u'sergio@example.com', u'state': u'present', u'authorize_to': u'icinga', u'name': u'alex', u'key': u'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCu5vAfoePg8Z5Tc3uOfua+RvxpA/WyDNKtHJze/pogssAtA+RZ6wdmvwuBGVFILgVKrpHItsUWZwWwsrNF0rjXV4MsPZzS8TbUNISoV7sETOk6pM1vg8ldy5XEszCokFsHmYLfovlqaICneKwiZ4SfQo21WjgzNXeVSND/1W5BZNAptobH6HC9oASRgeidwbDjDEux8//zvjjcIQlj9HXGmsKT5/r2+LR5Wo8rRkhdC+sbDJcDfN9OFg2Wo+PmwX6ArMfZ3oqh4xEGS3hN4Yo8lAVK8APPKNojtKv53qnFNTkfNrHRw9MbiXpPiYMFmKtGZztc9vPNKB6kBF1DFL/'})
ok: [web1.shared] => (item={u'comment': u'web-user@example.com', u'state': u'present', u'authorize_to': u'web-user', u'name': u'mueller', u'key': u'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCg6fvX0UIN22fPHHbSxPZQCyeqa8j2AEeqF7TlX3TSkJ17Q7Rz3g4B1RG8L+WfI+eb11bmESPTQ5Pym27qmNvfYl6dJynSdrRihnNj4Id1fKEzoW6J835rtRmaKD0V7KJ1JJvip0m24oIlFJIYSfShWSrOuvUgP3TpYUsU3KA2AQhoNtXWhgUM+wwFfCF3qZ4AX5zhQFy+bdqyYudmW99DasuZjaCq3djoMh9Ocm9lRPdDTpGS/Yb1yAqKU7rizrwlS3EmNcbQddRd2ZXFQnustfSX+ejo7PcEpimAPpcC2jbbS38pFbYdIvdm7BXsRMpsOXEuEZLrrbZMsPpEQ8FJ'})
ok: [web1.shared] => (item={u'comment': u'sergio@example.com', u'state': u'present', u'authorize_to': u'root', u'name': u'sergio', u'key': u'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDjuyGPTdo17ATAu69v2Bqui/f38e1uqOLvObiMw+uS9+Raxy5sO12pagVJeN9BTQaL09Sp9Dw+ozozg67WXfgTo+yoC5wy5a3d3Ihl4XJ/68SFUmEO2qo8Zg3914teL+FQMw5BE52LyqJjjBulGQ+jloGaldqxBfvjrmGMnz3mafCLtix+/UO1W+51gkea925XAOQ+KR1u1WFbEM3E6TfAoMv5Ev41e3jIcS+O+dnBqiZLsSIF3h72ni4eGr6h6x51itoFGQhN1jRcK9J5QfJG4O8OciReUCU5gETLi8RgFuBlQqDztZT1jGPbpkH2/swt3WGBUGOv+GizQg8iK9F'})
ok: [icinga-master1.shared] => (item={u'comment': u'sergio@example.com', u'state': u'present', u'authorize_to': u'root', u'name': u'sergio', u'key': u'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDjuyGPTdo17ATAu69v2Bqui/f38e1uqOLvObiMw+uS9+Raxy5sO12pagVJeN9BTQaL09Sp9Dw+ozozg67WXfgTo+yoC5wy5a3d3Ihl4XJ/68SFUmEO2qo8Zg3914teL+FQMw5BE52LyqJjjBulGQ+jloGaldqxBfvjrmGMnz3mafCLtix+/UO1W+51gkea925XAOQ+KR1u1WFbEM3E6TfAoMv5Ev41e3jIcS+O+dnBqiZLsSIF3h72ni4eGr6h6x51itoFGQhN1jRcK9J5QfJG4O8OciReUCU5gETLi8RgFuBlQqDztZT1jGPbpkH2/swt3WGBUGOv+GizQg8iK9F'})
ok: [icinga-master1.shared] => (item={u'state': u'absent', u'authorize_to': u'root', u'name': u'david', u'key': u'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDRsAZ3DKxisCchDBd1dqtM9kPEgq+8MXbkClYVAoAlidx6qLz8Z6zBeSZ8ob+ae0dEegoN31mXv9ITaFhIdgIUxP0s54Mb4kgc9FE4njNRlb7xkTwsU3C6G7JUN9pJ64ucEqLI2iJ1JLI+zh0u5sWZS0tMoyEy73ZaAE6O32XyywIw5X+Uw7ISwqeeFsnWc4faKoWVgjt/s1MsuEOGsD1ocUxVPG7PKMEy8LQtQ7WL+RybV/TYH56hxBMi5H4BfC61Wm6Fn7pck5pCMzZOwstYikHoTWhJ16UYoJ6MubddoAOq/JyTimC/559U3lYgdHBXGJyCyW2xcGF7blmh0U97'})
ok: [web1.shared] => (item={u'state': u'absent', u'authorize_to': u'root', u'name': u'david', u'key': u'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDRsAZ3DKxisCchDBd1dqtM9kPEgq+8MXbkClYVAoAlidx6qLz8Z6zBeSZ8ob+ae0dEegoN31mXv9ITaFhIdgIUxP0s54Mb4kgc9FE4njNRlb7xkTwsU3C6G7JUN9pJ64ucEqLI2iJ1JLI+zh0u5sWZS0tMoyEy73ZaAE6O32XyywIw5X+Uw7ISwqeeFsnWc4faKoWVgjt/s1MsuEOGsD1ocUxVPG7PKMEy8LQtQ7WL+RybV/TYH56hxBMi5H4BfC61Wm6Fn7pck5pCMzZOwstYikHoTWhJ16UYoJ6MubddoAOq/JyTimC/559U3lYgdHBXGJyCyW2xcGF7blmh0U97'})

PLAY RECAP ****************************************************************************************************************************************************************************************************************************
icinga-master1.shared      : ok=5    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web1.shared                : ok=5    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

 

Wenn Du mehr über Ansible lernen und Dein wissen vertiefen willst, wäre eine Teilnahme an einer Ansible Schulung nicht verkehrt.

 

Afeef Ghannam
Afeef Ghannam
Systems Engineer

Afeef hat seine Ausbildung Als Fachinformatiker in Richtung Systemintegration im Juli 2020 bei NETWAYS absolviert, seitdem unterstützt er die Kolleg:innen im Operations Team der NETWAYS Professional Services bei der Betriebsunterstützung. Nach der Arbeit macht er gerne Sport, trifft Freunde oder mag es Filme zu schauen.