メドピア開発者ブログ

集合知により医療を再発明しようと邁進しているヘルステックリーディングカンパニーのエンジニアブログです。PHPからRubyへ絶賛移行中!継続的にアウトプットを出し続けられるようにみんなでがんばりまっす!

Rails × ECS でオートスケーリング&検証環境の自動構築

マリオカートでカーブを曲がるときに体を傾斜させてしまうCTO室 kenzo0107 です。

今回は 2018/04/02 にリニューアルしたイシコメの Rails × ECS についてです。

イシコメとは?

「イシコメ」は、医師10万人の声でつくるヘルスケアメディアです。
医師と一般の方々をつなげることで、医療情報格差を埋めることを目指しています。

MedPeerの10万人の医師会員に協力いただいたアンケート結果をもとに編集部で記事を執筆し、医師監修の上で配信。多くの医師の声を反映することで、より正しい情報を提供しています

ishicome.medpeer.jp

リニューアル経緯

リニューアル前は以下のような構成でした。

f:id:kenzo0107:20180618120335p:plain

  • フロントに Laravel 5
  • バックに Drupal
  • Docker on EC2
  • コンテナイメージの S3 でのプライベート管理

Docker がまだ出てきて間もない頃、当然、AWS ECS がリリースされておらず
果敢に技術的にチャレンジした痕跡が多々ありました。

ですが、 当時の構成では以下の問題がありました。

  • イメージの管理やデプロイがし辛い
  • スケーリングが考慮されてない

また、
Google 検索結果の医療や健康に関する検索結果の改善 が追い風となりトラフィックが伸び、スケーリングへの配慮が重要になりました。

webmaster-ja.googleblog.com

上記の問題を AWS ECS・ECR の恩恵を受けることで解決しようと考えました。

また、
開発促進をすべく、弊社で開発の知見のより多い Rails へのリプレイスを行う運びとなりました。

まず結論

f:id:kenzo0107:20180618121513p:plain

※ 左側がユーザサイド、右側が管理画面になります。

  • イメージ管理は ECR
  • デプロイは ecs-cli
  • CloudFront > ALB > Nginx > Rails というルーティング*1
  • Tasks, EC2 をオートスケーリング
  • RDS Aurora MySQL へリプレイスし Read Replica オートスケーリング

メディアサイトというサイト特性もあり、アクセスが集中しスパイクすることを鑑みて CloudFront でのキャッシュを有効にし、オートスケーリングできるところはする様にしました。

リニューアル直後の 2018年4月頃、麻疹が流行した際に麻疹関連の記事へのアクセスが急増した際は、本当にこの構成にしておいてよかったと思いました。

ishicome.medpeer.jp

続いて ECS でサービス構成するに当たって考慮したことをまとめました。

ECS への準備で考慮したこと

  • コンテナ設計
  • デプロイ
  • ロギング
  • オートスケール
  • バッチ処理
  • 検証環境

コンテナ設計

Nginx

Nginx をルーティングに挟んだのは以下の理由からです。

  • Rate limit の設定を細かくコントロールしたかった。*2
    • AWS WAF では最低でも 5分間に 2000 リクエスト以上でアクセス制限可能
  • IP 直指定回避

Rails

Rails コンテナについて以下の点を設計考慮しました。

secrets.yml

yaml_vault で暗号化・復号するようにしました。*3

  • KMS のエイリアスキーを作成し
  • そのキーでの暗・復号権限を IAM Group で管理する

こうすることで
権限の付与時には IAM Group に含める、
権限の剥奪時には IAM Group から除外する、
と管理が楽になりました。

Task Definition には基本秘密情報を載せない様にしました。*4

  • 実行コマンド
// secrets.yml の暗号化 (env: production)
yaml_vault encrypt \
  config/secrets.yml \
  -o config/encrypted_secrets.yml.production \
  --cryptor=aws-kms \
  --aws-region=ap-northeast-1 \
  --aws-kms-key-id=<kms alias> \
  --aws-profile <profile>

// secrets.yml の復号 (env: production)
yaml_vault decrypt \
  config/encrypted_secrets.yml.production \
  -o config/secrets.yml \
  --cryptor=aws-kms \
  --aws-region=ap-northeast-1 \
  --aws-kms-key-id=<kms alias> \
  --aws-profile <profile>
Dockerfile (Rails)

RAILS_ENV を渡してビルドし各環境毎に処理分けさせてます。

FROM ruby:2.5-alpine

