トレタ開発者ブログ

飲食店向け予約/顧客台帳サービス「トレタ」、モバイルオーダー「トレタO/X」などを運営するトレタの開発メンバーによるブログです。

トレタのバックエンドを ECS へ移行した話 [後編]

Advent Calendar 2020 の 4 日目の記事です。

こんにちは、 wind-up-bird です。

前回に引き続き、ECS移行について書いていきたいと思います。

  • 前編: 移行前の構成や課題、移行方針を記載しています。
  • 後編: 移行後の構成や旧環境との変更点を記載しています。

※前編をまだ読んでない人は是非チェックしてみてね!

目次

新環境

この章では、ECS移行後の構成やデプロイ方法、移行方法を紹介していきたいと思います。

全体構成

全体的な構成は以下のようなイメージです。

f:id:w1nd-up-b1rd:20201201153302p:plain

1 つの ECS クラスター上に複数の ECS Service が稼働しており、API やウェブ予約、非同期処理など、各ロールが ECS Service に対応しています。

なお、プラットフォームは Fargate を採用しました。 これはマネージドサービスを採用することで、EC2レイヤーの管理が不要になることが主な理由です。

詳細

f:id:w1nd-up-b1rd:20201201155416p:plain

EC2 の場合と似ていますがプロセスではなく、各タスク内ではコンテナが起動しています。 また、用途はそれぞれ同じですが、 Fluentd ではなく、Fluent Bit を採用しています。(詳細は後述)

AWS のリソース管理

引き続き使い慣れている Terraform を利用します。

アプリケーションとは別に、インフラ用のレポジトリを新たに作成し、AWSのリソース管理はここで完結するようにしました。 ディレクトリ構成としては以下のように Module 化して、 呼び出し元で環境毎の差分を吸収しています。

├── environments
│   ├── production
│   │   ├── main.tf
│   │   └── remote-state.tf
│   └── staging
│       ├── main.tf
│       └── remote-state.tf
└── modules
    ├── service1 ※ECS の service1 で利用するAWSリソース
    │   ├── acm.tf
    │   ├── ...
    │   └── variables.tf
    ├── common ※ECS で共通で利用するAWSリソース
    │   ├── codepipeline.tf
    │   ├── ...
    │   └── variables.tf
    ├── ...
    └── service4 ※ECS の service4 で利用するAWSリソース
        ├── appautoscaling.tf
        ├── ...
        └── variables.tf

デプロイ

旧環境では、アプリケーションのデプロイは Capistrano を利用して EC2 へデプロイを実行していました。

今回の移行に伴って、デプロイパイプラインとして CodePipeline を新規に採用しました。 CodePipeline の内部では、 CodeBuild および CodeDeploy が動作します。

f:id:w1nd-up-b1rd:20201201163352p:plain

CodePipeline の各ステージでは、以下のようなことを行っています。

ステージ 説明
Source Github からアプリケーションのソースをダウンロードします。
Build Docker build (アプリケーションのbuild)、Image のタグ付け、ECRへイメージの push を実施します。
Deploy 作成された Image を利用して、 各ECSサービスにBlue/Greenデプロイを実施します。

なお、デプロイ開始と終了時には AWS Chatbot を利用して、成功/失敗の通知を Slack に流しています。

  • 成功

f:id:w1nd-up-b1rd:20201201162027p:plain

  • 失敗(例: 特定の CodeDeploy が失敗)

f:id:w1nd-up-b1rd:20201201162111p:plain

ロギング

旧環境では、ログのルーティングを目的として Fluentd プロセスが起動していました。 今回、ECSへの移行に伴い、Firelens (Fluent bit) を導入しました。*1 これは、 Fluentd よりも軽量であることが主な理由です。

挙動としては、アプリケーションのログファイル出力に対して、 Fluent Bit は tail input プラグイン を利用して取り込みます。 その後、タグに応じて CloudWatch のロググループに配送します。 また、Upstream として、 example1.com と example2.com のサーバ(EC2のときにも利用していたログの集約サーバ)を指定しています。

