mirage-ecsで各メンバー専用開発サーバーを実現!まちのコインの運用事例を紹介します

SREチームの長田です。

突然ですが、 mirage-ecs というツールをご存知でしょうか? 今回はこのツールをまちのコインの開発チームでの使用例をもとに紹介します。

coin.machino.co

mirage-ecs を使うと動作確認用のサーバー環境を、サーバーサイドのエンジニアでなくとも自由にいくつでも立ち上げることができるようになります。 「環境」は AWS のECSクラスタ上で起動し、専用のURLが割り当てられ、 認証*1を通過すればどこからでもアクセスできます。 これにより 「クライアントアプリとつなぎ込んで動作確認したいけど、開発環境が空いてないから確認できない」 や、 「プロダクトオーナーに新機能を確認してもらいたいけど、開発環境が空いてないから(以下略)」 といった問題が解消し、 開発と動作確認のサイクルをスピーディーに回すことができるようになります。

mirage-ecsとは

mirage-ecsmirage の派生として作られた OSS です。 mirageはホストマシン上のdockerコンテナを「環境」として扱い、そこにHTTPリクエストをプロキシするものでしたが、 mirage-ecsはその名の通り ECS Task を「環境」としてリクエストをプロキシします。 ECS Task の起動・終了も担当します。

mirage-ecs の仕組み

今回紹介するのはまちのコインでの活用事例ですが、 カヤックが運用開発している「ぼくらの甲子園ポケット」や「Tonamel」などのサービスでも同じく mirage-ecs を使用しています。

koshien-pocket.kayac.com tonamel.com

mirage については当ブログにも記事がありますので、合わせて参照ください。

techblog.kayac.com techblog.kayac.com

開発環境の定義

企業あるいはプロダクトごとに「開発環境」が指すものは様々かと思いますが、今回のまちのコインでの事例では以下のような環境を「開発環境」と呼んでいます。

  • アプリケーションの動作確認を行うための環境である
  • 構成中のインフラリソースは必要最低限のもので、複数環境からの共用も許容する
  • 負荷のかかり方は本番環境とは異なる。他の開発環境の負荷影響も受ける

あくまでもアプリケーションの動作確認のための環境として「開発環境」を使用しています。 より本番環境に近い構成で動作確認を行う場合(インフラ構成の変更確認や負荷試験など)は、 構成を本番環境に合わせた別環境である「ステージング環境」を使用しています。

構成

まちのコインでの mirage-ecs 周りの構成は以下のようになっています。

まちのコインでのmirage-ecs周りの構成

Webサービスを動かすためのオーソドックスな構成です。 まずALBがサブドメイン部分をワイルドカード指定でHTTPリクエスト受け付けます。 ALBで受け付けたリクエストは ECS Service として稼働している mirage-ecs に送られます。 mirage-ecs ではHTTPリクエストの Host ヘッダーを読み、サブドメイン部分に対応した ECS Task にHTTPリクエストをプロキシします。

外部からのHTTPリクエストの認証にはGoogleアカウントを使ったOAuth2を使用しています。 ECS Task mirage-ecs の sidecar として動作している nginx コンテナで、 go-nginx-oauth2-adapter を設定しています。

github.com

mirage-ecs が管理する「環境」は、 Aurora PostgreSQL クラスタ内の、専用の database を使用します。

起動トリガー

「環境」起動処理の実行には GitHub Actions を使用しています。 起動のトリガーには以下の2パターンを用意しています。

  • workfrow の手動実行
  • 特定ブランチへの push
    • on.push
    • 特定gitブランチの変更を対応する「環境」に自動反映する

「workflow の手動実行」は GitHub のリポジトリに Actions を実行する権限を持つメンバーであれば、 サーバーサイドエンジニアでなくとも行えるようにしています。 対象のgitブランチを選択し「環境」名を入力すれば、数分後には専用の「環境」が利用できるようになります。

mirage-ecsで「環境」を起動するGitHub Actions workflowの実行フォーム

Actions workflow の中身

workflow 内では以下の処理を行います。

  1. 設定値の生成(config)
  2. コンテナイメージのビルド(buildbuild-frontend)
  3. mirage-ecs が管理する ECS Task として起動(deploy)
  4. 起動サマリーの記録(summray-inputssummary-results)
  5. 環境起動の通知

mirage-ecsで「環境」を起動するGitHub Actions workflowの全体像

各ステップを深掘りしてみましょう。

設定値の生成

入力された「環境」名のバリデーションや「環境」にアクセスする際に使用するサブドメインなど、 workflow 全体に関わる設定値を生成しています。

コンテナイメージのビルド

環境の起動に必要なコンテナイメージをビルドしています。

Actions workflow を実行している GitHub repository にあるソースコードを参照してビルドするものの他に、 別の GitHub repository にあるソースコードを参照するものもあります(図中の build-frontend の部分)。 workflow 中で対象 Git repository から clone し、ビルドを行っています。 別 repository へのアクセスは GitHub の Deploy Key を使用しています。 別 repository に同名のブランチを用意してそれをビルドに使用することにしていますが、 更新がなくビルドが不要な場合は、メインブランチでビルドしたイメージを代わりに利用することでビルド待ち時間短縮を図っています (図中の build-frontend が3秒で終了しているのはこのためです)。

Actions 上でのビルド時間短縮策として、 docker buildx--cache-from=gha --chache-to=gha オプションを活用しています。 Actions のキャシュに docker レイヤーキャッシュを保存するオプションです。 まだベータ版の機能ということでインターフェイスが変更される可能性があるとのことですが、 イメージの更新が Dockerfile の後半のファイルの入れ替えのみで済むような場合には特に有効な手段でしょう。