RUN apk update \
  && apk upgrade \
  && apk add --update build-base mysql-dev nodejs tzdata git \
  && rm -rf /var/cache/apk/*

# TZ JST
RUN cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

ENV app /work/app
WORKDIR $app

COPY . $app
RUN bundle install -j4 --retry 6 --without test development --no-cache \
  && npm install --production --no-progress \
  && mkdir -p tmp/sockets \
  && mkdir -p tmp/pids

ARG RAILS_ENV

# docker build 時に db 接続しようとする為、接続しない様、nulldb 指定
RUN chmod +x docker/on_build.sh \
  && sync \
  && docker/on_build.sh

CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

docker/on_build.sh

  • assets:precompile, assets:sync 実行時に S3 にアップロード, CloudFrontで配信 by asset_sync, fog-aws
#!/bin/sh

set -e

# docker build 時に db 接続しようとする為、接続しない様、nulldb 指定
DB_ADAPTER=nulldb bundle exec rake assets:precompile assets:sync RAILS_ENV=${RAILS_ENV}

デプロイ

ecs-cli を採用しました!

採用理由は以下の通りです。

  • ECS 向けの AWS オフィシャルのツール
  • 既存の設定した AWS credential 利用可
  • Task 定義が docker-compose.yml 形式
  • Fargate 対応可

Fargate Tokyo region が待ち遠しいです♪*5

デプロイフロー

f:id:kenzo0107:20180618125321p:plain

  1. master, develop, qa/* に merge をトリガーに CircleCI ビルド
  2. テストがパスしたら CodePipeline 開始
  3. CodePipeline で cap (production|staging) deploy 実行

CircleCI には CodePipeline の開始・更新権限のみの AWS Access Key ID, Secret Key を設定しています。

CircleCI 上からデプロイしないの?

はじめに検討したのですが以下理由により上記構成を選択しました。

  • CircleCI とビルドサーバを分けることで CircleCI はテストに専念できる。
    → デプロイが終わるまでリソースを手放さず他のビルドが遅延するのを避ける。

ゆくゆくは CodePipeline でテストも実行し全てを完結させてみてコスト比較する検証をしたいと思っています。
既にあったら教えてください

ロギング

f:id:kenzo0107:20180618130832p:plain

  1. コンテナから awslogs で CloudWatch に出力
  2. Lambda で日次で CloudWatch のロググループを S3 に保存

Rails コンテナでは環境変数に RAILS_LOG_TO_STDOUT: 1 を設定することで Rails のログを標準出力し、CloudWatch に流す様にしています。

また、お好みですが
lograge で CloudWatch 上のログの視認性が高まりました。

CloudWatch のイベントフィルターで

{$.db >= 1000}

とすることで DBで 1000 msec 以上時間を要したイベントを抽出することができます。

オートスケール

f:id:kenzo0107:20180618131419p:plain

スケールアウト時に考慮したことと

  • まず EC2 インスタンスを増やして、その後、Task を増やす
    → Task が一方に偏ってしまう等の事象が発生してしまう為です。

CloudWatch の監視設定で evaluation_periods を EC2 < Task の様に設定しました。

  • インスタンス単体の CPU 使用率等のメトリクスでなく、Service でのメトリクスで考えること
    → 一時的な偏りでなく、全体的に負荷が高い場合にスケールする、という様にする為です。

  • RDS Aurora MySQL のオートスケールは Read/Write を switch_point で参照先を切り替える様にしました。

バッチ処理

クラスタを指定し ECS Scheduled Tasks で one-off container で定期実行します。

以下 Sitemap 定期更新タスク例です。

f:id:kenzo0107:20180618132440p:plain

検証環境の自動構築

f:id:kenzo0107:20180618133934p:plain

qa/* というブランチ名で push するとそのブランチに紐づくポートで検証環境を自動で構築する様にしました。

これによりエンジニア・ディレクターの検証回数が飛躍的に増え、画面を通じてコミュニケーションすることで認識の齟齬が減りプロジェクトがより円滑に進む様になりました。

現状、インスタンスタイプ m5.large の EC2 インスタンスをスポットで利用することで価格を抑えつつ 3~5 程度の検証環境が動作しています。

構築手順

  1. ALB の空いている Listener Port 取得
  2. 空き Port を元に target group 作成
  3. target group に branch 名をタグ付け
  4. インスタンス登録
  5. ALB Listener 作成
  6. ECS Service 作成
  7. service 名を branch 名がわかるようにラベリング
  8. QA 環境へ deploy

現状不要となった QA は cap で削除用コマンドを用意してますが ブランチ削除時に自動削除される等、検討中です。

ちなみに、どのブランチがどのポートを使っているかは Slack bot が教えてくれます。

f:id:kenzo0107:20180618143727p:plain

まとめ

Rails × ECS へのリプレイスにより自動化できた部分が多く本当に運用が楽になったと感じます。

特に検証環境の自動構築は他プロジェクトでも望まれ導入を進めています。

また、Fargate の検証を実施しておりますので、その折には本ブログにまとめていきたいと思います。

以上です。
参考になれば幸いです。


是非読者になってください(︎ ՞ਊ ՞)︎


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

インフラ構築・運用や開発プロセスの改善に携わっていただける SRE 募集中です!

SRE(サイトリライアビリティエンジニア) - 採用情報 - メドピア株式会社

Rails未経験/経験年数が2年以内のポテンシャルエンジニア絶賛募集中です!*6

Rails開発してみたい人、新しい技術に意欲的な人、リードエンジニアに教育されてみたい(?)人、医療に関わるサービス開発を行ってみたい人、その他メドピアに興味を持った方などは、是非、コンタクトを取って頂ければと思います!

*1:Nginx を挟んだ理由はコンテナ設計で!

*2:時折、 /phpmyadmin のようなパスでアクセスしてくるような攻撃が1分間に 200 リクエスト程きますが、 AWS WAF の based rule では防げません。

*3:イシコメ リニューアル直後に Rails 5.2 credentials が出た為、secrets.yml での秘密情報管理をしています。

*4:EC2 上で ssm で値を取得し環境変数に設定する方法もありますが Fargate の時どうするのか検証仕切れず、一旦 secrets.yml に寄せる様にしました。

*5:本記事執筆時に Fargate Tokyo Region の 2018年7月 予定がアナウンスされる神タイミングでしたので、執筆を急ぎました

*6:リードエンジニアも募集中です!