CloudFrontのVPCオリジンで起動タイプEC2のECSコンテナに接続できるのか

AWS

2025.1.21

Topics

概要

2024/11/20に発表されたVPCオリジンが発表されました!

これは、CloudFrontから直接プライベートサブネットにある、ALB、NLB、EC2と通信できる便利な機能です。
VPC BPAと組み合わせることでよりセキュアなwebサイトの公開が可能となります。

上記図のようにVPCオリジンを作成するとVPCオリジンのENIが作成され、そのENIに専用のセキュリティグループが割り当てられます。
※Internet Gatewayは必須です。
※Route tableのルーティングは設定不要です。

これを見ていて天啓が舞い降りました。
直接EC2と通信できるのであればEC2で動くコンテナにも接続できるのではないか?
そのEC2をECSで作成し(起動タイプEC2)、awsvpcモードを使用すれば複数タスクに内部IPを持たせ、タスクごとにVPCオリジンでルーティングできるのではないか?
タスクへのルーティング自体はALBをVPCオリジンに設定することで実現可能ですが、前述を利用することでALBをなくした構成でコスト削減をはかれるのではないか?(推奨される使い方ではないと思いますが
と考えました。

今回は、CloudFrontのVPCオリジンで起動タイプEC2のECSコンテナに接続しようと検証した内容をご紹介します。
検証に至った思考や試したことをメインでご紹介するため、細かな作業手順については割愛します。

ネットワークモードawsvpcとは

まず、ECSのネットワークモード「awsvpc」について簡単に説明します。
・タスク単位でENIが割り当てられプライベートIPが付与される(このENIはコンテナホストに割り当てられる)
・そのプライベートIPを使用してコンテナへ接続が可能
・dockerコンテナでポートマッピングは行われない

上記のような特徴があり、ENIを追加した通常のEC2と同様と考えられるため、VPCオリジンの通信対象であると考えられます。

結論

先に結論のみ。
ネットワークモードで「bridgeモード」を使用すれば接続可能です。(hostモードでも可能かと考えられます。)

ですがVPCオリジン(CloudFront)とbridgeモードを併用した場合、CloudFrontはポート80、443しか利用できない都合上、2つのコンテナまでしか振り分けられず現実的ではありません。
また、80、443でwebサイトを分けるような運用もほぼあり得ないことも非現実を助長します。

そのため、通常のEC2へ直接接続したほうが構成も簡単なためまともです。

ではなぜawsvpcモードはできないのでしょうか。
気になり検証しましたので以降に載せます。

awsvpcモードで検証

起動タイプEC2、ネットワークモードawsvpcモードのECSを構築し、CloudFrontのVPCオリジンを使用して、ウェブ公開できるか検証しました。

まず、ECSコンソールより起動タイプEC2、ネットワークモードawsvpcのコンテナホスト(EC2)「ECS Instance – spam-ecs-cluster-ec2-awsvpc」を作成しました。

「10.0.149.0」がコンテナホスト(EC2)のデフォルトENIであり、「10.0.155.135」がコンテナのENIでありコンテナホスト(EC2)の追加ENIとなります。
※共にhttpを許可するセキュリティグループも割り当てています。

次にCloudFront コンソールでVPCオリジンを作成しました。
オリジンARNには作成した「ECS Instance – spam-ecs-cluster-ec2-awsvpc」のARNを設定しました。

最後にCloudFront ディストリビューションを作成し、「ECS Instance – spam-ecs-cluster-ec2-awsvpc」の追加ENIの内部DNSをオリジンドメインに設定しました。

作成したディストリビューションのドメインが以下です。

作成したディストリビューションのドメインに対し、アクセスしてみました。

$ curl http://dicpzyja7za07.cloudfront.net
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD><BODY>
<H1>504 Gateway Timeout ERROR</H1>
<H2>The request could not be satisfied.</H2>
<HR noshade size="1px">
We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
<BR clear="all">
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
<BR clear="all">
<HR noshade size="1px">
<PRE>
Generated by cloudfront (CloudFront) HTTP3 Server
Request ID: ppG_oL70crUFR19-plpP0a03aQDfteoDSl1JQ9J0PRYCCaOjDDJc7w&#x3D;&#x3D;
</PRE>
<ADDRESS>
</ADDRESS>
</BODY></HTML>
$ 

「504 Bad Gateway」となり接続できません。

以下2つの可能性があるため、同一VPC内の別プライベートサブネットに設置した別EC2からcurlを実行しました。
・追加ENIの内部DNSが使用できない可能性
・コンテナのhttpdが正常稼働していない可能性(docker psでコンテナ自体が稼働していることは確認済)

[root@ip-10-0-136-113 ~]# curl http://ip-10-0-155-135.ap-northeast-1.compute.internal
Hello World!
[root@ip-10-0-136-113 ~]#
[root@ip-10-0-136-113 ~]# curl http://10.0.155.135
Hello World!
[root@ip-10-0-136-113 ~]#

表示できました。コンテナ自体に異常はなさそうです。
であれば他に問題があると思われます。

しかし、VPCオリジン作成で作成されたENIは対象インスタンスの同サブネットに作成されているため、同一VPC内の別プライベートサブネットに設置した別EC2からのアクセスと似た状態であり、要件的には問題ないはずです。
なぜつながらないのか。謎は深まる。

bridgeモードで検証

結果のみとなりますが、VPCオリジンがbridgeモードのディストリビューションへの接続は以下のように成功します。

$ curl http://d2002kj8xx0cs8.cloudfront.net
Hello World!
$ 

最後まで読んでいただければつながった理由はわかりますので読んでいただけると幸いです。

現状

なぜつながらないのかわかりませんが以下が結果として残りました。
・awsvpcモードのコンテナには接続できない
・bridgeモードのコンテナには接続できる
・この二つの違いはENIの追加があるかないか
・VPCオリジンではARNを設定する

VPCオリジンの仕様が怪しいのでしょうか。

普通のEC2にENIを割り当てて検証

VPCオリジンにてARNを指定するためそのあたりの兼ね合いなのかと考え、普通のEC2にENIを追加して検証してみることにしました。

プライベートサブネットに「spam-ec2-int1」を作成し、追加のENIを1つ割り当てました。
・デフォルトの内部IPは「10.0.136.113」
・追加の内部IPは「10.0.130.73」

「spam-ec2-int1」でhttpdを起動し、特定のIPのみ接続できるようにVirtual Hostを作成しました。
これで、「10.0.136.113」「10.0.130.73」からの80番アクセスのみに絞れるはずです。

#Listen 80
Listen 10.0.136.113:80
Listen 10.0.130.73:80

<VirtualHost 10.0.136.113:80>
    DocumentRoot "/var/www/html/site1"
    ServerName site1.example.com
    <Directory "/var/www/html/site1">
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

<VirtualHost 10.0.130.73:80>
    DocumentRoot "/var/www/html/site2"
    ServerName site2.example.com
    <Directory "/var/www/html/site2">
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

表示するページは以下のようにデフォルトENIと追加ENIで見分けられるようにしています。

[root@ip-10-0-136-113 conf]# cat /var/www/html/site1/index.html
Welcome to Site 1 default ENI
[root@ip-10-0-136-113 conf]#
[root@ip-10-0-136-113 conf]# cat /var/www/html/site2/index.html
Welcome to Site 2 add ENI
[root@ip-10-0-136-113 conf]#

VPCオリジンとディストリビューションも先ほどと同様に作成し、オリジンドメインには追加ENIの内部DNSを設定しました。

一旦これで接続確認をしてみます。

$ curl http://dql04tejxdej5.cloudfront.net/
Welcome to Site 1 default ENI
$ 

ディストリビューションにて追加ENIのドメイン名を指定していますが、デフォルトのENIのページが開かれました。

何としても追加ENIに行ってほしいので、Virtual Hostの設定でデフォルトENIを閉じました。

#Listen 10.0.136.113:80
Listen 10.0.130.73:80

#<VirtualHost 10.0.136.113:80>
#    DocumentRoot "/var/www/html/site1"
#    ServerName site1.example.com
#    <Directory "/var/www/html/site1">
#        AllowOverride All
#        Require all granted
#    </Directory>
#</VirtualHost>

<VirtualHost 10.0.130.73:80>
    DocumentRoot "/var/www/html/site2"
    ServerName site2.example.com
    <Directory "/var/www/html/site2">
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

httpdを再起動し確認しました。

$ curl http://dql04tejxdej5.cloudfront.net/
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD><BODY>
<H1>504 Gateway Timeout ERROR</H1>
<H2>The request could not be satisfied.</H2>
<HR noshade size="1px">
We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
<BR clear="all">
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
<BR clear="all">
<HR noshade size="1px">
<PRE>
Generated by cloudfront (CloudFront) HTTP3 Server
Request ID: HuZmp5fzOGchzPOnCaVTDFOtHI9glwBL30Eu6-ndZJE6t9rFVQySDw&#x3D;&#x3D;
</PRE>
<ADDRESS>
</ADDRESS>
</BODY></HTML>
$ 

「504 Bad Gateway」となり接続できません。
awsvpcモードで検証していた状態と同じになっていると思われます。
これは・・・

最後に、Virtual HostでデフォルトENIを開放し、追加ENIを閉じました。
ディストリビューションのオリジンドメインの設定は変わらず追加ENIの内部ドメインです

#Listen 80
Listen 10.0.136.113:80
#Listen 10.0.130.73:80

<VirtualHost 10.0.136.113:80>
    DocumentRoot "/var/www/html/site1"
    ServerName site1.example.com
    <Directory "/var/www/html/site1">
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

#<VirtualHost 10.0.130.73:80>
#    DocumentRoot "/var/www/html/site2"
#    ServerName site2.example.com
#    <Directory "/var/www/html/site2">
#        AllowOverride All
#        Require all granted
#    </Directory>
#</VirtualHost>

再起動して確認しました。

$ curl http://dql04tejxdej5.cloudfront.net/
Welcome to Site 1 default ENI
$ 

無事、ディストリビューションのオリジンドメインに設定した追加ENIの内部DNSとは異なるデフォルトENIのページが開けました。

私の夢は儚く散りました。

結論(2回目)

EC2に直接つなぐ場合、VPCオリジンの仕様でディストリビューションのオリジンドメインにかかわらずデフォルトENIに到達してしまう可能性が高いかと思われます。
VPCオリジンのARN指定周りの問題なのかなと推察しています。
※予想ですが、ARNからインスタンス情報を取得し、ENIを指定していて1番目のENIしか選択しないのような

その為、awsvpcモードではコンテナホスト(EC2)のデフォルトENIに対し接続してしまい、コンテナホストは80番ポートを使用していないため「504 Bad Gateway」となっていたのだと思われます。(コンテナホストにhttpd等入れれば別サイトにはなりますが動きます。)
反対に、bridgeモードの場合、コンテナホスト(EC2)のデフォルトENIに通信した後、ポートマッピングによってコンテナへ到達するため利用できるのだと思われます。

雑記

そもそも、構想の時点で現実的な構成ではないため利用用途はないです。
気になってしまったため調査しましたが、VPCオリジンだけでなくECSの勉強となったためとてもよかったです。

機能追加されて、追加ENIにも到達できるようになったらうれしいです。
さらに、Fargateでも使えたら割と実用的になるのではないかとも思いました。

テックブログ新着情報のほか、AWSやGoogle Cloudに関するお役立ち情報を配信中!

Recommends

こちらもおすすめ

Special Topics

注目記事はこちら