EC2 で稼働しているシステムを ECS Fargate に移行させる

はじめに

弊社がサービス提供している popinfo のウェブ管理画面を EC2 中心のインフラから ECS Fargate 中心のインフラへ今年の1月末に移行しました。移行作業を行うにあたって得られた知識や経験などがいくつかありましたのでご紹介します。

移行前のインフラ構成

f:id:iridge-tech:20190424153940p:plain

まず移行前のインフラ構成についてご説明します。上記は移行前のインフラリソースとネットワークの流れを簡易的に表した図になります。少なくとも3年以上前にAWS上で構築されたシステムとなっており、今となってはかなりシンプルな構成です。

個別のリソース解説

End User

popinfo を導入して頂いているアプリの運用担当者様が管理画面の主な利用者になります。利用者は管理画面にログインしてプッシュ配信の登録や予約などの操作をされます。

Internet Gateway

VPC内のリソースがインターネットと通信できるようにするコンポーネントです。

Clasic Load Blancer

Internet Gateway で中継された End User からのリクエストを受け取り、複数ある Application Server にリクエストを振り分けます。

Application Server

EC2インスタンス上に起動している Nginx がまずリクエストを受け取り、同サーバ内の uWSGI + Django でリクエストを処理します。Python アプリケーションが後述の Static Files や Other Component にアクセスするために Public IP がインスタンスに割り当てられています。

MySQL DB

管理画面固有のユーザ情報やセッションなどのデータを保持しています。

Public Subnet (Multi AZ)

上記3つのリソース群は全て同じサブネットに所属しています。サブネットは2つのアベイラビリティゾーンで冗長化されています。 (Multi AZ)

Static Files

主に End User が管理画面上でアップロードした画像が格納されています。

Other Component

配信受付やクーポン、ポイントなど popinfo の諸機能は API Component として個別に稼働しています。管理画面は上記APIのウェブフロントエンドという位置付けになります。

Maintainer

筆者を含めた iRidge 社内の開発/運用担当者になります。

Bastion

Application Server にSSHログインするための踏み台サーバです。アプリケーションを更新する際は Bastion 上で Ansible を実行してデプロイを実施します。

移行前の課題

この移行前のシステムには以下の通り課題が複数存在している状態でした。

アプリケーションデプロイの手作業項目が多い。

Ansible によってデプロイの主作業自体はある程度自動化はされていました。しかし、作業者が踏み台サーバにSSHログインして、いくつかの目視確認や手動入力を挟みながら Ansible を実行するスタイルだったため、リリース手順書では30項目以上の人間によるオペレーションがありました。

ローカル開発環境の構築に手間と時間がかかる。

Vagrant + Ansible がドキュメントで用意されている標準のローカル環境構築方法でした。しかし、弊社内でVagrant を利用する機会が初回のローカル環境構築などでしか使わないため、チーム内の Vagrant に関する知見が乏しくメンテナンスがあまりされていませんでした。そのため、新しくジョインしたメンバーがローカル環境を構築するときに必ずハマる最初の関門となっていました。

インフラがコード管理されていない。

用意されていたCloudFormation のテンプレートファイルの全面的な適用は検証環境まででした。商用環境では歴史的経緯から極一部のリソースのみが CloudFormation で管理されている状態に留まっていました。

単一のパブリックサブネットなどネットワーク設計のレガシー感が否めない。

管理画面のネットワーク設計を行なった当初はAWSコンポーネントが現在より限られていたので、このようなシンプルな構成となったと推測します。特に、2015年末あたりはNATゲートウェイがマネージドサービスとして登場するかどうかのタイミングでした。

AWSアカウントが適切でない。

こちらは歴史的経緯かつ社内事情なのですが、AWS Organizations が一般公開される前は同一のAWSアカウントに複数プロジェクトのリソースが混在せざるを得ない状態でした。popinfo の主要コンポーネントは既にプロダクト専用のAWSアカウントに移行されていましたが、管理画面はまだ移行計画に着手できておらず、複数プロジェクトが混在したAWSアカウントにリソースが存在していました。これにより、同一プロダクトでもコンポーネントごとにリソースが存在するAWSアカウントが異なる状態が続いてしまっています。この状態はAWSアカウントを跨いで管理が複雑である点と、VPC内の完結が望ましい通信でもインターネット経由にせざるを得ないという2つの大まかな課題があります。

移行計画

先述の課題に加えて Docker コンテナ化の機運も世の中的に高まっているので、ECS Fargate を中心としたインフラに移行を決定しました。移行計画は2018年の夏頃から着手しましたが割り込みタスクもいくつかありましたので、実質的に作業に費やした期間は3ヶ月ほどになります。移行計画は大きく下記の4タスクに分けて実施しました。

1. アプリケーションの Dcoker コンテナ化

まず最初に着手した作業は、管理画面アプリケーションを実行できるコンテナイメージの作成です。管理画面は Nginx + uWSGI + Django の構成で稼働していたので、Nginx コンテナと uWSGI + Django コンテナをそれぞれ作成しました。Nginx コンテナは公式の nginx:alpine イメージをベースに nginx.conf ファイルを環境変数とテンプレートから動的に生成するスクリプトを仕込んだイメージを用意しました。uWSGI + Django コンテナは一部 Node.js で作成されたアプリケーションを含むため npm run build を行うステージと python:slim ベースのDjango実行環境を構築するステージに分けたマルチステージビルドで作成したイメージを用意しました。

また、ローカル開発環境用にDBも公式の mysql イメージを用いて一つの docker-compose.yml でサービスを起動できるようにしました。その際、Django Migrate 実行前にDBの起動を待つ必要があるので、下記のようなスクリプトを entrypoint に記述する必要がありました。

#!/bin/bash

# Wait for MySQL to wake up
until python manage.py check 2>/dev/null; do
    sleep 1s
done

python manage.py migrate

2. ECS Fargate を中心としたインフラの再設計

次に作成したイメージを実行させるコンテナ基盤を決める必要があるのですが、本番運用経験が社内で十分にあり、Kubernetes を使うほど大規模なアプリケーションではないため、今回は ECS をコンテナオーケストレーションに採用しました。また、Fargate については大きく下記2つのデメリットがありましたが、個別に検討してから採用を決めました。

インフラコストの増加

EC2 と比べて Fargate は同等のスペックを用意した場合、基本的にAWSの従量課金料が増加します。Fargate を採用した場合の料金を試算したところ、2倍程度のコスト増が見込まれましたが、元々のコストが少額だったのもあり Fargate による運用コストの削減効果で十分賄えると判断しました。また、今年初めの大幅値下げも Fargate 採用の後押しとなりました。

ログドライバーが CloudWatch Logs のみ

CloudWatch Logs は自前で fluentd などを組み合わせて構築したログ基盤に比べて、ログ量が膨大になるにつれてインフラコストが高額になります。ただ、今回は管理画面から日常的に出力されるログが少量でコストが少額だったため問題にはなりませんでした。また、Fargate でも fluentd などのログドライバーが使えるようになるアップデートが予定されているという情報もあります。

https://github.com/aws/containers-roadmap/issues/10

3. CI/CD の整備

CI/CI ツールには弊社で標準的に使用しているGitリポジトリマネージャーである GitLab を継続して使用しています。パイプラインは下記の4ステージに分けて順番に実行するように構築しました。

1. Docker イメージのビルド

Dockerfile を元にアプリケーションのコンテナイメージをビルドします。ビルドしたイメージは下記のようにファイルに出力してジョブ間でアーティファクトとして使い回します。

docker save application:ci_cache -o ${ARTIFACT_PATH}

2. ユニットテスト

CI用の docker-compose.yml からアプリケーション実行環境を起動してユニットテストを実施します。アプリケーションのコンテナイメージは下記のようにアーティファクトファイルから読み込んで実行時間を短縮させています。

docker load -i ${ARTIFACT_PATH}

このユニットテストを通過しないと後述のコンテナレジストリのプッシュがされないので、不備のあるコンテナイメージのデプロイを防止しています。

3. コンテナレジストリへのプッシュ

ユニットテストと同様にアーティファクトファイルからコンテナイメージを読み込んでコンテナレジストリへのプッシュを行います。

4. アプリケーションデプロイ

ECS CLI を用いて docker-compose ファイルを元にECSへのデプロイを行います。(ECS CLI は公式ドキュメントの英語ページと日本語ページで情報量の差がある場合があるので要注意。)デプロイするコンテナイメージは環境変数を用いてCI内で動的に指定されます。デプロイステージは自動的に開始されず、ウェブUI上で開始ボタンをクリックする必要があります。

4. リソースのAWSアカウント移設

