Start an ActionCable Puma service

parent ac84441a
......@@ -50,6 +50,7 @@ to work best with the available resources. Check out the [documentation](setting
- [LDAP](settings/ldap.md)
- [Puma](settings/puma.md)
- [Unicorn](settings/unicorn.md)
- [ActionCable](settings/actioncable.md)
- [Redis](settings/redis.md)
- [Logs](settings/logs.md)
- [Database](settings/database.md)
......
......@@ -15,6 +15,7 @@ by default:
| <a name="postgresql"></a> PostgreSQL | Yes | Socket | Port (5432) | X |
| <a name="redis"></a> Redis | Yes | Socket | Port (6379) | X |
| <a name="puma"></a> Puma | Yes | Socket | Port (8080) | X |
| <a name="actioncable"></a> ActionCable | Yes | Socket | Port (8081) | X |
| <a name="gitlab-workhorse"></a> GitLab Workhorse | Yes | Socket | Port (8181) | X |
| <a name="nginx-status"></a> NGINX status | Yes | Port | X | 8060 |
| <a name="prometheus"></a> Prometheus | Yes | Port | X | 9090 |
......
# ActionCable
ActionCable is run as a separate Puma server that only handles websocket connections.
NOTE: **Note:** ActionCable is **experimental** and the features that use this service are behind feature flags.
This service is disabled by default. To enable:
```ruby
actioncable['enable'] = true
```
## Configuring the worker pool size
ActionCable uses a separate thread pool to handle the websocket connections. The number of threads can be configured
using the `actioncable['worker_pool_size']` option.
......@@ -21,6 +21,7 @@ gitlab_geo_helper = GitlabGeoHelper.new(node)
dependent_services = []
dependent_services << "runit_service[unicorn]" if omnibus_helper.should_notify?("unicorn")
dependent_services << "runit_service[puma]" if omnibus_helper.should_notify?("puma")
dependent_services << "runit_service[actioncable]" if omnibus_helper.should_notify?("actioncable")
dependent_services << "runit_service[sidekiq]" if omnibus_helper.should_notify?("sidekiq")
bash 'migrate gitlab-geo tracking database' do
......
......@@ -413,7 +413,7 @@ default['gitlab']['gitlab-rails']['monitoring_whitelist'] = ['127.0.0.0/8', '::1
default['gitlab']['gitlab-rails']['monitoring_unicorn_sampler_interval'] = 10
default['gitlab']['gitlab-rails']['shutdown_blackout_seconds'] = 10
# Default dependent services to restart in the event that files-of-interest change
default['gitlab']['gitlab-rails']['dependent_services'] = %w{unicorn puma sidekiq sidekiq-cluster}
default['gitlab']['gitlab-rails']['dependent_services'] = %w{unicorn puma actioncable sidekiq sidekiq-cluster}
###
# Unleash
......@@ -468,6 +468,25 @@ default['gitlab']['puma']['exporter_enabled'] = false
default['gitlab']['puma']['exporter_address'] = "127.0.0.1"
default['gitlab']['puma']['exporter_port'] = 8083
####
# ActionCable
####
default['gitlab']['actioncable']['enable'] = false
default['gitlab']['actioncable']['ha'] = false
default['gitlab']['actioncable']['log_directory'] = "/var/log/gitlab/actioncable"
default['gitlab']['actioncable']['listen'] = "127.0.0.1"
default['gitlab']['actioncable']['port'] = 8081
default['gitlab']['actioncable']['socket'] = '/var/opt/gitlab/gitlab-rails/sockets/gitlab_actioncable.socket'
# Path to the puma server Process ID file
# defaults to /opt/gitlab/var/actioncable/actioncable.pid. The install-dir path is set at build time
default['gitlab']['actioncable']['pidfile'] = "#{node['package']['install-dir']}/var/actioncable/actioncable.pid"
default['gitlab']['actioncable']['state_path'] = "#{node['package']['install-dir']}/var/actioncable/actioncable.state"
default['gitlab']['actioncable']['worker_timeout'] = 60
default['gitlab']['actioncable']['per_worker_max_memory_mb'] = nil
default['gitlab']['actioncable']['worker_processes'] = 2
default['gitlab']['actioncable']['min_threads'] = 4
default['gitlab']['actioncable']['max_threads'] = 4
####
# Sidekiq
####
......
......@@ -24,6 +24,7 @@ class LogrotateHelper < AccountHelper
# to add a service here.
# https://gitlab.com/gitlab-org/omnibus-gitlab/issues/4606
{
'actioncable' => { username: gitlab_user, group: gitlab_group },
'alertmanager' => { username: prometheus_user, group: prometheus_user },
'consul' => { username: consul_user, group: consul_group },
'crond' => { username: 'root', group: 'root' },
......
#
# Copyright:: Copyright (c) 2012 Opscode, Inc.
# Copyright:: Copyright (c) 2020 GitLab.com
# License:: Apache License, Version 2.0
#
# 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.
#
account_helper = AccountHelper.new(node)
omnibus_helper = OmnibusHelper.new(node)
metrics_dir = File.join(node['gitlab']['runtime-dir'].to_s, 'gitlab/actioncable') unless node['gitlab']['runtime-dir'].nil?
rails_app = 'gitlab-rails'
svc = 'actioncable'
user = account_helper.gitlab_user
rails_home = node['gitlab']['gitlab-rails']['dir']
puma_listen_socket = node['gitlab'][svc]['socket']
puma_pidfile = node['gitlab'][svc]['pidfile']
puma_state_path = node['gitlab'][svc]['state_path']
puma_log_dir = node['gitlab'][svc]['log_directory']
puma_socket_dir = File.dirname(puma_listen_socket)
puma_listen_tcp = [node['gitlab'][svc]['listen'], node['gitlab'][svc]['port']].join(':')
puma_etc_dir = File.join(rails_home, "etc")
puma_working_dir = File.join(rails_home, "working")
puma_log_dir = node['gitlab'][svc]['log_directory']
puma_rb = File.join(puma_etc_dir, "puma_actioncable.rb")
[
puma_log_dir,
File.dirname(puma_pidfile)
].each do |dir_name|
directory dir_name do
owner user
mode '0700'
recursive true
end
end
directory puma_socket_dir do
owner user
group AccountHelper.new(node).web_server_group
mode '0750'
recursive true
end
puma_config puma_rb do
tag 'gitlab-puma-actioncable-worker'
rackup 'cable/config.ru'
environment node['gitlab'][rails_app]['environment']
listen_socket puma_listen_socket
listen_tcp puma_listen_tcp
worker_timeout node['gitlab'][svc]['worker_timeout']
per_worker_max_memory_mb node['gitlab'][svc]['per_worker_max_memory_mb']
working_directory puma_working_dir
worker_processes node['gitlab'][svc]['worker_processes']
min_threads node['gitlab'][svc]['min_threads']
max_threads node['gitlab'][svc]['max_threads']
stderr_path File.join(puma_log_dir, "puma_actioncable_stderr.log")
stdout_path File.join(puma_log_dir, "puma_actioncable_stdout.log")
pid puma_pidfile
state_path puma_state_path
install_dir node['package']['install-dir']
owner "root"
group "root"
mode "0644"
action :create
dependent_services omnibus_helper.should_notify?(svc) ? ["runit_service[#{svc}]"] : []
end
runit_service svc do
down node['gitlab'][svc]['ha']
# sv-control-h handles a HUP signal and issues a SIGINT, SIGTERM
# to the master puma process to perform a graceful restart
restart_command 'hup'
template_name 'puma'
control %w[t h]
options({
service: svc,
user: account_helper.gitlab_user,
groupname: account_helper.gitlab_group,
rails_app: rails_app,
puma_rb: puma_rb,
log_directory: puma_log_dir,
metrics_dir: metrics_dir,
clean_metrics_dir: false
}.merge(params))
log_options node['gitlab']['logging'].to_hash.merge(node['gitlab'][svc].to_hash)
end
if node['gitlab']['bootstrap']['enable']
execute "/opt/gitlab/bin/gitlab-ctl start #{svc}" do
retries 20
end
end
consul_service 'actioncable' do
action Prometheus.service_discovery_action
ip_address node['gitlab'][svc]['listen']
port node['gitlab'][svc]['port']
reload_service false unless node['consul']['enable']
end
#
# Copyright:: Copyright (c) 2012 Opscode, Inc.
# Copyright:: Copyright (c) 2020 GitLab.com
# License:: Apache License, Version 2.0
#
# 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.
#
runit_service "actioncable" do
action :disable
end
consul_service 'actioncable' do
action :delete
reload_service false unless node['consul']['enable']
end
......@@ -36,5 +36,6 @@ ruby_block "Move existing certs and link to #{ssl_certs_dir}" do
only_if { cert_helper.new_certificate_added? }
notifies :restart, "runit_service[unicorn]" if omnibus_helper.should_notify?("unicorn")
notifies :restart, "runit_service[puma]" if omnibus_helper.should_notify?("puma")
notifies :restart, "runit_service[actioncable]" if omnibus_helper.should_notify?("actioncable")
notifies :restart, "runit_service[gitlab-pages]" if omnibus_helper.should_notify?("gitlab-pages")
end
......@@ -26,6 +26,7 @@ initial_runner_token = node['gitlab']['gitlab-rails']['initial_shared_runners_re
dependent_services = []
dependent_services << "runit_service[unicorn]" if omnibus_helper.should_notify?("unicorn")
dependent_services << "runit_service[puma]" if omnibus_helper.should_notify?("puma")
dependent_services << "runit_service[actioncable]" if omnibus_helper.should_notify?("actioncable")
dependent_services << "runit_service[sidekiq]" if omnibus_helper.should_notify?("sidekiq")
dependent_services << "runit_service[sidekiq-cluster]" if omnibus_helper.should_notify?("sidekiq-cluster")
......
......@@ -85,11 +85,12 @@ include_recipe "gitlab::selinux"
# add trusted certs recipe
include_recipe "gitlab::add_trusted_certs"
# Create dummy unicorn and sidekiq services to receive notifications, in case
# Create dummy services to receive notifications, in case
# the corresponding service recipe is not loaded below.
%w(
unicorn
puma
actioncable
sidekiq
mailroom
).each do |dummy|
......@@ -137,6 +138,7 @@ include_recipe "gitlab::logrotate_folders_and_configs"
%w[
unicorn
puma
actioncable
sidekiq
sidekiq-cluster
gitlab-workhorse
......
resource_name :puma_config
property :filename, String, name_property: true
property :tag, String, default: 'gitlab-puma-worker'
property :rackup, String, default: 'config.ru'
property :environment, String, default: 'production'
property :install_dir, [String, nil], default: nil
property :listen_socket, [String, nil], default: nil
......
......@@ -9,7 +9,7 @@
# The default is "config.ru".
#
environment '<%= @environment %>'
rackup '<%= @install_dir %>/embedded/service/gitlab-rails/config.ru'
rackup '<%= @install_dir %>/embedded/service/gitlab-rails/<%= @rackup %>'
pidfile '<%= @pid %>'
state_path '<%= @state_path %>'
......@@ -77,7 +77,7 @@ end
preload_app!
tag 'gitlab-puma-worker'
tag '<%= @tag %>'
# Verifies that all workers have checked in to the master process within
# the given timeout. If not the worker process will be restarted. Default
......
#!/bin/sh
# This control handler is meant to be invoked by omnibus-gitlab via Runit
# Let runit capture all script error messages
exec 2>&1
PID=$(cat /opt/gitlab/service/<%= @options[:service] %>/supervise/pid)
readonly puma_graceful_shutdown_sec=90
readonly puma_forceful_shutdown_sec=20
readonly puma_kill_shutdown_sec=10
echo "Received HUP from runit, sending INT signal to perform graceful restart"
signal() {
local pid="$1"
local signal="$2"
local tries="$3"
echo "Sending $signal signal to Puma $pid..."
kill "-$signal" "$pid"
for i in $(seq 1 "$tries"); do
echo "Waiting for Puma $pid to exit ($i)..."
if ! kill -0 "$pid"; then
echo "Puma $pid did exit."
return 0
fi
sleep 1
done
echo "Puma $pid did not exit after $signal."
return 1
}
(
# we run it in subshell, as `runit` requires
# the process it runs to exit in order to reap
# the service
signal "$PID" "INT" "$puma_graceful_shutdown_sec" ||
signal "$PID" "TERM" "$puma_forceful_shutdown_sec" ||
signal "$PID" "KILL" "$puma_kill_shutdown_sec"
) &
exit 0
#!/bin/sh
echo "Received TERM from runit, sending to process group (-PID)"
kill -- -$(cat /opt/gitlab/service/<%= @options[:service] %>/supervise/pid)
<%= "s#@svlogd_size" if @svlogd_size %>
<%= "n#@svlogd_num" if @svlogd_num %>
<%= "t#@svlogd_timeout" if @svlogd_timeout %>
<%= "!#@svlogd_filter" if @svlogd_filter %>
<%= "u#@svlogd_udp" if @svlogd_udp %>
<%= "p#@svlogd_prefix" if @svlogd_prefix %>
#!/bin/sh
exec svlogd -tt <%= @options[:log_directory] %>
#!/bin/bash
# Let runit capture all script error messages
exec 2>&1
<%= render("make_metrics_rundir.erb") %>
<%= render("mount_point_check.erb") %>
exec chpst -P \
-u <%= @options[:user] %>:<%= @options[:groupname] %> \
-U <%= @options[:user] %>:<%= @options[:groupname] %> \
-e /opt/gitlab/etc/<%= @options[:rails_app] %>/env \
/usr/bin/env \
prometheus_multiproc_dir="${prometheus_run_dir}" \
/opt/gitlab/embedded/bin/bundle exec puma -C <%= @options[:puma_rb] %>
......@@ -83,6 +83,7 @@ module Gitlab
attribute('logging', priority: 20).use { Logging }
attribute('unicorn', priority: 20).use { Unicorn }
attribute('puma', priority: 20).use { Puma }
attribute('actioncable', priority: 20)
attribute('mailroom', priority: 20).use { IncomingEmail }
attribute('gitlab_pages', priority: 20).use { GitlabPages }
attribute('storage_check', priority: 30).use { StorageCheck }
......
......@@ -43,6 +43,7 @@ module Services
service 'crond'
service 'praefect'
service 'unicorn'
service 'actioncable'
end
# Define the services included in the EE edition of GitLab
......
......@@ -183,6 +183,7 @@ describe 'gitlab-ee::geo-secondary' do
sidekiq-cluster
unicorn
puma
actioncable
gitaly
geo-postgresql
gitlab-pages
......
......@@ -35,6 +35,7 @@ describe 'gitlab-ee::geo-database-migrations' do
sidekiq-cluster
unicorn
puma
actioncable
gitaly
geo-postgresql
gitlab-pages
......
require 'chef_helper'
describe 'gitlab::actioncable with Ubuntu 16.04' do
let(:chef_run) do
runner = ChefSpec::SoloRunner.new(
step_into: %w(runit_service),
path: 'spec/fixtures/fauxhai/ubuntu/16.04.json'
)
runner.converge('gitlab::default')
end
before do
allow(Gitlab).to receive(:[]).and_call_original
stub_gitlab_rb(actioncable: { enable: true })
stub_default_should_notify?(true)
stub_should_notify?('actioncable', true)
end
context 'when actioncable is enabled' do
it_behaves_like 'enabled runit service', 'actioncable', 'root', 'root', 'git', 'git'
describe 'logrotate settings' do
context 'default values' do
it_behaves_like 'configured logrotate service', 'actioncable', 'git', 'git'
end
context 'specified username and group' do
before do
stub_gitlab_rb(
user: {
username: 'foo',
group: 'bar'
}
)
end
it_behaves_like 'configured logrotate service', 'actioncable', 'foo', 'bar'
end
end
it 'creates runtime directories' do
expect(chef_run).to create_directory('/var/log/gitlab/actioncable').with(
owner: 'git',
group: nil,
mode: '0700'
)
expect(chef_run).to create_directory('/opt/gitlab/var/actioncable').with(
owner: 'git',
group: nil,
mode: '0700'
)
expect(chef_run).to create_directory('/var/opt/gitlab/gitlab-rails/sockets').with(
owner: 'git',
group: 'gitlab-www',
mode: '0750'
)
end
it 'renders the runit configuration with expected configuration' do
expect(chef_run).to render_file('/opt/gitlab/sv/actioncable/run')
.with_content { |content|
expect(content).not_to match(/export prometheus_run_dir=\'\'/)
expect(content).not_to match(/rm \/run\/gitlab\/actioncable/)
expect(content).to match(/-u git:git/)
expect(content).to match(/-U git:git/)
expect(content).to match(/mkdir -p \/run\/gitlab\/actioncable/)
expect(content).to match(/chmod 0700 \/run\/gitlab\/actioncable/)
expect(content).to match(/chown git \/run\/gitlab\/actioncable/)
expect(content).to match(/export prometheus_run_dir=\'\/run\/gitlab\/actioncable\'/)
expect(content).to match(%r(/opt/gitlab/embedded/bin/bundle exec puma -C /var/opt/gitlab/gitlab-rails/etc/puma_actioncable.rb))
}
end
it 'renders the puma_actioncable.rb file' do
expect(chef_run).to create_puma_config('/var/opt/gitlab/gitlab-rails/etc/puma_actioncable.rb').with(
tag: 'gitlab-puma-actioncable-worker',
rackup: 'cable/config.ru',
environment: 'production',
pid: '/opt/gitlab/var/actioncable/actioncable.pid',
state_path: '/opt/gitlab/var/actioncable/actioncable.state',
listen_socket: '/var/opt/gitlab/gitlab-rails/sockets/gitlab_actioncable.socket',
listen_tcp: '127.0.0.1:8081',
working_directory: '/var/opt/gitlab/gitlab-rails/working',
worker_processes: 2,
min_threads: 4,
max_threads: 4
)
end
end
context 'with custom ActionCable settings' do
before do
stub_gitlab_rb(
actioncable: {
enable: true,
worker_timeout: 120,
worker_processes: 4,
min_threads: 5,
max_threads: 10,
listen: '10.0.0.1',
port: 9000,
socket: '/tmp/actioncable.socket',
state_path: '/tmp/actioncable.state',
per_worker_max_memory_mb: 1000
}
)
end
it 'renders the puma_actioncable.rb file' do
expect(chef_run).to create_puma_config('/var/opt/gitlab/gitlab-rails/etc/puma_actioncable.rb').with(
state_path: '/tmp/actioncable.state',
listen_socket: '/tmp/actioncable.socket',
listen_tcp: '10.0.0.1:9000',
worker_processes: 4,
min_threads: 5,
max_threads: 10,
per_worker_max_memory_mb: 1000
)
end
end
context 'with custom user and group' do
before do
stub_gitlab_rb(
actioncable: {
enable: true
},
user: {
username: 'foo',
group: 'bar'
}
)
end
it_behaves_like 'enabled runit service', 'actioncable', 'root', 'root', 'foo', 'bar'
end
context 'with custom runtime_dir' do
before do
stub_gitlab_rb(
runtime_dir: '/tmp/test-dir',
actioncable: {
enable: true
}
)
end
it 'uses the user-specific runtime_dir' do
expect(chef_run).to render_file('/opt/gitlab/sv/actioncable/run')
.with_content { |content|
expect(content).to match(%r(export prometheus_run_dir='/tmp/test-dir/gitlab/actioncable'))
expect(content).to match(%r(mkdir -p /tmp/test-dir/gitlab/actioncable))
}
end
end
end
describe 'gitlab::actioncable Ubuntu 16.04 with no tmpfs' do
let(:chef_run) do
runner = ChefSpec::SoloRunner.new(
path: 'spec/fixtures/fauxhai/ubuntu/16.04-no-run-tmpfs.json',
step_into: %w(runit_service)
)
runner.converge('gitlab::default')
end
before do
allow(Gitlab).to receive(:[]).and_call_original
stub_gitlab_rb(actioncable: { enable: true })
end
context 'when ActionCable is enabled on a node with no /run or /dev/shm tmpfs' do
it_behaves_like 'enabled runit service', 'actioncable', 'root', 'root', 'git', 'git'
it 'populates the files with expected configuration' do
expect(chef_run).to render_file('/opt/gitlab/sv/actioncable/run')
.with_content { |content|
expect(content).to match(/export prometheus_run_dir=\'\'/)
expect(content).not_to match(/mkdir -p \/run\/gitlab\/actioncable/)
}
end
end
end
describe 'gitlab::actioncable Ubuntu 16.04 Docker' do
let(:chef_run) do
runner = ChefSpec::SoloRunner.new(
path: 'spec/fixtures/fauxhai/ubuntu/16.04-docker.json',
step_into: %w(runit_service)
)
runner.converge('gitlab::default')
end
before do
allow(Gitlab).to receive(:[]).and_call_original
stub_gitlab_rb(
actioncable: {
enable: true
}
)
end
context 'when actioncable is enabled on a node with a /dev/shm tmpfs' do
it_behaves_like 'enabled runit service', 'actioncable', 'root', 'root', 'git', 'git'
it 'populates the files with expected configuration' do
expect(chef_run).to render_file('/opt/gitlab/sv/actioncable/run')
.with_content { |content|
expect(content).to match(/export prometheus_run_dir=\'\/dev\/shm\/gitlab\/actioncable\'/)
expect(content).to match(/mkdir -p \/dev\/shm\/gitlab\/actioncable/)
}
end
end
end
......@@ -73,6 +73,8 @@ describe 'gitlab::puma with Ubuntu 16.04' do
it 'renders the puma.rb file' do
expect(chef_run).to create_puma_config('/var/opt/gitlab/gitlab-rails/etc/puma.rb').with(
tag: 'gitlab-puma-worker',
rackup: 'config.ru',
environment: 'production',
pid: '/opt/gitlab/var/puma/puma.pid',
state_path: '/opt/gitlab/var/puma/puma.state',
......
......@@ -89,6 +89,7 @@ describe OmnibusHelper do
%w(
unicorn
puma
actioncable
sidekiq
sidekiq-cluster
gitlab-workhorse
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment