diff --git a/ansible/library/kolla_docker.py b/ansible/library/kolla_docker.py index c76392eda6..22bd78b55c 100644 --- a/ansible/library/kolla_docker.py +++ b/ansible/library/kolla_docker.py @@ -27,6 +27,8 @@ import traceback import docker +from distutils.version import StrictVersion + from ansible.module_utils.basic import AnsibleModule DOCUMENTATION = ''' @@ -149,6 +151,16 @@ options: default: None choices: - host + cgroupns_mode: + description: + - Set docker cgroups namespace (default depends on Docker config) + - Supported only with Docker 20.10 (Docker API 1.41) and later + required: False + type: str + default: None + choices: + - private + - host privileged: description: - Set the container to privileged @@ -275,6 +287,9 @@ class DockerWorker(object): self.dc = get_docker_client()(**options) + self._cgroupns_mode_supported = ( + StrictVersion(self.dc._version) >= StrictVersion('1.41')) + def generate_tls(self): tls = {'verify': self.params.get('tls_verify')} tls_cert = self.params.get('tls_cert'), @@ -347,6 +362,7 @@ class DockerWorker(object): self.compare_labels(container_info) or self.compare_privileged(container_info) or self.compare_pid_mode(container_info) or + self.compare_cgroupns_mode(container_info) or self.compare_tmpfs(container_info) or self.compare_volumes(container_info) or self.compare_volumes_from(container_info) or @@ -402,6 +418,21 @@ class DockerWorker(object): if new_pid_mode != current_pid_mode: return True + def compare_cgroupns_mode(self, container_info): + if not self._cgroupns_mode_supported: + return False + new_cgroupns_mode = self.params.get('cgroupns_mode') + if new_cgroupns_mode is None: + # means we don't care what it is + return False + current_cgroupns_mode = (container_info['HostConfig'] + .get('CgroupnsMode')) + if current_cgroupns_mode == '': + # means the container was created on Docker pre-20.10 + # it behaves like 'host' + current_cgroupns_mode = 'host' + return new_cgroupns_mode != current_cgroupns_mode + def compare_privileged(self, container_info): new_privileged = self.params.get('privileged') current_privileged = container_info['HostConfig']['Privileged'] @@ -760,7 +791,16 @@ class DockerWorker(object): if binds: options['binds'] = binds - return self.dc.create_host_config(**options) + host_config = self.dc.create_host_config(**options) + + if self._cgroupns_mode_supported: + # NOTE(yoctozepto): python-docker does not support CgroupnsMode + # natively so we stuff it in manually. + cgroupns_mode = self.params.get('cgroupns_mode') + if cgroupns_mode is not None: + host_config['CgroupnsMode'] = cgroupns_mode + + return host_config def _inject_env_var(self, environment_info): newenv = { @@ -1074,6 +1114,8 @@ def generate_module(): cap_add=dict(required=False, type='list', default=list()), security_opt=dict(required=False, type='list', default=list()), pid_mode=dict(required=False, type='str', choices=['host', '']), + cgroupns_mode=dict(required=False, type='str', + choices=['private', 'host']), privileged=dict(required=False, type='bool', default=False), graceful_timeout=dict(required=False, type='int', default=10), remove_on_exit=dict(required=False, type='bool', default=True), diff --git a/releasenotes/notes/docker-cgroupns-mode-9e1b32c357a14095.yaml b/releasenotes/notes/docker-cgroupns-mode-9e1b32c357a14095.yaml new file mode 100644 index 0000000000..efc540093a --- /dev/null +++ b/releasenotes/notes/docker-cgroupns-mode-9e1b32c357a14095.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Adds support in ``kolla_docker`` module to set ``CgroupnsMode`` for Docker + containers (via ``cgroupns_mode`` module param). Requires Docker 20.10. + Note that pre-20.10 all containers behave as if they were run with mode + ``host``. diff --git a/tests/test_kolla_docker.py b/tests/test_kolla_docker.py index 29db3111f4..e681ff3d41 100644 --- a/tests/test_kolla_docker.py +++ b/tests/test_kolla_docker.py @@ -71,6 +71,8 @@ class ModuleArgsTest(base.BaseTestCase): cap_add=dict(required=False, type='list', default=list()), security_opt=dict(required=False, type='list', default=list()), pid_mode=dict(required=False, type='str', choices=['host', '']), + cgroupns_mode=dict(required=False, type='str', + choices=['private', 'host']), privileged=dict(required=False, type='bool', default=False), graceful_timeout=dict(required=False, type='int', default=10), remove_on_exit=dict(required=False, type='bool', default=True), @@ -204,12 +206,13 @@ FAKE_DATA = { } -@mock.patch("docker.APIClient") -def get_DockerWorker(mod_param, mock_dclient): +def get_DockerWorker(mod_param, docker_api_version='1.40'): module = mock.MagicMock() module.params = mod_param - dw = kd.DockerWorker(module) - return dw + with mock.patch("docker.APIClient") as MockedDockerClientClass: + MockedDockerClientClass.return_value._version = docker_api_version + dw = kd.DockerWorker(module) + return dw class TestMainModule(base.BaseTestCase): @@ -248,6 +251,15 @@ class TestMainModule(base.BaseTestCase): result=False, some_key="some_value") + def test_sets_cgroupns_mode_supported_false(self): + self.dw = get_DockerWorker(self.fake_data['params']) + self.assertFalse(self.dw._cgroupns_mode_supported) + + def test_sets_cgroupns_mode_supported_true(self): + self.dw = get_DockerWorker(self.fake_data['params'], + docker_api_version='1.41') + self.assertTrue(self.dw._cgroupns_mode_supported) + class TestContainer(base.BaseTestCase): @@ -976,6 +988,40 @@ class TestAttrComp(base.BaseTestCase): self.dw = get_DockerWorker({'pid_mode': 'host2'}) self.assertTrue(self.dw.compare_pid_mode(container_info)) + def test_compare_cgroupns_mode_neg(self): + container_info = {'HostConfig': dict(CgroupnsMode='host')} + self.dw = get_DockerWorker({'cgroupns_mode': 'host'}, + docker_api_version='1.41') + self.assertFalse(self.dw.compare_cgroupns_mode(container_info)) + + def test_compare_cgroupns_mode_neg_backward_compat(self): + container_info = {'HostConfig': dict(CgroupnsMode='')} + self.dw = get_DockerWorker({'cgroupns_mode': 'host'}, + docker_api_version='1.41') + self.assertFalse(self.dw.compare_cgroupns_mode(container_info)) + + def test_compare_cgroupns_mode_ignore(self): + container_info = {'HostConfig': dict(CgroupnsMode='private')} + self.dw = get_DockerWorker({}, docker_api_version='1.41') + self.assertFalse(self.dw.compare_cgroupns_mode(container_info)) + + def test_compare_cgroupns_mode_pos(self): + container_info = {'HostConfig': dict(CgroupnsMode='private')} + self.dw = get_DockerWorker({'cgroupns_mode': 'host'}, + docker_api_version='1.41') + self.assertTrue(self.dw.compare_cgroupns_mode(container_info)) + + def test_compare_cgroupns_mode_pos_backward_compat(self): + container_info = {'HostConfig': dict(CgroupnsMode='')} + self.dw = get_DockerWorker({'cgroupns_mode': 'private'}, + docker_api_version='1.41') + self.assertTrue(self.dw.compare_cgroupns_mode(container_info)) + + def test_compare_cgroupns_mode_unsupported(self): + container_info = {'HostConfig': dict()} + self.dw = get_DockerWorker({'cgroupns_mode': 'host'}) + self.assertFalse(self.dw.compare_cgroupns_mode(container_info)) + def test_compare_privileged_neg(self): container_info = {'HostConfig': dict(Privileged=True)} self.dw = get_DockerWorker({'privileged': True})