Dockerized WebServers with Dynamic Inventory and Automated using Ansible

Prathamesh Mistry
6 min readMar 24, 2021
Project Architecture

A container is a standard unit of software that packages up code and all its dependencies so the application runs quickly and reliably from one computing environment to another. Containerization will be adopted by 75% of the IT Industry by the end of next year. These containers are very light-weight and could be quickly created and destroyed and most importantly isolated.

Docker

Docker is an open platform for developing, shipping, and running applications. Docker enables you to separate your applications from your infrastructure so you can deliver software quickly. With Docker, you can manage your infrastructure in the same ways you manage your applications.

We will be configuring and deploying web servers inside docker containers.

This article covers:-

  • How to provision containers that run webservers in them.
  • Create a dynamic inventory so that further configuration could be done inside the containers and automating the same.
  • Automating everything.

Tools Used:-

  • Red Hat Enterprise Linux 8 as the base OS
  • Docker for creating containers and building images
  • Ansible for Automation

Creating the Project

Let’s start by installing Ansible and setting it up for our project

Ansible is an open-source software provisioning, configuration management, and application-deployment tool enabling infrastructure as code. Ansible code is written in YAML format.

There are various ways with which we can install ansible. One of the simplest ways is installing using pip3

# pip3 install ansible docker_with_dynamic_inventory

Create a workspace for our project

# mkdir docker_with_dynamic_inventory/
# cd docker_with_dynamic_inventory/

Create an Ansible configuration file

vim ansible.cfg[defaults]
inventory = inventory
roles=roles/
host_key_checking=False
deprecation_warnings=False
remote_user=docker
private_key_file=container_key
[privilege_escalation]
become=True
become_user=root
become_method=sudo
become_ask_pass=False

Note: do not worry about remote_user=docker and private_key_file=container_key will be explained further if you get your heads around it now

Creating an inventory

# vim inventory[docker_containers]

The group docker_containers will be filled up dynamically once the container is created.

Creating Keys

In order to ssh to a Docker Container, we need keys to access the container. Let’s create those

# ssh-keygen -f ./container_key

The above command will create 2 files. One private key(container_key) and one public key(container_key.pub). We are storing the public key inside the container and we will be using the private key to ssh to the container.

Creating a Dockerfile

We will be using the centos:7 image for configuring as a web server. We will be configuring the apache httpd server in this image. We also need to configure ssh inside the container so that we can manage this container using Ansible just like any other node in the inventory.

For this, we need to create a docker file that will build an Image with our requirements.

# vim Dockerfile/DockerfileFROM centos:7ARG USERNAME=docker
EXPOSE 22
RUN yum update -y && yum install net-tools -y && yum install openssh-server -y && yum install httpd -y sudoRUN useradd -ms /bin/bash $USERNAMERUN yum autoremove -yCOPY entrypoint.sh entrypoint.sh
RUN chmod +x /entrypoint.sh
USER $USERNAME
RUN mkdir /home/$USERNAME/.ssh
COPY container_key.pub /home/$USERNAME/.ssh/authorized_keys
USER root
RUN chown $USERNAME /home/$USERNAME/.ssh/authorized_keys && chmod 600 /home/$USERNAME/.ssh/authorized_keys
RUN ifconfig
RUN netstat -tnlp
RUN echo "${USERNAME} ALL=(ALL) NOPASSWD: ALL " >> /etc/sudoersCMD ["/entrypoint.sh"]

What is this Dockefile building?

  1. getting the base image as centos:7
  2. Exposing ssh port 22
  3. installing required files
  4. creating a user docker
  5. removing unneeded dependencies
  6. copying the entry point inside the container
  7. creating the directory that stores authorized keys using the docker user and copying the public key we created earlier in this directory
  8. changing permissions
  9. creating an entry for docker user in the sudoers file

Creating entrypoint.sh file

