Merge "Implement tripleo-systemd-wrapper role"

This commit is contained in:
Zuul 2019-11-14 19:03:34 +00:00 committed by Gerrit Code Review
commit c96d839968
16 changed files with 603 additions and 0 deletions

View File

@ -0,0 +1,90 @@
==============================
Role - tripleo-systemd-wrapper
==============================
An Ansible role to manage systemd wrappers in TripleO.
What it does
------------
In a nutshell, this role helps to configure systemd so it manages side car
containers (e.g. dnsmasq, HAproxy, keepalived, etc, requested by Neutron
agents).
Underneath, this role creates four files:
- /etc/systemd/system/<service>.path
This file will allow the host to monitor changes to
/var/lib/<service>/<service>-processes-timestamp which keeps track of the
service processes in a text file.
<service>-processes-timestamp file is managed by the <service>-wrapper script
with a flock to avoid race conditions.
- /etc/systemd/system/<service>.service
This file is the SystemD service that will run the synchronization of
processes. It is run as "Type=oneshot" because we just want the unit to execute
the <service>-process-sync script without keeping active processes.
In this Ansible role, we automatically enable and start this service.
- /var/lib/<service>/<service>/wrapper
Script that wrap the service lifecycle management. It takes care of starting
the side containers everytime the service is called.
Because it's a wrapper, the script has to be bind mounted from the host into
the container.
e.g.: /var/lib/neutron/neutron-dnsmasq/wrapper:/usr/local/bin/dnsmasq:ro
So in the case of Neutron DHCP agent, when an operator will create a network,
Neutron will call dnsmasq which will actually call our side container wrapper.
- /var/lib/neutron/<service>/process-sync
This script helps to keep the list of processes (side containers) up to date,
so we don't create more than one container per namespace. We use flock to avoid
a race condition if at the same time the wrapper is called. The flock protects
the list of processes and also the timestamps.
Requirements
------------
It requires systemd on the host. This role isn't designed nor tested to run
within a container.
Role variables
--------------
- tripleo_systemd_wrapper_cmd: -- Command to run in the container.
- tripleo_systemd_wrapper_config_bind_mount: -- Bind-mount used for container config.
- tripleo_systemd_wrapper_container_cli: -- Name of the container cli command to use (podman | docker).
- tripleo_systemd_wrapper_docker_additional_sockets: -- Additional docker sockets to use when interacting with docker
- tripleo_systemd_wrapper_image_name: -- Container image name.
- tripleo_systemd_wrapper_service_dir: -- Directory where state files will be created.
- tripleo_systemd_wrapper_service_kill_script: -- Name of the script to create for the kill action
- tripleo_systemd_wrapper_service_name: -- Name of the service to wrap in Systemd.
Example Playbook
----------------
Sample playbook to call the role::
- name: Create Neutron dnsmasq systemd wrapper
hosts: all
roles:
- tripleo-systemd-wrapper
vars:
tripleo_systemd_wrapper_cmd: "/usr/sbin/dnsmasq -k"
tripleo_systemd_wrapper_config_bind_mount: "/var/lib/config-data/puppet-generated/neutron/etc/neutron:/etc/neutron:ro"
tripleo_systemd_wrapper_container_cli: podman
tripleo_systemd_wrapper_image_name: "docker.io/tripleomaster/centos-binary-neutron-dhcp-agent:current-tripleo"
tripleo_systemd_wrapper_service_dir: /var/lib/neutron
tripleo_systemd_wrapper_service_kill_script: dnsmasq-kill
tripleo_systemd_wrapper_service_name: neutron-dnsmasq
.. ansibleautoplugin::
:role: tripleo_ansible/roles/tripleo-systemd-wrapper

View File

@ -0,0 +1,22 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# 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.
# All variables intended for modification should be placed in this file.
# All variables within this role should have a prefix of "tripleo_systemd_wrapper"
tripleo_systemd_wrapper_debug: false
tripleo_systemd_wrapper_container_cli: podman

View File

