Akatsuki Hackers Lab | 株式会社アカツキ(Akatsuki Inc.)

Akatsuki Hackers Labは株式会社アカツキが運営しています。

開発環境インフラを ECS 移行している話

 この記事は Akatsuki Advent Calendar 2018 の24日目の記事です。 前回は id:NeoCat さんのElixirのサーバアプリケーションをDatadog APMでトレースするでした。

はじめに

はじめまして、運用中ゲームタイトルでサーバーエンジニアをしている守屋と申します。 私の所属するチームでは AWS EC2 上でサーバーサイドの運用を行ってきました。サービスリリース当初からの様々な改善の積み重ねの結果、近頃はインフラ障害も少なくなり、安定した運用が実現しています。しかし安定した運用が実現すればサーバーエンジニアの仕事は終わりなのでしょうか?安定しているとはいえ既存インフラには様々な技術的な負債が蓄積されています。我々サーバーチームは既存インフラを設計から見直し、新環境へ全てを移行する一大決心を下したのです。

既存開発環境インフラ構成 

以下に大まかな構成図を示します。*1

f:id:Zepprix:20181222155134p:plain

 サーバーサイドは Nginx & Ruby on Rais 用いたゲーム API サーバー、マスタ等を GUI で管理できる Admin ツールを同一の EC2 上で動かしています。

 ゲームのマスタ & ユーザーデータの格納に RDS 、キャッシュに ElastiCache を利用しており、ゲーム内で収集する各種 log データはアプリ用とは別に log 収集用のEC2 インスタンス( Log Aggregator )を立てており、Fluentd を用いて log DB へ転送しています。開発現場では複数の新規バージョンをチームで分担して同時平行に開発を進めており、新機能の開発や検証を行う環境も複数存在しています。 

デプロイフロー 

f:id:Zepprix:20181224002442p:plain デプロイ用のサーバー上に Capistrano3 を用いたデプロイスクリプトを配置しています。Slack から Github社製のチャット bot である Hubot のコマンドを叩くと、コマンド引数として与えた環境名がデプロイスクリプトに渡り、指定先にデプロイが実行される仕組みとなっています。DB Migration もデプロイスクリプトに組み込んでおり、デプロイサーバーから実行する形です。

 デプロイサーバーには他にもマスタデータの Seeding や検証用のダミーデータ生成など様々なスクリプト(ほとんどが Rails の Rake タスク)を配置しており、エンジニア以外でも Slack から Hubot コマンドを叩くことで簡単に実行できるようにしています。

 

実現したいこと

真の Infrastructure as Code の実現

 複数の開発環境は必要性が生じる度にサーバーエンジニアが手動で立ち上げてきた経緯があります。個々のサーバーの設定は構成管理ツールの Chef による自動化とコード管理が実現していますが、EC2 や RDS インスタンスの立ち上げなどインフラ全体の構成自動化までには手が回っておらず、新規環境の立ち上げには非常に手間のかかる状態となっていました。インフラ全体をコード管理下に置くことで新規構築が容易にできると同時に、新メンバーが既存の構成をコードで把握することができる等のメリットが期待されます。

Feature 毎に環境を立ち上げ可能

 サーバーエンジニアが新機能を実装する際、Git で Feature ブランチを切って作業することがよくあります。しかし実装完了してクライアントとの結合をした上で検証をするには結局、一つの EC2 上にデプロイしなければなりません。よってデプロイしたコードにバグがあった場合、当該サーバーで別機能の検証を行なっていた他メンバーの作業にも深刻な影響を与えることになってしまいます。アプリケーションが動作する環境を例えば Feature ブランチ毎にサクッと立ち上げて検証が完了したら本流にマージして環境を破棄する、みたいな運用を実現したいと考えました。

夢を実現する為のソリューション

Cloud​Formation の導入

 開発環境を構成する全ての AWS リソースをコードで管理し、構築・変更・破棄をコマンドで楽に実行できるようにします。今回は EC2、RDS 等のインスタンスだけではなく、VPC やセキュリティグループ等のネットワーク周りの設定まで全てを Cloud​Formation で作り直します。移行コストは大きいですが、既存設定の整理や見直しの良い機会にもなると考えました。デフォルトでは JSON か YAML で記述することになりますが、 kumogata2 を使うと Ruby DSL で便利に書くことができます。

ECS への移行

 Rails アプリケーション、Log Aggregator といったアプリを動かす為の仕組みを Docker コンテナに移行し、全てのコンテナを Docker Compose で単一ホスト上で動かす運用に切り替えます。よって AWS のコンテナマネージドサービスである ECS に移行する形がスムーズです。ECS には EC2 および Fargate で起動する 2 種類のタイプがありますが、Fargate は料金が高く、ホストへの ssh 接続が不可なのでデバッグのやり易さに懸念があり、EC2 起動タイプを採用することにしました。 

既存のインターフェースを変更しないようにする

 今回行う大規模なインフラ構築の最大のネックは既存環境の移行であるという点です。我々が新規インフラ構築作業を行う傍で、既存インフラでは多くのメンバーが開発・検証業務を行なっています。よって新規インフラに移行する際は他メンバーへの負担を可能な限り抑える必要がありました。その為に以下のような方針を打ち立てました。

  • 新 VPC 下にインフラを立て直すが、DNS を切り替えることで既存のエンドポイントを変えないようにする
  • デプロイは今まで通り Slack => Hubot 経由で実行可能にする(当然、その他の Rake タスクも)
  • RDSはコンテナ化しない  *2   