設定ファイルの主要な部分を抜き出すと、以下のようになっています。*2

  • fluent-bit.conf
    [SERVICE]
        Flush 1
        Grace 30
    
    [INPUT]
        Name tail
        Path /path/to/a.log
        Tag a.tag
    
    [OUTPUT]
        Name cloudwatch
        Match a.tag
        region ap-northeast-1
        log_group_name ${A_LOG_GROUP_NAME}
        log_stream_name a.$(ecs_task_id)
        log_format json
    
    [OUTPUT]
        Name forward
        Match a.tag
        Upstream /upstream.conf
  • upstream.conf
    [UPSTREAM]
        name       forward-balancing
    
    [NODE]
        name       aggregator-1
        host       example1.com
        port       24224
    
    [NODE]
        name       aggregator-2
        host       example2.com
        port       24224

なお、Fluent Bit コンテナとアプリケーションコンテナは Fargate タスクストレージ を利用して、各コンテナで Volume マウントしています。

移行作業

上記の通り、移行先の環境が整ったので、移行作業です。

旧環境と新環境は並行稼動期間を設けて、Route53 の加重レコードを利用して徐々に切り替える方法を取りました。

  • EC2:ECS = 100:0
  • EC2:ECS = 99:1 (3日間様子見)
  • EC2:ECS = 90:10 (3日間様子見)
  • EC2:ECS = 50:50 (3日間様子見)
  • EC2:ECS = 0:100 (1週間様子見)

というように徐々にトラフィックを新環境に向けていき、ユーザ影響を可能な限り最小化するようにしました。*3

最終的に、全く問題なく移行が完了しました。

と言えればよかったのですが、残念なことに移行先の設定ミスにより一部のサービスで障害が発生してしまいました。 しかし、これも最初の 3日間 の間に発覚したので、影響は最小限にすることができたのではないかと思います。

設定ミスを修正後、移行を再開し、その後は問題なく並行稼動期間を終えることができました。

最後、旧環境を削除して移行が完了しました。

振り返り

ECSに移行してどうどうだったかまとめてみます。

Build 時間の短縮

Packer + Ansible がなくなり、代わりに Nginx と Fluent Bit の Docker イメージ作成のみとなりました。 これにより 30分以上かかっていたイメージの作成が2分程度で終わるようになりました。

EC2とOSの管理から開放された

ECS on Fargate を採用したので、EC2とOSを管理・運用する必要なくなりました。 また、Ruby の Runtime 、各サービス向けの設定ファイルはすべてアプリケーションレポジトリ単独で管理できるようになりました。 これによって、SREチームへの以来 => 作業待ちという状況も解消されました。

起動時間と費用

EC2 では起動に10分弱かかっていたのが、 ECS のタスク起動は1〜2分で完了するようになりました。 これにより、より柔軟にスケーリングポリシーを設定することができ、余剰なコンピューティングリソースの削減にも繋がりました。

結果として、33万円/月 程度の費用削減も実現できました。*4

ハマった点など

長々と移行について書いてきましたが、せっかくなので今回の移行で悩んだことやハマった点なども残しておこうと思います。

設定ファイルの管理

これは、ちょっと悩んだ点です。

前述の AWSリソースの管理はアプリケーションのレポジトリとは別に管理しています。

  • レポA : アプリケーションのレポジトリ ( Ruby on Rails )
  • レポB : インフラのレポジトリ ( Terraform )

では、この境界をどこにするのか?という点で少し悩みました。 具体的には、ECSのタスク定義や buildspec.yml ファイル、 appspec.yml はレポAで管理したほうがいいのか、それともレポBで管理するべきか?という感じです。

これは、ライフサイクルと役割という観点から、以下のようにしてみました。

  • レポA : AWS のリソース上で "何を実行するか" を管理。
    • 例: buildspec.yaml、 appspec.yaml、 タスク定義ファイルなどは、こちらで管理。
  • レポB : AWS のリソース "そのもの" を管理。
    • 例: ELB、 CodePipeline、ECSクラスタ、ECSサービスなどは、こちらで管理

これがベストではないかもしれませんが、環境変数やビルドの内容などアプリケーションの変更と連動するものは レポA に寄せたほうがよかろうと考えました。

ECS 標準デプロイアクション

