Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an option to configure networks and static IP addresses for individual containers #6

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .ansible-lint
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
skip_list:
- 'risky-shell-pipe'
- 'role-name'
- 'name[template]'
- 'no-handler'

warn_list:
- package-latest
Expand Down
11 changes: 6 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ on:
push:
workflow_dispatch:

env:
ANSIBLE_ROLES_PATH: roles
ANSIBLE_HASH_BEHAVIOUR: merge

jobs:
lint:
runs-on: ubuntu-latest
Expand All @@ -18,7 +22,7 @@ jobs:
python-version: '3.x'

- name: Install dependencies.
run: pip install netaddr yamllint ansible-lint ansible
run: pip install -r requirements.txt

- name: Run ansible-lint
run: "ansible-lint"
Expand All @@ -37,10 +41,7 @@ jobs:
- name: Install dependencies.
run: |
python -m pip install --upgrade pip
pip install netaddr ansible docker molecule molecule-plugins

- name: Install Galaxy dependencies.
run: ansible-galaxy collection install community.docker
pip install -r requirements.txt

- name: Run molecule
run: "molecule test"
98 changes: 73 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,86 @@ A Docker role which can be used to deploy Docker containers as SystemD services.

Installs the latest version of Docker from the official repos

Compatible with Ubuntu 20.04 and 22.04
Compatible with Ubuntu 20.04 & 22.04, as well as Debian 12.

### Usage
Create a template in the role that manages your docker container with the following contents:
```
#jinja2: trim_blocks: False
The following example describes setting up a Keycloak container.

For a full example, please refer to our [ansible-keycloak](https://github.com/punktDe/ansible-keycloak) role

* Create a template in the role that manages your docker container with the following contents:
```jinja2
{%- import (role_path + "/../docker/templates/systemd/container.service")|relpath(playbook_dir) as service with context -%}
{{ service.All(example_container) }}
{{ service.All(keycloak) }}
```

Configure the container parameters using Ansible variables
```
example_container:
container_name: example
image: example:latest
container_stop_timeout: 55
volumes:
"/etc/config.cfg": { host_dir: "/var/example/config.cfg", relabel: unshared, read_only: yes }
ports:
8080: 80
environment:
KEY: "value"
entrypoint:
/etc/entrypoint
command:
echo "hello world"
* Configure the container parameters using Ansible variables. You can add other arbitrary variables to the root of the `keycloak` dictionary (in this case, `domain` and `prefix`), and refer to them inside the same dictionary using the `vars.` prefix:
```yaml
keycloak:
domain: auth.example.com
prefix:
opt: /var/opt/keycloak
container_name: keycloak
image: quay.io/keycloak/keycloak:latest
container_stop_timeout: 55
depends_on:
- postgresql
- nginx
volumes:
"/opt/keycloak/conf":
host_dir: "{{ vars.keycloak.prefix.opt | quote }}/conf"
relabel: unshared
read_only: yes
"/opt/keycloak/themes":
host_dir: "{{ vars.keycloak.prefix.opt | quote }}/current/themes"
"/opt/keycloak/providers":
host_dir: "{{ vars.keycloak.prefix.opt | quote }}/current/providers"
ports:
127.0.0.1:8080: 8080
environment:
KEYCLOAK_FRONTEND_URL: "https://{{ vars.keycloak.domain }}/auth"
KC_PROXY: "edge"
entrypoint:
/bin/kc.sh start-dev
command:
echo "hello world"
```

