1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

素人基盤エンジニアがDockerでDjango REST Frameworkとreactを触るシリーズ④:CloudFront-S3

Posted at

素人基盤エンジニアがDockerでDjango REST Frameworkとreactを触るシリーズ③:reactの導入のつづき。
①からみたい場合はこちら。素人基盤エンジニアがDockerでDjango REST Frameworkとreactを触るシリーズ①:Djangoの導入

reactアプリのS3への配置

前回、reactとDjango REST FrameworkをEC2インスタンスで構築し、連携ができていることを確認したので、今回はreactで作成したフロントエンド部分をS3に配置し、CloudFrontで配信する形に変更する。
まずは、S3バケットを作成していく。
001.png
002.png
003.png
004.png

バケットが作成できたので、reactをビルドし、AWS CLIを利用してバケットにビルドしたソースコードを配置する。※AWS CLIを利用する際は、aws configureコマンドで適切なIAMクレデンシャルを設定する。(IAMクレデンシャルの設定については割愛する)
また、reactで一部IPアドレスを書いていた部分があったが、CloudFrontの構成に変更すればポートはCloudFrontがルーティングしてくれるので、パスのみの記載に変更する。

src/App.js(20行目と31行目を変更)
import React, { Component } from 'react';
import axios from 'axios';

class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            todos: [],
            hostname: ""
        };
    }

    componentDidMount() {
        this.getTodos();
        this.getHostname();
    }

    getTodos() {
        axios
            .get('api/todos/')
            .then(res => {
                this.setState({ todos: res.data });
            })
            .catch(err => {
                console.log(err);
            });
    }

    getHostname() {
        axios
            .get('api/hostname/')
            .then(res => {
                this.setState({ hostname: res.data });
            })
            .catch(err => {
                console.log(err);
            });
    }

    render() {
        return (
            <div>
                {this.state.todos.map(item => (
                    <div key={item.id}>
                        <h2>{item.title}</h2>
                        <p className="item_body">{item.body}</p>
                    </div>
                ))}

                <div className="box30">
                    <div className="box-title">HOSTNAME</div>
                    <p>{this.state.hostname.hostname}</p>
                </div>
            </div>
        );
    }
}

export default App;

yarn buildでビルドを実行する。

yarn build

すると、frontendの下にbuildというディレクトリができる。

frontend
∟build
∟node_module
∟package.json
∟public
∟README.md
∟src
∟yarn.lock

これをディレクトリごと先ほど作ったS3バケットにコピーする。※適切なIAM権限を付与している前提

aws s3 cp ./build s3://<your-bucket-name>/ --recursive 

GUIで対象のバケットを見てみると、ちゃんとオブジェクトが配置されていることがわかる。
005.png

ALBとWAFの作成

CloudFrontの、多数のエッジサーバからアクセス先へ通信が発生するという性質上、アクセス先(オリジン)ではセキュリティグループによってIPアドレスを制限することが難しい。
そのため、今回は立てているEC2サーバの前段にALBを配置し、CloudFrontで付与したヘッダを持つアクセスのみを通過させるようなWAFをアタッチすることによって、直接ALBへアクセスする経路を防御する。
まずはターゲットグループを作成する。
006.png
007.png
適当な名前をつけて、ポートをDjangoの待ち受けポートである8000に指定する。
VPCは、Djangoが動いているEC2サーバと同じVPCにする。
008.png
009.png
Djangoが動いているEC2サーバをターゲットとして登録する。EC2サーバにチェックをいれて、[Include as pending below]を押下。
010.png
すると下のTargetに追加されるので、これで作成を押す。
011.png
ターゲットグループが作成できた。
次は、ALBを作成する。
012.png
013.png
014.png
今回は、ロードバランサーのポートもDjangoの待ち受けポートと同じ8000番にする。
015.png
VPCとサブネットを選択する。VPCはEC2サーバと同じVPCで、サブネットはInternetGWへのルーティングルールがあるパブリックサブネットを選択する。
016.png
017.png
セキュリティグループを選択する。セキュリティグループは、インバウンドは8000に対して0.0.0.0/0で開放していて、アウトバウンドは制限しないものをALB用に作成しておき、それを選択する。
また、EC2サーバにアタッチされているセキュリティグループは、このセキュリティグループからの8000番ポートのインバウンドアクセスを許可する用に変更しておく。
018.png
ターゲットグループは、先ほど作成したターゲットグループを指定する。
019.png
020.png
021.png
これでロードバランサーまで作成された。
次にWAFを作成する。
今回は特にclassicを利用したい要件もないので、新しいほう(WAF v2)で作成していく。
022.png
023.png
WebACLを作成する前に、リージョンをALBを作成したリージョンに合わせておく。自分は東京リージョンで作成しているので、東京を指定。
024.png
025.png
適当な名前を付ける。
026.png
027.png
リソースとして、先ほど作成したALBを登録する。
028.png
029.png
030.png
ルールを作成していく。
次の通りに指定する。
If a request : mathces the statement
Inspect : Header
Header field name : x-pre-shared-key
Match type : Exactly matches string
String to match : sample-string
これは、リクエストのHeaderを検査して、x-pre-shared-keyというフィールドがsample-stringとなっているかどうかを調べるルールという意味。
x-pre-shared-keyやsample-stringはもちろん変えてもらってもいいが、その場合は後述のCloudFront作成時に付与するヘッダも同様に変更してやる必要がある。

