koboriakira.com

チュートリアル実践「現代的なウェブアプリケーションの構築」

September 23, 2020

はじめに

Amazonが提供する現代的なウェブアプリケーションの構築を読み進めることにした。

なお実践はCloud9ではなくローカル端末で試すことにした。

また請求が発生する可能性があるため、早めに済ませること。EC2でめちゃくちゃお金がかかった。

モジュール1「静的ウェブサイトの構築」

(前提)AWS CLIの設定

AWS CLIを使う必要があるのでaws configureでAWSの設定ファイル(.aws)を作成した。アクセスキーが古くなってきたので新しくした。

(前提)S3とは

S3 は、保存されたオブジェクトをHTTP経由で直接提供できる、耐久性と可用性に優れた、低コストのオブジェクトストレージサービスです。

「ファイルをHTTPで転送できる」というのがポイント。つまりWebサイトにもなりうる。

S3バケットの作成

aws s3 mbコマンドでS3バケットを作成した。バケット名はaws-modern-application-workshop-koboriにしたので、具体的には次のコマンドを打つ。

aws s3 mb s3://aws-modern-application-workshop-kobori

S3バケットの設定

静的ウェブサイトのホスティングができるように、「ウェブサイトとして利用」と「外部からのアクセスを許可」の設定をする。

ホスティング

aws s3 websiteコマンドで静的Webサイトのホスティング設定をする。--index-documentオプションはindex.htmlを指定。

外部からのアクセスを許可

バケットを作成した時点ではプライベート(外部からはアクセスできない)ため、S3バケットポリシーを変更する必要がある。

バケットポリシーは一般的にはJSONファイルを定義して、これをaws s3api put-bucket-policyコマンドで送るのがいいみたい。そのためJSONファイルを次のように送った

aws s3api put-bucket-policy --bucket aws-modern-application-workshop-kobori --policy file://JSONファイル

JSONファイルの中身は下記の通り。

{
  "Id": "MyPolicy",
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadForGetBucketObjects",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::aws-modern-application-workshop-kobori/*"
    }
  ]
}

index.htmlをデプロイ

最後に実際に利用されるindex.htmlをS3バケットに配置する。これはaws s3 cpコマンドで設定。

cpコマンドと同じなので、具体的には次のようにした。

aws s3 cp ローカルのindex.html s3://aws-modern-application-workshop-kobori/index.html

これでウェブサイトが構築できた。というかこんなに簡単なのか。心配なのは金額だけ。

調子がよかったので「モジュール 2: ウェブサーバーでのアプリケーションのホスト」もちょっと読んでみよう。実際のメモは明日以降に。

モジュール2「静的ウェブサイトの構築」

(前提)各サービス、用語の概要

  • AWS CloudFormation: Amazon Web Servicesリソースのモデル化およびセットアップのサービス。インフラストラクチャ管理を簡略化する。
  • NLB: Network Load Balancer
  • Amazon ECR: Elastic Container Registry、完全マネージド型のDockerコンテナレジストリです。このレジストリを使うと、開発者はDockerコンテナイメージを簡単に保存、管理、デプロイできます。
  • Amazon ECS: Elastic Container Service、フルマネージド型のコンテナオーケストレーションサービスです。
  • AWS CodeCommit: Gitベースのリポジトリをセキュアにホストする完全マネージド型のソース管理サービスです。
  • AWS CodeBuild: ソースコードをコンパイルし、テストを実行し、デプロイ可能なソフトウェアパッケージを作成できる完全マネージド型のビルドサービスです。
  • AWS CodePipeline: 完全マネージド型の継続的デリバリーサービスで、素早く確実性のあるアプリケーションとインフラストラクチャのアップデートのための、パイプラインのリリースを自動化します

モジュール2A: CloudFormationのテンプレートをデプロイする

aws cloudformation create-stackコマンドで、ローカルにあるテンプレートのymlとIAMリソースをつかってスタックを作成する。

