2020年8月6日木曜日

AppMeshを試す環境(ECS+SpotFleet)をCloudformationで構築してみた


AppMeshでX-RAY(CloudWatch->ServiceLens)を試したいと思い、下図構成の環境を作りました。
これは、構築時のメモです。


## アプリケーション

動作確認用のサンプルアプリケーションは、bffとapi01, api02を用意しました。
  • bffでログイン画面を表示し、api01で認証を行います。
  • api02はjwt認証でアクセス制限します。
  • 認証に成功すると、bffからapi01とapi02を実行して結果をダッシュボード画面を表示します。
  • bff, api01は、pythonのflaskで作成し、ECSで実行します。
  • api02は、ApiGateway(HttpApi)でLamdaを実行します。
  • ユーザ認証は、Cognitoを使用します。

## インフラ

費用を抑えた構成にしています。本番運用は意識しません。
  • 構築は、Cloudformationで行いました。
  • ECSはEC2タイプを使用し、EC2はSpotFreetにしています。
  • NatGatewayのかわりにEC2 Nat Instance(Spot Instance)を使用しています。
  • bffでApiGatewayを使用しているのは、費用を抑えるためです。ALBだと費用が発生するので。
  • 上記構成だと、大量にアクセスせず、1日12時間以内に環境を削除すれば、費用が発生するのはSpotInstance, ECRぐらいです。ちなみに、12時間以上環境を使用するとCloudMap(Route53)の費用が発生するそうです。

環境構築手順


## 作業環境(ローカル環境)

構築作業は、WSL2(ubuntu20.04)で実施します。
なお、AWSのリージョンは us-east-1を使用します。
  • Windows10
  • WSL2(ubuntu20.04)
  • docker 19.03.12
  • docker-compose 1.25.0
  • make 4.2.1
  • aws-cli 1.17.14

1. aws-cli の設定


aws-cli実行時はプロファイルを指定します。プロファイルの設定は下記のとおり。
  • blue21 という名称でプロファイルを作成
  • リージョンは us-east-1 を使用し, Output形式は json
  • 管理者権限のアクセスキーを使用

2. ソース一式のdownload


環境構築に必要なCloudformationテンプレートやサンプルアプリケーションなどのソース一式は、Bitbucketに登録しています。
Bitbucketの下記URLからダウンロードできます。

3. app.env の作成


サンプルアプリケーション用の設定ファイルを作成します。この設定ファイルはインフラ構築時にも使用します。

テンプレートをコピーします。
$ cd app
$ cp app.env.template app.env
app.env の設定値を修正します。
  • COGNITOからメール送信するので正しいメールアドレスを設定すること
  • AWSアクセスキーは、COGNITO, X-RAYの権限が必要
#--- アプリケーション用
#--- アクセスキー、リージョンを記載してください
AWS_ACCESS_KEY_ID=<ACCESS KEY>
AWS_SECRET_ACCESS_KEY=<SECRET KEY>
AWS_DEFAULT_REGION=<REGION>

#--- xray daemon用
#--- リージョンを記載してください
AWS_REGION=<REGION>

#--- COGNITO構築用
#--- UserPoolに登録するユーザ情報を記載してください
DEMO_USER_NAME=<COGNITO USER. ex:demo>
DEMO_USER_GROUP=<COGNITO GROUP. ex:developers>
DEMO_USER_EMAIL=<COGNITO EMAIL. ex:demo@gmail.com>
DEMO_USER_PASSWORD=<COGNITO USER's PASSWORD. ex:Passw0rd!>

#--- アプリケーション用(apl-->cognito)
#--- 編集しないこと. make cfncgnt が更新します
USER_POOL_ID=<COGNITO USER POOL ID>
CLIENT_ID=<COGNITO USER POOL CLIENT-ID>

#--- アプリケーション用(apl-->xray daemon)
#--- 編集しないこと
AWS_XRAY_DAEMON_ADDRESS=xray:2000

4. COGNITOの構築


Cloudformationで構築するCOGNITOは、IdPool と UserPoolです。
スタック名は次のとおり
  • スタック名:  Blue21Cgnt00PoC
COGNITOのリソース名は次のとおり(スタック名と同じです)
  • IdPool: Blue21Cgnt00PoC
  • UserPool: Blue21Cgnt00PoC

COGNITOを構築して、app.env に設定したユーザ(DEMO_USER_*)をUserPoolに登録します。
cfn ディレクトリで下記コマンドを実行すると
COGNITOが構築され、app.env の USER_POOL_ID, CLIENT_ID が自動的に更新されます。
$ cd cfn
$ make cfncgnt
makeコマンドを実行すると下図のように表示されます。
aws-cli のパラメータを、app.env から取得してcreate-stackした後、stack_status が "CREATE_COMPLETE" になるまで待機します。
そして、aws-cli の実行終了後、app.env の USER_POOL_ID, CLIENT_ID を更新します。



cfn ディレクトリで下記コマンドを実行し、COGNITOに登録したユーザのステータスを確認します。
UserStatus が "FORCE_CHANGE_PASSWORD"と表示されるはずです。
この状態だとユーザ登録が未完了です。
$ cd cfn
$ make userstat

