My solutions to Ansible Puzzle labs with VirtualBox and Terraform
- 1. Setting up Ansible
- 2. Documentation
- 3. Setup and Ad Hoc Commands
- 4. Ansible Playbooks - Basics
- 4.1. Ansible Playbooks - Variables and Loops
- 4.2. Ansible Playbooks - Templates
- 4.3. Ansible Playbooks - Output
- 4.4. Ansible-Pull
- 4.5. Task control
- 5. Ansible Roles - Basics
- 5.1. Ansible Roles - Handlers and Blocks
- 6. Managing Secrets with Ansible Vault
- 7. Ansible Galaxy and more
- 8. Ansible Collections
- 9.1. AWX / Ascender / AAP / Installation
- 10. Ansible-Navigator
- 10.1. Ansible-Builder
- 10.2. Ansible Runner
- 11.1. Event Driven Ansible - Basics
- 11.2. Event Driven Ansible - Events and Facts
https://ansible.puzzle.ch/docs/01/
- Install Ansible on controller (localhost)
sudo apt update && sudo apt install ansible -y
- Ping hosts
ansible all:!controller -i inventory/hosts -m ping
https://ansible.puzzle.ch/docs/02/
- List all ansible modules
ansible-doc --list
https://ansible.puzzle.ch/docs/03/
- Ping all
nodes
withansible.builtin.ping
ansible nodes -i inventory/hosts -m ping
- Gather all facts from
nodes
ansible nodes -i inventory/hosts -m gather_facts
# or
ansible nodes -i inventory/hosts -m setup
- Gather only
ansible_default_ipv4
fact from allnodes
ansible nodes -i inventory/hosts -m setup -a "filter=ansible_default_ipv4"
- Find module with
hostname
ansible-doc -l | grep hostname
ansible-doc -s hostname
- Setup
hostname
on all hosts using the inventory
ansible nodes -i inventory/hosts -b -m hostname -a "name={{ inventory_hostname }}"
ansible nodes -i inventory/hosts -a "cat /etc/hostname"
- Install
apache2
onweb
ansible web inventory/hosts -b -m apt -a "name=apache2 state=installed"
- Start and enable
apache2
ansible web inventory/hosts -b -m systemd -a "name=apache2 state=started enabled=true"
- Revert changes
ansible web -i inventory/hosts -b -m systemd -a "name=apache2 state=stopped enabled=false"
ansible web -i inventory/hosts -b -m apt -a "name=apache2 state=absent"
- Create file
/etc/ansible/testfile.txt
onap-worker-node2
ansible ap-worker-node2 -i inventory/hosts -m file -a "path=/home/ansible/testfile.txt state=touch"
- Paste custom test into the file using
copy
module
ansible ap-worker-node2 -i inventory/hosts -m copy -a "dest=/home/ansible/testfile.txt content='custom text'"
- Remove the file
ansible ap-worker-node2 -i inventory/hosts -m file -a "path=/home/ansible/testfile.txt state=absent"
https://ansible.puzzle.ch/docs/04/
- Create user config file
echo "[defaults]\ninventory = /home/ansible/techlab/inventory/hosts" > ansible.cfg
https://ansible.puzzle.ch/docs/04/01/
- Set variable via command line
ansible-playbook playbooks/04-motd.yaml --extra-vars motd_content="new text\n"
- Play playbook and limit nodes to
ap-worker-node1
andap-worker-node2
ansible-playbook playbooks/04-motd.yaml -l ap-worker-node1,ap-worker-node2
https://ansible.puzzle.ch/docs/04/02/
https://ansible.puzzle.ch/docs/04/03/
- Ensure
httpd
is stopped on the groupweb
by using an Ansible ad hoc command.
ansible web -b -a "systemctl stop apache2"
- By using an ansible ad hoc command, place an invalid configuration file
/etc/httpd/conf/httpd.conf
and backup the file before. Use theansible.builtin.copy
module to do this in ad hoc command. ansible.builtin.copy
ansible web -b -m copy -a "content='invalid config' dest=/etc/apache2/apache2.conf backup=true"
- Restart
httpd
by using an Ansible ad hoc command. This should fail since the config file is not valid.
ansible web -b -m systemd -a "name=apache2 state=restarted"
https://ansible.puzzle.ch/docs/04/04/
- Use an ansible-pull command that uses the resources in the folder resources/ansible-pull/ of our GitHub repository located at https://github.com/puzzle/ansible-techlab. Apply the playbook local.yml located at the resource/ansible-pull folder and run it on all hosts in the inventory file hosts
ansible-pull --url https://github.com/puzzle/ansible-techlab -i resources/ansible-pull/hosts resources/ansible-pull/local.yml
- Show the content of /etc/motd and verify, that the file was copied using ansible-pull
cat /etc/motd
- Also verify, that no content of the git repository was copied to the local folder.
ls -l
- Cron job
# /etc/cron.d/ansible-pull
* * * * * vagrant ansible-pull -U https://github.com/puzzle/ansible-techlab -i resources/ansible-pull/hosts resources/ansible-pull/local.yml
- Command
watch
to show the content of/etc/motd
every second
watch -n 1 cat /etc/motd
https://ansible.puzzle.ch/docs/04/05/
- Write an ad-hoc command that sleeps for 1000 seconds and runs on
node1
. Ensure that the command times out after 10 seconds if not completed by then.
ansible ap-worker-node1 -B 10 -a "sleep 1000"
- Use the
time
command to see how long your ad-hoc command had to run. Useman time
to see howtime
works.
# -B, --background run asynchronously, failing after N seconds
time ansible ap-worker-node1 -B 10 -a "sleep 1000"
- Now add a polling interval of 30 seconds. Run the task, and ensure with the
time
command, that it had a longer runtime.
# -P, --poll poll interval for background tasks
time ansible ap-worker-node1 -B 10 -P 30 -a "sleep 1000"
https://ansible.puzzle.ch/docs/05/
roles/apache2
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── README.md
├── tasks
│ └── main.yml #
├── templates
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml
roles/base
├── defaults
│ └── main.yml #
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── README.md
├── tasks
│ ├── main.yml #
│ ├── motd.yaml #
│ └── packages.yaml #
├── templates
│ └── motd.j2 #
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml
roles/base
├── defaults
│ └── main.yml #
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml #
├── README.md
├── tasks
│ ├── main.yml #
│ ├── motd.yaml #
│ └── packages.yaml #
├── templates
│ └── motd.j2 #
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml
https://ansible.puzzle.ch/docs/05/01/
roles/handlerrole
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml #
├── meta
│ └── main.yml
├── README.md
├── tasks
│ ├── main.yml #
│ └── timestamp.yaml #
├── templates
│ └── readme.j2 #
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml
05-download.yaml
roles/downloader/
roles/downloader ├── defaults │ └── main.yml ├── files ├── handlers │ └── main.yml ├── meta │ └── main.yml ├── README.md ├── tasks │ ├── downloadfile.yaml # │ └── main.yml # ├── templates ├── tests │ ├── inventory │ └── test.yml └── vars └── main.yml
https://ansible.puzzle.ch/docs/06/
06-secretservice.yaml
mi6.j2
- Check
ansible nodes -a "cat /etc/MI6"
- Encrypt the
secret_vars.yaml
file by usingansible-vault
with the password goldfinger.
ansible-vault encrypt playbooks/secret_vars.yaml
# New Vault password:
# Confirm New Vault password:
- Rerun the playbook providing the password for decrypting
secret_vars.yaml
at the command prompt.
ansible-playbook playbooks/06-secretservice.yaml --ask-vault-pass
- Rerun the playbook providing the password for decrypting
secret_vars.yaml
from the filevaultpassword
.vaultpassword
# decrypt
ansible-vault decrypt playbooks/secret_vars.yaml
# encrypt with vault id
ansible-vault encrypt playbooks/secret_vars.yaml --vault-id vaultpassword
# run with vault id
ansible-playbook playbooks/06-secretservice.yaml --vault-id vaultpassword
- Decrypt the file
secret_vars.yaml
.
ansible-vault decrypt playbooks/secret_vars.yaml
# Decryption successful
- Encrypt the values of the variables
username
andpassword
and put them into thesecret_vars.yaml
file.
ansible-vault encrypt_string jamesbond -n var_username
# Encryption successful
ansible-vault encrypt_string miss_moneypenny -n var_password
# Encryption successful
ansible-playbook playbooks/06-secretservice.yaml
- Remove the
/etc/MI6
file on the nodes using an ad hoc command.
ansible nodes -b -a "rm /etc/MI6"
# or
ansible nodes -b -m file -a "path=/etc/MI6 state=absent"
- Encrypt another file
secret_vars2.yaml
. Ensure it is encrypted with your vault password filevaultpassword
ansible-vault encrypt playbooks/secret_vars2.yaml
- Change the encryption of the file: encrypt it with another password provided at the command line.
ansible-vault rekey playbooks/secret_vars2.yaml --new-vault-id @prompt
# New vault password (default): test
# Confirm new vault password (default): test
# Rekey successful
# Test
ansible-vault decrypt playbooks/secret_vars2.yaml --output - --vault-id @prompt
# or
ansible-vault view playbooks/secret_vars2.yaml --vault-id @prompt
# Vault password (default): test
# ---
# var_username: jamesbond
# var_password: miss_moneypenny
- Avoid to print out sensitive data at runtime with
no_log: true
06-secretservice.yaml
https://ansible.puzzle.ch/docs/07/
- Search the Ansible Galaxy for a nginx role. https://galaxy.ansible.com/ui/
ansible-galaxy search nginx | grep nginxinc
- Install such a nginx role using
ansible-galaxy
.
ansible-galaxy install nginxinc.nginx
- Create a tar.gz file nginx.tar.gz with the content of the role using an Ansible ad hoc command.
ansible controller -m archive -a "path=$HOME/ansible-puzzle-labs/ansible/roles/nginxinc.nginx dest=$HOME/ansible-puzzle-labs/ansible/nginx.tar.gz"
- Remove the nginx role using
ansible-galaxy
.
ansible-galaxy remove nginxinc.nginx
07-requirements.yaml
- Install the role by using an appropriate
ansible-galaxy
command and therequirements.yml
file.
ansible-galaxy install -r roles/07-requirements.yaml
- Remove the role mynginx using
ansible-galaxy
.
ansible-galaxy remove mynginx
- Remove the file
nginx.tar.gz
androles/requirements.yml
by using an ad hoc command for each.
ansible controller -m file -a "path=$HOME/ansible-puzzle-labs/ansible/nginx.tar.gz state=absent"
ansible controller -m file -a "path=$HOME/ansible-puzzle-labs/ansible/roles/07-requirements.yaml state=absent"
https://ansible.puzzle.ch/docs/08/
- Create a collection with the
ansible-galaxy collection
command. Choose a namespace and a collection name of your liking.
ansible-galaxy collection init --init-path collections puzzle.mycollection
/collections/puzzle/mycollection/
- Build a collection from your newly initialized collection-skeleton. Have a close look at the name that was set.
ansible-galaxy collection build collections/puzzle/mycollection --output-path collections
- Change the namespace and collection name in the file
galaxy.yml
in the skeleton.galaxy.yml
- Rebuild the collection and see the new name.
ansible-galaxy collection build collections/puzzle/mycollection --output-path collections
- Install one of your newly built collections from the
tar.gz
file. See where it was installed.
ansible-galaxy collection install collections/puzzle-mycollection-1.0.0.tar.gz
# ...
# Installing 'puzzle.mycollection:1.0.0' to '/home/sergey/.ansible/collections/ansible_collections/puzzle/mycollection'
# ...
- Change the ansible configuration so that the collection gets installed at
/home/ansible/techlab/collections
.ansible.cfg
collections_paths
- Use
ansible-config dump
to see what default galaxy server is configured
ansible-config dump
# ...
# GALAXY_SERVER_LIST(default) = None
# ...
- Add another galaxy server to your
GALAXY_SERVER_LIST
. This entry can point to a nonexistent galaxy server.ansible.cfg
server_list
- Set it explicitly to galaxy.ansible.com in the
ansible.cfg
file, even though this is the default value.ansible.cfg
[galaxy_server.mygalaxyserver]
- Install the collection
nginxinc.nginx_controller
using theansible-galaxy
command.
ansible-galaxy collection install nginxinc.nginx_controller
- Write a requirements file
requirements.yml
that ensures the collectioncloud
fromcloudscale_ch
is installed. Install the collection by using this requirements file.08-requirements.yaml
ansible-galaxy collection install --requirements-file collections/08-requirements.yaml
- Install the collection podman from namespace containers using any of the methods you know.
ansible-galaxy collection install containers.podman --force
- Write a playbook
collection.yml
08-collection.yaml
ansible-playbook playbooks/08-collection.yaml --ask-become-pass
sudo podman ps
- Remove podman with an ad-hoc command to not interfere with the next labs.
ansible controller -b -m apt -a "name=podman state=absent purge=true autoremove=true" --ask-become-pass
https://ansible.puzzle.ch/docs/09/01/
- Ascender https://github.com/ctrliq/ascender-install:
- install with
ascender-install
script - available without subscription
- install with
- AAP (Ansible Automation Platform) https://access.redhat.com/documentation/en-us/red_hat_ansible_automation_platform/:
- install with RPM
- only available when you have valid subscription
https://ansible-community.github.io/awx-operator-helm/
helm repo add awx-operator https://ansible-community.github.io/awx-operator-helm/
kubectl apply -f awx/00-namespace.yaml
helm install awx-operator awx-operator/awx-operator -n awx
mkdir /mnt/awx-storage
chown -R 26:26 /mnt/awx-storage
chmod -R 750 /mnt/awx-storage
kubectl apply -f awx/
kubectl get pods -n awx
kubectl get svc ansible-awx-service -n awx
# <minikube ip>:<nodeport>
# username admin
# password
kubectl get secret ansible-awx-admin-password -o jsonpath="{.data.password}" -n awx | base64 --decode ; echo
https://ansible.puzzle.ch/docs/10/
https://ansible.readthedocs.io/projects/navigator/installation/
# Ready to run
ansible-navigator
ansible.cfg
https://ansible.readthedocs.io/projects/navigator/settings/#the-ansible-navigator-settings-fileansible-navigator.yaml
- Run the playbook
site.yml
by using ansible-navigator and the configuration from Task 2.
ansible-navigator
:run playbooks/10-site.yaml
# Or
ansible-navigator run playbooks/10-site.yaml
- While running the playbook, check in another terminal window if the container gets startet and stopped. You can do this by issuing
watch podman container list
.
watch docker ps
- After a successful run of your playbook, we play around with the TUI. Be sure to not let ansible-navigator run in interactive mode and not stdout mode (-m stdout). Since interactive is the default, you shouldn’t have any problems with that.
ansible-navigator run playbooks/10-site.yaml
# Or
ansible-navigator run playbooks/10-site.yaml -m interactive
- Use
ansible-navigator
to see the documentation of thefile
module.
ansible-navigator
:doc file
# Or
ansible-navigator doc file
- Use
ansible-navigator
to see the documentation of thedig
lookup plugin.
ansible-navigator
:doc dig -t lookup
# Or
ansible-navigator doc dig -t lookup
- Use
ansible-navigator
to see the current inventory.
ansible-navigator
:inventory
# Or
ansible-navigator inventory
- Use
ansible-navigator
to see the current ansible configuration.
ansible-navigator
:config
# Or
ansible-navigator config
- Replay the run by using
ansible-navigator
with the corresponding option.
ansible-navigator replay artifacts/10-site-artifact...
- Use
ansible-navigator
to show all available collections.
ansible-navigator
:collections
# Or
ansible-navigator collections
https://ansible.puzzle.ch/docs/10/01/
ansible-builder -h
- Create a playbook
container.yml
that installspodman
and pulls the imagedocker.io/bitnami/mariadb
on alldb
servers.10-continer.yaml
- Run this playbook and observe how it fails because the collection
containers.podman
is not available in the demo EEansible-navigator-demo-ee
.
ansible-navigator run playbooks/10-container.yaml
ansible-builder build -f 10-default-ee.yaml -t default-ee -vvv
ansible-navigator images
https://ansible.puzzle.ch/docs/10/02/
ansible-runner --version
ansible-runner --help
- Set up the folder structure needed by ansible-runner to find your inventory and put your playbook in the correct folder as well. https://ansible.readthedocs.io/projects/runner/en/stable/intro/
.
...
├── inventory
│ └── hosts # without extension
└── project
└── 10-site.yaml
...
- Use ansible-runner to run the play
site.yml
.
ansible-runner run . -p 10-site.yaml
- Runner artifacts directory https://ansible.readthedocs.io/projects/runner/en/stable/intro/#runner-artifacts-directory-hierarchy
artifacts/
.
├── env
│ ├── settings
│ └── ssh_key
├── inventory
│ └── hosts
└── project
└── 10-site.yaml
https://ansible.puzzle.ch/docs/11/01/
https://ansible.readthedocs.io/projects/rulebook/en/latest/rulebooks.html
python3 -m pip install ansible-rulebook
ansible-rulebook -h
- Write a rulebook
webserver_rulebook.yml
.11-webserver_rulebook.yaml
- Install
ansible.eda
collection.
ansible-galaxy collection install ansible.eda
- Start
webserver_rulebook.yml
in verbose mode.
ansible-rulebook --rulebook 11-webserver_rulebook.yaml -i inventory/hosts --verbose
- Write the rulebook
webhook_rulebook.yml
that opens a webhook on port 5000 of the control nodecontrol0
.11-webhook_rulebook.yaml
- Run the rulebook webhook_rulebook.yml in verbose mode.
ansible-rulebook --rulebook 11-webhook_rulebook.yaml -i inventory/hosts --verbose
- Send the string “webservers running” to the webhook.
curl -H 'Content-Type: application/json' -d "{\"message\": \"webservers running\"}" 127.0.0.1:5000/endpoint
- Now send the message “webservers down” to the webhook. See how the playbook webserver.yml is run.
curl -H 'Content-Type: application/json' -d "{\"message\": \"webservers down\"}" 127.0.0.1:5000/endpoint
11-complex_rulebook.yaml
Run with
ansible-rulebook --rulebook 11-complex_rulebook.yaml -i inventory/hosts --verbose
Check with
curl -H 'Content-Type: application/json' -d "{\"message\": \"webservers down\"}" 127.0.0.1:5000/endpoint
https://ansible.puzzle.ch/docs/11/02/
11-debug_event_rulebook.yaml
Run with
ansible-rulebook --rulebook 11-debug_event_rulebook.yaml -i inventory/hosts --verbose
# output
...
** 2025-03-26 22:01:37.247327 [debug] ********************************************
event: {'url_check': {'url': 'http://192.168.0.26', 'status': 'down', 'error_msg': "Cannot connect to host 192.168.0.26:80 ssl:default [Connect call failed ('192.168.0.26', 80)]"}, 'meta': {'source': {'name': 'Check webserver', 'type': 'ansible.eda.url_check'}, 'received_at': '2025-03-26T19:01:37.243178Z', 'uuid': 'b7fa6fe6-8e69-4fb8-8cd8-1fc9b7e7083c'}}
...
11-debug_event_rulebook.yaml
11-sos.yaml
- Run with
ansible-rulebook --rulebook 11-debug_event_rulebook.yaml -i inventory/hosts --verbose
- sosreports will be at
/tmp/
on node