When I started using Ansible (version 1.4) docker wasn't such popular as it is nowadays and to test playbooks I used VMs created on VMware or VirtualBox. That approach was better that bare metal machines but still the environment wasn't light and flexible. Having docker-based testing environment not in place but rather at hand speed up the testing process.
Docker
First of all let me clarify what does it mean here docker-based environment. This solution is based only on docker. In other words it means that you don't find here any solutions related to:
- Docker swarm
- Kubernetes
All we need is to have docker engine installed and docker-compose. My versions are below
# docker version
Client: Docker Engine - Community
Version: 19.03.5
API version: 1.40
Go version: go1.12.12
Git commit: 633a0ea838
Built: Wed Nov 13 07:29:52 2019
OS/Arch: linux/amd64
Experimental: false
Server: Docker Engine - Community
Engine:
Version: 20.10.6
API version: 1.41 (minimum version 1.12)
Go version: go1.13.15
Git commit: 8728dd2
Built: Fri Apr 9 22:44:56 2021
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.4.4
GitCommit: 05f951a3781f4f2c1911b05e61c160e9c30eaa8e
runc:
Version: 1.0.0-rc93
GitCommit: 12644e614e25b05da6fd08a38ffa0cfe1903fdec
docker-init:
Version: 0.19.0
GitCommit: de40ad0
# docker-compose version
docker-compose version 1.29.1, build c34c88b2
docker-py version: 5.0.0
CPython version: 3.7.10
OpenSSL version: OpenSSL 1.1.0l 10 Sep 2019
Dockerfile
We can put into the image almost everything we want. However I would like to concentrate only on way how to create environment and not on possible cases to test.
Despite the lightest base image is Alpine I not recommend it due to specific version of the packages provided by this OS. I've picked up the most popular Linux distribution Ubuntu.
FROM ubuntu:20.04
RUN apt-get update && apt-get install -y openssh-server
# to prevent Missing privilege separation directory: /run/sshd
RUN mkdir /run/sshd
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]
As you can see I install only one package: SSH Server
docker-compose.yml
Source
version: '3.1'
services:
sshd01:
build: .
image: local_ansible_target
volumes:
- ./authorized_keys:/root/.ssh/authorized_keys
ports:
- '8881:22'
sshd02:
image: local_ansible_target
volumes:
- ./authorized_keys:/root/.ssh/authorized_keys
ports:
- '8882:22'
sshd03:
image: local_ansible_target
volumes:
- ./authorized_keys:/root/.ssh/authorized_keys
ports:
- '8883:22'
sshd04:
image: local_ansible_target
volumes:
- ./authorized_keys:/root/.ssh/authorized_keys
ports:
- '8884:22'
sshd05:
image: local_ansible_target
volumes:
- ./authorized_keys:/root/.ssh/authorized_keys
ports:
- '8885:22'
Few things need to be clarified. First: in the service sshd01 I'm building the image this is named local_ansible_target. Mentioned earlier Dockerfile has to be in the same directory.
I'm using key-based authentication that's why I need private and public key. Public key I put to authorized_keys and share with all containers. How to generate ssh private/public rsa key pair? Please have a look:
# ssh-keygen -t rsa -f ./id_rsa
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in ./id_rsa.
Your public key has been saved in ./id_rsa.pub.
The key fingerprint is:
SHA256:9+D6nhx+P7gbwyeTcPxgVyAFSaB1VJlZl8lfRjIL+t8 root@roma
The key's randomart image is:
+---[RSA 2048]----+
| o+B=B**|
| o o.o+O+|
| . . ..+|
| o ..|
| S + * . |
| o B * . |
| o O.+ E|
| + oo*. |
| .o*.o+.. |
+----[SHA256]-----+
Last but not least element is port exposing to the host. Or course in containers port 22 is being used whereas on the host side I pickup ones related to hostname.
Operations
How to set it up?
Create containers
# docker-compose up -d
Creating network "strategy_default" with the default driver
Building sshd01
[+] Building 1.2s (7/7) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 38B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:20.04 1.0s
=> [1/3] FROM docker.io/library/ubuntu:20.04@sha256:adf73ca014822ad8237623d388cedf4d5346aa72c270c5acc01431cc93e18e2d 0.0s
=> => resolve docker.io/library/ubuntu:20.04@sha256:adf73ca014822ad8237623d388cedf4d5346aa72c270c5acc01431cc93e18e2d 0.0s
=> CACHED [2/3] RUN apt-get update && apt-get install -y openssh-server 0.0s
=> CACHED [3/3] RUN mkdir /run/sshd 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:10baaf952dfa19bfda17a311b17cb1270dc8986e478e68ce9a12f22fb278d046 0.0s
=> => naming to docker.io/library/local_ansible_target 0.0s
WARNING: Image for service sshd01 was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating strategy_sshd02_1 ... done
Creating strategy_sshd05_1 ... done
Creating strategy_sshd04_1 ... done
Creating strategy_sshd03_1 ... done
Creating strategy_sshd01_1 ... done
List
# docker-compose ps
Name Command State Ports
------------------------------------------------------------------------------------
strategy_sshd01_1 /usr/sbin/sshd -D Up 0.0.0.0:8881->22/tcp,:::8881->22/tcp
strategy_sshd02_1 /usr/sbin/sshd -D Up 0.0.0.0:8882->22/tcp,:::8882->22/tcp
strategy_sshd03_1 /usr/sbin/sshd -D Up 0.0.0.0:8883->22/tcp,:::8883->22/tcp
strategy_sshd04_1 /usr/sbin/sshd -D Up 0.0.0.0:8884->22/tcp,:::8884->22/tcp
strategy_sshd05_1 /usr/sbin/sshd -D Up 0.0.0.0:8885->22/tcp,:::8885->22/tcp
Inventory
The time has come for first Ansible's part: Inventory
all:
hosts:
sshd01:
ansible_port: 8881
sshd02:
ansible_port: 8882
sshd03:
ansible_port: 8883
sshd04:
ansible_port: 8884
sshd05:
ansible_port: 8885
vars:
ansible_host: localhost
ansible_ssh_connection: ssh
ansible_ssh_private_key_file: id_rsa
ansible_ssh_transfer_method: scp
As you can see all ansible_host
is set to localhost however it will not be a local connection. For hosts I specified only port which should reflect these from docker-compose.yml
file. ansible_ssh_private_key_file
points to the private key we generated at the begging.
To have all in place please create Ansible's configuration
[defaults]
host_key_checking = False
inventory = hosts
[ssh_connection]
scp_if_ssh = True
Ansible Playbook
For the purpose of this post below playbook is enough. However, this is just starting point.
---
- name: PLAY TEST
hosts: all
remote_user: root
gather_facts: no
tasks:
- name: Task1
shell: hostanme
Testing the environment
Now our Ansible testing environment is ready to use.
# ansible-playbook playbook.yml -v
Using ansible.cfg as config file
PLAY [PLAY TEST] **********************************************************************************************************************************************************
TASK [Task1] **************************************************************************************************************************************************************
changed: [sshd05] => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"}, "changed": true, "cmd": "hostname", "delta": "0:00:00.003674", "end": "2021-06-09 20:36:35.755540", "rc": 0, "start": "2021-06-09 20:36:35.751866", "stderr": "", "stderr_lines": [], "stdout": "abb91d77c385", "stdout_lines": ["abb91d77c385"]}
changed: [sshd02] => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"}, "changed": true, "cmd": "hostname", "delta": "0:00:00.003041", "end": "2021-06-09 20:36:35.773521", "rc": 0, "start": "2021-06-09 20:36:35.770480", "stderr": "", "stderr_lines": [], "stdout": "3fbffffdd06f", "stdout_lines": ["3fbffffdd06f"]}
changed: [sshd04] => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"}, "changed": true, "cmd": "hostname", "delta": "0:00:00.003345", "end": "2021-06-09 20:36:35.781516", "rc": 0, "start": "2021-06-09 20:36:35.778171", "stderr": "", "stderr_lines": [], "stdout": "9671084e7614", "stdout_lines": ["9671084e7614"]}
changed: [sshd01] => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"}, "changed": true, "cmd": "hostname", "delta": "0:00:00.003692", "end": "2021-06-09 20:36:35.800871", "rc": 0, "start": "2021-06-09 20:36:35.797179", "stderr": "", "stderr_lines": [], "stdout": "c14313afc815", "stdout_lines": ["c14313afc815"]}
changed: [sshd03] => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"}, "changed": true, "cmd": "hostname", "delta": "0:00:00.003777", "end": "2021-06-09 20:36:35.813409", "rc": 0, "start": "2021-06-09 20:36:35.809632", "stderr": "", "stderr_lines": [], "stdout": "3c16c45d680f", "stdout_lines": ["3c16c45d680f"]}
PLAY RECAP ****************************************************************************************************************************************************************
sshd01 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
sshd02 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
sshd03 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
sshd04 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
sshd05 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Summary
Firstly, Ansible testing environment is something what can speed up your writing playbooks process. Therefore, it is worth have. In addition if you are starting the journey with Ansible it can improve your learning curve by giving you fast access to as many targets as you machine is able to create. Moreover if you are advanced user please consider it a nice playground for you tests.