How to remove outdated containers using ansible? - docker

I'm using with_sequence to iteratively create copies of a container on a single node using ansible. The number of containers is determined by a variable set at the time of deploy. This works well for increasing the number of containers to scale up, but when I reduce the number to deploy less containers the old containers are left running. Is there a way to stop the old containers? Prune won't seem to work correctly since the old containers aren't stopped.

One option is to move from Ansible to docker-compose, which knows how to scale up and scale down (and honestly provides a better use experience for manage complex Docker configurations).
Another idea would be to include one loop for starting containers, and then a second loop that attempts to remove containers up to some maximum number, like this (assuming the number of containers you want to start is in the ansible variable container_count):
---
- hosts: localhost
gather_facts: false
vars:
container_count: 4
maximum_containers: 20
tasks:
- name: Start containers
docker_container:
state: present
name: "service-{{ item }}"
image: fedora
command: "sleep inf"
loop: "{{ range(container_count|int)|list }}"
- name: Stop containers
docker_container:
state: absent
name: "service-{{ item }}"
loop: "{{ range(container_count|int, maximum_containers|int)|list }}"
Called with the default values defined in the playbook, it would create 4 containers and then attempt to delete 16 more. This is going to be a little slow, since Ansible doesn't provide any way to prematurely exit a loop, but it will work.
A third option is to replace the "Stop containers" task with a shell script, which might be slightly faster but less "ansible-like":
---
- hosts: localhost
gather_facts: false
vars:
container_count: 4
tasks:
- name: Start containers
docker_container:
state: present
name: "service-{{ item }}"
image: fedora
command: "sleep inf"
loop: "{{ range(container_count|int)|list }}"
- name: Stop containers
shell: |
let i={{ container_count }}
while :; do
name="service-$i"
docker rm -f $name || break
echo "removed $name"
let i++
done
echo "all done."
Same idea, but somewhat faster and it doesn't require you to define a maximum container count.

Related

How to force Ansible to recreate a docker container if mounted files have changed

I'm trying to get Ansible to recreate an existing docker container in case one of the mounted files have changed. I tried to use docker_containerto remove the container, if it exists and any file has changed, before I deploy it using docker_stack and a compose file. Here is the code:
- name: Template configuration files to destination
template:
...
loop:
...
register: template_files_result
- name: Get docker container name
shell: "docker ps -f 'name=some_name' -q"
register: container_id
- name: Remove container
docker_container:
name: container_id.stdout
force_kill: yes
state: absent
vars:
files_changed: "{{ template_files_result | json_query('results[*].changed') }}"
when: container_id.stdout and files_changed is any
- name: Deploy
docker_stack:
state: present
name: stack_name
compose:
- "compose.yml"
with_registry_auth: true
However, the Remove container task never does anything and I can't figure out why.
What am I missing?

ansible jenkins_plugins mdule : error when playing again a playbook : Jenkins Home not found

I have a problem with the jenkins_plugins module.
Within a playbook that pull a jenkins docker image (jenkins/jenkins:lts-alpine) and runs it to install the instance and configure it, I have a task that installs a list of plugins on an instance, which is :
- name: Install plugins
jenkins_plugin:
owner: "{{ jenkins_process_user }}"
group: "{{ jenkins_process_group }}"
name: "{{ item }}"
state: latest
timeout: 120
url: "http://{{ jenkins_hostname }}:{{ jenkins_http_port }}{{ jenkins_url_prefix }}"
url_username: "{{ jenkins_admin_name }}"
url_password: "{{ jenkins_admin_password }}"
with_dependencies: yes
loop: "{{ jenkinsPlugins }}"
register: pluginResult
until: not pluginResult.failed
retries: 5
notify: restart_image
become: True
become_user: "{{ jenkins_process_user }}"
It works correctly when the playbook is run for the first time.
All plugins are installed, and possibly retried in case of problem.
But, when I relaunch exactly the same playbook, Each and every plugin installation is retried up to the max nbr of retry and fails with (for example):
failed: [devEnv] (item=blueocean) => {"ansible_loop_var": "item", "attempts": 5, "changed": false, "item": "blueocean", "msg": "Jenkins home directory doesn't exist."}
For sure, I have verified that the jenkins home directory actually exists and has the awaited "{{ jenkins_process_user }}" and
"{{ jenkins_process_group }}" owner and group, which is jenkins:jenkins.
Note that the docker container is bound to a local directory which belongs to jenkins:jenkins. To be sure uid and gid are the same on the local machine (a VM created with vagrant) and on the container, the uid:gid are forced to 1001:1001 when starting the container.
I also have checked that it actually the case.
I really cannot explain why I get this error, which clearly makes this playbook not idempotent !
I know that there is a way to install plugins via a shell script provided by Jenkins, but I'd like to stick with ansible playbook as far as possible.
For sure, I can give the whole playbook if you need additional information.
Thanks for your help.
J-L
Ok, I understand the problem.
Reading again the jenkins_plugins documentation, and looking at the jenkins_plugins module code, I found that installation and check already installed plugin version do not run the same code (two different alternatives of a test).
And the second one needs **JENKINS_HOME** to be defined, which is optional (defaults to /var/lib/jenkins) on the module parameters. I did not set it.
Well, it is actually /var/lib/jenkins on the container, but not on the docker controler machine, which is the ansible playbook target where it is /home/jenkins/jenkins_home.
So... This question is closed, unless someone has an additional information to give. You're welcome !
Best Regards.

