Redmine on Fargate でサーバーレスチケット駆動開発

## この記事について

[「AWS & Game Advent Calendar 2020」](https://qiita.com/advent-calendar/2020/aws-game)3日目はゲーム開発に役立つコンテナ技術についてご紹介します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866419/f8517146-b25c-dd79-1706-c4c2be245764.png)
大規模なゲーム開発においてはプロジェクト管理するツールとして Redmine というソフトウェアを利用される方は数多くいらっしゃいます。ゲームを開発する過程では頻繁に変更が発生するため、その作業工程を管理し、往々にして人数が膨れ上がりがちなプロジェクトメンバーに最新の状況をシェアする必要があります。

デバッグ時に発見されたバグはチケットの形で一覧化され、1つ1つを作業として割り振っていくチケット駆動開発のスタイルはアジャイル開発と相性もよく、多くのゲーム開発会社で取られている開発スタイルとなります。

ゲーム開発において身近な存在である Redmine ですが、下手をするとプロジェクト毎にサーバーが乱立しがちとなり、その構築や運用のための工数が気付かぬうちに膨れ上がるケースがあります。

このような背景から、Redmine をコンテナ化することで構築手順を減らし、運用負荷を軽減させたいと考える現場の声も多く、そんな要望に答えるべくインターネット上では様々な記事を見つけることができます。しかし、現存する多くの記事では docker-compose 等を使って、仮想サーバー上での運用方法を紹介しているものが多く、依然としてインフラエンジニアに運用の負担がかかってしまいます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866419/478ecb1e-84bd-7a7b-8505-329d032265d2.png)

そこで、この記事では **AWS Fargate を使ってサーバーレスにコンテナ化された Redmine を構築** する手順についてご紹介します。

AWS Fargate では、インスタンスの選択やクラスター容量のスケーリングなしに、適切なコンピューティング容量を割り当てることができます。そのため、サーバーのスケーリング、パッチ適用、保護、管理にまつわる運用上のオーバーヘッドは発生しません。
最小限のリソースで Redmine サーバーを運用できるため、運用コストだけでなくインフラ費用も最適化することができます。

## アーキテクチャについて
アーキテクチャとコンテナイメージは次に示す構成を用います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866419/604050ff-d711-a301-548a-f0b1fd4d1db6.png)
#### Dockerfile

```Docker
FROM redmine:4.1-passenger

COPY config/configuration.yml config
ENV TZ=Asia/Tokyo\
    REDMINE_PLUGINS_MIGRATE=true

RUN set -eux; \
    # install plugins
    git clone --depth 1 https://github.com/akiko-pusu/redmine_banner.git plugins/redmine_banner; \
    git clone --depth 1 https://github.com/akiko-pusu/redmine_issue_templates.git  plugins/redmine_issue_templates; \ 
    \
    # install themes
    git clone -b redmine4.1 --depth 1 https://github.com/farend/redmine_theme_farend_bleuclair.git public/themes/bleuclair; \
    \
    # change owner 
    chown -R redmine:redmine plugins/ public/themes/;\
    # install gems
    bundle install;
```

ポイントとなる点は次の3点です。
- [Docker公式の Redmine コンテナ](https://hub.docker.com/_/redmine)に Template と Plugin を埋め込み、コンテナ起動時に `rake redmine:plugins:migrate` を実行する
- 添付ファイルを EFS、データベースを RDB、ログを CloudWatchLogs に流すことで、Stateless なアプリケーションにする
- メールは SES から送信を行う（※今回の説明の範囲外）

#### プラグインやテーマ
Redmineはバックアップのために次のファイルをコンテナの外に保存しておく必要があります。

    redmine files(/usr/src/redmine/files)
    redmine plugins(/usr/src/redmine/plugins)
    redmine public/themes(/usr/src/redmine/public/themes)
    redmine config(/usr/src/redmine/config/configuration.yaml)

ここで、plugins と themes、config はコンテナ実行中には不変であるものとすると、外に出しておくべきファイルは添付ファイル（redmine files)だけとなります。

