Ansible w/ Docker - Show current Container state - docker

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.

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 remove outdated containers using ansible?

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.

How to run Kafka from Jenkins Pipeline using Groovy and Kubernetes plugin?

I couldn't find such a specific command around the internet so I kindly ask for your help with this one :)
Context
I have defined a podTemplate with a few containers, by using the containerTemplate methods:
ubuntu:trusty (14.04 LTS)
postgres:9.6
and finally, wurstmeister/kafka:latest
Doing some Groovy coding in Pipeline, I install several dependencies into my ubuntu:trusty container, such as latest Git, Golang 1.9, etc., and I also checkout my project from Github.
After all that dependencies are dealt with, I manage to compile, run migrations (which means Postgres is up and running and my app is connected to it), and spin up my app just fine until it complains that Kafka is not running because it couldn't connect to any broker.
Debugging sessions
After some debug sessions I have ps aux'ed each and every container to make sure all the services I needed were running in their respective containers, such as:
container(postgres) {
sh 'ps aux' # Show Postgres, as expected
}
container(linux) {
sh 'ps aux | grep post' # Does not show Postgres, as expected
sh 'ps aux | grep kafka' # Does not show Kafka, as expected
}
container(kafka) {
sh 'ps aux' # Does NOT show any Kafka running
}
I have also exported KAFKA_ADVERTISED_HOST_NAME var to 127.0.0.1 as explained in the image docs, without success, with the following code:
containerTemplate(
name: kafka,
image: 'wurstmeister/kafka:latest',
ttyEnabled: true,
command: 'cat',
envVars: [
envVar(key: 'KAFKA_ADVERTISED_HOST_NAME', value: '127.0.0.1'),
envVar(key: 'KAFKA_AUTO_CREATE_TOPICS_ENABLE', value: 'true'),
]
)
Questions
This image documentation details https://hub.docker.com/r/wurstmeister/kafka/ is explicit about starting a Kafka cluster with docker-compose up -d
1) How do I actually do that with this Kubernetes plugin + Docker + Groovy + Pipeline combo in Jenkins?
2) Do I actually need to do that? Postgres image docs (https://hub.docker.com/_/postgres/) also mentions about running the instance with docker run, but I didn't need to do that at all, which makes me think that containerTemplate is probably doing it automatically. So why is it not doing this for the Kafka container?
Thanks!
So... problem is with this image, and way how kubernetes works with them.
Kafka does not start because you override dockers CMD with command:'cat' which causes start-kafka.sh to never run.
Because of above I suggest using different image. Below template worked for me.
containerTemplate(
name: 'kafka',
image: 'quay.io/jamftest/now-kafka-all-in-one:1.1.0.B',
resourceRequestMemory: '500Mi',
ttyEnabled: true,
ports: [
portMapping(name: 'zookeeper', containerPort: 2181, hostPort: 2181),
portMapping(name: 'kafka', containerPort: 9092, hostPort: 9092)
],
command: 'supervisord -n',
envVars: [
containerEnvVar(key: 'ADVERTISED_HOST', value: 'localhost')
]
),

Escaping double curly braces in Ansible

How to escape double curly braces in Ansible 1.9.2?
For instance, how can I escape double curly braces in the following shell command?
- name: Test
shell: "docker inspect --format '{{ .NetworkSettings.IPAddress }}' instance1"
Whenever you have problems with conflicting characters in Ansible, a rule of thumb is to output them as a string in a Jinja expression.
So instead of {{ you would use {{ '{{' }}:
- debug: msg="docker inspect --format '{{ '{{' }} .NetworkSettings.IPAddress {{ '}}' }}' instance1"
Topic "Escaping" in the Jinja2 docs.
This:
- name: Test
shell: "docker inspect --format {% raw %}'{{ .NetworkSettings.IPAddress }}' {% endraw %} instance1"
Should work
Another way to do is using backslashes like \{\{ .NetworkSettings.IPAddress \}\}
Hope it helps
Tried on with ansible 2.1.1.0
{%raw%}...{%endraw%} block seems the clear way
- name: list container images and name date on the server
shell: docker ps --format {%raw%}"{{.Image}} {{.Names}}"{%endraw%}
Only need to escape leading '{{'
tasks:
- name: list container images and names
shell: docker ps --format "{{'{{'}}.Image}} {{'{{'}}.Names}}"
No harm to escap the tailing '}}', except more difficult to read.
tasks:
- name: list container images and names
shell: docker ps --format "{{'{{'}}.Image{{'}}'}} {{'{{'}}.Names{{'}}'}}"
Backslash '\' seems do not work
New in Ansible 2.0 is the ability to declare a value as unsafe with the !unsafe tag.
In your example you could do:
- name: Test
shell: !unsafe "docker inspect --format '{{ .NetworkSettings.IPAddress }}' instance1"
See the docs for details.
I have a similar issue: i need to post a JSON doc made from a jinja2 template containing some go templates variables (yes, i know :-P), such as
"NAME_TEMPLATE": %{{service_name}}.%{{stack_name}}.%{{environment_name}}
Trying to fence this part of the template between
{% raw %} ... {% endraw %}
didn't work because there is some sort of magic in ansible which will run the template and variable substition twice (i'm not sure about that, but it definitively looks like this)
You end up with "undefined variable service_name" when trying to use the template...
So i ended up using a combination of !unsafe and {% raw %} ... {% endraw %} to define a fact that's later used in the template.
- set_fact:
__rancher_init_root_domain: "{{ rancher_root_domain }}"
#!unsafe: try to trick ansible into not doing substitutions in that string, then use %raw% so the value won't substituted another time
__rancher_init_name_template: !unsafe "{%raw%}%{{service_name}}.%{{stack_name}}.%{{environment_name}}{%endraw%}"
- name: build a template for a project
set_fact:
__rancher_init_template_doc: "{{ lookup('template', 'templates/project_template.json.j2') }}"
the template contains this:
"ROOT_DOMAIN":"{{__rancher_init_root_domain}}",
"ROUTE53_ZONE_ID":"",
"NAME_TEMPLATE":"{{__rancher_init_name_template }}",
"HEALTH_CHECK":"10000",
and the output is ok:
"NAME_TEMPLATE": "%{{service_name}}.%{{stack_name}}.%{{environment_name}}",
Here's a shorter alternative to udondan's answer; surround the whole string with double brackets:
shell: "docker inspect --format {{ '{{ .NetworkSettings.IPAddress }}' }} instance1"
The solution by using raw has been already mentioned but the command in the answer before unfortunately didn't work for me.
Without ansible:
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' docker_instance_name
With ansible:
- name: Get ip of db container
shell: "{% raw %}docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' docker_instance_name{% endraw %}"
register: db_ip_addr
- debug:
var: db_ip_addr.stdout
I managed to work around my issue using a small script:
#!/usr/bin/env bash
docker inspect --format '{{ .NetworkSettings.IPAddress }}' "$1"
And the following Ansible play
- copy:
src: files/get_docker_ip.sh
dest: /usr/local/bin/get_docker_ip.sh
owner: root
group: root
mode: 0770
- shell: "/usr/local/bin/get_docker_ip.sh {{ SWIFT_ACCOUNT_HOSTNAME }}"
register: swift_account_info
Nevertheless, it's very surprising that Ansible doesn't allow escaping double curly braces!
I was unable to get #Ben's answer to work (shell: !unsafe ...)
What follows here is a complete (and working!) answer to the OP's question, updated for Ansible >2.0
---
# file: play.yml
- hosts: localhost
connection: local
gather_facts: no
vars:
# regarding !unsafe, please see:
# https://docs.ansible.com/ansible/latest/user_guide/playbooks_advanced_syntax.html
#
- NetworkSettings_IPAddress: !unsafe "{{.NetworkSettings.IPAddress}}"
tasks:
- shell: "docker inspect --format '{{NetworkSettings_IPAddress}}' instance1"
register: out
- debug: var="{{item}}"
with_items:
- out.cmd
- out.stdout
outputs: ([WARNINGS] removed)
# ansible-playbook play.yml
PLAY [localhost] ***************************************************************
TASK [shell] *******************************************************************
changed: [localhost]
TASK [debug] *******************************************************************
ok: [localhost] => (item=out.cmd) => {
"item": "out.cmd",
"out.cmd": "docker inspect --format '{{.NetworkSettings.IPAddress}}' instance1"
}
ok: [localhost] => (item=out.stdout) => {
"item": "out.stdout",
"out.stdout": "172.17.0.2"
}
PLAY RECAP *********************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0
# ansible --version | head -1
ansible 2.6.1
Here is a mostly clean and Ansible native workaround not depending on docker --inspect with curly braces. We assume we have just referenced one container with the Ansible docker module before:
- name: query IP of client container
shell: "docker exec {{ docker_containers[0].Id }} hostname -I"
register: _container_query
- name: get IP of query result
set_fact:
_container_ip: "{{ _container_query.stdout | regex_replace('\\s','') }}"
You now have the IP of the Docker container in the Variable _container_ip. I also published this workaround on my article The Marriage of Ansible with Docker.
[Update 2015-11-03] Removed whitespaces of the stdout of the container query.
[Update 2015-11-04] BTW, there were two pull requests in the official Ansible repository, that would made this workaround needless by recovering the facts returned by the Docker module. So you could acces the IP of a docker container via docker_containers[0].NetworkSettings.IPAddress. So please vote for those pull requests:
fixed broken facts #1457
docker module: fix regressions introduced by f38186c and 80aca4b #2093

Resources