Установка Redmine на стеке RVM + RoR + Unicorn + Nginx

23.11.2020
Эта статья является результатом работы по установке и автономизации работы сервера на базе ОС Linux с Redmine по различным источникам (в том числе и по официальной инструкции), часть команд и последовательность действий были взяты из других источников. Все используемые источники указаны в конце статьи.

Введение

В общем задача звучала так: установить Redmine на сервер, где веб-сервер на nginx.

Так как Redmine написан на RoR, то необходимо иметь RoR среду, но проблема в том, что разные RoR приложения могут требовать разные версии окружения. В моем случае необходимо было предусмотреть возможность установки RoR приложений с разным окружением, а значит нужен менеджер версий, который будет разворачивать нужную среду в нужном месте.

RVM - менеджер версий сред ruby, начиная от версий интепретатора и заканчивая джемами. Нужен для того чтобы запускать разные RoR приложения на одном сервере, которые могут требовать разные среды исполнения.

Также хотелось классический вариант веб-сервера на nginx. Однако, nginx не знает как исполнять приложение, и в данном случае выступает в качестве прокси на исполняющий веб-сервер RoR приложения.

Unicorn - веб-сервер для Rack (и RoR в том числе) приложений. Почему-то мне не захотелось использовать passenger, наверное потому что nginx должен быть скомпилен с passenger, то есть не чистый nginx. А еще потому что Unicorn может быть свой для каждого RoR приложения, а не один глобальный.

Теперь задача становится более ясной: установить Redmine на сервер, развернув стек RoR+Unicorn+Nginx, с автоматическим запуском.

Подготовка

Отключение ipv6

Примечание: необязательно, написал потому что мне надо было решить проблему :)

Установив на VPS Ubuntu 16.04 при попытке apt-get install не мог получить данные по ipv6:

•••
plaintext
0% [Connecting to archive.ubuntu.com (2001:67c:1562::15)]

Решение: отключить ipv6 добавив соответсвующую информацию в /etc/sysctl.conf:

•••
bash
echo '' >> /etc/sysctl.conf echo 'net.ipv6.conf.all.disable_ipv6 = 1' >> /etc/sysctl.conf echo 'net.ipv6.conf.default.disable_ipv6 = 1' >> /etc/sysctl.conf echo 'net.ipv6.conf.lo.disable_ipv6 = 1' >> /etc/sysctl.conf sysctl -p

Установка общего софта

curl для установки RVM (Ruby Version Manager):

•••
bash
apt-get install curl

Примечание: остальное необязательно если читатель пользуется другим софтом для этих дел :)

Midnight Commander (файловый менеджер):

•••
bash
apt-get install mc

Nano (файловый менеджер):

•••
bash
apt-get install nano

Установка

Получение Redmine

На данный момент самая свежая стабильная версия Redmine 4.1.1. Заходим в директорию /opt/ скачиваем туда архив, распаковываем переименовываем в redmine и удаляем архив:

•••
bash
wget --no-check-certificate https://www.redmine.org/releases/redmine-4.1.1.tar.gz tar xvzf redmine-4.1.1.tar.gz mv redmine-4.1.1 redmine rm redmine-4.1.1.tar.gz

Теперь путь до Redmine: /opt/redmine/

Проверив владельца директории можно увидеть неизвестного юзера 1000, нам надо чтобы директорией владел www-data (чтобы автоматически поднимать сервис от юзера www-data):

•••
bash
chown -R www-data /opt/redmine/

PostgreSQL

Этот раздел нужен только если Вы выбрали в качестве СУБД PostgreSQL!

Здесь все просто, делаем как написано в официальной инструкции по установке redmine - устанавливаем PostgreSQL версии >9.2.

Проверим текущую версию пакета в репозитории:

•••
bash
apt policy postgresql
Получаем:
•••
bash
postgresql: Installed: (none) Candidate: 9.5+173ubuntu0.3 Version table: 9.5+173ubuntu0.3 500 500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages 500 http://security.ubuntu.com/ubuntu xenial-security/main amd64 Packages 9.5+173 500 500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages

Если все нормально двигаемся дальше, иначе ищем способ установить нужную версию пакета.

•••
bash
apt install postgresql

Теперь нужно зайти на сервер PostgreSQL (чтобы зайти надо сменить юзера терминала, потому что только юзер postgres может в psql), создать пользователя и базу данных:

•••
bash
sudo -i -u postgres #Или su postgres psql

•••
sql
CREATE ROLE redmine LOGIN ENCRYPTED PASSWORD 'password' NOINHERIT VALID UNTIL 'infinity'; CREATE DATABASE redmine WITH ENCODING='UTF8' OWNER=redmine; ALTER DATABASE "redmine" SET datestyle="ISO,MDY";

Для выхода надо ввести \q

На работе уже использовалась СУБД PostgreSQL, в тоже место предполагалось ставить GitLab (он работает с PostgreSQL), поэтому здесь сделал так.

MySQL

Этот раздел нужен только если Вы выбрали в качестве СУБД MySQL!

Установка сервера MySQL:

•••
bash
apt-get install mysql-server

Вход в mysql:

•••
bash
mysql -u root -p

Действия с MySQL < 8.0 такие же как в официальной инструкции redmine, создать базу данных и пользователя, назначив ему полные привелегии:

•••
sql
CREATE DATABASE redmine CHARACTER SET utf8mb4; CREATE USER 'redmine'@'localhost' IDENTIFIED BY 'password'; GRANT ALL PRIVILEGES ON redmine.* TO 'redmine'@'localhost';

Однако, в MySQL >= 8.0 для пользователя необходимо использовать старый метод аутентификации mysql_native_password (и вот еще ссылка):

•••
sql
ALTER USER 'redmine'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';

У меня в одном проекте уже использовалась СУБД MySQL 8, поэтому решил поставить с использованием MySQL.

RVM

Сначала надо установить gnupg2 и установить проверочные ключи для установки RVM:

•••
bash
apt install gnupg2 gpg2 --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB

Теперь в директорию /opt/ надо скачать rvm скрипт:

•••
bash
curl -sSL https://get.rvm.io -o rvm.sh

Или так:

•••
bash
wget --output-document=rvm.sh https://get.rvm.io

Затем установка самого RVM (из директории /opt/):

•••
bash
cat rvm.sh | bash -s stable --rails

После установки RVM будет создана новая группа rvm, в которую надо поместить пользователя www-data (чтобы от него запускать RVM):

•••
bash
usermod -a -G rvm www-data

Окружение Ruby On Rails для текущей сессии терминала можно установить так (а можно и по другому):

•••
bash
source /usr/local/rvm/scripts/rvm

На данный момент мы скачали самую последнюю стабильную версию Redmine 4.1.1, которая требует версию Ruby [2.3, 2.6]. Узнаем что есть в RVM:

•••
bash
rvm list known

Вывод достаточно большой, но нас интересует именно это:

•••
bash
[ruby-]1.8.6[-p420] [ruby-]1.8.7[-head] # security released on head [ruby-]1.9.1[-p431] [ruby-]1.9.2[-p330] [ruby-]1.9.3[-p551] [ruby-]2.0.0[-p648] [ruby-]2.1[.10] [ruby-]2.2[.10] [ruby-]2.3[.8] [ruby-]2.4[.9] [ruby-]2.5[.7] [ruby-]2.6[.5] [ruby-]2.7[.0]

Последняя версия Ruby 2.6.5, установим ее и назначим использовать по умолчанию:

•••
bash
rvm install 2.6.5 rvm use 2.6.5

Создадим окружение для Redmine:

•••
bash
rvm gemset create redmine echo "rvm use ruby-2.6.5@redmine" > /opt/redmine/.rvmrc

Теперь при входе в директорию /opt/redmine/ через терминал, для этой директории будет установлено окружение ruby-2.6.5@redmine:

•••
plaintext
Using /usr/local/rvm/gems/ruby-2.6.5 with gemset redmine

Настройка и тестовый запуск Redmine

Настройка базы данных

Файл /opt/redmine/config/database.yml.sample является примером как должен выглядеть конфиг подключения к БД. Создадим конфиг:

•••
bash
touch /opt/redmine/config/database.yml

Запишем в конфиг данные для работы с БД:

