AWS マルチアカウントの管理を Toil にしないために

この記事は [GLOBIS Advent Calendar 2019 - Qiita](https://qiita.com/advent-calendar/2019/globis) の9日目です。

7月に GLOBIS へ SRE として参画してから、集中的に取り組んできたことの1つである「AWSマルチアカウントの構成や運用効率化」に関して書いていきます。

## AWS マルチアカウント構成について

GLOBIS では AWS アカウントを環境ごとに分ける構成を取っています。各サービスにつき開発、ステージング、本番環境という形で 3 アカウントを設けており、現在その総数は 20 に届こうかというところです。私が参画し始めた後にもアカウントは増えています。

マルチアカウント構成はポピュラーな AWS アカウントの使い方だとは思いますが、 GLOBIS の場合以下のメリットを感じています。

* GLOBIS はサービスごとに開発チームが分かれており、各チームへ的確に分割された AWS IAM 権限を付与しやすい。
* AWS リソースを操作する際に、誤ったサービス / 環境を操作してしまう可能性が低い。
* 1アカウント内のリソースはすべて１つのサービスを構成する要素となっているため、インフラ構成が見通しやすい。

しかし一方で、サービスの増加に比例して管理対象のアカウントが増えてしまうのが SRE には辛いところです。いわゆる [SRE book](https://landing.google.com/sre/sre-book/toc/index.html) にも、 Toil の定義として「サービスの成長に対して $O(n)$ であること」が書かれており、アカウントの増加に比例して管理作業が増えるような事態は避けていく必要があります。そこで７月からの活動の中では、以下のような効率化作業を実施しました。

* Switch role の導入
* ChatOps の促進
* Infrastructure as Code の促進

順に見ていきます。

## Switch role の導入

様々な AWS アカウントを日々行き来しながら作業する SRE については、すべて Switch Role (sts:AssumeRole) を使った IAM に移行しました。

通常ですと、 AWS マネジメントコンソールからは同時に1つの AWS アカウントにしかログインできませんので、複数のアカウントで並行作業を行いたいときは、複数のブラウザを使うなどして、セッションを分ける必要があります。 Switch Role を活用すると、 １つの IAM User から、別の AWS アカウントの IAM Role へコンソール上で Switch ができるようになり、アカウントごとにログインや認証操作を行わずとも、スムーズにアカウント間を行き来できるようになります。

### Terraform による設定作業自動化

Switch Role を「使う」こと自体は非常に便利なのですが、そのための設定作業については、 Switch 元のアカウントに IAM User を作成し、さらに Switch 先の各アカウントに IAM Role を設ける必要があり、煩雑になりがちです。そこで Switch Role の作成や削除は Terraform で管理しています。

Terraform の実行は CodePipeline + CodeBuild を用いて自動化しました。 GitHub で Pull Request を出すと `terraform plan` が実行されて、 [mercari/tfnotify](https://github.com/mercari/tfnotify) により結果が Pull Request に貼り付けられます。 Pull Request を master ブランチへ merge されると `terraform apply` が実行される仕組みです。

<a href="https://gyazo.com/73192747e85ca4ddbc3b790eeb3c530e"><img src="https://i.gyazo.com/73192747e85ca4ddbc3b790eeb3c530e.png" alt="Image from Gyazo" width="681"/></a>

ポイントとしては、この Terraform の実行にもマルチアカウントな権限を必要とする点です。そこで CodeBuild の IAM Role には以下のような権限を与えています。

```hcl
data "aws_iam_policy_document" "codebuild_iam_terraform_policy" {
  statement {
    actions = [
      "sts:AssumeRole"
    ]
    resources = [
      "arn:aws:iam::*:role/codebuild_iam_terraform",
    ]
  }
}
```

この権限の意味するところは、任意の AWS アカウントの `codebuild_iam_terraform` という role へ `assumeRole` が可能となる、ということです。そのため新しい AWS アカウントが追加された際には、そのアカウントに IAM Role `codebuild_iam_terraform` を作成するという最小限の操作だけで、 Terraform の動作対象に含めることができます。

### 開発 / ステージングと本番は混在させない

Switch Role を設定する際の原則として、開発 / ステージングから本番へ、本番から開発 / ステージングへは Switch をさせないようにしています。これはひとえに誤操作の防止という観点です。

### サードパーティ OSS の活用

また Switch Role の利用にあたっては、以下のようなサードパーティツールを活用することで、さらに利便性を高めることができています。

* [AWS Extended Switch Roles](https://chrome.google.com/webstore/detail/aws-extend-switch-roles/jpmkfafbacpgapdghgdpembnojdlgkdl)
    * マネジメントコンソール右上に表示される Switch 先アカウントの履歴をカスタマイズ可能になる Chrome 機能拡張
    * 通常は直近5アカウントの履歴しか表示されませんが、好きなアカウントを自由に表示させることができるようになります
* [aws-vault](https://github.com/99designs/aws-vault)
    * AWS CLI の API キーを Keychain で管理できるなど、認証操作を強化してくれる CLI ツール
    * AWS CLI で switch role を行う場合、一時的な switch 用のクレデンシャルキーを発行して、そのキーを環境変数に設定して、、、と面倒だけど、 aws-vault だと1コマンドで switch できます
    * 詳細な背景は割愛しますが、現時点で Terraform で Switch Role + MFA を使う場合、このツールを使うのがほぼ必須になります
        * 参考） [Doesn't ask MFA token code when using assume_role with MFA required · Issue #2420 · terraform-providers/terraform-provider-aws](https://github.com/terraform-providers/terraform-provider-aws/issues/2420#issuecomment-411345124)
        * 詳細な背景は僭越ながら拙ブログにまとめています → [Terraform で AWS assume role が使用できない場合がある · the world as code](https://chroju.github.io/blog/2018/12/10/terraform_with_aws_assume_role/)

## ChatOps の促進

AWS アカウントの切り替えが簡単になったとは言え、簡単かつ頻発する作業であればログインすら行わずに済ませたいところです。そこで ChatOps を活用しています。

例えば弊社ではデプロイに CodePipeline を用いているのですが、本番環境のみ事故を避けるために手動でパイプラインを起動する必要があり、またさらに承認フェーズを挟むようにしています。これを Slack 上だけで完結できるようにしました。

[![Image from Gyazo](https://i.gyazo.com/d78d4938b6e289611596e1ffa81607e9.png)](https://gyazo.com/d78d4938b6e289611596e1ffa81607e9)

具体的にはこのような構成です。 slash command でパイプラインを起動し、承認フェーズに至ると SNS と Lambda を経由して、以下のように Interactive Message Button が Slack に表示されます。

<a href="https://gyazo.com/860d548408ce4a7f3955d624b68bfc7c"><img src="https://i.gyazo.com/860d548408ce4a7f3955d624b68bfc7c.png" alt="Image from Gyazo" width="300"/></a>

ボタンをクリックすると、誰がクリックしたのかが明示的に Slack へ残されます。

<a href="https://gyazo.com/cc64447fe42104d158265da22b535659"><img src="https://i.gyazo.com/cc64447fe42104d158265da22b535659.png" alt="Image from Gyazo" width="300"/></a>

図中にも書いてある通り、この構成は Serverless Framework で記述しており、新しい AWS アカウントが追加されたときにも、すぐに展開が可能になっています。

この他にも、各メンバーが必要に応じて Lambda を書くなどして、 GuardDuty の検知やセキュリティ上重要な操作の実行など、様々な Notification を Slack へ送っています。

## Infrastructure as Code の促進

GLOBIS では積極的なインフラのコード化を是としており、従来から Ansible や [Codenize.tools](https://codenize.tools/) を利用しているほか、独自の Python スクリプトなども用いています。最近ではこれに加えて Terraform や Serverless Framework も活用が進んでいます。

Ansible は別として、 Terraform 、 Serverless Framework 、 Codenize.tools はいずれも AWS のコード化に使うツールで担当範囲が重なるようにも思えますが、主に以下のように適材適所で使い分けています。

* Codenize.tools : Route53 (roadworker)
* Serverless Framework : AWS Lambda 関連
* Terraform : それ以外

### Codenize.tools

Route53 についてはレコード数が増えてくると `terraform plan` がかなり遅くなります。 `--parallelism` による並列数の増加なども試みましたが、今のところ改善策が見つからないため、 Terraform ではなく roadworker を使う形としています。記述も Terraform に比べてかなりシンプルに書くことができるのが魅力です。

### Serverless Framework

GLOBIS では先の slack 通知に使う Lambda のように、全アカウント横断で用いることができる汎用的な Lambda が少なくありません。そのため Lambda のコードを更新すると、全アカウントに対してデプロイ作業を行わなくてはなりません。

さすがにいちいち zip で固めてアップロードして、、、とはやっていられず、デプロイをワンコマンドで済ませるために Serverless Framework (sls) の導入を進めました。実行に必要となる IAM Role なども含めて YAML で簡潔に定義できる点、 zip にまとめてデプロイする処理がワンコマンドで済む点などを鑑みると、 Lambda については Terraform より sls でコード化したほうが取り回しがしやすいと感じています。

ただ、すでにデプロイ済みの Lambda を後から sls でコード化する、 import 操作を行うのは困難です。これは sls が裏側では Cloud Formation (CFn) に紐ついているため、単にコード化するだけではなく、 CFn の stack も作らなくてはならないあたりが関係しています（まぁ一旦 Lambda を削除して、 sls から改めてアップロードすればいい話だったりもしますが）。

そのため sls 導入以前よりデプロイ済みの Lambda については、代替として Makefile によるデプロイコマンドの統一を進めつつあります。

```makefile
FUNCTION_NAME := dummy
PROFILE := dummy

lambda_function.zip:
    zip lambda_function.zip lambda_function.py

.PHONY: update-code
update-code: lambda_function.zip
    aws lambda update-function-code --function-name $(FUNCTION_NAME) --zip-file fileb://lambda_function.zip --publish --profile $(PROFILE)

.PHONY: clean
clean:
    rm -f lambda_function.zip

.PHONY: deploy
deploy: update-code clean
```

なお、最終的には sls や Makefile のコマンド実行自体を CodePipeline などで自動化するつもりです。

### Terraform

Terraform は部分的に導入していますが、まだ活用方法を模索中という段階です。すべてをコード化することは時間的にも難しいことがあり、再利用性の高い構成を module 化することで、効果的に導入していければと考えています。

例えば SPA などは、 CloudFront + S3 + Lambda@Edge というほぼ決め打ちの構成が存在しているので、 module 化を進めやすい部分です。また各 AWS アカウントで統一的な構成を取っている箇所もコード化を進めるべき部分です。 GLOBIS の場合、サブネット構成が DMZ は第3オクテットが1、 Trust は2、という具合に統一したルールを設けているほか、 S3 バケットもログ用のバスケット、 CodeBuild の artifact を置くためのバケットなど、標準的に必要となるものがいくつか定まっています。このように標準化されている部分をコード化することから進めるのが妥当と考えています。

## 今後取り組みたいこと

最後に、今後取り組んでいきたいことをいくつかまとめて、この記事の〆とします。

### Slack からの AWS CLI 実行

先日発表されたばかりの新機能です。 AWS Chatbot を使って、 Slack 上から AWS CLI の実行ができます。

[Running AWS commands from Slack using AWS Chatbot | AWS DevOps Blog](https://aws.amazon.com/jp/blogs/devops/running-aws-commands-from-slack-using-aws-chatbot/)

さすがにリソース変更を伴う操作を Slack Channel のメンバー全員に許可してしまうのは怖いですが、稼働中　instance  の ID や IP を調べるなど、 read 系の操作を可能にするだけでも利便性はかなり高まるのではないかと思います。

### Organizations の活用

これは個人的な感想なのですが、正直 AWS Organizations はローンチ当初そこまで出来ることが多いわけでもなく、それほど魅力は感じていませんでした。

しかし、最近は Control Tower や Saving Plans など、 Organizations が有効化されていることを前提として、マルチアカウントにガバナンスをもたらしてくれるサービスが増えてきています（Saving Plansで「推奨事項」を表示するのに、 Organizations が必要です）。おそらく今後もこの流れが続き、マルチアカウントを統制する上で Organizations が必須になりそうな気がしています。そのためそろそろ Organizations 導入も考えるべきではないかという声は、チーム内で何度か挙がっています。

### 構成自体の見直し

GLOBIS のエンジニア組織が出来て3年近くが経ち、インフラ構成自体も大きく見直して良い時期になりつつあります。

例えば EC2 へ ssh 接続するためのいわゆる「踏み台サーバー」を現在は AWS アカウントごとに設けていますが、これを統合したり、 SSM Session Manager に代替することで、踏み台の管理コストが抑制が図れるのではないかと考えています。またコンテナの導入も検討を始めていますが、仮に EKS を導入するとなると、 k8s の RBAC と IAM をどのように絡めて権限制御を行うかを考えなくてはならなくなったり、コンテナエコシステムを前提とした AWS 構成が求められるようになります。今後どのようにサービスが拡充されていくのか長期的に考えつつ、スケールしやすいインフラを改めて考えていきたいところです。

このような Toil を抑える仕組みを常に考えていくことによって、 GLOBIS のサービス拡大を今後も SRE の立場から下支えできればと考えています。