Commit 6728044a1e6b87dd4e505a1b04569fa2bc48ae8b
1 parent
1202a727
Exists in
master
and in
9 other branches
Zero-downtime Unicorn restarts during reconfigure
Showing
5 changed files
with
123 additions
and
6 deletions
Show diff stats
CHANGELOG
| ... | ... | @@ -5,6 +5,7 @@ |
| 5 | 5 | - Support changing the name of the "git" user / group (Michael Fenn) |
| 6 | 6 | - Configure omniauth in gitlab.yml |
| 7 | 7 | - Expose more fields under 'extra' in gitlab.yml |
| 8 | +- Zero-downtime Unicorn restarts | |
| 8 | 9 | |
| 9 | 10 | 6.9.0 |
| 10 | 11 | - Make SSH port in clone URLs configurable (Julien Pivotto) | ... | ... |
README.md
| ... | ... | @@ -131,7 +131,13 @@ sudo gitlab-ctl restart |
| 131 | 131 | It is also possible to start, stop or restart individual components. |
| 132 | 132 | |
| 133 | 133 | ```shell |
| 134 | -sudo gitlab-ctl restart unicorn | |
| 134 | +sudo gitlab-ctl restart sidekiq | |
| 135 | +``` | |
| 136 | + | |
| 137 | +Unicorn supports zero-downtime reloads. These can be triggered as follows: | |
| 138 | + | |
| 139 | +```shell | |
| 140 | +sudo gitlab-ctl hup unicorn | |
| 135 | 141 | ``` |
| 136 | 142 | |
| 137 | 143 | ## Configuration | ... | ... |
files/gitlab-cookbooks/gitlab/attributes/default.rb
| ... | ... | @@ -146,6 +146,7 @@ default['gitlab']['unicorn']['worker_processes'] = 2 |
| 146 | 146 | default['gitlab']['unicorn']['listen'] = '127.0.0.1' |
| 147 | 147 | default['gitlab']['unicorn']['port'] = 8080 |
| 148 | 148 | default['gitlab']['unicorn']['socket'] = '/var/opt/gitlab/gitlab-rails/tmp/sockets/gitlab.socket' |
| 149 | +default['gitlab']['unicorn']['pidfile'] = '/opt/gitlab/var/unicorn/unicorn.pid' | |
| 149 | 150 | default['gitlab']['unicorn']['tcp_nopush'] = true |
| 150 | 151 | default['gitlab']['unicorn']['backlog_socket'] = 64 |
| 151 | 152 | default['gitlab']['unicorn']['worker_timeout'] = 30 | ... | ... |
files/gitlab-cookbooks/gitlab/recipes/unicorn.rb
| ... | ... | @@ -21,12 +21,14 @@ gitlab_rails_etc_dir = File.join(gitlab_rails_dir, "etc") |
| 21 | 21 | gitlab_rails_working_dir = File.join(gitlab_rails_dir, "working") |
| 22 | 22 | |
| 23 | 23 | unicorn_listen_socket = node['gitlab']['unicorn']['socket'] |
| 24 | +unicorn_pidfile = node['gitlab']['unicorn']['pidfile'] | |
| 24 | 25 | unicorn_log_dir = node['gitlab']['unicorn']['log_directory'] |
| 25 | 26 | unicorn_socket_dir = File.dirname(unicorn_listen_socket) |
| 26 | 27 | |
| 27 | 28 | [ |
| 28 | 29 | unicorn_log_dir, |
| 29 | - unicorn_socket_dir | |
| 30 | + unicorn_socket_dir, | |
| 31 | + File.dirname(unicorn_pidfile) | |
| 30 | 32 | ].each do |dir_name| |
| 31 | 33 | directory dir_name do |
| 32 | 34 | owner node['gitlab']['user']['username'] |
| ... | ... | @@ -51,6 +53,19 @@ unicorn_config File.join(gitlab_rails_etc_dir, "unicorn.rb") do |
| 51 | 53 | working_directory gitlab_rails_working_dir |
| 52 | 54 | worker_processes node['gitlab']['unicorn']['worker_processes'] |
| 53 | 55 | preload_app true |
| 56 | + stderr_path File.join(unicorn_log_dir, "unicorn_stderr.log") | |
| 57 | + stdout_path File.join(unicorn_log_dir, "unicorn_stdout.log") | |
| 58 | + pid unicorn_pidfile | |
| 59 | + before_fork <<-'EOS' | |
| 60 | + old_pid = "#{server.config[:pid]}.oldbin" | |
| 61 | + if old_pid != server.pid | |
| 62 | + begin | |
| 63 | + sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU | |
| 64 | + Process.kill(sig, File.read(old_pid).to_i) | |
| 65 | + rescue Errno::ENOENT, Errno::ESRCH | |
| 66 | + end | |
| 67 | + end | |
| 68 | + EOS | |
| 54 | 69 | owner "root" |
| 55 | 70 | group "root" |
| 56 | 71 | mode "0644" |
| ... | ... | @@ -59,6 +74,7 @@ end |
| 59 | 74 | |
| 60 | 75 | runit_service "unicorn" do |
| 61 | 76 | down node['gitlab']['unicorn']['ha'] |
| 77 | + restart_command 2 # Restart Unicorn using SIGUSR2 | |
| 62 | 78 | options({ |
| 63 | 79 | :log_directory => unicorn_log_dir |
| 64 | 80 | }.merge(params)) | ... | ... |
files/gitlab-cookbooks/gitlab/templates/default/sv-unicorn-run.erb
| 1 | -#!/bin/sh | |
| 2 | - | |
| 3 | -cd /opt/gitlab/embedded/service/gitlab-rails | |
| 1 | +#!/bin/bash | |
| 4 | 2 | |
| 3 | +# Let runit capture all script error messages | |
| 5 | 4 | exec 2>&1 |
| 6 | -exec chpst -P -U <%= node['gitlab']['user']['username'] %> -u <%= node['gitlab']['user']['username'] %> /usr/bin/env HOME="<%= node['gitlab']['user']['home'] %>" /opt/gitlab/embedded/bin/bundle exec unicorn -E <%= node['gitlab']['gitlab-rails']['environment'] %> -c <%= File.join(node['gitlab']['gitlab-rails']['dir'], "etc", "unicorn.rb") %> /opt/gitlab/embedded/service/gitlab-rails/config.ru | |
| 5 | + | |
| 6 | +readonly current_pidfile=<%= node['gitlab']['unicorn']['pidfile'] %> | |
| 7 | +readonly oldbin_pidfile=${current_pidfile}.oldbin | |
| 8 | +readonly unicorn_wait_start=1 # time in seconds | |
| 9 | +readonly unicorn_poll_alive=1 # time in seconds | |
| 10 | + | |
| 11 | +function main | |
| 12 | +{ | |
| 13 | + cd /opt/gitlab/embedded/service/gitlab-rails | |
| 14 | + find_us_a_unicorn | |
| 15 | + trap_signals | |
| 16 | + wait_for_unicorn_to_exit | |
| 17 | +} | |
| 18 | + | |
| 19 | +function find_us_a_unicorn | |
| 20 | +{ | |
| 21 | + adopt ${current_pidfile} | |
| 22 | + if [[ ${unicorn_pid} ]]; then | |
| 23 | + echo "adopted existing unicorn master ${unicorn_pid}" | |
| 24 | + return | |
| 25 | + fi | |
| 26 | + | |
| 27 | + adopt ${oldbin_pidfile} | |
| 28 | + if [[ ${unicorn_pid} ]]; then | |
| 29 | + echo "adopted existing oldbin unicorn master ${unicorn_pid}" | |
| 30 | + return | |
| 31 | + fi | |
| 32 | + | |
| 33 | + echo "starting new unicorn master" | |
| 34 | + start_unicorn_master | |
| 35 | + sleep ${unicorn_wait_start} | |
| 36 | + | |
| 37 | + adopt ${current_pidfile} | |
| 38 | + if [[ ${unicorn_pid} ]]; then | |
| 39 | + echo "adopted new unicorn master ${unicorn_pid}" | |
| 40 | + return | |
| 41 | + fi | |
| 42 | + | |
| 43 | + echo "failed to start a new unicorn master" | |
| 44 | + exit | |
| 45 | +} | |
| 46 | + | |
| 47 | +function adopt | |
| 48 | +{ | |
| 49 | + local pid=$(cat $1 2>/dev/null) | |
| 50 | + if alive ${pid} && is_unicorn ${pid}; then | |
| 51 | + readonly unicorn_pid=${pid} | |
| 52 | + fi | |
| 53 | +} | |
| 54 | + | |
| 55 | +function alive | |
| 56 | +{ | |
| 57 | + kill -0 $1 > /dev/null 2>&1 | |
| 58 | +} | |
| 59 | + | |
| 60 | +function is_unicorn | |
| 61 | +{ | |
| 62 | + ps -p $1 -o args | grep -q unicorn | |
| 63 | +} | |
| 64 | + | |
| 65 | +function start_unicorn_master | |
| 66 | +{ | |
| 67 | + chpst -P -U <%= node['gitlab']['user']['username'] %> -u <%= node['gitlab']['user']['username'] %> /usr/bin/env HOME="<%= node['gitlab']['user']['home'] %>" /opt/gitlab/embedded/bin/bundle exec unicorn -D -E <%= node['gitlab']['gitlab-rails']['environment'] %> -c <%= File.join(node['gitlab']['gitlab-rails']['dir'], "etc", "unicorn.rb") %> /opt/gitlab/embedded/service/gitlab-rails/config.ru | |
| 68 | +} | |
| 69 | + | |
| 70 | +function trap_signals | |
| 71 | +{ | |
| 72 | + # Forward all common runit signals except: | |
| 73 | + # - HUP which we handle below; | |
| 74 | + # - KILL which cannot be caught. | |
| 75 | + for sig in STOP CONT ALRM INT QUIT USR1 USR2 TERM; do | |
| 76 | + trap "forward_signal ${sig}" ${sig} | |
| 77 | + done | |
| 78 | + | |
| 79 | + # Omnibus-ctl does not have a subcommand that sends USR2 but it can send HUP. | |
| 80 | + # To allow for reloading unicorn from the command line, translate HUP to | |
| 81 | + # USR2. | |
| 82 | + trap "echo 'wrapper received HUP'; forward_signal USR2" HUP | |
| 83 | +} | |
| 84 | + | |
| 85 | +function forward_signal | |
| 86 | +{ | |
| 87 | + echo "forwarding $1 to unicorn master ${unicorn_pid}" | |
| 88 | + kill -$1 ${unicorn_pid} | |
| 89 | +} | |
| 90 | + | |
| 91 | +function wait_for_unicorn_to_exit | |
| 92 | +{ | |
| 93 | + while sleep ${unicorn_poll_alive}; do | |
| 94 | + alive ${unicorn_pid} || break | |
| 95 | + done | |
| 96 | +} | |
| 97 | + | |
| 98 | +main | |
| 99 | +echo "wrapper for unicorn master ${unicorn_pid} exiting" | ... | ... |