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,6 +5,7 @@ | ||
5 | - Support changing the name of the "git" user / group (Michael Fenn) | 5 | - Support changing the name of the "git" user / group (Michael Fenn) |
6 | - Configure omniauth in gitlab.yml | 6 | - Configure omniauth in gitlab.yml |
7 | - Expose more fields under 'extra' in gitlab.yml | 7 | - Expose more fields under 'extra' in gitlab.yml |
8 | +- Zero-downtime Unicorn restarts | ||
8 | 9 | ||
9 | 6.9.0 | 10 | 6.9.0 |
10 | - Make SSH port in clone URLs configurable (Julien Pivotto) | 11 | - Make SSH port in clone URLs configurable (Julien Pivotto) |
README.md
@@ -131,7 +131,13 @@ sudo gitlab-ctl restart | @@ -131,7 +131,13 @@ sudo gitlab-ctl restart | ||
131 | It is also possible to start, stop or restart individual components. | 131 | It is also possible to start, stop or restart individual components. |
132 | 132 | ||
133 | ```shell | 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 | ## Configuration | 143 | ## Configuration |
files/gitlab-cookbooks/gitlab/attributes/default.rb
@@ -146,6 +146,7 @@ default['gitlab']['unicorn']['worker_processes'] = 2 | @@ -146,6 +146,7 @@ default['gitlab']['unicorn']['worker_processes'] = 2 | ||
146 | default['gitlab']['unicorn']['listen'] = '127.0.0.1' | 146 | default['gitlab']['unicorn']['listen'] = '127.0.0.1' |
147 | default['gitlab']['unicorn']['port'] = 8080 | 147 | default['gitlab']['unicorn']['port'] = 8080 |
148 | default['gitlab']['unicorn']['socket'] = '/var/opt/gitlab/gitlab-rails/tmp/sockets/gitlab.socket' | 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 | default['gitlab']['unicorn']['tcp_nopush'] = true | 150 | default['gitlab']['unicorn']['tcp_nopush'] = true |
150 | default['gitlab']['unicorn']['backlog_socket'] = 64 | 151 | default['gitlab']['unicorn']['backlog_socket'] = 64 |
151 | default['gitlab']['unicorn']['worker_timeout'] = 30 | 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,12 +21,14 @@ gitlab_rails_etc_dir = File.join(gitlab_rails_dir, "etc") | ||
21 | gitlab_rails_working_dir = File.join(gitlab_rails_dir, "working") | 21 | gitlab_rails_working_dir = File.join(gitlab_rails_dir, "working") |
22 | 22 | ||
23 | unicorn_listen_socket = node['gitlab']['unicorn']['socket'] | 23 | unicorn_listen_socket = node['gitlab']['unicorn']['socket'] |
24 | +unicorn_pidfile = node['gitlab']['unicorn']['pidfile'] | ||
24 | unicorn_log_dir = node['gitlab']['unicorn']['log_directory'] | 25 | unicorn_log_dir = node['gitlab']['unicorn']['log_directory'] |
25 | unicorn_socket_dir = File.dirname(unicorn_listen_socket) | 26 | unicorn_socket_dir = File.dirname(unicorn_listen_socket) |
26 | 27 | ||
27 | [ | 28 | [ |
28 | unicorn_log_dir, | 29 | unicorn_log_dir, |
29 | - unicorn_socket_dir | 30 | + unicorn_socket_dir, |
31 | + File.dirname(unicorn_pidfile) | ||
30 | ].each do |dir_name| | 32 | ].each do |dir_name| |
31 | directory dir_name do | 33 | directory dir_name do |
32 | owner node['gitlab']['user']['username'] | 34 | owner node['gitlab']['user']['username'] |
@@ -51,6 +53,19 @@ unicorn_config File.join(gitlab_rails_etc_dir, "unicorn.rb") do | @@ -51,6 +53,19 @@ unicorn_config File.join(gitlab_rails_etc_dir, "unicorn.rb") do | ||
51 | working_directory gitlab_rails_working_dir | 53 | working_directory gitlab_rails_working_dir |
52 | worker_processes node['gitlab']['unicorn']['worker_processes'] | 54 | worker_processes node['gitlab']['unicorn']['worker_processes'] |
53 | preload_app true | 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 | owner "root" | 69 | owner "root" |
55 | group "root" | 70 | group "root" |
56 | mode "0644" | 71 | mode "0644" |
@@ -59,6 +74,7 @@ end | @@ -59,6 +74,7 @@ end | ||
59 | 74 | ||
60 | runit_service "unicorn" do | 75 | runit_service "unicorn" do |
61 | down node['gitlab']['unicorn']['ha'] | 76 | down node['gitlab']['unicorn']['ha'] |
77 | + restart_command 2 # Restart Unicorn using SIGUSR2 | ||
62 | options({ | 78 | options({ |
63 | :log_directory => unicorn_log_dir | 79 | :log_directory => unicorn_log_dir |
64 | }.merge(params)) | 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 | exec 2>&1 | 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" |