031.png
このルールに合致したアクセスのみを通したいので、ActionはAllowにする。
032.png
ルールに合致しなかったアクセスははじきたいので、Default ActionはBlockにする。
033.png
034.png
035.png
036.png
037.png
ここまでで、ALBとWAFが作成できた。

CloudFrontの構築

今回の肝となるCloudFrontを作成していく。
038.png
039.png
040.png
Originの部分には、一番最初に作成したS3バケットを指定する。ただ、ここで注意したいのが、Origin Domain Nameのところを選択すると出てくるドロップダウンリストで選択できるs3のドメイン名は、<bucket名>.s3.amazonaws.comというドメイン名になっており、今回はここを、<bucket名>.s3-ap-northeast-1.amazonaws.comに変更してやる必要があるということ。
リージョン指定なしのドメイン名では、CloudFrontの伝播が完了するまではS3のURLにリダイレクトされる仕様になっているため、S3を公開せずにCloudFrontで表示させたい今回のような場合、最初の1日間はうまく表示ができなくなる。リージョンを指定する形に変更すれば、この問題を解決することができる。
041.png
CloudFront⇒S3間はOrigin Access Identityという仕組みを使って、CloudFrontからのみS3を表示できるような構成とする。CloudFrontから設定をする場合は、赤枠で囲まれた3つのオプションを有効にするだけで、OAIの作成とS3のバケットポリシーの更新をやってくれるので、楽に設定が可能。
042.png
デフォルトBehaviorのキャッシュの設定としては、今回はキャッシュを持たせないようにしたいので、Managed CachingDisabledを選択する。
043.png
あとは、Distribution SettingsでDefault Root Objectにindex.htmlを指定し、ディストリビューションを作成する。
044.png
これでディストリビューションのベースは完成したので、細かい部分を追加していく。
作成されたディストリビューション名をクリックすると詳細画面に遷移することができる。
045.png
046.png
Originとして先ほど作成したALBを追加したいので、Create Originを押下
047.png
Origin Domain Nameには、作成したALBを指定する。ここは、素直にドロップダウンリストから選択してしまってOK。
HTTP Portは、ALBのリスナーポートと合わせる必要があるので8000番を指定する。
048.png
また、origin Custom Headersでは、WAFで指定したHeaderのフィールド名と値を入力する。
この手順通り進めている場合は、
Header Name : x-pre-shared-key
Value : sample-string
を入力する。
049.png
これでALBもOriginとして追加することができた。
今度は、Behaviorsの設定を追加して、どんなパスでアクセスが来た時にALBにアクセスをルーティングするかという部分を追加していく。

Path Pattern api* admin* static/admin* static/rest_framework*
Origin or Origin Group ALBOrigin ALBOrigin ALBOrigin ALBOrigin
Allowed HTTP Methods GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE GET, HEAD GET, HEAD
Cache Policy Managed-CachingDisabled Managed-CachingDisabled Managed-CachingDisabled Managed-CachingDisabled
Origin Request Policy Managed-AllViewer Managed-AllViewer Managed-CORS-CustomOrigin Managed-CORS-CustomOrigin

この下から、スクリーンショット3枚分は、↑の表に書いてある列の数だけ繰り返し実施する。スクリーンショットは、代表してapi*のパスパターンの場合の構築時のスクリーンショットを掲載している。
050.png
051.png
052.png

表のとおり、4つのbehaviorを追加すると、下図のようになる。
053.png

054.png
最後に、Error Pagesというタブに移動して、Error時の挙動を追加する。
055.png
403エラーが出た場合は改めて/のページに飛ばすような設定をいれる。
056.png
以上で設定は完了なので、EC2サーバに戻ってdocker-compose upでDjango REST Frameworkを立ち上げた後、

docker-compose up

CloudFrontのドメイン名にアクセスをすると。。。
057.png
無事、想定通りの画面が表示された。
今は表示をさせるだけのアプリになってしまっているので、今後はTODOを登録する用のテキストボックスや、Delete機能を追加していこうと思う。

つづく

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?