How to reload config with ansible docker_container module?

I am trying to accomplish docker kill -s HUP <container> in Ansible but it looks like the options I try always restart the container or attempt to instead of reloading the config.
Running the following command allows me to reload the configuration without restarting the container:
docker kill -s HUP <container>
The Ansible docker_container docs suggest the following options:
force_kill Use the kill command when stopping a running
container.
kill_signal Override default signal used to kill a running
container.
Using the kill_signal in isolation did nothing.
Below is an example of what I hoped would work:
- name: Reload haproxy config
docker_container:
name: '{{ haproxy_docker_name }}'
state: stopped
image: '{{ haproxy_docker_image }}'
force_kill: True
kill_signal: HUP
I assumed overriding force_kill and kill_signal would give me the desired behaviour. I have also tried setting state to 'started' and present.
What is the correct way to do this?
I needed to do the same with an haproxy docker instance to reload the configuration. The following worked in ansible 2.11.2:
handlers:
- name: Restart HAProxy
docker_container:
name: haproxy
state: stopped
force_kill: True
kill_signal: HUP
I went with a simple shell command, which runs whenever the docker-compose file has my service:
---
- hosts: pis
remote_user: pi
tasks:
- name: Get latest docker images
docker_compose:
project_src: dc
remove_orphans: true
pull: true
register: docker_compose_output
- name: reload prometheus
command: docker kill --signal=HUP dc_prometheus_1
when: '"prometheus" in ansible_facts'
- name: reload blackbox
command: docker kill --signal=HUP dc_blackbox_1
when: '"blackbox" in ansible_facts'
Appendix
I found some examples using GitHub advanced search, but they didn't work for me:
https://github.com/search?q=kill_signal%3A+HUP+docker+extension%3Ayml&type=Code
An example:
- name: Reload HAProxy
docker_container:
name: "haproxy"
state: started
force_kill: true
kill_signal: HUP

Ansible w/ Docker - Show current Container state