##### (Redmine on Fargate で Plugin を更新させるための補足)
ここで問題となるのは Fargate ではホストマシンに直接 SSH することは出来ない点です。
そのため、起動中のコンテナに対して`rake` コマンドや `rails`コマンドを実行しないよう意識した構成が必要となります。

今回はこの問題の解決策として、Dockerfile内で Plugin と Theme を用意(`git clone`)し、コンテナの環境変数に`REDMINE_PLUGINS_MIGRATE = true` を与えることで、`rake redmine:plugins:migrate` コマンドと `db:migrate` コマンドをコンテナ起動時に実行し、Plugins/Themes に変更があれば適宜 DB に状態を反映させています。

>REDMINE_PLUGINS_MIGRATE

>This variable allows you to control if rake redmine:plugins:migrate is run on container start. Just set the variable to a non-empty string like 1 or true and the migrate script will be automatically run on every container start. It will be run after db:migrate.
>https://hub.docker.com/_/redmine

#### 添付ファイル
添付ファイルに関してはコンテナ外に出しておく必要があるため、2020年4月のUpdateである[FargateにEFSをマウントする機能](https://aws.amazon.com/jp/about-aws/whats-new/2020/04/aws-fargate-launches-platform-version-14/)を使います。
Fargate のタスク定義で、ECSコンテナのボリュームマウントからマウントされたEFSを参照させます。

#### ログファイル
Redmine では log/production.log に Redmine へのアクセス情報といったログがファイルに追記されていきます。コンテナを再起動すると、このログファイルも消滅してしまうため、外に出しておく必要があります。

Docker 公式の redmine コンテナでは、production.log の代わりに stdout にログ出力がされるよう変更がされているため、stdout の出力を awslogs ドライバーが拾い、CloudWatch Logs にログを送信します。
https://github.com/docker-library/redmine/pull/146

#### メールサーバーの設定
Redmine ではメールサーバーを使ってメールの送信を行います。Amazon SES というEメール送信のマネージドサービスを使うことで、高可用性かつ低価格なメールサーバーを使うことができます。

今回の記事では SES の設定については省略するため、下記のような記事をご参照願います。
[-備忘録- AWS SES を使ってRedmineからのメールを送る](https://qiita.com/evakichi/items/738d1c55c8c3e0b5cd20)
[全部AWSでRedmineをサクッと構築する](https://qiita.com/hamham/items/791b2e855792033aad63)

ここからは、Redmine を Fargate 上で動作させるための構築手順について見ていきましょう。

## 1.ネットワーク、ファイルシステム、データベースの設定
#### 1.1 VPC の作成
まずは VPC Wizard から VPC を作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866419/ba7a725d-1194-dc12-4af2-d11aa1edbc2f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866419/882076f6-bbb0-c6c9-cce2-3d19ab9dec1b.png)

つぎに、パブリックサブネットを1つ追加します。後ほど ALB の作成で 2 AZ のサブネットが必要となるため、アベイラビリティーゾーンをパブリックサブネット1と違うものを選ぶよう、注意します。（今回はap-northeast-1とap-northeast-1c)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866419/e10bc77a-e176-d7d2-f7af-d6024b41d6bc.png)
サブネット新規作成後、ルートテーブルの関連付けを編集して、インターネットゲートウェイへのルーティングが含まれるようにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866419/08ac5f2f-6442-d194-983e-c4acc00d9187.png)

#### 1.2 SecurityGroup の作成
EC2 コンソールから SecurityGroup を作成します。名前とプロトコル、ソースは次のように設定します。

|名前 |プロトコル  |ソース  |
|---|---|---|
|redmine-alb-sg  |TCP 80(HTTP)  |0.0.0.0/0  |
|redmine-fargate-sg  |TCP 3000 |redmine-alb-sg  |
|redmine-efs-sg  |TCP 2049(NFS)  |redmine-fargate-sg  |
|redmine-db-sg  |TCP 3306(Aurora)  |redmine-fargate-sg  |

