【AWS_ECS】ブラウザからWEBサーバーへのアクセスとコンテンツ取得

### 第1章: Webアクセスの基本フロー ～すべてはブラウザから始まる～

まず、あなたがブラウザで `https://www.example.com/path/to/page.html` というURLにアクセスしようとした瞬間から物語は始まります。この一連の流れをステップごとに見ていきましょう。

#### 1.1. URLの解析

ブラウザはまず、入力されたURLを解析し、以下の要素に分解します。

*   **スキーム (Scheme):** `https` (通信プロトコルを指定。ここではHTTPS、つまり暗号化されたHTTP通信)
*   **ホスト名 (Host):** `www.example.com` (アクセス先のサーバーを特定するための名前)
*   **パス (Path):** `/path/to/page.html` (サーバー上で取得したいリソース (ファイル) の場所を示す)
*   **ポート (Port):** (省略されている場合、スキームによってデフォルト値が決まる。`http`なら80、`https`なら443)
*   **クエリパラメータ (Query Parameters):** (URLの `?` 以降、例: `?key=value&lang=ja`) サーバーに追加情報を渡すために使われる。
*   **フラグメント (Fragment):** (URLの `#` 以降、例: `#section1`) ページ内の特定箇所を示す。これはサーバーには送信されず、ブラウザがページ読み込み後に使用する。

#### 1.2. DNS (Domain Name System) による名前解決

コンピュータは `www.example.com` という名前を直接理解できません。通信するには、IPアドレス (例: `192.0.2.1`) という数値の住所が必要です。この名前 (ドメイン名) からIPアドレスを調べる仕組みがDNSです。

1.  **ブラウザ/OSのキャッシュ確認:** まず、過去にアクセスしたことがあれば、その時のIPアドレスがキャッシュ (一時保存) されていないか確認します。あればそれを使います。
2.  **リゾルバ (Resolver) への問い合わせ:** キャッシュになければ、PCやネットワークに設定されているDNSリゾルバ (通常はプロバイダや社内ネットワーク、またはGoogle Public DNS (`8.8.8.8`) など) に問い合わせます。「`www.example.com` のIPアドレス教えて？」
3.  **再帰的な問い合わせ:** リゾルバは、世界のDNSサーバーの階層構造を辿ってIPアドレスを探します。
    *   **ルートDNSサーバー:** まず、最上位のルートサーバーに「`.com` ドメインはどのサーバーが管理してる？」と聞きます。
    *   **TLD (Top Level Domain) DNSサーバー:** ルートから教えられた `.com` を管理するTLDサーバーに「`example.com` ドメインはどのサーバーが管理してる？」と聞きます。
    *   **権威DNSサーバー (Authoritative DNS Server):** TLDから教えられた `example.com` を管理する権威DNSサーバーに「`www.example.com` のIPアドレスは？」と最終的に問い合わせます。この権威DNSサーバーに、ドメインとIPアドレスの対応情報 (DNSレコード) が登録されています。
4.  **IPアドレスの取得とキャッシュ:** 権威DNSサーバーからIPアドレス (`192.0.2.1` など) が返され、リゾルバ経由でブラウザに届きます。リゾルバやブラウザ/OSはこの情報を一定期間キャッシュします。

**AWSにおけるDNS:** AWSでは、**Route 53** というDNSサービスがこの「権威DNSサーバー」の役割を担います。ここでドメイン名と、後述するロードバランサー (ELB) のIPアドレス (実際にはELBのDNS名) を紐付けます。

#### 1.3. TCP/IP 通信の確立

IPアドレスがわかったら、いよいよサーバーとの通信を開始します。Webで標準的に使われるTCP/IPプロトコル群のうち、TCP (Transmission Control Protocol) を使って、ブラウザ (クライアント) とWebサーバーの間で信頼性のあるコネクションを確立します。

1.  **TCP 3ウェイハンドシェイク:**
    *   クライアント → サーバー: 「接続したいんだけどいい？ (SYN)」
    *   サーバー → クライアント: 「いいよ！そっちも接続準備OK？ (SYN/ACK)」
    *   クライアント → サーバー: 「OK！接続確立！ (ACK)」
    この3回のやり取りで、データの送受信が可能な状態になります。HTTPSの場合は、この後さらにTLS/SSLハンドシェイクが行われ、通信が暗号化されます。

#### 1.4. HTTPリクエストの送信

コネクションが確立したら、ブラウザはWebサーバーに対して「この情報が欲しい」というHTTPリクエストメッセージを送信します。GETリクエストの例を見てみましょう。

```http
GET /path/to/page.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Language: ja,en-US;q=0.9,en;q=0.8
Connection: keep-alive
Upgrade-Insecure-Requests: 1
```

*   **リクエストライン:** `GET /path/to/page.html HTTP/1.1`
    *   `GET`: HTTPメソッド。サーバーからリソースを取得したい場合に使う。他にフォーム送信で使われる `POST` などがある。
    *   `/path/to/page.html`: 取得したいリソースのパス。URLのパス部分。
    *   `HTTP/1.1`: 使用するHTTPプロトコルのバージョン。
*   **ヘッダー:**
    *   `Host: www.example.com`: アクセス先のホスト名。これは非常に重要で、1台のサーバーで複数のWebサイトを運用 (仮想ホスト) するために必須。後述する**ホストベースルーティング**の鍵。
    *   `User-Agent`: ブラウザの種類やバージョン情報。サーバーはこれを見て、ブラウザごとに最適な表示を返すことがある。
    *   `Accept`: ブラウザが受け入れ可能なコンテンツの形式 (MIMEタイプ)。
    *   `Accept-Language`: ブラウザが希望する言語。
    *   `Connection: keep-alive`: 一度のTCPコネクションで複数のリクエスト/レスポンスをやり取りするための指定。
    *   `Upgrade-Insecure-Requests: 1`: HTTPでアクセスした場合でも、サーバーが対応していればHTTPSに移行するよう要求するヘッダー。

#### 1.5. サーバー側の処理 (概要)

リクエストを受け取ったWebサーバーは、以下の処理を行います。

1.  **リクエスト解析:** 受信したHTTPリクエストを解析し、メソッド、パス、ヘッダーなどを理解する。
2.  **リソース特定:** `Host` ヘッダーやパス情報に基づき、どのコンテンツを返すか、またはどのプログラムを実行するかを決定する。
3.  **コンテンツ生成/取得:**
    *   **静的コンテンツ:** HTML、CSS、JavaScript、画像ファイルなど、そのまま返せるファイル。サーバー上の指定された場所からファイルを読み込む。
    *   **動的コンテンツ:** サーバーサイドのプログラム (PHP, Ruby, Python, Node.jsなど) を実行し、データベースアクセスなどを行って、結果を動的にHTMLなどで生成する。
4.  **HTTPレスポンスの作成:** 処理結果をHTTPレスポンスメッセージとして組み立てる。

#### 1.6. HTTPレスポンスの受信とレンダリング

Webサーバーは、処理結果をHTTPレスポンスとしてブラウザに返します。