cfn ディレクトリで下記コマンドを実行し、COGNITOからパスワードリセット用のメールを送信します。
$ cd cfn
$ make usermail
下図のようにパスワードがメールされます。


COGNITOに登録したユーザのパスワードを、app.env に設定したパスワードに変更します。
メールに記載されたリセット用のパスワードを指定して下記コマンドを実行します。
$ cd cfn
$ make userpwd RESETPWD='X>Oa5dkR'
COGNITOに登録したユーザのステータスを確認します。
UserStatus が "CONFIRMED" ならOKです。app.env に設定したパスワードに変更されています。
$ cd cfn
$ make userstat

以上でCOGNITOの構築は完了です。


5.ローカル環境で動作確認


COGNITOの構築が完了すると、ローカル環境(下図参照)でサンプルアプリケーションの動作確認ができるようになります。
AWSにアプリケーションをデプロイする前に、ローカル環境で動作確認します。

注)ローカル環境に api02(ApiGateway+Lambda)はありません。


5.1. Dockerイメージのビルド


app ディレクトリで下記コマンドを実行します。
$ cd app
$ docker-compose build
下記のdockerイメージが作成されます。
$ docker images | grep blue21
blue21/api_flask                                                latest              840f37b0af4e        4 days ago          537MB
blue21/bff_flask                                                latest              87f9991ee352        5 days ago          345MB
blue21/api_nginx                                                latest              6d7b5f2b4650        7 days ago          132MB

5.2. Docker環境の起動

appディレクトリで下記コマンドを実行し、ローカル環境を起動します。
ターミナルで、リアルタイムにログを見たい場合は -d はつけません。
$ docker-compose up -d

5.3. 動作確認

ブラウザで http://localhost:5000 にアクセスすると、下図のログイン画面が表示されます。


app.env に設定したユーザとパスワードを入力して、LOGINボタンをクリックします。
下図のダッシュボード画面が表示されたらOKです。


ちなみに、
ローカル環境のアプリケーションからxrayデーモン(コンテナ)経由で X-RAY にトレース情報を送信してます。
下記ページを参照してPythonのライブラリにxrayのpatchをあててます。
AWSコンソールのCloudWatch->ServiceLens->サービスマップを見ると下図のように表示されます。(今回は CloudWatchでX-RAYの確認をします)



[MEMO]
私の環境(WSL2)だと。。。
  • WiFiで接続しているときは、xray デーモンの通信は問題ない。(スマホのテザリング)
  • LANケーブル接続は、xrayデーモンの通信エラーになる(フレッツ光IPv6だからか?)

6. ECRの構築


CloudformationでECRを構築します。
スタック名は次のとおり
  • スタック名:  Blue21Cgnt00PoC
レポジトリ名は次のとおり
  • blue21/bff_flask
  • blue21/api_nginx
  • blue21/api_flask

cfnディレクトリで下記コマンドを実行し、ECRを構築します。
このコマンドは、stack_statusが "CREATE_COMPLETE" になるまで wait します。 
$ cd cfn
$ make cfnecr

アプリケーションのDockerイメージをECRに登録します。
cfnディレクトリで下記コマンドを実行します。
$ cd cfn
$ make ecrbff
$ make ecrnginx
$ make ecrapi

cfnディレクトリで下記コマンドを実行し、ECRに登録されたイメージを表示します。
$ cd cfn
$ make ecrls

latestタグのイメージが3つ表示されたらECRの構築は完了です。

7. アプリケーション実行環境の構築


VPC, CloudMap, AppMesh,ECS, ApiGateway など、アプリケーションの実行環境をCloudformation で構築します。
Cloudformationで作成するスタックは次のとおりです。
各種リソース名には、スタック名が含まれます。
  • スタック名: Blue21Vpc00PoC
    VPC,サブネットなどネットワーク環境を構築します。
  • スタック名: Blue21Nat00PoC
    Nat Instanceを構築します。Spotインスタンスを使用します。
  • スタック名: Blue21Cmap00PoC
    CloudMap, AppMeshを構築します。
  • スタック名: Blue21Ecs00PoC
    ECSクラスタを構築します。EC2タイプでSpotFleetを使用します。
  • スタック名: Blue21Api01PoC
    ECSサービスを構築します。api01 です。
  • スタック名: Blue21Api02PoC
    ApiGateway+Lambda を構築します。api02です。
  • スタック名: Blue21Bff00PoC
    ECSサービスを構築します。bffです。

AppMeshの構築イメージは下図のとおり
  • api01だけRouteを設定したのは再試行を試したいからです。
  • bffをsrvレコードにしたのは、ApiGatway(HTTP_API)のプライベート統合用です。
  • api02の設定は失敗です。ApiGatewayのエンドポイントがSSLエラーになります。
    ApiGatewayをカスタムドメインにして、CloudmapをPublicNamespaceにすればよかったかもしれない。
  • AppMeshは「外部トラフィック無効」にしているので、bffのbackendにapi01, api02を設定しないと、bffからapi01,api02にアクセスできない。