•••
plaintext
production: adapter: postgresql database: redmine host: localhost username: redmine password: password

Сборка зависимостей и решение проблем

Теперь надо собрать все зависимости RoR приложения:

•••
bash
bundle

При сборки RoR приложения в разных условиях могут возникать разные проблемы.

Используя PostgreSQL в качестве СУБД команда bundle может закончится ошибкой связанной с модулем pg:

•••
plaintext
Gem::Ext::BuildError: ERROR: Failed to build gem native extension. ... Gem files will remain installed in /usr/local/rvm/gems/ruby-2.6.5/gems/pg-1.1.4 for inspection. Results logged to /usr/local/rvm/gems/ruby-2.6.5/extensions/x86_64-linux/2.6.0/pg-1.1.4/gem_make.out An error occurred while installing pg (1.1.4), and Bundler cannot continue. Make sure that gem install pg -v '1.1.4' --source 'https://rubygems.org/' succeeds before bundling.

Решение нашел здесь, а именно:

•••
bash
apt-get install libpq-dev

Для MySQL тоже возникает проблема с модулем mysql2 (детали ошибки аналогичны как и для PostgreSQL). Решение нашлось здесь, а именно:

•••
bash
apt-get install build-essential ruby-dev libmysqlclient-dev

Еще была проблема с nokogiri, решение нашел здесь, а именно:

•••
bash
apt-get install build-essential patch ruby-dev zlib1g-dev liblzma-dev gem install nokogiri

Не забываем опять запустить bundle

Инициализация БД

Теперь нужно сделать 5-ый, 6-ой и 7-ой шаги: сгенерировать случайный ключ для сессий, создать структуру БД и инициализировать данные в БД:

•••
bash
bundle exec rake generate_secret_token RAILS_ENV=production bundle exec rake db:migrate RAILS_ENV=production REDMINE_LANG=ru bundle exec rake redmine:load_default_data

Тестовый запуск

Пробуем запустить Redmine на webrick:

•••
bash
bundle exec rails server webrick -e production

Если запуск прошел успешно, то на 3000 порту можно посмотреть сайт (localhost:3000 или ip:3000).

В продакшене не рекомендуется использовать webrick, поэтому двигаемся дальше.

Unicorn

В /opt/redmine/GemFile запишем зависимость:

•••
plaintext
gem 'unicorn'

Создадим конфигурационный файл для unicorn:

•••
bash
touch /opt/redmine/config/unicorn.rb

И вставляем туда текст конфига:

•••
bash
# установка путей до приложения app_dir = File.expand_path("../..", __FILE__) shared_dir = "#{app_dir}/shared" working_directory app_dir # установка некоторых опций unicorn worker_processes 2 preload_app true timeout 30 # сокет файл для доступа nginx listen "#{shared_dir}/sockets/unicorn.sock", :backlog => 64 # установка файлов логов stderr_path "#{shared_dir}/log/unicorn.stderr.log" stdout_path "#{shared_dir}/log/unicorn.stdout.log" # установка pid файла, который будет сопоставим с процессом unicorn (чтобы по нему определять процесс) pid "#{shared_dir}/pids/unicorn.pid" before_fork do |server, worker| defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect! end after_fork do |server, worker| defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection end

И опять:

•••
bash
bundle

Создаем директории в соответсвии с конфигом и меняем владельца:

•••
bash
mkdir -p /opt/redmine/shared/pids /opt/redmine/shared/sockets /opt/redmine/shared/log chown -R www-data:rvm shared

Теперь выполнив bundle (если нет ошибок), можно запускать unicorn (из директории /opt/redmine/):

•••
bash
unicorn_rails -c config/unicorn.rb -E production -D

Уничтожить процесс unicorn можно по pid файлу:

•••
bash
pkill -QUIT --pidfile /opt/redmine/shared/pids/unicorn.pid

Или вот еще варианты уничтожения процесса Unicorn.

Nginx

Ставим nginx:

•••
bash
apt-get install nginx

Пишем в конфиг nginx (у меня путь /etc/nginx/nginx.conf), в секцию http (при этом виртуальные хосты должны быть отключены):