@ -0,0 +1,44 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# 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.
galaxy_info:
author: OpenStack
description: TripleO OpenStack Role -- tripleo-systemd-wrapper
company: Red Hat
license: Apache-2.0
min_ansible_version: 2.7
#
# Provide a list of supported platforms, and for each platform a list of versions.
# If you don't wish to enumerate all versions for a particular platform, use 'all'.
# To view available platforms and versions (or releases), visit:
# https://galaxy.ansible.com/api/v1/platforms/
#
platforms:
- name: Fedora
versions:
- 28
- name: CentOS
versions:
- 7
galaxy_tags:
- tripleo
# List your role dependencies here, one per line. Be sure to remove the '[]' above,
# if you add dependencies to this list.
dependencies: []

View File

@ -0,0 +1,37 @@
# Molecule managed
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# 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.
{% if item.registry is defined %}
FROM {{ item.registry.url }}/{{ item.image }}
{% else %}
FROM {{ item.image }}
{% endif %}
RUN if [ $(command -v apt-get) ]; then apt-get update && apt-get install -y python sudo bash ca-certificates && apt-get clean; \
elif [ $(command -v dnf) ]; then dnf makecache && dnf --assumeyes install sudo python*-devel python*-dnf bash {{ item.pkg_extras | default('') }} && dnf clean all; \
elif [ $(command -v yum) ]; then yum makecache fast && yum install -y python sudo yum-plugin-ovl python-setuptools bash {{ item.pkg_extras | default('') }} && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf && yum clean all; \
elif [ $(command -v zypper) ]; then zypper refresh && zypper install -y python sudo bash python-xml {{ item.pkg_extras | default('') }} && zypper clean -a; \
elif [ $(command -v apk) ]; then apk update && apk add --no-cache python sudo bash ca-certificates {{ item.pkg_extras | default('') }}; \
elif [ $(command -v xbps-install) ]; then xbps-install -Syu && xbps-install -y python sudo bash ca-certificates {{ item.pkg_extras | default('') }} && xbps-remove -O; fi
{% for pkg in item.easy_install | default([]) %}
# install pip for centos where there is no python-pip rpm in default repos
RUN easy_install {{ pkg }}
{% endfor %}
CMD ["sh", "-c", "while true; do sleep 10000; done"]

View File

@ -0,0 +1,60 @@
---
driver:
name: docker
log: true
platforms:
- name: centos7
hostname: centos7
image: centos:7
dockerfile: Dockerfile
pkg_extras: python-setuptools
easy_install:
- pip
environment: &env
http_proxy: "{{ lookup('env', 'http_proxy') }}"
https_proxy: "{{ lookup('env', 'https_proxy') }}"
# needed for systemd stuff
command: /sbin/init
capabilities:
- SYS_ADMIN
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
- name: centos8
hostname: centos8
image: centos:8
dockerfile: Dockerfile
pkg_extras: python*-setuptools
environment:
<<: *env
# needed for systemd stuff
command: /sbin/init
capabilities:
- SYS_ADMIN
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
provisioner:
name: ansible
log: true
env:
ANSIBLE_STDOUT_CALLBACK: yaml
scenario:
test_sequence:
- destroy
- create
- prepare
- converge
- verify
- destroy
lint:
enabled: false
verifier:
name: testinfra
lint:
name: flake8

View File

@ -0,0 +1,29 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# 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.
- name: Converge
hosts: all
roles:
- "tripleo-systemd-wrapper"
vars:
tripleo_systemd_wrapper_cmd: "/usr/sbin/dnsmasq -k"
tripleo_systemd_wrapper_config_bind_mount: "/var/lib/config-data/puppet-generated/neutron/etc/neutron:/etc/neutron:ro"
tripleo_systemd_wrapper_container_cli: podman
tripleo_systemd_wrapper_image_name: "docker.io/tripleomaster/centos-binary-neutron-dhcp-agent:current-tripleo"
tripleo_systemd_wrapper_service_dir: /var/lib/neutron
tripleo_systemd_wrapper_service_kill_script: dnsmasq-kill
tripleo_systemd_wrapper_service_name: neutron-dnsmasq

