Alice in Cloud

外資系ITコンサルタントのブログです

FlaskアプリをAWS ACMでSSL化しながらFargateで公開する全ステップ (後編)

FlaskアプリをAWS ACMSSL化しながらFargateで公開する全ステップの後編です。Flaskアプリ公開まで一気に行きましょう。

アーキテクチャ(再掲)


実装手順(グレーアウト部分は前編で実施済み)
Step 項目 対象サービス
1 ドメイン発行Route 53
2 証明書発行ACM、Route53
3 VPC作成VPC
4 セキュリティグループ作成セキュリティグループ
5 VPCエンドポイント作成VPCエンドポイント
6 ALB作成ALB
7 コンテナイメージ作成 ローカルマシンなど
8 レポジトリ作成とイメージアップロード ECR
9 コンテナ作成 ECS/Fargate
10 ALBのリスナー修正 ALB
11 CNAMEレコード登録 Route 53
12 テスト テスト
7. コンテナイメージ作成

Flaskのコンテナイメージを作成します。サンプルとしてHello Worldを表示するFlaskのイメージをローカルマシンで作成します。

ディレクトリ構造

flask-testというフォルダを作成し、その中に次のような階層でコードを配置します。

dockerfile
FROM python:3.8.11-slim-buster

RUN pip install --upgrade pip
RUN pip install flask==2.0.1
RUN pip install pillow==8.00

ENV PORT 80

WORKDIR /app

COPY ./app/ /app

CMD ["python", "app.py"]
app.py
import os 

from flask import Flask


port = int(os.environ['PORT'])
app = Flask(__name__)

@app.route('/')
def index():
   return 'Hello World!!!'

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=port)

dockerをまだ導入していない方は、Docker Desktopを先に導入してください。私の実行環境はMacですが、ターミナルを開いてflask-hwの階層に移動し、以下コマンドを投入してください。

% docker image build -t flask-hw .
% docker image ls
REPOSITORY                                                       TAG       IMAGE ID       CREATED          SIZE
flask-hw                                                         latest    2d783442b531   10 seconds ago   146MB

続いて以下コマンドを実行しコンテナを起動させます。

% docker container run --rm -d -p 5000:80 -v ${PWD}/app:/app --name flask-hw flask-hw

ブラウザでlocalhost:5000と入力すると以下の様にHello Worldが表示されるはずです。今しがた作ったイメージからコンテナを作成し、ポート5000→80と変換しアクセスしています。

無事ローカルでテストできたら、稼働しているコンテナを停止しておきます。

% docker ps -a                                                                       
CONTAINER ID   IMAGE      COMMAND           CREATED         STATUS         PORTS                                   NAMES
06f87ee03035   flask-hw   "python app.py"   7 minutes ago   Up 7 minutes   0.0.0.0:5000->80/tcp, :::5000->80/tcp   flask-hw
% docker stop  06f87ee03035
06f87ee03035
8. レポジトリ作成とイメージアップロード

ECRのプライベートレポジトリを作成し、7で作成したイメージをアップします。ローカルマシンでAWS CLIを叩く必要があるので、導入されていない方はインストールしてください。

Amazon ECR < リポジトリ < リポジトリを作成から、リポジトリを作成していきます。リポジトリ名は先程作成したイメージに合わせると後の手順が簡単です。イメージスキャンや暗号化は推奨項目ではありますが、今回は主題と逸れるので省略します。

リポジトリが作成できたら「プッシュコマンドの表示」をクリックし、プッシュコマンドを表示します。表示されたコマンドをローカルマシンのターミナルで実行するだけですが、ECRにイメージをプッシュするための権限が必要です。簡単な例として、必要な権限を付与したIAMユーザを作成し、アクセスキーを発行してaws configureコマンドで設定しておきましょう。

コマンド実行結果はこんな感じです。

% aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com
Login Succeeded
% docker build -t flask-hw .
[+] Building 2.2s (12/12) FINISHED                                                                                                                                      
 => [internal] load build definition from Dockerfile                                                                                                               0.0s
 => => transferring dockerfile: 177B                                                                                                                               0.0s
 => [internal] load .dockerignore                                                                                                                                  0.0s
 => => transferring context: 2B                                                                                                                                    0.0s
 => [internal] load metadata for docker.io/library/python:3.8.11-slim-buster                                                                                       2.1s
 => [auth] library/python:pull token for registry-1.docker.io                                                                                                      0.0s
 => [1/6] FROM docker.io/library/python:3.8.11-slim-buster@sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx                                 0.0s
 => [internal] load build context                                                                                                                                  0.0s
 => => transferring context: 193B                                                                                                                                  0.0s
 => CACHED [2/6] RUN pip install --upgrade pip                                                                                                                     0.0s
 => CACHED [3/6] RUN pip install flask==2.0.1                                                                                                                      0.0s
 => CACHED [4/6] RUN pip install pillow==8.00                                                                                                                      0.0s
 => CACHED [5/6] WORKDIR /app                                                                                                                                      0.0s
 => CACHED [6/6] COPY ./app/ /app                                                                                                                                  0.0s
 => exporting to image                                                                                                                                             0.0s
 => => exporting layers                                                                                                                                            0.0s
 => => writing image sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx                                                                  0.0s
 => => naming to docker.io/library/flask-hw                                                                                                                        0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
% docker tag flask-hw:latest 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/flask-hw:latest
% docker push 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/flask-hw:latest
The push refers to repository [123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/flask-hw]
xxxxxxxxxxxx: Pushed 
xxxxxxxxxxxx: Pushed 
xxxxxxxxxxxx: Pushed 
xxxxxxxxxxxx: Pushed 
xxxxxxxxxxxx: Pushed 
xxxxxxxxxxxx: Pushed 
xxxxxxxxxxxx: Pushed 
xxxxxxxxxxxx: Pushed 
xxxxxxxxxxxx: Pushed 
xxxxxxxxxxxx: Pushed 
latest: digest: sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx size: 2417
% 

ECR側でもイメージがプッシュされました。

9. コンテナ作成

いよいよアプリが稼働するコンテナを作っていきます。AWSでのコンテナ実行環境としてECSを利用し、ホストマシンの管理が不要なFargateを使っていきます。
ECS<クラスター<クラスターの作成から、クラスターを作成します。Fargateを使いたいので、ネットワーキングのみを選択します。

前編作成したVPCを利用するため、VPCの作成はチェックしません。

続いてタスク定義<新しいタスクの定義を行います。Fargateを選択します。

パラメータを設定していきます。特にコンテナがAWS操作をするわけではないので、タスクロールは不要です。

CPUやメモリをお好みの値を入れて、「コンテナの追加」をクリックします。

ECRへ先程アップしたイメージのURIをコピーして入力します。またALBでSSL終端された後、ALBからコンテナへのアクセスはHTTPとなるためポートマッピングに80(HTTP)を入力しておきます。残りはデフォルトのままとし、コンテナの追加<タスク定義の作成と進みタスク定義作成を完了させます。

作成したタスクをもとにコンテナを作成するためにサービスを作成します。起動タイプはFargateとし、任意のサービス名とタスクの数(コンテナの数)を入力し次に行きます。


続いてネットワーク関連の設定です。前編で作成したVPC、サブネット(コンテナ用なのでプライベートサブネットx2)、SG(ECS用)を選択します。

続いてロードバランサーの設定です。ここが一番わかりにくい。。。ALBを選択するとタスク定義で設定したポート(今回は80番)の設定ができるので、「ロードバランサーの追加」をクリックします。

画像のように設定します。リスナーポートとはインターネットからALBへのアクセスを受け付けるポートのことなので本当はHTTPSなのですが、なぜかACMの証明書が選択できません。一旦HTTPでリスナーポートの設定をし、後ほど設定変更します。

Auto-scalingはスキップし、サービスの作成を完了します。サービス作成が完了すると、サービスで設定した数のタスク(コンテナ)が自動で作成されます。

上手くNW周りの設定が出来ていればタスクがRUNNINGとなります。ここがPENDINGのままになっていたり、何度もタスクの作成削除が繰り返されて不安定になる場合NW周りの設定に問題がある可能性があります。挙動として、VPC内のクラスターがECRやS3にエンドポイント経由でアクセスしイメージを取得してくるので、エンドポイントやSGなどで通信がブロックされているなどが考えられます。

10. ALBのリスナー修正

リスナーポートを443に修正するため、前編で作成したALBを選択し、ECSが作成したリスナーの設定を変更します。

プロトコルがHTTPになっていると思いますのでHTTPSに修正します。

下段のACMの部分で前編で作成したACMの証明書を選択し、変更内容を保存します。

またHTTPでリクエストが有った場合にHTTPSへリダイレクトする処理を追加します。リスナー追加をクリックし次のように設定します。これによりHTTPSでの接続を強制できます。

設定が完了したら、ALBの説明タブの中からDNS名をコピーしておきます。

11. CNAMEレコード登録

さて、いよいよ最後のステップです。Route 53に遷移し、ACMを発行したFlask アプリのドメインをCNAMEレコードでALBのDNS名に紐付けます。


12. テスト

ここまで来たらテストしてみましょう。ステップ11でRoute 53に登録したドメインに対してブラウザで接続してみましょう(https://www.xyz.comなど)。Hello Worldの画面が表示されれば無事設定完了です。

FlaskアプリをAWS ACMでSSL化しながらFargateで公開する全ステップ (前編)

Flaskで開発したWebアプリをFargateに乗せ、かつAWS ACMSSL対応する記事に巡り会えなかったので、趣味で開発したアプリを公開する傍らで記事にしてみました。

アーキテクチャ

よくあるWEBアプリのベストプラクティスのようなアーキテクチャです。

実装手順
Step 項目 対象サービス
1 ドメイン発行 Route 53
2 証明書発行 ACM、Route53
3 VPC作成 VPC
4 セキュリティグループ作成 セキュリティグループ
5 VPCエンドポイント作成 VPCエンドポイント
6 ALB作成 ALB
7 コンテナイメージ作成 ローカルマシンなど
8 レポジトリ作成とイメージアップロード ECR
9 コンテナ作成 ECS/Fargate
10 ALBのリスナー修正 ALB
11 CNAMEレコード登録 Route 53
12 テスト テスト
1. ドメイン発行

既にドメインをお持ちの方はこのステップはスキップしてください。
ドメインをお持ちでない方は、公開するWEBアプリに用いるドメインを発行する必要があります。ドメインにもよりますが10ドル程かかりますので、検証目的の方はご注意を。
手順は簡単で、Route 53>登録済みのドメインドメインの登録へ遷移します。

お好きなドメインを購入します。購入後の注意点として、ドメイン管理者のメールアドレスに対してメールが飛んできますので、忘れず検証URLをクリックします。


ドメインが利用できるまで数日かかる場合があります。なのではじめのステップに持ってきています。

2. 証明書発行

続いて証明書発行まで行ってしまいましょう。ACM<リクエストと遷移。

パブリック証明書を選択し、任意のドメイン(www.abc.comなど)を入力します。証明書の検証はDNSを選択します。そのまま進み証明書を発行します。発行した証明書はそのままではまだ未検証で利用することができません。そこで証明書画面からダウンロードできるDNS検証用のCNAMEのパラメータを取得します。

Route 53に戻り、ホストゾーン<対象のドメインと遷移します。先程取得したDNS検証用のパラメータを使ってCNAMEレコードを作成します。それぞれのパラメータは先頭が半角アンダースコア(_)がついているので注意してください。
検証が完了するまで少し時間がかかるので、次のステップに進んで後で確認してみてください。

3. VPC作成

お次は仮想ネットワークを作っていきます。お手軽にVPC環境が作れるウィザードに任せてサクッと作ります。以前と比べてとても簡単になりましたね。

4. セキュリティグループ作成

以下に記載する順序通りにセキュリティグループ(以下SG)を作っていきます。SGはポート番号やIPアドレス、またはIPアドレスの代わりに別のSGを指定してルールを作成できます。SGを指定してルールを作成すれば、細かくIPアドレスをルールに記載する必要がなくなり運用が楽になります。以下表にまとめている3件のSGを作成します。インバウンドルールのみ設定し、アウトバウンドは全てデフォルトのまま全許可で使用します(なのでアウトバウンドは特に記載していません)。

ALB用SG
タイプ プロトコル ポート範囲 ソース
HTTP TCP 80 0.0.0.0/0
HTTPS TCP 443 0.0.0.0/0
ECS用SG
タイプ プロトコル ポート範囲 ソース
すべてのトラフィック すべて すべて ALB用SG
VPCエンドポイント用SG
タイプ プロトコル ポート範囲 ソース
すべてのトラフィック すべて すべて ECS用SG
5. VPCエンドポイント作成

ECS/Fargateのコンテナはプライベートサブネットに配置するため、コンテナイメージの取得やCloudWatch Logsへのログ出力はエンドポイント経由で行うか、NATゲートウェイなどを用いてインターネット経由で接続できる必要があります。今回はセキュリティの求められる環境を想定し、VPCエンドポイントを用います。

VPC > エンドポイント > エンドポイントを作成に遷移し、任意の名前をつけ「ecr」で検索し、「com.amazonaws.ap-northeast-1.ecr.api」を見つけてチェックします。
VPCを選択したらサブネットを選ぶ画面が出てくるのですが、プライベートサブネットが選択されていることを確認してください。

SGは先ほど作成したエンドポイント用SGを選択し、ポリシーは一旦フルアクセスのままとし、「エンドポイント作成」をクリックします。少し時間が経てばエンドポイントが利用できるようになります。
この通り一つ一つエンドポイントを必要な分だけ作成します。今回は4つのエンドポイントを作成します。S3のみゲートウェイタイプ、それ以外はインターフェースタイプで作成します。

6. ALB作成

続いてALBを作成します。EC2<ロードバランサー<Application Load Balancerから作成します。用語が少しわかりにくいのですが、いつの間にかこんなページが追加されていました。

任意の名前を入力、VPCを選択してパブリックサブネットを2つ以上選択します。

SGは先程作成したものを選択します。次にリスナーを設定するのですが、事前にターゲットグループを作成する必要がありますので、「ターゲットグループの作成」をクリックします。ただしこの時点でまだFargateのコンテナを作成していないので、取り急ぎALBの作成が完了できるようダミーのターゲットグループを作成します。パラメータは何でもいいのでターゲットグループを作成します。

ALB側でダミーのターゲットグループを作成したら、ALB作成を完了します。

作成が完了したALBを確認し、いま設定したリスナーを削除します。またターゲットグループに遷移し、ダミーのターゲットグループも削除します。


かなり長くなってしまったので、一旦ここで切ります。よろしければ後半もご確認ください。

Mac のローカル環境を汚さずに Jupyter notebook を使う


これから Python でゴリゴリ開発をしていきたい、という方はローカルマシンに色々なパッケージを入れている方も多いかもしれません。 この場合色々な開発に手を出せば出すほど、ローカルマシンの環境が汚れていきますし、 導入したパッケージが他のプログラムに影響を及ぼすこともあるかもしれません。

このため、これから Python 開発を行う場合、是非コンテナを利用することをオススメします。 この場合、どれだけ自由にパッケージをインストールしてもローカルマシンには影響せず、 自由に環境を作って捨ててができるようになります。

自分メモも兼ねて簡単な手順を記載しておきます。 是非試して見てください。

1: Docker Desktop をインストールする
2: Jupyter notebook が利用できる Docker Image を Pull します。

docker pull jupyter/scipy-notebook

3: Pull したコンテナイメージを使ってコンテナを起動します

docker run -p 10000:8888 --name jupyter jupyter/spicy-notebook
ターミナルに起動ログが流れますが、以下xxxxxxxxの箇所に記載のあるトークン(文字列)をコピーしてください

    To access the server, open this file in a browser:
        file:///home/jovyan/.local/share/jupyter/runtime/jpserver-9-open.html
    Or copy and paste one of these URLs:
        http://1234567890ab:8888/lab?token=xxxxxxxx
     or http://127.0.0.1:8888/lab?token=xxxxxxxx
4: ブラウザで localhost:10000 にアクセスします

先程コピーしたトークンを入力してログインします

5: Notebook で Python を選択し、あとは自由に開発を進めましょう

6: 開発が終わったら、起動しているコンテナを削除しておきましょう

起動しているコンテナを確認
docker pa -a
起動しているコンテナの廃止
docker rm --force <コンテナID>