aws cloudformation create-stack --stack-name MythicalMysfitsCoreStack --capabilities CAPABILITY_NAMED_IAM --template-body file://module-2/cfn/core.yml

スタックとは、

スタックは、単一のユニットとして管理できる AWS リソースのコレクションです。 つまり、スタックを作成、更新、削除することで、リソースのコレクションを作成、更新、削除できます。 … リソースが作成できない場合、AWS CloudFormation はスタックをロールバックし、作成されたリソースを自動的に削除します。

ちなみにスタックをつくるときにIAMロール(ポリシー)でつまづいた。EC2とIAMに関してフルアクセスの権限を付与して解決したが、ベストプラクティスがわからない。このあたりを勉強すべきか迷う。

モジュール2B: サービスをNLB経由でデプロイ・公開する

aws ecr create-repositoryコマンドでリポジトリを作成する。AmazonEC2ContainerRegistryFullAccessの権限を付与して実行した(FullAccessがベストではない気がするのだが、コマンドとポリシー、パーミッションの関係が見つけられない)。

このあとDockerログインをするのだが、aws ecr get-loginコマンドはセキュリティの関係から(コマンド履歴にパスワードが残る)使えなくなっていた。そのためこちらを参考にして、aws ecr get-login-passwordコマンドでdocker loginを進めた。IssueかPRを出せるといいな。

つぎにECRにプッシュしたイメージをECSにデプロイする。

aws ecs create-clusterでサーバのクラスター、aws logs create-log-groupでロググループをそれぞれ作成する。

与えられたテンプレートを使って、aws ecs register-task-definitionコマンドでECSタスク定義を登録する。

つぎにサービススタックに必要なインフラストラクチャをプロビジョニングする。

プロビジョニングとは、必要に応じてネットワークやコンピューターの設備などのリソースを提供できるよう予測し、準備しておくことです
https://www.idcf.jp/words/provisioning.html

ただ単純に公開するのではなくNLBをかませることで、実際のサービスのスケールイン/スケールアウトを容易にできる。

というわけでaws elbv2 create-load-balancerコマンドでロードバランサーを作成する。サブネットはCloudFormationで作成したスタックから利用。

次にaws elbv2 create-target-groupコマンドでNLBターゲットグループ、aws elbv2 create-listenerコマンドでNLBロードバランサーのリスナーをそれぞれ作成。これで80番ポートが受信したリクエストをターゲットグループに登録されているターゲットに転送することをロードバランサーに知らせる。あまり意味はわかっていないけど、とりあえず書いておこう。

よくわからないけど、はじめてECSを利用する場合はaws iam create-service-linked-role --aws-service-name ecs.amazonaws.comが必要なよう。

ECSで実際のサービスを作成するため、aws ecs create-serviceコマンドを実行する

aws ecs create-service --cli-input-json file://service-definition.json

これで実際のサービス(Flask)にNLBを介してアクセスできるようになった。NLBのDNSでcurlを飛ばしてみると、実際にレスポンスが帰ってくる。

ちなみに最初に時間がかかったのはコンテナを作成・起動しているからか。2度目は一瞬だったので。

このサービスを利用するようにフロントエンドのindex.htmlを修正して、モジュールBは終了。

モジュール2C: CD/CIの設定

上記のまま開発を続けると、デプロイする際にはイメージを更新、プッシュして、またタスク定義を更新しないといけなさそう(その方法はチュートリアルにない)。

そのためCD/CIを設定する。

まずCI/CDアーティファクト用のS3バケットをつくる。バケットポリシーはチュートリアル側で用意してくれたものを使う。GETとPUTを許可しているみたい。

つぎにaws codecommit create-repositoryでCodeCommitリポジトリ、aws codebuild create-projectでCodeBuildプロジェクトを作成。いろんな値をコピペするので忘れても調べられるようにしないといけないな。

最後に、CodeBuildプロジェクトを使用してCodeCommitリポジトリを継続的に統合することと、新しくビルドされたアーティファクトをECSのサービスに継続的に提供するサービスとして、AWS CodePipelineをつくる。