これは少しハマった点です。

このブログでは詳細を記載していませんが、 トレタでは非同期処理として Sidekiq を利用しています。*5 このような ELB に紐付かない ECS サービスは当初 ECSの標準デプロイアクションを利用する予定でした。

具体的には、 CodePipeline の Build ステージで aws ecs register-task-definition を実行後、Deploy ステージで imagedefinitions.json を利用して、登録したタスク定義を反映する想定です。

しかし、 実際は、 アクション実行時にサービスに設定されているタスク定義のリビジョンを元に imagedefinitions.json に従って image プロパティのみを更新した 新しいタスク定義のリビジョンを作成するという挙動になります。 (つまり、 aws ecs register-task-definition で登録したタスク定義は使われない。)

解決策としては、

  • Build ステージでタスク定義を更新後、 aws ecs update-service を実行する。
  • CodeDeployToECS アクションを使用して、ECS サービスに対して Blue/Green デプロイを行う
  • CloudFormation デプロイアクションを使用してサービスの更新を行う

が挙げられると思いますが、今回は Blue/Green デプロイの手法を採用しました。 理由は、

  • 他サービスのデプロイと並列実行が可能
  • デプロイ方法が Blue/Green デプロイに統一できる

ためです。 一方、これには課題もあり、 Blue/Green デプロイのためだけにしか使われない ELB が必要になります。 これはAWSのサポートに機能要望として挙げたので、今後のアップデートに期待したいと思います。

16kB のログチャンクサイズ

これは、ハマったところです。

Fluent Bit 導入当初は アプリケーションコンテナはログを標準出力していました。 従って、アプリケーションログの転送経路は以下のようになります。

  1. アプリケーションコンテナ
  2. Fluentd Docker Logging Driver
  3. Docker Daemon
  4. Unix Domain Socket
  5. Fluent Bit コンテナ

※参考 aws.amazon.com

しかし、導入後しばらく経ってから、一部のログレコードが分割されていることに気づきました。 調査したところ、Docker 側の制限により 16kB でログが分割される 動作になることが分かりました。

※参考 github.com

解決策としては、(他にもあるかもしれませんが)

  • Fluentd ロガーライブラリの導入
  • 標準出力をやめて、Fargate タスクストレージにログファイルとして出力する

を考えていましたが、導入が容易だった後者を選択しました。*6

なお、タスクストレージを利用する場合は、 20GB の容量制限(プラットフォームバージョン1.4.0以降)があるので、定期的なタスク入れ替えやログローテーションの仕組みが必要になります。

最後に

2020年3月のプロジェクト発足から2020年11月に完了するまで、ずっと関わってきました。 正直な感想としては、無事に移行完了してホッとしています。

しかし、今の構成やデプロイ方法などがベストだとは思っていないので、 今後も運用しながら改善していきたいと思います。

お決まり

エンジニア募集しています!!!

カジュアル面談も可能なので、ぜひ遊びに来てください。

https://corp.toreta.in/recruit/midcareer/


*1:Fluent Bit は初挑戦だったので、検証からはじめました。結果、本番環境でも利用可能であると判断し導入を決めました。

*2:実際は、FilterStream Processing、複数ログファイル対応などもう少し複雑なことをしています。

*3:もちろん移行先で問題があれば、すぐに EC2:ECS = 100:0 に戻します。

*4:前編で記載しましたが、移行前にEC2インスタンスサイズおよび台数を最適化できていなかったことも要因としてあります。

*5:これも今回の移行対象です

*6:それ以外にも、台帳以外のシステムでも同様の仕組みを導入できそうだ、という理由もありました

トレタのバックエンドを ECS へ移行した話 [前編]

Advent Calendar 2020 の 3 日目の記事です。

こんにちは、 wind-up-bird です。

トレタではSREチームに所属しており、このブログには初登場です。

今回は、トレタが飲食店向けに提供している顧客台帳・予約台帳サービスのバックエンドを EC2 から ECS へ移行したので、その時のお話を書いてみたいと思います。

内容は、前編と後編に分かれています。

  • 前編: 移行前の構成や課題、移行方針を記載しています。
  • 後編: 移行後の構成や旧環境との変更点を記載しています。

