AWS運用のノウハウ

# 概要

AWSを始めとするパブリッククラウド環境でインフラ運用を始めると、サーバ構築からインフラ監視、セキュリティ対策、コスト管理まで考えなければならないことがたくさん出てきます。
ここではAWSを初めて運用するインフラエンジニア、SRE向けにスモールスタートな環境構築からスケーラビリティの高いインフラ構成まで、様々なノウハウを公開していこうと思います。

# 初めにやること

## アカウント設計

システムを運用する上で、本番環境以外に開発環境や検証環境を作成するケースは多いと思います。AWSでサービスを運用する場合は大まかに次のパターンに分かれることが多くなります。

* 1つのAWSアカウント、1つのVPC (ネットワーク) に全ての環境を構築
* 1つのAWSアカウント、VPCごとに環境を分けて構築
* AWSアカウントを環境ごとに分けて作成

どのパターンが最適かは、システム構成や規模によって変わりますし、運用コストにも影響があります。インフラを設計する上では初めに考えるべきポイントとなるでしょう ([^1])。

## 命名規則の設計

インフラを設計するに辺り、プロダクトのコードネームを決めておきましょう。コードネームはインフラ・アプリケーション間で統一しておくことが望ましいです。

|タイプ|用途|例|命名規則|
|:--|:--|:--|:--|
|サービス名|プロダクトのサービス名|Qiita|なし|
|ドメイン名|プロダクトのドメイン名|qiita.com|RFC 1035|
|コードネーム|インフラリソースのプレフィックス (キーペア名、S3、IAMなど)、アプリケーション識別子など|qiita|ドメイン名からハイフンとピリオドを省いた形式|

## ルートアカウントの作成

* 決済方法の変更やアカウントの停止含め、AWSに関するあらゆる操作が可能なアカウントです。運用中は基本的に使用するべきではありません。代わりにIAMを用いて作業用ユーザーを作成しましょう
* 乗っ取りを防ぐため、必ずMFA (二段階認証) を有効にしましょう
* 組織で使用する場合、管理者不在の状況を防ぐため、個人のアドレスは避けるべきです
    * 退職や異動に伴い管理者不在となる可能性があります。専用のアカウントを用意しましょう
    * 重要なお知らせはルートアカウントのメールアドレス宛に配信されます。アカウントはメーリングリスト形式にして、運用チームに配信する形がお勧めです
* デフォルトで請求情報にアクセスできるのはルートアカウントのみです。運用上不便なので、作業用アカウント (IAMユーザー) でもアクセス閲覧できるよう設定しておくと便利です ([^2])
* サポートプランやペネトレーションテストなど、一部の操作はルートアカウントでしか行うことができません ([^3])

## リソースの構築手段

誰しも初めはAWSコンソールからリソースを作成したり削除することと思います。コンソールからの操作は直感的で分かりやすい反面、サービスがスケールするにつれ構成管理が複雑化し、オペレーションミスや運用コストの増大へと発展します。

AWSはあらゆるリソースをAPIから操作できるため、インフラをコードベース (IaC: Infrastructure as Code) で管理することができます。
コード管理の手法としては、AWSが提供するCloudFormationやCDK、HashiCorpが提供するTerraformなどがあります。
AWSが提供するサービスは新機能への対応が早い利点がありますが、TerraformはAWS以外にもGCPやAzureといった多数のプロバイダ ([^4]) に対応しているため、マルチクラウド対応のプラットフォームや、システムアーキテクチャとしてIaCを基盤としたシステムで構成管理を統合できる利点があります。

|タイプ|メリット|デメリット|
|:--|:--|:--|
|AWSコンソールから手動でリソースを作成|* 直感的で分かりやすい<br>* 新機能をすぐに試すことができる|* 構成が複雑になるにつれ管理コストが高くなる<br>* 同じ構成のシステムを構築する際に手間がかかる |
|コードベースでリソースを管理| * コードが仕様書となる<br>* コードレビューを取り入れることができる | * 設計・実装に時間を要する |

# サービス

## IAM

