commit da764e602e0ce9ff2f73894e875a49adf2c8e492 Author: Ludovic Cartier Date: Fri Aug 5 20:59:55 2022 +0200 initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..a17f15f --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +Docker services +=============== + +The present role : + - installs Docker on host + - installs various services through containers and docker-compose manifest + +It has bienn tested on : + - Debian 9 + - Debian 10 + - Debian 11 + +Role variables +--------------- + +| Variable | Type | Choices | Default | Comment | +|----------------------------------------------|---------|------------------------------------------------------------------------------------|-------------------------|-----------------| + +Dependencies +------------ + +None. + +Example Playbook +---------------- + + - hosts: example + ignore_errors: "{{ ansible_check_mode }}" # ignore errors only in check mode ! + + roles: + - { role: docker-services, tags: ['docker-services'] } + +Example variables +----------------- + + --- + docker_services: + - traefik + - watchtower + - grafana + + traefik_domain: 'mydomain.com' + traefik_letsencrypt_email: 'cert@mydomain.com' + +TODO +---- + +License +------- + +MIT Modern + +Author Information +------------------ + +Written by Ludovic Cartier diff --git a/TODO b/TODO new file mode 100644 index 0000000..1789f92 --- /dev/null +++ b/TODO @@ -0,0 +1,10 @@ +##### traefik +template / vars for basic auth ? for each user / pass +-> several users OK ? +global auth vs service auth ? + +#### grafana +auth / pas auth ? +push provisionner +push dashboard + diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..14ad003 --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,52 @@ +--- +# grafana +grafana_auth_anonymous_enabled: true +grafana_auth_anonymous_org_role: Editor # Viewer +grafana_auth_anonymous_org_name: 'Main Org.' +grafana_auth_disable_login_form: true +grafana_editors_can_admin: false +grafana_users_viewers_can_edit: false +grafana_log_level: error +grafana_router_logging: false +grafana_disable_sanitize_html: true + + +# provisionning dashboards +# see https://grafana.com/docs/administration/provisioning/#dashboards +awh_services_grafana_provisionning_dashboards: + apiVersion: 1 + providers: + - name: 'Grafana Dashboards' + orgId: 1 + folder: '' + folderUid: '' + type: file + disableDeletion: false + editable: true + updateIntervalSeconds: 11 + options: + path: /var/lib/grafana/dashboards + +# provisionning datasources. +# see https://grafana.com/docs/administration/provisioning/#datasources +awh_services_grafana_provisionning_datasources: + - name: loki + type: loki + access: proxy + url: http://loki:3100 + jsonData: + httpMode: GET + editable: false + isDefault: false + + #apiVersion: 1 + #datasources: + - name: prometheus + type: prometheus + access: proxy + database: prometheus + url: http://10.0.226.252:9090 + jsonData: + httpMode: GET + editable: false + isDefault: true diff --git a/files/traefik/logrotate b/files/traefik/logrotate new file mode 100644 index 0000000..f09ae7b --- /dev/null +++ b/files/traefik/logrotate @@ -0,0 +1,17 @@ +/var/log/docker/docker-daemon.log +/var/log/docker/*.log { + daily + missingok + rotate 356 + compress + compresscmd /bin/bzip2 + uncompresscmd /bin/bunzip2 + compressoptions -9 + compressext .bz2 + dateext + dateformat -%Y%m%d-%s + dateyesterday + notifempty + create 640 root adm + sharedscripts +} diff --git a/files/traefik/rsyslog b/files/traefik/rsyslog new file mode 100644 index 0000000..90b5f5b --- /dev/null +++ b/files/traefik/rsyslog @@ -0,0 +1,12 @@ +# {{ ansible_managed }} + +$Template docker_split_log,"/var/log/docker/%syslogtag:R,ERE,1,DFLT:docker_(.+)(\[[0-9]+\]):--end%.log" +$Template docker-daemon_log,"/var/log/docker/docker-daemon.log" + + +# docker services logs splitted per tag prefixed docker_ +:syslogtag, startswith, "docker_" ?docker_split_log +:syslogtag, startswith, "docker_" stop + +:syslogtag, startswith, "dockerd" ?docker-daemon_log +:syslogtag, startswith, "dockerd" stop diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..dd9645f --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,18 @@ +--- +- name: traefik-restart + systemd: + name: docker-compose@traefik + state: restarted + tags: ['docker_traefik'] + +- name: watchtower-restart + systemd: + name: docker-compose@watchtower + state: restarted + tags: ['docker_watchtower'] + +- name: grafana-restart + systemd: + name: docker-compose@grafana + state: restarted + tags: ['docker_grafana'] diff --git a/meta/main.yml b/meta/main.yml new file mode 100644 index 0000000..300c90b --- /dev/null +++ b/meta/main.yml @@ -0,0 +1,9 @@ +--- +galaxy_info: + author: Ludovic Cartier + description: Install and configure Docker and services + company: brainsys + license: GPLv3 + min_ansible_version: 2.8 + issue_tracker_url: https://github.com/brainsys-io/ansible-role-docker-services/issues + github_branch: master diff --git a/tasks/base.yml b/tasks/base.yml new file mode 100644 index 0000000..db4d819 --- /dev/null +++ b/tasks/base.yml @@ -0,0 +1,37 @@ +--- +#- name: debug +# debug: +# msg: "Service: {{ service }}" + +- name: "{{ service }} | create docker-compose directory" + file: + path: /opt/docker-compose/{{ service }} + state: directory + mode: '0755' + tags: [ 'docker_{{ service }}' ] + +- name: "{{ service }} | copy docker-compose file" + template: + src: compose/{{ service }}.yml.j2 + dest: /opt/docker-compose/{{ service }}/docker-compose.yml + owner: root + group: root + mode: 0644 + notify: "{{ service }}-restart" + tags: [ 'docker_{{ service }}' ] + +- name: "{{ service }} | install unit file to systemd" + template: + src: systemd/docker-compose.service.j2 + dest: /etc/systemd/system/docker-compose@{{ service }}.service + owner: root + group: root + mode: 0600 + tags: [ 'docker_{{ service }}' ] + +- name: "{{ service }} | enable service" + systemd: + daemon_reload: yes + name: docker-compose@{{ service }} + enabled: true + tags: [ 'docker_{{ service }}' ] diff --git a/tasks/docker.yml b/tasks/docker.yml new file mode 100644 index 0000000..430863f --- /dev/null +++ b/tasks/docker.yml @@ -0,0 +1,56 @@ +--- +- name: add official GPG key + apt_key: + url: https://download.docker.com/linux/{{ ansible_distribution | lower }}/gpg + state: present + tags: ['docker'] + +- name: add repository + apt_repository: + repo: "deb https://download.docker.com/linux/{{ ansible_distribution | lower }} {{ ansible_distribution_release }} stable" + state: present + tags: ['docker'] + +- name: install packages + apt: + name: + - docker-ce + - docker-ce-cli + - docker-compose + state: present + tags: ['docker'] + +- name: create docker-compose directory + file: + path: /opt/docker-compose + state: directory + mode: '0755' + tags: ['docker'] + +- name: install Python module + pip: + name: + - docker + - docker-compose + tags: ['docker'] + +- name: ensure Docker is started and enabled at boot + service: + name: docker + state: started + enabled: true + tags: ['docker'] + +- name: copy rsyslog config + copy: + src: traefik/rsyslog + dest: /etc/rsyslog.d/10-docker.conf + mode: '0644' + force: yes + +- name: copy logrotate config + copy: + src: traefik/logrotate + dest: /etc/logrotate.d/docker + mode: '0644' + force: yes diff --git a/tasks/grafana.yml b/tasks/grafana.yml new file mode 100644 index 0000000..584d465 --- /dev/null +++ b/tasks/grafana.yml @@ -0,0 +1,67 @@ +--- +- name: grafana | check vars are defined + assert: + that: + - grafana_admin_password is defined + - grafana_auth_anonymous_org_role is defined + - grafana_auth_anonymous_org_name is defined + - grafana_domain is defined + tags: ['docker_grafana'] + +- include_tasks: base.yml + tags: ['docker_grafana'] + +- name: grafana | create docker volume data + docker_volume: + name: grafana__var_lib_grafana + tags: ['docker_grafana'] + +- name: grafana | create provisioning dashboards docker volume + docker_volume: + name: grafana__etc_grafana_provisioning_dashboards + tags: ['docker_grafana'] + +- name: grafana | create provisioning datasources docker volume + docker_volume: + name: grafana__etc_grafana_provisioning_datasources + tags: ['docker_grafana'] + +#- name: grafana | ensure data perms +# file: +# path: '{{ item }}' +# owner: '472' +# group: '472' +# state: directory +# with_items: +# - '{{ register_docker_volume_grafana__var_lib_grafana.ansible_facts.docker_volume.Mountpoint }}' +# - '{{ register_docker_volume_grafana__var_lib_grafana.ansible_facts.docker_volume.Mountpoint }}/dashboards' +# - '{{ register_docker_volume_grafana__etc_grafana_provisioning_dashboards.ansible_facts.docker_volume.Mountpoint }}' +# - '{{ register_docker_volume_grafana__etc_grafana_provisioning_datasources.ansible_facts.docker_volume.Mountpoint }}' +# notify: 'docker restart grafana' +# tags: ['grafana'] +# +#- name: grafana | configure provisionning dashboards +# copy: +# dest: '{{ register_docker_volume_grafana__etc_grafana_provisioning_dashboards.ansible_facts.docker_volume.Mountpoint }}/local.yml' +# content: | +# {{ grafana_provisionning_dashboards|to_nice_yaml }} +# notify: 'docker restart grafana' +# tags: ['grafana'] +# +#- name: grafana | configure provisionning datasources +# copy: +# dest: '{{ register_docker_volume_grafana__etc_grafana_provisioning_datasources.ansible_facts.docker_volume.Mountpoint }}/datasources.yml' +# content: | +# {{ grafana_provisionning_datasources|to_nice_yaml }} +# notify: 'docker restart grafana' +# tags: ['grafana'] +# +#- name: grafana | download dashboard +# get_url: +# url: '{{ item.url }}' +# dest: '{{ register_docker_volume_grafana__var_lib_grafana.ansible_facts.docker_volume.Mountpoint + "/dashboards/" + item.name }}.json' +# force: '{{ item.force|default(grafana_dashboards_force|default("no")) }}' +# with_items: '{{ grafana_dashboards|default([]) }}' +# loop_control: +# label: '{{ item.name }}' +# tags: ['grafana'] diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..c545dc2 --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,17 @@ +--- +- name: requirements + include_tasks: requirements.yml + +- name: docker + include_tasks: docker.yml + +- name: services + vars: + service: "{{ item }}" + include_tasks: "{{ item }}.yml" + tags: + - docker_traefik + - docker_watchtower + - docker_grafana + with_items: + - "{{ docker_services }}" diff --git a/tasks/requirements.yml b/tasks/requirements.yml new file mode 100644 index 0000000..577ec87 --- /dev/null +++ b/tasks/requirements.yml @@ -0,0 +1,19 @@ +--- +- name: update APT Cache + apt: + update_cache: yes + cache_valid_time: 3600 + +- name: pre-requirements install + apt: + name: + - apt-transport-https + - ca-certificates + - curl + - gnupg-agent + - software-properties-common + - python3-pip + - virtualenv + - python3-setuptools + - gnupg2 + state: present diff --git a/tasks/traefik.yml b/tasks/traefik.yml new file mode 100644 index 0000000..2510c36 --- /dev/null +++ b/tasks/traefik.yml @@ -0,0 +1,37 @@ +--- +- name: traefik | check vars are defined + assert: + that: + - traefik_domain is defined + - traefik_letsencrypt_email is defined + tags: ['docker_traefik'] + +- include_tasks: base.yml + tags: ['docker_traefik'] + +- name: traefik | create docker network + docker_network: + name: 'traefik' + tags: ['docker_traefik'] + +- name: traefik | create letsencrypt docker volume + docker_volume: + name: traefik__letsencrypt + register: register_docker_volume_traefik__letsencrypt + tags: ['docker_traefik'] + +- name: traefik | create config docker volume + docker_volume: + name: traefik__etc_traefik + register: register_docker_volume_traefik__etc_traefik + tags: ['docker_traefik'] + +- name: traefik | copy configuration file + template: + src: config/traefik/traefik.yml.j2 + dest: /var/lib/docker/volumes/traefik__etc_traefik/_data/traefik.yml + owner: root + group: root + mode: 0644 + notify: traefik-restart + tags: ['docker_traefik'] diff --git a/tasks/watchtower.yml b/tasks/watchtower.yml new file mode 100644 index 0000000..e6abbf9 --- /dev/null +++ b/tasks/watchtower.yml @@ -0,0 +1,3 @@ +--- +- include_tasks: base.yml + tags: ['docker_watchtower'] diff --git a/templates/compose/grafana.yml.j2 b/templates/compose/grafana.yml.j2 new file mode 100644 index 0000000..60054db --- /dev/null +++ b/templates/compose/grafana.yml.j2 @@ -0,0 +1,61 @@ +--- +version: '3.7' + +networks: + traefik: + external: true + +volumes: + grafana__var_lib_grafana: + external: true + grafana__etc_grafana_provisioning_dashboards: + external: true + grafana__etc_grafana_provisioning_datasources: + external: true + +services: + grafana: + image: grafana/grafana:{{ grafana_version | default("latest") }} + container_name: grafana + restart: 'unless-stopped' + volumes: + - grafana__var_lib_grafana:/var/lib/grafana + - grafana__etc_grafana_provisioning_dashboards:/etc/grafana/provisioning/dashboards + - grafana__etc_grafana_provisioning_datasources:/etc/grafana/provisioning/datasources + labels: + traefik.enable: true + traefik.docker.network: traefik + traefik.http.routers.grafana.rule: Host(`{{ grafana_domain }}`) + traefik.http.routers.grafana.tls: true + traefik.http.routers.grafana.tls.certresolver: letsencrypt + traefik.http.routers.grafana.entrypoints: websecure + traefik.http.services.grafana.loadbalancer.server.port: 3000 + environment: + GF_AUTH_ANONYMOUS_ENABLED: "{{ grafana_auth_anonymous_enabled|string|lower }}" + GF_AUTH_ANONYMOUS_ORG_ROLE: "{{ grafana_auth_anonymous_org_role }}" + GF_AUTH_ANONYMOUS_ORG_NAME: "{{ grafana_auth_anonymous_org_name }}" + GF_AUTH_DISABLE_LOGIN_FORM: "{{ grafana_auth_disable_login_form|string|lower }}" + GF_AUTH_EDITORS_CAN_ADMIN: "{{ grafana_editors_can_admin|string|lower }}" + GF_SECURITY_ADMIN_PASSWORD: "{{ grafana_admin_password }}" + GF_USERS_VIEWERS_CAN_EDIT: "{{ grafana_users_viewers_can_edit|string|lower }}" + GF_ROOT_URL: "{{ grafana_domain }}" + GF_SMTP_ENABLED: "false" + GF_LOG_LEVEL: "{{ grafana_log_level|string }}" + GF_ROUTER_LOGGING: "{{ grafana_router_logging|string|lower }}" + GF_PANELS_DISABLE_SANITIZE_HTML: "{{ grafana_disable_sanitize_html|string|lower }}" +{% if grafana_install_plugins is defined %} + GF_INSTALL_PLUGINS: "{{ grafana_install_plugins|string|lower }}" +{% endif %} +{% if grafana_smtp_enabled is defined %} + GF_SMTP_ENABLED: "{{ grafana_smtp_enabled|string|lower }}" + GF_SMTP_HOST: "{{ grafana_smtp_host|string }}" + GF_SMTP_FROM_ADDRESS: "{{ grafana_smtp_from_address|string }}" + GF_SMTP_FROM_NAME: "{{ grafana_smtp_from_name|string }}" + GF_SMTP_SKIP_VERIFY: "{{ grafana_smtp_skip_verify|string|lower }}" +{% endif %} + logging: + driver: syslog + options: + tag: docker_grafana + networks: + - traefik diff --git a/templates/compose/traefik.yml.j2 b/templates/compose/traefik.yml.j2 new file mode 100644 index 0000000..0f49890 --- /dev/null +++ b/templates/compose/traefik.yml.j2 @@ -0,0 +1,45 @@ +--- +version: '3.7' + +networks: + traefik: + external: true + +volumes: + traefik__etc_traefik: + external: true + traefik__letsencrypt: + external: true + +services: + traefik: + image: traefik:latest + container_name: traefik + restart: 'unless-stopped' + ports: + - "80:80" + - "443:443" + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - traefik__etc_traefik:/etc/traefik:ro + - traefik__letsencrypt:/letsencrypt + command: + - "--providers.providersthrottleduration=100" + labels: + traefik.enable: true + traefik.http.routers.traefik.rule: Host(`{{ traefik_domain }}`) + traefik.http.routers.traefik.entrypoints: websecure + traefik.http.routers.traefik.service: api@internal + traefik.http.routers.traefik.tls: true + traefik.http.routers.traefik.tls.certresolver: letsencrypt + traefik.http.routers.traefik.middlewares: auth + traefik.http.routers.dashboard.rule: Host(`{{ traefik_domain }}`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`)) + traefik.http.middlewares.auth.basicauth.users: "ludal:$$apr1$$N3vklVTY$$zrq2kwkaVdynGlakyb4J7." + traefik.http.middlewares.auth.basicauth.realm: {{ traefik_domain}} - restricted access + logging: + driver: syslog + options: + tag: docker_traefik + networks: + - traefik + diff --git a/templates/compose/watchtower.yml.j2 b/templates/compose/watchtower.yml.j2 new file mode 100644 index 0000000..3d74fb8 --- /dev/null +++ b/templates/compose/watchtower.yml.j2 @@ -0,0 +1,22 @@ +version: '3.7' + +services: + watchtower: + image: containrrr/watchtower:latest + container_name: watchtower + restart: unless-stopped + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + environment: + TZ: {{ watchtower_tz | default("Europe/Paris") }} +{% if watchtower_notifications is defined %} + WATCHTOWER_NOTIFICATIONS: {{ watchtower_notifications }} + WATCHTOWER_NOTIFICATION_URL: {{ watchtower_notifications_url }} +{% endif %} + WATCHTOWER_SCHEDULE: "{{ watchtower_schedule | default('0 0 3 * * *') }}" + WATCHTOWER_CLEANUP: "{{ watchtower_cleanup | default('true') }}" + WATCHTOWER_DEBUG: {{ watchtower_debug | default('"false"') }} +{% if watchtower_http_api_metrics is defined %} + WATCHTOWER_HTTP_API_METRICS: "{{ watchtower_http_api_metrics }}" + WATCHTOWER_HTTP_API_TOKEN: "{{ watchtower_http_api_token | default('changeme_') }}" +{% endif %} diff --git a/templates/config/traefik/traefik.yml.j2 b/templates/config/traefik/traefik.yml.j2 new file mode 100644 index 0000000..bbda0ad --- /dev/null +++ b/templates/config/traefik/traefik.yml.j2 @@ -0,0 +1,35 @@ +log: + level: INFO + +providers: + docker: + network: traefik + exposedByDefault: false + +global: + sendAnonymousUsage: false + +api: + dashboard: true + +entryPoints: + openvpn: + address: :1194 + + web: + address: :80 + http: + redirections: + entryPoint: + to: websecure + scheme: https + + websecure: + address: :443 + +certificatesResolvers: + letsencrypt: + acme: + email: {{ traefik_letsencrypt_email }} + storage: /letsencrypt/acme.json + tlschallenge: true diff --git a/templates/systemd/docker-compose.service.j2 b/templates/systemd/docker-compose.service.j2 new file mode 100644 index 0000000..255a87f --- /dev/null +++ b/templates/systemd/docker-compose.service.j2 @@ -0,0 +1,14 @@ +[Unit] +Description=%i service with docker compose +PartOf=docker.service +After=docker.service + +[Service] +Type=oneshot +RemainAfterExit=true +WorkingDirectory=/opt/docker-compose/{{ service }} +ExecStart=/usr/bin/docker-compose up -d +ExecStop=/usr/bin/docker-compose down + +[Install] +WantedBy=multi-user.target