目次

旧環境と課題

全体構成

トレタのバックエンドは Ruby on Rails を採用しています。これは 1ソース (= 1 Github レポジトリ)で管理されており、API やウェブ予約、非同期処理など、各ロール毎にAWSのリソースが別れています。

また、アプリケーションのホスト先はEC2を採用しており、全体的な構成としては以下のようなイメージです。

f:id:w1nd-up-b1rd:20201201120217p:plain

詳細

もう少し具体的に見てみます。

f:id:w1nd-up-b1rd:20201201122724p:plain

1つのサービス(APIやウェブ予約などの各ロール)は、ALB と EC2 で構成されています。

プロセス 説明
Nginx リバースプロキシ、キャッシュなどの用途です。
App アプリケーションのプロセス。Unicorn が起動しています。
Flunentd Appログの収集。example1.com, example2.com サーバへ転送しています。
Datadog Datadog にEC2のメトリクスを転送しています。

デプロイ

アプリケーションを EC2 にデプロイするまでの流れは、

ツール 用途
Packer AMI を作成します。
Ansible Packer の Provisioner として利用しています。Ruby の Runtime、Nginx などのMW、各サービス向けの設定、Fluentd や Datadog エージェントをインストールします。
Terraform ALB、Listener、Target Group、Launch Template、 Auto Scaling Group などを管理。AMIを差し替えることで、新しい EC2 を起動することができます。

導入当初から、(いくつか変更点はあるものの)おおよそこの流れで開発してきたのですが、運用していくなかでいくつか課題も出てきました。

  • 当時のブログ:

tech.toreta.in

課題

1. 開発スピード

サービスの機能追加などに伴い、 Packer + Ansible が担当する部分が多くなっていきました。 結果として、1回の Packer build で 40分かかってしまうようになり、小さな変更でも都度この待ち時間が発生する状況でした。

f:id:w1nd-up-b1rd:20201201124908p:plain

また、AMIの作成後も Terraform でAMI IDを差し替えて、 Staging および Production 環境で別々にデプロイしていました。

従って、 どんな小さな変更でも

  • packer build
  • terraform apply (in staging)
  • terraform apply (in production)

のようにリリースサイクルをまわす必要がありました。(約1時間の作業...)

2. OSバージョンアップ

トレタではこれまで AMI のベースイメージは Ubuntu を利用していました。 従って、EOLに伴うバージョンアップ作業は今後も定期的に発生し続けることになります。*1

  • 前回OSバージョンアップしたときの様子

f:id:w1nd-up-b1rd:20201203115519p:plain

3. Packer と アプリケーションの依存

前述の通り、 Ruby の Runtime や各種設定ファイルのインストールも Packer 側で実施していたため、例えば、アプリケーションチームが Ruby のバージョンを上げたいような場合は、SREチームに依頼して、そこで作業待ちという状況も発生していました。

4. 起動スピードとキャパシティプランニング

最新のアプリケーションのダウンロードおよびプロセスの起動は、 userdata で実行していたため、EC2 インスタンスの1台の起動に10分弱かかっていました。 このような特性から、突発的なアクセス増を見越して、大きめのサイズのインスタンスを余剰に起動させておく、という対策をとっていました。*2

移行先の検討と移行方針

前述のような課題があり、今年プロジェクト化してアプリケーションチームとSREチームで移行に踏み切りました。

移行先

検討当時(2020年3月頃)、移行先の候補としては、EKS と ECS があったのですが、以下の理由で ECS を選択しました。

  1. プラットフォームのバージョンアップがない。
  2. EKS だけでなくECSも将来の選択肢として入れたかった。
  3. AWSのサービスとの親和性

それぞれ、もう少し詳しく書いてみます。

まず、移行検討の段階でトレタはコンテナオーケストレーションツールとしてEKSをすでに導入済みだったこともあり、自然な流れとしてEKSを移行先として想定していました。 しかし、EKS を選択した場合、3ヶ月に1回または最長でも年に1回のアップグレードが必要になります。 今回移行対象のサービスはトレタの土台を支えるバックエンドシステムということもあり、都度移行時の作業時間やリスクを取るよりはバージョンアップを必要としない ECS の方がよいだろうと判断しました。

