This project automates the provisioning of a simulated environment and deploys an Nginx server that acts as a reverse proxy to an http-https-echo service.
The entire process is orchestrated by a single start.sh script which utilizes Docker, Docker Compose, and Ansible.
├── ansible
│ ├── echo-compose.yaml # Compose file
│ ├── inventory.ini # Ansible inventory configuration
│ ├── nginx.conf # Nginx configuration for reverse-proxy
│ ├── ansible.cfg # Ansible user configuration
│ └── deployment.yaml # Ansible playbook to provision and deploy applications
├── docker-compose.yml # Container configuration
├── Dockerfile.a # Container A definition (controller)
├── Dockerfile.b # Container B definition
└── start.sh # Entry script
- Host: The primary machine running Docker Desktop, which hosts the entire simulated environment.
instance_a(Docker Container on Host):- Acts as the Ansible control node.
- Initiates deployments to
instance_b's Docker daemon and the Host's Docker daemon. - Has the Ansible project files and SSH private key mounted into it.
instance_b(Docker Container on Host):- Hosts its own isolated Docker daemon (Docker-in-Docker).
- The
http-echo-bservice runs within this internal Docker environment.
http-echo-b(Docker Container insideinstance_b's Docker):- Runs the
mendhak/http-https-echoimage. - Deployed via
echo-compose.yamlorchestrated by Ansible running oninstance_a.
- Runs the
nginx_ansible_direct_test(Docker Container on Host):- An Nginx server running directly on the Host's Docker daemon.
- Controlled by Ansible running on
instance_a(via a mounted Docker socket). - Configured to proxy requests to
http-echo-b.
provisioning_network_global(Docker Network on Host):- A custom Docker bridge network on the Host.
- Connects
instance_a,instance_b, andnginx_ansible_direct_test, enabling them to communicate by service name.
Pre-step needed:
- The variables
HOST_GIDandHOST_UIDshould be set (e.g.export HOST_GID=$(id -g)andexport_HOST_UID=$(id -u)) - Before running the scripts, the user should adapt the local path
/Users/franco/git/anybotics_challenge/ansible/nginx_host_docker_configs/nginx.confto their local full path of./ansible/nginx.conf. For an explanation check the section Limitations below.
- The user executes
./start.shon the Host. The cwd should be the repository root. - The script generates an SSH key pair (
~/.ssh/ansible_keyand~/.ssh/ansible_key.pub) if it doesn't already exist, for SSH access into the instances. - The top-level
docker-compose.ymlis invoked bystart.shto build and startinstance_aandinstance_bcontainers. These containers are connected to theprovisioning_network_globalon the Host. start.shthen executes the main Ansible playbook (ansible/working_playbook.yaml) from within theinstance_acontainer.- The Ansible playbook performs the following:
- Installs Docker and its dependencies on both
instance_a(if needed for Ansible modules) andinstance_b. - Configures
instance_b's internal Docker daemon to listen on a TCP socket (e.g.,tcp://0.0.0.0:2375) for remote commands frominstance_a. - Ensures a network (e.g.,
provisioning_network_global) exists insideinstance_b's Docker environment for thehttp-echo-bservice (ifecho-compose.yamldeclares it as external). - Deploys
http-echo-btoinstance_b's Docker daemon using theansible/echo-compose.yamlfile. - Copies the
nginx.confto a location oninstance_athat is shared/mounted from the Host (e.g.,/home/ansible/ansible_project/nginx_host_docker_configs/nginx.confinsideinstance_amaps to/Users/your_user/.../nginx_host_docker_configs/nginx.confon the host). - Deploys the
nginx_ansible_direct_testcontainer to the Host's Docker daemon, using the mountednginx.conf.
- Installs Docker and its dependencies on both
- The setup is complete, with Nginx on the Host proxying requests from
localhost:80to thehttp-echo-bservice running withininstance_b.
- Docker & General Containerization:
- Named Containers: (e.g.,
instance_a,nginx_ansible_direct_test) for easier reference. - Custom Networks: (e.g.,
provisioning_network_global) for controlled communication. - Explicit Port Mappings: Clearly defined for all exposed services.
- Read-Only Volumes: (e.g.,
nginx.confmounted as:ro) to enhance security. - Use of Official Images: (e.g.,
nginx:latest,mendhak/http-https-echo:latest).
- Named Containers: (e.g.,
- Docker Compose (for
echo-compose.yamland top-leveldocker-compose.yml):- Service Definitions: Clear separation of services.
- Declarative Configuration: Defining the desired state of services, networks, and volumes.
- Environment Isolation:
instance_brunning its own Docker daemon provides a level of isolation.
- Ansible for Docker Provisioning & Deployment:
- Idempotency: Efforts made in Ansible tasks to be idempotent (e.g.,
|| truein shell commands for creation) - Modularity: Using Ansible modules for specific tasks where possible (e.g.,
ansible.builtin.copy,community.docker.docker_container). - Configuration Management: Managing
daemon.jsonandnginx.confthrough Ansible. - Orchestration: Managing multi-step, multi-host deployments.
- Idempotency: Efforts made in Ansible tasks to be idempotent (e.g.,
- The Ansible playbook includes a URI check (
Test Nginx proxying to http-echo) as a basic end-to-end test of the deployed services.
- As seen in the core components section, the Docker-in-Docker setup does not use the Docker daemon of
instance_ato deploy the nginx container as it's flaky, but instead relies in the host's Docker daemon - Although exhaustively troubleshooted, the Ansible tasks do not behave well when using the docker compose plugin and external networks. Thus, the deployment of the nginx container is done with bare docker commands instead
- The latter is inconsitent with the deployment of the https-http-echo container, which uses docker-compose
- Furthermore, the SSH keys setup in the host machine does not behave well when working with Docker-in-Docker deployments. Hence, the Ansible configuration should not pass
ssh_args = -o IdentityFilePermissions=0600 - In a production setting, specific versions should be used for the docker images
- The Docker TCP socket on
instance_bis exposed without TLS for simplicity. A true production-grade deployment should address this. - In order to overcome the quirks of a Docker-in-Docker setup,
privileged:truehad to be used in order tu grant nested containers access to the host Docker socket. This should be reviewed in production. - The user entry-point
start.shshould ideally be more resilient and include full tear-down options for a better user-experience - For observability, the app containers could forward their logs to a centralized logging aggregation system
- Similarly, the
http-https-echoandnginxservices should provide metrics like http requests, active connections, etc