Finally, provision the service file:
```
- name: Install systemd service for example_container
```yaml
- name: Install systemd service for Keycloak
template:
src: example_container.service
dest: "/etc/systemd/system/example_container.service"
src: keycloak.service
dest: "/etc/systemd/system/keycloak.service"
trim_blocks: no
```


### Custom networks
This role can be used to create custom Docker networks in the following format:
```yaml
docker:
networks:
- name: example_network
subnet: 10.22.11.0/24
- name: example_network_2
subnet: 172.156.11.0/24
```

The networks will then be created automatically on system boot using SystemD services.

A container can then be connected to a network as follows:
```yaml
example_container:
network:
name: example_network
ip: 10.22.11.21
```

If the appropriate network exists, its SystemD service will be added as a dependency to the container's service.

Alternatively, if you'd like to omit the IP address (for example, with `host` network), the following structure can be used:
```yaml
example_container:
network: example_network
```
1 change: 1 addition & 0 deletions defaults/main.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
docker:
networks: []
repository:
apt: "deb [arch={{ 'arm64' if ansible_architecture == 'aarch64' else 'amd64' }}] https://download.docker.com/linux/{{ ansible_distribution | lower }} {{ ansible_distribution_release }} stable"
key: "https://download.docker.com/linux/{{ ansible_distribution | lower }}/gpg"
Expand Down
58 changes: 58 additions & 0 deletions files/docker-network.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#! /usr/bin/env bash
action=$1
network_name=$2
subnet=$3
driver=$4

tempfolder="/tmp/docker-networks"
mkdir -p $tempfolder

case $action in
start)
docker network create --driver=$driver --subnet=$subnet $network_name
containers_in_network="$tempfolder/containers_in_network_$network_name"
if [ -f "$containers_in_network" ]; then
for i in `cat $containers_in_network`; do
echo $i
docker network connect $network_name $i;
done;
fi;
rm -f $containers_in_network
;;
reload)
containers_in_network=`docker network inspect -f '{{range .Containers}}{{.Name}} {{.IPv4Address}} {{end}}' $network_name`

if [[ -n "$containers_in_network" ]]; then
while IFS= read -r line; do
container=`echo $line | awk '{print $1}'`
docker network disconnect -f $network_name $container;
done <<< "$containers_in_network"
fi

docker network rm $network_name
docker network create --driver=$driver --subnet=$subnet $network_name

if [[ -n "$containers_in_network" ]]; then
while IFS= read -r line; do
container=`echo $line | awk '{print $1}'`
ip=`echo $line | awk '{print substr($2, 1, (length($2)-3))}'`
docker network connect $network_name $container --ip $ip;
done <<< "$containers_in_network"
fi
;;
stop)
containers_in_network=`docker network inspect -f '{{range .Containers}}{{.Name}} {{.IPv4Address}} {{end}}' $network_name`


echo $containers_in_network > "$tempfolder/containers_in_network_$network_name"

if [[ -n "$containers_in_network" ]]; then
while IFS= read -r line; do
container=`echo $line | awk '{print $1}'`
docker network disconnect -f $network_name $container;
done <<< "$containers_in_network"
fi

docker network rm $network_name
;;
esac
6 changes: 6 additions & 0 deletions meta/.requirements.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
collections:
- name: https://github.com/ansible-collections/community.general
type: git
- name: https://github.com/ansible-collections/community.docker
type: git
3 changes: 2 additions & 1 deletion molecule/default/molecule.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
dependency:
name: galaxy
name: shell
command: ansible-galaxy install -r meta/.requirements.yml -p roles/ --force
driver:
name: docker
platforms:
Expand Down
6 changes: 6 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ansible
docker
molecule
molecule-plugins
ansible-lint
yamllint
4 changes: 4 additions & 0 deletions tasks/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@

- name: Configure DNS for docker
ansible.builtin.include_tasks: dns.yaml

- name: Configure docker networks
when: docker.networks
ansible.builtin.include_tasks: networks.yaml
33 changes: 33 additions & 0 deletions tasks/networks.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
- name: Template the docker network creation services
loop: "{{ docker.networks }}"
register: docker_network_template_config_result
ansible.builtin.template:
src: systemd/docker-network.service
dest: "/etc/systemd/system/docker-network@{{ item.name }}.service"
owner: root
mode: "0644"

- name: Template the docket-network script
ansible.builtin.copy:
dest: "/usr/local/bin/docker-network.sh"
src: "files/docker-network.sh"
mode: "0755"
owner: root

- name: Activate the docker network services
loop: "{{ docker.networks }}"
register: docker_network_activated
ansible.builtin.service:
name: "docker-network@{{ item.name }}.service"
enabled: yes
state: started
daemon_reload: yes

- name: Reload docker-network@{{ item.name }}.service
loop: "{{ docker.networks }}"
when: docker_network_template_config_result.changed
ansible.builtin.service:
name: docker-network@{{ item.name }}.service
daemon_reload: yes
state: reloaded
30 changes: 28 additions & 2 deletions templates/systemd/container.service
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,26 @@ ExecStartPre=docker create --name={{ container.container_name }} --rm \
{%- for variable, value in (container.environment.items()|rejectattr('1', 'eq', none) if container.environment|default(none) else []) %}
--env={{ variable|quote }}={{ value|quote }} \
{%- endfor %}
{%- if container.network|default(none) %}
--network={{ container.network|quote }} \


{%- if container.network | default(none) %}
{%- if container.network is mapping %}

{%- if container.network.name|default(none) %}
--network={{ container.network.name|quote }} \
{%- endif %}

{%- if container.network.ip|default(none) %}
--ip={{ container.network.ip|quote }} \
{%- endif %}

{%- else %}

--network={{ container.network | quote }} \

{%- endif %}
{%- endif %}

{%- if container.entrypoint|default(none) %}
--entrypoint={{ container.entrypoint|quote }} \
{%- endif %}
Expand All @@ -76,6 +93,15 @@ WantedBy=docker.service
{% macro All(container) -%}
{{ Unit(container) }}

{%- if container.network | default(none) %}
{%- if container.network is mapping and (container.network.name | default(none)) %}
Requires=docker-network@{{ container.network.name }}.service
{%- else %}
Requires=docker-network@{{ container.network }}.service
{%- endif %}
{%- endif %}


{{ Service(container) }}

{{ Install(container) }}
Expand Down
12 changes: 12 additions & 0 deletions templates/systemd/docker-network.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[Unit]
Requires=docker.service
After=docker.service

[Service]
Type=oneshot
RemainAfterExit=true
RestartSec=10
TimeoutStopSec=60
ExecStart=/usr/local/bin/docker-network.sh start {{ item.name }} {{ item.subnet }} {{ item.driver | default("bridge") }}
ExecReload=/usr/local/bin/docker-network.sh reload {{ item.name }} {{ item.subnet }} {{ item.driver | default("bridge") }}
ExecStop=/usr/local/bin/docker-network.sh stop {{ item.name }} {{ item.subnet }} {{ item.driver | default("bridge") }}
Loading