7

I have automated the setup of several docker containers with ansible. This works pretty well but there are certain recurring tasks that have to be executed within the docker containers.

It is possible to implement this using the docker connection driver for ansible but this only seems to work when ansible is running on the host that is the docker runner. This is not the case and I don't want to run ansible on multiple hosts if possible. Is there a better way to use ansible to execute playbooks within containers?

Notes:

  • I know how I can execute commands in a docker container from the host system but I don't only want to execute commands. I want to be able to use Ansible for docker containers as if they where standalone hosts.
  • I tried it out of curiosity and it works pretty well using ansible directly on the docker runner,... anyway not my favorite solution.
5
  • 1
    How about this stackoverflow.com/a/41626257/3888850 ?
    – Levi
    Commented Jul 4, 2018 at 16:45
  • Well thank you! Wonder why I didn't find that post on my own,.... o.O That would have some security implications but I could deal with that! Im going to report back on it when I had the opportunity to try it out,...
    – davidb
    Commented Jul 4, 2018 at 18:02
  • 1
    @Levi: thanks for your help I answered my own post describing it in a more understandable way.
    – davidb
    Commented Jul 4, 2018 at 19:54
  • In my opinion docker images should be immutable and stateless.
    – 030
    Commented Jul 5, 2018 at 5:05
  • @030 Well I think that depends on the application in the container. In my scenario signature updates & actions of the application need to be triggered centrally and I need to validate if the progress of the process in the docker container from the outside to trigger SDN Functionallities
    – davidb
    Commented Jul 5, 2018 at 6:05

2 Answers 2

14

With the help of @Levi (refered to Stackoverflow) I managed to find a way to connect straigt into the docker containers using ansibles docker connection driver and the remote API capability of Docker.

First of all you have to expose the API which is by default not the case. Simply add -H tcp://0.0.0.0:1337 to the ExecStart constant of the systemd script. Then reload the start script and restart the docker service. (From this blogpost)

Then you can list the containers in the inventory file like this:

[containers]
container-name ansible_connection=docker ansible_docker_extra_args="-H tcp://1.2.3.4:1337"

And execute any playbook like your used to.

ansible-playbook -i docker_inventory playbooks/my_playbook.yml

The upside of this way of implementing it this way is that you can mix up "real" hosts/vms that are maintained through SSH and Docker containers. One negative thing I have to mention is that the docker API is much slower than using SSH as a control tunnel.

This setup is of cause horribly insecure and its only purpose is to explain the concept. Before using it in production you should read into the security features and limit access to the daemon as far as possible.

1
  • As you said insecure for remote, but for local development is a great solution! +1 Commented Apr 4, 2020 at 15:13
0

I have achieved the same goal but a bit more securely, by forwarding the unix socket (but not a tcp socket). However it is a bit convoluted. The following example contains everything from starting an example container, forwarding the socket and tearing down everything afterwards.

In this scenario I have 3 "hosts" (in the sense of ansible hosts), localhost, the server and the container. Docker-cli must be present on localhost.

My inventory looks like this, I've explicitly specified the forwarded docker socket for the connection to the container:

---
all:
  hosts:
    server:
    container:
      ansible_connection: docker
      ansible_docker_extra_args: "-H unix:///tmp/remote_docker.sock"
  vars:
    docker_sock_path: "/tmp/remote_docker.sock"

The playbook consists of multiple plays, as it needs to apply to different hosts.

  1. At first an ssh-forwarding of the unix-socket is setup. This is done on localhost (however it's not a separate play, just delegated
  2. The docker container is started (this is done on the server). The container is started with interactive: true so it stays alive without anything to do.
  3. Tasks are run in the Container
  4. The SSH Process is killed and the socket file is deleted (this is done on localhost)
---
- name: "Preparing SSH, starting the container"
  hosts: server
  tasks:
  - name: Create SSH tunnel for the unix docker socket
    command: 
      cmd: "ssh -fnNT -L {{ docker_sock_path }}:/var/run/docker.sock {{ ansible_user }}@{{ ansible_host }}"
    delegate_to: localhost
  - name: create connection container
    community.docker.docker_container:
      name: container
      image: python:3.12-slim-bookworm
      state: started
      interactive: true
      entrypoint: "/bin/sh"

- name: "Executing steps in docker container"
  hosts: container
  tasks:
    [...]
- name: "Shutting down proxy container"
  hosts: server
  tasks:
  - name: stop connection container
    community.docker.docker_container:
      name: server
      state: stopped
- name: "Shutting down socket forwarding"
  hosts: localhost
  connection: local
  tasks:
  - name: get process ids
    shell: ps xf
    register: all_pid
  - name: extract pid of our ssh process
    ansible.builtin.set_fact:
     ssh_pid: "{{ item | trim | split | first }}"
    loop: "{{ all_pid.stdout_lines }}"
    when: item is match("^.*{{docker_sock_path }}:/var/run/docker.sock.*$")
  - name: kill ssh process
    ansible.builtin.command:
      cmd: "kill {{ ssh_pid }}"
  - name: Remove socket file
    ansible.builtin.file:
      path: "{{ docker_sock_path }}"
      state: absent

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.