View File

@ -0,0 +1,21 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# 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.
- name: Prepare
hosts: all
roles:
- role: test_deps

View File

@ -0,0 +1,15 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# 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.

View File

@ -0,0 +1,112 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# 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.
# "tripleo-systemd-wrapper" will search for and load any operating system variable file
# found within the "vars/" path. If no OS files are found the task will skip.
- name: Gather variables for each operating system
include_vars: "{{ item }}"
with_first_found:
- skip: true
files:
- "{{ ansible_distribution | lower }}-{{ ansible_distribution_version | lower }}.yml"
- "{{ ansible_distribution | lower }}-{{ ansible_distribution_major_version | lower }}.yml"
- "{{ ansible_os_family | lower }}-{{ ansible_distribution_major_version | lower }}.yml"
- "{{ ansible_distribution | lower }}.yml"
- "{{ ansible_os_family | lower }}-{{ ansible_distribution_version.split('.')[0] }}.yml"
- "{{ ansible_os_family | lower }}.yml"
tags:
- always
- name: "Ensure {{ tripleo_systemd_wrapper_service_dir }} exists"
become: true
file:
path: "{{ tripleo_systemd_wrapper_service_dir }}"
state: directory
setype: svirt_sandbox_file_t
selevel: s0
- name: "Ensure {{ tripleo_systemd_wrapper_service_dir }}/kill_scripts exists"
become: true
file:
path: "{{ tripleo_systemd_wrapper_service_dir }}/kill_scripts"
state: directory
setype: svirt_sandbox_file_t
selevel: s0
when: tripleo_systemd_wrapper_service_kill_script is defined
- name: "Ensure {{ tripleo_systemd_wrapper_service_dir }}/{{ tripleo_systemd_wrapper_service_name }} exists"
become: true
file:
path: "{{ tripleo_systemd_wrapper_service_dir }}/{{ tripleo_systemd_wrapper_service_name }}"
state: directory
setype: svirt_sandbox_file_t
selevel: s0
mode: '4750'
- name: "Create {{ tripleo_systemd_wrapper_service_name }} process command script"
become: true
template:
dest: "{{ tripleo_systemd_wrapper_service_dir }}/{{ tripleo_systemd_wrapper_service_name }}/command"
src: service_command.j2
mode: '0750'
# TODO(emilien) figure out secure permissions & ownership & labeling
- name: "Create {{ tripleo_systemd_wrapper_service_name }} wrapper script"
become: true
template:
dest: "{{ tripleo_systemd_wrapper_service_dir }}/{{ tripleo_systemd_wrapper_service_name }}/wrapper"
src: service_wrapper.j2
mode: '0750'
# TODO(emilien) figure out secure permissions & ownership & labeling
- name: "Create {{ tripleo_systemd_wrapper_service_name }} process sync script"
become: true
template:
dest: "{{ tripleo_systemd_wrapper_service_dir }}/{{ tripleo_systemd_wrapper_service_name }}/sync"
src: service_sync.j2
mode: '0750'
- name: "Create {{ tripleo_systemd_wrapper_service_name }} service kill script"
become: true
template:
dest: "{{ tripleo_systemd_wrapper_service_dir }}/kill_scripts/{{ tripleo_systemd_wrapper_service_kill_script }}"
src: service_kill.j2
mode: '0755'
when: tripleo_systemd_wrapper_service_kill_script is defined
- name: "Create {{ tripleo_systemd_wrapper_service_name }} systemd path file"
become: true
template:
dest: "/etc/systemd/system/{{ tripleo_systemd_wrapper_service_name }}.path"
src: service.path.j2
mode: '0644'
- name: "Create {{ tripleo_systemd_wrapper_service_name }} systemd service file"
become: true
template:
dest: "/etc/systemd/system/{{ tripleo_systemd_wrapper_service_name }}.service"
src: service.service.j2
mode: '0644'
- name: "Start {{ tripleo_systemd_wrapper_service_name }} path"
become: true
systemd:
name: "{{ tripleo_systemd_wrapper_service_name }}.path"
enabled: true
state: started
daemon_reload: true

