user1876484
user1876484

Reputation: 630

Ansible: docker related command doesn't work inside playbook, but does work once run directly

I have following task in my playbook:

- name: install pg_stat_statements extension in the postgres container
  shell: docker exec octopus_postgres_{{ group_id }} /bin/bash -c 'psql -h localhost -U postgres -p 5433 -c "CREATE EXTENSION pg_stat_statements;"' #  && service postgres restart')"
  async: 10
  poll: 0

Once I run the playbook this task seems to successfully finished, but if I check the postgres database - there are no changes to it. The task didn't actually worked.

If I run the above mentioned command manually on the host via bash, everything works fine and the databased gets updated, like this:

docker exec octopus_postgres_iaa /bin/bash -c 'psql -h localhost -U postgres -p 5433 -c "CREATE EXTENSION pg_stat_statements;"'

While trying to check what is wrong with the task, I tried the following:

- name: install pg_stat_statements extension in the postgres container
  shell: docker exec octopus_postgres_{{ group_id }} /bin/bash -c 'touch /1 && psql -h localhost -U postgres -p 5433 -c "CREATE EXTENSION pg_stat_statements;" && touch /2' #  && service postgres restart')"
  async: 10
  poll: 0

I've noticed that file /1 indeed has been created inside of the container, but the file /2 hasn't...

What is wrong with the command?

Upvotes: 1

Views: 427

Answers (1)

David Maze
David Maze

Reputation: 159382

Using docker exec at all isn't right here. In general you want to avoid it for tasks like this: if the container gets deleted and recreated, any local setup you've done with docker exec will be lost. When you're trying to make a change to some sort of server using its API, you usually just call its API, instead of getting a root shell on the server host and then doing things, but this latter step is what docker exec does.

The standard postgres image supports putting SQL fragments in a container-side /docker-entrypoint-initdb.d directory, which will get processed the very first time the container is started (only). A very typical use is to mount a host-system directory with initialization scripts on to that directory. In Ansible this might look like:

- name: create pg_stat_statements extension file
  copy:
    dest: /docker/postgres/initdb/create-stat-statements.sql
    content: |-
      CREATE EXTENSION pg_stat_statements;

- name: start postgres container
  docker_container:
    image: 'postgres:11'
    name: octopus_postgres_{{ group_id }}
    published_ports: ['5433:5432']
    volumes:
      - '/docker/postgres/initdb:/docker-entrypoint-initdb.d'
      - '/docker/postgres/data:/var/lib/postgresql/data'

Alternatively, you can manage the database like any other PostgreSQL database (local, cloud-hosted, remote, ...) using Ansible's built-in tools; in this case, the postgresql_ext module for creating extensions.

- name: enable pg_stat_statements PostgreSQL extension
  postgresql_ext:
    name: pg_stat_statements
    port: 5433

In terms of your original statement, there are probably two things going on. First of all, if you do use the docker exec path to interact with a container, you always need to use the port the server thinks it's running on and not any remapped ports from the docker run -p option or equivalents: in your statement you need to use the default port 5432 and not 5433. Secondly, since you run the task with async: 10, poll: 0, Ansible launches the task and immediately moves on to the next one, without ever checking to see whether it succeeds (see Asynchronous Actions and Polling), so you don't actually know whether or not the docker exec task succeeded. My guess is that nothing is happening because it's failing to connect to the database, but you never see this error.

Upvotes: 3

Related Questions