また、トレタはこれまでEC2とEKSは利用してきていたものの、ECS利用の実績がまだありませんでした。 従って、このタイミングで自分たち自身でECSや周辺ツールを設計・構築・運用することで、選択肢の幅が広がることを期待したというのもあります。

最後に、AWSとの親和性があります(が、1, 2 と比べてそこまで大きくはないです)。

特に検討段階では、まだ EKS と AWS の各種サービスの連携(特にセキュリティ周り)はまだこれから発展していきそうだなという印象があり、前述のとおり土台となるバックエンドのホスト先としてEKS導入はまだ早いかもしれないと判断して見送りました。*3

  • 当時の移行検討資料

f:id:w1nd-up-b1rd:20201202160621p:plain

方針

移行先が決まったので、次は移行方針です。SREチーム内で協議し、以下のような方針を設けました。

  • 移行に伴うシステム停止やメンテナンス時間は設けない。
  • 新旧環境を並行稼動させつつ、ユーザ影響を可能な限り小さくする。
  • 可能な限りAWSのマネージドのサービスを利用する。
  • これまで使っていなかったAWSサービスも積極的に検証し、導入検討する。

今回のように移行対象が ALB + EC2 の場合は、DNSの重みを利用した段階的移行が可能であると考えていたため、無停止を目標に切り替える方針としています。

また、並行稼動にすることで移行時になにか問題があっても、すぐに旧環境にトラフィックを切り戻すことができるためです。

最後に、今回は検証の時間は十分確保できることもあり、(移行先のところでも触れましたが)将来選択できる技術要素を広げておこう!というのも狙いとしてありました。*4

後編へ続く

ということで、ECS移行前の状況や課題などまとめてみました。

後編ではこれを踏まえて、どのように移行していったのか、どのような仕組みにしたか、について書いていきたいと思います。

後編はこちら


*1:実際、2018年には Ubuntu 14.04 から 18.04 へ アップグレードしており、systemd 対応や各種MWのバージョンアップ、移行作業など、一筋縄ではいかない場面もありました。

*2:当時ここは改善の余地があったのですが、移行が決まったというのもあり、一旦そのままに。。。

*3: 2020年3月時点。このブログを書いている現在(2020年12月)は Pod への Security Group のアタッチメントkubernetes サービスアカウントに IAM パーミッションを割り当てる、ということもできるようになりました。EKSも日々進化しているため、将来的にはEKSへの移行も検討するかもしれません。

*4:検証したけど導入は見送った、というものも今後の資産になると考えています。

インフラエンジニア見習いによるAWS認定 クラウドプラクティショナー合格体験記

本記事はトレタアドベントカレンダーの2日目の記事です。

始めに

皆さま、こんにちは!
スプラトゥーンが大好きなのに一向にウデマエが上がらないトレタのエンジニア兼佐久間まゆちゃんのプロデューサーの@hiroki_tanakaです。
持ちブキを1つに固定してからは少しづつ勝てるようになり、楽しいです。
先日、AWS認定資格の1つであるAWS認定 クラウドプラクティショナーを受験したので、振り返りの意味も込めて試験までの道のりをご紹介させていただきます。

自己紹介

  • 社会人歴6年目エンジニア
  • 得意領域はサーバサイドのアプリケーションレイヤ(RubyとJavaが好き)
  • 最近、インフラエンジニアに入門した。
  • AWSはEC2やS3・VPCの存在を知っているが、自身で一から構築したことはない。

AWS認定 クラウドプラクティショナーとは

AWS認定 クラウドプラクティショナーとは、公式サイトにある通りAWSクラウドの知識とスキルを身につけ、全体的な理解を効果的に説明出来るようになることが目標となる試験です。
そのため、この資格を持っているとAWS クラウドの持つ各サービスを導入するメリットやコスト・運用方法などを網羅的に理解しているという証明になります。

また、この資格は数あるAWSの資格の中でも最も基礎レベルの資格となっているため、持っておくと今後AWSの上位の資格取得を目指した際に確実に役に立ちます。
AWSの勉強を始めたばかりの私にとってはまさにピッタリの資格でした。

