職業プログラマの休日出勤

職業プログラマによる日曜自宅プログラミングや思考実験の成果たち。リアル休日出勤が発生すると更新が滞りがちになる。記事の内容は個人の意見であり、所属している(いた)組織の意見ではない。

EC2上にRedmineを構築(Ubuntu 22.04 / Nginx / Puma / UNIX Domain Socket 版)

業務上、非プログラマも混在したチームで新規プロジェクトの課題管理をする必要に迫られたので、EC2上にRedmineを建てた。
近年では他にも色々と選択肢はあるんじゃないの?という話もあるけども、どうしてもサーバにインストールするタイプのものが必要だったのでRedmineを選択した。それでも他に選択肢はあるだろうけど、選ぶ時間がもったいなかった😇
この記事の本題は、issue tracking system の製品選択の理由や経緯ではなく、「EC2上にRedmineを構築する」ときの作業手順等の記録である。

2015年に似た作業を実施していたが、その時は Amazon Linux 1 / Passenger / Apache という構成だった。
tmotooka.hatenablog.jp

2024年の現在では、取るべき構成や、ハマりポイントなどは大きく変わっているので、改めて記事にした。

想定読者

筆者自身のような人間、つまり以下の条件を全て満たす人が想定読者である。この条件に当てはまらない人にとっては説明不足だったり、「当たり前のことを言ってるだけ」になる可能性が高い。

  • AWSVPC, Route53, LoadBalancer 周りのことは、業務上必要な知識は十分に持ってる。
  • AWSは、(繰り返し作業以外は)CLIではなく Management Console で操作する派である。
  • MySQLはチョットデキル
  • rubyは素人に毛が生えた程度

システム要件

  • HTTPS限定にできる構成であること
  • 運用費用は全て、AWSの通常の費用として計上可能であること(market place とか無しに)
  • RPO*1 : 1時間
  • RTO*2:1日

構成の概要

全てAWS上のものである。

  • LB : Application Load Balancer、ここでHTTPS終端する構成。
  • EC2 : t4g.micro(構築作業中はt4g.small) で Ubuntu 22.04 を稼働しているマシン1台に、以下のものを詰め込んでいる構成。
    • Nginx
    • Puma ※UNIX Domain Socket をlistenする構成
    • MySQL
RDSを使わない理由
  • 前述のRPO要件から、multi-AZである必要が無いから
  • 費用を抑えたかったから😅

事前準備

  • AWSの基本的な設定(Organizationsの member account を作るとか、switch role のための設定とか、CloudTrailとか、billingの警告とか。詳細は割愛。)
  • バックアップ用のS3バケットの作成
  • EC2用の IAM Role の作成
    • SessionManagerを使うので AmazonSSMManagedInstanceCore が必要
    • CloudWatchにデータとかを送るので CloudWatchAgentServerPolicy が必要
    • バックアップ用S3へのwrite権限が必要
  • LoadBalancer用のSecurityGroupの作成
    • 任意の場所からHTTP接続を受ける(もう不要かな?)
    • 任意の場所からHTTPS接続を受ける
    • 後述のEC2へのHTTP接続を出す
  • EC2用のSecurityGroupの作成
    • LoadBalancerからのHTTP接続を受ける
    • sshの接続は、SessionManagerを使わない場合のみ必要
    • 任意の場所へのHTTP / HTTPS を出す(apt や bundle とかで必要)
    • 任意の場所へのsshを出す(bundleで必要。たぶん native extension のビルドで必要)
    • 任意の場所へのntpを出す(過去にUbuntu on EC2 なマシンで、時計がズレまくって障害になった記憶があり…)
  • ACMHTTPS証明書を作成
  • LoadBalancerの作成

EC2の構築

  • bundle installではメモリを沢山食うらしく、t4g.micro だと処理が止まる。small以上である必要がある。構築後はmicroにしても良いけど、Redmineruby gems の更新が必要になるくらい長期の運用をするのなら、その時はsmallに戻すことになる。
    • 数年前に試したときの記憶だと確か、nano サイズだとそもそもMySQLも起動できなかったはず。
  • 面倒なので、public subnetに配置して、IPアドレスを取得したw 他の業務のVPCの中に構築するのであれば、きちんとした設計(=private subnetに配置して、外向け通信は NAT instance とかを経由するやつ)にするのも良い。そうでなくても、お金に余裕があるのならそうした方が良い!
  • Credit Specification はstandard にした。課金こわい。。
  • sshのポートはSessionManager依存にするから開けないし、sshログイン用の鍵の設定もしない(非推奨だけど)