管理画面リソースをプロダクト専用のAWSアカウントに移設する作業は、RDSとS3バケットが移行対象に含まれていたのでサービス停止を伴う作業にせざるを得ませんでした。特にS3バケットについてはアプリケーション実装の制約でバケット名を変更ができず、一度バケットを削除して別のAWSアカウントから同一名で再作成する必要がありました。これは少々リスクを伴った作業で、削除されたバケットと同一名のバケットを別のAWSアカウントで即座に再作成はできず、1時間のインターバルが強制的に設けられています。この際、Terraform を使用してリソースを作成すると最大5分間まで10秒間隔でリトライしてくれるので、インターバルから間髪入れずに再作成したい場合に役立ちました。

移行後のインフラ構成

f:id:iridge-tech:20190424154030p:plain

このような移行計画を経た最終的な移行後のインフラ構成を、移行前と同様に簡易的に図式化したものが上の図になります。

個別のリソース解説

Application Load Blancer

Clasic Load Blancer からリプレイスしましたが、基本的な役割は変わっていません。Application Load Blancer はカスタムセキュリティポリシーをサポートしていないため、レガシーな暗号方式を許容しているシステムからの移行は End User がHTTPS接続できない場合があり得るので注意が必要です。

Application Server

Fargate 起動タイプのECS上に起動している Nginx コンテナがまずリクエストを受け取り、uWSGI + Django コンテナでリクエストを処理します。各コンテナに Public IP は割り当てておらず、Python アプリケーションは NAT Gateway を経由して Static Files や Other Component にアクセスします。

Public/Private Subnet

インターネットとの通信を Internet Gateway 経由で行う Public Subnet と、NAT Gateway 経由で行う Private Subnet に分割しています。

NAT Gateway

インターネットとの通信を行うためのVPCコンポーネントですが、Internet Gateway とは異なりアドレス変換を用いて通信を行うため、インターネット側から背後のVPCインスタンスへ接続の開始ができません。

GitLab

弊社で標準的に使用しているGitリポジトリマネージャーで、管理画面のソースコードもこちらで管理しています。また、その他にもソフトウェア開発のために必要な統合的機能(Git、タスク管理、コンテナレジストリ、CI/CD、監視)を提供しているサービスです。

GitLab CI/CD

リポジトリにコミットがプッシュされると GitLab Runner が自動的にユニットテストやコンテナイメージのビルドを行い、後述の Container Registry にコンテナイメージをプッシュします。開始は手動 (ワンクリック) ですが ECS CLI と docker-compose を用いてアプリケーションデプロイもCIによって自動化されています。

Container Registry

ビルドされたコンテナイメージをホスティングしているプライベートレジストリです。

AWS Secrets Manager

ECSが上記のプライベートレジストリからコンテナイメーシをプルする際に必要な秘密情報を格納しています。

改善点

コンテナ化によって実行環境を柔軟かつ容易に構築できるようになった。

アプリケーションのコンテナイメージが用意されたので、商用環境やローカル開発環境、CI Runner 環境など様々な環境でアプリケーションの実行環境を構築が容易になりました。また今回はECSを採用しましたが、将来的にEKSやGKEなどといった別のコンテナオーケストレーションサービスに移行する戦略も取りやすくなりました。

CIによってデプロイ作業がより自動化された。

踏み台サーバでのコマンド実行からGitLabのウェブUI主体のデプロイ方法へ移行により、デプロイ作業が実行ボタンを押すだけのシンプルなオペレーションになりました。人間による判断や作業が入る箇所を減らしたりシンプルにすることで、デプロイ作業中に起きるミスの抑制につながります。

管理するインフラリソースが減った。

Fargate を採用によりアプリケーション実行環境のインフラレイヤーを管理する必要がなくなりました。これによって単純な管理コストの削減に加えて、スケールアウトや構成変更、環境の複製などもスピーディーに行いやすくなります。また、今回の移行に合わせてインフラのコード化を Terraform で行いましたが、記述するコード量が少なくシンプルになったのも大きなメリットでした。

おわりに

以上、popinfo 管理画面のインフラ移行について要点をピックアップしながらご紹介しました。今回の管理画面は数あるコンポーネントの中では比較的移行しやすい部類となっていまして、より難易度が高いシステムのインフラ移行に弊社メンバーが挑戦しています。こちらも時期が来ましたらご紹介しますのでご期待ください。

また、サービス停止を伴う移行作業にご協力いただきました利用者の方々にはこの場を借りてお礼を申し上げます。