#### 1.3 ALBの作成
EC2 コンソールから Application Load Balancer を作成していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866419/8d0fc711-5647-1dcc-93f9-8a14936e5841.png)
後ほど ECS サービスを設定するために、ALB作成完了後にリスナーを削除します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866419/935ff060-a508-cea1-c63a-130d2b1006d7.png)

#### 1.4 EFS の作成
EFS コンソールから添付ファイルの保存先となるファイルシステムを作成していきます。
マウントターゲットのセキュリティグループを`redmine-efs-sg`とすることで、Fargate コンテナからの通信が可能となります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866419/295b7f81-6d85-67ba-d4b0-377c786c4959.png)

#### 1.5 RDS の作成
次の設定値に従い、MySQLのデータベースを作成していきます。

|名前 |設定値|
|---|---|
|DBエンジンのバージョン  |MySQL 5.7.31  |
|DBインスタンスのクラス  |db.t2.micro  |
|DBインスタンス識別子  |fargate-db |
|マスターユーザーの名前  |redmineuser |
|マスターパスワード  |redminepassword |
|VPC  |redmine |
|データベースの名前  |redmine|

DB 作成後、セキュリティーグループを`redmine-db-sg`に変更します。
また、後ほど RDS Endpoint を利用するため値を控えておきます。

## 2.コンテナの設定
#### 2.1 Dockerfile の記述
次のような配置でDockerfile と configuration.yml を作成します。

    フォルダ構成
    ├── Dockerfile
    └── config
        └── configuration.yml

`Dockerfile` の詳細については、[Dockerfile](#Dockerfile) をご参照ください。
`configuration.yml`の詳細については[メールサーバーの設定](#メールサーバーの設定)をご覧下さい。（下記コードはサンプルとなります）

```yml
default:
  email_delivery:
    delivery_method: :smtp
    smtp_settings:
      address: mail.example.com
      port: 25
      authentication: :login
      domain: redmine.example.com
      user_name: myaccount
      password: password
```

#### 2.2 ECR レポジトリの作成
ECR コンソールからリポジトリを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866419/b73c7704-8a71-cb98-e043-09894aec518b.png)

作成したリポジトリのプッシュコマンドを表示し、Dockerfile と同じディレクトリで実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866419/07e9936d-5cd2-633f-5c7b-52a1059fbc7a.png)

```bash
    // docker login
    aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin <account_number>.dkr.ecr.ap-northeast-1.amazonaws.com
    // build container image
    docker build -t redmine-fargate .
    // taging to image
    docker tag redmine-fargate:latest <account_number>.dkr.ecr.ap-northeast-1.amazonaws.com/redmine-fargate:latest
    // push image to ECR repository
    docker push <account_number>.dkr.ecr.ap-northeast-1.amazonaws.com/redmine-fargate:latest
```

## 3.ECS Fargate の設定
#### 3.1 ECS Cluster の作成
ECS コンソールから AWS Fargate(ネットワーキングのみ) のクラスターを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866419/cc084aa2-a1dd-38a9-e7f4-dd8b637b1478.png)

#### 3.2 ECS タスク定義の作成
Fargate のタスク定義を作成します。

|名前 |設定値|
|---|---|
|タスク定義名  |redmine-fargate-taskdef  |
|タスク実行ロール  |ecsTaskExecutionRole  |
|タスクメモリ  |1GB |
|タスク CPU  |0.25vCPU|

#### 3.3 ボリュームの追加
タスク定義に EFS ボリュームを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866419/143ebab4-3ee2-c3ee-c7a6-1c436a6396f3.png)

#### 3.4 コンテナの追加
ECR に Push したコンテナを指定していきます。

|名前 |設定値|
|---|---|
|コンテナ名  |redmine-fargate-container |
|イメージ  |＜account_number＞.dkr.ecr.ap-northeast-1.amazonaws.com/redmine-fargate:latest |
|ポートマッピング  |3000 |
|マウントポイント |redmine-efs|
|コンテナパス |/usr/src/redmine/files|

そして、環境変数として DB の接続情報を記入していきます。
**※本番環境では AWS SecretsManager の利用をご検討ください**

