🙌

[AWS] Docker on EC2で激安のLaravel環境を構築する

2023/07/22に公開

Docker on EC2はまれにアリ

この記事では、僕が時々限定された状況でやる可用性を無視した激安使い捨てLaravel環境の構築を紹介します。
ほぼ僕用のメモ書きのような感じになっておりますが、参考になれば幸いです。

例えば、ローカルマシンでDockerを使って以下のような構成で開発をしていたとします。

NginxとPHP(Laravel)とMySQLという超チュートリアル的な環境です。

これをAWSにちゃんとしたアプリとしてデプロイしようとした場合、
僕だったらECS FargateでNginxとPHP(Laravel)は動かして、MySQLはAuroraを使うと思います。ECSの前段にはALB、ALBの前段にはCloudFrontを配置します。
ちなみにALBを直接エンドポイントにすることもできますが、ALBに不正なアクセスがあった場合、すでにVPC内であるというのは、精神衛生上よくないので、よりセキュリティを向上させたいというような観点からCloudFrontを最前段にして一番エッジの部分でWAFをかけることが多いです。

おそらく案件で構築するならこんな感じです。
管理と運用のオーバーヘッドを削減したいので極力マネージド系のサービスに頼ります。

ただ一つ残念なことがあります。
料金が高くなることです。
実際に案件としてお客様からお金を頂戴している場合など、ほとんどの場合では人件費の方が高いので問題ではありません。

ただ、自分用にちょっとしたサービスをデプロイしたいとか、研修用途に利用したいとかの場合、リッチすぎるのではないかと思うこともあります。
例えば、新卒研修で何かWebアプリを作ってもらう。そのアプリをちょっとだけAWS環境にデプロイして、研修関連の人だけが見れるようにする。
こういった場合には上で述べたような構成にしなくても良い場合もあるかなと思います。

管理と運用に人がさける状況
かつ
ある程度のダウンタイムが許容できる
かつ
DBに入れてるデータは最終なくなってもなんとかなる

という本当に特殊な状況においては、Docker on EC2はアリではないかという話になります。
ずいぶん安くなります。1日8時間稼働でt4g nanoとかだと月40ドルいかないんじゃないかな。

構成

実際の構成は以下のとおりです。

FigJamで書くというのを初めてやってみたんですが、慣れてなさすぎてきれいに書けませんでした...

Single AZの弊害

Single AZでEC2上にDockerを作って、そのうえでNginx、PHP、MySQL全部建てるという暴挙に出てます。
可用性はないので、AZが死んだ場合は別のAZで立て直す必要があります。
なので最低でもCloudFormationを使って管理しないとマズイことになります。
マネコンから作成してAZが死んだら絶望する気がします。

もちろんMySQLのデータは戻りませんので、案件等でこの構成はほぼありえないと思いますね。

Single AZなのになぜALBがあるのか

HTTPS通信を楽にしたいからです。

仮にALBがないとします。
その場合、まずやることとしては、EC2のセキュリティグループにCloudFrontのプレフィックスリストからのみのインバウンドアクセスを許可します。
これである程度のセキュリティは担保できます。(CloudFrontにアタッチするWAFでIPアドレスの制限をかけている想定です。)

ただCloudFrontはVPCの外にありますから、なんとかすれば第三者が通信の中身を見ることができます。ちゃんと暗号化したほうが流石に良いと思います。

ここで、CloudFrontとEC2間をHTTPS通信できるようにするには、外部で発行したSSL証明書をEC2内のNginxに紐づけるというめんどくさい作業が発生します。ACMで管理している証明書はEC2に直接紐づけられません。
Let's Encryptを使えば無料ではありますが、結構めんどくさい作業になります。

それとCloudFormationを使って構築する場合、ACMで証明書を管理できる方がはるかに楽です。

ALBを置いてしまえば、ACMで発行して管理している証明書をALBに簡単に紐づけることができます。
CloudFront<->ALB間をHTTPSにしてしまえば、もうVPCの中なのでALBとEC2はHTTPで問題ありません。

つまり、このALBはHTTPS通信をしたいがためだけに設置されておりLoad Balancerとしての機能は何も使ってません。
この構成の気持ち悪いところの一つがこれです。
また、ALBを配置する場合は2AZ以上のサブネットにまたがる必要があるのですが、シングルAZ構成のため何の役目も持たないサブネットが一つ増えます。

Auto Scalingについて

実はこの構成だとAuto Scalingがいい感じに設定できません。
なぜかというとDockerの中にDBがあるので、スケールしてDBが新しくできたり減ったりしてもDB間でデータは共有されていないからです。