View File

@ -0,0 +1,5 @@
[Path]
PathModified={{ tripleo_systemd_wrapper_service_dir }}/{{ tripleo_systemd_wrapper_service_name }}/processes-timestamp
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,10 @@
[Unit]
Description=Tripleo {{ tripleo_systemd_wrapper_service_name }} sync service
[Service]
{% if tripleo_systemd_wrapper_debug %}
Environment=SYSTEMD_LOG_LEVEL=debug
{% endif %}
Type=oneshot
ExecStart={{ tripleo_systemd_wrapper_service_dir }}/{{ tripleo_systemd_wrapper_service_name }}/sync
User=root

View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
#
# We wrap the command in a bash script so we can do complex logic
# to make the command dynamic based on the internals of the container.
# This is necessary for backwards compatibily when commands change their
# args based on version (I'm looking at you haproxy).
#
{{ tripleo_systemd_wrapper_cmd }} $@

View File

@ -0,0 +1,71 @@
#!/bin/bash
{% if tripleo_systemd_wrapper_debug|bool -%}
set -x
{% endif -%}
add_date() {
echo "$(date) $@"
}
# Set up script logging for debugging purpose.
# It will be taken care of by logrotate since there is the .log
# suffix.
exec 3>&1 4>&2
trap 'exec 2>&4 1>&3' 0 1 2 3
exec 1>>/var/log/neutron/kill-script.log 2>&1
SIG=$1
PID=$2
NETNS=$(ip netns identify ${PID})
if [ "x${NETNS}" == "x" ]; then
add_date "No network namespace detected, exiting"
exit 1
fi
{% if tripleo_systemd_wrapper_container_cli == 'podman' %}
CLI="nsenter --net=/run/netns/${NETNS} --preserve-credentials -m -t 1 podman"
{% elif tripleo_systemd_wrapper_container_cli == 'docker' %}
{% if tripleo_systemd_wrapper_docker_additional_sockets and tripleo_systemd_wrapper_docker_additional_sockets|length > 0-%}
export DOCKER_HOST=unix://{{ tripleo_systemd_wrapper_docker_additional_sockets[0] }}
{% endif -%}
CLI='docker'
{% else %}
CLI='echo noop'
{% endif %}
kill_container() {
add_date "Stopping container $1 ($2)"
$CLI stop $2
add_date "Deleting container $1 ($2)"
$CLI rm $2
}
hup_container() {
add_date "Sending HUP signal to $1 ($2)"
$CLI kill --signal HUP $2
}
{% raw -%}
if [ -f /proc/$PID/cgroup ]; then
# Get container ID based on process cgroups
CT_ID=$(awk 'BEGIN {FS="[-.]"} /name=/{print $3}' /proc/$PID/cgroup)
CT_NAME=$($CLI inspect -f '{{.Name}}' $CT_ID)
case $SIG in
HUP)
hup_container $CT_NAME $CT_ID
;;
9)
kill_container $CT_NAME $CT_ID
;;
*)
add_date "Unknown action ${SIG} for ${$CT_NAME} ${CT_ID}"
exit 1
;;
esac
else
add_date "No such PID: ${PID}"
exit 1
fi
{% endraw %}

View File