```http
HTTP/1.1 200 OK
Date: Mon, 23 May 2023 10:00:00 GMT
Server: Apache/2.4.54 (Unix)
Last-Modified: Wed, 18 May 2023 08:30:00 GMT
Content-Length: 1234
Content-Type: text/html; charset=UTF-8
Connection: keep-alive

<!DOCTYPE html>
<html>
<head>
  <title>Example Page</title>
  <link rel="stylesheet" href="/css/style.css">
</head>
<body>
  <h1>Hello, World!</h1>
  <p>This is the content of page.html.</p>
  <script src="/js/script.js"></script>
</body>
</html>
```

*   **ステータスライン:** `HTTP/1.1 200 OK`
    *   `HTTP/1.1`: プロトコルバージョン。
    *   `200`: ステータスコード。リクエストが成功したことを示す。他に `404 Not Found` (リソースが見つからない)、`500 Internal Server Error` (サーバー内部エラー) などがある。
    *   `OK`: ステータスコードの説明。
*   **ヘッダー:**
    *   `Date`: レスポンスが生成された日時。
    *   `Server`: サーバーソフトウェアの情報。
    *   `Last-Modified`: リソースの最終更新日時。キャッシュ制御に使われる。
    *   `Content-Length`: レスポンスボディのサイズ (バイト単位)。
    *   `Content-Type`: レスポンスボディの内容の種類 (MIMEタイプ) と文字エンコーディング。ブラウザはこれを見て内容をどう解釈するか判断する (`text/html` ならHTMLとして描画)。
    *   `Connection: keep-alive`: コネクションを維持する指定。
*   **ボディ:** 実際のコンテンツ (この場合はHTMLコード)。

ブラウザはこのレスポンスを受け取ると、まず `Content-Type` を見てHTMLだと判断し、HTMLコードの解析 (パース) を開始します。

#### 1.7. レンダリングと追加リクエスト

1.  **DOM (Document Object Model) の構築:** ブラウザはHTMLコードを解析し、ページの構造を表現するDOMツリーをメモリ上に構築します。
2.  **CSSOM (CSS Object Model) の構築:** HTML内で `<link rel="stylesheet" href="/css/style.css">` のようなタグを見つけると、ブラウザはCSSファイル (`/css/style.css`) を取得するために、**再度WebサーバーへHTTPリクエストを送信**します (ステップ1.4から繰り返す)。取得したCSSを解析し、スタイル情報を表現するCSSOMツリーを構築します。
3.  **レンダーツリーの構築:** DOMとCSSOMを組み合わせて、実際に画面に表示される要素とそのスタイルを決定したレンダーツリーを構築します。
4.  **レイアウト (リフロー):** 各要素が画面上のどこに、どのくらいの大きさで配置されるかを計算します。
5.  **ペイント (描画):** 計算されたレイアウト情報に基づき、画面のピクセルを実際に描画します。
6.  **JavaScriptの実行:** HTML内で `<script src="/js/script.js">` のようなタグを見つけると、JavaScriptファイル (`/js/script.js`) を取得するために**再度WebサーバーへHTTPリクエストを送信**します。取得したJavaScriptコードを実行します。JavaScriptはDOMを操作したり、イベントに応じて動作したり、さらに非同期通信 (Ajax/Fetch API) でサーバーとやり取りしたりできます。

このように、1つのHTMLページを表示するだけでも、HTMLファイル本体に加えて、CSSファイル、JavaScriptファイル、画像ファイルなど、複数のリソースを取得するために、何度もブラウザとサーバーの間でリクエストとレスポンスのやり取りが発生します。

ここまでが、Webアクセスの基本的な流れです。次章からは、この流れがAWS ECS環境でどのように実現されているかを詳しく見ていきます。

### 第2章: AWSインフラストラクチャにおけるリクエスト処理

ブラウザからのリクエストは、インターネットを経由してAWSのネットワークに入り、いくつかのサービスを経て最終的にWebサーバー (コンテナ) に到達します。ここでは、Route 53, ELB (ALB), ECS を中心に解説します。

#### 2.1. Route 53: AWSのDNSサービス

前述のDNS名前解決プロセスにおいて、`example.com` の権威DNSサーバーの役割を果たすのが Route 53 です。

*   **ホストゾーン (Hosted Zone):** 管理するドメイン (`example.com` など) の単位。ここにDNSレコードを登録します。
*   **レコードセット (Record Set):** 特定の名前 (例: `www`, `api`, またはドメイン名自体 `@`)、タイプ (例: `A`, `AAAA`, `CNAME`, `MX`, `ALIAS`)、値 (例: IPアドレス、別のドメイン名、ELBのDNS名) の組み合わせ。
    *   **Aレコード:** ホスト名をIPv4アドレスに紐付ける。
    *   **AAAAレコード:** ホスト名をIPv6アドレスに紐付ける。
    *   **CNAMEレコード:** あるホスト名を別のホスト名に紐付ける (別名定義)。ただし、Zone Apex (ドメイン名自体、例: `example.com`) には設定できない制限がある。
    *   **ALIASレコード:** Route 53独自の機能。CNAMEに似ているが、Zone Apexにも設定可能で、AWSリソース (ELB, CloudFront, S3 Website Endpointなど) を直接指定できる。内部的にIPアドレス解決を行うため、CNAMEより効率的な場合がある。**ELBへドメイン名を向ける場合は、通常ALIASレコードを使用します。**

**設定例:**

`example.com` のホストゾーンに、以下のALIASレコードを作成します。

*   名前: `www.example.com`
*   タイプ: `A - IPv4 address`
*   エイリアス: はい
*   エイリアスターゲット: (後述する) Application Load Balancer (ALB) のDNS名を選択
*   ルーティングポリシー: シンプル (通常はこれ)

これで、ブラウザが `www.example.com` のIPアドレスを問い合わせると、Route 53 は関連付けられたALBの持つIPアドレス (これは動的に変わる可能性があります) を返します。

#### 2.2. ELB (Elastic Load Balancing): リクエストの分散とルーティングの要

DNSによってALBのIPアドレスが判明し、ブラウザからのリクエストはまずALBに到達します。ELBは単なる負荷分散だけでなく、現代的なWebアプリケーションにおいて非常に重要な役割を担います。ここでは特に **ALB (Application Load Balancer)** に焦点を当てます。ALBはHTTP/HTTPS (レイヤー7) の情報を理解し、柔軟なルーティングが可能です。

**ALBの主な役割:**

1.  **負荷分散 (Load Balancing):** 受信したリクエストを、複数のバックエンドサーバー (ECSタスク) に均等または設定に基づいて振り分けます。これにより、単一サーバーへの負荷集中を防ぎ、スケーラビリティと可用性を向上させます。
2.  **SSL/TLS終端 (SSL/TLS Termination):** ブラウザとALBの間はHTTPSで暗号化し、ALBとバックエンドサーバー (ECSタスク) の間はHTTPで通信する構成が可能です。これにより、バックエンドサーバーでの証明書管理や暗号化/復号処理の負荷を軽減できます。AWS Certificate Manager (ACM) と連携し、無料のSSL/TLS証明書を簡単に発行・管理・更新できます。
3.  **ヘルスチェック (Health Checks):** 定期的にバックエンドサーバーにリクエストを送り、正常に応答するかを確認します。異常なサーバーにはリクエストを送らないようにし、サービスの信頼性を高めます。
4.  **スティッキーセッション (Sticky Sessions):** 特定のユーザーからのリクエストを、常に同じバックエンドサーバーに送るようにします。セッション情報をサーバー側で保持する場合に有効ですが、ステートレスな設計が推奨されることが多いです。
5.  **ホストベースルーティング (Host-based Routing):** リクエストヘッダーの `Host` フィールドの値 (例: `www.example.com`, `blog.example.com`, `api.example.com`) に基づいて、リクエストを異なるバックエンドグループに振り分けます。
6.  **パスベースルーティング (Path-based Routing):** リクエストURLのパス (例: `/users/*`, `/products/*`, `/static/*`) に基づいて、リクエストを異なるバックエンドグループに振り分けます。
7.  **セキュリティ:** セキュリティグループと連携してアクセス制御を行ったり、AWS WAF (Web Application Firewall) と統合してSQLインジェクションやクロスサイトスクリプティング (XSS) などの攻撃から保護したりできます。