•••
plaintext
upstream redmine { server unix:/opt/redmine/shared/sockets/unicorn.sock fail_timeout=0; } server { listen 80; root /opt/redmine/public; try_files $uri/index.html $uri @redmine; location @redmine { proxy_pass http://redmine; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; } }

Или вот полный конфиг (для доступа к Redmine по ip адресу):

•••
plaintext
user www-data; worker_processes auto; pid /run/nginx.pid; include /etc/nginx/modules-enabled/*.conf; events { worker_connections 768; } http { sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; server_names_hash_bucket_size 64; include /etc/nginx/mime.types; default_type application/octet-stream; # Logging Settings access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; # Gzip Settings gzip on; upstream redmine { server unix:/opt/redmine/shared/sockets/unicorn.sock fail_timeout=0; } server { listen 80; root /opt/redmine/public; try_files $uri/index.html $uri @redmine; location @redmine { proxy_pass http://redmine; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; } } }

Перезагружаем nginx:

•••
bash
service nginx restart

Если nginx запустился без ошибок, то пробуем обратится на сайт по ip адресу (или по localhost), Redmine должно работать.

Автозагрузка

Если перезагрузить сервер (или внезапно уничтожить Unicorn), то сам Unicorn автоматически не поднимется, надо это организовать.

Почитать о том, что происходит ниже можно здесь и здесь.

Идем в /etc/systemd/system/ и создаем файл redmine.service:

•••
bash
touch /etc/systemd/system/redmine.service

Записываем туда:

•••
plaintext
[Unit] Description=redminerun After=syslog.target network.target [Service] Type=forking PIDFile=/opt/redmine/shared/pids/unicorn.pid WorkingDirectory=/opt/redmine/ User=www-data Group=rvm Environment=RAILS_ENV=production ExecStart=/opt/redmine/config/unicorn_start ExecReload=/opt/redmine/config/unicorn_reload ExecStop=/opt/redmine/config/unicorn_stop OOMScoreAdjust=-100 TimeoutSec=30 Restart=always RestartSec=20s [Install] WantedBy=multi-user.target

Вкратце:

  • PIDFile - pid файл процесса, по которому будет отслеживаться жизнь процесса
  • User и Group - юзер и группа от которых будет производится запуск
  • ExecStart - команда запуска
  • ExecReload - команда перезагрузки
  • ExecStop - команда остановки
  • RestartSec - рестартовать процесс через N времени в случае если процесс отвалился
ExecStart, ExecReload, ExecStop могут быть путями до bash скриптов. Именно так и было мной сделано (потому что прежде выполнения команд старта/перезагрузки необходимо было разворачивать окружение, сделать это в файле конфига не представлялось возможным :))

Создаем файлы скрипты в /opt/redmine/config/ меняем владельца и группу, и ставим права на запуск:

•••
bash
cd /opt/redmine/config/ touch unicorn_start unicorn_reload unicorn_stop chown www-data:rvm unicorn_* chmod +x unicorn_*

unicorn_start для запуска unicorn (запускаем rvm, переходим в директорию с redmine - установится нужное окружение, запускаем unicorn):

•••
bash
#!/bin/bash - source /usr/local/rvm/scripts/rvm cd /opt/redmine/ unicorn_rails -c config/unicorn.rb -E production -D

unicorn_stop для остановки сервиса:

•••
bash
#!/bin/bash - pkill -QUIT --pidfile /opt/redmine/shared/pids/unicorn.pid

unicorn_reload для перезапуска сервиса (останавливаем и запускаем):

•••
bash
#!/bin/bash - /opt/redmine/config/unicorn_stop /opt/redmine/config/unicorn_start

Теперь просмотрев статус сервиса можно увидеть:

•••
bash
systemctl status redmine ● redmine.service - redminerun Loaded: loaded (/etc/systemd/system/redmine.service; disabled; vendor preset: enabled) Active: inactive (dead)

Включаем сервис:

•••
bash
systemctl enable redmine

Теперь можно start/reload/stop.

Запустим сервис:

•••
bash
systemctl start redmine

Если запуск произошел успешно, значит все верно настроено, в случае перезагрузки или внезапного падения Unicorn - он будет поднят автоматически!

Источники