|キー |値|
|---|---|
|REDMINE_DB_DATABASE  |redmine |
|REDMINE_DB_ENCODING  |utf8mb4|
|REDMINE_DB_MYSQL |＜RDS Endpoint＞ |
|REDMINE_DB_USERNAME |redmineuser|
|REDMINE_DB_PASSWORD |redminepass|

#### 3.5 ECS サービスの作成
ECS コンソールから ECS サービスを作成します。
今回は EFS を利用するため、プラットフォームバージョンを `1.4.0` にする必要があります。

|名前 |設定値|
|---|---|
|起動タイプ  |FARGATE |
|タスク定義 |redmine-fargate-taskdef |
|プラットフォームのバージョン |1.4.0 |
|クラスター  |redmine-fargate |
|サービス名 |redmine-fargate-service|
|タスクの数 |2|

VPCやロードバランサーの設定も進めていきます。

|名前 |設定値|
|---|---|
|クラスターVPC  |redmine |
|サブネット |パブリックサブネット1、パブリックサブネット2 |
|セキュリティグループ |redmine-fargate-sg |
|ヘルスチェックの猶予期間 |30 |
|ロードバランサーの種類  |Application Load Balancer |
|ロードバランサー名 |fargate-alb|
|プロダクションリスナーポート |80|
|プロダクションリスナーポート |80|

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866419/5ececd35-00f1-1446-555a-5811e1c6f8f8.png)

ECS サービス作成後、Task がヘルスチェックに失敗して落とされ続ける場合は、EC2 コンソールから ECS サービスに紐づく TargateGroup のヘルスチェックを変更してみてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866419/a3d00711-dda2-2597-a330-0474625484b3.png)

ここまでの作業で Redmin onFargate 構築は終わりです。
最後に動作確認をしていきます。

## 4.動作確認
早速、作成した Redmine を実際に触ってみます。
ALB のドメイン名にアクセスすると、Redmine の TOP 画面を開くことができます。
ログインを選び、ID:admin、パスワード:admin を入力します。ログイン後はパスワードの変更を求められます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866419/8e1cab18-114a-8a74-b458-b203840a4bea.png)

まずは Theme をデフォルトから変更してみましょう。
トップナビゲーションから管理→設定→表示→テーマから、Dockefile で記述した Bleuclair を選択してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866419/d0597e4a-3207-fd6b-475a-00e11c2c58bc.png)

無事に変更することができました。テーマを追加する場合は Dockerfile に記述し、コンテナを Build&Pushし、ECS Service を更新します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866419/44d9ba54-b214-e51d-f524-d7c46e3c0033.png)

次にプラグインを確認してみます。まずは 管理→プラグイン を表示して、Redmine Banner Plugin がインストールされていることを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866419/9b5f559e-6534-3636-6b61-3598ae2ce7b7.png)

管理→バナーからプラグインを有効化してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866419/03a78d85-c21f-fb7f-39d8-d28b86d54b6a.png)

無事に画面上部にバナーが表示されるようになりました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866419/46337421-e77c-cb81-6b12-a272763d395d.png)

他にも、添付ファイルをアップロードしてみたり、ECS Task をあえて停止させてみる、ALB+ACM を使って HTTPS 化を行うなど今回作った環境をご自由にお試し頂けますと幸いです。

## おわりに
ゲーム開発に必要なシステムをサーバーレスにコンテナ化して運用する方法についてご紹介いたしました。
Redmine 以外にも Fargate 上でアプリケーションを動かす際の参考となりましたら幸いです。

**それでは、残りの年末もよいコンテナ×サーバーレスライフをお過ごし下さい！**

(免責) 本記事の内容はあくまでも個人の意見であり、所属する企業や団体とは関係がございません。


## 参考資料
Redmine.JP https://redmine.jp/
最新の Redmine 4 を Docker 公式イメージで運用する https://qiita.com/bezeklik/items/b9d75ee74e0ae4c6d42c
コンテナでRedmineの環境構築をやってみた https://qiita.com/asubee/items/db6985549d83334d0a46
Redmine(Docker)環境構築メモ https://qiita.com/KWS68810828/items/962ff0db367d8ea2473a