そして出来上がった新インフラ構成案

f:id:Zepprix:20181224003842p:plain  ゲーム API サーバー、Admin ツール、Fluentd、Log Aggregator をそれぞれ Docker コンテナ化して単一の EC2 上で動作させ、ECS で管理します。*3

 ゲーム API サーバーと Admin ツールのコンテナは別々ですが、Admin ツールのコードはゲームと同じ Rails リポジトリに入っているので Docker イメージは両者で分ける必要はなく、Docker Compose で Port と環境変数を指定することで起動モードを切り替えて運用します。また RDS、ElastiCache はインスタンスを Cloud​Formation で立ち上げて、既存のデータを移行する方針です。

 アプリを動かす為の仕組みが全てコンテナ化したことで、Ruby や td-agent のインストールなど今まで Chef で 0 から行なっていた EC2 インスタンスのセットアップも大幅に簡略化できることも嬉しい所です。ただしサーバーへのユーザー追加等、最小限の設定は必要なので、この機会に使い古した Chef から Ansible に乗り換えることにしました。

新しいデプロイフロー

f:id:Zepprix:20181224003918p:plain  デプロイは今まで通りデプロイサーバーにスクリプトを配置して、Hubot で実行する形にします。Docker イメージのビルドには AWS のマネージド型ビルドサービスである CodeBuild を使用します。*4

CodeBuild はデプロイコマンドの引数として渡した環境名に対応したブランチのソースコードを GitHub から取得し、ビルドした Docker イメージを Elastic Container Registry(ECR) に Push します。その際に環境名と日時をタグとしてイメージに付与することで、環境によって中身が異なるゲーム API サーバーのイメージを同一の ECR リポジトリにまとめることが可能です。

あとはデプロイサーバーに配置した docker-compose.yml ファイルを利用して ecs-cli composeコマンドでコンテナを起動すれば良いのですが、その前に DB の Migration を Rake タスクで実行する必要があります。以前のようにデプロイサーバー上に Rails リポジトリを Clone しておくのも手ですが、デプロイサーバー上には ECR から Pull された Docker イメージが存在するので、docker run コマンドでコンテナを立ち上げて Rake タスクを実行してしまえば良さそうです。以前よりもサーバー内のディレクトリがスッキリしますね。

Hubot 経由で実行していたその他の Rake タスクも同じ方法でデプロイサーバー上で今まで通り実行できそうです。これで中身を大幅に変更しつつもインターフェースは既存の仕組みを継続するという要件に応えることができます。

実装をどう進めるか

完成した設計を元にタスクを JIRA チケット化してチームで開発を進めています。タスクはかなりざっくりですが、以下の項目が挙げられます。

  • Docker イメージ
    • ゲーム API サーバー
    • Fluentd
    • Log Aggregator
  • CloudFormation テンプレート & Stack
    • VPC、Subnet、Security Group、IAM Role 等のネットワーク・セキュリティ設定
    • ECS、ECR、CodeBuild、RDS、 ElastiCache 等のリソース設定
    • 各開発環境の Stack
  • デプロイフロー
    • CodeBuild
    • ECS
    • Hubot コマンド側の対応
  • Ansible
    • 各サーバーへの User 追加
    • デプロイサーバーへの Docker、ecs-cli インストール
  • クライアントとの結合テスト
  • データ等の移行手順の検討

属人化を防ぐ為、出来るだけタスクをサーバーエンジニアチーム全員に割り振って実装を進める形にしています。手始めに CloudFormation で VPC を立ち上げることで他のメンバーも作業することが可能となりました。

アカツキでは既に複数の PJ にて Docker、ECS での運用が実現しており、他 PJ でのノウハウを活かしながら実装を進めることが出来ています。本記事執筆時点では Sandbox 環境へのデプロイが通るところまで完了していますが、まだまだやるべきことは多いですね。  

まとめと今後の展望

既存のインフラを移行する場合、0 からの環境構築とは違った現場特有の要件に応えていく必要があります。開発環境の移行が完了したら当然、次は本番環境の移行に着手する予定です。インフラ・クラウドに限らず技術は日進月歩、今後も現状に満足せず常により良い環境の構築を目指していきたいと思います。

*1:本記事で紹介するインフラ構成には新・旧どちらにも適宜省略している要素があります。また環境名は実際のものとは異なります。

*2:DB はエンジニア以外のメンバーも Sequel Pro 経由で接続できるようにする必要がある上、データ永続化が必要 & Amazon Auroraの良さが消える等々、コンテナ化するメリットがあまり感じられませんでした。

*3:Log Aggregator は API サーバーと同じホスト上で運用すると Heartbeat UDP Packet が正常に疎通しない問題が他 PJ で発生した為、Staging、Production 環境では別の EC2 インスタンス上にコンテナを配置する予定です。

*4:CodeBuild は e_koma さんによる15日目の記事でも紹介されていますので是非ともご覧下さい。