docs.docker.com

mirage-ecs が管理する ECS Task として起動

mirage-ecs にAPI POST /api/launch をリクエストすることで、 指定したサブドメインパターンと Task Definition で ECS Task が起動します。
※ この Task は ECS Run Task で起動したもので、ECS Service には所属しません
※ mirage-ecs へのAPIリクエストには何らかの認証をかけるのが安全です

「環境」起動に必要な前処理のうち、AWS の VPC 内で実行する必要がある処理(データベースのマイグレーションなど)は、 メインコンテナの entrypoint に指定したスクリプト内で行っています。

#!/bin/bash

set -ex

# アプリケーション起動に必要な前処理
bin/rails db:create
bin/rails db:migrate --trace
...

# アプリケーションを起動
# Task Definition の command に指定されたコマンドを実行
exec "$@"

また、「環境」起動後でもよい処理(検索インデックスの更新など)は、 ECS Task の sidecar で非同期に実行しています。

これらの前処理は、以前は Actions から ECS Run Task で前処理を実行していました。 しかし、Run Task の起動オーバーヘッドが思いのほか大きかったため、待ち時間の改善として今の形に変更されました。

起動サマリーの記録

起動した環境の情報を workflow のサマリーとして記録しています。 起動した環境にアクセスするためのURLを一覧にしています。

GitHub Actions workflowのサマリーに「環境」へのアクセス方法を表示

環境起動の通知

起動処理の開始と終了をSlack に通知しています。 こちらは workflow 上の処理ではなく、Slack の GitHub 連携を利用しています。

Slackへの通知例。workflowの各ステップの進捗がリアルタイムで更新される

Slack上で以下ようなコマンドを実行することで設定できます。お手軽。

/github subscribe {リポジトリ名} workflows:{name:"{workflow名}"}

github.blog

通知には workflow 実行履歴へのリンクも含まれるため、 上記の起動サマリーを参照して環境に関する情報をたどることができます。

終了

現状は mirage-ecs のコンソールから Terminate しています。

mirage-ecsのWebコンソールには起動中の環境が一覧表示される

こまめに不要になった「環境」を終了する習慣があればいいのですが、使っているのは人間なのでうっかり止め忘れることがあります。 ECS Task として起動している以上、止め忘れればその分課金が発生します。 プロダクション環境に比べれば微々たる量ではありますが、無駄な出費はできるだけ減らしたいものです。

解決策として、使用していない「環境」を自動で終了する仕組みを準備中です。 mirage-ecs に最近実装されたAPI GET /api/access を利用すれば、指定した期間内に対象の「環境」に対して何回HTTPリクエストがあったのかを取得することができます。

GET /api/access の結果を見て、 アクセスがなければ POST /api/terminate をリクエストし「環境」を終了するエンドポイントをアプリケーションに実装し、 ECS Task のコンテナヘルスチェックでそのエンドポイントにリクエストを送れば実現できそうだ、という目論見です。

まちのコイン以外の事例

社内の各開発運用チームでの要件に合わせて、より使いやすいよう操作インターフェース等を工夫しています。

例えばmirage(非ECS)時代から使用歴が長い「ぼくらの甲子園ポケット」開発運用チームではchatbotによるSlackからの操作手段が充実していますし、 マイクロサービス化を進めている「Tonamel」開発運用チームでは、10以上のコンテナを一つの「環境」として扱うために *2 mirage link という 複数の ECS Task を束ねて使用する機能を活用しています。*3

これらの工夫については各チームのエンジニアが紹介記事を書いてくれる・・・かも。

mirage-ecs の最近のアップデート

最近のアップデートにより導入の敷居が下がっています!

README

設定ファイルの書き方、APIリファレンスの追加など、大幅更新されています。
beforeafter

terraform サンプル

terraform/ ディレクトリが追加されました。 以下のコマンドを実行するだけで AWS で運用する上で必要なリソースがそろいます。

$ terraform init
$ terraform apply -var domain={使用したいドメイン}
$ ecspresso deploy

※ 実際に AWS 上にリソースが作られます。AWS 利用費がかかりますのでご注意ください。

Minimal Configuration

設定ファイルを書かなくても、環境変数 MIRAGE_DOMAIN をセットすれば動くようになりました。 上記の terraform サンプルでも使用されています。

おわり

以前の記事でも紹介したように、 まちのコインはもともと EKS 上で運用していました。

当時は開発環境も EKS 上に構築されており、本番環境と同じ構成を取っており、ステージング環境に近いものでした。 環境を増やす場合は入り口である CloudFront から ALB、データベースである Aurora クラスタや ElastiCache Redis まで、 一式すべてを用意するスタイルを取っていました。 *4 k8s 管理外のAWSリソースについては、 terraform のようなリソース管理ツールも導入されていなかったため、 セットアップ職人が温かみのある手作業で時間をかけてセットアップやトラブルが発生したときの対応をしていました。

mirage-ecs を導入したことによって、「環境」セットアップの手間が劇的に軽減され、 ECS Task を一つ追加するだけで済むようになったのでコスト面でも改善されました。

開発環境不足に悩まされている方はぜひお試しください。

カヤックでは開発効率の向上を実現するエンジニアも募集しております。

hubspot.kayac.com

*1:運用時のオプション。mirage-ecs の機能ではありません

*2:Task Definition あたりのコンテナは10個までという制限があります https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/service-quotas.html

*3:mirage link は Tonamel チームの要望により実装されました

*4:データベースは同じインスタンスを使い回せるんじゃないの?という話ももちろんありますが・・・当時は個別に用意していたのです