**ALBの構成要素:**

*   **リスナー (Listener):** 特定のプロトコル (HTTP/HTTPS) とポート (80/443) でリクエストを受け付けます。リスナーには**ルール (Rules)** が定義されます。
*   **ルール (Rules):** リスナーが受け取ったリクエストをどのように処理するかを定義します。各ルールは、**条件 (Conditions)** と **アクション (Actions)** を持ちます。ルールは優先度順に評価され、最初に一致したルールのアクションが実行されます。
    *   **条件:** ホストヘッダー、パス、HTTPヘッダー、クエリ文字列などに基づいてリクエストを識別します。
    *   **アクション:**
        *   **転送 (Forward):** リクエストを1つ以上の**ターゲットグループ (Target Group)** に転送します。
        *   **リダイレクト (Redirect):** ブラウザに別のURLへリダイレクトするよう指示します (例: HTTPからHTTPSへ)。
        *   **固定レスポンス (Fixed Response):** 事前に定義したHTTPレスポンス (例: メンテナンス中ページ) を返します。
        *   **認証 (Authenticate):** Cognito や OIDC を使ってユーザー認証を行います。
*   **ターゲットグループ (Target Group):** リクエストの転送先となるバックエンドリソース (ECSタスク、EC2インスタンス、Lambda関数など) の集まりです。ターゲットグループごとにプロトコル、ポート、ヘルスチェック設定を行います。

**【重要】ホスト/パスベースルーティングの実現 (ALBレベル):**

これが最初の重要なポイントです。ALBのリスナールールを使うことで、URLに応じてリクエストの振り分け先を変えることができます。

**例1: ホストベースルーティング**

*   `www.example.com` へのリクエスト → `WebApp-TargetGroup` へ転送
*   `api.example.com` へのリクエスト → `ApiServer-TargetGroup` へ転送

HTTPSリスナー (ポート443) に以下のルールを設定します。

1.  **ルール1:**
    *   優先度: 10
    *   **条件:** ホストヘッダーが `api.example.com`
    *   **アクション:** `ApiServer-TargetGroup` に転送
2.  **ルール2 (デフォルトルール):**
    *   優先度: デフォルト (最後)
    *   **条件:** (なし、常に一致)
    *   **アクション:** `WebApp-TargetGroup` に転送

**例2: パスベースルーティング**

*   `www.example.com/app1/*` へのリクエスト → `App1-TargetGroup` へ転送
*   `www.example.com/app2/*` へのリクエスト → `App2-TargetGroup` へ転送
*   `www.example.com/*` (上記以外) → `Default-WebApp-TargetGroup` へ転送

HTTPSリスナー (ポート443) に以下のルールを設定します (ホストヘッダーが `www.example.com` であることは前提とします)。

1.  **ルール1:**
    *   優先度: 10
    *   **条件:** パスが `/app1/*`
    *   **アクション:** `App1-TargetGroup` に転送
2.  **ルール2:**
    *   優先度: 20
    *   **条件:** パスが `/app2/*`
    *   **アクション:** `App2-TargetGroup` に転送
3.  **ルール3 (デフォルトルール):**
    *   優先度: デフォルト (最後)
    *   **条件:** (なし)
    *   **アクション:** `Default-WebApp-TargetGroup` に転送

このように、ALBのルール設定によって、1つのALBで複数のサービスやアプリケーションへのリクエストを適切に振り分けることが可能です。これがマイクロサービスアーキテクチャなどでも活用される基本的なルーティング方法です。

#### 2.3. ECS (Elastic Container Service): コンテナオーケストレーション

ALBから転送されたリクエストは、いよいよアプリケーションが動作しているコンテナに到達します。ECSはDockerコンテナのデプロイ、管理、スケーリングを容易にするためのコンテナオーケストレーションサービスです。

**ECSの主要コンポーネント:**

*   **クラスター (Cluster):** コンテナを実行するためのリソース (EC2インスタンスまたはFargate) の論理的なグループです。
*   **タスク定義 (Task Definition):** アプリケーションの設計図のようなもの。JSON形式で記述され、以下の情報を定義します。
    *   使用するDockerイメージ (ECRやDocker Hubから)
    *   必要なCPUとメモリ
    *   起動タイプ (Fargate or EC2)
    *   ネットワークモード (awsvpcが推奨)
    *   コンテナ間のリンク
    *   **ボリューム定義 (Volume Definitions):** コンテナが使用するデータボリューム (EFSなど) を定義します。**コンテンツ配置**に重要。
    *   **コンテナ定義 (Container Definitions):** 1つ以上のコンテナについて、以下の情報を定義します。
        *   コンテナ名
        *   イメージ名とタグ
        *   CPU/メモリ割り当て
        *   **ポートマッピング (Port Mappings):** コンテナのポート (例: 80) をホスト (Fargate/EC2) のポートにマッピングします (awsvpcモードでは通常不要)。ALBからのトラフィックを受け付けるポート。
        *   環境変数
        *   **マウントポイント (Mount Points):** タスク定義で定義したボリュームをコンテナ内のどのパスにマウントするかを指定します。**コンテンツ配置**に重要。
        *   ログ設定 (例: `awslogs` ドライバーでCloudWatch Logsへ転送)
*   **タスク (Task):** タスク定義に基づいて起動されたコンテナの実行インスタンスです。1つのタスクは1つ以上のコンテナを実行します。
*   **サービス (Service):** 指定された数のタスクをクラスター内で常に実行し続けるための機能です。
    *   **自動復旧:** タスクが異常終了した場合、自動的に新しいタスクを起動します。
    *   **ロードバランシング連携:** ALBのターゲットグループにタスクを自動的に登録・解除します。
    *   **スケーリング:** CPU使用率などのメトリクスに基づいて、実行するタスク数を自動的に増減 (オートスケーリング) させます。
    *   **デプロイ戦略:** ローリングアップデート、Blue/Greenデプロイメントなどをサポートします。
*   **起動タイプ (Launch Type):**
    *   **Fargate:** サーバーレスのコンテナ実行環境。EC2インスタンスの管理が不要で、タスクが必要とするリソースを指定するだけです。インフラ管理の手間が省けます。
    *   **EC2:** クラスター内にEC2インスタンスをプロビジョニングし、その上でコンテナを実行します。インスタンスタイプの選択やOS管理が必要ですが、より細かい制御が可能です (例: GPU利用、特定のネットワーク設定)。

**リクエストの流れ (ALBからECSタスクへ):**

