Qiita を ECS 化しました

Qiita 及び Qiita Team は AWS の EC2 を利用してサービス提供と運用を行ってきましたが、今年 (2024年) の7月に ECS への移行を行いました。

この記事では、どういう変更をしたか、どう移行を進めたかについて書いていこうと思います。
## どういう変更を行ったか
### Before: EC2 時代の構成と、運用の課題

Qiita 及び Qiita Team は Ruby on Rails を利用した Web アプリケーションです。EC2 サーバーでこれらのサーバーを運用し、以下の方式でデプロイを行ってきました。

* アプリケーションのデプロイは (Ruby 用のデプロイツールである) Capistrano を利用
* EC2 サーバー側の設定変更や更新が必要なケースでは、新規の AMI を作成し、EC2 サーバーを入れ替え
	* AMI は [Packer](https://www.packer.io/) と [Chef](https://www.chef.io/) を利用してビルド

![ec2.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7048/fc52b2c1-6d67-519c-27d7-da2882ae172c.png)


この方式で長年運用してきたのですが、サービスを運用していくうえでいくつかの課題がありました。

* EC2 側の設定変更 (例: 言語、ミドルウェア、OS 等の更新、新たな種類のサーバーを追加) を行う手順が複雑で、時間がかかり、こなせるエンジニアが限られる
* 長年運用していく中で行ってきた改良が蓄積し、設定が複雑化
* 利用しているツールの一部が老朽化し、同じ構成での維持が困難になりつつある

特に最初の課題が様々な場面で足を引っ張っており、各種アップデートのペースが全体的に及び腰になってしまったり、設定やシステムを柔軟に変化させにくいなど、Qiita を全体的に新しい状態に持っていきにくい状態になっていました。

![Pasted image ami-change.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7048/0c8bbef9-a082-57f3-0d32-2c9a8f48def3.png)

(↑ 長期の運用で複雑化していったサーバー入れ替え手順 (とてもつらい))

### After: ECS (on EC2) で運用

今回は、EC2 上で ECS を動かす (ECS on EC2) 形で Qiita のサーバー群を移行しました。 

![ecs.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7048/caade6b4-dbd1-1f61-844e-c38d13532dee.png)

(※ 今回は契約の都合などで一旦 EC2 から ECS on EC2 に移行したのですが、移行後検討を行い、 Fargate に再度移行を進めています。そのあたりはまた別の記事で触れます。)

### ECS に対するデプロイ

GitHub Actions を利用することで、アプリケーションリポジトリから ECS Service / Scheduled Tasks の自動デプロイを行えるようにしています。大まかには以下の流れでデプロイを行っています。

```mermaid
graph LR
Build(docker build)-->PreTask("前処理 (migration, assets の配信等) の ecspresso deploy と run ")
PreTask-->Deploy1(ECS Service 1 の ecspresso deploy)
PreTask-->Deploy2(ECS Service 2 の ecspresso deploy)
PreTask-->DeployN(ECS Service N の ecspresso deploy)
Deploy1-->DeployCron(Scheduled Tasks の ecschedule apply)
Deploy2-->DeployCron
DeployN-->DeployCron
DeployCron-->PostTask(後処理)
```

ECS Service / Scheduled Tasks の設定の管理 & デプロイツールとして、今回は [ecspresso](https://github.com/kayac/ecspresso) と [ecschedule](https://github.com/Songmu/ecschedule) を採用しています。
ecspresso の概要などに関しては検索すれば多く事例やドキュメントが出てくるため、詳細は割愛しますが、 Qiita においては以下の利点から採用することにしました。

- 国内で利用実績が多く、事例やドキュメントが充実している
- CLI として配布されているので、
	- 手元で動作検証やデバッグ等が行いやすい
	- 社内のユースケースに合わせたスクリプトやツールを提供しやすい
- 設定の宣言的管理を行いやすい
- Jsonnet を利用できるので、共通の設定の括り出しや抽象化が行いやすい

この中だと、特に Jsonnet による設定の共通化や抽象化が行えることが非常に大きかったです。
Qiita, Qiita Team の元々の構成によるものと、 ecspresso 自体が ECS Service と Task Definition を対にする前提のため、ECS Service が多く必要になりやすく、これらの設定を効率的に管理しやすくするという点で Jsonnet は効果的でした。

実際に社内でタスク定義がだいたいこのような感じ
(↓)で、同じような定義が多数発生しがちなのですが、そういう場合でも後からまとめて変更するのがやりやすかったり、設定がレベルごとに分けて設定意図を残しやすかったりします。

```jsonnet

local AwsDefs = import '../shared/aws-defs.libsonnet';
local QiitaProxyContainerBase = import '../shared/container-base/qiita-proxy-container.libsonnet';
local QiitaRailsWebContainerBase = import '../shared/container-base/qiita-rails-web-container.libsonnet';

{
  family: AwsDefs.taskFamilyName('qiita-web-server'),
  containerDefinitions: [
    QiitaRailsWebContainerBase($.family) {
      cpu: '...',
      memoryReservation: '...',
	  environment+: [
        { name: 'WORKER_NUM', value: '...' },
      ],
    },
    QiitaProxyContainerBase($.family),
  ],
  taskRoleArn: AwsDefs.iamRoleArn.taskRole.app,
  executionRoleArn: AwsDefs.iamRoleArn.executionRole.app,
}
```

こうした利点があることから、 同じく Jsonnet を利用して ECS Schedule Tasks の管理を行える  [ecschedule](https://github.com/Songmu/ecschedule) も合わせて採用を行いました。
その他の関連リソース (ECS クラスタ、 ECS の AutoScaling 等) は Terraform で管理しています。

今回の移行によって、 Qiita リポジトリあるいは Terraform リポジトリでシンプルな形で設定が載せられるようになり、入れ替え手順も ECS を利用する形で規格化、標準化したことで、デプロイが簡単に行えるようになり、移行以前の課題を大きく改善することが出来ました。
例えば、これまではバックグラウンドワーカーのキューを1つ追加するだけでも1日作業が必要なのですが、移行後は一瞬で済むようになりました。あまりの楽さに思わず感動しました。

## ECS をどう安全に導入し、社内に広めるか

今回の移行で非常に気を使ったのは以下の2点です。

- 歴史が積み重なった過去の基盤からの移行をどうリスク小さく行うか
- 新しい基盤の使い方やトラブルシューティングを、他のメンバーにいかに浸透させるか

このプロジェクトを始めるにあたって、EC2 + Chef の設定の洗い出しを時間をかけて行い、その上で設計を行ったのですが、 元々 Qiita は非常に長い間 EC2 + Chef の構成で運用していたこともあり、「気を付けて移植したとしても何か漏れている設定がある」ぐらいの気持ちで、慎重に移行を行いました。

また、ECS の本格運用は Qiita 内で初めてかつ自分1人 + レビュアーの体制で移行を行いました。この体制だと知見が自分に集中し、属人化しがちです。 
(エンジニア毎の担当領域というのはあるのですが、)ECS はアプリケーションの土台であるためこういう状況は望ましくなく、これをある程度防ぐために適度に社内のメンバーを巻き込むタイミングも設けることにしました。

### 段階的に移行を進める

今回の ECS への移行はすべて無停止で行いました。そして、小さいうちに問題を発見し、直ちにロールバックできるように、以下のステップでかなり細かく刻みながら段階的に導入を行いました。

1. 検証環境を ECS 化
2. 本番環境の Web サーバーを ECS 化
	1. 社内メンバーのリクエストのみ ECS 化
	2. 影響の小さい箇所から、一定割合のリクエストを少しずつ割合を上げなから ECS 化
		1. 1%のリクエストを ECS 化
		2. 2%のリクエストを ECS 化
		3. ...
		4. 100%のリクエストを ECS 化
3. 非同期のタスクワーカーを同様に段階的に ECS 化

Web サーバーのリクエストを段階的に ECS 経由にする方法として、 ALB の[加重ターゲットグループ](https://aws.amazon.com/jp/blogs/news/new-application-load-balancer-simplifies-deployment-with-weighted-target-groups/) などで、リクエストの流量などの制御を行いました。
また、[Sentry](https://sentry.io/welcome/) や [Datadog](https://www.datadoghq.com/ja/) などのモニタリングツールにはタグ付けの機能があり、これを利用して、ECS で発生した問題を容易に特定を行える状態にしていました。

かなり細かいカナリアリリースと言う感じですが、これにより、実際にリリース範囲が小さいうちに、不具合や設定の不備などの発見が出来ました。

### 一緒に触る、書く、見る機会を作る

属人化を防ぐためにドキュメントは色々用意したりは当然するのですが、新たに何かを導入する場面では、ドキュメントだけだと中々伝わりにくく、理解がされにくいものです。そこで一緒に手を動かし、手で覚える機会を積極的に作りました。

まず、検証環境での ECS 環境の構築が出来てきたタイミングで、 ECS Exec を実際に行って検証環境を触るチュートリアルを作り、各メンバーに実際にやってもらう会を設けました。

![Pasted image hands-on.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7048/852d8f99-ac2e-396c-9f93-e0dddc0b1cf8.png)

反応としては上々で ECS の各種用語を大まかに把握できた、 ECS で動くイメージを掴めたなどの反応がありました。

また、ECS を利用していく上での tips やドキュメントを書く場を用意し、質問や相談が来たタイミングで、一緒にドキュメントを書いたり、口頭で教えた内容をドキュメント化して別のメンバーに伝えるのもお願いするなどしていました。

![Pasted image ecs-handbook.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7048/2aa1ef8e-6e1a-b4f6-1137-5e53aaf2e3a2.png)


元々 Qiita 社ではドキュメント文化が浸透しており、お互いにノウハウをドキュメント化して教え合い、ドキュメントを ([GitLab Handbook](https://handbook.gitlab.com/) を参考に) 整理することを日常的にやっていました。そのため、こうした動きがスムーズに始めやすかったです。
そして書くことで、各メンバー内の理解が進み、共有する場所を作ることで、メンバー間での教え合いが進み、自分が知見を伝える負荷が減り、と非常に良いことだらけでした。

また、移行の際のサービス監視についても、 [Datadog Notebook](https://docs.datadoghq.com/ja/notebooks/) を利用し、移行状況や ECS の状態の可視化を行い、チーム朝会などでこれを一緒に見るタイミングを設けていました。

![Pasted image datadog-notebook.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7048/57db7e38-cd88-714a-0c8c-4ef8f1ec7ccd.png)

Datadog Notebook は簡易版ダッシュボード、Datadog metrics 版 Jupyter Notebook といった感じですが、ECS 導入段階で知見が溜まっていない場面での可視化の試行錯誤が行えたり、自分が何の指標に着目しているかをチームメンバーに伝えたりと重宝しました。

移行状況や、ECS への移行の利点、ドキュメント化など、プロジェクトに関する情報の共有もかなり細かく行いました。

## 終わりに

ということで、Qiita の ECS 化以前の課題と、どう移行をすすめたかについて、この記事では紹介しました。システムの移行の事例、ECS の導入事例として参考になれば幸いです。(詳細は割愛している部分もあるので、気になる部分などは Qiita や SNS 上でコメントいただけると嬉しいです)

また、こういった基盤の変更はユーザーの目には直接は見えないものなのですが、システムの進化を支えるうえでは欠かせないものです。基盤の変更により、来年はユーザーの目に見えるような Qiita の改善を加速させていきますので、どうぞ今後ともよろしくお願いします 💪