diff --git a/.kitchen.yml b/.kitchen.yml index 561e276..b9f90ae 100644 --- a/.kitchen.yml +++ b/.kitchen.yml @@ -2,7 +2,7 @@ driver: name: vagrant provider: virtualbox - box: bento/debian-9.5 + box: bento/debian-10.0 provisioner: name: ansible_playbook @@ -15,6 +15,9 @@ provisioner: platforms: - name: debian-9 + provisioner: + extra_vars: + ansible_python_interpreter: python3 suites: - name: default diff --git a/Gemfile.lock b/Gemfile.lock index 528e4b4..815c92d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,43 +2,55 @@ GEM remote: https://rubygems.org/ specs: ast (2.4.0) + bcrypt_pbkdf (1.0.1) builder (3.2.3) + ed25519 (1.2.4) + equatable (0.6.1) erubis (2.7.0) - ffi (1.9.25) - gssapi (1.2.0) + ffi (1.11.1) + gssapi (1.3.0) ffi (>= 1.0.1) gyoku (1.3.1) builder (>= 2.1.2) httpclient (2.8.3) - kitchen-ansible (0.49.0) + kitchen-ansible (0.50.1) net-ssh (>= 3) - test-kitchen (~> 1.4) - kitchen-vagrant (1.3.6) - test-kitchen (~> 1.4) + test-kitchen (>= 1.4) + kitchen-vagrant (1.6.0) + test-kitchen (>= 1.4, < 3) + license-acceptance (1.0.13) + pastel (~> 0.7) + tomlrb (~> 1.2) + tty-box (~> 0.3) + tty-prompt (~> 0.18) little-plugger (1.1.4) logging (2.2.2) little-plugger (~> 1.1) multi_json (~> 1.10) - mixlib-install (3.11.5) + mixlib-install (3.11.18) mixlib-shellout mixlib-versioning thor - mixlib-shellout (2.4.2) - mixlib-versioning (1.2.2) + mixlib-shellout (2.4.4) + mixlib-versioning (1.2.7) multi_json (1.13.1) - net-scp (1.2.1) - net-ssh (>= 2.6.5) - net-ssh (4.2.0) - net-ssh-gateway (1.3.0) - net-ssh (>= 2.6.5) + necromancer (0.5.0) + net-scp (2.0.0) + net-ssh (>= 2.6.5, < 6.0.0) + net-ssh (5.2.0) + net-ssh-gateway (2.0.0) + net-ssh (>= 4.0.0) nori (2.6.0) - parallel (1.12.1) - parser (2.5.3.0) + parallel (1.17.0) + parser (2.6.3.0) ast (~> 2.4.0) + pastel (0.7.3) + equatable (~> 0.6) + tty-color (~> 0.5) powerpack (0.1.2) rainbow (2.2.2) rake - rake (12.3.2) + rake (12.3.3) rubocop (0.50.0) parallel (~> 1.10) parser (>= 2.3.3.1, < 3.0) @@ -46,22 +58,47 @@ GEM rainbow (>= 2.2.2, < 3.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) - ruby-progressbar (1.10.0) + ruby-progressbar (1.10.1) rubyntlm (0.6.2) - rubyzip (1.2.2) - test-kitchen (1.23.3) + rubyzip (1.2.3) + strings (0.1.5) + strings-ansi (~> 0.1) + unicode-display_width (~> 1.5) + unicode_utils (~> 1.4) + strings-ansi (0.1.0) + test-kitchen (2.2.5) + bcrypt_pbkdf (~> 1.0) + ed25519 (~> 1.2) + license-acceptance (~> 1.0, >= 1.0.11) mixlib-install (~> 3.6) mixlib-shellout (>= 1.2, < 3.0) - net-scp (~> 1.1) - net-ssh (>= 2.9, < 5.0) - net-ssh-gateway (~> 1.2) + net-scp (>= 1.1, < 3.0) + net-ssh (>= 2.9, < 6.0) + net-ssh-gateway (>= 1.2, < 3.0) thor (~> 0.19) winrm (~> 2.0) winrm-elevated (~> 1.0) winrm-fs (~> 1.1) thor (0.20.3) - unicode-display_width (1.4.0) - winrm (2.3.0) + tomlrb (1.2.8) + tty-box (0.4.0) + pastel (~> 0.7.2) + strings (~> 0.1.5) + tty-cursor (~> 0.7) + tty-color (0.5.0) + tty-cursor (0.7.0) + tty-prompt (0.19.0) + necromancer (~> 0.5.0) + pastel (~> 0.7.0) + tty-reader (~> 0.6.0) + tty-reader (0.6.0) + tty-cursor (~> 0.7) + tty-screen (~> 0.7) + wisper (~> 2.0.0) + tty-screen (0.7.0) + unicode-display_width (1.6.0) + unicode_utils (1.4.0) + winrm (2.3.2) builder (>= 2.1.2) erubis (~> 2.7) gssapi (~> 1.2) @@ -70,14 +107,15 @@ GEM logging (>= 1.6.1, < 3.0) nori (~> 2.0) rubyntlm (~> 0.6.0, >= 0.6.1) - winrm-elevated (1.1.0) + winrm-elevated (1.1.1) winrm (~> 2.0) winrm-fs (~> 1.0) - winrm-fs (1.3.1) + winrm-fs (1.3.2) erubis (~> 2.7) logging (>= 1.6.1, < 3.0) rubyzip (~> 1.1) winrm (~> 2.0) + wisper (2.0.0) PLATFORMS ruby @@ -89,4 +127,4 @@ DEPENDENCIES test-kitchen BUNDLED WITH - 1.16.0 + 1.17.3 diff --git a/README.md b/README.md index 0d2d606..d01e19f 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,58 @@ -# Ansible role: Common +# Ansible role: Docker Swarm [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://framagit.org/artifaille/ansible-role-docker/blob/master/LICENSE) -Install and configure docker service +Install and configure docker with swarm ## Requirements -* Ansible >= 2.7 -* Debian Stretch +* Ansible >= 2.8 +* Debian Buster ## Role variables * `docker_repo_distribution` - set the distribution for docker repository (default: `ansible_distribution_release`) -* `docker_compose_version` - set the docker-compose version to install (default: `1.23.2`) -* `docker_service_enabled` - enable systemd service (default: `yes`) +* `docker_swarm_manager` - set the cluster role (default: `false`) +* `docker_advertise_addr` - listen address +* `docker_join_token` - join token for slave node +* `docker_remote_addrs` - manager addresses for slave node * `docker_networks` - array with the docker networks ``` -- name: workgroup - subnet: 192.168.0.0/24 - gateway: 192.168.0.1 +- name: proxy + config: + - gateway: 192.168.0.1 + subnet: 192.168.0.0/16 + iprange: 192.168.0.0/16 + state: present ``` -* `docker_services` - hash with the docker-compose configuration (see [docker documentation](https://docs.docker.com/compose/compose-file/compose-file-v2/#service-configuration-reference)) +* `docker_services` - array with the service to manage ``` -wordpress: - version: '2' - services: - db: - image: mysql:5.7 - volumes: - - db_data:/var/lib/mysql - restart: always - environment: - MYSQL_ROOT_PASSWORD: somewordpress - MYSQL_DATABASE: wordpress - MYSQL_USER: wordpress - MYSQL_PASSWORD: wordpress - wordpress: - depends_on: - - db - image: wordpress:latest - ports: - - "8000:80" - restart: always - environment: - WORDPRESS_DB_HOST: db:3306 - WORDPRESS_DB_USER: wordpress - WORDPRESS_DB_PASSWORD: wordpress - volumes: - db_data: {} +- name: wordpress + image: wordpress:latest + args: + - '--api' + mounts: + - source: /opt/data + target: /usr/local/data + type: bind + networks: + - net + publish: + - published_port: 80 + target_port: 8000 + replicas: 3 + limits: + cpus: 0.5 + memory: 100M + env: + WORDPRESS_DB_HOST: db:3306 + WORDPRESS_DB_USER: wordpress + WORDPRESS_DB_PASSWORD: wordpress + container_labels: + region: FR + state: present ``` ## How to use @@ -63,6 +66,7 @@ wordpress: ``` ## Development + ### Test syntax with yamllint * install `python` and `python-pip` @@ -77,8 +81,7 @@ wordpress: ### Tests with docker -* install [docker](https://docs.docker.com/engine/installation/) -* install ruby +* install ruby, vagrant and virtualbox * install bundler `gem install bundler` * install dependencies `bundle install` * run the tests `kitchen test` diff --git a/defaults/main.yml b/defaults/main.yml index 3a79bc6..a69eca4 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -1,8 +1,4 @@ --- -docker_service_enabled: yes +docker_swarm_manager: false docker_repo_distribution: '{{ ansible_distribution_release }}' -docker_compose_version: 1.23.2 -docker_compose_url: "https://github.com/docker/compose/releases/download/\ - {{ docker_compose_version }}/docker-compose-Linux-x86_64" -docker_services: {} -docker_networks: [] +docker_services: [] diff --git a/files/docker-compose.service b/files/docker-compose.service deleted file mode 100644 index efd027e..0000000 --- a/files/docker-compose.service +++ /dev/null @@ -1,12 +0,0 @@ -[Unit] -Description= Docker compose for %i -After=network.target docker.service - -[Service] -Type=simple -ExecStart=/usr/bin/docker-compose -f /etc/docker/compose/%i/docker-compose.yml up -ExecReload=/usr/bin/docker-compose -f /etc/docker/compose/%i/docker-compose.yml up --no-recreate -d -Restart=always - -[Install] -WantedBy=multi-user.target diff --git a/handlers/main.yml b/handlers/main.yml deleted file mode 100644 index f6b7fa7..0000000 --- a/handlers/main.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -- name: reload docker-compose - systemd: - name: 'docker-compose@*' - state: reloaded diff --git a/meta/main.yml b/meta/main.yml index a4ca9a1..3cf8ee6 100644 --- a/meta/main.yml +++ b/meta/main.yml @@ -3,16 +3,17 @@ galaxy_info: role_name: docker author: Adrien Waksberg company: Artifaille - description: Install docker and configure service for docker-compose + description: Install docker and configure docker swarm license: Apache2 - min_ansible_version: 2.7 + min_ansible_version: 2.8 platforms: - name: Debian versions: - - stretch + - buster galaxy_tags: - docker + - swarm dependencies: [] diff --git a/tasks/cluster.yml b/tasks/cluster.yml new file mode 100644 index 0000000..69a5a76 --- /dev/null +++ b/tasks/cluster.yml @@ -0,0 +1,16 @@ +--- +- name: init swarm cluster + docker_swarm: + advertise_addr: '{{ docker_advertise_addr }}' + state: present + when: docker_swarm_manager + tags: docker + +- name: join swarm cluster + docker_swarm: + advertise_addr: '{{ docker_advertise_addr }}' + join_token: '{{ docker_join_token }}' + remote_addrs: '{{ docker_remote_addrs }}' + state: join + when: not docker_swarm_manager + tags: docker diff --git a/tasks/config.yml b/tasks/config.yml new file mode 100644 index 0000000..d37eaca --- /dev/null +++ b/tasks/config.yml @@ -0,0 +1,37 @@ +--- +- name: manage networks + docker_network: + name: '{{ item.name }}' + driver: '{{ item.driver|default("bridge") }}' + ipam_config: '{{ item.config|default([]) }}' + scope: swarm + state: '{{ item.state|default("present") }}' + loop: '{{ docker_networks }}' + tags: docker + +- name: manage services + docker_swarm_service: + name: '{{ item.name }}' + image: '{{ item.image }}' + args: '{{ item.args|default([]) }}' + mounts: '{{ item.mounts|default([]) }}' + networks: '{{ item.networks|default([]) }}' + publish: '{{ item.publish|default([]) }}' + replicas: '{{ item.replicas|default(1) }}' + limits: '{{ item.limits|default({}) }}' + env: '{{ item.env|default({}) }}' + container_labels: '{{ item.labels|default({}) }}' + state: present + loop: '{{ docker_services }}' + when: '"state" not in item or item.state != "absent"' + no_log: false + tags: docker + +- name: remove services + docker_swarm_service: + name: '{{ item.name }}' + state: absent + loop: '{{ docker_services }}' + when: '"state" in item and item.state == "absent"' + no_log: true + tags: docker diff --git a/tasks/install.yml b/tasks/install.yml index f8ba52c..75c55fa 100644 --- a/tasks/install.yml +++ b/tasks/install.yml @@ -8,6 +8,7 @@ - ca-certificates - gnupg2 - software-properties-common + - python3-docker - python-docker tags: docker @@ -23,32 +24,11 @@ - name: install docker package apt: - name: '{{ packages }}' - vars: - packages: - - docker-ce + name: docker-ce tags: docker -- name: install docker-compose binary - get_url: - url: '{{ docker_compose_url }}' - dest: /usr/bin/docker-compose - checksum: 'sha256:{{ docker_compose_url }}.sha256' - owner: root - group: root - mode: 0755 - tags: docker - -- name: copy systemd service - copy: - src: docker-compose.service - dest: /etc/systemd/system/docker-compose@.service - owner: root - group: root - mode: 0644 - tags: docker - -- name: reload systemd +- name: enable and start service systemd: - daemon_reload: yes + name: docker + state: started tags: docker diff --git a/tasks/main.yml b/tasks/main.yml index 626e0ab..ea7dadc 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,3 +1,5 @@ --- - import_tasks: install.yml -- import_tasks: service.yml +- import_tasks: cluster.yml +- import_tasks: config.yml + when: docker_swarm_manager diff --git a/tasks/service.yml b/tasks/service.yml deleted file mode 100644 index 208ca1a..0000000 --- a/tasks/service.yml +++ /dev/null @@ -1,39 +0,0 @@ ---- -- name: create networks - docker_network: - name: '{{ item.name }}' - ipam_options: - subnet: '{{ item.subnet }}' - gateway: '{{ item.gateway }}' - loop: '{{ docker_networks }}' - tags: docker - -- name: create docker-compose folders - file: - path: '/etc/docker/compose/{{ item }}' - owner: root - group: root - mode: 0750 - state: directory - loop: '{{ docker_services|list }}' - tags: docker - -- name: copy docker-compose configuration - copy: - content: '{{ item.value|to_nice_yaml }}' - dest: '/etc/docker/compose/{{ item.key }}/docker-compose.yml' - owner: root - group: root - mode: 0640 - loop: '{{ docker_services|dict2items }}' - notify: reload docker-compose - no_log: true - tags: docker - -- name: enable and start docker-compose - systemd: - name: 'docker-compose@{{ item }}' - state: started - enabled: '{{ docker_service_enabled }}' - loop: '{{ docker_services|list }}' - tags: docker diff --git a/test/integration/default/default.yml b/test/integration/default/default.yml index 1acc412..e8a6f03 100644 --- a/test/integration/default/default.yml +++ b/test/integration/default/default.yml @@ -2,21 +2,36 @@ - hosts: default connection: local vars: + docker_swarm_manager: true + docker_advertise_addr: 127.0.0.1 docker_networks: - - name: workgroup - subnet: 192.168.1.0/24 - gateway: 192.168.1.1 + - name: proxy + config: + - gateway: 172.45.0.1 + subnet: 172.45.0.0/16 docker_services: - test: - version: '2.4' + - name: traefik + image: traefik:1.7 networks: - default: - external: - name: workgroup - services: - test: - image: alpine:latest - entrypoint: /bin/sleep infinity - restart: always + - proxy + args: + - '--api' + - '--docker' + publish: + - published_port: 80 + target_port: 80 + mounts: + - source: /var/run/docker.sock + target: /var/run/docker.sock + - name: whoami + image: jwilder/whoami + networks: + - proxy + labels: + traefik.enable: 'true' + traefik.backend: whoami + traefik.frontend.rule: 'Host:whoami' + traefik.port: '8000' + roles: - ansible-role-docker diff --git a/test/integration/default/serverspec/default_spec.rb b/test/integration/default/serverspec/default_spec.rb index 015b957..a025157 100644 --- a/test/integration/default/serverspec/default_spec.rb +++ b/test/integration/default/serverspec/default_spec.rb @@ -7,25 +7,24 @@ puts '================================' puts %x(ansible --version) puts '================================' +sleep 60 + describe package('docker-ce') do it { should be_installed } end -describe file('/etc/docker/compose/test/docker-compose.yml') do - it { should exist } - it { should be_file } - it { should be_owned_by 'root' } - it { should be_grouped_into 'root' } - it { should be_mode 640 } - it { should contain '/bin/sleep infinity' } -end - describe command('docker network list') do its(:exit_status) { should eq 0 } - its(:stdout) { should match 'workgroup' } + its(:stdout) { should match 'proxy' } end describe command('docker ps') do its(:exit_status) { should eq 0 } - its(:stdout) { should match 'alpine' } + its(:stdout) { should match 'whoami' } + its(:stdout) { should match 'traefik' } +end + +describe command('curl -H "Host: whoami" http://127.0.0.1') do + its(:exit_status) { should eq 0 } + its(:stdout) { should match "I'm " } end