1.  ALBは、ルールに基づいてリクエストを適切なターゲットグループに転送します。
2.  ターゲットグループには、ECSサービスによって起動・管理されている複数のタスクのIPアドレスとポートが登録されています。
3.  ALBは、ターゲットグループに登録されている正常なタスクの中から1つを選び、そのIPアドレスとポート (例: タスクのIPアドレスとコンテナ定義で指定されたポート80) にリクエストを転送します。
4.  リクエストは、選択されたタスク内の指定されたポートで待機しているコンテナ (通常はWebサーバーコンテナ) に到達します。

**ネットワークモード `awsvpc` について:**

Fargateでは必須、EC2でも推奨されるネットワークモードです。このモードでは、各タスクに個別のENI (Elastic Network Interface) とプライベートIPアドレスが割り当てられます。これにより、コンテナはホストのネットワークスタックを経由せず、直接VPCネットワークに接続されます。ポートマッピングの競合を心配する必要がなくなり、ネットワーク設定がシンプルになります。ALBはタスクのENIのIPアドレスとコンテナポートに直接リクエストを転送します。

### 第3章: コンテナ内部でのリクエスト処理とコンテンツ配置 (最重要)

リクエストはようやく、ECSタスク内のコンテナに到達しました。通常、Webアプリケーションを提供するタスクでは、少なくともWebサーバーコンテナ (NginxやApacheなど) が動作しています。動的なアプリケーションの場合は、さらにアプリケーションサーバーコンテナ (Node.js, Python/uWSGI, Ruby/Puma, PHP-FPMなど) が連携することもあります。

#### 3.1. Webサーバー (Nginx / Apache httpd) の役割

コンテナ内で最初にリクエストを受け取るのが、NginxやApacheといったWebサーバーソフトウェアです。これらの主な役割は以下の通りです。

1.  **リクエストの受付:** 指定されたポート (例: 80) でHTTPリクエストを待ち受けます。
2.  **静的コンテンツの配信:** HTML, CSS, JavaScript, 画像などの静的ファイルに対するリクエストの場合、設定されたドキュメントルート (後述) からファイルを直接読み込み、レスポンスとして返します。これは非常に高速です。
3.  **リバースプロキシ:** 動的コンテンツのリクエストの場合、自身では処理せず、バックエンドで動作しているアプリケーションサーバー (例: Node.jsアプリがポート3000で動作) にリクエストを転送 (プロキシ) し、その結果を受け取ってクライアントに返します。
4.  **ロードバランシング (内部):** 複数のアプリケーションサーバーインスタンスがある場合、それらにリクエストを振り分けることもできます (ALBがある場合は通常不要)。
5.  **SSL/TLS処理:** ALBでTLS終端を行わない場合、Webサーバーで証明書を設定し、HTTPS通信を処理します (ECSではALB終端が一般的)。
6.  **リクエスト/レスポンスヘッダーの書き換え:** 必要に応じてヘッダー情報を追加・削除・変更します。
7.  **アクセス制御:** IPアドレスベースのアクセス制限などを行います。
8.  **圧縮:** Gzipなどでレスポンスボディを圧縮し、転送量を削減します。
9.  **キャッシュ:** レスポンスをキャッシュし、次回以降のレスポンスを高速化します。

#### 3.2. 【最重要】コンテンツ (ディレクトリ・ファイル) の配置方法

Webサーバーがコンテンツを提供するためには、サーバー上のどこにファイルが配置されているかを知る必要があります。ECS環境におけるコンテンツ配置には、主に以下の方法があります。

**方法1: コンテナイメージに含める (Bake into the Image)**

*   **方法:** アプリケーションのコードや静的ファイルを、Dockerfile を使ってコンテナイメージをビルドする際に含めてしまいます。
*   **Dockerfile例 (Nginx + 静的コンテンツ):**

    ```dockerfile
    # ベースイメージを指定 (公式のNginxイメージを使用)
    FROM nginx:1.23-alpine

    # ローカルの 'html' ディレクトリの内容を、
    # コンテナ内のNginxのデフォルトドキュメントルートにコピーする
    COPY ./html /usr/share/nginx/html

    # (オプション) Nginxの設定ファイルをカスタムのものに置き換える場合
    # COPY ./my-nginx.conf /etc/nginx/nginx.conf
    # COPY ./my-site.conf /etc/nginx/conf.d/default.conf

    # Nginxが使用するポートを公開 (ドキュメンテーション目的、実際のマッピングはECSタスク定義で行う)
    EXPOSE 80

    # コンテナ起動時にNginxをフォアグラウンドで実行
    CMD ["nginx", "-g", "daemon off;"]
    ```

    このDockerfileと同じディレクトリに `html` ディレクトリを作成し、その中に `index.html`, `css/style.css`, `js/script.js` などを配置しておきます。`docker build` コマンドでイメージを作成し、ECR (Elastic Container Registry) にプッシュします。ECSタスク定義では、このECR上のイメージを指定します。
*   **メリット:**
    *   **イミュータブル (Immutable):** コンテナイメージ自体が特定のバージョンのコード/コンテンツを含むため、デプロイが単純で再現性が高い。「どこでも同じように動く」。
    *   **デプロイ容易性:** 新しいバージョンのイメージをビルドしてECSサービスを更新するだけでデプロイが完了する。
    *   **シンプル:** ボリューム管理が不要な場合、構成がシンプルになる。
*   **デメリット:**
    *   **イメージサイズ増大:** コンテンツ量が多いとイメージサイズが大きくなり、ビルドやプルに時間がかかる。
    *   **更新の手間:** コンテンツを少し変更するだけでも、イメージの再ビルドと再デプロイが必要になる。頻繁に更新されるコンテンツには不向き。
    *   **永続化されない:** コンテナが停止・再作成されると、コンテナ内で行われた変更 (例: ユーザーがアップロードしたファイルなど) は失われる。
*   **適しているもの:** アプリケーションコード、ビルド済みの静的ファイル (React, Vue, Angularなどのビルド成果物)、設定ファイルなど、デプロイ単位でバージョン管理されるもの。

**方法2: ボリュームマウントを使用する (Use Volumes)**

*   **方法:** コンテナの外部にあるストレージ領域を、コンテナ内の特定のディレクトリにマウント (接続) します。これにより、コンテナが再作成されてもデータが保持され、複数のコンテナ間でデータを共有することも可能になります。ECS (特にFargate) でよく使われるのは **EFS (Elastic File System)** です。
*   **EFS (Elastic File System):**
    *   フルマネージド型のNFS (Network File System) 互換ファイルストレージ。
    *   複数のアベイラビリティゾーン (AZ) にまたがってアクセス可能。
    *   容量は自動的にスケーリング。
    *   複数のECSタスク (コンテナ) から同時にマウントして、ファイルを共有できる。
    *   永続的なデータ (ユーザーアップロードファイル、共有設定、ログなど) の保存に適している。
