Last-modified: 2020-12-17 (木) 09:44:37 (1437d)
Redmineの導入 †
Redmineについては、下記3種の導入方法を検討した。
- 公式サイトの本体コード一式 + RHELのRPM + gemコマンドによるライブラリ導入
- 各々のライブラリ依存性管理が非常に大変。特にnokogiriとopensslが鬼門。
- bitnami-redmineを用いた一括導入
- Git, Redmine, PhpMyAdminなども導入されるが、今回は使わない
- Redmine公式ではない
- 公式dockerイメージを用いた導入
今回は「公式dockerイメージを用いた導入」を採用する。
導入サーバの前提条件 †
- OSはRHEL7(or CentOS7)かそれ以降の版とする。
- 事前導入するソフトウェアパッケージは最小構成。
- インターネットに接続可能 ※必要ならproxy設定
/etc/dnf/dnf.conf
proxy=http://proxy.jomura.net:8080/ #as your own
~/.bashrc
export HTTP_PROXY=http://proxy.jomura.net:8080/ #as your own
export HTTPS_PROXY=${HTTP_PROXY}
- パッケージの更新が事前に実行されている
dnf clean all && dnf -y update && reboot
- Redmine Webサイト用のFully Qualified Domain Name(FQDN)が用意されている。
Redmine用 configuration.ymlの作成 †
Redmine初期設定用SQLの作成 †
- Ansibleサーバ上の一般ユーザで実行
cat << "_EOF_" > settings.sql
INSERT INTO `settings` (`name`, `value`) VALUES
('search_results_per_page','30'), -- ページごとの検索結果表示件数 (10)
('host_name','cxits.tg-group.tokyo-gas.co.jp'), -- ホスト名とパス (localhost:3000)
('protocol','http'), -- プロトコル (http)
('text_formatting','textile'), -- テキスト書式 (textile)
('default_language','ja'), -- デフォルトの言語 (en)
('force_default_language_for_anonymous','0'), -- 匿名ユーザーにデフォルトの言語を強制 (0)
('force_default_language_for_loggedin','0'), -- ログインユーザーにデフォルトの言語を強制 (0)
('user_format','lastname_firstname'), -- ユーザー名の表示形式 (firstname_lastname)
('thumbnails_enabled','1'), -- 添付ファイルのサムネイル画像を表示 (0)
('login_required','1'), -- 認証が必要 (0)
('autologin','7'), -- 自動ログイン (0)
('max_additional_emails','3'), -- 追加メールアドレス数の上限 (5)
('session_lifetime','86400'), -- 有効期間の最大値 (0)
('session_timeout','240'), -- 無操作タイムアウト (0)
('default_users_time_zone','Tokyo'), -- タイムゾーン ()
('rest_api_enabled','1'), -- RESTによるWebサービスを有効にする (0)
('jsonp_enabled','1'), -- JSONPを有効にする (0)
('default_projects_public','0'), -- デフォルトで新しいプロジェクトは公開にする (1)
('default_projects_modules','---
- issue_tracking
- time_tracking
- wiki
- repository
- calendar
- gantt
'), -- 新規プロジェクトにおいてデフォルトで有効になるモジュールチケットトラッキング
('default_projects_tracker_ids','---
- \'1\'
- \'2\'
- \'3\'
'), -- 新規プロジェクトにおいてデフォルトで有効になるトラッカー
('cross_project_issue_relations','1'), -- 異なるプロジェクトのチケット間で関連の設定を許可 (0)
('default_issue_start_date_to_creation_date','1'), -- 現在の日付を新しいチケットの開始日とする (1)
('issue_done_ratio','issue_status'), -- 進捗率の算出方法 (issue_field)
('issue_list_default_totals','---
- estimated_hours
- spent_hours
'), -- チケットの一覧で表示する項目(合計)
('attachment_max_size','51200'), -- 添付ファイルサイズの上限 (5120)
('repositories_encodings','utf-8,cp932,euc-jp'), -- 添付ファイルとリポジトリのエンコーディング ()
('mail_from','user@jomura.net'), -- 送信元メールアドレス (redmine@example.net)
('enabled_scm','---
- Subversion
- Git
'), -- 使用するバージョン管理システム
('commit_ref_keywords','refs,references,IssueID,*'), -- 参照用キーワード (refs,references,IssueID)
('commit_cross_project_ref','1'); -- 異なるプロジェクトのチケットの参照/修正を許可 (0)
_EOF_
docker-compose.ymlファイルの作成 †
docker用環境変数ファイルの作成 †
- Ansibleサーバ上の一般ユーザで実行
cat << _EOF_ > docker-env
REDMINE_PATH={{ redmine_path }}
TZ=Asia/Tokyo
MYSQL_ROOT_PASSWORD=$(< /dev/urandom tr -dc 'A-Za-z0-9!$%&()*+,-./:;<=>?@[\]^_{|}~' | head -c 16; echo)
REDMINE_DB_MYSQL=mysql
REDMINE_DB_DATABASE=redmine
REDMINE_DB_USERNAME=redmine
REDMINE_DB_PASSWORD=$(< /dev/urandom tr -dc 'A-Za-z0-9!$%&()*+,-./:;<=>?@[\]^_{|}~' | head -c 16; echo)
REDMINE_DB_ENCODING=utf8mb4
REDMINE_MEMCACHED=memcached
_EOF_
chmod go-rwx docker-env
- catに指定する _EOF_ を"でくくってはいけない。urandamコマンド等が実行されなくなる。
playbookの作成 †
- Ansibleサーバ上の一般ユーザで実行
- sudoの場合、become_method: sudo
cat << "_EOF_" > pb_redmine_server-redmine.yml
# install redmine
- hosts: redmine_servers
become: true
become_method: su
environment: "{{ proxy_env }}"
vars:
redmine_path: /srv/redmine
tasks:
- name: check facts
fail:
msg: "Not compatible with [{{ ansible_os_family }}] \
{{ ansible_distribution }} \
{{ ansible_distribution_major_version }}."
when: >
ansible_os_family != 'RedHat'
or ansible_distribution_major_version|int < 7
- name: add docker repo
shell: dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
args:
warn: false
changed_when: false
- name: install RPMs
dnf:
name:
- httpd
- docker-ce
state: latest
- name: set proxy for docker
file: path=/etc/systemd/system/docker.service.d state=directory
when: proxy_env.http_proxy is defined
- copy:
dest: /etc/systemd/system/docker.service.d/http-proxy.conf
force: no
content: "[Service]\n \
Environment = \"http_proxy={{ proxy_env.http_proxy }}\" \
\"https_proxy={{ proxy_env.https_proxy }}\"\n"
when: proxy_env.http_proxy is defined
- name: restart Docker
systemd:
name: docker
state: restarted
daemon_reload: yes
enabled: yes
- name: install docker-compose
stat: path=/usr/local/bin/docker-compose
register: result01
- shell: |
curl --location --output /usr/local/bin/docker-compose \
$(curl --silent --show-error \
https://api.github.com/repos/docker/compose/releases/latest \
| grep 'Linux-x86_64"' \
| grep url \
| cut --delimiter='"' --fields=4 \
)
chmod +x /usr/local/bin/docker-compose
args:
warn: false
when: result01.stat.exists == false
- name: "create {{ redmine_path }}"
file: path={{ redmine_path }} state=directory
register: result02
- shell: |
/usr/sbin/matchpathcon {{ redmine_path }}
/usr/sbin/semanage fcontext --add --type container_file_t {{ redmine_path }}
/usr/sbin/restorecon -v {{ redmine_path }}
/usr/sbin/matchpathcon {{ redmine_path }}
args:
warn: false
when: result02.changed == true
- name: create config files
file: path={{ redmine_path }}/config state=directory
- copy:
src: configuration.yml
dest: "{{ redmine_path }}/config/configuration.yml"
force: no
- copy:
dest: "{{ redmine_path }}/config/additional_environment.rb"
force: no
content: |
config.cache_store = :mem_cache_store, "memcached"
config.logger = Logger.new("#{Rails.root}/log/#{ENV['RAILS_ENV']}.log", 50, 1000000)
config.logger.level = Logger::INFO
- copy:
dest: "{{ redmine_path }}/Gemfile.local"
force: no
content: "gem 'dalli'\n "
- file: path={{ redmine_path }}/../mysql/conf.d state=directory
- copy:
dest: "{{ redmine_path }}/../mysql/conf.d/redmine.cnf"
force: no
content: |
[mysqld]
innodb_buffer_pool_size = 536870912
innodb_log_file_size = 201326592
- name: create a parent dir of svn-repos
file:
path: /var/lib/svn
owner: nobody
group: users
state: directory
mode: 02775
register: result03
- shell: |
/usr/sbin/semanage fcontext -a -t git_rw_content_t "/var/lib/svn(/.*)?"
/usr/sbin/restorecon -Rv /var/lib/svn
args:
warn: false
when: result03.changed == true
- name: create a parent dir of git-repos
file:
path: /var/lib/git
owner: nobody
group: users
state: directory
mode: 02775
register: result03
- shell: |
/usr/sbin/semanage fcontext -a -t git_rw_content_t "/var/lib/git(/.*)?"
/usr/sbin/restorecon -Rv /var/lib/git
args:
warn: false
when: result03.changed == true
- name: docker-compose up
template:
src: docker-compose.yml
dest: "{{ redmine_path }}/docker-compose.yml"
force: no
- template:
src: docker-env
dest: "{{ redmine_path }}/.env"
force: no
mode: 0400
- shell: docker-compose --project-directory {{ redmine_path }} up --detach
args:
chdir: "{{ redmine_path }}"
register: result04
changed_when: '" is up-to-date" not in result04.stderr'
- name: wait for Completed 200 OK
shell: docker container logs redmine 2>/dev/null | tail -15
register: result05
changed_when: false
until: '"Completed 200 OK " in result05.stdout'
retries: 100
delay: 5
- name: set db password file
slurp:
src: "{{ redmine_path }}/.env"
register: result06
- copy:
dest: ~/.my.cnf.org
force: no
mode: 0400
content: "[client]\nuser = redmine\n \
password = {{ result06['content'] | b64decode | \
regex_findall('REDMINE_DB_PASSWORD=(.+)\\n') | first }}\n \
host = localhost\n"
register: result07
- shell: docker cp ~/.my.cnf.org mysql:root/.my.cnf
when: result07.changed
- name: load default data
shell: docker exec {% if proxy_env.http_proxy is defined -%}
-e HTTP_PROXY={{ proxy_env.http_proxy }} -e HTTPS_PROXY={{ proxy_env.https_proxy }} {% endif -%}
redmine bundle exec rake redmine:load_default_data RAILS_ENV=production REDMINE_LANG=ja
register: result08
changed_when: '" is already loaded." not in result08.stdout'
- name: update roles
shell: |
cat << '_EOQ_' | docker exec -i mysql mysql redmine
UPDATE `roles` SET `permissions` = NULL WHERE `id` = '1' OR `id` = '2';
_EOQ_
changed_when: false
- name: insert settings
shell: |
cat << '_EOQ_' | docker exec -i mysql mysql redmine
SELECT count(*) FROM `settings`
_EOQ_
changed_when: false
register: result09
- shell: |
cat settings.sql | docker exec --interactive mysql mysql redmine && \
docker exec redmine passenger-config restart-app /usr/src/redmine
when: result09.stdout_lines[1] == "0"
- name: clear rails cache
shell: docker exec {% if proxy_env.http_proxy is defined -%}
-e HTTP_PROXY={{ proxy_env.http_proxy }} -e HTTPS_PROXY={{ proxy_env.https_proxy }} {% endif -%}
redmine bundle exec rails runner 'Rails.cache.clear'
changed_when: false
- name: bundle install
shell: docker exec {% if proxy_env.http_proxy is defined -%}
-e HTTP_PROXY={{ proxy_env.http_proxy }} -e HTTPS_PROXY={{ proxy_env.https_proxy }} {% endif -%}
redmine bundle install
changed_when: false
- name: restart passenger
shell: docker exec {% if proxy_env.http_proxy is defined -%}
-e HTTP_PROXY={{ proxy_env.http_proxy }} -e HTTPS_PROXY={{ proxy_env.https_proxy }} {% endif -%}
redmine passenger-config restart-app /usr/src/redmine
changed_when: false
- name: modify httpd.conf for redmine
copy:
dest: /etc/httpd/conf.d/proxy-redmine.conf
force: no
mode: 0644
content: |
<IfModule !proxy_module>
LoadModule proxy_module modules/mod_proxy.so
</IfModule>
<IfModule !proxy_http_module>
LoadModule proxy_http_module modules/mod_proxy_http.so
</IfModule>
ProxyPassMatch /git.* !
ProxyPassMatch /svn.* !
ProxyPassMatch /kibana.* !
ProxyPass / http://localhost:3000/
ProxyPassReverse / http://localhost:3000/
- name: httpd_can_network_connect
shell: /usr/sbin/getsebool httpd_can_network_connect
register: result10
- shell: /usr/sbin/setsebool -P httpd_can_network_connect 1
when: result10.stdout == 'httpd_can_network_connect --> off'
notify:
- restart Apache
- name: open ports
firewalld:
service: "{{ item }}"
permanent: true
state: enabled
immediate: yes
loop:
- http
- https
- file: path={{ redmine_path }}/../backup state=directory
- name: backup-redmine-db.service
copy:
dest: /etc/systemd/system/backup-redmine-db.service
content: |
[Unit]
Description=backup redmine db
[Service]
Type=oneshot
ExecStart=/bin/sh -c "docker exec mysql mysqldump --no-tablespaces redmine \
| gzip > {{ redmine_path }}/../backup/redmine_db_`date +%%F`.sql.gz"
ExecStartPost=/bin/find {{ redmine_path }}/../backup \
-name "redmine_db_*.sql.gz" -mtime +30 -delete
- name: backup-redmine-db.timer
copy:
dest: /etc/systemd/system/backup-redmine-db.timer
content: |
[Unit]
Description=backup redmine db
[Timer]
OnCalendar=daily
RandomizedDelaySec=1h
Persistent=true
[Install]
WantedBy=timers.target
- name: backup-redmine-files.service
copy:
dest: /etc/systemd/system/backup-redmine-files.service
content: |
[Unit]
Description=backup redmine files
[Service]
Type=oneshot
ExecStart=/bin/sh -c "tar -C {{ redmine_path }} \
-czf {{ redmine_path }}/../backup/redmine_files_$(date +%%F).tar.gz \
files"
ExecStartPost=/bin/find {{ redmine_path }}/../backup \
-name "redmine_files_*.tar.gz" -mtime +30 -delete
- name: backup-redmine-files.timer
copy:
dest: /etc/systemd/system/backup-redmine-files.timer
content: |
[Unit]
Description=backup redmine files
[Timer]
OnCalendar=daily
RandomizedDelaySec=1h
Persistent=true
[Install]
WantedBy=timers.target
- name: enable timers
systemd:
name: "{{ item }}"
daemon_reload: yes
enabled: yes
state: started
loop:
- backup-redmine-db.timer
- backup-redmine-files.timer
- name: check
shell: systemctl list-unit-files | egrep "STATE|backup" && systemctl list-timers --all
changed_when: false
register: result01
- debug: msg="{{ result01.stdout_lines }}"
handlers:
- name: restart Apache
systemd:
name: httpd
state: restarted
daemon_reload: yes
enabled: yes
_EOF_
- docker_composeはRPMパッケージではなく最新版を使う
playbookの実行 †
補足事項 †
- mysqlコンテナを削除すると、DBデータはどうなる?
ホストの /var/lib/docker/volumes/mysql/_data/ にMySQLのDBデータは永続化されている。mysqlコンテナを削除しても、これらは削除されず、コンテナを再導入すると再利用される。
- mysql_native_passwordの指定は必要?
defaultのsha2_passwordにrailsが対応していない場合を考慮している。
- MySQLやmemcashedは、コンテナではなく、ホスト側にrpm導入したらどうか。
dockerコンテナからホストのプロセスに通信するには、便利な宛先指定方法が無く、ホストのIPアドレスを指定することになり、ポータビリティに欠ける。
同一のcompose.ymlファイルに記述されたコンテナは、同一ネットワークに属しコンテナ名で指定できる。また、RedmineのWebサイトでも、公式dockerイメージはコンテナ化DBを利用するようガイドされている。
- RedmineのURLに、コンテキストパス(/redmine)を設定できる?
RedmineのWebサイトに紹介されている。dockerコンテナ内のpassengerを再設定することで、技術的には可能だが、難易度が高い。また、コンテナの更新を考慮し、できるだけコンテナの編集は避けたい。
今回は、「基本的に全てのURL要求をredmineコンテナにproxyしつつ、/gitだけ除外する」という方式としている。
- MySQLへの設定追加
ホストの/srv/mysql/conf.d/redmine.cnf に永続化されているMySQLの設定ファイルに追記し、mysqlコンテナを再起動すると反映される。
ファイルに記述する前に、"set global …"コマンドを用いて稼働中に動的設定し、要否の判断をするとよい。
また、slow queryの常時出力設定は不要と考えている。必要な時に、動的に設定(set global slow_query=1)すればよい。
set global slow_query_log_file = 'slow.log';
set global long_query_time = 5;
set global slow_query_log = ON;
- Redmine導入用のplaybookに、git用のタスクがあるんだけど
gitリポジトリ用のSELinux設定は、Redmine用のdocker-compose upを実行する前に実施する必要があるため、仕方なく。
- ログの循環
Apache HTTP Serverのaccess_log, error_logは、logrotatedで循環される(defaultのまま)。
Redmineのproduction.logは、additional_environment.rbで循環設定している(playbookに記載)。
Pluginの導入 †
playbookの作成 †
playbookの実行 †
Gitの導入 †
GitLabは「Redmineとの連携は結構面倒」なのと「リソース(主にメモリ)を大量に消費し、しばしば動作不安定を招くほど」との評判があることから、導入を断念。CGI"git-http-backend"とRedmine.pmを用いた、従来どおりのGitリポジトリサービスを選択。ついでに、GitWebも導入。
Apache用設定ファイルの作成 †
- Ansibleサーバ上の一般ユーザで実行
cat << "_EOF_" > repos-git.conf
#<IfModule mod_rewrite.c>
# RewriteEngine On
# RewriteCond %{REQUEST_URI} ^/git/?$
# RewriteRule ^/git /gitweb [R=301,L]
## RewriteRule ^/git/$ /gitweb/ [L,R]
#</IfModule>
PerlLoadModule Apache::Authn::Redmine
SetEnv GIT_PROJECT_ROOT /var/lib/git
SetEnv GIT_HTTP_EXPORT_ALL
ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
<Location /git/>
Order allow,deny
Allow from all
PerlAccessHandler Apache::Authn::Redmine::access_handler
PerlAuthenHandler Apache::Authn::Redmine::authen_handler
AuthType Basic
AuthName Git
# <LimitExcept GET PROPFIND OPTIONS REPORT>
# SSLRequireSSL
# </LimitExcept>
# for Redmine Authentication
RedmineDSN "DBI:mysql:database=redmine;host=127.0.0.1"
RedmineDbUser "redmine"
RedmineDbPass ""
RedmineGitSmartHttp yes
Require valid-user
# SetEnvIf Request_URI "^/git/$" allow
# Order allow,deny
# Allow from env=allow
# Satisfy any
</Location>
_EOF_
- contextpath"/git"ではなく、VirtualHostにしてもいい。
playbookの作成 †
playbookの実行 †
Git嫌な人のために、Subversionも入れちゃう。
Apache用設定ファイルの作成 †
playbookの作成 †
playbookの実行 †
管理作業 †
Gitリポジトリの新規作成 †
- Ansibleサーバ上の一般ユーザで実行
ansible-playbook -i its.jomura.net, pb_create_git_repository.yml
- または、"-e"オプションでリポジトリ名を指定することもできる。
ansible-playbook -i its.jomura.net, -e repos_name=myrepos.git pb_create_git_repository.yml
- リポジトリ名の"."より前は、Redmineプロジェクトの"識別子"と同一でなければ、http経由で利用できない。"."より後は、リポジトリを区別できるような文字列とすること。
- /var/lib/git/{リポジトリ名}/ の直下に、下記のファイルを配備すると、gitwebでの見栄えが良くなるので(gitwebを使う場合は)お勧め。
- README.html : リポジトリの説明をHTMLで記述する。
- description : リポジトリの説明を簡単に1行程度で記述する。ファイルは既にある。
- cloneurl : リポジトリアクセス用のURLを記述する。有用。
|