様々な障害に耐えうるSSRのインフラを設計しました！

![eyecatch_2106DurableInfrastracture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218415/db985bb6-9546-6865-6f12-9a48fa2ff2a4.png)

※ 本稿は <a href="https://tech.drecom.co.jp/?_src=qiita">Tech Inside Drecom</a> に掲載された記事、「<a href="https://tech.drecom.co.jp/server-side-rendering-infra/?_src=qiita">様々な障害に耐えうるSSRのインフラを設計しました！</a>」の Qiita 出張版です。

---

#はじめに

こんにちは！enza SREチームのmendと申します！
現在enzaではサーバーサイドレンダリング(=SSR)の導入を進めております！
SSRを導入するにあたり、CloudFront + SSRのサーバー では CloudFront + S3 の構成と比べて複雑になってしまうため、様々な障害によってページが見れなくなってしまう要因が増えてしまいます。
今回はSSRを実現しつつ、様々な障害が起こってもページを見ることができるインフラを設計しましたのでご紹介させていただきます！

#SSRとは

SSRはServer Side Rendering(サーバーサイドレンダリング)の略称で、クライアント側でJavaScript等を動かしてHTMLを生成するのではなく、サーバー側でJavaScript等を動かしてHTMLを生成することを言います。厳密にはもっと様々な解釈や詳細な定義がありますが、<b>少なくとも今回の記事ではサーバー側でJavaScript等を動かしてHTMLを生成すること</b>をSSRとします。

###SSRのメリット

SSRのメリットとして、SPAでのSEO対策が簡単になるメリットがあります。
SPAでは一つのHTMLをJavaScriptで動的に変更して画面を描画しますが、検索エンジンのクローラーによってはJavaScriptを読み込まなかったり、意図した通りに読み込まれない場合があります。
このような問題に対し、SSRを使用することで検索エンジンが読み込むページをサーバーで処理されたHTMLにすることができるようになり、SPAでも細かなSEO対策ができるようになります。

#インフラから見たSSRで必要な条件

インフラ視点からSSRを実現するために必要な条件をまとめてみると、以下のようになります。

###サーバーを動かす環境を用意する

SSRを行うサーバーを動かす環境ですが今回はKubernetes環境上のPodで動かすようにします。
前回の記事[ダウンタイムなしでEC2からEKSへ移行しました！](https://tech.drecom.co.jp/ec2toeks-without-downtime/?_src=qiita)に記載したとおり、enzaではシステムの一部にEKSで構築したKubernetes環境を使用しております。
SSRのサーバーでレンダリングされたHTMLはCDN側でキャッシュされるため、通常のAPIサーバー等と比較するとアクセス数が少ないことから、マシンリソースを管理しやすいKubernetes環境のPodで動かすことにしました。
どのようなImageを使用してSSRを行うか、といったような具体的な実装に関わる内容は今回は省略します。

###CDNの設定を変える

SSR導入前の設定では、CDNであるCloudFrontのOriginはSPAの静的ファイルを置いているS3を参照しております。
enzaではCDNにAWSのサービスの一つであるCloudFrontを使用していますが、このままの設定ではSSRを行うPodを配置しているKubernetesクラスタに対してアクセスが届きません。
そのためCloudFrontに対して<b>Multi Origin</b>の設定を行い、S3, Kubernetesクラスタ両方にアクセスできるようにします。

#Multi Originとは

<b>Multi Origin</b>とはCloudFrontに対して複数のOriginを設定できる機能になります。
複数のOriginを設定できることで、1つのOriginが使用できなくなったときに予め設定しておいたもう一方のOriginにアクセスを割り振るといったことが可能になります。
さらにこちらのMulti Origin機能、なんとBehaviorにPath Patternを設定することでリクエストのPathのパターンに応じてOriginのアクセスを分けることができます。
origin1にはS3, origin2にはEC2インスタンスをそれぞれ設定したと仮定したうえで、例として以下のリクエストがあったとします。

* reqA：https://example.com/aaa
* reqB：https://example.com/bbb
* reqC：https://example.com/image.jpg

Multi Origin機能を使いBehaviorでPath Patternを設定しておくと、CloudFrontに対してreqAとreqBのようなリクエストではEC2インスタンスからレスポンスを返し、reqCのような拡張子がついたリクエストが届いた場合はS3からレスポンスを返すといった細かい設定が可能です。 こちらの機能を使用し、実際にSSRを導入する場合のインフラを設計してみました。

#インフラ設計

まずSSRを導入する前のインフラがこちらになります。
![1-ssr.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218415/bfe2d7e9-da6f-160a-5351-07f5a9f5e1fe.png)
この画像の通り現在enzaはCloudFrontを使用しており、CloudFrontのOriginとしてbucketのS3を1つ設定しており、
cloudfrontで静的webページを公開する方法としてはかなり基本的な設定となっております。
ここからSSRを行えるように大改造を行います。大改造後はこちらです。
![2-ssr.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218415/8234129e-e0b6-3b1b-9394-48cf0ec694cb.png)

SSRを実現するため、上記画像に示したように以下の内容で変更を行います。

* `/*.*`のような特定の拡張子以外がついたリクエストは既存と同様にS3から静的ファイルを返す
* `/`や`/*`のような拡張子のないリクエストと`/*.html`はSSRした結果を返すようにSSRのドメインに向ける
* CloudFrontに対してSSRのリクエストを渡すドメインとしてRoute53レコードをMulti Originに設定
* Route53レコードにALBを紐付け
* ALBのターゲットグループにEKSで構成したKubernetesクラスタのワーカーノードを指定
* ターゲットグループのポートにSSRを行うPodにアクセスするServiceのNodePortを指定

このような設計とすることで、後述する<b>障害に耐えられる構成</b>になっております。 上記のインフラを設計する際には、S3だけでは起きることがなかった障害を防ぎつつ、移行後にも高い可用性を保てるように設計を行いました。

#考えられる障害

導入後に発生すると考えられる障害と、障害による影響を防ぐための工夫について説明したいと思います。

###ALBの障害の対策

AWSでは滅多にありませんが、ALBが正常に動作しない障害が起こることがあります。
主にAZ単位で起こるものですが、万が一SSRで使用するALBに障害があるとSSRを使用するページ全てが見れなくなる可能性があります。
このような問題に対し、ALBの障害が起こったときでもページを見れるようにするためCloudFrontの<b>Origin Failover</b>を使用します。

####Origin Failover

Origin FailoverはCloudFrontに設定したOriginから任意のステータスコードが返って来たりタイムアウトした場合、もう一方のOriginでリクエストを処理するように設定する機能になります。
こちらの機能を使用し、もしALB側で問題が起こりレスポンスが異常だった場合はこれまで同様にS3から返すよう設定しておきます。

###ワーカーノードによる障害の対策

ワーカーノードの実態はEC2インスタンスになります。
EC2インスタンスはEC2側の障害発生頻度よりもAWSの障害時以外でEC2インスタンスの終了するペースが早く、インスタンスの終了がユーザーにとっての障害の原因になりやすくなってしまいます。
そのような場合はKubernetes側のDeploymentやHorizontal Pod Autoscaler, PodDisruptionBudgetを使用し、複数のPodを複数のワーカーノードで起動するようにすることでワーカーノードの障害が起こった際でも別のワーカーノードからリクエストを処理することができます。

###Podレベルの障害

KubernetesでPodを動かす場合、アプリケーション側の問題が起きたり、Podが使用するマシンリソースが割り当てられないなど様々な要因でPod自体が起動できなくなってしまう状態が考えられます。
もしKubernetes内で設定したSSRのServiceに紐付けられるPodが全て動作しない場合でServiceに対してリクエストが届いた場合はリクエストに対して502が返却されます。
このような問題が起こったとき、ただOriginを設定しただけではユーザーまで502エラーが伝わってしまうため、上記で紹介したCloudFrontの<b>Origin Failover</b>機能を使用します。最終的にCloudFrontに502エラーが返ってきた際には別のOriginにアクセスするように設定することでユーザーは変わらずS3から返されたページをみることができます。

#まとめ

SSRを行うためのインフラを構築するにあたり、CloudFrontにKubernetesを組み合わせることで様々な障害に耐えうるインフラの設計を行いました。
CloudFrontのOriginにS3を設定する組み合わせは静的サイトを公開するにあたってよくある組み合わせであり、高い可用性を持つ構成でもあります。
本来SSRを導入する際にはSSRを行うサーバーのみをOriginに設定すると思われますが、その状態ではこれまでの高い可用性が犠牲となってしまいます。しかし今回はCloudFrontが持つMulti Originの機能を利用することで、S3が持つ高い可用性をベースにSSR機能を組み合わせることができるため、ぜひSSRを導入する際には参考にしてみてください。

---

Tech Inside Drecom の最新の情報は Facebook や Twitter からお届けしています、フォローよろしくおねがいします！

* Twitter: <a href="https://twitter.com/DRECOM_TECH" target="_blank">@DRECOM_TECH</a>
* Facebook: <a href="https://www.facebook.com/tech.inside.drecom" target="_blank">tech.inside.drecom</a>

---

※ その他の Tech Inside Drecom の記事は、<a href="https://tech.drecom.co.jp/?_src=qiita">コチラ</a>からお探しいただけます！!