aws codepipeline create-pipelineコマンドで、CodePipelineのパイプラインを作成。

そしてCodeBuildがECRリポジトリにアクセスできるよう設定する。具体的にはaws ecr set-repository-policyコマンドでECRリポジトリのポリシーを設定する。

これで使えるようになったのでテストをする。gitの認証まわりに関する設定を行った後、codecommitの空リポジトリをクローンして、今回のアプリケーションのソースをcommitしてpush。

CodePipelineのコンソールで自動ビルドが行われていることを確認して完了。

モジュール3「情報の保存」

テーブル、データの追加

aws dynamodb create-tableコマンドでデータベースを作成。aws dynamodb scanコマンドでテーブルの中身が見れる(見れるということは作成に成功している証拠)。

aws dynamodb batch-write-itemコマンドでレコードを追加する。scanして追加されたことを確認。

ウェブサイトの更新

つぎにこのDBを使うようにアプリを直してcommit, pushしたのだが、403エラーで失敗してしまった。

どうやらHTTPSでやるとMacのキーチェーンの問題があるようなので、SSHを使って回避するようにした。具体的には、

  1. IAMユーザの「AWS CodeCommit の SSH キー」に公開鍵を設定する
  2. SSHキーIDが発行されるので、これを使った~/.ssh/configを設定(下記参照)
  3. ssh git-codecommit.ap-northeast-1.amazonaws.comで疎通確認

`~/.ssh/config Host git-codecommit.*.amazonaws.com User <SSHキーID> IdentityFile ~/.ssh/id_rsa

またフロントのindex.htmlも更新。

パイプラインが成功したらサイトを確認。API、DBを使ってリストを描画していることを確認して終了。

## モジュール4「ユーザ登録の設定」

### (前提)Amazon Cognitoとは

ウェブアプリケーションおよびモバイルアプリに素早く簡単にユーザーのサインアップ/サインインおよびアクセスコントロールの機能を追加する。

### ユーザプールの作成

`aws cognito-idp create-user-pool`コマンドで、Cognitoユーザープールを作成する。オプションも入れると次の通り。

```bash
aws cognito-idp create-user-pool --pool-name MysfitsUserPool --auto-verified-attributes email

またユーザプールとセットで、aws cognito-idp create-user-pool-clientコマンドで、ユーザープールクライアントも作成する。

aws cognito-idp create-user-pool-client --user-pool-id <ユーザプールのID> --client-name MysfitsUserPoolClient

“Id”: “ap-northeast-1RoHXOwkFB”, “Arn”: “arn:aws:cognito-idp:ap-northeast-1:365487138721:userpool/ap-northeast-1RoHXOwkFB”

{ “UserPoolClient”: { “UserPoolId”: “ap-northeast-1_RoHXOwkFB”, “ClientName”: “MysfitsUserPoolClient”, “ClientId”: “71gov86tiiphs9amjmd6kjopje”, “LastModifiedDate”: “2020-09-20T09:36:05.872000+09:00”, “CreationDate”: “2020-09-20T09:36:05.872000+09:00”, “RefreshTokenValidity”: 30, “AllowedOAuthFlowsUserPoolClient”: false } }

REST APIとAmazon API Gatewayの設定

NLBの前にAmazon API Gatewayをはさんで、GatewayがCognitoを利用するようにする。またGatewayがNLBとプライベートで疎通するように、API Gateway VPCリンクも必要となる。

まずaws apigateway create-vpc-linkコマンドでNLBへのVPCリンクを作成。

aws apigateway create-vpc-link --name MysfitsApiVpcLink --target-arns <NLBのarn> > api-gateway-link-output.json
# いまさらだけどarnとは"Amazonリソースネーム"のこと。AWSリソースを一意に識別する。

つぎにaws apigateway import-rest-apiコマンドで

aws apigateway import-rest-api --parameters endpointConfigurationTypes=REGIONAL --body file://api-swagger.json --fail-on-warnings

