Ansible: docker-based testing environment

workstation, home office, computer-405768.jpg

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.

workstation, home office, computer-405768.jpg

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.

Leave a Reply

Your email address will not be published. Required fields are marked *