*   **設定手順 (概要):**
    1.  **EFSファイルシステムの作成:** AWSコンソールやCLIでEFSファイルシステムを作成します。アクセスポイントも作成すると、特定のディレクトリやユーザーID/グループIDでのアクセス制御が容易になります。
    2.  **ECSタスク定義でのボリューム定義:** タスク定義の `volumes` セクションで、EFSファイルシステムを指定します。

        ```json
        // タスク定義JSONの一部 (抜粋)
        "volumes": [
          {
            "name": "efs-web-content", // ボリュームの識別名 (任意)
            "efsVolumeConfiguration": {
              "fileSystemId": "fs-xxxxxxxxxxxxxxxxx", // 作成したEFSファイルシステムのID
              "rootDirectory": "/", // EFS内のマウントするルートディレクトリ (アクセスポイントを使う場合は省略可)
              "transitEncryption": "ENABLED", // 転送中の暗号化を有効にする
              "authorizationConfig": { // (オプション) IAM認証やアクセスポイントを使う場合
                "accessPointId": "fsap-yyyyyyyyyyyyyyyyy", // 作成したアクセスポイントID
                "iam": "ENABLED" // IAM認証を有効にする (タスクIAMロールに権限が必要)
              }
            }
          }
        ],
        ```
    3.  **ECSタスク定義でのマウントポイント定義:** コンテナ定義の `mountPoints` セクションで、上記で定義したボリュームをコンテナ内のどのパスにマウントするか指定します。

        ```json
        // タスク定義JSONの一部 (コンテナ定義内)
        "containerDefinitions": [
          {
            "name": "nginx-container",
            "image": "nginx:1.23-alpine",
            "portMappings": [
              {
                "containerPort": 80,
                "protocol": "tcp"
              }
            ],
            "mountPoints": [
              {
                "sourceVolume": "efs-web-content", // volumesで定義したボリューム名
                "containerPath": "/usr/share/nginx/html", // コンテナ内のマウント先パス (Nginxのドキュメントルート)
                "readOnly": false // 読み取り専用にする場合はtrue
              }
            ],
            // ... 他の設定 ...
          }
        ]
        ```
*   **メリット:**
    *   **データの永続化:** コンテナが停止・再作成されてもデータが失われない。
    *   **データ共有:** 複数のコンテナ (タスク) 間で同じファイルシステムを共有できる。
    *   **コンテンツ更新の容易さ:** イメージを再ビルドせずに、EFS上のファイルを直接更新できる (ただし、更新方法の確立が必要)。CMS (コンテンツ管理システム) などで生成されるコンテンツに適している。
*   **デメリット:**
    *   **パフォーマンス:** ネットワーク経由のファイルアクセスになるため、コンテナイメージ内のローカルファイルアクセスより一般的に遅延が大きい。特に大量の小さなファイルを扱う場合に影響が出やすい。
    *   **設定の複雑さ:** EFSの作成、アクセス権の設定、タスク定義の設定など、手順が増える。
    *   **コスト:** EFSのストレージ容量やスループットに応じたコストがかかる。
*   **適しているもの:** ユーザーがアップロードしたファイル、CMSが管理するコンテンツ、複数のコンテナで共有が必要な設定ファイルやデータ、ログファイル (CloudWatch Logsへの転送が推奨だが、場合によってはEFSも使う)。

**方法3: サイドカーコンテナによる同期**

*   **方法:** メインのWebサーバーコンテナとは別に、データ同期用のコンテナ (サイドカー) を同じタスク内で起動します。例えば、S3バケットから定期的にコンテンツをダウンロードして、共有ボリューム (EFSや、タスク内のコンテナ間で共有されるEmpty Volume) に配置するようなコンテナです。
*   **メリット:** S3などの安価なストレージをコンテンツのマスター置き場として利用でき、コンテナ起動時や定期的に最新のコンテンツを取得できる。
*   **デメリット:** 同期のためのコンテナとロジックが必要になり、構成が複雑になる。同期のタイミングによっては古いコンテンツが表示される可能性がある。

**どの方法を選ぶべきか？**

多くの場合、以下の組み合わせが採用されます。

*   **アプリケーションコード、フレームワーク、ビルド済み静的アセット:** **コンテナイメージに含める**。これらはアプリケーションのバージョンと一体であり、イミュータブルなデプロイの恩恵を受けられる。
*   **ユーザーアップロードファイル、CMS管理コンテンツなど、頻繁に更新され永続性が必要なデータ:** **EFSボリュームマウント**を使用する。
*   **設定ファイル:** イメージに含めるのが基本だが、環境ごとに異なる設定値は**環境変数**や **AWS Systems Manager Parameter Store** / **Secrets Manager** を使用してコンテナ起動時に注入するのがベストプラクティス。共有が必要な設定ファイルはEFSも選択肢。
*   **大量の静的ファイル (画像、動画など):** Webサーバーから直接配信するのではなく、**S3 (Simple Storage Service)** に配置し、**CloudFront (CDN)** 経由で配信するのが最も効率的でスケーラブル。HTMLからはCloudFrontのURLを参照するようにする。

#### 3.3. 【重要】ディレクトリ構成のベストプラクティス

Webサーバーがコンテンツを正しく見つけられるように、ディレクトリ構成を適切に設計することが重要です。

**基本的な考え方:**

*   **ドキュメントルート (Document Root):** Webサーバーが公開するファイルの起点となるディレクトリ。Nginxでは `root` ディレクティブ、Apacheでは `DocumentRoot` ディレクティブで設定します。ブラウザからのリクエストパス (例: `/css/style.css`) は、このドキュメントルートからの相対パスとして解釈されます (例: ドキュメントルートが `/usr/share/nginx/html` なら、`/usr/share/nginx/html/css/style.css` を探す)。
*   **静的ファイルと動的スクリプトの分離 (意識):** 静的ファイル (HTML, CSS, JS, 画像) を置くディレクトリと、サーバーサイドで実行されるスクリプト (PHPファイルなど) を置くディレクトリを分けて管理することが多いです。
*   **フレームワークの規約に従う:** Ruby on Rails, Django, Laravel, Next.js などのフレームワークを使用する場合、それぞれ推奨されるディレクトリ構成があります (例: Railsの `public`, Djangoの `static`)。基本的にはその規約に従います。
*   **設定ファイル、ログファイルは公開領域外に:** NginxやApacheの設定ファイル (`nginx.conf`, `httpd.conf` など) やログファイル (`access.log`, `error.log`) は、ドキュメントルートの外に配置し、Webから直接アクセスできないようにします。コンテナ環境では、ログは標準出力/標準エラー出力に出力し、ECSのログドライバー (`awslogs`) でCloudWatch Logsに転送するのが一般的です。

**具体的なディレクトリ構成例:**

**例1: シンプルな静的サイト (Nginx)**

コンテナイメージに含める場合、プロジェクトルートに以下のような構成で配置し、Dockerfileでコピーします。

```
my-static-site/
├── Dockerfile
├── nginx.conf       # (オプション) Nginxのカスタム設定
├── html/            # このディレクトリをドキュメントルートにする
│   ├── index.html
│   ├── about.html
│   ├── css/
│   │   └── style.css
│   ├── js/
│   │   └── script.js
│   └── images/
│       └── logo.png
```

Dockerfile (`COPY ./html /usr/share/nginx/html`) で、`html` ディレクトリの内容がコンテナ内の `/usr/share/nginx/html` にコピーされます。Nginxのデフォルト設定では、この `/usr/share/nginx/html` がドキュメントルートになっています。

**例2: Node.js アプリケーション (Express + 静的ファイル)**

```
my-node-app/
├── Dockerfile
├── server.js        # Expressアプリケーション本体
├── package.json
├── node_modules/    # (Dockerfile内で npm install される)
├── public/          # 静的ファイルを配置するディレクトリ
│   ├── css/
│   │   └── style.css
│   ├── js/
│   │   └── client.js
│   └── images/
│       └── banner.jpg
├── views/           # (もしテンプレートエンジンを使うなら)
│   └── index.ejs
└── nginx.conf       # (オプション) フロントにNginxを置く場合の設定
```