じゃあ、DBだけ複数の別のEC2インスタンスにおいて常にレプリケートするようにすればよいか。
もうそれならRDSとかAuroraとか使えば良くないか(笑)ということになります。
AWSというのはよく考えられてますねー。

パブリックサブネットにEC2を置くのはいかがなものか

これはNAT Gatewayの料金を削減するためにあえてパブリックサブネットに配置しています。

ALBを配置するのであれば、サーバーはプライベートサブネットに置いたほうがセキュリティが向上します。ただ、その場合、NAT Gatewayをパブリックサブネットに置き、プライベートサブネットからインターネットへの通信をNAT Gatewayに向かうようにルートテーブルを設定します。
NAT Gatewayというのは結構お金がかかるんですよね。

ということでEC2をパブリックサブネットにおいてしまっています。
これでNAT Gatewayが不要になりますが、重大なセキュリティ的な問題が発生します。
お客様のいる案件では絶対にやらないほうがいいです。

パブリックサブネットに置くということはインターネットからEC2インスタンスにアクセスすることができることを意味します。
つまり、正しく設定をしてアクセスできないようにしないとデータの流出やハッキングされる可能性があります。
プライベートサブネットにおけば、そもそもインターネットとの通信はIN、OUTどちらもできません。

大事なことなのでもう一度、お客様のいる案件でパブリックサブネットにEC2を置くのは絶対にやらないほうがいいです。ミスったときに大事故です。

そのうえでパブリックサブネットにEC2を置く場合、EC2のセキュリティグループのインバウンドルールをALBにアタッチしたセキュリティグループからの通信のみ許可するように変更します。

ALBのセキュリティグループのインバウンドルールは、CloudFrontのプリフィックスリスト(エッジロケーションのIPリスト)からのみの通信を許可するようにします。

CloudFrontには、自分のIPアドレスからのみの通信を許可するルールを設定したWAFをアタッチします。

これでセキュリティをある程度保ったままパブリックサブネットに置くことができます。
が、一つでも間違えると大問題です。
作成後は必ずアクセスが制限できているかのチェックはしたほうが良いです。
自分も書いていてなにか間違っているのに気がついてない可能性があるので、ご注意ください。

なぜCloudFrontが必要なのか

ALBを最前段にするでもOKではあります。
ここで、CloudFrontを置いたのには、冒頭で話した「WAFはエッジでかけたいという話」ともう一つ理由があります。
それは、S3にReactとかのビルドしたファイルを置いておいてそれをOriginにするCloudFrontを置くとかになったとき、WAFが使い回せるからです。
ALBにつけるWAFは作成するリージョンをALBと同一リージョンにする必要があります。
が、CloudFrontにアタッチするWAFはバージニア北部で作成する決まりがあります。
例えば東京リージョンでALB用にWAFを作ってもそれをCloudFrontに使い回せないんですね。

WAFは割とお金がかかるため使いまわしたいです。
CloudFrontは置いているだけでは課金されず、リクエストも少なければほぼ無料みたいなものなので、WAFを流用するためにCloudFrontをわざわざ置いてます。

EC2インスタンスにはどのインスタンスタイプを使うか

個人的には、t4g一択です。
なぜ個人的にはといったかというと、自分の開発マシンがm1 macでARMアーキだからです。
Docker周りはCPUアーキテクチャによって動く動かないみたいな問題が起きることがあるので、同じアーキのインスタンスタイプをできる限り使用したいです。
ただ、t4gについては若干ハマるポイントがあるので、そちらについては別記事でまとめようと思います。スワップ領域を確保したほうがいいんですよね...

冒頭で話したような特殊な状況下である点を考慮するとt系で良いと思います。
断続的にリクエストが来てCPU使用率がt系のベースライン以上にあがるのであればt系はだめです。
自分も初心者の頃に陥ったのですが、t系が一番シンプルなインスタンスタイプだと思うと全然そんなことはなく、バーストという概念があるため若干ややこしくなっています。
リクエストが少なく断続的でもない場合はt系で問題ないと思います。

ちなみにインスタンスタイプは簡単に変更できるので、t4g microぐらいから始めても良いかなと思います。

ソースコードをどうやってもってくるか

ソースコードはCodeCommitで管理して、CodeCommitからEC2にプルしましょう。これが一番簡単です。

まとめ

本当に特殊な状況においてのみ価値のある?ナレッジの共有でした。
ちなみに、t4gのCPUのGravitonなんですけど、PHPの速度があまり出ないという問題が報告されてたりするみたいですが、この超限定された使い方でしたら特に体感できませんでした。

Discussion