オンプレエンジニアがAWSを触って思ったのと違うと感じたこと

# はじめに
この仕事を始めた当初(約20年前)はオンプレミスという言葉がありませんでした。いや厳密には私の周りではパブリッククラウドとオンプレミスを分けて話す人はおらず、インフラ構築といえば今でいうオンプレミスが中心でした(世の中的にはパブリッククラウドがサービスとして存在していました)。オンプレミスみたいに新しい概念が出てきた時にそれまでの概念を説明するためにできる言葉を**レトロニム**というそうです。
私が本格的にパブリッククラウドの仕事をし始めたのは約3年前でAWSでした。研修ではAzureを先に触れていたのと、[この本](https://www.amazon.co.jp/dp/B01KUN5LI2 "Cloud First Architecture 設計ガイド（日経BP Next ICT選書）")を読んでいたという知識があった程度です。

ここではずっとオンプレミスのインフラ構築をしていた私がAWSに触れて最初に戸惑ったことを記事したいと思います。また、戸惑いましたということだけ書いても学びがないため**対応したこと**も併せて記載します。AWSに慣れている人からすれば常識ですがお付き合いください。

# 本編
## EC2(仮想マシン)を終了するとEC2が消える
有名なやつですね。シャットダウン相当だと思いきや実はEC2を**削除する命令**でしたというものです。

### 対応したこと
インスタンスを終了、ではなく[**インスタンスを停止**](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/Stop_Start.html)をしてください。
この間違いを本番環境でやらかすと大変なインシデントになります。そこで**終了を保護**フラグをつけることもできます。
もともとパブリッククラウドは1つの仮想マシンを大切にするのではなく同じ機能を持った別のマシンに置き換えても良いという思想があります。この思想の違いをペットとキャトル(家畜)に例えられます。従来の仮想マシンを大事に育てる考え方をペットに喩える。それに対してパブリッククラウドの仮想マシンは家畜のように扱うという考え方です。某アニメの「私が死んでも代わりはいるもの」と同じ考え方です。ずっとオンプレミスでサーバを構築していた人ほどこの考え方には馴染めないようです。
コンテナやオートスケーリングはまさにこの考え方に則っており、当該コンテナ/仮想マシンの障害発生後に復活をしてくるコンテナ/仮想マシンは同じものでなく同じ機能を持った**他人が起動**をしてきます。元々のマシンが持っていたデータや記憶はすべて捨て去ります。そのため、アプリケーションは**ステートレス**な設計が求められます。ステートフルなアプリケーションでどうしてもそれまでやり取りしていたセッションを引き継がせたい場合、Databaseに格納するかEFS(Elastic File System)に記録しておき、新しいコンテナや仮想マシンからセッション継続に必要なデータを呼び出します。

## サーバのNIC(ネットワークインタフェース)は基本1つ
ミッションクリティカルなオンプレミスでサーバを構築するしたことがある方は共感いただけると思いますが、サーバのNICは業務用と運用管理用(監視やジョブ関連通信やSSH接続など業務に直接関係ない通信を分ける)に分けることが多いです。ですがAWSをはじめ多くのパブリッククラウドは特別な理由がない限りは各仮想マシンのNICは1つです。もちろんENI(Elastic Network Interface)として追加ができますがそのまま使うことが多いです。
そこでオンプレミスでずっとサーバやネットワークを構築していた私たちとしてははたと思うわけです。**そもそもなぜ業務用サブネットと運用管理サブネットに分ける必要があるのか**本質的な理由の説明に迫られます。私の個人的な見解としては以下と定義しています。
- セキュリティ境界の分離のため
- ネットワーク帯域リソースの分離のため

前者のセキュリティ境界の分離はメンテナンスなどで業務通信に影響を極力与えたくないという思想から発生したものと推測します。後者の帯域は過去のネットワーク帯域が貧弱だった頃の名残であると推測します。その証拠に帯域が増えた昨今は物理的にはNICやサーバ接続用のネットワーク機器を業務用と運用管理用とで一緒にしてタグVLANで分けるという実装も増えてきました。

### 対応したこと
基本的にはNICを分けないというのが基本方針となります。理由としては、以下が挙げられます。
- SG(Security Group)などのマイクロセグメンテーションを実現する機能で通信単位のセキュリティが担保されること
- 帯域がほとんどの場合で問題にならないため
 
＊分散ファイアウォールによるマイクロセグメンテーションは通信をPoint to Pointに分離することできる。SGをEC2に適用することで同等の効果が得られる。
＊インスタンスタイプごと異なるがほとんどの場合が10Gbpsの帯域を超える。

NICを分けることに本質的な目的がない限りNICは用途で分けるもの**考え方自体を更新**しましょう。運用負荷やコストを減らせるというパブリッククラウドのメリットを享受しやすいです。NICが増えると仮想マシン内のルーティングテーブルの管理や把握が必要になり設計や運用コストに跳ねてきます。
あえてENIでNICを追加するシチュエーションの例として挙げられるのはインライン型のセキュリティアプライアンスを導入するときです。同アプライアンス内でNATさせる場合複数サブネットにNICを持たせます。入力用のNICと出力用のNICを分ける必要があるため、ENIによるNICの追加を行います。

## サーバごとにルーティングテーブルを書かない
先の話と関連しますが、AWSでは仮想マシンごとにルーティングテーブルをカスタマイズしません。設計時にも意識しません。理由は明快でNICが1つしかないからと、サブネットに所属するゲートウェイが1つしかないからです。そのゲートウェイに自身が所属しているサブネット以外の宛先となっているすべてのパケットを投げれば良いわけです。そのため各仮想マシンのルーティングテーブルには標準で用意されているゲートウェイアドレス＊が、デフォルトでネクストホップとして記述されています。

＊サブネットを構成するとネットワークアドレスの次のアドレスがゲートウェイアドレスとして予約されます。その次のアドレスはDNSリゾルバ用のアドレスです。将来用も含めて**5つのアドレスが予約**されます。

https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/subnet-sizing.html

### 対応したこと
結論だけ言うと**サブネットのルーティングテーブルで制御**します。なお、同一VPC内であれば異なるサブネット間のルーティングは自動的に追加されています。VPCピアリング、Transit Gatewayによる異なるVPC間通信、インターネット向け通信やAmazon Direct Connect(DX)などでAWS外の通信をさせたい場合にルールを追記します。
サーバのルーティング管理に苦慮していたOSエンジニアやネットワークエンジニアにとっては朗報ではないでしょうか。

## EC2をただ2台以上並べても可用性対策にならない
これはクラスタウェアの有無の話ではありません。正確には同一AZ(Availability Zone)に可用性対策のつもりで複数EC2を並べても**可用性が保証されない**ということです。
2つ以上のAZにオブジェクトを配置することを**マルチAZ**と呼びますが、可用性を担保するためにはAZごとに配置しなさいというものです。AZごとにどの物理サーバに配置されるのかユーザでは指定できないため、同一AZに複数台EC2を作成した際に同一の物理サーバに配置されてしまうことがあります。同物理サーバに障害が起こった場合、いずれのEC2が障害に巻き込まれてしまい結果、可用性対策になりません。

### 対応したこと
先述したとおり、マルチAZ構成にしましょう。また、マルチAZにおけるAZ間の通信は確実にDCを超える通信となるためレイテンシが多少大きくなります。マルチAZを想定した(異なるAZ間)通信になる箇所については同一AZ通信と比較して**レイテンシが増大**するため一定の考慮が必要になる点について覚えておきたいものです(50-100msが200-900ms程度に増加する)。
また、コストを抑えたい場合はあえてEC2を単一AZかつ1台だけ配置するという選択も取れます。AWSにはAuto Recovery機能があり、CloudWatch Alarmと組み合わせることで自動復旧を実装することができます。ダウンしてから復旧するまでの時間が待てるなら同機能任せにすると言うのも手です。
余談ですがAZイコールDC(データセンタ)ではなく、AZはデータセンタの集まりです。このDC間通信はユーザが意図して避けることは難しいため、AWSとしては[**クラスタープレイスメントグループ機能**](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/placement-strategies.html)でEC2間通信の低レイテンシを実現することも出来ます。私は使ったことがありませんが選択肢の一つとして覚えておくとよいかもしれません。

## ALBのプライベートアドレスが固定されない
AWSが提供する負荷分散装置のサービスとしてELB(Elastic Load Balancer)が提供されている。ELBは以下の3種類が提供されている。
- CLB(Classic Load Balancer) ＊
- ALB(Application Load Balancer)
- NLB(Network Load Balancer)

＊ほぼ使われない。後方互換やアプリケーションクッキーを利用したいなど特定用途で利用されることがある。

オンプレミスでよく利用されるL7(Layer7: HTTP/HTTPSを解釈できる)用の負荷分散装置に相当するものはALBであり、何も考えずに利用しようとすると以下の制約に困惑します。
- リスナで**固定のプライベートIPアドレス**が利用できない
- リスナのIPアドレスが**サブネットごと最大8個**にスケールされる

まず前提として(特に枯れた)オンプレミスシステムではIPアドレスを持つインタフェース間の通信はhostsを利用した名前解決をすることが多いです。つまりいずれのインタフェースに付与されるIPアドレスは**スタティック(静的)なアドレス**が期待されます。これが動的に変更されるアドレスですと毎回hostsを書き換えねばらなず現実的な実装と言えません。hostsを利用した名前解決にはIPアドレスの固定が必要不可欠です。同じようなノリでALBを構成してみるとプライベートIPアドレスを固定する設定が無いことに気づきます。
これには理由があって2つ目の「リスナのIPアドレスが8個にスケール」に起因します。ALBは負荷に併せて自動的にスケールするようになっており、スケール時にリスナアドレスが各サブネットごとに最大で8つ生成されることになります。よってALBが所属するサブネット設計としては同アドレスの個数(最大8個)が収まるサブネット範囲の設計が必要になります。
以下が参考になります。
特に2つ目のリンクは挙動の理解として非常に興味深いです。

https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/application-load-balancers.html

https://qiita.com/hidekko/items/19871f8fd14304c13e84

### 対応したこと
ALBをターゲットとしたNLBを構成します(2021年09月から利用可能になった)。(プライベートアドレス利用の)NLBのリスナはサブネットごとに1つだけ付与され、ALBのような(負荷増加時などによる)スケール時にリスナアドレスが増殖するという現象が発生しません。よってこのリスナアドレスは**任意の固定アドレスが付与可能**です。こうして接続元からは固定されたプライベートアドレスをめがけてHTTP(S)接続を確立することが出来ます。
しかしこれもマルチAZ構成となるとサブネットごとに払い出されたリスナアドレスの非障害AZ側のリスナアドレスにつなぎ直すということが必要になってしまいます。

これを回避するには、クライアントからAZごとに接続アドレスを変更する仕組みを入れるか、クライアントから名前解決を行えるようにしてLambdaになどで接続先ホスト名とIPアドレスを切り替える仕組みを導入しましょう。接続元サーバやクライアントがオンプレミスの場合、接続先ホスト名切り替えのために同接続元から**Route53上のDNSコンテンツを参照**させる必要があります。プライベートな接続の場合はAmazon Route53 Resolver Inbound Endpointを利用します。逆向き(AWSからオンプレミスの名前解決をする)の場合はOutbound Endpointを利用します。
以下のサイトが参考になります。

https://blog.serverworks.co.jp/route-53-resolver-inbound-endpoint

## ALBでSorryページに飛ばない
F5 BIG-IPなどのロードバランサを利用していると同機器にSorryサーバとしての振る舞いをさせるという実装をすることがあります。iRulesというプログラマブルな機能があり、特定条件になると用意したコンテンツを表示するということができます。具体的にはBIG-IP上に定義したプールメンバが誰もいなくなった(振り分け先のアプリケーションサーバが全滅)際に、BIG-IP内に用意したコンテンツを表示させ、同コンテンツには「このページは現在利用できません」という旨のハイパーテキストを用意しておけばよいわけです。Sorryサーバを別途用意する必要がないためこの機能は重宝されます。
ELBにもiRulesに相当する機能([**リスナールール**](https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/listener-update-rules.html))があり、一見同じような実装ができそうですが、プールメンバが誰もいなくなった状態(ELBではターゲットグループのすべてのインスタンスがUnhealthyになる状態)になっても**振り分けを空振りし続ける**という挙動を取るという性質に邪魔をされてしまいます。
詳しくは以下をご覧ください。

https://dev.classmethod.jp/articles/tsnote-alb-targetgroup-unhealthy-002/

### 対応したこと
結論だけ申し上げると**リスナルールの優先度設定**で同じような実装が可能です。通常利用のターゲットグループの優先度を上げておき、同ターゲットグループの障害時は次の優先度に設定しているSorryページを表示させます。結果的にはBIG-IPのiRulesと同様の設定で実装できますが、ターゲットグループのインスタンスがすべて障害になった時の不思議な挙動をお知らせするために話をあげてみました。
本実装については以下が参考になります。

https://blog.serverworks.co.jp/2022/07/21/165439

## クラスタウェアで仮想IPアドレスと共有ディスクが使えない
この記事をご覧になっている方はミドルウェアの可用性のためにクラスタウェアを導入されたことがあるのではないでしょうか。可用性対策の中でも多くの場合クラスタウェアを導入する際はHA(High Availability)構成を期待します。2台以上のサーバで1つ以上のリソースを共有し、いずれか一つのノードが落ちてもリソースの稼働ノードの切り替え(フェイルオーバ)によってサービスの継続を実現します。共有されるリソースとして主に以下が挙げられます。
- IPアドレス
- DISK
- プロセス・サービス・アプリケーション

IPアドレスはクラスタウェア製品によって仮想IP、フローティングIP、サービスIPなどと呼称され、特徴としてはクラスタグループ内のすべてのノードで共有されます。
サービスが実際に提供されるノード上でのみこのアドレスが付与され、同ノードに障害が発生すると、待機ノードに同アドレスが引き継がれることで接続元ノードからサービス提供ノードが抽象化されます。

DISKについては以下の方式があります。
- DISKレプリケーション方式
- 共有DISK方式

前者はDISKが書き込まれるマスタDISKからそれ以外のノードに同じ書き込みがコピーされます。コピーは製品が自動的にブロックレベルで行ってくれ、ユーザは意識しません。この機能はクラスタウェアの機能というよりディスクをレプリケーションしてくれるオプション製品で実現します。考慮する点があるとするなら、書き込み遅延によるデータの損失です。レプリケーション先のDISKに書き込みが完了する前にフェイルオーバが発生すると、そのデータは失われてしまいます。それを防ぐ完全同期モードもありますが、この方式はレプリケーション先の書き込みを待つ挙動になるため**DISK IO性能**に影響があります。
共有DISK方式は1つのDISK(パーティション、ファイルシステム)を複数のノードで共有します。通常のファイルシステムは複数ノードからの同時書き込みには対応しないため排他処理が必要になりますが、レプリケーション方式のようなデータの損失は発生しません。専用ストレージ装置が導入できる高級なシステムはこの方式を採用することが多いです。共有DISK方式の派生としてNFS(Network File System)やCIFS(Common Internet File System)を利用したものがあります。こちらは複数ノードからの書き込みをサポートしており排他処理の考慮は不要なものの、 IOパフォーマンス(ネットワーク層を挟むことによるオーバヘッドやファイルロック機構によるオーバヘッドが影響)や障害発生時のエラーハンドリングに難があり、性能要件の高いシステムでは採用されていません。

プロセス・サービスについては起動と停止を制御します。サービスを提供したいノードでは起動を実行し、それ以外のノードでは停止を実行します。依存関係として先のIPアドレスやDISKが提供されていることが条件になります。

上記のうち、**仮想IPアドレスと共有DISKがAWSでは利用できません**。理由としては以下です。
- IPアドレス: サブネットがAZによって異なるため、共有できるIPアドレスが存在しない
- 共有DISK: 複数ノードで利用できるブロックストレージが提供されない＊

＊[Amazon EBSマルチアタッチ](https://docs.aws.amazon.com/ja_jp/ebs/latest/userguide/ebs-volumes-multi.html)があるが、同一AZに限る。先述した理由があるため、クラスタはマルチAZで構成することがセオリーである。

### 対応したこと
以下としました。
- IPアドレス: IPアドレスで待ち受けない、Route53フェイルオーバー機能と連携する
- DISK: レプリケーションを利用する。 IO要件に合わせて同期機能を使う。同機能が使えない場合は、データ損失時に備えた非機能設計を行う

前者はNLBによるIPアドレス固定と近い実装です。CloudWatch Alarmで対象ノードの障害を検知し、Route53のエントリを更新し、接続先ノードを透過的に切り替えます。
後者は性能を重視した場合データの損失の可能性は免れませんが、業務機能や運用で対処します。

## 異なるアカウント間でAZ名と実体が違う
AZを指定する際に1aやら1cやら設定をされたと思いますが、アカウントが異なるとAZの実体が異なりますという話です。これだけだと何のことだと思いますが、例えば東京リージョンですと以下のAZが指定できます。
- ap-northeast-1a
- ap-northeast-1c
- ap-northeast-1d
＊1bは現在欠番になっています。
このAZの実体が実はアカウントごとに異なっています。理由としては単一AZを指定する場合、1aに偏ってしまうことが容易に想像できるためです。よって、AZ名とAZの実体をアカウントごとに入れ替えることで各AZの利用を無意識に分散させることが出来ます。

### 対応したこと
AZ IDで指定しましょう。AZ IDはリージョンで一意ものになるため、異なるアカウント間でも実体を違えることはありません。**レイテンシ事由などで異なるAZ間で極力通信させたくない**場合は意識して設計しましょう。

## アカウントとロールとユーザ
AWSには各種サービスの権限管理機能としてIAM(Identity and Access Management)が用意されています。IAMは大きく以下に分かれています。
- IAMユーザ
- IAMグループ
- IAMロール
- IAMポリシ

IAMユーザはその名の通り個のユーザを識別します。グループもシンプルでユーザをまとめたものです。IAMポリシについても権限のルールを定義したものだと直感的にわかると思います。IAMポリシについては後述します。
くせ者なのがIAMロールで、これがポリシをまとめ上げたものと思いきやそうではなく、AWSの各オブジェクトなどに**一時的に割り当てる権限**という扱いになります。
IAMポリシは以下に分かれます。
- アイデンティティベース
- リソースベース

更にAWSにはアカウントという言葉も登場します。AWS未経験者と会話するとユーザとごちゃごちゃになる概念の1つです。AWSでは一つのテナント(リソースの境界)ごとにアカウントという単位で契約をします。このアカウントの中にIAMユーザをつくっていきます。IAMユーザ=アカウントとはならないのです。＊

＊アカウントの中に**rootアカウントというユーザの親玉**みたいのがいて、これがまた混乱の元となっている。

また、中規模以上のAWS基盤ではアカウントごとに役割を分けたマルチアカウント構成がベストプラクティスとして推奨されます。[**AWS Organizations**](https://docs.aws.amazon.com/ja_jp/organizations/latest/userguide/orgs_introduction.html)というサービスで複数アカウントを効率的に管理します。このサービスの説明だけで1つの記事になるので、詳細は別途記事にします。ユーザ管理用のアカウントにだけIAMユーザを作成し、他アカウントのリソースにはIAMユーザで権限委譲をするAssumeRoleによるアカウント切替を行い1つのユーザから複数のアカウント上のリソースの操作を実現します。

### 対応したこと
これはもうIAMロールとは何かを覚えました。基本的にはリソースに権限を与えるものと覚えればOKです(もちろんユーザ、アプリケーション、サービスにも付与できます)。また、クロスアカウントで権限を与える場合に、アクセスキーIDやシークレットアクセスキーを利用しない安全な方法もこのIAMロールで実現します。一時的な付与ということも出来ます。
アクセスキーを利用しない安全な権限付与については以下が詳しいです。

https://zenn.dev/alivelimb/articles/20220503-aws-accesskey-sec

# 番外編
ここまではオンプレミスエンジニアから見たAWSの不思議なことを書いてきましたが、AWSを少し覚えてきたあたりで躓いた内容を書いていきます。

## EC2のアドレスを固定と考えるか可変と考えるか
実はEC2に付与したプライベートIPアドレスは終了をしない限り、IPアドレスは固定になります。バックアップからのリカバリ時の再作成時もIPアドレスを指定可能であり、オンプレミスのようなIPアドレス管理はやろうと思ってできなくはないです。
ただ、**以下の理由で私は推奨しません**。
- EC2への接続はIPアドレスやhostsで接続せず、DNSによる名前解決を行うためIPアドレスを運用上意識しないこと
- IPアドレスの管理は設計や運用の手間を生み、コストに跳ねるため
- SGなどの通信制御も原則IPアドレスを指定しないため
- AWS都合でEC2に障害が発生した場合、タイミングによって(当該ENIに付与された)同アドレスを一時的に取り返せない可能性を排除できないため

## AZは結局いくつ使えばいいのか
結論だけ言いますと、**3つ**です。繰り返しますが2つではなく3つです。
理由は以下です。
- サブネットアドレスを予約しておくため
- AZ障害時にALBを使用不能にしないため

他にも色々あると思いますが、私が考える理由は**上記2つ**です。
前者は、後からアドレスを確保しようとしてもVPCに定義したアドレス帯が枯渇すると身動きが取れない憂き目に遭います。CIDRの追加で一応対応可能ですがルーティング追加などの管理の煩雑さからお勧めできません。
パブリックサブネット、プライベートサブネット(実際の運用ではさらに細分化されるでしょう)単位で3AZ分のサブネットアドレスを確保しましょう。
後者は、**最低でも2AZ以上に所属すること**というALBの特性によるものです。通常運用時は2AZに所属させていればいいですが、AZ大規模障害時でAZそのものが利用できなくなった際、1AZしか有効にならずALBが使用不要になったという事例があります。
**以下のサイトが非常に参考**になりますので目を通しておくことをお勧めします。数年前に発生した今もAWSを利用する上で語り草になっているAZ障害の記事です。マルチAZにしておけば万全なのか? ということに対して考えさせられる内容となっています

https://www.itmedia.co.jp/news/articles/1908/28/news127.html

# まとめ
いかがでしたでしょうか。
かなり長文になってしまいましたが、私が遭遇したAWSの実装で困惑したことを思い出せる限り挙げてみました。また何か思い出したら第2弾を書いてみたいと思います。
ここで挙げた事例や対応した内容が少しでも参考になれば幸いです。

以上