*   **Node.js (Express) 側:** `server.js` 内で `express.static` ミドルウェアを使って `public` ディレクトリを静的ファイルの配信元として設定します。

    ```javascript
    const express = require('express');
    const path = require('path');
    const app = express();

    // publicディレクトリを静的ファイルのルートとして設定
    app.use(express.static(path.join(__dirname, 'public')));

    // APIエンドポイントなど
    app.get('/api/users', (req, res) => {
      // ... 処理 ...
      res.json([{ id: 1, name: 'Alice' }]);
    });

    // その他のルート (例: SPAならindex.htmlを返す)
    app.get('*', (req, res) => {
      res.sendFile(path.join(__dirname, 'public', 'index.html')); // もしSPAなら
    });

    const PORT = process.env.PORT || 3000;
    app.listen(PORT, () => {
      console.log(`Server listening on port ${PORT}`);
    });
    ```
*   **構成パターン:**
    *   **Node.js単体:** コンテナはNode.jsのみ実行。Dockerfileで `COPY . .` し、`CMD ["node", "server.js"]`。静的ファイルもNode.js (Express) が配信。小規模ならこれでもOK。
    *   **Nginx + Node.js:** NginxコンテナとNode.jsコンテナを同じタスク内で起動 (またはNginxコンテナ内でNode.jsを別プロセス起動)。
        *   **静的ファイル:** Nginxが `/public` ディレクトリの内容を直接配信するように設定 (`root /path/to/public;`)。
        *   **動的リクエスト (APIなど):** `/api/*` へのリクエストなどを、NginxがNode.jsアプリ (例: `localhost:3000`) へ `proxy_pass` するように設定。
        *   この構成の方が、静的ファイル配信のパフォーマンスが良い。

**例3: EFSボリュームマウントを使用 (ユーザーアップロード)**

*   **タスク定義:** EFSボリューム (`efs-uploads`) をコンテナ内の `/mnt/uploads` にマウントするように設定。
*   **アプリケーションコード:** ユーザーがファイルをアップロードしたら、アプリケーション (Node.js, PHP, Pythonなど) はファイルを `/mnt/uploads` ディレクトリ内に保存するように実装します。
*   **Webサーバー (Nginx) 設定:**
    *   もしアップロードされたファイルをWebサーバー経由で直接配信したい場合 (非推奨、セキュリティリスクあり)、Nginxの設定で `/uploads/*` のようなパスへのリクエストを `/mnt/uploads` ディレクトリに対応付ける (`location /uploads { alias /mnt/uploads; }`) ことが考えられます。
    *   **推奨:** 通常、アップロードファイルはアプリケーション経由でのみアクセスされるか、あるいは別の仕組み (例: S3+CloudFront) で配信します。Webサーバーのドキュメントルートとは別の場所にマウントし、直接Webアクセスできないようにします。

**重要なポイント:**

*   **コンテナ内のパス:** Dockerfileの `COPY` や `WORKDIR`、タスク定義の `mountPoints` で指定するパスは、**コンテナファイルシステム内でのパス**です。
*   **Webサーバーの設定:** NginxやApacheの設定ファイル内で指定するパス (例: `root`, `alias`, `proxy_pass` のパス) も、**コンテナファイルシステム内でのパス**を基準に考えます。EFSをマウントした場合、そのマウント先のパスを指定します。

#### 3.4. 【重要】ホスト/パスベースルーティングの実現 (Webサーバーレベル)

ALBで大まかなルーティング (例: ホスト名や主要なパスプレフィックスでターゲットグループを分ける) を行いましたが、さらに細かいルーティング制御や、特定のパスに対する処理 (静的ファイル配信、別サーバーへのプロキシなど) をコンテナ内のWebサーバー (Nginx/Apache) で行うことがよくあります。

**Nginxでの設定 (`nginx.conf` または `conf.d/*.conf`)**

Nginxでは、`server` ブロックと `location` ブロックを使ってルーティングを制御します。

*   **`server` ブロック:** 仮想ホスト (Virtual Host) を定義します。`listen` ディレクティブでポートを、`server_name` ディレクティブで対応するホスト名を指定します。
*   **`location` ブロック:** 特定のパスに対するリクエストの処理方法を定義します。

**例1: 1つのコンテナで複数サイトをホスト (ホストベース)**

ALBからのリクエストはすべてこのNginxコンテナに来るが、`Host` ヘッダーを見て処理を分ける場合。 (ただし、ALBでターゲットグループを分ける方が一般的)

```nginx
# /etc/nginx/conf.d/my_sites.conf

server {
    listen 80;
    server_name site1.example.com; # Hostヘッダーがこれなら
    root /var/www/site1/html;      # このディレクトリをドキュメントルートに
    index index.html index.htm;

    location / {
        try_files $uri $uri/ =404;
    }
}

server {
    listen 80;
    server_name site2.example.com; # Hostヘッダーがこれなら
    root /var/www/site2/html;      # このディレクトリをドキュメントルートに
    index index.html index.htm;

    location / {
        try_files $uri $uri/ =404;
    }
}
```

この設定では、コンテナ内の `/var/www/site1/html` と `/var/www/site2/html` にそれぞれのサイトのファイルを配置する必要があります (Dockerfileでコピーするか、ボリュームマウント)。

**例2: パスによって処理を分岐 (パスベース)**

ALBから `WebApp-TargetGroup` に転送されてきたリクエストを、Nginxがパスによって処理を分けます。静的ファイル配信とアプリケーションサーバーへのプロキシを組み合わせる典型的な例です。

```nginx
# /etc/nginx/conf.d/default.conf (または任意の .conf ファイル)

server {
    listen 80;
    server_name www.example.com; # ALBから転送される想定のHostヘッダー (または "_" でワイルドカード)

    # 静的ファイル用のlocation (先に評価されるように正規表現などで工夫するか、配置順に注意)
    location /static/ {
        root /app/public; # コンテナ内の /app/public/static/ を探す
        # または alias /app/public/static/; # この場合、/app/public/static/ を探す
        expires 1d;      # キャッシュ設定 (例)
        add_header Cache-Control "public";
    }

    location /images/ {
        root /app/public; # コンテナ内の /app/public/images/ を探す
        expires 7d;
        add_header Cache-Control "public";
    }

    # APIリクエストをバックエンドのNode.jsアプリ (同じタスク内の別コンテナ or 別プロセス) へ転送
    location /api/ {
        proxy_pass http://localhost:3000; # Node.jsアプリがポート3000で待機している場合
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme; # ALBからのプロトコル (http/https) を伝える
    }

    # 上記以外のすべてのリクエスト (例: ReactなどのSPA)
    location / {
        root /app/public;   # コンテナ内の /app/public をドキュメントルートに
        index index.html;   # index.html をデフォルトファイルに
        try_files $uri $uri/ /index.html; # URLに対応するファイルがなければ index.html を返す (SPAルーティング用)
    }

    # (オプション) エラーページ設定
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /usr/share/nginx/html; # Nginxデフォルトのエラーページ置き場など
    }

    # (オプション) ログ設定 (コンテナログは標準出力/エラーが基本)
    # access_log /var/log/nginx/access.log;
    # error_log /var/log/nginx/error.log;
    # ↓ 標準出力/エラーに出力する設定 (Dockerfileでシンボリックリンク作成などでも可)
    access_log /dev/stdout;
    error_log /dev/stderr;
}
```