cfnディレクトリで下記コマンドを実行すると、前述したスタックを順次作成します。
所要時間は20分ぐらいです。
なお、EC2用にキーペア(名称:virginia_key)を使用するので、事前に同じ名前で用意するかMakefileに記述したaws-cliコマンドのパラメータでキーペアを変更してください。
$ cd cfn
$ make cfnapps

cfnディレクトリで下記コマンドを実行し、スタックの状態を表示します。
$ cd cfn
$ make stackls
aws cloudformation describe-stacks \
--profile blue21 \
--query 'Stacks[].{StackName:StackName,StackStatus:StackStatus,Desc:Description}' \
--output table
------------------------------------------------------------------------
|                            DescribeStacks                            |
+------------------------------+-------------------+-------------------+
|             Desc             |     StackName     |    StackStatus    |
+------------------------------+-------------------+-------------------+
|  bff (Ecs service)           |  Blue21Bff00PoC   |  CREATE_COMPLETE  |
|  api02 (ApiGateway)          |  Blue21Api02PoC   |  CREATE_COMPLETE  |
|  api01 (Ecs service)         |  Blue21Api01PoC   |  CREATE_COMPLETE  |
|  Ecs cluster by EC2 SpotFleet|  Blue21Ecs00PoC   |  CREATE_COMPLETE  |
|  Cloudmap & AppMesh          |  Blue21Cmap00PoC  |  CREATE_COMPLETE  |
|  EC2 NatInstance             |  Blue21Nat00PoC   |  CREATE_COMPLETE  |
|  VPC & subnet                |  Blue21Vpc00PoC   |  CREATE_COMPLETE  |
|  ECR                         |  Blue21Ecr00PoC   |  CREATE_COMPLETE  |
|  Cognito & demo user/group   |  Blue21Cgnt00PoC  |  CREATE_COMPLETE  |
+------------------------------+-------------------+-------------------+
すべて "CREATE_COMPLETE" であればOKです。
以上で、環境構築は完了です。

8. アプリケーションの動作確認


bff のエンドポイントを確認します。
下図のようにCloudformation のスタック(Blue21Bff00PoC)の出力タブを表示します。


HttpApiEndpoint に表示されているURLをクリックするとログイン画面が表示されます。
あとは、ローカル環境と同じように操作して動作確認します。

9. X-RAY の確認


CloudWatch->ServiceLens->サービスマップを表示します。
bff にアクセスする前は、下図のとおり。ヘルスチェックが表示されています。


ログイン操作をすると、下図のとおり。
アプリケーションとAppMeshで送信してるトレース情報が、別々に表示されてます。
もっと、いいかんじに、マージにされることを期待してましたが、、


ログイン認証のトレースを見ると下図のとおり。
bff -> api01 -> cognito までの流れをトレースすることができます。


[MEMO]
  • ApiGatewayに、xray 対応のREST-API を使用したら、もっと違う見え方をするかもしれない。

10. AWS環境のあとかたずけ


下記のスタックを上から順番に削除します。
(削除が完了してから、次のスタックを削除)
  • ECR は、登録したイメージをAWSコンソールで削除してからスタックを削除すること。イメージが残ってるとスタック削除できない。
  • COGNITOのスタックを削除しても、CloudWatchLogsの/aws/lambda/Blue21Cgnt00PoCが残ってしまうので、これはAWSコンソールで削除する。
    このロググループが残ってると、次回、COGNITO構築時にCloudFormationがエラーになる。
------------------------------------------------------------------------
|                            DescribeStacks                            |
+------------------------------+-------------------+-------------------+
|             Desc             |     StackName     |    StackStatus    |
+------------------------------+-------------------+-------------------+
|  bff (Ecs service)           |  Blue21Bff00PoC   |  CREATE_COMPLETE  |
|  api02 (ApiGateway)          |  Blue21Api02PoC   |  CREATE_COMPLETE  |
|  api01 (Ecs service)         |  Blue21Api01PoC   |  CREATE_COMPLETE  |
|  Ecs cluster by EC2 SpotFleet|  Blue21Ecs00PoC   |  CREATE_COMPLETE  |
|  Cloudmap & AppMesh          |  Blue21Cmap00PoC  |  CREATE_COMPLETE  |
|  EC2 NatInstance             |  Blue21Nat00PoC   |  CREATE_COMPLETE  |
|  VPC & subnet                |  Blue21Vpc00PoC   |  CREATE_COMPLETE  |
|  ECR                         |  Blue21Ecr00PoC   |  CREATE_COMPLETE  |
|  Cognito & demo user/group   |  Blue21Cgnt00PoC  |  CREATE_COMPLETE  |
+------------------------------+-------------------+-------------------+

Appendix. 構築時のトラブル

  • AppMeshのVirtualServiceName
  • ECSサービスのデプロイ
    • ローリング更新だが、デフォルトだと新コンテナ1台増えてから古いコンテナが削除される。
      これだと、EC2(m3.small) のNICが不足してエラーになるので、古いコンテナを削除してから新コンテナを起動するようにしている。

Appendix. 構築時の参考サイト