* 元も子もないですが、AWSが公開しているベストプラクティスを読みましょう ([^5])
* 実際に運用を始めてみると、全てのベストプラクティスに従うのは至難の業なので、ここでは特筆すべき事項についてピックアップして紹介します
    * 個々のIAMユーザーの作成
        * 「誰が」「どのリソースに対し」「どのような操作をしたか」を把握するため、アカウントは個々に作成するべきです。CloudTrailを有効にすることで証跡ログを残すこともできます
    * 最小権限を付与する
        * 全てのアカウントに `AdministratorAccess` ポリシーが付与されているのはAWSあるあるパターンだと思います。いつの間にか見知らぬリソースが作られてるだけならまだしも、インバウンドアクセスがフルオープンだったり (AWSコンソールからEC2を作るとデフォルトで新たにセキュリティグループを作ろうとする)、命名規則に反してたりと混乱の元になります
        * リソースはインフラ・SREのみが作成・更新できるものとし、開発者は参照など限定したポリシーを付与する体制が望ましいです
    * IAMユーザーへのアクセス許可を割り当てるためにグループを使用する
        * 全てのアカウントは何らかのグループに所属させましょう。アカウント単位でのインラインポリシーの適用は管理を煩雑化させるためお勧めしません
        * プロジェクトに関わる担当者をロールに落とし込み、グループとしてまとめたのが下表の例になります

            |グループ名|ロール|所属メンバー|
            |:--|:--|:--|
            |administrator|AdministratorAccessポリシー相当の権限を持つ|SRE|
            |operator|一部のリソースに対する参照権限を持つ|運用担当|
            |developer|一部のリソースに対し操作を行える権限を持つ (S3など)|開発者|
            |billing|Billingポリシー相当の権限を持つ|経理|
    * アクセスキーを共有しない
        * 出来ることなら、アクセスキーは開発者に付与しない体制を築きましょう
        * 開発環境がDockerで整っているのであれば、ローカルからAWSへのアクセスは不要となるはずです。代表的な代替コンテナは下表の通りです

            |サービス名|代替コンテナ|
            |:--|:--|
            |RDS|[mysql](https://hub.docker.com/_/mysql)|
            |ElastiCache| [redis](https://hub.docker.com/_/redis) / [memcached](https://hub.docker.com/_/memcached)|
            |S3|[minio](https://hub.docker.com/r/minio/minio)|
            |SQS|[elasticmq](https://hub.docker.com/r/s12v/elasticmq/)|
            |Dynamo DB|[dynamodb-local](https://hub.docker.com/r/amazon/dynamodb-local/)|
* サインインリンク
    * AWSコンソールのURLをアカウントIDではなく任意の名前を付けてアクセスできるようになります。冒頭で決めたコードネームでアクセスできるようにしておくと分かりやすいでしょう
    ![Screen_Shot_2020-08-31_at_2_20_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27454/e18f5678-e3bc-48b3-1cb3-28e3145f15c8.png)

* ロール
    * デフォルトで様々なロールが提供されています。新たにロールを作成する際は、既存のロールと区別するためプレフィックスを付けておくと分かりやすくなります (ポリシーも同様)
* ユーザー
    * アカウント名はコンソールから変更することはできませんが、CLIやAPI経由で変更可能です ([^6])
    * グループやアカウントにはパスという概念があり、所属を明示的に分けたい場合に使用することができます
        * デフォルトのパスは `/` です
        * パスはコンソールから変更することはできません
* アカウント設定
    * デフォルトのパスワードポリシーは弱いので、所属する組織の規則に合わせてセキュリティポリシーを強化しましょう

## VPC

* VPC
    * `172.31.0.0/16` はAWSが提供するデフォルトVPCです。デフォルトサブネットも提供されており、この中にEC2を始めとする各種リソースを配置することでサービスを公開することが可能となります
    * 1つのAWSアカウントで複数のVPCを作る場合、デフォルトVPCは使わず新たにCIDRを切った方が管理上分かりやすくなります
        * デフォルトVPCは削除することもできますが、残しておいても特に支障はありません
    * 1つのAWSアカウントで複数の環境を構築する場合、例えば下表のようにCIDRを切ると良いでしょう

        | VPC         | CIDR                     |
        | ----------- | ------------------------ |
        | development | 172.29.0.0/16  |
        | staging     | 172.28.0.0/16            |
        | production  | 172.27.0.0/16            |
    * VPCペアリングで他のネットワークと接続する場合、CIDRが重複しないようアドレスの設計に注意してください

* サブネット
    * 下表のような変換表を作成し、サブネットを算出しています
    ![5ef13212f1746800482e64a3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27454/31941c02-f8e2-bf31-3d8e-529a8660b4c0.png)
        * Class ID: 1つのサブネットに必要なホスト数で使い分けます。例えばクラスBなら251のホストを管理できます
        * Function ID: 機能ごとにサブネットを分けて管理します。クラスBであれば16の機能を持たせることができます
            * 0000: アプリケーション (public)
            * 0001: DB (private)
            * ...
        * AZ ID: アベイラビリティゾーンの識別
            * 00: 1a
            * 01: 1c
            * 10: 1d
    * VPC CIDRが `172.30.0.0/24`、最大251のアドレスを必要とするDB (public) サブネットの求め方
        1. Class ID: `01`
        2. Function ID: `0001`
        3. AZ ID: `00`、`01`、`10`
        4. 必要なサブネット
            * `01000100` (68): `172.30.68.0/24`
            * `01000101` (69): `172.30.69.0/24`
            * `01000110` (70): `172.30.70.0/24`
* エンドポイント
    * S3/DynamoDB VPCエンドポイント
        * 通常、プライベートサブネットからS3に通信するにはNAT経由でインターネットに出る必要がありますが、VPCエンドポイントを作成することで、インターネット (NAT) を経由せず通信が可能となります
            * インターネットを経由しないためセキュアな通信
            * NATを経由しないため通信量を抑えられる
            * S3データ転送量がかからない (※ただし別途VPCエンドポイント料金は発生)

## EC2

* インスタンス
    * Amazon Linux AMIを使う場合
        * Amazon Linuxは2020年12月でサポートが終了します。Amazon Linux 2を使いましょう
        * Amazon LinuxからAmazon Linux 2へのアップグレードはサポートされていません
    * インフラをコード管理している場合、最新のイメージはSystem Managerから取得することができます ([^7])
    * インスタンスを誤って削除しないよう削除保護を有効化
        * コンソールから操作する場合、対象インスタンスを選択して `アクション` から `インスタンスの設定` - `終了保護の設定` を選択。ダイアログが開いたら「はい、有効化する」を選択します
　　　　![Screen_Shot_2020-08-31_at_2_45_39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27454/25d80905-2c70-9c9b-7d3e-a24f1e6ab8db.png)
    * インスタンスの作成
        * インスタンスの詳細の設定
            * 自動割り当てパブリックIP: 有効にすることで、インスタンスにパブリックIPが付与されます。アプリケーションサーバなど、ALB配下で動くインスタンスであれば原則的に外部から直接接続することはないので無効にしておきましょう
            * 終了保護の有効化: 前述の通り、有効にしておきます
        * ストレージの追加
            * ガバナンス要件に応じてストレージを暗号化するか決めます
* ロードバランシング
    * 突発的なスパイクアクセスが見込まれる場合、数日前を目安にサポートに暖気申請が必要です
    * 暖機申請にはビジネスプランへの加入が必要です
* Elastic IP
    * デフォルトの上限値は5つです。必要に応じて忘れずに上限緩和申請を行いましょう
    * NAT Gatewayを利用している場合はNATのIPも対象となることに注意してください
* キーペア
    * AWS上でキーペアを作成する場合、秘密鍵はAWSからダウンロードする形となりセキュリティ上の懸念があります。鍵はローカルで作成した上でインポートする形を取りましょう
    * AWSでキーペアを作成した場合、パスフレーズは空になります。ローカルで作成時は必要に応じてパスフレーズを設定することができます ([^8])

## S3

* バケット名はグローバルに一意となる名前を付ける必要があります
    * バケット名には `.` (ピリオド) を含めることができますが、不要なトラブルの原因となるためお勧めしません ([^9])。代わりに `-` (ハイフン) などの記号を使いましょう
    * ドメイン名をバケット名にすると名前が重複しないのでお勧めです
        * `production-qiita-com`
        * `production-logs-qiita-com`
        * `production-assets-qiita-com`
* ガバナンス要件に応じてバケットの暗号化方針を決めましょう
* TerraformのStateファイルなど、状態を管理するファイルを保管するバケットは必ずバージョニングを有効にしておきます。万が一Stateファイルが破損した場合も状態を元に戻すことができます
* ブロックパブリックアクセス
    * インターネットから直接参照することのないバケットはブロックパブリックアクセスをすべてオンにします


[^1]: [AWSアカウントとVPC、分ける？ 分けない？: 分割パターンのメリット・デメリット](https://dev.classmethod.jp/articles/account-and-vpc-dividing-pattern/)
[^2]: [【AWS】IAMユーザで請求情報を見る](https://qiita.com/hisayuki/items/d1d13fd0172d96296dd7)
[^3]: [AWSアカウントでしかできないことをまとめてみた](https://dev.classmethod.jp/articles/only_aws_account/)
[^4]: [Lists of Terraform Providers](https://www.terraform.io/docs/providers/index.html#lists-of-terraform-providers)
[^5]: [IAM でのセキュリティのベストプラクティス](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/best-practices.html)
[^6]: [AWS CLIからIAMユーザー名を変更](https://dev.classmethod.jp/articles/aws-cli-iam-update-username/)
[^7]: [Public parameters](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/parameter-store-public-parameters.html)
[^8]: [お前らのSSH Keysの作り方は間違っている](https://qiita.com/suthio/items/2760e4cff0e185fe2db9)
[^9]: [S3でHTTPSを使いたかったらバケット名にピリオド入れちゃダメ](https://qiita.com/devneko/items/66662bb03e317a5d1221)