From f07bf2a507b55cae841808b65cfaafc44bab332a Mon Sep 17 00:00:00 2001 From: Ian Wienand Date: Thu, 27 Sep 2018 15:15:58 +1000 Subject: [PATCH] Import install-docker role This is a role for installing docker on our control-plane servers. It is based on install-docker from zuul-jobs. Basic testinfra tests are added; because docker fiddles the iptables rules in magic ways, the firewall testing is moved out of the base tests and modified to partially match our base firewall configuration. Change-Id: Ia4de5032789ff0f2b07d4f93c0c52cf94aa9c25c --- .zuul.yaml | 18 +++++ playbooks/base.yaml | 5 ++ playbooks/roles/install-docker/README.rst | 16 +++++ .../roles/install-docker/defaults/main.yaml | 65 +++++++++++++++++ .../roles/install-docker/tasks/distro.yaml | 5 ++ .../roles/install-docker/tasks/main.yaml | 25 +++++++ .../roles/install-docker/tasks/upstream.yaml | 32 +++++++++ .../install-docker/templates/daemon.json.j2 | 2 + .../install-docker/templates/sources.list.j2 | 1 + playbooks/zuul/templates/gate-groups.yaml.j2 | 3 + testinfra/test_base.py | 9 ++- testinfra/test_docker.py | 26 +++++++ testinfra/test_firewall.py | 69 +++++++++++++++++++ 13 files changed, 271 insertions(+), 5 deletions(-) create mode 100644 playbooks/roles/install-docker/README.rst create mode 100644 playbooks/roles/install-docker/defaults/main.yaml create mode 100644 playbooks/roles/install-docker/tasks/distro.yaml create mode 100644 playbooks/roles/install-docker/tasks/main.yaml create mode 100644 playbooks/roles/install-docker/tasks/upstream.yaml create mode 100644 playbooks/roles/install-docker/templates/daemon.json.j2 create mode 100644 playbooks/roles/install-docker/templates/sources.list.j2 create mode 100644 testinfra/test_docker.py create mode 100644 testinfra/test_firewall.py diff --git a/.zuul.yaml b/.zuul.yaml index fde5a35498..11a08e11a6 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -215,6 +215,22 @@ - playbooks/templates/clouds/ - testinfra/test_nodepool.py +- job: + name: system-config-run-docker + parent: system-config-run + description: | + Test docker installation and setup + nodeset: + nodes: + - name: bridge.openstack.org + label: ubuntu-bionic + - name: bionic-docker + label: ubuntu-bionic + files: + - .zuul.yaml + - playbooks/roles/install-docker + - testinfra/test_docker.py + - job: name: system-config-run-dns parent: system-config-run @@ -287,6 +303,7 @@ - system-config-run-dns - system-config-run-eavesdrop - system-config-run-nodepool + - system-config-run-docker gate: jobs: - tox-linters @@ -299,3 +316,4 @@ - system-config-run-dns - system-config-run-eavesdrop - system-config-run-nodepool + - system-config-run-docker diff --git a/playbooks/base.yaml b/playbooks/base.yaml index d4165d3fe3..e70b9b2889 100644 --- a/playbooks/base.yaml +++ b/playbooks/base.yaml @@ -52,3 +52,8 @@ name: "Base: configure authoritative nameservers" roles: - nameserver + +- hosts: "docker:!disabled" + name: "Base: install and configure docker on docker hosts" + roles: + - install-docker diff --git a/playbooks/roles/install-docker/README.rst b/playbooks/roles/install-docker/README.rst new file mode 100644 index 0000000000..c27188b219 --- /dev/null +++ b/playbooks/roles/install-docker/README.rst @@ -0,0 +1,16 @@ +An ansible role to install docker in the OpenStack infra production environment + +**Role Variables** + +.. zuul:rolevar:: use_upstream_docker + :default: True + + By default this role adds repositories to install docker from upstream + docker. Set this to False to use the docker that comes with the distro. + +.. zuul:rolevar:: docker_update_channel + :default: stable + + Which update channel to use for upstream docker. The two choices are + ``stable``, which is the default and updates quarterly, and ``edge`` + which updates monthly. diff --git a/playbooks/roles/install-docker/defaults/main.yaml b/playbooks/roles/install-docker/defaults/main.yaml new file mode 100644 index 0000000000..6351680187 --- /dev/null +++ b/playbooks/roles/install-docker/defaults/main.yaml @@ -0,0 +1,65 @@ +use_upstream_docker: True +docker_update_channel: stable +ubuntu_gpg_key: | + -----BEGIN PGP PUBLIC KEY BLOCK----- + + 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 + YT90qFF93M3v01BbxP+EIY2/9tiIPbrd + =0YYh + -----END PGP PUBLIC KEY BLOCK----- diff --git a/playbooks/roles/install-docker/tasks/distro.yaml b/playbooks/roles/install-docker/tasks/distro.yaml new file mode 100644 index 0000000000..99fd589ac7 --- /dev/null +++ b/playbooks/roles/install-docker/tasks/distro.yaml @@ -0,0 +1,5 @@ +- name: Install docker + become: yes + package: + name: docker.io + state: present diff --git a/playbooks/roles/install-docker/tasks/main.yaml b/playbooks/roles/install-docker/tasks/main.yaml new file mode 100644 index 0000000000..296a1e391a --- /dev/null +++ b/playbooks/roles/install-docker/tasks/main.yaml @@ -0,0 +1,25 @@ +- name: Create docker directory + become: yes + file: + state: directory + path: /etc/docker + +- name: Install docker configuration + become: yes + template: + dest: /etc/docker/daemon.json + group: root + mode: 0644 + owner: root + src: daemon.json.j2 + +- name: Install docker-ce from upstream + include: upstream.yaml + when: use_upstream_docker + +- name: Install docker-engine from distro + include: distro.yaml + when: not use_upstream_docker + +- name: reset ssh connection to pick up docker group + meta: reset_connection diff --git a/playbooks/roles/install-docker/tasks/upstream.yaml b/playbooks/roles/install-docker/tasks/upstream.yaml new file mode 100644 index 0000000000..ca5463c3a1 --- /dev/null +++ b/playbooks/roles/install-docker/tasks/upstream.yaml @@ -0,0 +1,32 @@ +- name: Install pre-reqs + package: + name: "{{ item }}" + state: present + with_items: + - apt-transport-https + - ca-certificates + - curl + - software-properties-common + become: yes + +- name: Add docker GPG key + become: yes + apt_key: + data: "{{ ubuntu_gpg_key }}" + +# TODO(mordred) We should add a proxy cache mirror for this +- name: Add docker apt repo + become: yes + template: + dest: /etc/apt/sources.list.d/docker.list + group: root + mode: 0644 + owner: root + src: sources.list.j2 + +- name: Install docker + become: yes + apt: + name: docker-ce + state: present + update_cache: yes diff --git a/playbooks/roles/install-docker/templates/daemon.json.j2 b/playbooks/roles/install-docker/templates/daemon.json.j2 new file mode 100644 index 0000000000..2c63c08510 --- /dev/null +++ b/playbooks/roles/install-docker/templates/daemon.json.j2 @@ -0,0 +1,2 @@ +{ +} diff --git a/playbooks/roles/install-docker/templates/sources.list.j2 b/playbooks/roles/install-docker/templates/sources.list.j2 new file mode 100644 index 0000000000..7cf0b6843e --- /dev/null +++ b/playbooks/roles/install-docker/templates/sources.list.j2 @@ -0,0 +1 @@ +deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_lsb.codename }} {{ docker_update_channel }} diff --git a/playbooks/zuul/templates/gate-groups.yaml.j2 b/playbooks/zuul/templates/gate-groups.yaml.j2 index 990453e264..a8c8bfeb05 100644 --- a/playbooks/zuul/templates/gate-groups.yaml.j2 +++ b/playbooks/zuul/templates/gate-groups.yaml.j2 @@ -7,3 +7,6 @@ groups: - xenial - centos7 # note: bionic currently isn't puppeted + + docker: + - bionic-docker diff --git a/testinfra/test_base.py b/testinfra/test_base.py index 05032aa427..e55e2969d3 100644 --- a/testinfra/test_base.py +++ b/testinfra/test_base.py @@ -62,7 +62,7 @@ def test_iptables(host): rules = host.iptables.rules() rules = [x.strip() for x in rules] - start = [ + needed_rules = [ '-P INPUT ACCEPT', '-P FORWARD DROP', '-P OUTPUT ACCEPT', @@ -72,11 +72,10 @@ def test_iptables(host): '-A openstack-INPUT -p icmp -m icmp --icmp-type any -j ACCEPT', '-A openstack-INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT', '-A openstack-INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT', + '-A openstack-INPUT -j REJECT --reject-with icmp-host-prohibited' ] - assert rules[:len(start)] == start - - reject = '-A openstack-INPUT -j REJECT --reject-with icmp-host-prohibited' - assert reject in rules + for rule in needed_rules: + assert rule in rules # Make sure that the zuul console stream rule is still present zuul = ('-A openstack-INPUT -p tcp -m state --state NEW' diff --git a/testinfra/test_docker.py b/testinfra/test_docker.py new file mode 100644 index 0000000000..1b71579e67 --- /dev/null +++ b/testinfra/test_docker.py @@ -0,0 +1,26 @@ +# Copyright 2018 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +testinfra_hosts = ['bionic-docker'] + + +def test_docker_config(host): + daemon_json = host.file('/etc/docker/daemon.json') + assert daemon_json.exists + + +def test_docker_service(host): + docker = host.service('docker') + assert docker.is_running diff --git a/testinfra/test_firewall.py b/testinfra/test_firewall.py new file mode 100644 index 0000000000..b42293640a --- /dev/null +++ b/testinfra/test_firewall.py @@ -0,0 +1,69 @@ +# Copyright 2018 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import socket + +# TODO(ianw): docker fiddles the firewall rules; update these to +# handle docker too. +testinfra_hosts = ['all:!bionic-docker'] + + +def get_ips(value, family=None): + ret = set() + try: + addr_info = socket.getaddrinfo(value, None, family) + except socket.gaierror: + return ret + for addr in addr_info: + ret.add(addr[4][0]) + return ret + + +def test_iptables(host): + rules = host.iptables.rules() + rules = [x.strip() for x in rules] + + start = [ + '-P INPUT ACCEPT', + '-P FORWARD DROP', + '-P OUTPUT ACCEPT', + '-N openstack-INPUT', + '-A INPUT -j openstack-INPUT', + '-A openstack-INPUT -i lo -j ACCEPT', + '-A openstack-INPUT -p icmp -m icmp --icmp-type any -j ACCEPT', + '-A openstack-INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT', + '-A openstack-INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT', + ] + assert rules[:len(start)] == start + + reject = '-A openstack-INPUT -j REJECT --reject-with icmp-host-prohibited' + assert reject in rules + + # Make sure that the zuul console stream rule is still present + zuul = ('-A openstack-INPUT -p tcp -m state --state NEW' + ' -m tcp --dport 19885 -j ACCEPT') + assert zuul in rules + + # Ensure all IPv4+6 addresses for cacti are allowed + for ip in get_ips('cacti.openstack.org', socket.AF_INET): + snmp = ('-A openstack-INPUT -s %s/32 -p udp -m udp' + ' --dport 161 -j ACCEPT' % ip) + assert snmp in rules + + # TODO(ianw) add ip6tables support to testinfra iptables module + ip6rules = host.check_output('ip6tables -S') + for ip in get_ips('cacti.openstack.org', socket.AF_INET6): + snmp = ('-A openstack-INPUT -s %s/128 -p udp -m udp' + ' --dport 161 -j ACCEPT' % ip) + assert snmp in ip6rules