f:id:hiroki_tanaka:20201201132404p:plain

ちなみに、
試験時間は90分で問題数は65問・合格最低点は700点(1000点満点)です。
受験料は11,000円(税別)です。

勉強法

勉強期間は1ヶ月で平日の朝夜にそれぞれ30〜40分でした。
(土日はあまり勉強していませんでした。。。)
なので、合計勉強時間は20時間程度かと思います。

参考書

これまでの自身の勉強経験から私は参考書と紙とペンを用いた勉強方法が一番性に合っていると分かっていたので、 下記のAWS クラウドプラクティショナーの参考書を紙に書き出しながら2周し、ほぼ暗記している状態まで読み込みました。

問題集

上記の参考書は問題数が少なかったので、問題集を追加で購入し参考書で身につけた知識の確認を行いました。
問題の難度としては本試験と同等程度の印象だったので、この問題集を常に9割以上取れるようになっていれば合格確率は高いと思います。
私は受験前に3周して、確実に満点が取れる状態になるように仕上げました。

また、AWSが提供しているサンプル問題も問題数が10問と少ないですがあります。
こちらは本試験より簡単な印象でした。

APNパートナーのクラウドプラクティショナー演習

これはトレタがAWSのAPNパートナーになっているため、受講出来ることができました。
内容としてはAWSの方から直接、各サービスにおける重要箇所や試験対策のコツを教えて頂ける場なので、正直、受講してとても良かったです。
私は受講後に演習で出てきた押さえるポイントを参考書で振り返るということをして、記憶の定着を図りました。

実際にAWSサービスを触れる

日々の業務の中で、terraformを用いてVPCやEC2・RDSを構築してマネジメントコンソールで確認するといったことを行っていました。
実際にAWSを手を動かしながら学ぶことで、参考書の内容の理解を深めたり、逆に曖昧だった箇所を確認する事ができたりと知識を確かなものにすることが出来ました。
terraformを用いての環境構築は下記の本の流れに沿って行いました。

受験と結果

コロナ禍なこともあって、自宅でのオンライン受験かテストセンターでのオフライン受験を選択出来るのですが、私は自宅ではあまり集中出来ない可能性を考えて最寄りのテストセンターで受験しました。
また、オンライン受験する場合はPSIピアゾンVUEを選択出来るのですが、日本語対応しているピアゾンVUEがオススメです。
PSIは試験官とのやり取りが全て英語のチャットで行われるため、日常会話程度の英語が出来ないと厳しいかと思います。

本試験は緊張していたこともあってか問題が難しく感じましたが、問題数に対して時間の余裕がかなりあるので、一問一問落ち着いて対応することができました。
(見直しを3回くらいしました笑)

試験後に簡単なアンケートに答えて、表示された結果は………
無事合格! f:id:hiroki_tanaka:20201201133321p:plain

それなりに自信はあったのですが、やはり合格の文字が出てくるまではかなりドキドキでした。
いやー、良かったですo(≧◇≦*)o

終わりに

インフラエンジニアに入門してからの数日は周りが呪文を唱えているように感じていましたが、クラウドプラクティショナーの受験のために知識を身に着けたこともあってその意味がわかるようになり、 少しずつですが議論に参加出来るようになったと感じています。
そのため、クラウドプラクティショナーの知識は確実に日々の業務の中で役に立っている実感があります。

ちなみに、試験を通じて好きになったAWSサービスはVPCです!
ほぼ全てのアーキテクチャに関わってくるネットワークですが、その構築が非常に簡単に出来るというクラウドの恩恵が最も大きいサービスの1つだと思っています。

そして、現在は次のステップであるAWS ソリューションアーキテクト アソシエイトの取得に向けて勉強を進めています。
資格取得は体系だった知識が身につけられるので、やりがいがあります!

終わりの終わりに

トレタに少しでも興味を持っていただいた方がいれば、ぜひ遊びに来てくださいヾ(=^▽^=)ノ
仲間も募集しています!
(今回のようなケースに対応した資格補助制度もあります!)

© Toreta, Inc.

Powered by Hatena Blog