aptでインストールするもの

  • nginx
  • ruby ※バージョンにこだわりがある人は、ここでは入れずにrbenvとかを使うのだろう。
  • ruby-bundler ※これが無いと「bundle」が使えない。rbenvとかで入れたら一緒に入るはずだけど。
  • ruby-mysql2 ※これが無い時に何が起きるのかは試してない。
  • ruby-dev ※これが無いと「bundle install」で落ちる。たぶん native extension のビルドで落ちる。
  • build-essential ※これが無いと、bundle install の native extension のビルドで落ちる。
  • libmysqlclient-dev ※これが無いと、bundle install のmysql2のビルドで落ちる(ということは、ruby-mysql2 のインストール物は使われていないかも?)
  • mysql-server
  • awscli ※バックアップをS3に置いたりしないのなら不要

Redmineのインストール

基本は、公式マニュアルに従う。
RedmineInstall - Redmine

以下の点は補足。

  • Redmineのバージョン選択の情報は Download - Redmine を参照。本記事執筆時点での適切バージョンは 5.1.2 だった。
  • アプリケーションをどこに置くべきか。諸説あるだろうが、ここでは /var/www/redmine-5.1.2 とした。雑すぎるが、数ヶ月で廃止する鯖ならこれで十分。
  • Step 4 のbundle操作の前に echo "gem 'puma'" > Gemfile.local して、pumaをインストールさせるべし。
    • これは完全に筆者が浦島太郎状態だったのだが、Ruby 3.0 からはWebrickを同梱しなくなったらしい。何も入れないまま「rails s」すると、Could not find server '' というエラーが出た。

stackoverflow.com

Pumaのサービス起動

systemd環境下において、Pumaをサービスとして起動する方法は、公式のガイドがあるので、これを参考にして進めた。
github.com

要約すると、こうなる。

  • Pumaのサービスの設定ファイルを作れ
  • (今回のように UNIX Domain Socket を使う通信の場合)Socketの設定ファイルも作れ
  • これらをsystemdに取り込んで有効化しろ

以下は補足。

  • 数ヶ月で廃止する鯖とは言えども、Pumaの実行ユーザはrootじゃない方が良いので、「puma」というユーザを作った。 useradd --no-create-home --system puma ※他にも入れた方が良いオプションは色々あるだろうけど…
  • ソケットのファイルパスは、pumaユーザから書き込む(作る?)必要があるので、公式ガイドにある /run/puma.sock というのは不適切である。 /run/puma/ というディレクトリを事前に作っておいて、ディレクトリの所有者をpumaユーザにした上で、ソケットのファイルパスを /run/puma/puma.sock とした。
  • サービス設定ファイルの ExecStart は、こうした: /usr/bin/bundle exec puma -e production -b unix:///run/puma/puma.sock
    • ここで「bundle exec rails server」じゃないのが気持ち悪いところだが、こっちの方式だと、pumaに「このsocket使ってね😘」とお願いするために設定ファイルを書かなければならない。それでも良いんだけど、「bundle exec puma」方式だと「-b」オプションでソケット指定できるのが良い。もしかしたら「rails s」でもソケット指定できるのかもしれないけども、短時間の調査では見当たらなかった。
  • TCPじゃなくてSocketを採用した理由:なんかカッコいいから😆
  • 本稿から少し離れた話だが、↓の記事の話は面白かった

qiita.com

NginxからPumaに通信できるようにする

qiita.com
↑の記事の「nginxの設定」のコーナーに従えばすぐにできる。
以下は補足。

  • UbuntuのaptでNginxをインストールした場合、80番ポートをlistenする設定が既に sites-enabled の中に(symlink)で存在してるので、新規に設定ファイルを作るのではなくて、これを編集する(または新規に作る+既存ファイルを消す)という方法で設定することになる。
  • ここではALBでhttpsを終端化した構成なので、Nginxの立場では平文HTTPの通信を受け付ける構成である。X-Forwarded-Proto でpumaに渡す値は、$schemeとしてしまった場合、意図せず平文httpであることをpumaに通知してしまう。今の時代においては固定で「http」を渡すのが良いはずだ。
  • Hostヘッダとかをpumaに渡す値を明示しない場合、どうやらNginx設定ファイルにおけるupstreamの名称がHostに設定されるらしく、Railsアプリがリダイレクトを返す時に、壊れたURLがユーザの元に届く😆

その他の設定

さいごに

久し振りにrubyを触って疲れた。(主に bundle install まわりで)

*1:Recovery Point Objective: データ破損系のシステム障害発生時、どれくらいの時間の業務データが消失することを許容するか?

*2:Recovery Time Objective: システム障害発生後、どれくらいの時間止まっていることを許容するか?