*   `root /app/public;`: ドキュメントルートを `/app/public` に設定。`/static/css/style.css` へのリクエストは `/app/public/static/css/style.css` を探します。
*   `alias /app/public/static/;`: `location /static/` 内で使うと、`/static/css/style.css` へのリクエストは `/app/public/static/css/style.css` を探します。`root` と `alias` の違いに注意。`alias` は location で指定したパス部分を置き換えます。
*   `proxy_pass http://localhost:3000;`: `localhost:3000` で動作しているアプリケーションサーバーにリクエストを転送します。同じタスク内の別コンテナでアプリが動いている場合、`localhost` で通信できます (ネットワークモードによる)。あるいは、ECS Service Discovery を使ってアプリコンテナ名を指定することもできます。
*   `try_files $uri $uri/ /index.html;`: シングルページアプリケーション (SPA) でよく使われる設定。URLに対応する静的ファイル (`$uri`) やディレクトリ (`$uri/`) が存在すればそれを返し、存在しなければ `/index.html` を返します。これにより、React Routerなどのクライアントサイドルーティングが機能します。
*   `proxy_set_header`: バックエンドのアプリケーションに、元のリクエスト情報 (Hostヘッダー、クライアントIP、プロトコルなど) を伝えるために重要です。特に `X-Forwarded-Proto` は、ALBでHTTPS終端している場合に、アプリ側が元々HTTPSリクエストだったことを知るために必要です。

**Apache httpd での設定 (`httpd.conf` または `conf.d/*.conf` または `.htaccess`)**

Apacheでは、`<VirtualHost>` ディレクティブ、`<Directory>` / `<Location>` / `<Files>` ディレクティブ、`mod_proxy`、`mod_rewrite` などを使ってルーティングや処理を制御します。

**例 (Nginxの例2に相当):**

```apache
# /etc/httpd/conf/httpd.conf や conf.d/my_app.conf など

Listen 80

# ALBからの接続を想定
<VirtualHost *:80>
    # ServerName www.example.com # 必要に応じて設定

    DocumentRoot "/app/public"

    # 静的ファイル用ディレクトリの設定
    <Directory "/app/public/static">
        Options FollowSymLinks
        AllowOverride None
        Require all granted
        ExpiresActive On
        ExpiresDefault "access plus 1 day"
        Header set Cache-Control "public"
    </Directory>
    <Directory "/app/public/images">
        Options FollowSymLinks
        AllowOverride None
        Require all granted
        ExpiresActive On
        ExpiresDefault "access plus 7 days"
        Header set Cache-Control "public"
    </Directory>

    # mod_proxy を使ってAPIリクエストを転送
    ProxyPass /api/ http://localhost:3000/api/
    ProxyPassReverse /api/ http://localhost:3000/api/

    # (オプション) ヘッダー設定 (mod_headersが必要)
    RequestHeader set X-Forwarded-Proto "http"
    <If "%{HTTP:X-Forwarded-Proto} == 'https'">
        RequestHeader set X-Forwarded-Proto "https"
    </If>
    # X-Forwarded-For や Host は通常 mod_proxy が適切に設定

    # SPAルーティングのための mod_rewrite 設定
    <Directory "/app/public">
        RewriteEngine On
        # ファイルやディレクトリが存在しない場合
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteCond %{REQUEST_FILENAME} !-d
        # index.html にフォールバック
        RewriteRule . /index.html [L]
    </Directory>

    # ログ設定 (標準出力/エラーへ)
    ErrorLog /dev/stderr
    CustomLog /dev/stdout combined

</VirtualHost>
```

*   `DocumentRoot "/app/public"`: ドキュメントルートを設定。
*   `<Directory>`: 特定のディレクトリに対する設定 (アクセス権、オプション、キャッシュヘッダーなど)。
*   `ProxyPass /api/ http://localhost:3000/api/`: `/api/` で始まるリクエストを `http://localhost:3000/api/` に転送。`ProxyPassReverse` はリダイレクト時のヘッダー書き換え用。
*   `RewriteEngine On` と `RewriteRule`: `mod_rewrite` を使ってURLの書き換えを行います。ここではSPA用に、存在しないファイル/ディレクトリへのリクエストを `/index.html` に内部的に書き換えています。
*   ログ出力先を `/dev/stdout`, `/dev/stderr` に向けることで、コンテナログとして収集できます。

**まとめ: ルーティング階層**

1.  **DNS (Route 53):** ドメイン名をALBに向ける。
2.  **ALB:**
    *   ホスト名 (`Host` ヘッダー) に基づいてターゲットグループを選択。
    *   パス (`/app1/*`, `/app2/*`) に基づいてターゲットグループを選択。
    *   HTTPS終端、リダイレクト、固定レスポンスなどの処理。
3.  **Webサーバー (Nginx/Apache in Container):**
    *   ALBから受けたリクエストをさらに細かく処理。
    *   特定のパス (`/static/`, `/images/`) を静的ファイルとして配信。
    *   特定のパス (`/api/`) をバックエンドのアプリケーションサーバーにプロキシ (`proxy_pass`, `ProxyPass`)。
    *   URL書き換え (`try_files`, `mod_rewrite`) によるSPA対応など。
    *   ドキュメントルート (`root`, `DocumentRoot`) やエイリアス (`alias`) でファイルシステム上のコンテンツ配置場所を指定。

このように、複数のレイヤーでルーティングとコンテンツ配信の設定が行われ、連携することで柔軟なWebサービスが実現されています。

### 第4章: セキュリティに関する考慮事項

AWS ECS環境でWebアプリケーションを公開する際には、セキュリティ対策が不可欠です。

*   **HTTPSの強制:**
    *   ALBのリスナーでHTTPS (443) を設定し、ACMで発行したSSL/TLS証明書を適用します。
    *   HTTP (80) リスナーも作成し、すべてのリクエストをHTTPSにリダイレクトするルールを設定します。
    *   ALBとバックエンド (ECSタスク) 間の通信も暗号化する場合は、ターゲットグループのプロトコルをHTTPSにし、コンテナ内のWebサーバーにも証明書を設定する必要があります (管理が煩雑になるため、ALB終端が一般的)。
*   **AWS WAF (Web Application Firewall):**
    *   ALBにWAFを統合し、一般的なWeb攻撃 (SQLインジェクション, XSS, 不正ボットなど) からアプリケーションを保護します。マネージドルール (AWSやサードパーティ提供) やカスタムルールを設定できます。
*   **セキュリティグループ (Security Groups):**
    *   仮想ファイアウォールとして機能し、リソース (ALB, ECSタスク, EC2インスタンス) へのインバウンド/アウトバウンドトラフィックを制御します。
    *   **ALBのセキュリティグループ:** インターネットからのHTTP (80) / HTTPS (443) リクエストのみを許可します。
    *   **ECSタスクのセキュリティグループ (awsvpcモード):** ALBからのトラフィックのみを許可します (ソースとしてALBのセキュリティグループを指定)。データベースなど他のAWSサービスへのアウトバウンド接続も必要に応じて許可します。
    *   **最小権限の原則:** 必要なポートとソースIP/セキュリティグループからのみアクセスを許可するように設定します。
