こんにちは。カミナシでソフトウェアエンジニアをしているaomanです。
私のエンジニアとしての経歴はカミナシが2社目で、前職も含めフロントエンドからバックエンドまで一通り開発はしていました。ですが、AWSなどインフラに関しては、アプリケーション開発時必要になったところを少し触ったりするくらいで、ガッツリと本格的に学んだことがありませんでした。
そんな私ですが、今回ECS Clusterの切り替え作業を先輩エンジニア監修の元一緒に行う機会をいただきました。どのようなことをしたのか、簡単にではありますがご紹介させて頂こうと思います!
ざっくり概要
カミナシのサービスでは、APIサーバーの運用にAmazon ECS(on Fargate)を利用しています。また、APIサーバーコンテナの他にいくつかのコンテナが起動しています。以下がざっくりとした図になります。1つのTask定義があり、4つのコンテナが起動しています。
AppConfigはフィーチャーフラグのために利用しています。同僚のエンジニアの方がAWS Startup Dayに登壇された際スライドや、投稿されたブログ記事があるので、興味のある方はご覧ください!
そんなインフラを運用する中で、3つほどの大きな問題点がありました。それは「リリースパイプラインの問題」「可用性の問題」「IAMロールの問題」です(後ほど詳しく解説します)。
これらの問題を解決するために、現行のECS Clusterに変更を加えていくことはリスクが大きいと判断し、新しいECS Clusterを横に立ち上げて、ゼロダウンタイムで徐々にトラフィックを切り替えていき移行しました🎉
きっかけ
きっかけは、興味ある人いたら一緒にやりましょう!という先輩エンジニアからのお誘いに手を上げたことでした。
カミナシではスクラム開発をしており、基本的に1週間1スプリントで開発をしています。また、通常スプリントとは別に、月に1度ほど「内部品質改善スプリント」という取り組みをしていて、その名の通り、内部的な品質を改善しちゃるぜ!というスプリントを行っています。
「内部品質改善スプリント」では、個々に改善したい取り組みを行うことや、人を募って複数人で一つの課題を解決するなど柔軟に開発しています。
本記事の取り組みは、事前にDesign Docを作成&共有いただいており、実際に手を動かすところをやりたい人いたら一緒にやりましょう!と人を募っていたので、私含め2名(もう1名もインフラ初心者)が手を上げ、3名で行うこととなりました。
このような機会をいただけたことには感謝しかありません🙏
式年遷宮とは?
由来はこちらをご参照ください: https://blog.kenjiskywalker.org/2013/08/11/shikinen-sengoo-infrastracture/
今回初めて式年遷宮という言葉を知りました。三重の伊勢神宮で20年に1度行う儀式で(リンク)、新しい社殿を造って御神体を遷すことだそうです。これに準えて、インフラでは維持管理のため、一定期間で更新するような取り組みを指すようです。
既存であった問題点
大きく3つあった問題がどのようなものだったのか?具体的にご紹介します。
1.リリースパイプラインの問題
カミナシでは、Dev環境とProd環境でAWSアカウントを分けて運用していますが、それぞれ別のデプロイ方法だったのです。
- Dev環境: GitHub Actions & ecspresso
- Prod環境: AWS CodeBuild & CodeDeploy
過去にリリースパイプラインを変更しようとしたようでしたが、Dev環境のみの適用で止まっており、やり切ることなく放置されているような状態でした。
環境ごとに別々のリリースパイプラインであるため、認知負荷が非常に高く、意図せずにTask定義の変更が巻き戻ってしまうなど、事故も発生していました。
また、Terraformでコード管理されていない箇所も多々あり、Dev環境では一部、Prod環境ではECSリソースが丸っとコード化されていませんでした。
2.可用性の問題
VPCには3AZあるにも関わらず、ECSとALBでは2AZしか利用していませんでした。
AZ障害が起きたときには半分を失う可能性があること。また、ALBには最低2AZ以上を設定する必要があるため、2AZ構成では、AZに障害のあった場合片方のAZを切り離すことができないなどの問題がありました。(詳しくはこちらの記事を参照)
3.IAMロールの問題
ECSの「Taskロール」と「Task実行ロール」に同じIAMロールが設定されていました。
同一のIAMロールが利用されてしまっていることで、本来不要であるはずの権限が付与されている状態となり、不測の事態が発生した際に幅広いリソースに対してリスクがある状態でした。最小限の権限の割り当てに変更する必要があります。
また、使用していたIAMロールに割り当てられていたいくつかのIAMポリシーでは、重複した権限が付与されていたり、不必要なAction/Resourceが付与されていたりと、とても煩雑な状態でした。
最小限のアクセス権限をIAMロールに付与することは、ベストプラクティスを文書化したAWS Well-Architected Framework(リンク)にも基本として記載されています。
それぞれの問題に対しての改善方針
1.リリースパイプラインの問題
▶ Prod環境をDev環境に合わせ、GitHub Actions & ecspresso にする
2.可用性の問題
▶ 3AZ構成にする
3.IAMロールの問題
▶ Taskロール/Task実行ロールでそれぞれ必要最低限の権限を付与したロールに変更する
実際にやったこと
それでは、実際にやったことの流れについてご紹介していきます。
🟩IAMロールとIAMポリシーの整理
必要な権限の調査
APIのTask定義に対する、Taskロール/Task実行ロールに適切なIAMロールを設定するために、それぞれどのような権限が必要かの洗い出しと調査を実施しました。特にTaskロールでは、APIサーバーのコンテナ含め、どのようなAWSリソースへの権限が必要なのか調査が大変でした。
(以下のようなイメージで、機能と権限をスプレッドシートで地道に調査しました)
一度広い権限を付与し運用を始めてしまうと、あとから権限を整理することは大変骨の折れる作業であることを今回改めて実感しました。幸いカミナシは、まだそこまで大きな規模のアプリケーションではなかったため数日ほどの調査で終わりましたが、より大規模なアプリケーションであったらと思うと、背筋が凍るような思いです。
Taskロール/Task実行ロールに利用するIAMロールを作成
調査結果を元に、それぞれで利用するためのIAMロールろIAMポリシーのリソースをTerraformで新たに作成しました。
🟩Terraformで新ECS Clusterを作成
Terraform定義
TerraformではVPCやSubnetなど一部のリソースのみしか定義されておらず、ECS Clusterは丸々Terraform管理されていませんでした。そのため、前ステップで作成したIAMロールをTaskロールとTask実行ロールに設定しつつ、新たにECS Clusterの定義をTerraformで書いていきました。
リソースのApply
最終的にTask定義はTerraformで管理せずecspressoを利用します。ただ、初回のECS Cluster立ち上げ時にTask&Containerを起動して動作検証等を行いたかったため、Task定義もTerraformに記述しました。初回のApply以降は不要になるので、変更差分を検知されないようにlifecycleのignore_changesを以下のように記述しています。
# ------------------------ # ECS cluster # ------------------------ resource "aws_ecs_cluster" "harami_api_server" { ... # Task definition は ecspresso 側で管理するため差分を無視するようにする lifecycle { ignore_changes = [ task_definition, ] } }
また、動作検証をするために、作業者のアクセスのみ新ECS Clusterへ向くように、IPを指定したListener Ruleを作成しました。
(以下のようなTerraform定義。新ECS Cluster環境の検証に関わる5名のみ新環境へトラフィックを流す)
# TODO: このリスナールールは新クラスターをテストするためのもので切り替えたら削除する。 resource "aws_lb_listener_rule" "harami_api_server_tester" { ... condition { source_ip { values = [ "xxx.xxx.xxx.xx/xx", "xxx.xxx.xx.xxx/xx", "xxx.xxx.xxx.xxx/xx", "xxx.xx.xxx.xxx/xx", "xxx.xxx.xxx.xx/xx" ] } } }
以上のTerraform定義をApplyすることで、新しいECS Clusterが横に立ち上がりました🎉
新ECS Clusterの動作検証
今回IAMロールを新しくしたので、AWSのリソースを触るような機能を洗い出しチェックリストを作成して、動作確認を行いました。
🟩リリースパイプラインの整備(escpressoのデプロイ設定)
旧ECS ClusterへのデプロイはAWS CodeBuild&CodeDeployで行われていましたが、新ECS ClusterへのデプロイGitHub Actions&ecspressoを利用していきます。
カミナシでは、Terraformでインフラを管理しているリポジトリとAPIサーバーのリポジトリを分けているので、APIサーバーのリポジトリでGitHub Actionsを回しデプロイするようにします。
GitHub Actionsを使用する理由は、WebフロントエンドやモバイルのCI/CDでも利用されているため、統一することでエンジニアの学習コストが下げられることがあります。また、ecspressoを採用したのは、Dev環境で十分に実績があること。そして、AWS CodeDeployの高度なデプロイ方法を必要としていないからでした。CodeDeployDefault.ECSAllAtOnceを使っており、すべてのトラフィックを同時に新しく立ち上げたECSコンテナに移行する設定でしたが、ECSのrolling updateで十分でした。
ecspresso configファイルの生成
ecspressoでデプロイするためには、設定ファイル(config)とタスク定義ファイルが必要になります。
ecspresso initコマンドを実行することで、すでに存在するECS Clusterリソースを読み込み、以下のようにconfigファイルとタスク定義ファイルを吐き出してくれます。
GitHub Actionsの設定
生成したconfigファイルを指定して ecspresso deploy --config config.yml
のように実行することで、ECSへのデプロイが可能です。
このコマンドをGitHub Actionsでmainブランチにマージされた時に実行されるようにしました。
※この時点では、mainブランチへマージしたら、新/旧ECS Clusterへのデプロイが両方走ります
🟩ALBのSubnetを2AZ→3AZへ変更
ALBのAZに関してはダウンタイムなしで変更が可能なため、このタイミングで2AZ→3AZへと変更を行いました(ドキュメントリンク)。
この時点ではまだ、旧ECS Clusterは2AZ構成ですが、クロスゾーン負荷分散が有効であったため、ALBを3AZの冗長化することが出来ました(クロスゾーン負荷分散参考)。
🟩トラフィックを切り替え
いよいよ実際の利用ユーザーのトラフィックを徐々に移行していきます。
以下のように、TerraformでListener Ruleのforwardでweigthを指定し、新/旧ECS Clusterのターゲットグループへのトラフィック量の比重を指定することが出来ます。
resource "aws_lb_listener_rule" "https_rule" { ... action { order = 1 type = "forward" forward { target_group { arn = xxx # 旧クラスターのターゲットグループを指定 weight = 90 } target_group { arn = xxx # 新クラスターのターゲットグループを指定 weight = 10 } } } ... }
3段階に分けてトラフィックを移行していきました。
すべてのトラフィックを新クラスターへ流し終わったあと、AWSコンソールにてターゲットグループのRequestメトリクスを確認すると、無事アクセスが0になったことを観測することが出来ました。
このような移行作業が初めてだった私にとって、この瞬間はかなりの感動ものでした笑
🟩諸々お掃除🧹
次のような諸々のお掃除フェーズです。
- 旧クラスターのタスク数を0にする(新クラスターで問題が発生した際に戻せるようにしばらく残しておく)
- CodeBuild & CodeDeployの無効化
- 無効化するという設定が見当たらなかったため、以下のCode PipelineのSource→Buildへの連鎖をDisableさせました
以上で無事移行が完了しました🎉
おわりに
拙い文章ではありましたが、ここまで読んでいただきありがとうございます!
今回の移行作業で、幸いにも大きな障害なくスムーズに移行することができました。
最後に宣伝です📣
カミナシでは絶賛仲間を募集中です!今回のような改善などを一緒にやってくれるエンジニアの方がいましたら、お話だけでも聞きに来てください!