@ -0,0 +1,53 @@
#!/usr/bin/env bash
{% if tripleo_systemd_wrapper_debug %}
set -x
{% endif %}
function start_service {
local NETNS=$1
shift
local NAME="{{ tripleo_systemd_wrapper_service_name }}-${NETNS}"
local CLI='{{ tripleo_systemd_wrapper_container_cli }}'
local CMD="{{ tripleo_systemd_wrapper_service_dir }}/{{ tripleo_systemd_wrapper_service_name }}/command"
local CONTAINER_CMD="ip netns exec ${NETNS} ${CMD}"
{% if tripleo_systemd_wrapper_container_cli == 'podman' %}
local LOGGING="--log-driver k8s-file --log-opt path=/var/log/containers/stdouts/${NAME}.log"
{% else %}
local LOGGING=''
{% endif %}
$CLI stop $NAME &> /dev/null || true
$CLI rm -f $NAME &> /dev/null || true
$CLI run --detach \
-v "{{ tripleo_systemd_wrapper_config_bind_mount }}" \
-v "/run/netns:/run/netns:shared" \
-v "{{ tripleo_systemd_wrapper_service_dir }}:{{ tripleo_systemd_wrapper_service_dir }}:z,shared" \
-v "/dev/log:/dev/log" $LOGGING \
--net host \
--pid host \
--privileged \
--rm=true \
-u root \
--name $NAME \
{{ tripleo_systemd_wrapper_image_name }} \
$CONTAINER_CMD $@
}
exec {lock_fd}>/var/lock/{{ tripleo_systemd_wrapper_service_name }}-processes.lock || exit 1
# In case service_wrapper script already locked the commands, we just wait.
flock "$lock_fd"
IFS=$'\n'
for LINE in $(cat {{ tripleo_systemd_wrapper_service_dir }}/{{ tripleo_systemd_wrapper_service_name }}/processes); do
NETNS=$(echo $LINE | awk '{ print $1 }')
IFS=$' ' ARGS=$(echo $LINE | sed -e "s|$NETNS ||" | xargs)
# TODO(emilien) investigate if we should rather run docker/podman ps instead of ps on the host
if ! ps -e -o pid,command | grep "$(echo $NETNS | sed 's|^[^-]*\-||')" | grep -v grep &> /dev/null; then
start_service $NETNS $ARGS
fi
done
# truncate the file so we don't start them again
:> {{ tripleo_systemd_wrapper_service_dir }}/{{ tripleo_systemd_wrapper_service_name }}/processes
flock -u "$lock_fd"

View File

@ -0,0 +1,17 @@
#!/usr/bin/env bash
{% if tripleo_systemd_wrapper_debug %}
set -x
{% endif %}
ARGS="$@"
NETNS=$(ip netns identify)
exec {lock_fd}>/var/lock/{{ tripleo_systemd_wrapper_service_name }}-processes.lock || exit 1
# In case service_sync script already locked the commands, we just wait.
flock "$lock_fd"
echo "$NETNS $ARGS" >> {{ tripleo_systemd_wrapper_service_dir }}/{{ tripleo_systemd_wrapper_service_name }}/processes
# only update the timestamp which fires systemd if there was an update
date > {{ tripleo_systemd_wrapper_service_dir }}/{{ tripleo_systemd_wrapper_service_name }}/processes-timestamp
flock -u "$lock_fd"

View File

@ -41,6 +41,7 @@
- tripleo-ansible-centos-7-molecule-tripleo-container-manage
- tripleo-ansible-centos-7-molecule-tripleo-modules
- tripleo-ansible-centos-7-molecule-tripleo-puppet-cache
- tripleo-ansible-centos-7-molecule-tripleo-systemd-wrapper
gate:
jobs:
- tripleo-ansible-centos-7-molecule-aide
@ -82,6 +83,7 @@
- tripleo-ansible-centos-7-molecule-tripleo-container-manage
- tripleo-ansible-centos-7-molecule-tripleo-modules
- tripleo-ansible-centos-7-molecule-tripleo-puppet-cache
- tripleo-ansible-centos-7-molecule-tripleo-systemd-wrapper
name: tripleo-ansible-molecule-jobs
- job:
files:
@ -371,3 +373,10 @@
parent: tripleo-ansible-centos-7-base
vars:
tripleo_role_name: tripleo-puppet-cache
- job:
files:
- ^tripleo_ansible/roles/tripleo-systemd-wrapper/.*
name: tripleo-ansible-centos-7-molecule-tripleo-systemd-wrapper
parent: tripleo-ansible-centos-7-base
vars:
tripleo_role_name: tripleo-systemd-wrapper