*   **コンテナイメージの脆弱性スキャン:**
    *   ECRには、プッシュ時や継続的にコンテナイメージ内のOSパッケージやライブラリの既知の脆弱性をスキャンする機能があります。これを利用して、脆弱なイメージがデプロイされるのを防ぎます。
*   **機密情報管理:**
    *   データベースのパスワード、APIキーなどの機密情報は、コードやコンテナイメージに直接埋め込まず、**AWS Secrets Manager** や **AWS Systems Manager Parameter Store (SecureString)** を使用して安全に管理し、ECSタスク定義で環境変数やファイルとしてコンテナに注入します。
*   **IAMロール:**
    *   ECSタスクには、AWSサービス (S3, Secrets Manager, Parameter Store, EFSなど) にアクセスするための適切な権限を持つIAMロール (**タスクロール**) を割り当てます。EC2起動タイプの場合は、EC2インスタンス自体にもIAMロール (**インスタンスプロファイル**) が必要です。ここでも最小権限の原則を適用します。

### 第5章: デプロイメントと運用

開発したアプリケーションとコンテンツをECS環境に展開し、安定して運用するための仕組みも重要です。

*   **CI/CD (継続的インテグレーション/継続的デリバリー):**
    *   コードの変更をリポジトリ (例: AWS CodeCommit, GitHub) にプッシュすると、自動的にテスト、ビルド、デプロイが行われるパイプラインを構築します。
    *   **AWS CodePipeline:** CI/CDのワークフローを定義・自動化します。
    *   **AWS CodeBuild:** ソースコードのコンパイル、テスト、Dockerイメージのビルド、ECRへのプッシュなどを行います。Dockerfileはこのステップで使われます。
    *   **AWS CodeDeploy (ECS向け):** 新しいバージョンのタスク定義を使ってECSサービスを安全に更新します。ローリングアップデート、Blue/Greenデプロイメント、Canaryデプロイメントなどの戦略を選択できます。
        *   **Blue/Greenデプロイメント:** 新バージョン (Green) を旧バージョン (Blue) と並行して起動し、テスト後にALBのトラフィックを瞬時に切り替える方法。問題発生時のロールバックが容易。
*   **モニタリング:**
    *   **Amazon CloudWatch Metrics:** CPU使用率、メモリ使用率、ALBのリクエスト数、レイテンシ、エラーレート、ECSサービスの実行中タスク数などのメトリクスを収集・監視します。
    *   **Amazon CloudWatch Alarms:** メトリクスにしきい値を設定し、超えた場合に通知 (SNS経由でメールやSlackなど) やアクション (例: ECSサービスのオートスケーリング) をトリガーします。
    *   **Amazon CloudWatch Logs:** ECSタスク (コンテナ) の標準出力/標準エラー出力を収集・保存・検索・分析します。タスク定義のログ設定で `awslogs` ログドライバーを指定します。アプリケーションやWebサーバーのログはこの仕組みで集約するのが基本です。
    *   **AWS X-Ray:** 分散トレーシングシステム。リクエストが複数のサービス (ALB, ECS, Lambda, DBなど) を通過する際のパフォーマンスボトルネックやエラー箇所を特定するのに役立ちます。
*   **オートスケーリング:**
    *   **ECS Service Auto Scaling:** CloudWatchメトリクス (例: 平均CPU使用率、ALBのリクエスト数/タスク) に基づいて、ECSサービスが実行するタスク数を自動的に増減させます。これにより、負荷に応じてリソースを最適化し、コスト効率とパフォーマンスを両立できます。
    *   **EC2起動タイプの場合:** クラスター内のEC2インスタンス数も **EC2 Auto Scaling Group** で自動調整する必要があります (Cluster Auto Scaling)。

### 第6章: まとめ ～全体像の再確認～

ブラウザからAWS ECS上のWebコンテンツが表示されるまでの長い旅路を振り返ってみましょう。

1.  **ブラウザ:** URL解析 → DNS問い合わせ (→ Route 53)
2.  **Route 53:** ドメイン名からALBのDNS名を解決 → ALBのIPアドレスを返す
3.  **ブラウザ:** ALBのIPアドレスへTCP接続確立 → HTTPSハンドシェイク → HTTPリクエスト送信 (`Host`, パス情報含む)
4.  **ALB:** HTTPSリクエスト受信 → SSL/TLS終端 → リスナールール評価 (ホスト名、パスで条件分岐) → 適切なターゲットグループ選択 → ヘルスチェックで正常なECSタスク選択 → リクエスト転送 (HTTPで)
5.  **ECSタスク (Fargate/EC2):** タスクのENIがリクエスト受信 → 指定されたポートで待機中の**Webサーバーコンテナ (Nginx/Apache)** へ
6.  **Webサーバーコンテナ:**
    *   HTTPリクエスト受信 → 設定ファイル (`nginx.conf`, `httpd.conf` 等) に基づき処理判断
    *   **コンテンツ配置場所の特定:**
        *   `root` / `DocumentRoot` / `alias` ディレクティブで指定されたパス (例: `/usr/share/nginx/html`, `/app/public`) を確認。
        *   そのパスは、**コンテナイメージ内** (Dockerfileの`COPY`) か、**マウントされたボリューム** (タスク定義の`mountPoints` でEFSなどを `/mnt/efs-data` などにマウント) を指している。
    *   **ルーティングと処理:**
        *   `location` / `<Location>` / `<Directory>` / `RewriteRule` などでパスに基づき処理分岐。
        *   **静的ファイルの場合:** 特定されたパスからファイル (HTML, CSS, JS, 画像) を読み込み、HTTPレスポンスを作成して返す。
        *   **動的コンテンツの場合:** `proxy_pass` / `ProxyPass` などで、バックエンドの**アプリケーションサーバーコンテナ** (Node.js, PHP-FPM, Python/uWSGIなど。同じタスク内 or 別タスク) にリクエストを転送。
7.  **アプリケーションサーバーコンテナ (動的コンテンツの場合):** リクエスト処理 (DBアクセス、ロジック実行) → 結果を生成 (HTMLなど) → Webサーバーコンテナに応答を返す。
8.  **Webサーバーコンテナ:** アプリケーションサーバーからの応答を受け取る → 必要ならヘッダー追加など → HTTPレスポンスをALBに返す。
9.  **ALB:** レスポンスをブラウザに返す。
10. **ブラウザ:** HTTPレスポンス受信 → HTML解析 → CSS, JS, 画像などの追加リソースがあれば、ステップ3から再度リクエスト → 全てのリソースを取得し、レンダリングしてページ表示完了！

**コンテンツ配置とルーティングの要点:**

*   **コンテンツ配置:**
    *   コードやビルド済みアセットは**イメージ内**が基本 (Immutable)。
    *   永続・共有データは**EFSマウント** (コンテナパスとEFSパスをタスク定義で紐付け)。
    *   大量の静的アセットは**S3 + CloudFront** が最適。
    *   Webサーバー設定 (`root`, `alias`) で、コンテナ内のどのパスを公開するか指定。
*   **ルーティング:**
    *   **ALB** で大まかな振り分け (ホスト/パス → ターゲットグループ)。
    *   **Webサーバー (コンテナ内)** で細かい制御 (パス → 静的配信 or アプリへプロキシ)。