# vim Dockerfile/entrypoint.sh#!/usr/bin/env bash# create /var/log/auth.log if not exist
if [[ ! -f /var/log/auth.log ]]
then
sudo touch /var/log/auth.log
fi
# start ssh service
ssh-keygen -A
/usr/sbin/sshd
# start httpd service
/usr/sbin/httpd
tail -f /var/log/auth.log

This file with start ssh and httpd servers inside the container.

The tree structure till this stage should look like this:

Now Let’s create a role to build an Image from the above Dockerfile and provision a container using the newly created docker image with exposed ports

# mkdir roles/

creating a role

# ansible-galaxy init roles/docker-webserver-role

writing the tasks for the role:

# vim roles/docker-webserver-role/tasks/main.yml---
# tasks file for docker-containers-role
#
- yum_repository:
name: "docker-ce"
baseurl: "https://download.docker.com/linux/centos/7/x86_64/stable/"
description: "docker repo file"
gpgcheck: no
- package:
name: "docker-ce-18.09.1-3.el7.x86_64"
state: present
skip_broken: yes
- service:
name: "docker"
state: started
enabled: yes
- package:
name: "python3"
state: present
- pip:
name: "docker"
state: present
executable: pip3
- file:
state: directory
path: "./docker_ws_dir"
- copy:
dest: "./docker_ws_dir/index.html"
content: "This is the ansible managed webserver inside a container\n"
- docker_image:
name: "centos_ws:v1"
pull: yes
path: Dockerfile/
state: present
source: build
- docker_container:
name: "ws_container"
image: "centos_ws:v1"
state: started
detach: true
interactive: true
exposed_ports:
- "80"
ports:
- "1234:80"
volumes:
- "./docker_ws_dir:/var/www/html"
- name: Retrieve the Ip adress of the Created Container
docker_container_info:
name: "ws_container"
register: container_details
- debug:
var: container_details.container.NetworkSettings.IPAddress
- name: Add the container to the in-memory inventory
add_host:
name: "{{ container_details.container.NetworkSettings.IPAddress }}"
groups: docker_containers
- firewalld:
port: "80/tcp"
port: "1234/tcp"
state: enabled
immediate: yes
permanent: yes
- debug: var=hostvars[inventory_hostname].groups.docker_containers- name: add container ip to inventory file
lineinfile:
path: inventory
insertafter: "[docker_ws_containers]"
line: "{{ item }}"
with_items:
- "{{ hostvars[inventory_hostname].groups.docker_containers }}"
- meta: refresh_inventory

The following tasks builds an image, creates a container, provision a web server in the container, get the IP of the container(s), and store it in the inventory.

Create a Playbook to run the role

Now that we have created the Dockerfile and the role we now would like to run the role using the Playbook.

# vim provision_docker_webservers.yml---- hosts: localhost
roles:
- docker-webserver-role
- hosts: docker_containers
tasks:
- command: ifconfig eth0

In this playbook, there are 2 plays :

  1. we are using the role we created to create a container and load its IP in the inventory dynamically.
  2. Use the newly created inventory and perform some tasks on the containers. Here we are simply getting the parameters of the eth0 card of the docker

The final tree of the entire project looks like this:

# tree -L 2
.
├── ansible.cfg
├── container_key
├── Dockerfile
│ ├── container_key.pub
│ ├── Dockerfile
│ └── entrypoint.sh
├── inventory
├── provision_docker_webservers.yml
├── README.md
└── roles
└── docker-webserver-role

Note: I have moved the private key to its parent directory.

Running the Playbook

Checking that there are no running or stopped container(s).

Let’s run the playbook:

Now, Let’s check out if the inventory is updated:

Check out if the images and the container is present and running

You would notice that there is a new folder created in your workspace which contains a webpage. This directory is connected directly to the Document Root of the webserver. Updating files in this directory will update the content of the server.

Let’s update the webpage content and hit the server.

Thank You!

Get the Entire code at :

--

--

Prathamesh Mistry

Final Year Student, understanding the industrial approach and tools