しかし”Invalid base64”でエラーになってしまった。調べると、

AWS CLIバージョン2では、デフォルトでバイナリパラメータをbase64エンコードされた文字列として渡すようになったことが原因でした。
https://dev.classmethod.jp/articles/got-into-about-blob-type-arguments/

ということでjsonファイルがエンコードされないように(エンコードされていないバイナリとして扱うように)、fileb://指定で実行すると成功した。

{ “id”: “8wx0hv82z5”, “name”: “MysfitsApi”, “createdDate”: “2020-09-20T09:59:39+09:00”, “apiKeySource”: “HEADER”, “endpointConfiguration”: { “types”: [ “REGIONAL” ] } }

このAPIはまだデプロイされていない。そのためaws apigateway create-deploymentコマンドで(本番ステージの)デプロイを作成する。

aws apigateway create-deployment --rest-api-id <さきほど作成したREST APIのID> --stage-name prod

するとAPIがhttps://<REST APIのID>.execute-api.ap-northeast-1.amazonaws.com/prodで利用できる。末尾にmysfitsをつけて、レスポンスが帰ってくる(NLBを経由している)ことを確認。

ウェブサイトの更新

用意してくれているソースに更新してpush。

CodePipelineが始まったのだが、ビルドが失敗してしまった。調べてみるとDockerfileが壊れていたので、もとに戻して再度ビルド。

CodePipelineが完了したことを確認して、フロントエンドも修正。

サイトにアクセスして、ユーザ登録。認証メールが送られてくるので、記載のコードを入れて登録完了。

Amazon Cognitoのユーザプールを見てみると、実際にユーザが追加されていることがわかる。

モジュール5「ユーザー行動の把握」

チュートリアルを始める時点では、何をつくろうとしているのかわからなかった。

とりあえずこれまでと同様に、新しいサービスをつくるのでCodeCommitリポジトリ、スタックを作成、デプロイするようだ。

ストリーミングサービスのコードを作成してコミット

aws codecommit create-repositoryコマンドで、CodeCommitリポジトリを作成。

aws codecommit create-repository --repository-name MythicalMysfitsStreamingService-Repository

cloneUrlが発行されるので、これをもとに空のgitリポジトリをcloneする。用意してもらっているソースとCloudFormationテンプレートをリポジトリにコピー。

また外部ライブラリ(requests)をLambdaで使えるように、ライブラリをプロジェクト内にインストールしたい。そのとき使えるのがpip install-tオプション。

pip install requests -t .

スタックを作成

aws s3 mbコマンドで、S3バゲットを作成。名前はaws-modern-application-workshop-kobori-streamingとした。

aws s3 mb s3://aws-modern-application-workshop-kobori-streaming

つぎにsam packageコマンドで、コードをパッケージ化してアップロードする。

sam package --template-file ./real-time-streaming.yml --output-template-file ./transformed-streaming.yml --s3-bucket aws-modern-application-workshop-kobori-streaming

そしてaws cloudformation deployコマンドで、スタックをデプロイ

aws cloudformation deploy --template-file transformed-streaming.yml --stack-name MythicalMysfitsStreamingStack --capabilities CAPABILITY_IAM

aws cloudformation describe-stacksコマンドで、デプロイしたスタックのAPI Gatewayエンドポイントを確認する。

最後にフロントサイトを更新して、サイトを確認

【重要】ワークショップのクリーンアップ

aws cloudformation delete-stackコマンドで、今回つくったスタック・デプロイを削除しておくこと。そうしないと請求が発生するため。

aws cloudformation delete-stack --stack-name MythicalMysfitsStreamingStack
aws cloudformation delete-stack --stack-name MythicalMysfitsCoreStack

Kobori Akira

IT業界の社会人。音楽、パッカーズ、スワローズ、ポーカー。
読む価値のある記事はQiitaやnoteに投稿する予定です。
過去人気だったブログ記事はこちらから。