Im working on a little Ansible project in which I'm using Docker Containers.
I'll keep my question short:
I want to get the state of a running Dockercontainer!
What I mean by that is, that i want to get the current state of the container, that Docker shows you by using the "docker ps" command.
Examples would be:
Up
Exited
Restarting
I want to get one of those results from a specific container. But without using the Command or the Shell module!
KR
As of Ansible 2.8 you can use the docker_container_info, which essentially returns the input from docker inspect <container>:
- name: Get infos on container
docker_container_info:
name: my_container
register: result
- name: Does container exist?
debug:
msg: "The container {{ 'exists' if result.exists else 'does not exist' }}"
- name: Print the status of the container
debug:
msg: "The container status is {{ result.container['State']['Status'] }}"
when: result.exists
With my Docker version, State contains this:
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 8235,
"ExitCode": 0,
"Error": "",
"StartedAt": "2019-01-25T14:10:08.3206714Z",
"FinishedAt": "0001-01-01T00:00:00Z"
}
See https://docs.ansible.com/ansible/2.8/modules/docker_container_info_module.html for more details.
Unfortunately, none of the modules around docker can currently "List containers".
I did the following as work around to grab the status:
- name: get container status
shell: docker ps -a -f name={{ container }} --format {%raw%}"table {{.Status}}"{%endraw%} | awk 'FNR == 2 {print}' | awk '{print $1}'
register: status
Result will then be available in the status variable
This worked for me:
- name: Get container status
shell: docker inspect --format={{ '{{.State.Running}}' }} {{ container_name }}
register: status
#Start the container if it is not running
- name: Start the container if it is in stopeed state.
shell: docker start heuristic_mestorf
when: status.stdout != "true"
Edit: If you are running Ansible 2.8+ you can use docker_container_info. See David Pärsson's answer for details.
Here is one way to craft it using the docker_container module (note that it will create the container if it does not exist):
- name: "Check if container is running"
docker_container:
name: "{{ container_name }}"
state: present
register: container_test_started
ignore_errors: yes
- set_fact:
container_exists: "{{ container_test_started.ansible_facts is defined }}"
- set_fact:
container_is_running: "{{ container_test_started.ansible_facts is defined and container_test_started.ansible_facts.docker_container.State.Status == 'running' }}"
container_is_paused: "{{ container_test_started.ansible_facts is defined and container_test_started.ansible_facts.docker_container.State.Status == 'paused' }}"
For me the gotchya was that if the container doesn't exist, ansible_facts is not defined. If it does though, then that contains basically the whole docker inspect <container> output so I navigate that for the status.
If you just need to short circuit, a simpler alternative would be to move the desired set_fact'd value into a failed_when on the docker_container task.
I do it through set_fact to keep my options open for forking behavior elsewhere.. e.g. stop, do task, then put back how it was.
I included pause because it is commonly forgotten as a state :)
There is an ansible module docker_image_facts which give you information about images. You are looking for something that would be docker_container_facts, which does not currently exist. Good idea though.
The question is not clear, but generally speaking you can use ansible with docker in two cases:
by using docker module of ansible
http://docs.ansible.com/ansible/latest/docker_module.html
- name: data container
docker:
name: mydata
image: busybox
state: present
volumes:
- /data
by calling ansible inside Dockerfile
FROM centos7
RUN ansible-playbook -i inventory playbook.yml
Your question is slightly unclear.
My best try - you want to have output of 'docker ps' - the first thing comes in mind is to use the Ansible command module, but you don't want to use it.
Then there are few docker modules:
docker - This is the original Ansible module for managing the Docker container life cycle.
docker_container - ansible module to manage the life cycle of docker containers.
You can look into the options -> parameters to get what exactly you're looking for.
Here's the complete list of Ansible modules for managing/using Docker.

How to use wait_for in a two node cluster docker container deployment?

I have a cluster with two machines. I have a playbook to run a docker container in each one:
---
- hosts: machines_in_the_cluster
tasks:
- name: Run app container
docker:
name: "name"
image: whatever:1.0
pull: always
state: reloaded
ports:
- "8080:8080"
It starts a tomcat server in each machine. But I don't want to execute the task in the second machine until the first has finished starting tomcat.
How can I solve it? Is there any kind of health checking via http? Is there a solution using wait_for?
You can run an entire playbook in serial mode to make sure that Ansible completes the entire playbook against a subset of hosts before moving on to another set.
You can do this simply by adding the serial parameter to the playbook like so:
---
- hosts: machines_in_the_cluster
serial: 1
tasks:
- name: Run app container
...
You can specify either an absolute number of hosts to do at a time (the above example just does one at a time) or a percentage of the available hosts. Running this:
- hosts: machines_in_the_cluster
serial: 50%
tasks:
- name: Run app container
...
When you have 5 hosts in the targeted group will run 3 times, targeting the first 2 hosts, the second 2 hosts and then finally the last host.
I have solved it by adding a task to wait for the tomcat start up:
- name: Wait for server startup
local_action: wait_for host={{inventory_hostname}} port={{port}} timeout=50 delay=10
This will wait 10 seconds doing nothing and then will start polling to check if port is available. If timeout is reached, task will fail.
I have also added serial: 1 in the playbook (as ydaetskcoR mentions), in order to run tasks sequentally

Resources