From 3c66833bb9adb58008fa00ea946b693a250ac8eb Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Mon, 19 Jul 2021 10:39:47 -0700 Subject: [PATCH] Convert to systemd resources (#771) * Convert to systemd resources Signed-off-by: Robert Detjens * Manually specify java path Older version of systemd require the full path to the java binary. The current method of using node[java] is broken since the java cookbook no longer sets attributes. Signed-off-by: Robert Detjens * appease lints Signed-off-by: Robert Detjens * Add automatic upgrade and upgrade instructions Signed-off-by: Robert Detjens * Exec with /bin/sh to support quotes in args ExecStart does not fully support shell quoting, unlike Runit. Jenkins arguments may be expecting shell quoting, so exec via /bin/sh to correctly interpret them Signed-off-by: Robert Detjens Co-authored-by: davidsainty <> --- CHANGELOG.md | 12 ++ README.md | 28 ++--- UPGRADING.md | 17 +++ attributes/master.rb | 13 --- kitchen.yml | 32 +++--- libraries/helpers.rb | 26 +++++ libraries/slave_jnlp.rb | 104 ++++++++++++------ metadata.rb | 1 - recipes/_master_war.rb | 64 +++++++++-- templates/sv-jenkins-log-run.erb | 8 -- templates/sv-jenkins-run.erb | 23 ---- templates/sv-jenkins-slave-log-run.erb | 8 -- templates/sv-jenkins-slave-run.erb | 22 ---- .../jenkins_server_wrapper/recipes/default.rb | 3 + .../jenkins_slave/recipes/create_jnlp.rb | 2 +- .../jenkins_smoke/controls/jenkins.rb | 17 +-- .../jenkins_smoke/controls/jenkins_slave.rb | 30 +---- test/integration/jenkins_smoke/inspec.yml | 3 - 18 files changed, 217 insertions(+), 196 deletions(-) create mode 100644 UPGRADING.md delete mode 100644 templates/sv-jenkins-log-run.erb delete mode 100644 templates/sv-jenkins-run.erb delete mode 100644 templates/sv-jenkins-slave-log-run.erb delete mode 100644 templates/sv-jenkins-slave-run.erb diff --git a/CHANGELOG.md b/CHANGELOG.md index e41c84bd9e..e3e510b257 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ This file is used to list changes made in each version of the jenkins cookbook. ## Unreleased +- Remove runit dependency +- Use systemd units instead of runit services + +### Breaking Changes / Deprecations + +- `jenkins_jnlp_slave`: + - Renamed `runit_groups` property to `service_groups` + - New service created -- old Runit service will need manual cleanup + +- `jenkins::_master_war`: + - New service created -- old Runit service will need manual cleanup + ## 8.2.3 - *2021-03-25* - Cookstyle fixes diff --git a/README.md b/README.md index 388e4b276a..ccd4531b95 100644 --- a/README.md +++ b/README.md @@ -16,18 +16,14 @@ This cookbook is maintained by the Sous Chefs. The Sous Chefs are a community of ### Platforms -- Debian 7+ (Package installs require 9+ due to dependencies) -- Ubuntu 14.04+ (Package installs require 16.04+ due to dependencies) -- RHEL/CentOS/Scientific/Oracle 6+ +- Debian 9+ +- Ubuntu 18.04+ +- RHEL/CentOS 7+ ### Chef - Chef 13.0+ -### Cookbooks - -- runit - #### Java cookbook This cookbook does not install, manage, or manipulate a JDK, as that is outside of the scope of Jenkins. The `package` installation method will automatically pull in a valid Java if one does not exist on Debian. RHEL jenkins packages do not depend on java as there are far too many options for a package to do the right thing. We recommend including the java cookbook on your system which allows for either openJDK or Oracle JDK installations. @@ -47,7 +43,7 @@ Documentation and examples are provided inline using YARD. The tests and fixture The master recipe will create the required directory structure and install jenkins. There are two installation methods, controlled by the `node['jenkins']['master']['install_method']` attribute: - `package` - Install Jenkins from the official jenkins-ci.org packages -- `war` - Download the latest version of the WAR file and configure it with Runit +- `war` - Download the latest version of the WAR file and configure a systemd service ## Resources @@ -456,22 +452,12 @@ end Depending on the plugin, you may need to restart the Jenkins instance for the plugin to take affect: -Package installation method: - ```ruby jenkins_plugin 'a_complicated_plugin' do notifies :restart, 'service[jenkins]', :immediately end ``` -War installation method: - -```ruby -jenkins_plugin 'a_complicated_plugin' do - notifies :restart, 'runit_service[jenkins]', :immediately -end -``` - For advanced users, this resource exposes an `options` attribute that will be passed to the installation command. For more information on the possible values of these options, please consult the documentation for your Jenkins installation. ```ruby @@ -504,7 +490,7 @@ jenkins_plugin 'greenballs' do end ``` -**NOTE** You may need to restart Jenkins after changing a plugin. Because this varies on a case-by-case basis (and because everyone chooses to manage their Jenkins infrastructure differently) this LWRP does **NOT** restart Jenkins for you. +**NOTE** You may need to restart Jenkins after changing a plugin. Because this varies on a case-by-case basis (and because everyone chooses to manage their Jenkins infrastructure differently) this resource does **NOT** restart Jenkins for you. ### jenkins_slave @@ -562,8 +548,8 @@ jenkins_jnlp_slave 'smoke' do idle_delay 3 labels ['runner', 'fast'] - # User's groups to be configured with the runit service. - runit_groups ['jenkins', 'docker'] + # List of groups to run the slave service under + service_groups ['jenkins', 'docker'] end # Create a slave with a full environment diff --git a/UPGRADING.md b/UPGRADING.md new file mode 100644 index 0000000000..80f1b2290c --- /dev/null +++ b/UPGRADING.md @@ -0,0 +1,17 @@ +# Upgrading + +## (9.0.0) Runit to Systemd conversion + +Version 9.0.0 of this cookbook replaced the Runit services for WAR installation and JNLP slaves with Systemd services. +This replacement is done mostly automatically, but some manual cleanup is needed to completely remove Runit. + +What is done automatically: + +- stop / disable the old runit service +- remove the old service files in `/etc/service` and `/etc/init.d` +- create / start / enable the new systemd service + +What needs to be done manually: + +- stop / disable runit manager service (`runit` / `runsvdir-start`) +- uninstall runit diff --git a/attributes/master.rb b/attributes/master.rb index 7f483ca159..3d48c601f1 100644 --- a/attributes/master.rb +++ b/attributes/master.rb @@ -231,19 +231,6 @@ # master['maxopenfiles'] = 8192 - # - # The groups of user under which Jenkins is running. Works for runit only. - # - master['runit']['groups'] = [node['jenkins']['master']['group']] - - # - # The timeout passed to the runit cookbook's service resource. Override the - # default timeout of 7 seconds. This option implies verbose. - # - # node.normal['jenkins']['master']['runit']['sv_timeout'] = 60 - # - master['runit']['sv_timeout'] = 7 - # # The limits for the Java process running the master server. # Example to configure the maximum number of open file descriptors: diff --git a/kitchen.yml b/kitchen.yml index e96e7a9b34..eb470da033 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -21,8 +21,6 @@ provisioner: host: localhost install_method: package mirror: https://updates.jenkins.io - runit: - sv_timeout: 30 # This is to help slaves to connect to the master # Sometimes with slow performance the connection is dropped/cut too early # 30 seconds was too low, so I bumped it to 5 minutes @@ -48,11 +46,9 @@ verifier: name: inspec suites: - # - # Smoke suite - # - name: smoke_package_stable - run_list: jenkins_smoke::default + run_list: + - jenkins_smoke::default attributes: jenkins: master: @@ -63,7 +59,8 @@ suites: - windows-2019 - name: smoke_package_current - run_list: jenkins_smoke::default + run_list: + - jenkins_smoke::default attributes: jenkins: master: @@ -75,15 +72,13 @@ suites: - windows-2019 - name: smoke_war_stable - run_list: jenkins_smoke::default + run_list: + - jenkins_smoke::default attributes: jenkins: master: install_method: war source: https://updates.jenkins.io/stable/latest/jenkins.war - verifier: - inputs: - service_type: 'runit' - name: smoke_war_latest run_list: jenkins_smoke::default @@ -92,18 +87,17 @@ suites: master: install_method: war source: https://updates.jenkins.io/latest/jenkins.war - verifier: - inputs: - service_type: 'runit' # # Authentication suites # - name: authentication_private_key # Tested in smoke suite - run_list: jenkins_authentication::private_key + run_list: + - jenkins_authentication::private_key - name: authentication_username_password - run_list: jenkins_authentication::username_password + run_list: + - jenkins_authentication::username_password excludes: - windows-2012r2 - windows-2016 @@ -115,6 +109,8 @@ suites: # As the proxy settings are global # - name: jenkins_proxy_config # Tested in smoke suite - run_list: jenkins_proxy::config + run_list: + - jenkins_proxy::config - name: jenkins_proxy_remove - run_list: jenkins_proxy::remove + run_list: + - jenkins_proxy::remove diff --git a/libraries/helpers.rb b/libraries/helpers.rb index 49a7e85f8a..d07ddab3b1 100644 --- a/libraries/helpers.rb +++ b/libraries/helpers.rb @@ -8,6 +8,32 @@ def jenkins_font_packages %w(fonts-dejavu-core fontconfig) end end + + def ulimits_to_systemd(ulimits) + # see https://www.freedesktop.org/software/systemd/man/systemd.exec.html#Process%20Properties + + return unless ulimits + + mapping = { + t: 'LimitCPU', + f: 'LimitFSIZE', + d: 'LimitDATA', + s: 'LimitSTACK', + c: 'LimitCORE', + m: 'LimitRSS', + n: 'LimitNOFILE', + v: 'LimitAS', + u: 'LimitNPROC', + l: 'LimitMEMLOCK', + x: 'LimitLOCKS', + i: 'LimitSIGPENDING', + q: 'LimitMSGQUEUE', + e: 'LimitNICE', + r: 'LimitRTPRIO', + } + + ulimits.map { |k, v| "#{mapping[k.to_sym]}=#{v}" }.join "\n" + end end end end diff --git a/libraries/slave_jnlp.rb b/libraries/slave_jnlp.rb index 58ca4c42ea..48cbec3596 100644 --- a/libraries/slave_jnlp.rb +++ b/libraries/slave_jnlp.rb @@ -30,17 +30,19 @@ class Resource::JenkinsJnlpSlave < Resource::JenkinsSlave actions :create, :delete, :connect, :disconnect, :online, :offline default_action :create - # Attributes - attribute :group, - kind_of: String, + # Properties + property :group, String, default: 'jenkins', regex: Config[:group_valid_regex] - attribute :service_name, - kind_of: String, + + property :service_name, String, default: 'jenkins-slave' - attribute :runit_groups, - kind_of: Array, - default: ['jenkins'] + + property :service_groups, Array, + default: lazy { [group] } + + deprecated_property_alias 'runit_groups', 'service_groups', + '`runit_groups` was renamed to `service_groups` with the move to systemd services' end end @@ -89,20 +91,76 @@ def load_current_resource r.backup(false) r.mode('0755') r.atomic_update(false) - r.notifies :restart, "runit_service[#{new_resource.service_name}]" unless platform?('windows') + r.notifies :restart, "systemd_unit[#{new_resource.service_name}.service]" unless platform?('windows') end # The Windows's specific child class manages it's own service return if platform?('windows') - include_recipe 'runit' + # disable runit services before starting new service + # TODO: remove in future version + + %W( + /etc/init.d/#{new_resource.service_name} + /etc/service/#{new_resource.service_name} + ).each do |f| + file f do + action :delete + notifies :stop, "service[#{new_resource.service_name}]", :before + end + end - service_resource + # runit_service = if platform_family?('debian') + # 'runit' + # else + # 'runsvdir-start' + # end + # service runit_service do + # action [:stop, :disable] + # end + + exec_string = "#{java} #{new_resource.jvm_options}" + exec_string << " -jar #{slave_jar}" if slave_jar + exec_string << " -secret #{jnlp_secret}" if jnlp_secret + exec_string << " -jnlpUrl #{jnlp_url}" + + systemd_unit "#{new_resource.service_name}.service" do + content <<~EOU + # + # Generated by Chef for #{node['fqdn']} + # Changes will be overwritten! + # + + [Unit] + Description=Jenkins JNLP Slave (#{new_resource.service_name}) + After=network.target + + [Service] + Type=simple + User=#{new_resource.user} + Group=#{new_resource.group} + SupplementaryGroups=#{(new_resource.service_groups - [new_resource.group]).join(' ')} + Environment="HOME=#{new_resource.remote_fs}" + Environment="JENKINS_HOME=#{new_resource.remote_fs}" + WorkingDirectory=#{new_resource.remote_fs} + ExecStart=#{exec_string} + + [Install] + WantedBy=multi-user.target + EOU + action :create + end + + service new_resource.service_name do + action [:enable, :start] + end end action :delete do # Stop and remove the service - service_resource.run_action(:disable) + service "#{new_resource.service_name}" do + action [:disable, :stop] + end do_delete end @@ -134,7 +192,7 @@ def jnlp_url # def jnlp_secret return @jnlp_secret if @jnlp_secret - json = executor.groovy! <<-EOH.gsub(/^ {8}/, '') + json = executor.groovy! <<~EOH output = [ secret:jenkins.slaves.JnlpSlaveAgentProtocol.SLAVE_SECRET.mac('#{new_resource.slave_name}') ] @@ -155,26 +213,6 @@ def slave_jar_url @slave_jar_url ||= uri_join(endpoint, 'jnlpJars', 'slave.jar') end - def service_resource - declare_resource(:runit_service, new_resource.service_name).tap do |r| - # We need to use .tap() to access methods in the provider's scope. - r.cookbook('jenkins') - r.run_template_name('jenkins-slave') - r.log_template_name('jenkins-slave') - r.options( - service_name: new_resource.service_name, - jvm_options: new_resource.jvm_options, - user: new_resource.user, - runit_groups: new_resource.runit_groups, - remote_fs: new_resource.remote_fs, - java_bin: java, - slave_jar: slave_jar, - jnlp_url: jnlp_url, - jnlp_secret: jnlp_secret - ) - end - end - # # The path to the +slave.jar+ on disk (which may or may not exist). # diff --git a/metadata.rb b/metadata.rb index fb2b85c41b..bb4cf415ba 100644 --- a/metadata.rb +++ b/metadata.rb @@ -16,5 +16,4 @@ supports 'scientific' supports 'ubuntu' -depends 'runit', '>= 1.7' depends 'dpkg_autostart' diff --git a/recipes/_master_war.rb b/recipes/_master_war.rb index ded88b3309..e5aff8e394 100644 --- a/recipes/_master_war.rb +++ b/recipes/_master_war.rb @@ -52,9 +52,6 @@ recursive true end -# Include runit to setup the service -include_recipe 'runit::default' - package jenkins_font_packages # Download the remote WAR file @@ -63,12 +60,63 @@ checksum node['jenkins']['master']['checksum'] if node['jenkins']['master']['checksum'] owner node['jenkins']['master']['user'] group node['jenkins']['master']['group'] - notifies :restart, 'runit_service[jenkins]' + notifies :restart, 'service[jenkins]' +end + +# disable runit services before starting new service +# TODO: remove in future version + +%w( + /etc/init.d/jenkins + /etc/service/jenkins +).each do |f| + file f do + action :delete + notifies :stop, 'service[jenkins]', :before + end end -Chef::Log.warn('Here we go with the runit service') +# runit_service = if platform_family?('debian') +# 'runit' +# else +# 'runsvdir-start' +# end +# service runit_service do +# action [:stop, :disable] +# end + +# +# systemd ExecStart does not fully support shell-style quoting as +# might be expected by jvm_options etc. But it does support a leading +# quote, trailing quote, and escaping in between, and we can ask +# /bin/sh to make sense of the rest. +# +systemd_unit 'jenkins.service' do + content <<~EOU + # + # Generated by Chef for #{node['fqdn']} + # Changes will be overwritten! + # + + [Unit] + Description=Jenkins master service (WAR) + + [Service] + Type=simple + User=#{node['jenkins']['master']['user']} + Group=#{node['jenkins']['master']['group']} + Environment="HOME=#{node['jenkins']['master']['home']}" + Environment="JENKINS_HOME=#{node['jenkins']['master']['home']}" + WorkingDirectory=#{node['jenkins']['master']['home']} + #{ulimits_to_systemd(node['jenkins']['master']['ulimits'])} + ExecStart=/bin/sh -c 'exec #{"#{node['jenkins']['java']} #{node['jenkins']['master']['jvm_options']} -jar jenkins.war --httpPort=#{node['jenkins']['master']['port']} --httpListenAddress=#{node['jenkins']['master']['listen_address']} #{node['jenkins']['master']['jenkins_args']}".gsub("'", "\\\\'")}' + + [Install] + WantedBy=multi-user.target + EOU + action :create +end -# Create runit service -runit_service 'jenkins' do - sv_timeout node['jenkins']['master']['runit']['sv_timeout'] +service 'jenkins' do + action [:enable, :start] end diff --git a/templates/sv-jenkins-log-run.erb b/templates/sv-jenkins-log-run.erb deleted file mode 100644 index c77add9b10..0000000000 --- a/templates/sv-jenkins-log-run.erb +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -# -# This file was generated by Chef. -# Do NOT modify this file by hand. -# - -exec chpst -unobody logger -d -p local6.info -t [jenkins] diff --git a/templates/sv-jenkins-run.erb b/templates/sv-jenkins-run.erb deleted file mode 100644 index f960a079b3..0000000000 --- a/templates/sv-jenkins-run.erb +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh - -# -# This file was generated by Chef. -# Do NOT modify this file by hand. -# - -[ -r /etc/profile ] && . /etc/profile - -<% groups = ([node['jenkins']['master']['user']] + node['jenkins']['master']['runit']['groups']).join(':') %> -exec 2>&1 -cd <%= node['jenkins']['master']['home'] %> -<% limits = node['jenkins']['master']['ulimits'] %> -<% if limits && !limits.empty? %> -ulimit <% limits.each { |option, value| %>-<%= option %> <%= value %> <% } %> -<% end %> -exec chpst -u <%= groups %> -U <%= groups %> \ - env HOME=<%= node['jenkins']['master']['home'] %> \ - JENKINS_HOME=<%= node['jenkins']['master']['home'] %> \ - <%= node['jenkins']['java'] %> <%= node['jenkins']['master']['jvm_options'] %> -jar jenkins.war \ - --httpPort=<%= node['jenkins']['master']['port'] %> \ - --httpListenAddress=<%= node['jenkins']['master']['listen_address'] %> \ - <%= node['jenkins']['master']['jenkins_args'] %> diff --git a/templates/sv-jenkins-slave-log-run.erb b/templates/sv-jenkins-slave-log-run.erb deleted file mode 100644 index d37ae0abc0..0000000000 --- a/templates/sv-jenkins-slave-log-run.erb +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -# -# This file was generated by Chef. -# Do NOT modify this file by hand. -# - -exec chpst -unobody logger -d -p local6.info -t [<%= @options[:service_name] %>] diff --git a/templates/sv-jenkins-slave-run.erb b/templates/sv-jenkins-slave-run.erb deleted file mode 100644 index c5ad43e61a..0000000000 --- a/templates/sv-jenkins-slave-run.erb +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -# -# This file was generated by Chef. -# Do NOT modify this file by hand. -# - -<% groups = ([@options[:user]] + @options[:runit_groups]).join(':') %> -exec 2>&1 -cd <%= @options[:remote_fs] %> -exec chpst -u <%= groups %> -U <%= groups %> \ - env HOME=<%= @options[:remote_fs] %> \ - JENKINS_HOME=<%= @options[:remote_fs] %> \ - <%= @options[:java_bin] %> \ -<%- if @options[:jvm_options] -%> - <%= @options[:jvm_options] %> \ -<%- end -%> - -jar <%= @options[:slave_jar] %> \ -<%- if @options[:jnlp_secret] -%> - -secret <%= @options[:jnlp_secret] %> \ -<%- end -%> - -jnlpUrl <%= @options[:jnlp_url] %> diff --git a/test/fixtures/cookbooks/jenkins_server_wrapper/recipes/default.rb b/test/fixtures/cookbooks/jenkins_server_wrapper/recipes/default.rb index 2cf0b1952c..0280cc69b5 100644 --- a/test/fixtures/cookbooks/jenkins_server_wrapper/recipes/default.rb +++ b/test/fixtures/cookbooks/jenkins_server_wrapper/recipes/default.rb @@ -4,6 +4,9 @@ variant 'hotspot' end +# node[java] is gone, so manually set java path since some platforms need absolute path for service +node.default['jenkins']['java'] = '/usr/bin/java' + include_recipe 'jenkins::master' # Install some plugins needed, but not installed on jenkins2 by default diff --git a/test/fixtures/cookbooks/jenkins_slave/recipes/create_jnlp.rb b/test/fixtures/cookbooks/jenkins_slave/recipes/create_jnlp.rb index 365d6f815d..0a7f548210 100644 --- a/test/fixtures/cookbooks/jenkins_slave/recipes/create_jnlp.rb +++ b/test/fixtures/cookbooks/jenkins_slave/recipes/create_jnlp.rb @@ -28,7 +28,7 @@ labels %w(runner fast) user 'jenkins-smoke' group 'jenkins-smoke' - runit_groups %w(jenkins-smoke) + service_groups %w(jenkins-smoke) action :create end diff --git a/test/integration/jenkins_smoke/controls/jenkins.rb b/test/integration/jenkins_smoke/controls/jenkins.rb index 16729bbf73..1316757c13 100644 --- a/test/integration/jenkins_smoke/controls/jenkins.rb +++ b/test/integration/jenkins_smoke/controls/jenkins.rb @@ -1,24 +1,15 @@ # copyright: 2018, The Authors title 'Jenkins' -service_type = input('service_type') control 'jenkins-1.0' do impact 0.7 title 'Jenkins is running' - if service_type == 'systemd' - describe service 'jenkins' do - it { should be_installed } - it { should be_enabled } - it { should be_running } - end - else - describe runit_service 'jenkins' do - it { should be_installed } - it { should be_enabled } - it { should be_running } - end + describe service 'jenkins' do + it { should be_installed } + it { should be_enabled } + it { should be_running } end end diff --git a/test/integration/jenkins_smoke/controls/jenkins_slave.rb b/test/integration/jenkins_smoke/controls/jenkins_slave.rb index 60d1ab351c..27451de427 100644 --- a/test/integration/jenkins_smoke/controls/jenkins_slave.rb +++ b/test/integration/jenkins_smoke/controls/jenkins_slave.rb @@ -34,14 +34,8 @@ it { should be_directory } end - describe.one do - describe service('jenkins-slave-builder') do - it { should be_running } - end - - describe runit_service('jenkins-slave-builder') do - it { should be_running } - end + describe service('jenkins-slave-builder') do + it { should be_running } end # @@ -75,14 +69,8 @@ it { should be_directory } end - describe.one do - describe service('jenkins-slave-smoke') do - it { should be_running } - end - - describe runit_service('jenkins-slave-smoke') do - it { should be_running } - end + describe service('jenkins-slave-smoke') do + it { should be_running } end # @@ -111,14 +99,8 @@ it { should be_directory } end - describe.one do - describe service('jenkins-slave-executor') do - it { should be_running } - end - - describe runit_service('jenkins-slave-executor') do - it { should be_running } - end + describe service('jenkins-slave-executor') do + it { should be_running } end end diff --git a/test/integration/jenkins_smoke/inspec.yml b/test/integration/jenkins_smoke/inspec.yml index 5c9c0f2313..516b364cc2 100644 --- a/test/integration/jenkins_smoke/inspec.yml +++ b/test/integration/jenkins_smoke/inspec.yml @@ -9,6 +9,3 @@ version: 0.1.0 depends: - name: jenkins_inspec path: ../helpers/jenkins_inspec -inputs: - - name: service_type - value: 'systemd'