From a18e970e1d502b40835fd73eb0b6b09fb8b65aa6 Mon Sep 17 00:00:00 2001 From: Ivan Suzdal Date: Mon, 23 Nov 2015 18:40:20 +0300 Subject: [PATCH] Systemd units for docker containers Make every docker container a standard systemd unit, allowing it to be managed by standard way with systemctl. Also add systemd support to dockercrl. Blueprint: master-on-centos7 Breaks: nothing Change-Id: I924534b43d083f93130d2af805609a81b302d7de --- .../puppet/docker/manifests/dockerctl.pp | 1 + deployment/puppet/docker/manifests/systemd.pp | 53 ++++++++++++ .../puppet/docker/manifests/systemd/config.pp | 35 ++++++++ .../docker/templates/dockerctl_config.erb | 2 + .../templates/systemd/template.service.erb | 16 ++++ .../puppet/nailgun/examples/host-only.pp | 80 ++++++++++++------- files/fuel-docker-utils/dockerctl | 2 + files/fuel-docker-utils/dockerctl_config | 2 + files/fuel-docker-utils/functions.sh | 34 +++++++- .../noop/spec/hosts/master/host-only_spec.rb | 68 +++++++++++++++- 10 files changed, 259 insertions(+), 34 deletions(-) create mode 100644 deployment/puppet/docker/manifests/systemd.pp create mode 100644 deployment/puppet/docker/manifests/systemd/config.pp create mode 100644 deployment/puppet/docker/templates/systemd/template.service.erb diff --git a/deployment/puppet/docker/manifests/dockerctl.pp b/deployment/puppet/docker/manifests/dockerctl.pp index f8d4416f10..4855046046 100644 --- a/deployment/puppet/docker/manifests/dockerctl.pp +++ b/deployment/puppet/docker/manifests/dockerctl.pp @@ -5,6 +5,7 @@ class docker::dockerctl ( $profile_dir = '/etc/profile.d', $admin_ipaddress = $::fuel_settings['ADMIN_NETWORK']['ipaddress'], $docker_engine = 'native', + $use_systemd = false, $release, $production, ) { diff --git a/deployment/puppet/docker/manifests/systemd.pp b/deployment/puppet/docker/manifests/systemd.pp new file mode 100644 index 0000000000..828bdaa658 --- /dev/null +++ b/deployment/puppet/docker/manifests/systemd.pp @@ -0,0 +1,53 @@ +# == Class: docker::systemd +# +# Systemd units generator for docker containers +# +# === Parameters +# +# [*release*] +# (required) String. Determine MOS release. +# This release will use for correct docker container names, +# e.g. if release == '8.0' and container name is 'astute' - +# the full container name will be fuel-core-8.0-astute +# +# [*stop_timeout*] +# (required) Integer. Number of seconds to wait for the container +# to stop before killing it. +# +# [*containers*] +# (required) Array. This is an array of container names which should be start +# as systemd units. +# +# [*depends*] +# (optional) Hash. This is a hash of container dependencies. +# Key is a container name, value is a container name +# which should be started before. +# + +class docker::systemd ( + $release = undef, + $stop_timeout = 30, + $containers = ['astute', 'cobbler', 'keystone', 'mcollective', 'nailgun', + 'nginx', 'ostf', 'postgres', 'rabbitmq', 'rsync', 'rsyslog'], + $depends = { + 'astute' => 'rsync', + 'cobbler' => 'nginx', + 'keystone' => 'rabbitmq', + 'mcollective' => 'cobbler', + 'nailgun' => 'rsyslog', + 'nginx' => 'ostf', + 'ostf' => 'nailgun', + 'rsync' => 'keystone', + 'rsyslog' => 'astute', + 'rabbitmq' => 'postgres' + }, +) { + # No empty release allowed + validate_string($release) + + docker::systemd::config {$containers: + release => $release, + depends => $depends, + timeout => $stop_timeout} +} + diff --git a/deployment/puppet/docker/manifests/systemd/config.pp b/deployment/puppet/docker/manifests/systemd/config.pp new file mode 100644 index 0000000000..8554dc2bff --- /dev/null +++ b/deployment/puppet/docker/manifests/systemd/config.pp @@ -0,0 +1,35 @@ +# +# docker::systemd::config resource deploys systemd units for fuel-related +# docker containers and enable to running a containers as a standard +# system service. This resource doesn't changes any state of a container. + +# Variables: +# +# release - will use for correct docker container names +# e.g. if release == '8.0' and container name is 'astute' - +# the full container name will be fuel-core-8.0-astute +# +# depends - this is a hash which describes dependencies of containers +# Key is a container name which apply setting, value is a container name +# which should be started before. +# +# timeout - Number of seconds to wait for the container to stop before killing it. +# + +define docker::systemd::config( $release, $depends, $timeout ) { + file { "/usr/lib/systemd/system/docker-${title}.service": + ensure => file, + content => template('docker/systemd/template.service.erb'), + owner => 'root', + group => 'root', + mode => '0644', + notify => Service["docker-${title}"] + } + + # We use ensure => undef to prevent unnecessary start service + # because at first boot time, the container is launched by dockerctl + service { "docker-${title}": + enable => true, + ensure => undef, + } +} diff --git a/deployment/puppet/docker/templates/dockerctl_config.erb b/deployment/puppet/docker/templates/dockerctl_config.erb index 181d8ac925..57ca64d405 100644 --- a/deployment/puppet/docker/templates/dockerctl_config.erb +++ b/deployment/puppet/docker/templates/dockerctl_config.erb @@ -16,6 +16,8 @@ if [ -z "$VERSION" ]; then VERSION="_VERSION_" fi +SYSTEMD="<%= @use_systemd.to_s %>" + IMAGE_PREFIX="fuel" # busybox image for storage containers BUSYBOX_IMAGE="busybox.tar.gz" diff --git a/deployment/puppet/docker/templates/systemd/template.service.erb b/deployment/puppet/docker/templates/systemd/template.service.erb new file mode 100644 index 0000000000..e0d1e88086 --- /dev/null +++ b/deployment/puppet/docker/templates/systemd/template.service.erb @@ -0,0 +1,16 @@ +[Unit] +Name=<%= @title %> container +Requires=docker.service +After=docker.service <% if @depends[@title] -%>docker-<%= @depends[@title] -%>.service<% end %> + +[Service] +Restart=on-failure +RestartSec=10 +StartLimitBurst=5 +StartLimitInterval=60 +ExecStartPre=/usr/bin/dockerctl create <%= @title %> +ExecStart=/usr/bin/docker start -a fuel-core-<%= @release %>-<%= @title %> +ExecStop=/usr/bin/docker stop -t <%= @timeout %> fuel-core-<%= @release %>-<%= @title %> + +[Install] +WantedBy=multi-user.target diff --git a/deployment/puppet/nailgun/examples/host-only.pp b/deployment/puppet/nailgun/examples/host-only.pp index 89bd691d22..e0ed335ef2 100644 --- a/deployment/puppet/nailgun/examples/host-only.pp +++ b/deployment/puppet/nailgun/examples/host-only.pp @@ -18,13 +18,23 @@ $admin_network = ipcalc_network_wildcard( $::fuel_settings['ADMIN_NETWORK']['netmask']) $extra_networks = $fuel_settings['EXTRA_ADMIN_NETWORKS'] +case $::osfamily { + 'RedHat': { + if $::operatingsystemmajrelease >= '7' { + $use_systemd = true + } else { + $use_systemd = false + } + } + default: { $use_systemd = false } +} + Class['nailgun::packages'] -> Class['nailgun::host'] -> Class['nailgun::client'] -> Class['docker::dockerctl'] -> Class['docker'] -> Class['openstack::logrotate'] -> -Class['nailgun::supervisor'] -> Class['monit'] -> Class['nailgun::bootstrap_cli'] @@ -56,10 +66,11 @@ class { 'openstack::clocksync': } class { 'docker::dockerctl': + use_systemd => $use_systemd, release => $::fuel_release, production => $production, admin_ipaddress => $::fuel_settings['ADMIN_NETWORK']['ipaddress'], - docker_engine => 'native', + docker_engine => 'native', } class { "docker": @@ -81,13 +92,6 @@ class { 'nailgun::client': keystone_pass => $::fuel_settings['FUEL_ACCESS']['password'], } -class { 'nailgun::supervisor': - nailgun_env => false, - ostf_env => false, - require => File['/etc/supervisord.d/current', "/etc/supervisord.d/${::fuel_release}"], - conf_file => 'nailgun/supervisord.conf.base.erb', -} - class { 'nailgun::bootstrap_cli': settings => $::fuel_settings['BOOTSTRAP'], direct_repo_addresses => [ $::fuel_settings['ADMIN_NETWORK']['ipaddress'] ], @@ -99,27 +103,42 @@ class { 'osnailyfacter::ssh': password_auth => 'yes', } -file { '/etc/supervisord.d': - ensure => directory, -} - -class { 'docker::supervisor': - release => $::fuel_release, - require => File["/etc/supervisord.d/${::fuel_release}"], -} - -file { "/etc/supervisord.d/${::fuel_release}": - ensure => directory, - require => File['/etc/supervisord.d'], - owner => root, - group => root, -} - -file { '/etc/supervisord.d/current': - ensure => link, - target => "/etc/supervisord.d/${::fuel_release}", - require => File["/etc/supervisord.d/${::fuel_release}"], - replace => true, +if $use_systemd { + class { 'docker::systemd': + release => $::fuel_release, + } + Class['openstack::logrotate'] -> + Class['docker::systemd'] -> + Exec['sync_deployment_tasks'] +} else { + class { 'nailgun::supervisor': + nailgun_env => false, + ostf_env => false, + require => File['/etc/supervisord.d/current', "/etc/supervisord.d/${::fuel_release}"], + conf_file => 'nailgun/supervisord.conf.base.erb', + } + file { '/etc/supervisord.d': + ensure => directory, + } + class { 'docker::supervisor': + release => $::fuel_release, + require => File["/etc/supervisord.d/${::fuel_release}"], + } + file { "/etc/supervisord.d/${::fuel_release}": + ensure => directory, + require => File['/etc/supervisord.d'], + owner => 'root', + group => 'root', + } + file { '/etc/supervisord.d/current': + ensure => link, + target => "/etc/supervisord.d/${::fuel_release}", + require => File["/etc/supervisord.d/${::fuel_release}"], + replace => true, + } + Class['openstack::logrotate'] -> + Class['docker::supervisor'] -> + Exec['sync_deployment_tasks'] } exec {'sync_deployment_tasks': @@ -127,5 +146,4 @@ exec {'sync_deployment_tasks': path => '/usr/bin', tries => 12, try_sleep => 10, - require => Class['nailgun::supervisor'] } diff --git a/files/fuel-docker-utils/dockerctl b/files/fuel-docker-utils/dockerctl index 44d3add34e..94cfc09f13 100644 --- a/files/fuel-docker-utils/dockerctl +++ b/files/fuel-docker-utils/dockerctl @@ -68,6 +68,8 @@ case "$1" in else check_ready $container fi ;; + create) + create_container $container;; start) if [[ "$container" == 'all' ]]; then for service in $container_seq; do diff --git a/files/fuel-docker-utils/dockerctl_config b/files/fuel-docker-utils/dockerctl_config index b741482fa1..caade4462a 100644 --- a/files/fuel-docker-utils/dockerctl_config +++ b/files/fuel-docker-utils/dockerctl_config @@ -17,6 +17,8 @@ if [ -z "$VERSION" ]; then VERSION="_VERSION_" fi +SYSTEMD="false" + IMAGE_PREFIX="fuel" # busybox image for storage containers BUSYBOX_IMAGE="busybox.tar.gz" diff --git a/files/fuel-docker-utils/functions.sh b/files/fuel-docker-utils/functions.sh index 0252fca193..e79de43091 100644 --- a/files/fuel-docker-utils/functions.sh +++ b/files/fuel-docker-utils/functions.sh @@ -26,6 +26,7 @@ function show_usage { echo "Available commands:" echo " help: show this message" echo " build: create all Docker containers" + echo " create: create container without running (or starting) it" echo " list: list container short names (-l for more output)" echo " start: start all Docker containers" echo " restart: restart one or more Docker containers" @@ -57,7 +58,7 @@ function parse_options { nonopts+=("$@") return ;; - help|build|start|check|list|copy|restart|stop|revert|shell|upgrade|restore|backup|destroy|logs|post_start_hooks) + help|build|create|start|check|list|copy|restart|stop|revert|shell|upgrade|restore|backup|destroy|logs|post_start_hooks) nonopts+=("$@") return ;; @@ -135,7 +136,12 @@ function check_ready { echo "checking container $1" case $1 in - nailgun) retry_checker "shell_container nailgun supervisorctl status nailgun | grep -q RUNNING" ;; + nailgun) if [ "${SYSTEMD:-false}" == "true" ]; then + retry_checker "shell_container nailgun systemctl is-active nailgun" + else + retry_checker "shell_container nailgun supervisorctl status nailgun | grep -q RUNNING" + fi + ;; ostf) retry_checker "egrep -q ^[2-4][0-9]? < <(curl --connect-timeout 1 -s -w '%{http_code}' http://$ADMIN_IP:8777/ostf/not_found -o /dev/null)" ;; #NOTICE: Cobbler console tool does not comply unix conversation: 'cobbler profile find' always return 0 as exit code cobbler) retry_checker "shell_container cobbler ps waux | grep -q 'cobblerd -F' && pgrep dnsmasq" @@ -228,6 +234,30 @@ function commit_container { image="$IMAGE_PREFIX/$1_$VERSION" ${DOCKER} commit $container_name $image } + +function create_container() { + # wrapper for systemd unit + if [ -z "$1" ]; then + echo "Must specify a container name" 1>&2 + exit 1 + fi + if [ "$1" = "all" ]; then + for container in $CONTAINER_SEQUENCE; do + create_container $container + done + return + fi + opts="${CONTAINER_OPTIONS[$1]} ${CONTAINER_VOLUMES[$1]}" + container_name="${CONTAINER_NAMES[$1]}" + image="$IMAGE_PREFIX/$1_$VERSION" + if ! container_created $container_name; then + pre_setup_hooks $1 + ${DOCKER} create $opts --privileged --name=$container_name $image + post_setup_hooks $1 + fi + return 0 +} + function start_container { lock if [ -z "$1" ]; then diff --git a/tests/noop/spec/hosts/master/host-only_spec.rb b/tests/noop/spec/hosts/master/host-only_spec.rb index c8cbffa86e..7f9fa6a438 100644 --- a/tests/noop/spec/hosts/master/host-only_spec.rb +++ b/tests/noop/spec/hosts/master/host-only_spec.rb @@ -47,6 +47,72 @@ describe manifest do }) end - end + + let(:params) { { + :containers => ['astute', + 'cobbler', + 'keystone', + 'mcollective', + 'nailgun', + 'nginx', + 'ostf', + 'postgres', + 'rabbitmq', + 'rsync', + 'rsyslog'] + } } + + context 'running on centos 6' do + let(:facts) do + Noop.centos_facts.merge({ + :operatingsystemmajrelease => '6' + }) + end + it 'configure containers supervisor' do + release = facts[:fuel_release] + + should contain_class('docker::supervisor').with({ + :release => release, + :require => "File[/etc/supervisord.d/#{release}]", + }) + params[:containers].each do |container| + should contain_file("/etc/supervisord.d/#{release}/#{container}.conf").with({ + :owner => 'root', + :group => 'root', + :mode => '0644' + }) + end + end #it do + end #context + + context 'running on centos 7' do + let(:facts) do + Noop.centos_facts.merge({ + :operatingsystemmajrelease => '7' + }) + end + + it 'configure containers systemd' do + release = facts[:fuel_release] + + should contain_class('docker::systemd').with({ + :release => release, + :containers => params[:containers] + }) + params[:containers].each do |container| + should contain_file("/usr/lib/systemd/system/docker-#{container}.service").with({ + :owner => 'root', + :group => 'root', + :mode => '0644', + }) + should contain_service("docker-#{container}").with({ + :ensure => nil, # we shouldn't start container from puppet + :enable => 'true', + }) + end + end #it do + end #context + end #shared_examples + test_centos manifest end