Redmineの導入

 Redmineについては、下記3種の導入方法を検討した。

  1. 公式サイトの本体コード一式 + RHELのRPM + gemコマンドによるライブラリ導入
    • 各々のライブラリ依存性管理が非常に大変。特にnokogiriとopensslが鬼門。
  2. bitnami-redmineを用いた一括導入
    • Git, Redmine, PhpMyAdminなども導入されるが、今回は使わない
    • Redmine公式ではない
  3. 公式dockerイメージを用いた導入

 今回は「公式dockerイメージを用いた導入」を採用する。

導入サーバの前提条件

  1. OSはRHEL7(or CentOS7)かそれ以降の版とする。
  2. 事前導入するソフトウェアパッケージは最小構成。
  3. インターネットに接続可能 ※必要なら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}
  4. パッケージの更新が事前に実行されている
    dnf clean all && dnf -y update && reboot
  5. Redmine Webサイト用のFully Qualified Domain Name(FQDN)が用意されている。

Redmine用 configuration.ymlの作成

Redmine初期設定用SQLの作成

docker-compose.ymlファイルの作成

docker用環境変数ファイルの作成

playbookの作成

   - 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/bash -c "docker exec mysql mysqldump --no-tablespaces redmine \
            | gzip > {{ redmine_path }}/../backup/redmine_db_`date +%%F`.sql.gz" && \
            find {{ redmine_path }}/../backup -name redmine_db_*.sql.gz -mtime +30 \
            | xargs --no-run-if-empty rm

    - 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/bash -c "cd {{ redmine_path }} && \
            tar -czf ../backup/redmine_files_`date +%%F`.tar.gz files" && \
            find {{ redmine_path }}/../backup -name redmine_files_*.tar.gz -mtime +30 \
            | xargs --no-run-if-empty rm

    - 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_

playbookの実行

補足事項

  1. mysqlコンテナを削除すると、DBデータはどうなる?
     ホストの /var/lib/docker/volumes/mysql/_data/ にMySQLのDBデータは永続化されている。mysqlコンテナを削除しても、これらは削除されず、コンテナを再導入すると再利用される。
  2. mysql_native_passwordの指定は必要?
     defaultのsha2_passwordにrailsが対応していない場合を考慮している。
  3. MySQLやmemcashedは、コンテナではなく、ホスト側にrpm導入したらどうか。
     dockerコンテナからホストのプロセスに通信するには、便利な宛先指定方法が無く、ホストのIPアドレスを指定することになり、ポータビリティに欠ける。
     同一のcompose.ymlファイルに記述されたコンテナは、同一ネットワークに属しコンテナ名で指定できる。また、RedmineのWebサイトでも、公式dockerイメージはコンテナ化DBを利用するようガイドされている。
  4. RedmineのURLに、コンテキストパス(/redmine)を設定できる?
     RedmineのWebサイトに紹介されている。dockerコンテナ内のpassengerを再設定することで、技術的には可能だが、難易度が高い。また、コンテナの更新を考慮し、できるだけコンテナの編集は避けたい。
     今回は、「基本的に全てのURL要求をredmineコンテナにproxyしつつ、/gitだけ除外する」という方式としている。
  5. 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;
  6. Redmine導入用のplaybookに、git用のタスクがあるんだけど
     gitリポジトリ用のSELinux設定は、Redmine用のdocker-compose upを実行する前に実施する必要があるため、仕方なく。
  7. ログの循環
     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用設定ファイルの作成

playbookの作成

playbookの実行

Subversionの導入

 Git嫌な人のために、Subversionも入れちゃう。

Apache用設定ファイルの作成

playbookの作成

playbookの実行

管理作業

Gitリポジトリの新規作成


トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS