From 085cf7d1759879169dddc69378ad784e1acaf524 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Sat, 20 Feb 2021 00:50:27 +0000 Subject: [PATCH] Adds support for configuring chrony Some hosts in the kayobe inventory might not be in the kolla-ansible inventory so it makes sense for kayobe to manage NTP. Change-Id: Iacb579a46b0e9769a4c404a858d17968f74dd7e0 Depends-On: https://review.opendev.org/c/openstack/kayobe-config-dev/+/786040 Story: 2007872 Task: 40240 --- ansible/group_vars/all/kolla | 2 +- ansible/group_vars/all/time | 33 +++++++++++++ ansible/roles/ntp/defaults/main.yml | 6 +++ ansible/roles/ntp/tasks/deploy.yml | 4 ++ ansible/roles/ntp/tasks/main.yml | 12 +++++ ansible/roles/ntp/tasks/prepare.yml | 27 +++++++++++ ansible/roles/ntp/tasks/validate.yml | 11 +++++ ansible/time.yml | 18 +++++++ ansible/timezone.yml | 28 ++++++++--- doc/source/configuration/reference/hosts.rst | 47 ++++++++++++++----- etc/kayobe/inventory/groups | 8 +++- etc/kayobe/time.yml | 26 ++++++++++ kayobe/cli/commands.py | 12 ++--- kayobe/tests/unit/cli/test_commands.py | 6 +-- .../overrides.yml.j2 | 8 ++++ .../tests/test_overcloud_host_configure.py | 37 +++++++++++++++ ...l-chrony-on-the-host-283fa6fdd9cf9b4c.yaml | 11 +++++ requirements.yml | 2 + 18 files changed, 269 insertions(+), 29 deletions(-) create mode 100644 ansible/roles/ntp/defaults/main.yml create mode 100644 ansible/roles/ntp/tasks/deploy.yml create mode 100644 ansible/roles/ntp/tasks/main.yml create mode 100644 ansible/roles/ntp/tasks/prepare.yml create mode 100644 ansible/roles/ntp/tasks/validate.yml create mode 100644 ansible/time.yml create mode 100644 releasenotes/notes/install-chrony-on-the-host-283fa6fdd9cf9b4c.yaml diff --git a/ansible/group_vars/all/kolla b/ansible/group_vars/all/kolla index 33aef2c69..932ce4738 100644 --- a/ansible/group_vars/all/kolla +++ b/ansible/group_vars/all/kolla @@ -470,7 +470,7 @@ kolla_enable_barbican: "no" kolla_enable_blazar: "no" kolla_enable_ceilometer: "no" kolla_enable_central_logging: "no" -kolla_enable_chrony: "yes" +kolla_enable_chrony: "no" kolla_enable_cinder: "no" kolla_enable_cinder_backend_iscsi: "{{ kolla_enable_cinder_backend_lvm | bool or kolla_enable_cinder_backend_zfssa_iscsi | bool }}" kolla_enable_cinder_backend_lvm: "no" diff --git a/ansible/group_vars/all/time b/ansible/group_vars/all/time index a2b277c1e..6ab7708e1 100644 --- a/ansible/group_vars/all/time +++ b/ansible/group_vars/all/time @@ -6,3 +6,36 @@ # Name of the local timezone. timezone: "{{ ansible_date_time.tz }}" + +############################################################################### +# Network Time Protocol (NTP). + +# Kayobe default time sources +chrony_ntp_servers_default: + - server: pool.ntp.org + type: pool + options: + - option: iburst + - option: minpoll + val: 8 + +# List of NTP time sources to configure. Format is a list of dictionaries with +# the following keys: +# server: host or pool +# type: (Optional) Defaults to server. Maps to a time source in the +# configuration file. Can be one of server, peer, pool. +# options: (Optional) List of options that depends on type, see Chrony +# documentation for details. +# See: https://chrony.tuxfamily.org/doc/4.0/chrony.conf.html +# +# Example of configuring a pool and customising the pool specific maxsources +# option: +# chrony_ntp_servers: +# - server: pool.ntp.org +# type: pool +# options: +# - option: maxsources +# val: 3 +# +chrony_ntp_servers: "{{ chrony_ntp_servers_default }}" +############################################################################### diff --git a/ansible/roles/ntp/defaults/main.yml b/ansible/roles/ntp/defaults/main.yml new file mode 100644 index 000000000..dea948074 --- /dev/null +++ b/ansible/roles/ntp/defaults/main.yml @@ -0,0 +1,6 @@ +--- +ntp_actions: ["validate", "prepare", "deploy"] + +ntp_service_disable_list: + - ntp.service + - systemd-timesyncd.service diff --git a/ansible/roles/ntp/tasks/deploy.yml b/ansible/roles/ntp/tasks/deploy.yml new file mode 100644 index 000000000..eee45417c --- /dev/null +++ b/ansible/roles/ntp/tasks/deploy.yml @@ -0,0 +1,4 @@ +--- +- name: Import role to configure chrony + import_role: + name: mrlesmithjr.chrony diff --git a/ansible/roles/ntp/tasks/main.yml b/ansible/roles/ntp/tasks/main.yml new file mode 100644 index 000000000..efb655192 --- /dev/null +++ b/ansible/roles/ntp/tasks/main.yml @@ -0,0 +1,12 @@ +--- +- name: Validate configuration + include_tasks: validate.yml + when: '"validate" in ntp_actions' + +- name: Pre-deploy preparation + include_tasks: prepare.yml + when: '"prepare" in ntp_actions' + +- name: Deploy service + include_tasks: deploy.yml + when: '"deploy" in ntp_actions' diff --git a/ansible/roles/ntp/tasks/prepare.yml b/ansible/roles/ntp/tasks/prepare.yml new file mode 100644 index 000000000..b9c2b70d0 --- /dev/null +++ b/ansible/roles/ntp/tasks/prepare.yml @@ -0,0 +1,27 @@ +--- + +- name: Populate service facts. + service_facts: + +- name: Mask alternative NTP clients to prevent conflicts + vars: + service_exists: "{{ item in services }}" + systemd: + name: "{{ item }}" + enabled: "{{ 'false' if service_exists else omit }}" + masked: true + state: "{{ 'stopped' if service_exists else omit }}" + become: true + with_items: "{{ ntp_service_disable_list }}" + +- name: Remove kolla-ansible installed chrony container + docker_container: + name: chrony + state: absent + become: true + # NOTE(wszumski): There is an ordering issue where on a fresh host, docker + # will not have been configured, but if that is the case, the chrony container + # can't possibly exist, but trying to execute this unconditionally will fail + # with: No module named 'docker' as we have not yet added the docker package + # to the kayobe target venv. + when: "'docker.service' in services" diff --git a/ansible/roles/ntp/tasks/validate.yml b/ansible/roles/ntp/tasks/validate.yml new file mode 100644 index 000000000..0d1b4266e --- /dev/null +++ b/ansible/roles/ntp/tasks/validate.yml @@ -0,0 +1,11 @@ +--- + +- name: Validate structure of chrony_ntp_servers dictionary + assert: + that: + - chrony_ntp_servers is sequence + - chrony_ntp_servers | selectattr('server', 'undefined') | list | length == 0 + msg: "chrony_ntp_servers set to invalid value" + when: + - chrony_ntp_servers is defined + - chrony_ntp_servers | length > 0 diff --git a/ansible/time.yml b/ansible/time.yml new file mode 100644 index 000000000..609723fa1 --- /dev/null +++ b/ansible/time.yml @@ -0,0 +1,18 @@ +--- +- name: Ensure timezone is configured + hosts: seed-hypervisor:seed:overcloud + tags: + - timezone + tasks: + - import_role: + name: yatesr.timezone + become: True + +- name: Ensure Chrony is installed and configured + hosts: ntp + tags: + - ntp + tasks: + - import_role: + name: ntp + when: not kolla_enable_chrony | bool diff --git a/ansible/timezone.yml b/ansible/timezone.yml index 2bf278743..c8b718910 100644 --- a/ansible/timezone.yml +++ b/ansible/timezone.yml @@ -1,8 +1,22 @@ --- -- name: Ensure timezone is configured - hosts: seed-hypervisor:seed:overcloud - tags: - - timezone - roles: - - role: yatesr.timezone - become: True +# Timezone configuration has moved to time.yml. +# This will be removed in the Xena release. + +# NOTE(wszumski): Making this a non-empty playbook has the benefit of +# silencing the tox syntax check which doesn't like empty playbooks. + +- hosts: localhost + tasks: + - name: Warn about deprecation of this playbook + fail: + msg: | + This playbook has been deprecated, please use time.yml instead. + Kayobe should not run this playbook, so if you are seeing this + message then either something has gone wrong, or you are trying + to run it manually. This playbook will be removed in the Xena + release. + # NOTE(wszumski): We want this to print a nice big red warning and + # not to fail the run. + ignore_errors: yes + +- import_playbook: time.yml diff --git a/doc/source/configuration/reference/hosts.rst b/doc/source/configuration/reference/hosts.rst index 1625a9f4c..86c3c2652 100644 --- a/doc/source/configuration/reference/hosts.rst +++ b/doc/source/configuration/reference/hosts.rst @@ -406,23 +406,48 @@ timezone. For example: NTP === +*tags:* + | ``ntp`` -Since the Ussuri release, Kayobe no longer supports configuration of an NTP -daemon on the host, since the ``ntp`` package is no longer available in CentOS -8. +Kayobe will configure `Chrony `__ on all hosts in the +``ntp`` group. The default hosts in this group are:: -Kolla Ansible can deploy a chrony container on overcloud hosts, and from the -Ussuri release chrony is enabled by default. There is no support for running a -chrony container on the seed or seed hypervisor hosts. +.. code-block:: console -To disable the containerised chrony daemon, set the following in -``${KAYOBE_CONFIG_PATH}/kolla.yml``: + [ntp:children] + # Kayobe will configure Chrony on members of this group. + seed + seed-hypervisor + overcloud + +This provides a flexible way to opt in or out of having kayobe manage +the NTP service. + +Variables +--------- + +Network Time Protocol (NTP) may be configured via variables in +``${KAYOBE_CONFIG_PATH}/time.yml``. The list of NTP servers is +configured via ``chrony_ntp_servers``, and by default the ``pool.ntp.org`` +servers are used. + +Internally, kayobe uses the the `mrlesmithjr.chrony +`__ Ansible role. Rather than +maintain a mapping between the ``kayobe`` and ``mrlesmithjr.chrony`` worlds, all +variables are simply passed through. This means you can use all variables that +the role defines. For example to change ``chrony_maxupdateskew`` and override +the kayobe defaults for ``chrony_ntp_servers``: .. code-block:: yaml + :caption: ``time.yml`` - kolla_enable_chrony: false - -.. _configuration-hosts-mdadm: + chrony_ntp_servers: + - server: 0.debian.pool.ntp.org + options: + - option: iburst + - option: minpoll + val: 8 + chrony_maxupdateskew: 150.0 Software RAID ============= diff --git a/etc/kayobe/inventory/groups b/etc/kayobe/inventory/groups index a009693e3..fa1ced47c 100644 --- a/etc/kayobe/inventory/groups +++ b/etc/kayobe/inventory/groups @@ -42,7 +42,7 @@ storage compute ############################################################################### -# Docker groups. +# Service groups. [docker:children] # Hosts in this group will have Docker installed. @@ -59,6 +59,12 @@ compute # registries which may become unsynchronized. seed +[ntp:children] +# Kayobe will configure Chrony on members of this group. +seed +seed-hypervisor +overcloud + ############################################################################### # Baremetal compute node groups. diff --git a/etc/kayobe/time.yml b/etc/kayobe/time.yml index c0a86d7c7..f5a0cbbfd 100644 --- a/etc/kayobe/time.yml +++ b/etc/kayobe/time.yml @@ -7,6 +7,32 @@ # Name of the local timezone. #timezone: +############################################################################### +# Network Time Protocol (NTP). + +# Kayobe default time sources +#chrony_ntp_servers_default: + +# List of NTP time sources to configure. Format is a list of dictionaries with +# the following keys: +# server: host or pool +# type: (Optional) Defaults to server. Maps to a time source in the +# configuration file. Can be one of server, peer, pool. +# options: (Optional) List of options that depends on type, see Chrony +# documentation for details. +# See: https://chrony.tuxfamily.org/doc/4.0/chrony.conf.html +# +# Example of configuring a pool and customising the pool specific maxsources +# option: +# chrony_ntp_servers: +# - server: pool.ntp.org +# type: pool +# options: +# - option: maxsources +# val: 3 +# +#chrony_ntp_servers: + ############################################################################### # Dummy variable to allow Ansible to accept this file. workaround_ansible_issue_8743: yes diff --git a/kayobe/cli/commands.py b/kayobe/cli/commands.py index 8ff034f8b..6b91b7881 100644 --- a/kayobe/cli/commands.py +++ b/kayobe/cli/commands.py @@ -414,7 +414,7 @@ class SeedHypervisorHostConfigure(KollaAnsibleMixin, KayobeAnsibleMixin, * Configure user accounts, group associations, and authorised SSH keys. * Configure the host's network interfaces. * Set sysctl parameters. - * Configure timezone. + * Configure timezone and ntp. * Optionally, configure software RAID arrays. * Optionally, configure encryption. * Configure LVM volumes. @@ -453,7 +453,7 @@ class SeedHypervisorHostConfigure(KollaAnsibleMixin, KayobeAnsibleMixin, if parsed_args.wipe_disks: playbooks += _build_playbook_list("wipe-disks") playbooks += _build_playbook_list( - "users", "dev-tools", "network", "sysctl", "timezone", + "users", "dev-tools", "network", "sysctl", "time", "mdadm", "luks", "lvm", "seed-hypervisor-libvirt-host") self.run_kayobe_playbooks(parsed_args, playbooks, limit="seed-hypervisor") @@ -574,7 +574,7 @@ class SeedHostConfigure(KollaAnsibleMixin, KayobeAnsibleMixin, VaultMixin, * Set sysctl parameters. * Configure IP routing and source NAT. * Disable bootstrap interface configuration. - * Configure timezone. + * Configure timezone and ntp. * Optionally, configure software RAID arrays. * Optionally, configure encryption. * Configure LVM volumes. @@ -608,7 +608,7 @@ class SeedHostConfigure(KollaAnsibleMixin, KayobeAnsibleMixin, VaultMixin, playbooks += _build_playbook_list("wipe-disks") playbooks += _build_playbook_list( "users", "dev-tools", "disable-selinux", "network", - "sysctl", "ip-routing", "snat", "disable-glean", "timezone", + "sysctl", "ip-routing", "snat", "disable-glean", "time", "mdadm", "luks", "lvm", "docker-devicemapper", "kolla-ansible-user", "kolla-pip", "kolla-target-venv") self.run_kayobe_playbooks(parsed_args, playbooks, limit="seed") @@ -948,7 +948,7 @@ class OvercloudHostConfigure(KollaAnsibleMixin, KayobeAnsibleMixin, VaultMixin, * Configure the host's network interfaces. * Set sysctl parameters. * Disable bootstrap interface configuration. - * Configure timezone. + * Configure timezone and ntp. * Optionally, configure software RAID arrays. * Optionally, configure encryption. * Configure LVM volumes. @@ -981,7 +981,7 @@ class OvercloudHostConfigure(KollaAnsibleMixin, KayobeAnsibleMixin, VaultMixin, playbooks += _build_playbook_list("wipe-disks") playbooks += _build_playbook_list( "users", "dev-tools", "disable-selinux", "network", - "sysctl", "disable-glean", "disable-cloud-init", "timezone", + "sysctl", "disable-glean", "disable-cloud-init", "time", "mdadm", "luks", "lvm", "docker-devicemapper", "kolla-ansible-user", "kolla-pip", "kolla-target-venv") self.run_kayobe_playbooks(parsed_args, playbooks, limit="overcloud") diff --git a/kayobe/tests/unit/cli/test_commands.py b/kayobe/tests/unit/cli/test_commands.py index e4b3dcce1..988bc2642 100644 --- a/kayobe/tests/unit/cli/test_commands.py +++ b/kayobe/tests/unit/cli/test_commands.py @@ -325,7 +325,7 @@ class TestCase(unittest.TestCase): utils.get_data_files_path("ansible", "dev-tools.yml"), utils.get_data_files_path("ansible", "network.yml"), utils.get_data_files_path("ansible", "sysctl.yml"), - utils.get_data_files_path("ansible", "timezone.yml"), + utils.get_data_files_path("ansible", "time.yml"), utils.get_data_files_path("ansible", "mdadm.yml"), utils.get_data_files_path("ansible", "luks.yml"), utils.get_data_files_path("ansible", "lvm.yml"), @@ -500,7 +500,7 @@ class TestCase(unittest.TestCase): utils.get_data_files_path("ansible", "ip-routing.yml"), utils.get_data_files_path("ansible", "snat.yml"), utils.get_data_files_path("ansible", "disable-glean.yml"), - utils.get_data_files_path("ansible", "timezone.yml"), + utils.get_data_files_path("ansible", "time.yml"), utils.get_data_files_path("ansible", "mdadm.yml"), utils.get_data_files_path("ansible", "luks.yml"), utils.get_data_files_path("ansible", "lvm.yml"), @@ -1045,7 +1045,7 @@ class TestCase(unittest.TestCase): utils.get_data_files_path("ansible", "disable-glean.yml"), utils.get_data_files_path( "ansible", "disable-cloud-init.yml"), - utils.get_data_files_path("ansible", "timezone.yml"), + utils.get_data_files_path("ansible", "time.yml"), utils.get_data_files_path("ansible", "mdadm.yml"), utils.get_data_files_path("ansible", "luks.yml"), utils.get_data_files_path("ansible", "lvm.yml"), diff --git a/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 b/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 index 39b1b6c81..791c38756 100644 --- a/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 +++ b/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 @@ -122,3 +122,11 @@ dnf_custom_repos: # Enable DNF Automatic. dnf_automatic_enabled: true {% endif %} + +# Override the default NTP pool +chrony_ntp_servers: + - server: time.cloudflare.com + type: pool + options: + - option: maxsources + val: 2 diff --git a/playbooks/kayobe-overcloud-host-configure-base/tests/test_overcloud_host_configure.py b/playbooks/kayobe-overcloud-host-configure-base/tests/test_overcloud_host_configure.py index 58654cf90..a6662bcd7 100644 --- a/playbooks/kayobe-overcloud-host-configure-base/tests/test_overcloud_host_configure.py +++ b/playbooks/kayobe-overcloud-host-configure-base/tests/test_overcloud_host_configure.py @@ -134,6 +134,43 @@ def test_timezone(host): assert "Pacific/Honolulu" in status +def test_ntp_alternative_services_disabled(host): + # Tests that we don't have any conflicting NTP servers running + # NOTE(wszumski): We always mask services even if they don't exist + ntpd_service = host.service("ntp") + assert ntpd_service.is_masked + assert not ntpd_service.is_running + + timesyncd_service = host.service("systemd-timesyncd") + assert timesyncd_service.is_masked + assert not timesyncd_service.is_running + + +def test_ntp_running(host): + # Tests that NTP services are enabled and running + assert host.package("chrony").is_installed + assert host.service("chronyd").is_enabled + assert host.service("chronyd").is_running + + +def test_ntp_non_default_time_server(host): + # Tests that the NTP pool has been changed from pool.ntp.org to + # time.cloudflare.com + if 'centos' in host.system_info.distribution.lower(): + chrony_config = host.file("/etc/chrony.conf") + else: + # Debian based distributions use the following path + chrony_config = host.file("/etc/chrony/chrony.conf") + assert chrony_config.exists + assert "time.cloudflare.com" in chrony_config.content_string + + +def test_ntp_clock_synchronized(host): + # Tests that the clock is synchronized + status_output = host.check_output("timedatectl status") + assert "synchronized: yes" in status_output + + @pytest.mark.parametrize('repo', ["AppStream", "BaseOS", "Extras", "epel", "epel-modular"]) @pytest.mark.skipif(not _is_dnf(), reason="DNF only supported on CentOS 8") diff --git a/releasenotes/notes/install-chrony-on-the-host-283fa6fdd9cf9b4c.yaml b/releasenotes/notes/install-chrony-on-the-host-283fa6fdd9cf9b4c.yaml new file mode 100644 index 000000000..acf6587ef --- /dev/null +++ b/releasenotes/notes/install-chrony-on-the-host-283fa6fdd9cf9b4c.yaml @@ -0,0 +1,11 @@ +--- +upgrade: + - | + Updates the NTP implementation from the chrony container deployed by + kolla-ansible to configuring chrony as a host service. Chrony is now + installed on all hosts in the ``ntp`` group, which defaults to include + the seed, overcloud, and seed-hypervisor groups. On existing deployments, + you should run `kayobe overcloud host configure` to migrate from the + kolla-ansible deployed container. This can optionally be scoped to just + use the ``ntp`` tag. You can continue to use the kolla container by + setting `kolla_enable_chrony` to ``true``. diff --git a/requirements.yml b/requirements.yml index b879dde18..1d7a5ca48 100644 --- a/requirements.yml +++ b/requirements.yml @@ -8,6 +8,8 @@ version: 8438592c84585c86e62ae07e526d3da53629b377 - src: MichaelRigart.interfaces version: v1.11.1 +- src: mrlesmithjr.chrony + version: v0.1.0 - src: mrlesmithjr.manage-lvm version: v0.1.4 - src: mrlesmithjr.mdadm