- 投稿日:2020-07-12T23:59:26+09:00
AWS日記14 (Forecast)
はじめに
今回は Amazon Forecastを試します。
Webページ上でデータを入力し、時系列予測結果をページ上に表示します。
作成したページ準備
Lambda , API Gatewayの準備をします。
S3の準備をします。[Amazon Forecastの資料]
Amazon Forecast
Amazon Forecast を使ってお手軽に時系列予測する(GUI編)
【初心者】Amazon Forecast を使ってみるWEBページ・API作成
GO言語のAWS Lambda関数ハンドラー aws-lambda-go を使用してHTMLやJSONを返す処理を作成します。
また、Forecast を使用するため aws-sdk-go を利用します。[参考資料]
AWS SDK for Go API Reference全体の処理の流れ
- データファイルをS3にアップロード
- データをインポート
- 学習モデルの作成
- 予測の作成
- 予測結果をエクスポート
- 結果ファイルをS3からダウンロード
DatasetGroupを作成するには CreateDatasetGroup を使う。
main.gofunc createDatasetGroup(name string) error { svc := getForecastservice() input := &forecastservice.CreateDatasetGroupInput{ DatasetGroupName: aws.String(name), Domain: aws.String("CUSTOM"), } res, err := svc.CreateDatasetGroup(input) if err != nil { return err } log.Println(aws.StringValue(res.DatasetGroupArn)) return nil }Datasetを作成するには CreateDataset を使う。
main.gofunc createDataset(name string)(string, error) { svc := getForecastservice() input := &forecastservice.CreateDatasetInput{ DatasetName: aws.String(name), DataFrequency: aws.String("D"), DatasetType: aws.String("TARGET_TIME_SERIES"), Domain: aws.String("CUSTOM"), Schema: &forecastservice.Schema{ Attributes: []*forecastservice.SchemaAttribute{ { AttributeName: aws.String("item_id"), AttributeType: aws.String("string"), }, { AttributeName: aws.String("timestamp"), AttributeType: aws.String("timestamp"), }, { AttributeName: aws.String("target_value"), AttributeType: aws.String("float"), }, }, }, } res, err := svc.CreateDataset(input) if err != nil { return "", err } return aws.StringValue(res.DatasetArn), nil }DatasetGroup に Datasetを追加するには UpdateDatasetGroup を使う。
main.gofunc updateDatasetGroup(datasetArn string, datasetGroupArn string) error { svc := getForecastservice() input := &forecastservice.UpdateDatasetGroupInput{ DatasetArns: []*string{aws.String(datasetArn)}, DatasetGroupArn: aws.String(datasetGroupArn), } _, err := svc.UpdateDatasetGroup(input) if err != nil { return err } return nil }データをインポートするには CreateDatasetImportJob を使う。
main.gofunc createDatasetImportJob(name string, datasetArn string, path string, roleArn string)(string, error) { svc := getForecastservice() input := &forecastservice.CreateDatasetImportJobInput{ DatasetImportJobName: aws.String(name), DatasetArn: aws.String(datasetArn), DataSource: &forecastservice.DataSource{ S3Config: &forecastservice.S3Config{ Path: aws.String(path), RoleArn: aws.String(roleArn), }, }, } res, err := svc.CreateDatasetImportJob(input) if err != nil { return "", err } return aws.StringValue(res.DatasetImportJobArn), nil }学習モデルを作成するには CreatePredictor を使う。
main.gofunc createPredictor(name string, datasetGroupArn string)(string, error) { svc := getForecastservice() input := &forecastservice.CreatePredictorInput{ PredictorName: aws.String(name), PerformAutoML: aws.Bool(true), ForecastHorizon: aws.Int64(10), InputDataConfig: &forecastservice.InputDataConfig{ DatasetGroupArn: aws.String(datasetGroupArn), }, FeaturizationConfig: &forecastservice.FeaturizationConfig{ ForecastFrequency: aws.String("D"), }, } res, err := svc.CreatePredictor(input) if err != nil { return "", err } return aws.StringValue(res.PredictorArn), nil }予測を作成するには CreateForecast を使う。
main.gofunc createForecast(name string, predictorArn string)(string, error) { svc := getForecastservice() input := &forecastservice.CreateForecastInput{ ForecastName: aws.String(name), PredictorArn: aws.String(predictorArn), } res, err := svc.CreateForecast(input) if err != nil { return "", err } return aws.StringValue(res.ForecastArn), nil }予測結果をエクスポートするには CreateForecastExportJob を使う。
main.gofunc createForecastExportJob(name string, forecastArn string, path string, roleArn string)(string, error) { svc := getForecastservice() input := &forecastservice.CreateForecastExportJobInput{ ForecastExportJobName: aws.String(name), ForecastArn: aws.String(forecastArn), Destination: &forecastservice.DataDestination{ S3Config: &forecastservice.S3Config{ Path: aws.String(path), RoleArn: aws.String(roleArn), }, }, } res, err := svc.CreateForecastExportJob(input) if err != nil { return "", err } return aws.StringValue(res.ForecastExportJobArn), nil }※ 各処理の進捗状態を確認するには DescribeDataset などを使い、Status を見て判定します。
終わりに
今回はAmazon Forecastを試しました。
今回試したデータ量は100行でしたが、全工程で1〜2時間程度の処理時間がかかりました。
- 投稿日:2020-07-12T23:44:47+09:00
SAAに合格するためにセルフマネジメントをTrelloでしてみました
タイトルの通り、先日SAA-C02を受験し合格しました。
ですが、本記事の主題としてはSAAそのものではなく、どう学習を計画し、その進捗を測るかということです。
私の場合、学習って何かしらでモチベートしないと結局中途半端になりがちなので、何かいい方法はないかなと思っていました。
そう考えた時、Trelloで必要なアクションを管理するのってピッタリなんじゃね?と思い、Trelloを活用してみたら結構ハマりました。まずはSAA取得に関する諸々情報
- 勉強に使った教材
以上。※もうSAAの話はでません笑
Trelloの活用
ステータスを定義しましょう
業務を推進する上でも同じことですが、タスクの進捗を計測するにはステータスの定義は重要です。
ここが曖昧だとボードに映るタスクの進捗は実態を映していません。要は甘い判断基準で今この辺〜っていうのと厳しい判断基準で今この辺!っていうのが混在してしまいます。
今回のボードには以下のステータスを用意しました。
- ToDo・・・やらなければいけない項目
- NiceToHave・・・優先度は低いが余裕があればやるとなお良い項目
- InProgress・・・進行中の項目
- Understand・・・アクションが完了した項目
- Complete・・・問題演習で正答した項目
なぜ
Understand
とComplete
があるのか?勉強って「このページ読んだ!」と「このページの内容理解した!」って違いますよね?
これを表現したかったのです。一旦読んだぜ!見終わったぜ!みたいなのはUnderstand
、これの問題解けたぜ!は理解したってことでComplete
にしました。学びはカードのアクティビティログに連ねる
学習していると当然、これメモっときたいなあってことがあります。Udemyにはメモ機能あるし、ノートに手書きのメモも取れるし〜ってあっちこっちにナレッジが点在するのって基本よろしくないと思っています。そこで、何か気づきがあればとにかく、関連するカードのアクティビティログに書き連ねました。これで、「この時なんか気になることあったよな〜」って時にはとにかくTrelloを確認すればいい状態を作りました。
カードの粒度
いくつかの教材を使っているので、当然「UdemyのS3」「参考書のS3」「BlackbeltのS3」ってな具合にS3に関するそれぞれのコンテンツがあります。この単位でカードを作って進捗を管理したいので、カードの粒度は教材×AWSサービスにしました。ただ、カードの名前を「XXXのXXX」ってするのは見た目的にごちゃごちゃするので、教材軸はラベル機能を使って分類しています。赤のラベルはUdemy、黒はBlackbelt、黄色は参考書って具合です。他にもハンズオンしてみる時は緑のラベルでそのアクティビティのカードを作ったりなどもしています。
実際のボードの様子
以下のような感じでTrelloを活用していました。個人的に大事にしていたのは何よりもカードの総数です。試験に合格するって絶対的にこれだけの学習をこなさなければならないっていうラインがあると思っているので、それをカードの数で測定していました。
その数のなかで、ToDoの割合やCompleteの割合をなんとなく見て、今こんくらいかなあ〜っていうのを管理していました。管理していたといっても個人でやってる世界の話なので、かなりざっくりふわっとですが。。。
※どうでもいいですが背景を山にしたのは、山を登り切って合格を掴もうぜ!っていう意味です笑
割とおすすめ
ツールを使って進捗を管理するのは試験勉強において結構オススメな気がしています。今回のSAA取得は学習期間大体1ヶ月ですが、決められた(あるいは決めた)タイムボックスの中で成果を出すのってやっぱり予実の管理が重要だと感じます。
この取り組みを通して、Doneの定義やカンバンボードの活用などのスクラム開発なんかに使えそうなエッセンスもある気がしていて、けっこううまみの多い活動のように思いました。
- 投稿日:2020-07-12T23:43:36+09:00
東京リージョンのAWS SESで、SMTP認証を使ってメール送信をする
TL;DR
- 周知のとおり(?)、AWS SESが東京リージョンで使えるようになりました
- 東京リージョンでSMTP認証情報を使う時のパスワードはシークレットアクセスキーを変換して使います
- 米国東部(バージニア北部)リージョンなら署名バージョン2で変換
- アジアパシフィック (東京)リージョンなら署名バージョン4で変換
Amazon SESとSMTP
Amazon SESでSMTP送信を行うためには、SMTP認証用のIAMユーザーを作成する必要があります。
SMTP認証で使うユーザー名とパスワードは、作成したIAMユーザーのアクセスキーID、シークレットアクセスキーを変換したものになります。
ここで作成したIAMユーザーを使い、smtp-cliを使ってメール送信を行ってみたいと思います。
この時、パスワードに使用する情報に注意します。
IAMユーザーは、Terraformで作成します。
環境
今回使用した環境は、こちらです。
$ terraform version Terraform v0.12.28 + provider.aws v2.70.0 $ ./smtp-cli --version smtp-cli version 3.10AWSのクレデンシャルは、環境変数で指定します。
export AWS_ACCESS_KEY_ID=xxxxx export AWS_SECRET_ACCESS_KEY=xxxxx export AWS_DEFAULT_REGION=ap-northeast-1IAMユーザーの作成
最初に、SMTP認証用のIAMユーザーを作成します。
main.tf
terraform { required_version = "= 0.12.28" } provider "aws" { version = "2.70.0" } resource "aws_iam_user" "ses_smtp" { name = "SesSmtpUser" } resource "aws_iam_user_policy" "ses_smtp_user" { name = "SesSmtpPolicy" user = aws_iam_user.ses_smtp.name policy = <<JSON { "Version":"2012-10-17", "Statement":[ { "Effect":"Allow", "Action":[ "ses:SendEmail", "ses:SendRawEmail" ], "Resource":"*" } ] } JSON } resource "aws_iam_access_key" "ses_smtp_key" { user = aws_iam_user.ses_smtp.name } output "aws_iam_access_key" { value = aws_iam_access_key.ses_smtp_key.id sensitive = true } output "aws_iam_secret" { value = aws_iam_access_key.ses_smtp_key.secret sensitive = true } output "aws_iam_smtp_password_v2" { value = aws_iam_access_key.ses_smtp_key.ses_smtp_password sensitive = true } output "aws_iam_smtp_password_v4" { value = aws_iam_access_key.ses_smtp_key.ses_smtp_password_v4 sensitive = true }Terraformを使ってIAMユーザーを作り、アクセスキーIDを作成すると、SMTP認証用のパスワードが得られるので、こちらを使用しましょう。
$ terraform apply
Outputに関しては、
terraform.tfstate
を参照…。メールアドレスの検証とメール送信を行う
続いて、Amazon SESのメールアドレス検証を行い、サンドボックス状態にします。
米国東部(バージニア北部)リージョンで試す
まず最初に、
us-east-1
リージョンで試します。メールアドレスの検証。
$ aws ses verify-email-identity --email-address [メールアドレス] --region us-east-1受信したメールのリンクにアクセスして、検証を完了しましょう。
続いて、
us-east-1
リージョンのSMTPサーバー(email-smtp.us-east-1.amazonaws.com:587
)に対してメール送信。$ ./smtp-cli --host email-smtp.us-east-1.amazonaws.com:587 --enable-auth --user [アクセスキーID] --from [Verifyしたメールアドレス] --to [Verifyしたメールアドレス] --subject 'test mail' --body-plain 'Hello SES!'この時、署名バージョン4を使って変換したパスワードを使うと
output "aws_iam_smtp_password_v4" { value = aws_iam_access_key.ses_smtp_key.ses_smtp_password_v4 sensitive = true }認証に失敗します。
Enter password for [アクセスキーID]@email-smtp.us-east-1.amazonaws.com:587 : AUTH LOGIN failed: 535 Authentication Credentials Invalid署名バージョン2を使って変換したパスワードを使うと
output "aws_iam_smtp_password_v2" { value = aws_iam_access_key.ses_smtp_key.ses_smtp_password sensitive = true }メール送信がうまくいきます。
リソース
aws_iam_access_key
のAttributeの説明を見ると署名バージョン2は非推奨なので、署名バージョン4なのかな?と思ったのですが、そうでもないようですね。
ses_smtp_password - DEPRECATED The secret access key converted into an SES SMTP password by applying AWS's SigV2 conversion algorithm
ses_smtp_password_v4 - The secret access key converted into an SES SMTP password by applying AWS's documented Sigv4 conversion algorithm. As SigV4 is region specific, valid Provider regions are ap-south-1, ap-southeast-2, eu-central-1, eu-west-1, us-east-1 and us-west-2. See current AWS SES regions
署名バージョン4は、使えるリージョンが限られているみたいですし。
アジアパシフィック (東京)リージョンで試す
続いて、
ap-northeast-1
リージョンで試します。まずは、メールアドレスの検証。こちらもあらためて
ap-northeast-1
リージョンで行いましょう。$ aws ses verify-email-identity --email-address [メールアドレス] --region ap-northeast-1送信されてきたメールを確認したら、今度はSMTPサーバー(
email-smtp.ap-northeast-1.amazonaws.com:587
)に対してメール送信を行います。$ ./smtp-cli --host email-smtp.ap-northeast-1.amazonaws.com:587 --enable-auth --user [アクセスキーID] --from [Verifyしたメールアドレス] --to [Verifyしたメールアドレス] --subject 'test mail' --body-plain 'Hello SES!'こちらは、署名バージョン2を使って変換したパスワードを使うと
output "aws_iam_smtp_password_v2" { value = aws_iam_access_key.ses_smtp_key.ses_smtp_password sensitive = true }認証に失敗します。
Enter password for [アクセスキーID]@email-smtp.ap-northeast-1.amazonaws.com:587 : AUTH LOGIN failed: 535 Authentication Credentials Invalid署名バージョン4を使って変換したパスワードを使うと
output "aws_iam_smtp_password_v4" { value = aws_iam_access_key.ses_smtp_key.ses_smtp_password_v4 sensitive = true }メール送信に成功します。
使うリージョンでSMTP認証時のパスワード形式が違うみたいなので、覚えておきましょう…。
参考
- 投稿日:2020-07-12T23:04:34+09:00
Amazon EC2(Linux)でWebサーバ構築(Apache利用)
Amazon EC2(Linux)でWebサーバ構築(Apache利用)
Amazon EC2にApacheをインストールし、静的なWebサイトを公開する構成をお試し構築します。本記事のゴールは公開したWebページにWebブラウザーで[http://xxxx.xxx] という形でアクセスできるようにすることです。
この構成もAWS 初心者向けのハンズオンとしてはよくあるパターンですね。
この手の記事も何番煎じか分かりませんが、初心者の方のお役に立てれば幸いです。前提条件
- 極力無料もしくは安上がりなサービスを利用する
- EC2はパブリックサブネットにデプロイされていること
- SSHでEC2インスタンスへアクセスできるようにセキュリティグループが設定されていること
- 独自ドメインを取得していること ※今回は無料で独自ドメインを取得できるFreenomを利用します ※Freenomでの無料ドメインの取得について丁寧な解説ブログがありましたの最後の参考ページで紹介します
- DNSサーバの操作(レコードの追加)ができること
イメージ図
他のWebサーバ構築パターンについて
AWS を利用して、Webサーバを構築するパターンは他にもいろいろありますので、ご参考までに紹介します。
1. EC2ではなく、S3にHTMLファイルを格納するパターン
メリット:高い可用性が確保できる+ユーザでサーバメンテナンス不要
デメリット:Wordpressなどを利用した動的なサイトは構築できない(HTMLで記述したWebページならOK)
2. Amazon LightSail を利用するパターン
メリット:定額制+必要なサービスがパッケージ化されている(Wordpressがプリインストールされている)
デメリット:可用性確保のためにユーザ側で対策が必要+細かいサーバのチューニングができない構築手順
1. VPCおよびEC2をデプロイ
2. Elastic IPをEC2へアタッチして、セキュリティグループでHTTPアクセスを許可する
3. EC2へSSH接続し、Apacheをインストールし、HTMLファイルを公開設定
4. DNSサーバのAレコードにElasticIPを登録1と2については丁寧な解説記事が多々あるため、割愛します。本記事では3から始めていきます。
まずEC2へSSHで接続して、Apacheをインストール、設定します。# SSH接続後にec2-user からRoot へユーザ切替 sudo su root # 最新状態へアップデータ yum -y update # Apacheのインストール yum -y install httpd # Apacheの起動 systemctl start httpd # EC2が再起動した際にApacheも自動起動するように設定 systemctl enable httpd # Apacheが起動したか確認 systemctl status httpd #下記のようにhttpd.serviceが[active]になっていればApacheのインストールはOKです。続いて、Apache内にHTMLファイルを作成して、公開設定していきます。
# HTMLファイルを格納する場所(/var/www/html)へ移動 cd /var/www/html #ファイル(ファイル名=index.html)の作成 touch index.html # HTMLファイルの中身を記述 vi index.html # i を押し、insert mode に移行し、以下内容を記述し、 :w! で保存して終了 <html lang="ja"> <head> <meta charset="utf-8"> <title>test-page</title> </head> <body> <h1>Hellow world!</h1> </body> </html> # 作成したindex.htmlがちゃんとあるか確認 lsEC2側(Apache)の設定はこれで完了です。
Apache ではWebブラウザーからアクセスがあった際にデフォルトで表示させる場所が「/var/www/html」のため、HTMLファイルはそこに作成しております。それでは、ちゃんとApacheの設定ができているかWebブラウザーで確認しましょう。
http://"Elastic IP" でアクセスして以下のように画面表示されればOKです。
※仮にElastic IPが123.123.123.123 ならhttp://123.123.123.123 へアクセス
最後にIPアドレスではなく、ドメイン名でアクセスできるようにDNSの設定をしていきます。
今回ドメインは無料のFrrenom で取得しましたので、FreenomのDNSを使いますがDNSサーバの設定部分はご自身の環境に読み替えてください。
※とりあえずAレコードにEC2のElastic IPを登録できればOKです
Frrenomにログインし、Freenom DNSの画面にAレコードとして、Erastic IPを登録すればOKです。
DNSの伝播に時間がかかるため、20分程度経ってから「http://"取得したドメイン"」でアクセスしてみましょう。
IPアドレスでアクセスしたときと同じ画面がでれば、これですべての設定が完了です!おわりに
作ってみるとAWS以外のApacheやらDNSやらも知識がなくて、勉強しないと...という気分になりました。
分からないことを格安な仮想サーバおよび無料サービスを使って、実際に手を動かしながら検証できるというは非常にありがたいですね。
今回はWebページのSSL化はしませんでしたが実際にWebページを運用するとなると、最近は常時SSL化は当たり前になってきているので、別途SSL化する構成についても記事も書こうと思います。参考ページ
記事を書く上で参考させて頂いたページです。
今更聞けない!Webサーバーの仕組みと構築方法
【Freenom】取得したドメインのDNS設定~Aレコードの登録手順~
- 投稿日:2020-07-12T22:31:33+09:00
Terraformでオレオレ証明書を作ってIaCだけでALBをHTTPS化する
はじめに
自分のプロジェクト内部に閉じたALB配下のOSSを使うのに、インターネット経由で平文は不安!だけどローカルなものだし暗号化はしておきたいよね!でもお金はかけたくない!という時のためのオレオレ証明書作成方法。
先に言っておくとおくと、
- マネコンの画面ポチポチができる環境なのであれば、AWS Certificate ManagerのプライベートCA経由でのルート証明書発行がめちゃくちゃ簡単なので、素直にそっちを使った方が良い。どうせローカル利用であれば大した金額にはならない(ACMのプライベートCAは、Terraformではルート証明書の発行までやってくれないので、フルIaCを目指すのであればこの案は採用できない)
- 自分でopenssl
コマンド作ってファイル作って良い環境なのであれば、その方が楽
- じゃあ何のためにやったんだよ……
という内容なので、あまり参考にはならないかもしれない。前提条件
以下が前提条件。
- Terraformはなんとなく分かる
- SSL証明書関連がなんとなくわかる。
openssl
コマンドで自分で証明書発行したことがないとちょっとつらいあと、環境的には、
- EC2でHTTPサーバが起動している
- EC2の前段のALBの構築は完了していて、ターゲットグループにEC2インスタンスをアタッチ済み
- セキュリティグループでHTTPSの穴あけができている
- あとは443ポートで受け付けるリスナーを作って、構築済みターゲットグループに転送するだけの状態
になっているところからスタートする。
Terraformの内容
全体構成
以下のようなフォルダ構成とする
. ├── 00_main.tf ├── 01_variables.tf ├── 02_data_sources.tf ├── 11_alb.tf └── 12_acm.tf00_main.tf はプロバイダ定義しか書いていないので、テキトーに設定しておく。
01_variables.tf には、内部で参照するALBとターゲットグループのリソース名を定義しておく。
01_variables.tfvariable "prefix" { default = "ACMTest" } locals{ alb_name = "${var.prefix}-ALB" tg_name = "${var.prefix}-TG" }02_data_sources.tf も、内部で使うデータリソースを定義するだけ。
02_data_sources.tfdata "aws_alb" "https_test" { name = "${local.alb_name}" } data "aws_alb_target_group" "https_test" { name = "${local.tg_name}" }ALBの設定
ここからが本番。
今回は、ACMに証明書登録して参照する方法を採用しているため、certificate_arn
にはこの後定義するaws_acm_certificate
のARNを設定する。それ以外は特に普通のALBの設定。11_alb.tfresource "aws_alb_listener" "https_test" { load_balancer_arn = "${data.aws_alb.https_test.arn}" port = "443" protocol = "HTTPS" ssl_policy = "ELBSecurityPolicy-2016-08" certificate_arn = "${aws_acm_certificate.https_test.arn}" default_action { type = "forward" target_group_arn = "${data.aws_alb_target_group.https_test.arn}" } }ルート証明書の作成
次がそのARNの証明書の設定。
今回はコマンドとかを使わずにフルIaCでALBをHTTPS化することを目的にしているため、TerraformのTLSプロバイダを使う。まずは、オレオレルート証明書を作る部分から。
tls_private_key
でalgorithm = "RSA"
するのが、opensslでいう$ openssl genrsa 2048
に該当する。
鍵を作ったら、その鍵を使って$ openssl req -new -x509 …
しているイメージだ。
作った証明書はlocal_file
リソースでファイル出力して保管しておく。12_acm.tf(ルート証明書定義部分)resource "tls_private_key" "https_test_root" { algorithm = "RSA" } resource "tls_self_signed_cert" "https_test_root" { key_algorithm = "${tls_private_key.https_test_root.algorithm}" private_key_pem = "${tls_private_key.https_test_root.private_key_pem}" subject { common_name = "HTTPS_TEST_ROOT" } validity_period_hours = 87600 is_ca_certificate = true allowed_uses = [ "digital_signature", "crl_signing", "cert_signing", "cert_signing", ] } resource "local_file" "https_test_root_key" { filename = "https_test_root.key" content = "${tls_private_key.https_test_root.private_key_pem}" } resource "local_file" "https_test_root_pem" { filename = "https_test_root.crt" content = "${tls_self_signed_cert.https_test_root.cert_pem}" }サーバ証明書の作成
ルート証明書ができたら今度はサーバ証明書の作成。
ルート証明書同様、tls_private_key
で鍵を作って、今度はtls_cert_request
リソースでその鍵を使って$ openssl req -new …
で証明書署名要求(CSR)を作成する。最後にtls_locally_signed_cert
リソースで$ openssl x509 -req …
してサーバ証明書を作成する。今回は、ALBのデフォルト名をそのまま使うので、CNやDNS名には
*.ap-northeast-1.elb.amazonaws.com
を設定する。12_acm.tf(サーバ証明書定義部分)resource "tls_private_key" "https_test" { algorithm = "RSA" } resource "tls_cert_request" "https_test" { key_algorithm = "${tls_private_key.https_test.algorithm}" private_key_pem = "${tls_private_key.https_test.private_key_pem}" subject { common_name = "*.ap-northeast-1.elb.amazonaws.com" } dns_names = [ "*.ap-northeast-1.elb.amazonaws.com", ] } resource "tls_locally_signed_cert" "https_test" { cert_request_pem = "${tls_cert_request.https_test.cert_request_pem}" ca_key_algorithm = "${tls_private_key.https_test_root.algorithm}" ca_private_key_pem = "${tls_private_key.https_test_root.private_key_pem}" ca_cert_pem = "${tls_self_signed_cert.https_test_root.cert_pem}" validity_period_hours = 87600 is_ca_certificate = false set_subject_key_id = true allowed_uses = [ "key_encipherment", "digital_signature", "server_auth", "client_auth", ] } resource "local_file" "https_test_key" { filename = "https_test.key" content = "${tls_private_key.https_test.private_key_pem}" } resource "local_file" "https_test_cert_pem" { filename = "https_test_cert.pem" content = "${tls_locally_signed_cert.https_test.cert_pem}" }これで証明書が作れたので、ACMにアタッチしてあげればよい。
certificate_chain でルート証明書を紐づけてあげる(これをやらないとブラウザが良い感じに処理してくれないように思われる)。
Nameタグが画面表示に使われるので、何か設定をしておこう。12_acm.tf(ACMにアタッチ)resource "aws_acm_certificate" "https_test" { private_key = "${tls_private_key.https_test.private_key_pem}" certificate_body = "${tls_locally_signed_cert.https_test.cert_pem}" certificate_chain = "${tls_self_signed_cert.https_test_root.cert_pem}" tags = { Name = "[テキトーな名前]" } }動かしてみる
さて、これで
terraform apply
すればといった感じで、ACMに証明書が登録される。
これで、ALBのDNS名にhttps://~でアクセスすれば、いつもの「信頼されていないサイト」の画面が出るので、
terraform apply
したディレクトリに出力されている https_test_root.crt の内容を、このあたりのサイトを参考にしつつ、ルート証明書に登録すれば良い。これで、エラー画面を経由せずにHTTPS化したALBにアクセスできたぞ!
※危険なのでProduction環境では絶対にやらないように。最初に書いた通り、素直にACMのプライベートCA使うのが楽だよ!その他
正常性確認が終わった後のゴミ掃除は以下のサイトを参考にすればOK。
(何回もトライ&エラーで消したりしていた)
- 投稿日:2020-07-12T22:29:23+09:00
AWS Hands-on for Beginners Network編#1 実施時の所感
概要
AWS Hands-on for Beginners Network編#1を先日実施したのでその所感などをまとめてみます。
以下の人におすすめです。
- AWSの勉強をしようとしている人
- 資格を取るために勉強中の人
- あまり実務経験がない人
学べる内容
このハンズオンで学べる内容としては
* public subnet と private subnet について
* VPC内から外部のインターネットに接続する方法
* VPC内から外部からの接続は禁止にして内部からの接続のみ可能にする方法
* VPCエンドポイントの使い方とVPCの基本を学ぶことができます。
Udemyのハンズオンなどでもこのあたりは学べますが、エンドポイントについての説明など細かくしていただけるので理解を深めるのによいかと思います。引っかかったポイント
私が実施した際に引っかかったポイントです。
- privateLinkを使ってSSMとの接続を行うところでエンドポイントの設定の反映が時間がかかったようで最初エラーになりました。
- インスタンス起動の時など時間がかかるポイントではコンソールで反映されてもちょっと待った方がよさそうです。
- 投稿日:2020-07-12T21:05:17+09:00
AWS VPC設計、サブネット設計メモ
VPC設計のポイント
VPCで割り当てるIPはプライベートIPアドレスの範囲の使用が推奨される
VPCでは仮想のプライベートネットワーク空間を作成するのでプライベートIPアドレスの使用が推奨される。
具体的には以下(IPv4の場合)
IPv4プライベートアドレス範囲(RFC1918):
- 10.0.0.0〜10.255.255.255(24ビットブロック)
- 172.16.0.0〜172.31.255.255(20ビットブロック)
- 192.168.0.0〜192.168.255.255(16ビットブロック)
VPCのIPアドレスの範囲を決めるときは大きめ(できれば/16)に設定する
後から変更できないため、IPの枯渇を防ぐために範囲を大きめにとる。
CIDRの範囲は/24〜/16と決まっている。できれば/16に設定しする。他のVPCやオンプレミスとIPアドレスが重複しないようにする。
VPCを分けるか、アカウントを分けるか
- 違うシステムの場合はアカウントを分ける
- 違うシステムを同一アカウントに入れると管理が煩雑になる
- 同一システムで環境(本番環境、ステージング環境など)が違う場合は
サブネット設計のポイント
IPアドレスの範囲はできれば/24推奨に設定する
一つのサブネット内に256個設定できればほとんどの場合足りるのではないか
サブネットに割り当てられるルーティングテーブルは1つ
高可用性のために、2つ以上のアベイラビリティゾーンを使う。
- 投稿日:2020-07-12T20:50:30+09:00
AWS SAA対策メモ(コンテナ、コスト管理&その他)
SAA対策の自分用のメモ。
どんどん更新して加筆修正していく予定。コンテナ
より少ないコンピュータリソースで、OS上に独立した実行環境を構築する仮想化技術
3つの構成要素
データプレーン コンテナ稼働の実行環境
コントロールプレーン コンテナの管理
レジストリ コンテナの起動元のイメージ格納場所Elastic Container Service (ECS)
Dockerコンテナを管理するオーケストレーションサービス。これにより、コンテナ化されたアプリケーションを AWS で簡単に実行およびスケールで可能。コンテナ上のタスクの増減をService Auto Scalingが行う。Fargate
サーバーやクラスターの管理の必要なしにコンテナを実行するための、Amazon ECS および EKS に対応したコンピューティングエンジンElastic Container Service for Kubernetes (EKS)
コンテナ化されたアプリケーションのデプロイ、管理、スケールを Kubernetes を使って AWS で簡単に実行Elastic Container Registry (ECR)
完全マネージド型の Docker コンテナレジストリです。Dockerコンテナイメージを簡単に保存、管理、デプロイコスト管理系
AWS Cost and Usage Report
コスト状況の詳細分析
CSV形式でS3やRedshiftに保存したり、QuickSightで分析・可視化できる。Cost Explorer
コストの可視化と傾向分析
部門ごとなどでタグをつけておくと、集計時にフィルタリングなどをして部門ごとの使用状況を可視化しやすくできる。AWS Budget
予算額を設定し、超過した場合にはCloudWatchアラームやSNS通知が来る。Trusted Advisor(ビシネス/エンタープライズサポートのみ)
5つの観点をベストプラクティスに基づいて利用状況をチェック
コスト最適化、セキュリティ、耐障害性、パフォーマンス、サービス制限DR
RPO(Recovery Point Objective)
過去のどの地点までデータ復元を許容できるかRTO(Recovery Time Objective)
復旧するまでの目標時間パイロットライト
常に利用可能であるアプリケーションの最小バージョン
DRシナリオでよく使われるその他
AWS Artifact
コンプライアンス関連情報を一元管理Server Migration Service (SMS)
多数のオンプレミスワークロードをAWSに移行可能
自動化した移行プロセスによって素早く展開することが可能Database Migration Service (DMS)
商用およびオープンソースのデータベースとの間でデータを移行するために使用Application Discovery Service (ADS)
オンプレミスサーバーのインベントリと動作を検出するために使用されます。AWSへの移行計画を作成するときに利用するものです。AWS Globel Accelerator
世界中の顧客に提供するアプリケーションの可用性とパフォーマンスを改善するネットワークサービスLightsail
仮想サーバー、ストレージ、データベース、およびネットワーキングを予測可能な低価格で提供するサービスAmazon Rekognition
画像分析と動画分析をアプリケーションに簡単に追加できます。Rekognition API に画像または動画を与えるだけで、このサービスが対象物、人、テキスト、シーン、アクティビティ、それに不適切なコンテンツまで検出します。Amazon Polly
文章をリアルな音声に変換するサービスAmazon Lex
音声やテキストを使用して、任意のアプリケーションに対話型インターフェイスを構築するサービスAWS DataSync
オンプレミスストレージと Amazon EFS の間でデータを迅速かつ簡単に移動することができるマネージド型のデータ転送サービスAmazon Elastic Transcoder
はクラウドのメディア変換サービスAWS Batch
バッチ管理機能
優先度高なジョブはオンデマンドインスタンスで、優先度低なジョブはスポットインスタンスで行う。ビッグデータ分析に利用。CloudSearch
簡易検索機能を追加できるSystems Manager
リソースの運用情報の可視化、制御AWS KMS
鍵管理サービス。暗号化鍵の自動ローテーション、キーの権限管理、暗号化鍵の使用状況の可視化、証跡管理ができる
SDKやCLIのクライアントアプリケーションやS3、EBS、RDS、Redshiftなどのストレージサービスで利用可能CloudHSM(Hardware Security Module)
HSMはデータセンター内にあるユーザー占有ハードウェアアプライアンス。HSMと連携可能なのは、RedshiftとRDS for Oracle。自動セットアップのアプローチ
ブートストラップ
CloudFormation, OpsWorksゴールデンイメージ
AMI, VM Import/Exportコンテナ
Elastic Beanstalk, ECS, Fargate, EKS
- 投稿日:2020-07-12T19:07:24+09:00
AWS MediaLive データ転送料金試算例
- 投稿日:2020-07-12T17:17:40+09:00
コロナ中、約2週間(16日)でAWS認定クラウドプラクティショナーを受けてみた。
はじめに
はじめまして。
私はIT企業に2020年の4月から勤めています。4月から8月までの研修が昨今のコロナウイルスの影響でずーーっと在宅なので、何か資格を取ってみようということで、勤め先の企業でも使用実績があり、かつこれからの時代で持っていて損はないだろうと思い、AWSの資格を受けてみようと思いました。パーソナル情報について
持っている資格
・基本情報技術者(ボーダーラインぎりぎりで受かった(笑))
出身大学
・非情報系
・卒業研究でPythonを少しいじった。
使用したことがある言語
・C (初心者)
・Python (初心者)
・COBOL (超初心者)ってことで、「The 新卒エンジニア」って感じ
(エンジニアといっていいのか?笑 -> 言わせてください!!)
ぐらいの実力です。(笑)クラウドに関しては、無知識。まずやったこと
情報収集
たぶん一番大事です。
自分の実力に合った資格を受けることで、お金も無駄にならず、受かると自身がついて勉強意欲がわきます。
これは、基本情報を受けて、受かった時に思いました。今回は、「クラウド」、「AWS」ともに初めて扱う話題だったので、AWSのなかでも一番ベーシックな試験である「AWSクラウドプラクティショナー」を選択しました。
使った教材
「AWS認定資格試験テキスト AWS認定クラウドプラクティショナー」
「Udemy教材」
「ホワイトペーパー」
「AWSが提供しているオンライン授業」1日目~4日目
ひたすら教科書を読み、問題を解く!!
ここで大事なのはとりあえずわからない言葉があったら調べること。
わかったふりをして、次に進むことも大事だとは思います。しかし、内容によっては意味が分からなければ、その後の内容も全くわからいことにも繋がります。そのため、つまったところは調べる癖をつけた方がいいと思います!
例:「ストレージとデータベース、何が違う??」
「リージョン、アベイラビリティゾーン、エッジロケーション」5日目~7日目
一番悩む・・・
なんとなーくは理解したけど、絶対うからない自信がある。
(自身のない資格試験あるある笑)
・もう1回教科書を読みなおして、総復習
・とりあえず問題を解いてみる
・ホワイトペーパー読む
の3つに絞り、考えます。んーーーーーーーーーーーー、わからん!!!
よし、とりあえず問題解いてみよう
ってことで問題解きました。
Udemyの問題(模擬試験問題集(7回分455問))を最初の2回分解いてみました。結果は、、、、56%と48%
絶対受からない(笑)
この試験問題から何がわかったかというと、とりあえずAWSの全体像がつかめていない。誰のための、どんなメリットがあるのか、が全くつかめていない、ということである。7日目~9日目
このままではまずいということで、本家本元のAWSさんに助けを求める。
AWSのオンライン講座を受けてみました。受けてみた感想
ー>「すごくわかりやすい。もっと早く受ければよかった...」英語の説明ではあるが、和訳が不自然ではなく、とても分かりやすかったです。
特に、IAMの説明に関しては、わかっていない人はぜひ見た方がいいと思います。(全部で6時間近くあるので、試験直前の人は分からない分野だけでもいいと思います。)10日目~12日目
よし、問題を解きまくる、ということでUdemyの問題を3回分解きました(応用問題)。応用問題だけあって、全て合格点とはいきませんでしたが、分かる内容は増えてきました。
また、わからない問題についてはしっかりと復習し、毎回毎回知識をインプットしていきました。13日目~15日目
問題の解きなおしをしました。
言うことは全くなく、只々たくさんの問題を解きなおしまくりました。
もう試験本番も近いので、頭に詰め込むだけ詰め込み、最後の方は85%程度とれるようになってきました。試験当日
試験の結果は、、、合格しました。
スコアは824で良いのか悪いのか分からないけど、受かってよかった!!試験を受けた感想は、「問題が難しい気がする...」
恥ずかしながら、受けている内も受け終わった後も、落ちた気しかしませんでした。でも、分かる知識から回答を絞っていけばどうにかなると思います。
また、ダミー問題のようなものもあるようなので、全く分からない問題があったとしてもビビらないことが重要だと思います。おわりに
今回AWSの資格を受けてみて思ったことは、実際に扱っていないと難しいということです。他の人の合格体験記を見てみると、「業務で使ったことがある」とか「いままで、SEの仕事をしてきた」など経験者の方が多く見受けられました。AWSは無料枠があるそうなので、実際に使いながら学ぶとより早く受かると思います。
私は今回、「テレワーク中」ということもあり、膨大な勉強量を確保できました。しかし、これから勉強時間は確保しづらくなってしまうので、コツコツ次の資格の勉強をしていきたいと思います。
最後に行ったことを簡単にまとめたいと思います。
1.クラウド、AWSの知識をインプット
2.問題解答
3.授業で再インプット
4.問題解答(解きなおし)まとめ
以上です。いかがだったでしょうか?改善点など多々あると思いますので、ご指摘よろしくお願いいたします。
また、Qiita初心者でもあるので、見やすいスライドのつくり方などあれば教えてください。
- 投稿日:2020-07-12T16:59:26+09:00
InvalidSmsRoleTrustRelationshipException
すぐにたどり着く解決策がないので、ユーザープール作り直しが吉。
- 投稿日:2020-07-12T16:41:51+09:00
EC2 の nginx ログを CloudWatch Logs → S3 → Athena を介して分析する
はじめに
__s_o__ です。
EC2 上のログをログエージェントを利用して CloudWatch Logs に転送することは、意外とやっている人が多いかもしれません。
今回はそこから少しだけ発展させて、CloudWatch Logs から S3 にログをエクスポートし、さらにそのログを Athena で取り込んで分析可能な状態にするまで掘り下げてみたいと思います。
各サービスの特徴と運用イメージ
サービス名 説明 実際の運用イメージ EC2 AWS の IaaS 環境 Web サーバ (nginx) を動かしている CloudWatch Logs AWS 環境のログを収集・整理する nginx のログをリアルタイムで収集し、マネジメントコンソールから閲覧可能な状態にする S3 AWS のオブジェクトストレージ 定期的に CloudWatch Logs のログをアーカイブする (gzip 保管) Athena S3 内のデータを、SQL などで分析可能な状態にする アーカイブされたログを分析する (アクセス件数など) 設定の流れ
EC2 (nginx ログ) → CloudWatch Logs の設定
EC2 上の nginx ログを CloudWatch Logs に出力します。下記記事を参考に、ひとまず access_log のみ転送するように設定します。
[/var/log/nginx/access.log] datetime_format = %d/%b/%Y:%H:%M:%S %z file = /var/log/nginx/access.log buffer_duration = 5000 log_stream_name = {instance_id} initial_position = start_of_file log_group_name = /var/log/nginx/access.log設定変更後、awslogs を再起動します。
$ sudo systemctl restart awslogsd $ sudo systemctl status awslogsdしばらく後、下記のように CloudWatchLogs にログが転送されます。
CloudWatch Logs → S3 の設定
CloudWatch Logs を S3 にエクスポートします。なお、あらかじめ「logs.xxx.yyy.zzz」というバケットを作成済みとします (「xxx.yyy.zzz」は任意のドメイン名を示しています。適時 読み替えてください)。
下記記事を参考にしながら、設定を進めていきます。
まずはバケット「logs.xxx.yyy.zzz」に、下記バケットポリシーを設定し、CloudWatch Logs からアクセス可能な状態にします。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "logs.ap-northeast-1.amazonaws.com" }, "Action": "s3:GetBucketAcl", "Resource": "arn:aws:s3:::logs.xxx.yyy.zzz" }, { "Effect": "Allow", "Principal": { "Service": "logs.ap-northeast-1.amazonaws.com" }, "Action": "s3:PutObject", "Resource": "arn:aws:s3:::logs.xxx.yyy.zzz/*", "Condition": { "StringEquals": { "s3:x-amz-acl": "bucket-owner-full-control" } } } ] }上記設定後、CloudWatch Logs に移動し、先ほど設定したロググループ (今回の場合は「/var/log/nginx/access.log」) を指定します。右上のメニューより「アクション」 > 「データを Amazon S3 にエクスポート」を選択します。
下記のように入力し、ロググループを S3 にエクスポートします。エクスポート単位 (=S3 上のオブジェクト単位) は 1 日単位としています。
項目 説明 開始 特定日付の最初の時間 (00:00) を指定します 終了 特定日付の最後の時間 (23:59) を指定します S3 バケット名 あらかじめ準備したバケット名 (今回の場合は「logs.xxx.yyy.zzz」) を指定します S3 バケットプレフィックス バケット配下に「log-export」というフォルダを作成し、その下にエクスポートします。「year=」や「month=」などは Athena のパーティションを意識したフォルダ構造です。Athena のパーティションについては後述します 上記でエクスポート後、S3 に下記のとおりエクスポートされます。なお、ログは gzip で圧縮された形で出力されます。Athena は gzip 圧縮済みのファイルも読み込めるため、特に後続の作業に問題ありません。逆に、読み込みサイズが小さいほど Athena の料金が低くなるため、コスト管理の面ではありがたいです。
S3 → Athena の設定
S3 に保管したログデータを Athena に読み込ませます。まずは S3 のログデータから DB & テーブル を作成します。
下記記事を参考にしながら設定を進めていきます。
参考 : nginxのアクセスログからAmazon Athenaを利用してリファラー別にリクエスト数を集計する
なお、以後の設定に関しては、上記記事と下記点が異なるのでご留意ください。
- 上記記事では nginx のログを直接 S3 に保管しているが、今回は CloudWatch Logs を介している
→ CloudWatch Logs 経由の場合、各ログエントリの先頭にタイムスタンプが付く。Data Format 指定時に左記を考慮する必要がある- 上記記事では Athena のパーティションを設定していないが、今回は パーティションを設定している
→ S3 のフォルダ構造や Athena の設定でパーティションを考慮する必要がある (S3 のフォルダ構造については、前述で考慮済み)それでは、実際の設定を進めていきます。まずは Athena の左メニューより「Table」 > 「Create Table」 > 「from s3 bucket data」を選択します。
Step1 は下記のとおり設定します。
項目 説明 Database 任意のデータベース名を指定します。今回は「server_log」としています Table Name 任意のアクセス名を指定します。今回は「access_log」としています Location of Input Data Set S3 バケットの URL を指定します。今回は「s3://logs.xxx.yyy.zzz/log-export/」です。URL の最後は必ずスラッシュを含めるようにしてください Step2 は下記のとおり設定します。
項目 説明 Data Format 「Apache Web Logs」を指定します Regex 「 ^\S+ (\S+) \S+ \S+ \[([^\[]+)\] "(\w+) (\S+) (\S+)" (\d+) (\d+) "([^"]+)" "([^"]+)".*$
」を設定します。CloudWatch Logs 経由で出力しているログのため、参考記事に比べて先頭に「\S+
」が増えていますなお、正規表現 (Regex) の検証に関しては下記サイトが役立ちます。
参考 : Regular Expression Test DriveStep3 では「Bulk add columns」ボタンを押下し、下記項目を入力して一括で列を設定します。
ip STRING, time_local STRING, method STRING, uri STRING, protocol STRING, status int, bytes_sent int, referer STRING, user_agent STRING最後の Step 4 では下記のとおりパーティションを設定します。パーティションは、S3 バケットのフォルダ構成に合わせて設定します。
Column Name Column type year int month int day int 設定完了後、「Create table」ボタンを押下してテーブルを作成します。
なお、Step 1 ~ Step 4 をクエリで設定する場合は下記構文となります。
CREATE EXTERNAL TABLE IF NOT EXISTS server_log.access_log ( `ip` string, `time_local` string, `method` string, `uri` string, `protocol` string, `status` int, `bytes_sent` int, `referer` string, `user_agent` string ) PARTITIONED BY ( year int, month int, day int ) ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.RegexSerDe' WITH SERDEPROPERTIES ( 'serialization.format' = '1', 'input.regex' = '^\\S+ (\\S+) \\S+ \\S+ \\[([^\\[]+)\\] \"(\\w+) (\\S+) (\\S+)\" (\\d+) (\\d+) \"([^\"]+)\" \"([^\"]+)\".*' ) LOCATION 's3://logs.xxx.yyy.zzz/log-export/' TBLPROPERTIES ('has_encrypted_data'='false');さて、今回はパーティションを使用しているため、最後に下記クエリを実行してパーティションを有効にします。
MSCK REPAIR TABLE access_log
以上で Athena 分析を行う準備がすべて整いました。
Athena のパーティションについて
このタイミングになってしまいましたが、Athena のパーティションについて説明します。
Athena では読み込んだファイルサイズによって課金されます。そのため、全データを一つのファイルで S3 に保管して、それを Athena で読み込ませたりすると、毎回それなりのコストが発生します。
そのため、通常は期間などでファイル (フォルダ) を分割し、必要な範囲だけを Athena で読み取れるようにします。パーティションを設定することによって、Athena の SQL 文の Where 句でパーティション項目を指定できるようになります。イメージは下記のとおりです。
select * from access_log where year=2020
上記の「year」は実際のデータに含まれるカラムではなく、パーティションで指定したカラムになります。実際のデータに含まれるカラムでは無いですが、Where 句の検索対象として指定することができます。
なお、Athena でパーティションを有効にするには下記が必要になります (すべて今までの手順で記載済みです)。
- パーティションの設定にあわせて、S3 のフォルダ構造を定義する必要がある
- Athena でテーブルを作成する際、パーティションを定義する必要がある
- Athena でテーブルを作成後、
MSCK REPAIR TABLE access_log
でパーティションを有効にする必要があるパーティションに関するさらに詳しい情報は下記をご参照ください。
参考 : データのパーティション分割
Athena で検索
Athena へのデータ設定が完了したので、最後に SQL を使ってデータを検索してみます。
select * from access_log where year=2020 and month =07 and ip <> ''
※2020 年 7 月のデータを検索しています。ip <> ''
で空白行を排除していますまとめ
以上、EC2 の nginx ログを CloudWatch Logs → S3 → Athena を介して分析する、でした。
今回の構成は、たとえば下記のようなカスタマイズができます。
- Zabbix などのログ監視機構を使っているので CloudWatch Logs は不要……ということであれば、EC2 から直接 S3 にログをエクスポートし、それを Athena で分析することが可能
- 今回、CloudWatch Logs から S3 へのエクスポートは手動で行っているが、Lambda や Kinesis Firehose を使うことでエクスポートを自動化することが可能
- Athena に設定したデータ (データセット) を QuickSight から参照し、データを可視化することが可能
……等々、いろいろと発展性の高い構成なので、この構成をベースに、今後もいろいろ試していきたいと思います。
参考 : Kinesis Firehoseを使ってCloudWatch Logをs3へ出力してみた
参考 : AWS Athena + QuickSightでS3に蓄積されたCSVデータを可視化
- 投稿日:2020-07-12T15:41:30+09:00
AWSのECSでDockerコンテナを起動して、ALBでHTTPのトラフィックを分散してみた。
AWSが提供しているコンテナオーケストレーションサービスを実際に触る機会がありましたので、「Elastic Container Service (ECS)でコンテナを起動し、コンテナへのHTTPトラフィックをApplication Load Balancer(ALB)で分散するまでの一連の流れ」を記事にしてみようと思います。
今回使用するAWSサービス
- Virtual Private Cloud(VPC)
- Elastic Compute Cloud(EC2)
- Elastic Container Registry(ECR)
- Elastic Container Service(ECS)/Fargate
- Application Load Balancer(ALB)
概要構成図
まず、作業を始める前に「ECSでコンテナを起動し、ALBでHTTPのトラフィックを分散する」をAWSの構成図で表現してみます。
ネットワークの作成&設定
VPCの作成
まずは、AWS上に仮想ネットワーク(VPC)を構築します。
なお、リージョンは米国東部(オハイオ)リージョンを選択しています。 理由は東京リージョンよりもオハイオリージョンのほうがAWS各種サービスの利用料金を安く抑えることができるためです。
インターネットゲートウェイのアタッチ
インターネットからVPC内の各種リソースに接続することを可能にするため、作成したVPCにインターネットゲートウェイをアタッチします。
サブネットの作成
実際にコンテナを実行するサブネットをVPC内に作成します。今回は2つのサブネットをそれぞれ異なるアベイラビリティゾーン(AZ)に作成します。
ルートテーブルの設定
作成したサブネットのルートテーブルにインターネットゲートウェイを追加します。
なお、「0.0.0.0」はデフォルトルートを指します。
作業用インスタンスの作成
EC2インスタンスの起動
構成図には含まれていませんが、Dockerイメージを作成しECRにイメージをプッシュするための作業用インスタンスを起動します。インスタンス起動時は自動割り当てパブリックIPを有効し、セキュリティグループはSSH接続できるようにポート22をインバウンドルールに追加します。
なお、今回はCentOSのイメージからインスタンスを起動します。
各種パッケージのインストール
作成したインスタンスにSSHでログインし、Dockerイメージの作成およびイメージをプッシュするために必要な各種パッケージをインストールします。
【参考】
Install Docker Engine on CentOS - docker docs
Linux で AWS CLI バージョン 2 をインストールする - AWSドキュメント
Dockerコマンドをsudoなしで実行する方法 - Qiita# unzip, wgetのインストール $ sudo yum install -y unzip wget # AWS CLI(Version2)のインストール $ curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" $ unzip awscliv2.zip $ sudo ./aws/install # Dockerのインストール&起動 $ sudo yum install -y yum-utils $ sudo yum-config-manager \ --add-repo \ https://download.docker.com/linux/centos/docker-ce.repo $ sudo yum install -y docker-ce docker-ce-cli containerd.io $ sudo gpasswd -a {ユーザー名} docker $ sudo systemctl restart docker $ sudo systemctl enable dokcerDockerの再起動まで完了したら、一度サーバーからexitして再度SSHでログインします。
コンテナの実行
Dockerイメージの作成
作業用インスタンスでDockerコンテナのイメージを作成します。
なお、今回は株式会社イーシーキューブ様が提供しているEC-CUBE4のDockerfileからDockerイメージを作成します。【参考】
ec-cube/Dockerfile at 4.0 - GitHub
Dockerを使用してインストールする - EC-CUBE4.0開発者向けドキュメント# EC-CUBE4をダウンロード $ wget https://github.com/EC-CUBE/ec-cube/archive/4.0.zip $ unzip 4.0.zip # Dokcerfileの生成 $ cd ec-cube-4.0/ $ docker build -t eccube4 . # 生成したDockerイメージの確認 $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE eccube4 latest xxxxxxxxxxxx ◯ minutes ago 754MB php 7.3-apache-stretch xxxxxxxxxxxx ◯ days ago 375MBECRのリポジトリにDockerイメージをプッシュ
AWSが提供しているコンテナイメージレジストリのElastic Container Registry(ECR)にEC-CUBEのDokcerイメージを保管するリポジトリを新規作成します。なお、リポジトリ名は作成したEC-CUBEのDockerイメージと同じ名前です。
次に作成したECRのリポジトリにEC-CUBEのDockerイメージをプッシュします。「プッシュコマンドの表示」をクリックし、表示されるコマンドを作業用インスタンスで実行していきます。
# 認証トークンを取得し、作成したレジストリに対してDockerクライアントを認証します。 $ aws ecr get-login-password --region us-east-2 | docker login --username AWS --password-stdin {アカウントID}.dkr.ecr.us-east-2.amazonaws.com # イメージのタグ付け。 $ docker tag eccube4:latest {アカウントID}.dkr.ecr.us-east-2.amazonaws.com/eccube4:latest # ECRのリポジトリにDockerイメージをプッシュします。 $ docker push {アカウントID}.dkr.ecr.us-east-2.amazonaws.com/eccube4:latestなお、Dockerイメージは既に作成済みなので
docker build
コマンドはスキップしています。また、認証トークン取得時に以下のようなエラーが表示された場合は
aws configure
コマンドでアクセスキーとシークレットアクセスキーを登録する必要があります。# 認証トークンを取得しようとするとcreentialsに関するエラーが表示される。 $ aws ecr get-login-password --region us-east-2 | docker login --username AWS --password-stdin {アカウントID}.dkr.ecr.us-east-2.amazonaws.com Unable to locate credentials. You can configure credentials by running "aws configure". Error: Cannot perform an interactive login from a non TTY device # IAMユーザーのアクセスキーとシークレットアクセスキーを登録する。 $ aws configure AWS Access Key ID [None]: IAMユーザーのアクセスキーを入力。 AWS Secret Access Key [None]: IAMユーザーのシークレットアクセスキーを入力。 Default region name [None]: 今回はオハイオリージョンで作業しているので「us-east-2」と入力。 Default output format [None]: 空欄のまま。ECSクラスターの作成
コンテナを実行するECSクラスターを作成します。なお、クラスターテンプレートはAWS Fargateを選択しています。
タスク定義
ECSでコンテナを実行するにはタスクを定義する必要があります。なお、起動タイプはクラスターと同様にAWS Fargateを選択します。
タスク定義の名前を入力し、CPUやメモリのタスクサイズを指定します。
「コンテナの追加」をクリックし、コンテナ名とECRに保管しているDockerイメージのURIを入力します。また、ポートマッピングに80番を追加します。
ロードバランサーの作成
タスク(コンテナ)に対するHTTPトラフィックを分散するために必要なApplication Load Balancer(ALB)をEC2のコンソールから作成します。
80番ポートに対するトラフィックを許可するセキュリティグループを設定します。
ルーティングのターゲットグループの設定でIPをダーゲットの種類として選択します。
ロードバランサーが問題なく作成されると以下のような画面が表示されます。
サービスの定義
必要となるタスク数(コンテナ数)を維持するためにサービスを定義します。
起動タイプにFargateを選択し、作成したタスク定義およびクラスターを指定します。
なお、今回はタスク数を3にしています。(同じコンテナが3つ立ち上がります。)
ネットワーク構成の設定で作成したVPCとサブネットを指定します。
また、パブリックIPの自動割り当てを「enabled」にします。
ロードバランシングの設定画面で「Application Load Balancer」を選択し、作成したロードバランサーを指定します。
動作確認
コンテナの動作確認
サービスが作成できたら、タスクが起動していることを確認します。
それぞれのタスクに割り振られているパブリックIPにブラウザからアクセスするとEC-CUBEのトップページが表示されます。(今回はわかりやすいようにEC-CUBEのショップ名をコンテナごとに変えています。)
ロードバランサーの動作確認
これらのコンテナはALBによるロードバランシングが適応されているので、ALBに割り当てられているDNS名からでもアクセスすることができます。
画面をリロードすると別のコンテナにHTTPトラフィックが切り替わります。
コンテナのセッション情報について
ここまでの手順で「ECSでコンテナを起動し、ALBでHTTPのトラフィックを分散する」ことができましたが、現時点ではセッション情報がそれぞれのコンテナに保持されている状態のため、ユーザー認証等ができません。EC-CUBEの認証画面でIDとパスワードを入力し「ログイン」を押しても、トラフィックが別のコンテナに振り分けられてしまうため、ユーザー認証を通過することができません。
上記の事象については2つの解決策があります。ALBのステッキーセッションを有効にする。
ElastiCacheでセッション情報を管理する。
感想
この記事では「ECSでコンテナを起動し、ALBでHTTPのトラフィックを分散する」を実際にやってみました。ECSやALBはAWSコンソールから操作できるため、想像していたよりも簡単にコンテナを起動しトラフィックを分散することができました。特にECSはAWSが提供しているコンテナオーケストレーションサービスということもあり、AWS各種サービスと比較的連携しやすいという印象を受けました。
機会があれば次回はElastic Kubernetes Service(EKS)も触ってみようと思います。
- 投稿日:2020-07-12T15:37:50+09:00
AWSで学びサーバとネットワークの基本1: なんでサブネットマスクに分けるのか?
ネットワークは割り当てられたCIDRブロックを、そのまま使わず、さらに小さなCIDRブロックに分割して利用する
細分化したCIDRブロック=「サブネット」と言う。なぜ、サブネットマスクに分けるのか?
サブネットに分割すると、その部分でネットワークを分けることができる。
理由1:物理的な隔離
社内LANを構築する時、フロアごとで別のサブネットに分けたい、部署でサブネットを分けたいというような物理的に分けたいことがある。
サブネットに分けておくと、万が一、どこかのサブネットが障害が起こした時も、違うところに、その影響が出にくい。理由2:セキュリティ上の理由
サブネットを分ければ、それぞれに対して、別のネットワークの設定ができる。
社内にサーバを持っている場合は、「サーバー群だけを別のサブネットにして、そのサブネットとの通信を監視したり一部しかデータを通さないように構成したりすることでセキュリティを高める」とか、「インターネットに接続するサーバーだけを別のサブネットにして、社内LANから隔離する」という構成も、よくある。
- 投稿日:2020-07-12T15:03:56+09:00
QuarkusでAWS Lambdaを作る
Quarkus(https://quarkus.io/) とは
SUPERSONIC SUBATOMIC JAVA
で、A Kubernetes Native Java stack tailored for OpenJDK HotSpot and GraalVM, crafted from the best of breed Java libraries and standards.
だそうです。
何言ってるかわかりませんがすごそうです。GraalVMの機能でJavaのプログラムをネイティブに変換することが可能なので、AWS Lambdaのカスタムランタイムと組み合わせることで AWS Lambda上で動作するJavaアプリケーションの特性である コールドスタートが遅い問題 の解決に期待ができます。
公式サイトの手順に従い試してみましたので、手順を残します。
QUARKUS - BUILDING A NATIVE EXECUTABLE
https://quarkus.io/guides/building-native-imageQUARKUS - AMAZON LAMBDA
https://quarkus.io/guides/amazon-lambda環境
- Docker Desktop (Mac) 2.3.0.4
- VSCode + Visual Studio Code Remote - Containers extension
- Amazon Linux 2 (on Docker)
雛形アプリのデプロイ手順
手順1. Amazon Linux 2の環境設定
DockerイメージのAmazon Linux 2は
tar
やunzip
ができないので色々入れておきます。
全部必要かわからないですが、これぐらい入れておきました。yum install -y sudo shadow-utils procps tar.x86_64 gzip xz unzip witch git python3 tree手順2. GraalVMのインストール
公式サイトからダウンロードして展開します。
curl -s -L -o /tmp/graalvm-ce-java11-linux-amd64-20.1.0.tar.gz https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.1.0/graalvm-ce-java11-linux-amd64-20.1.0.tar.gz tar zxf /tmp/graalvm-ce-java11-linux-amd64-20.1.0.tar.gz -C /opt/ ln -s /opt/graalvm-ce-java11-20.1.0 /opt/graalvm
JAVA_HOME
をGraalVMにして、MavenでのビルドにGraalVMを使用します。export GRAALVM_HOME=/opt/graalvm export JAVA_HOME=$GRAALVM_HOME export PATH=$GRAALVM_HOME/bin:$PATH最後にNativeビルド時に必要な
native-image
をインストールします。コマンドはgu
(GraalVM Updater)です。gu install native-image
手順3. Mavenのインストール
Quarkusのビルドで使用するMavenは3.6.2以上のバージョンが必要です。
yum
でインストールできるバージョンが古かったので、公式サイトからダウンロードしてインストールしました。curl -s -L -o /tmp/apache-maven-3.6.3-bin.tar.gz https://downloads.apache.org/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz tar zxf /tmp/apache-maven-3.6.3-bin.tar.gz -C /opt/ ln -s /opt/apache-maven-3.6.3 /opt/apache-mavenmvnのバージョン確認
bash-4.2# mvn --version Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f) Maven home: /opt/apache-maven Java version: 11.0.7, vendor: GraalVM Community, runtime: /opt/graalvm-ce-java11-20.1.0 Default locale: en_US, platform encoding: UTF-8 OS name: "linux", version: "4.19.76-linuxkit", arch: "amd64", family: "unix" bash-4.2#GraalVMのJVMで動作していることがわかります。
手順4. Mavenでプロジェクト作成
mvn
コマンドでプロジェクトを生成します。mvn archetype:generate \ -DarchetypeGroupId=io.quarkus \ -DarchetypeArtifactId=quarkus-amazon-lambda-archetype \ -DarchetypeVersion=1.6.0.Finalしばらくするとプロンプトで質問されますので答えます。
[]で囲んだ部分がユーザー入力です。Define value for property 'groupId': [myGroup] Define value for property 'artifactId': [myArtifact] Define value for property 'version' 1.0-SNAPSHOT: : [] Define value for property 'package' myGroup: : [example] Confirm properties configuration: groupId: myGroup artifactId: myArtifact version: 1.0-SNAPSHOT package: example Y: : [Y]
artifactId
(myArtifact)でディレクトリが作成され、プロジェクトが生成されます。作成直後のプロジェクト構成はこんな感じ。
bash-4.2# tree myArtifact/ myArtifact/ ├── build.gradle ├── gradle.properties ├── payload.json ├── pom.xml ├── settings.gradle └── src ├── main │ ├── java │ │ └── example │ │ ├── InputObject.java │ │ ├── OutputObject.java │ │ ├── ProcessingService.java │ │ ├── StreamLambda.java │ │ ├── TestLambda.java │ │ └── UnusedLambda.java │ └── resources │ └── application.properties └── test ├── java │ └── example │ └── LambdaHandlerTest.java └── resources └── application.properties 9 directories, 14 files bash-4.2#手順5. Lambdaで起動するHandlerの設定
Lambdaで呼び出されるHandlerは
resources/application.properties
のquarkus.lambda.handler
にて設定します。resources/application.propertiesquarkus.lambda.handler=test上記設定の場合は、以下の
test
と名前をつけたクラスが呼び出されます。main/java/example.TestLambda.java@Named("test") public class TestLambda implements RequestHandler<InputObject, OutputObject> { }あとは通常のLambdaと同様にコーディングします。
雛形にはtest
の他にstream
なども用意されています。手順6. デプロイパッケージの作成
一旦雛形のままデプロイパッケージを作成してみます。
mvn clean package -Pnative
めちゃくちゃ時間がかかります。10分以上かかると思います。
手順7. デプロイパッケージの内容の確認
無事デプロイパッケージができると、
target/function.zip
が生成されいます。試しに展開してみると、中身はbootstrap
のみでした。bash-4.2# unzip function.zip Archive: function.zip inflating: bootstrap bash-4.2#手順8. Lambdaのデプロイ
target
ディレクトリ内には、manage.sh
というファイルも生成されており、ここからAWS上にデプロイしたりできるようです。
私はカスタムランタイムが初めてだったこともあり、マネジメントコンソールから試してみました。
デプロイパッケージ作成後に、target/sam.native.yaml
というファイルも生成されるので、ハンドラー名や環境変数はこちらを参考にしました。関数を新規作成します。
ランタイムをユーザー独自のブートストラップを提供する
とします。関数を作成したら、プログラムをアップロードします。
関数コードのところのアクション
から.zipファイルをアップロード
を選び、function.zip
を選択します。ハンドラーは
not.used.in.provided.runtime
となります。環境変数に
DISABLE_SIGNAL_HANDLERS
を追加し値をtrue
とします。ここまでで設定は完了です。以下のJSONをインプットにテスト実行してみましょう。
{ "name": "Bill", "greeting": "hello" }動作結果がこちら。無事動きました。
AWS SDK を追加
AWS SDKを使用するには単純にpom.xmlに追加するだけでなく、いくつか設定が必要です。
手順1. SSL通信の有効化
resources/application.properties
に以下の内容を追加します。(公式ドキュメント的にはデフォルトで有効って書いてある気もしますが)resources/application.propertiesquarkus.ssl.native=true手順2. 依存関係の追加
まずは、
quarkus-jaxb
が必要とのことで、これを追加します。pom.xml<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-jaxb</artifactId> </dependency>次にAWS SDKのライブラリーを追加するのですが、
For native image, however the URL Connection client must be preferred over the Apache HTTP Client when using synchronous mode, due to issues in the GraalVM compilation (at present).
翻訳すると
ただし、ネイティブイメージの場合、GraalVMコンパイルの問題(現在)のため、同期モードを使用するときは、Apache HTTPクライアントよりもURL接続クライアントを優先する必要があります。
だそうです。そのため、以下のような記述となります。pom.xml<properties> <aws.sdk2.version>2.10.69</aws.sdk2.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>bom</artifactId> <version>${aws.sdk2.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>url-connection-client</artifactId> </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>apache-client</artifactId> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>s3</artifactId> <exclusions> <!-- exclude the apache-client and netty client --> <exclusion> <groupId>software.amazon.awssdk</groupId> <artifactId>apache-client</artifactId> </exclusion> <exclusion> <groupId>software.amazon.awssdk</groupId> <artifactId>netty-nio-client</artifactId> </exclusion> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.jboss.logging</groupId> <artifactId>commons-logging-jboss-logging</artifactId> <version>1.0.0.Final</version> </dependency> </dependencies>手順3. Javaコードの作成
S3にアクセスする例ですが、S3Clientを生成する際に、
httpClient
にて明示的にUrlConnectionHttpClient
を指定します。S3Client s3 = S3Client.builder() .region(Region.AP_NORTHEAST_1) .httpClient(software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient.builder().build()) .build();手順4. SSL通信に必要な設定
SSL通信を行うためにデプロイパッケージに以下を含める必要があります。
- カスタムBootstrap
- libsunec.so
- cacerts
まず、
src/main/zip.native/
ディレクトリを作成し、bootstrap
を作成します。zip.native/bootstrap#!/usr/bin/env bash ./runner -Djava.library.path=./ -Djavax.net.ssl.trustStore=./cacerts次に
libsunec.so
とcacerts
をコピーします。この2つのファイルはGraalVMに含まれています。cp $GRAALVM_HOME/lib/libsunec.so $PROJECT_DIR/src/main/zip.native/ cp $GRAALVM_HOME/lib/security/cacerts $PROJECT_DIR/src/main/zip.native/手順5. デプロイパッケージの作成
デプロイパッケージの作成手順は変わりません。
mvn clean package -Pnative
手順6. デプロイパッケージの内容の確認
SSL有効化した状態でデプロイパッケージを作成すると、
target/function.zip
の中身が変わります。bash-4.2# unzip function.zip Archive: function.zip inflating: bootstrap inflating: cacerts inflating: libsunec.so inflating: runner bash-4.2#事前準備した
bootstrap
cacerts
libsunec.so
が含まれているのがわかります。終わりに
通常のJavaランタイムよりも、コールドスタートが早くなる検証結果はこちらで確認ください。
QuarkusがJava Lambdaを救う!?
https://qiita.com/moritalous/items/4de31a66edac728ba088
- 投稿日:2020-07-12T13:51:40+09:00
AWS Toolkit for Visual Studio CodeでLamdbaデバッグができるようになるまで(自分用メモ)
前提
Installing the AWS SAM CLI on Windows
Step 1: Create an AWS Account Step 2: Create an IAM User with Administrator Permissions Step 3: Install Docker Step 4: Install the AWS SAM CLIdockerのインストールはlocalテストのみ
deploy目的のみであれば利用しなくてもよい
(それでも個別にAPIGateway、Lambdaをデプロイしてテストするより楽)ただし、docker Toolboxを以前利用していた環境(WinHomeでwsl2利用)だとdockerのエラーになりがち
環境変数削除で復帰(docker-machineの更新はなくても可)
対処法参考:
https://www.nuits.jp/entry/docker-could-not-read-ca-certificatepython用仮想環境を作成
conda create -n myenv python=3.8手順
Tutorial: Deploying a Hello World Application
#Step 1 - Download a sample application sam init #Step 2 - Build your application cd sam-app sam build #Step 3 - Deploy your application sam deploy --guidedStep1. sam init
テンプレートとなるファイル構成を作成するWhich template source would you like to use? 1 - AWS Quick Start Templates Which runtime would you like to use? 2 - python3.8 Project name [sam-app]: sample-sam AWS quick start application templates: 1 - Hello World ExampleStep2. Build your application
開発したコードおよび依存モジュールを.aws-sam\build以下に収集cd sample-sam sam buildStep3. Deploy your application
3-1. パッケージをS3へ格納
3-2. CloudFormation呼び出し
3-3. AWS CloudFormation stacktとして結果出力sam deploy --guided Stack Name [sam-app]: sample-sam AWS Region [us-east-1]: ap-northeast-1 Confirm changes before deploy [y/N]: y Allow SAM CLI IAM role creation [Y/n]: Y HelloWorldFunction may not have authorization defined, Is this okay? [y/N]: y Save arguments to samconfig.toml [Y/n]: Y (略) Key HelloWorldApi Description API Gateway endpoint URL for Prod stage for Hello World function Value https://<restapiid>.ap-northeast-1.amazonaws.com/Prod/hello/Step3.5. デプロイの確認
3-a. 表示されたエンドポイントに対してcurlでリクエスト実行
curl https://<restapiid>.execute-api.us-east-1.amazonaws.com/Prod/hello/
{"message": "hello world"}
3-b. AWSConsoleで確認(IAM Role,Lambda, API Gateway, CloudFormation, S3)
Cloud Formation Stackは2つ作成されている(S3、それ以外)
sam cliによるS3へのパッケージ展開、Cloud Formation呼び出しが2つに分かれる
3-c. 削除するときはFormation Stackの単位で削除するとよいStep 4: Testing Your Application Locally (Optional)
docker環境を利用して評価環境を立ち上げる
コードを更新すると自動的に反映される(start-apiを再び呼び出さなくてもよい)sam local start-api Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]4.5 確認
4-a. エンドポイントに対してcurlでリクエスト実行curl http://172.0.0.1:3000/hello/ {"message": "hello world"}4-b. 呼び出し後のコンソール確認
[37mGET /hello/ HTTP/1.1[0m" 200 -4-c. docker確認
docker image ls lambci/lambda python3.8自動で保存される
docker container ls 3f10xxxxxxxx lambci/lambda:python3.8 "/var/rapid/init --b…"リクエスト時のみ作成されてすぐ破棄される
4-d. Making One-off Invocations
sam local start-apiを呼び出さなくても起動、リクエスト送付、結果取得まで実施するsam local invoke "HelloWorldFunction" -e events/event.json5. AWS Toolkit for Visual Studio Code
- VS CodeにAWS Toolkitをインストール
- AWS ExplorerからLambda呼び出し(on AWS)できる
- ローカルで実行する場合はCodeLensのRun Localy/Debug Localy
その他メモ
- 認証ありのAPI Gateway/Lambdaを扱いたい場合は
Controlling Access to API Gateway APIs参考
- 投稿日:2020-07-12T13:25:18+09:00
はじめての AWS Amplify - ①初回設定を5分で終わらせるCloudFormationテンプレート
はじめに
AWS Amplify Console
は、 静的ウェブサイトを簡単かつ迅速にホスティングすることのできるAWSサービス です。Webアプリの構築やデプロイを行うための CI/CDワークフローも提供される ことから、React、Angular、Vue、Ember などの シングルページアプリケーション (SPA) を 簡単にデプロイ することができます。また、AWS CodeCommit
などの コードリポジトリと接続することで、コードコミットのたびにデプロイを実行 することもできます。
Amplify Console
を使い始めるにあたっては、コードリポジトリとの接続やブランチの作成などの 各種セットアップが必要 となります。もちろん、AWSのコンソール画面(AWS Management Console)から、これらのセットアップを行うことも可能ですが、本記事では 「Amplify Console のセットアップをできるだけ簡単に終わらせたい」 という想いから、CloudFormationテンプレートを用いて、 これらのセットアップをクリック1つで完了 させようと思います。TL;DR
以下の
CloudFormation
テンプレートを実行することで、AWS Amplify Console と AWS CodeCommit を用いたCI/CDワークフローをお手軽に実現します。下にあるボタンをクリックすると、自身のAWSアカウント(Asia Pacific Tokyo - ap-northeast-1)で、このCloudFormation
テンプレートを実行することが可能となります。作成されるAWSリソースとそのアーキテクチャ図はこちら。
AWS Amplify Consoleは、 複数のコードリポジトリに対応していますが、本記事では CloudFormation を使って簡単に環境構築が可能な
AWS CodeCommit
をコードリポジトリとして使用しています。アーキテクチャ
このテンプレートの設定内容は以下の通りです。なお、以下のYAMLコードは、 aws-cloudformation-templates/amplify - GitHub で公開しているCloudFormationテンプレートから、本記事用に一部抜粋したものです。本記事作成用に一部改変を行なっていること、またリポジトリの最新のコードを常に反映している訳ではないことをご了承ください。詳細は、GitHubの 当該リポジトリ をご覧ください。
AWS CodeCommit
リポジトリ名を指定 して、Gitリポジトリを作成します。また、
AWS CodeCommit
で発生した全てのイベントをトリガとして、Amazon SNS
に 通知が送信 されます。Parameters: DomainName: Type: String AllowedPattern: .+ Description: Custom domain name for your Amplify Console application [required] RepositoryName: Type: String AllowedPattern: .+ Description: Repository name on CodeCommit [required] Resources: CodeCommit: Type: 'AWS::CodeCommit::Repository' Properties: RepositoryDescription: !Ref DomainName RepositoryName: !Ref RepositoryName Triggers: - DestinationArn: !Ref SNSTopic Events: - all Name: Notify all events to SNSAmazon SNS
AWS CodeCommit
やAWS Amplify
から送信された通知を受け取るAmazon SNS
トピック を作成します。Resources: SNSTopic: Type: 'AWS::SNS::Topic' Properties: DisplayName: !Sub SNSTopic-createdby-${AWS::StackName} TopicName: !Sub SNSTopic-createdby-${AWS::StackName}AWS Serverless Application Repository にある sns-topic テンプレート を組み込むことで、SNSトピックを作成することも可能です。この方法の場合、 作成したSNSトピックに関連する CloudWatch アラームも同時に作成 することができます。
Resources: SNSTopic: Type: 'AWS::Serverless::Application' Properties: Location: ApplicationId: arn:aws:serverlessrepo:us-east-1:172664222583:applications/sns-topic SemanticVersion: 1.0.18 Parameters: TopicName: !Sub SNSTopic-createdby-${AWS::StackName}AWS Amplify Console
IAM Role の作成
AWS Amplify Console
で使用するIAM Role
を作成します。Resources: IAMRoleForAmplify: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: amplify.amazonaws.com Action: 'sts:AssumeRole' Description: Allows Amplify Backend Deployment to access AWS resources on your behalf. ManagedPolicyArns: - arn:aws:iam::aws:policy/AdministratorAccess RoleName: !Sub '${AWS::StackName}-AmplifyBackend-${AWS::Region}'AWS Amplify Console の作成
AWS Amplify Console
を作成し、先ほど作成したCodeCommit
を リポジトリに指定 します。また、master
とdevelop
の2つの ブランチを作成 します。Parameters: DomainName: Type: String AllowedPattern: .+ Description: Custom domain name for your Amplify Console application [required] RepositoryName: Type: String AllowedPattern: .+ Description: Repository name on CodeCommit [required] Resources: AmplifyConsole: Type: 'AWS::Amplify::App' Properties: AutoBranchCreationConfig: EnableAutoBranchCreation: true EnableAutoBuild: true Description: !Ref DomainName IAMServiceRole: !GetAtt IAMRoleForAmplify.Arn Name: !Ref RepositoryName Repository: !GetAtt CodeCommit.CloneUrlHttp AmplifyBranchDev: Type: 'AWS::Amplify::Branch' Properties: AppId: !GetAtt AmplifyConsole.AppId BranchName: develop Description: develop EnableAutoBuild: true EnvironmentVariables: - Name: USER_BRANCH Value: dev AmplifyBranchProd: Type: 'AWS::Amplify::Branch' Properties: AppId: !GetAtt AmplifyConsole.AppId BranchName: master Description: master EnableAutoBuild: true EnvironmentVariables: - Name: USER_BRANCH Value: prodカスタムドメインの指定
Amplify Console では、ブランチごとにデプロイが実行され、個別のURLが付与されます。また、これらのURLを カスタムドメインに差し替える ことが可能で、 指定したドメインのSSL証明書の作成から紐付けまでを自動で行う ことができます。下の例では、
master
ブランチのURLに、自身が指定したカスタムドメインを紐付けています。Parameters: DomainName: Type: String AllowedPattern: .+ Description: Custom domain name for your Amplify Console application [required] Resources: AmplifyDomainProd: Type: 'AWS::Amplify::Domain' Properties: AppId: !GetAtt AmplifyConsole.AppId DomainName: !Sub ${DomainName} SubDomainSettings: - BranchName: !GetAtt AmplifyBranchProd.BranchName Prefix: ''Amazon EventBridge
AWS Amplify Console
から送信された通知をAmazon SNS
にルーティングする、Amazon EventBridge
イベントルール を作成します。下の例では、デプロイのステータスが、SUCCEED
,FAILED
,STARTED
であった場合のみ、通知をAmazon SNS
にルーティングします。EventBridgeForAmplify: Type: 'AWS::Events::Rule' Properties: Description: !Sub Rule for Amplify created by ${AWS::StackName}. EventPattern: source: - aws.amplify detail-type: - Amplify Deployment Status Change detail: appId: - !GetAtt AmplifyConsole.AppId jobStatus: - SUCCEED - FAILED - STARTED Name: !Sub ${AWS::StackName}-Amplify State: ENABLED Targets: # SNSトピックを直接作成した場合 - Arn: !Ref SNSTopic # SNSトピックを AWS Serverless Application Repository から作成した場合 - Arn: !GetAtt SNSTopic.Outputs.SNSTopicArn以上で、
AWS Amplify Console
とAWS CodeCommit
を連携して、 CI/CDワークフローを作成することができました。CodeCommit から Amplify Console へのトリガ設定
ただ、 上記のままだと AWS CodeCommit へのコードのプッシュをトリガにした、AWS Amplify Console のデプロイは実行されません 。なぜなら、上記の CloudFormationテンプレートには、 AWS CodeCommit から AWS Amplify Console へのトリガ設定が含まれていない ためです。CloudFormationテンプレートで、このトリガ設定を記述しようとすると、AWS Amplify と AWS CodeCommit の両リソースの間で 循環参照 が発生してしまいます。
これを解決するためには、以下を実行します。
1. 手動でトリガを設定する
AWSのコンソール画面(AWS Management Console)からトリガを 手動追加 します。指定する項目は、「トリガ名」、「イベントの種類」、「SNSトピック名 ( = arn:aws:sns:{region}:{account_id}:amplify_codecommit_topic )」、「カスタムデータ ( = Amlify Console アプリケーションID)」です。
この AWS Amplify アプリケーションID は、 アプリケーション作成後でないと指定できない ため、 最初の CloudFormationデプロイが完了した後 に手動で入力する必要があります。
2.アプリケーションIDを指定できるテンプレートに変更する
スマートさには欠けますが、上述した循環参照を避けるために、AWS CodeCommit 上で指定する AWS Amplify アプリケーションID を手動で指定 します。そのために、上のCloudFormationテンプレートに少し修正を加えます。
Parameters: AmplifyConsoleAppId: Type: String Default: '' Description: Amplify Console application id this template created DomainName: Type: String AllowedPattern: .+ Description: Custom domain name for your Amplify Console application [required] RepositoryName: Type: String AllowedPattern: .+ Description: Repository name on CodeCommit [required] Conditions: CreateCodeCommitTrigger: !Not [ !Equals [ !Ref AmplifyConsoleAppId, ''] ] Resources: CodeCommit: Type: 'AWS::CodeCommit::Repository' Properties: RepositoryDescription: !Ref DomainName RepositoryName: !Ref RepositoryName Triggers: - DestinationArn: !Ref SNSTopic Events: - all Name: Notify all events to SNS - !If - CreateCodeCommitTrigger - DestinationArn: !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:amplify_codecommit_topic CustomData: !Ref AmplifyConsoleAppId Events: - all Name: !Sub AmplifyTrigger-${AmplifyConsoleAppId} - !Ref AWS::NoValue
AmplifyConsoleAppId
パラメータを手動で指定することでトリガを指定することができますが、この AWS Amplify アプリケーションID は、 アプリケーション作成後でないと指定できない ため、 最初の CloudFormationデプロイ は空白のままで実行 し、AWS Amplify アプリケーションID を指定した上で、 再度CloudFormationデプロイを実行 する必要があります。以上で、AWS CodeCommit へのコードプッシュのトリガで、AWS Amplify Console のデプロイが実行される、CI/CDワークフローを構築することができました 。
- 投稿日:2020-07-12T12:14:06+09:00
API Gateway generated by CloudFormation
AWS CloudFormationを用いて、API GatewayからLambdaを同期・非同期で呼び出します。
CloudFormationテンプレートと、Lambdaソースは以下のような構成にしました。構成を変える場合、LambdaのCodeUriでディレクトリを、Handlerでファイル名を変更できます。
| |--apigateway.yml |--Makefile #なくても構わないです。コマンドを毎回打つのが面倒なので作っただけです。 |--Lambda |--test test.py
Makefile.PHONY: deploy deploy: $(call blue , "Lambda package & deploy") @aws cloudformation package --template-file apigateway.yml --s3-bucket ご自身のS3バケット名 --output-template-file packaged-template.yml @aws cloudformation deploy --template-file packaged-template.yml --stack-name APIGateway define blue @tput setaf 6 && echo $1 && tput sgr0 endefAPI Gateway → Lambda 同期実行版
API GatewayとLambdaを作成します。
以下、Parameters内のlambda-roleというところを、ご自身のIAM Role名に変更してください。apigateway.ymlAWSTemplateFormatVersion: 2010-09-09 Transform: "AWS::Serverless-2016-10-31" Description: API Gateway Sample Parameters: #Lambda LambdaRole: Type: String Default: lambda-role Resources: #------------------------------------------------------- # API Gateway #------------------------------------------------------- #RestApi APIGatewayRestApi: Type: AWS::ApiGateway::RestApi Properties: Name: api_gateway #Resource APIGatewayResource: Type: AWS::ApiGateway::Resource Properties: RestApiId: !Ref APIGatewayRestApi ParentId: !GetAtt APIGatewayRestApi.RootResourceId PathPart: target_lambda #Method APIGatewayMethod: Type: AWS::ApiGateway::Method Properties: AuthorizationType: NONE HttpMethod: GET Integration: Type: AWS_PROXY IntegrationHttpMethod: POST # IntegrationResponses: # - StatusCode: 202 # PassthroughBehavior: WHEN_NO_TEMPLATES # RequestParameters: # "integration.request.header.X-Amz-Invocation-Type": "'Event'" # RequestTemplates: # "application/json": !Join # - "" # - - "#set($allParams = $input.params())\n{\n\"body-json\" : $input.json('$'),\n\"params\" : {\n#foreach($type in $allParams.keySet())\n #set($params = $allParams.get($type))\n\"$type\" : {\n #foreach($paramName in $params.keySet())\n \"$paramName\" : \"$util.escapeJavaScript($params.get($paramName))\"\n #if($foreach.hasNext),#end\n #end\n}\n " # - "#if($foreach.hasNext),#end\n#end\n},\n\"stage-variables\" : {\n#foreach($key in $stageVariables.keySet())\n\"$key\" : \"$util.escapeJavaScript($stageVariables.get($key))\"\n #if($foreach.hasNext),#end\n#end\n},\n" # - "\"context\" : {\n \"account-id\" : \"$context.identity.accountId\",\n \"api-id\" : \"$context.apiId\",\n \"api-key\" : \"$context.identity.apiKey\",\n \"authorizer-principal-id\" : \"$context.authorizer.principalId\",\n \"caller\" : \"$context.identity.caller\",\n \"cognito-authentication-provider\" : \"$context.identity.cognitoAuthenticationProvider\",\n \"cognito-authentication-type\" : \"$context.identity.cognitoAuthenticationType\",\n \"cognito-identity-id\" : \"$context.identity.cognitoIdentityId\",\n " # - "\"cognito-identity-pool-id\" : \"$context.identity.cognitoIdentityPoolId\",\n \"http-method\" : \"$context.httpMethod\",\n \"stage\" : \"$context.stage\",\n " # - "\"source-ip\" : \"$context.identity.sourceIp\",\n \"user\" : \"$context.identity.user\",\n \"user-agent\" : \"$context.identity.userAgent\",\n \"user-arn\" : \"$context.identity.userArn\",\n \"request-id\" : \"$context.requestId\",\n \"resource-id\" : \"$context.resourceId\",\n \"resource-path\" : \"$context.resourcePath\"\n }\n}" Uri: !Join - "" - - !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path" - !Sub "/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:" - !Sub "${AWS::AccountId}:function:${TestLambdaFunction}/invocations" MethodResponses: # - StatusCode: 202 #default response status code with async invocation - StatusCode: 200 ResourceId: !Ref APIGatewayResource RestApiId: !Ref APIGatewayRestApi #Deployment APIGatewayDeployment: Type: AWS::ApiGateway::Deployment DependsOn: - APIGatewayMethod Properties: RestApiId: !Ref APIGatewayRestApi StageName: test StageDescription: #X-Ray TracingEnabled: true #API Gateway needs to be given the right to invoke AWS Lambda APIGatewayLambdaInvokeRole: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !Ref TestLambdaFunction Principal: apigateway.amazonaws.com #------------------------------------------------------- # Lambda #------------------------------------------------------- #API Gateway Target Lambda Function TestLambdaFunction: Type: AWS::Serverless::Function Properties: CodeUri: Lambda/test FunctionName: target_lambda Handler: target_lambda.lambda_handler MemorySize: 128 Role: !Sub arn:aws:iam::${AWS::AccountId}:role/${LambdaRole} Runtime: python3.8 Timeout: 900 #X-Ray Tracing: Active Environment: Variables: Key: ValueAPI Gateway → Lambda 非同期実行版
非同期の場合、DLQという便利な機能が使えるので、それも付け足しておきます。
以下、Parameters内のlambda-roleというところを、ご自身のIAM Role名に変更してください。
apigateway.ymlAWSTemplateFormatVersion: 2010-09-09 Transform: "AWS::Serverless-2016-10-31" Description: API Gateway Sample Parameters: #Lambda LambdaRole: Type: String Default: lambda-role Resources: #------------------------------------------------------- # API Gateway #------------------------------------------------------- #RestApi APIGatewayRestApi: Type: AWS::ApiGateway::RestApi Properties: Name: api_gateway #Resource APIGatewayResource: Type: AWS::ApiGateway::Resource Properties: RestApiId: !Ref APIGatewayRestApi ParentId: !GetAtt APIGatewayRestApi.RootResourceId PathPart: target_lambda #Method APIGatewayMethod: Type: AWS::ApiGateway::Method Properties: AuthorizationType: NONE HttpMethod: GET Integration: Type: AWS IntegrationHttpMethod: POST IntegrationResponses: - StatusCode: 202 PassthroughBehavior: WHEN_NO_TEMPLATES RequestParameters: "integration.request.header.X-Amz-Invocation-Type": "'Event'" RequestTemplates: "application/json": !Join - "" - - "#set($allParams = $input.params())\n{\n\"body-json\" : $input.json('$'),\n\"params\" : {\n#foreach($type in $allParams.keySet())\n #set($params = $allParams.get($type))\n\"$type\" : {\n #foreach($paramName in $params.keySet())\n \"$paramName\" : \"$util.escapeJavaScript($params.get($paramName))\"\n #if($foreach.hasNext),#end\n #end\n}\n " - "#if($foreach.hasNext),#end\n#end\n},\n\"stage-variables\" : {\n#foreach($key in $stageVariables.keySet())\n\"$key\" : \"$util.escapeJavaScript($stageVariables.get($key))\"\n #if($foreach.hasNext),#end\n#end\n},\n" - "\"context\" : {\n \"account-id\" : \"$context.identity.accountId\",\n \"api-id\" : \"$context.apiId\",\n \"api-key\" : \"$context.identity.apiKey\",\n \"authorizer-principal-id\" : \"$context.authorizer.principalId\",\n \"caller\" : \"$context.identity.caller\",\n \"cognito-authentication-provider\" : \"$context.identity.cognitoAuthenticationProvider\",\n \"cognito-authentication-type\" : \"$context.identity.cognitoAuthenticationType\",\n \"cognito-identity-id\" : \"$context.identity.cognitoIdentityId\",\n " - "\"cognito-identity-pool-id\" : \"$context.identity.cognitoIdentityPoolId\",\n \"http-method\" : \"$context.httpMethod\",\n \"stage\" : \"$context.stage\",\n " - "\"source-ip\" : \"$context.identity.sourceIp\",\n \"user\" : \"$context.identity.user\",\n \"user-agent\" : \"$context.identity.userAgent\",\n \"user-arn\" : \"$context.identity.userArn\",\n \"request-id\" : \"$context.requestId\",\n \"resource-id\" : \"$context.resourceId\",\n \"resource-path\" : \"$context.resourcePath\"\n }\n}" Uri: !Join - "" - - !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path" - !Sub "/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:" - !Sub "${AWS::AccountId}:function:${TestLambdaFunction}/invocations" MethodResponses: - StatusCode: 202 #default response status code with async invocation ResourceId: !Ref APIGatewayResource RestApiId: !Ref APIGatewayRestApi #Deployment APIGatewayDeployment: Type: AWS::ApiGateway::Deployment DependsOn: - APIGatewayMethod Properties: RestApiId: !Ref APIGatewayRestApi StageName: test StageDescription: #X-Ray TracingEnabled: true #API Gateway needs to be given the right to invoke AWS Lambda APIGatewayLambdaInvokeRole: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !Ref TestLambdaFunction Principal: apigateway.amazonaws.com #------------------------------------------------------- # Lambda #------------------------------------------------------- #API Gateway Target Lambda Function TestLambdaFunction: Type: AWS::Serverless::Function Properties: CodeUri: Lambda/test DeadLetterQueue: TargetArn: !GetAtt SQS.Arn Type: SQS FunctionName: target_lambda Handler: target_lambda.lambda_handler MemorySize: 128 Role: !Sub arn:aws:iam::${AWS::AccountId}:role/${LambdaRole} Runtime: python3.8 Timeout: 900 #X-Ray Tracing: Active Environment: Variables: Key: Value #------------------------------------------------------- # SQS #------------------------------------------------------- SQS: Type: AWS::SQS::Queue Properties: QueueName: test DelaySeconds: 0 MaximumMessageSize: 262144 MessageRetentionPeriod: 345600 ReceiveMessageWaitTimeSeconds: 20 VisibilityTimeout: 30API Gateway → SQS Get method, Post method
CloudFormationを用いて API GatewayからSQSを実行するアーキテクチャを構築します。API Gateway呼び出しメソッドはGetとPostを作成しました。lambda-roleをご自身のLambda実行用IAM Role名、APIGatewayRoleをご自身のSQS実行用IAM Role名に変更してください。
AWSTemplateFormatVersion: 2010-09-09 Transform: "AWS::Serverless-2016-10-31" Description: API Gateway Sample Parameters: #API Gateway ApigatewayRole: Type: String Default: APIGatewayRole #Lambda LambdaRole: Type: String Default: lambda-role Resources: #------------------------------------------------------- # API Gateway #------------------------------------------------------- #RestApi APIGatewayRestApi: Type: AWS::ApiGateway::RestApi Properties: Name: api_gateway #Resource APIGatewayResource: Type: AWS::ApiGateway::Resource Properties: RestApiId: !Ref APIGatewayRestApi ParentId: !GetAtt APIGatewayRestApi.RootResourceId PathPart: target_sqs #Get Method APIGatewayGetMethod: Type: AWS::ApiGateway::Method Properties: AuthorizationType: NONE HttpMethod: GET RequestParameters: "method.request.querystring.id" : true Integration: #API GatewayがSQSを実行するためのIAM Role Credentials: !Sub arn:aws:iam::${AWS::AccountId}:role/${ApigatewayRole} Type: AWS IntegrationHttpMethod: POST IntegrationResponses: - StatusCode: 200 PassthroughBehavior: WHEN_NO_MATCH RequestParameters: "integration.request.querystring.Action": "'SendMessage'" "integration.request.querystring.MessageBody": "method.request.querystring.id" Uri: !Join - "" - - !Sub arn:aws:apigateway:${AWS::Region}:sqs:path/${AWS::AccountId}/ - !GetAtt SQS.QueueName MethodResponses: - StatusCode: 200 ResourceId: !Ref APIGatewayResource RestApiId: !Ref APIGatewayRestApi #Post Method APIGatewayPostMethod: Type: AWS::ApiGateway::Method Properties: AuthorizationType: NONE HttpMethod: POST Integration: Credentials: !Sub arn:aws:iam::${AWS::AccountId}:role/${ApigatewayRole} Type: AWS IntegrationHttpMethod: POST IntegrationResponses: - StatusCode: 200 PassthroughBehavior: WHEN_NO_MATCH RequestParameters: "integration.request.querystring.Action": "'SendMessage'" Uri: !Join - "" - - !Sub arn:aws:apigateway:${AWS::Region}:sqs:path/${AWS::AccountId}/ - !GetAtt SQS.QueueName MethodResponses: - StatusCode: 200 ResourceId: !Ref APIGatewayResource RestApiId: !Ref APIGatewayRestApi #Deployment APIGatewayDeployment: Type: AWS::ApiGateway::Deployment DependsOn: - APIGatewayGetMethod - APIGatewayPostMethod Properties: RestApiId: !Ref APIGatewayRestApi StageName: test #------------------------------------------------------- # SQS #------------------------------------------------------- SQS: Type: AWS::SQS::Queue Properties: QueueName: test DelaySeconds: 0 MaximumMessageSize: 262144 MessageRetentionPeriod: 345600 ReceiveMessageWaitTimeSeconds: 20 VisibilityTimeout: 30API Gateway → Application Load Balancer (ALB)
APIGatewayRoleを、API GatewayがALBにリクエストをつなげるための、ご自身のIAM Role名に変更してください。
AWSTemplateFormatVersion: 2010-09-09 Transform: "AWS::Serverless-2016-10-31" Description: API Gateway Sample Parameters: #Project Name Parameter ProjectPrefix: Type: String Default: "" #NetWork COnfigration Parameter VPCCIDR: Type: String Default: "192.168.0.0/16" PublicSubnetACIDR: Type: String Default: "192.168.0.0/24" PublicSubnetCCIDR: Type: String Default: "192.168.64.0/24" PrivateSubnetACIDR: Type: String Default: "192.168.128.0/24" PrivateSubnetCCIDR: Type: String Default: "192.168.192.0/24" #API Gateway ApigatewayRole: Type: String Default: APIGatewayRole Resources: #------------------------------------------------------- # API Gateway #------------------------------------------------------- #RestApi APIGatewayRestApi: Type: AWS::ApiGateway::RestApi Properties: Name: api_gateway #Resource APIGatewayResource: Type: AWS::ApiGateway::Resource Properties: RestApiId: !Ref APIGatewayRestApi ParentId: !GetAtt APIGatewayRestApi.RootResourceId PathPart: target_alb #Get Method APIGatewayGetMethod: Type: AWS::ApiGateway::Method Properties: AuthorizationType: NONE HttpMethod: GET Integration: Credentials: !Sub arn:aws:iam::${AWS::AccountId}:role/${ApigatewayRole} Type: HTTP IntegrationHttpMethod: GET IntegrationResponses: - StatusCode: 200 PassthroughBehavior: WHEN_NO_MATCH Uri: !Join - "" - - http:// - !GetAtt ALB.DNSName MethodResponses: - StatusCode: 200 ResourceId: !Ref APIGatewayResource RestApiId: !Ref APIGatewayRestApi #Post Method APIGatewayPostMethod: Type: AWS::ApiGateway::Method Properties: AuthorizationType: NONE HttpMethod: POST Integration: Credentials: !Sub arn:aws:iam::${AWS::AccountId}:role/${ApigatewayRole} Type: HTTP IntegrationHttpMethod: POST IntegrationResponses: - StatusCode: 200 PassthroughBehavior: WHEN_NO_MATCH Uri: !Join - "" - - http:// - !GetAtt ALB.DNSName MethodResponses: - StatusCode: 200 ResourceId: !Ref APIGatewayResource RestApiId: !Ref APIGatewayRestApi #Deployment APIGatewayDeployment: Type: AWS::ApiGateway::Deployment DependsOn: - APIGatewayGetMethod - APIGatewayPostMethod Properties: RestApiId: !Ref APIGatewayRestApi StageName: test # ------------------------------------------------------------# # VPC # ------------------------------------------------------------# VPC: Type: "AWS::EC2::VPC" Properties: CidrBlock: !Ref VPCCIDR EnableDnsSupport: "true" EnableDnsHostnames: "true" InstanceTenancy: default Tags: - Key: Name Value: !Sub "${ProjectPrefix}vpc" InternetGateway: Type: "AWS::EC2::InternetGateway" Properties: Tags: - Key: Name Value: !Sub "${ProjectPrefix}igw" InternetGatewayAttachment: Type: "AWS::EC2::VPCGatewayAttachment" Properties: InternetGatewayId: !Ref InternetGateway VpcId: !Ref VPC # ------------------------------------------------------------# # Subnet # ------------------------------------------------------------# PublicSubnetA: Type: "AWS::EC2::Subnet" Properties: MapPublicIpOnLaunch: true AvailabilityZone: !Sub ${AWS::Region}a CidrBlock: !Ref PublicSubnetACIDR VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${ProjectPrefix}public-subnet-a" PublicSubnetC: Type: "AWS::EC2::Subnet" Properties: MapPublicIpOnLaunch: true AvailabilityZone: !Sub ${AWS::Region}c CidrBlock: !Ref PublicSubnetCCIDR VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${ProjectPrefix}public-subnet-c" PrivateSubnetA: Type: "AWS::EC2::Subnet" Properties: MapPublicIpOnLaunch: false AvailabilityZone: !Sub ${AWS::Region}a CidrBlock: !Ref PrivateSubnetACIDR VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${ProjectPrefix}private-subnet-a" PrivateSubnetC: Type: "AWS::EC2::Subnet" Properties: MapPublicIpOnLaunch: false AvailabilityZone: !Sub ${AWS::Region}c CidrBlock: !Ref PrivateSubnetCCIDR VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${ProjectPrefix}private-subnet-c" # ------------------------------------------------------------# # RouteTable # ------------------------------------------------------------# PublicRouteTableA: Type: "AWS::EC2::RouteTable" Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${ProjectPrefix}public-route-a" PublicRouteTableC: Type: "AWS::EC2::RouteTable" Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${ProjectPrefix}public-route-c" PrivateRouteTableA: Type: "AWS::EC2::RouteTable" Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${ProjectPrefix}private-route-a" PrivateRouteTableC: Type: "AWS::EC2::RouteTable" Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${ProjectPrefix}private-route-c" # ------------------------------------------------------------# # Routing # ------------------------------------------------------------# PublicRouteA: Type: "AWS::EC2::Route" DependsOn: - InternetGatewayAttachment Properties: RouteTableId: !Ref PublicRouteTableA DestinationCidrBlock: "0.0.0.0/0" GatewayId: !Ref InternetGateway PublicRouteC: Type: "AWS::EC2::Route" DependsOn: - InternetGatewayAttachment Properties: RouteTableId: !Ref PublicRouteTableC DestinationCidrBlock: "0.0.0.0/0" GatewayId: !Ref InternetGateway # ------------------------------------------------------------# # RouteTable Associate # ------------------------------------------------------------# PublicSubnetARouteTableAssociation: Type: "AWS::EC2::SubnetRouteTableAssociation" Properties: SubnetId: !Ref PublicSubnetA RouteTableId: !Ref PublicRouteTableA PublicSubnetCRouteTableAssociation: Type: "AWS::EC2::SubnetRouteTableAssociation" Properties: SubnetId: !Ref PublicSubnetC RouteTableId: !Ref PublicRouteTableC PrivateSubnetARouteTableAssociation: Type: "AWS::EC2::SubnetRouteTableAssociation" Properties: SubnetId: !Ref PrivateSubnetA RouteTableId: !Ref PrivateRouteTableA PrivateSubnetCRouteTableAssociation: Type: "AWS::EC2::SubnetRouteTableAssociation" Properties: SubnetId: !Ref PrivateSubnetC RouteTableId: !Ref PrivateRouteTableC # ------------------------------------------------------------# # ALB # ------------------------------------------------------------# SecurityGroupForALB: Type: AWS::EC2::SecurityGroup Properties: GroupName: !Sub ${ProjectPrefix}SecurityGroupForALB GroupDescription: This is for SecurityGroupForALB VpcId: !Ref VPC SecurityGroupIngress: - CidrIp: "0.0.0.0/0" Description: This is for ALB FromPort: 80 IpProtocol: tcp ToPort: 80 Tags: - Key: Name Value: !Sub ${ProjectPrefix}SecurityGroupForALB #Listener Listener: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - TargetGroupArn: !Ref TargetGroup Type: forward LoadBalancerArn: !Ref ALB Port: 80 Protocol: HTTP #ALB ALB: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: IpAddressType: ipv4 Name: !Sub ${ProjectPrefix}LoadBalancer Scheme: internet-facing SecurityGroups: - !Ref SecurityGroupForALB Subnets: - !Ref PublicSubnetA - !Ref PublicSubnetC Type: application #TargetGroup TargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: HealthCheckEnabled: true HealthCheckIntervalSeconds: 60 #最大300 HealthCheckPath: /healthcheck HealthCheckPort: traffic-port HealthCheckProtocol: HTTP HealthCheckTimeoutSeconds: 30 #HealthCheckIntervalSeconds よりも小さい値でなくてはならない HealthyThresholdCount: 5 Matcher: HttpCode: 301 Name: !Sub ${ProjectPrefix}TargetGroup Port: 80 Protocol: HTTP TargetType: ip UnhealthyThresholdCount: 3 VpcId: !Ref VPCAPI Gateway Mock
Mockを作成し、固定値を返すサンプル。
AWSTemplateFormatVersion: 2010-09-09 Transform: "AWS::Serverless-2016-10-31" Description: API Gateway Mock Resources: #------------------------------------------------------- # API Gateway #------------------------------------------------------- #RestApi APIGatewayRestApi: Type: AWS::ApiGateway::RestApi Properties: Name: Mock #Resource APIGatewayResource: Type: AWS::ApiGateway::Resource Properties: RestApiId: !Ref APIGatewayRestApi ParentId: !GetAtt APIGatewayRestApi.RootResourceId PathPart: mock_test #POST Method APIGatewayPostMethod: Type: AWS::ApiGateway::Method Properties: AuthorizationType: NONE HttpMethod: POST Integration: Type: MOCK IntegrationHttpMethod: POST IntegrationResponses: - StatusCode: 200 ResponseTemplates: application/json: |- { "Method" : "POST", "name" : "Hello world", "age" : 100 } PassthroughBehavior: WHEN_NO_TEMPLATES RequestTemplates: application/json: |- { "statusCode" : 200, "message" : "Post test" } # RequestTemplates: # application/json: "{\"statusCode\": $input.json('$.statusCode'), \"message\": $input.json('$.message')}" MethodResponses: - StatusCode: 200 ResourceId: !Ref APIGatewayResource RestApiId: !Ref APIGatewayRestApi #Get Method APIGatewayGetMethod: Type: AWS::ApiGateway::Method Properties: AuthorizationType: NONE HttpMethod: GET Integration: Type: MOCK IntegrationHttpMethod: POST IntegrationResponses: - StatusCode: 200 ResponseTemplates: application/json: |- { "Method" : "GET", "name" : "Hello world", "age" : 100 } PassthroughBehavior: WHEN_NO_TEMPLATES RequestTemplates: application/json: |- { "statusCode" : 200, "message" : "Get test" } MethodResponses: - StatusCode: 200 ResourceId: !Ref APIGatewayResource RestApiId: !Ref APIGatewayRestApi #Deployment APIGatewayDeployment: Type: AWS::ApiGateway::Deployment DependsOn: - APIGatewayGetMethod - APIGatewayPostMethod Properties: RestApiId: !Ref APIGatewayRestApi StageName: test
- 投稿日:2020-07-12T11:30:27+09:00
インフラ素人のFlutterエンジニアがAWS SAA試験を受験してきました。
今回はAWS SAA試験を受験してきたため、その体験談をまとめたいと思います。
1.受験当時のAWSへの理解度
・AWS業務知識
業務では基本Flutterを書いているため、AWSを業務で使用した経験はありませんでした。IT知識という点ではFlutter, Firebase, Railsなどへの理解はありましたが、AWSなどのパブリッククラウドへの理解はほとんどなかったです。
2. まずは受験に備えて、試験範囲を把握しましょう
まずはこちらの公式サイトのリンクで、試験内容をざっくりと把握しましょう。
[https://aws.amazon.com/jp/certification/certified-solutions-architect-associate/:embed:cite]簡単に公式の試験内容をまとめると、求められるAWSの知識としては
AWSの各種サービス(EC2, EBS, RDS, ELB, S3)などにおいて、サービスを構築した経験
AWSの各種概念(IAMなどの責任共有モデル、リージョン、AZ)への理解
(EC2, EBS, RDS, ELB, S3)などの主要なサービス以外のサービスへの理解などがあるかと思います。合格ラインは1000点中720点以上で合格できます。
採点形式ですが、何問正解したら720点になるという仕組みではなく、受験者数の平均スコアなどを計算し、あなたの正答率と比べて店数が算出されます。
3. 参考にした、学習教材
試験勉強の為に、使用した学習教材としては、下記の教材を使用しました。
オンライン上のコンテンツ
- AWSサービス別の資料 - 別名Black Belt
こちらでは、AWSの各種サービスを網羅的に解説しています。スライドの情報量はかなり多いです。付随するYoutubeの動画などもありますが、長いものでは一つの動画で1時間ほどのものもあります。
最終的には、こちらも確認した方が良いですが、最初のつかみとしては、Udemyなどのオンライン講座の方が簡潔にまとめている為、オススメです。
自分はBlack Beltはそれほど詳しく確認していなかったため、最終的にはもう少し整理しておけば良かったなと後悔しています。
- Udemyの講座 - ハンズオン形式
Udemyの講座では、講義だけではなく、実際に手を動かしながら、ハンズオン形式で学べるものが多い為、動画を見ながら、手を動かしてサービスに対する理解を深めることができます。
英語では、こちら講座がオススメです。筆者はこちらの講座を使用しましたが、全てのサービスのハンズオンも用意されており、講義も重要なポイントをまとめているため、
・サービス内容の把握
・ハンズオンでの実践理解としてオススメです。
AWS Certified Solutions Architect - Associate 2020
日本語では、他の合格者の話を聞いている限りでは、こちらの講座がオススメです。
AWS 認定ソリューションアーキテクト – アソシエイト試験突破講座これらのサービスで、講義、ハンズオン形式で理解を深めた後は、模擬問題などを解きながら試験に備えるという形になるかと思います。その際には、筆者が作成した、オンライン上の有料の模擬問題などを500問題ほどまとめて掲載している、下記のアプリをお使い頂くのも、良いかと思います。
筆者が試験対策をする中で使用した模擬問題で、役に立った問題を全てまとめて、アプリに掲載しております。カテゴリー別に問題を出題できる機能などもあるため、苦手分野の克服などにも使用していただけます。
英語版からの翻訳などを含むオンライン上で有料で売られている模擬問題を全て掲載している為、それらの問題にかかる費用を節約できます。ただでさえ、高い試験料を払わなければいけない為、少しでも受験者の役に立てればと思い、今回作成しました。
クラウドプラクティショナー、ソリューションアーキテクトアソシエイト対策のアプリを現状公開しており、SAA対策は、一番上のオレンジ色のアイコンのアプリになります。
AWS試験対策アプリ
iOS
AWS認定ソリューションアーキテクト – 試験対策アプリAndroid
AWS 認定ソリューションアーキテクト – アソシエイト試験突破講座4. まとめ
冒頭でも述べましたが、試験の結果は642点とあと少し足りませんでした。知識としては上記のUdemyなどを使用して網羅的に学習したのですが、問題演習が足りなかったかなと思っています。
本試験中にも感じたのですが、問題の傾向としてはオンライン上にある問題と似ていると感じました。その為、試験合格のみを目的とするならハンズオンメインで学習するよりも問題に慣れてしまう方が合格できる可能性は高くなると思います。
少しでも、参考になりましたら、幸いです。質問などございましたら、コメント欄からお願いします!
- 投稿日:2020-07-12T09:43:52+09:00
EC2へのssh接続コマンドを短縮したい(エイリアス、関数)
sshコマンドが地味に長い
MacのターミナルからAWS EC2に接続する際にsshコマンドを打ちますが、
毎回このコマンドを全部打つのが面倒に感じることはないでしょうか?
ちなみに、sshコマンドは以下のような書き方になります。
(Amazon Linux2に接続する場合)$ ssh -i < キーペアのパス > ec2-user@< EC2のipアドレス >なので今回は、以下のようにコマンドを短縮していきたいと思います。
短縮版sshコマンド
$ sshec2 < EC2のipアドレス >だいぶ短くなりましたね!
接続したいEC2のIPアドレスを調べて、「sshec2」の後にスペースを入れてコピペするだけで
接続できるようになります。
※この「sshec2」は任意のコマンド名になりますので、ご自身で自由に設定できます
※使用するキーペアは固定化されますコマンド短縮の設定方法
ホームディレクトリにある、「.bashrcファイル」を編集して、関数を追加します。
エイリアスじゃダメなの?と思う方もいるかもしれませんが、実は関数でないと不具合が発生してしまいます。
理由は後ほどご説明します。$ vi ~/.bashrc.bashrcをvimエディタで開いたら、以下のコードを記述します。
function sshec2() { command ssh -i < キーペアのパス > ec2-user@$1 }※関数名は「sshec2」でなくても、任意で設定できます
コードを追加したら、ファイルを保存して閉じ、
以下のコマンドで設定を反映させます。$ source ~/.bashrcこれで設定は完了です!
sshec2のコマンドでssh接続ができるようになります。おまけ)エイリアスで設定できない理由
もし以下のようにエイリアスを作成してみるとどうなるでしょうか?
alias sshec2='ssh -i < キーペアのパス > ec2-user@$1'エイリアスでコマンドを打ってみます。
$ sshec2 < キーペアのパス >するとエラーが発生してしまします。
原因として、エイリアスの場合
以下のように@と引数の間にスペースが入ってしまうからなのでした。//引数($1) $ ssh -i < キーペアのパス > ec2-user@ < EC2のipアドレス > ↑
【参考サイト】
bashで引数つきのエイリアスを設定する
- 投稿日:2020-07-12T09:12:14+09:00
AWS CloudWatch Synthetics Monitoring を CloudFormation で設定する
参考) Using Synthetic Monitoring - Amazon CloudWatch Documentation
リソース
管理コンソールから CloudWatch Synthetics の Canary を作成すると、以下のリソースが展開される。
これを参考にして CloudFormation で各リソースを作成する。CloudFormation テンプレート
Parameters
以下をパラメタ化した。
- CanaryName : Canary の名前。配下にいろいろリソースを作る都合上からか、Canary の名前は
[a-z\-_]{,21}
と厳しい制約になっている。- BucketName : 結果の保存先とするバケット名
- TargetURL : 監視対象の URL
参考) Parameters - AWS CloudFormation
Parameters: CanaryName: Description: Synthetics Canary name to create Type: String BucketName: Description: Bucket name to save Synthetics results Type: String TargetURL: Description: Target URL to monitor Type: StringService Role
自動で作られる Role を参考に作成した。必要な permissions は Canary リソースに指定する ExecutionRoleArn の説明に書かれている。
- S3 バケットに結果を書き込むための s3:PutObject と s3::GetBucketLocation、あと s3:ListAllMyBuckets
- Lambda から実行ログを出力するための logs:CreateLogStream, logs:PutLogEvents, logs:CreateLogGroup
- CloudWatch Metrics に送信するための cloudwatch:PutMetricData
参考) AWS::IAM::Role - AWS CloudFormation
Resources: SyntheticsIamRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: "sts:AssumeRole" Policies: - PolicyName: "SyntheticsPolicy" PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - s3:PutObject - s3:GetBucketLocation Resource: - !Sub '${SyntheticsResultsBucket.Arn}/*' - Effect: Allow Action: - logs:CreateLogStream - logs:PutLogEvents - logs:CreateLogGroup Resource: - !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/cwsyn-${CanaryName}-*' - Effect: Allow Action: - s3:ListAllMyBuckets Resource: '*' - Effect: Allow Resource: '*' Action: cloudwatch:PutMetricData Condition: StringEquals: cloudwatch:namespace: CloudWatchSyntheticsS3 Bucket
参考) AWS::S3::Bucket - AWS CloudFormation
SyntheticsResultsBucket: Type: AWS::S3::Bucket Properties: BucketName: !Ref BucketName PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: trueSynthetics Canary
参考) AWS::Synthetics::Canary - AWS CloudFormation
上で生成した IAM Role、保存先バケット名を指定して作る。
Canary を生成すると Lambda 関数も自動的に生成される。ここで設定しているスクリプトは Blueprint として用意されている Heatbeat monitoring そのまま持ってきたもの。スクリプトは
Code
に直接記述するほか、S3 に保存しておいて場所を指定することができる。SAM で管理できると良いと思うのだけど...Canary: Type: AWS::Synthetics::Canary Properties: Name: !Ref CanaryName ExecutionRoleArn: !GetAtt SyntheticsIamRole.Arn Code: Handler: pageLoadBlueprint.handler Script: !Sub > var synthetics = require('Synthetics'); const log = require('SyntheticsLogger'); const pageLoadBlueprint = async function () { const URL = "${TargetURL}"; let page = await synthetics.getPage(); const response = await page.goto(URL, {waitUntil: 'domcontentloaded', timeout: 30000}); //Wait for page to render. //Increase or decrease wait time based on endpoint being monitored. await page.waitFor(15000); await synthetics.takeScreenshot('loaded', 'loaded'); let pageTitle = await page.title(); log.info('Page title: ' + pageTitle); if (response.status() !== 200) { throw "Failed to load page!"; } }; exports.handler = async () => { return await pageLoadBlueprint(); }; ArtifactS3Location: !Sub 's3://${SyntheticsResultsBucket}' RuntimeVersion: syn-1.0 Schedule: DurationInSeconds: 0 Expression: 'rate(5 minutes)' RunConfig: TimeoutInSeconds: 60 FailureRetentionPeriod: 31 SuccessRetentionPeriod: 31 StartCanaryAfterCreation: trueSNS Topic
Alarm の通知先トピック
参考) AWS::SNS::Topic - AWS CloudFormation
AlarmTopic: Type: AWS::SNS::TopicCloudWatch Alarm
Canary の実行結果は固定名の Metrics に送信されるので、それに対して Alarm を設定する。
参考) AWS::CloudWatch::Alarm - AWS CloudFormation
CanaryAlarm: Type: AWS::CloudWatch::Alarm Properties: MetricName: 'SuccessPercent' Namespace: 'CloudWatchSynthetics' Dimensions: - Name: CanaryName Value: !Ref CanaryName Statistic: Average Period: 300 EvaluationPeriods: 1 Threshold: 90 AlarmActions: - Ref: AlarmTopic OKActions: - Ref: AlarmTopic InsufficientDataActions: - Ref: AlarmTopic TreatMissingData: breaching ComparisonOperator: LessThanThreshold注意点
- Canary を生成すると Lambda が生成されるが、スタックを削除しても Lambda までは削除されないようなので、手動で削除する必要がある。
- 投稿日:2020-07-12T00:42:23+09:00
[AWS]S3でReact.jsを超爆速簡易型Webホスティングする
Reactを勉強中、それとなくおもしろおかしいWebページをスクラッチで作成することができたので、メンバーに
自慢公開した。今までメンバーに
自慢公開する際には、Heroku、GitHub ioなどを使用していたが、S3で爆速にできることが判明したので備忘録として残す。はじめに
ドメインとの紐付けやSSL対応などは実施しておりません。
なので公開する際には十分に気をつけてください。ReactPjの準備
公式ドキュメント参照。
$ npx create-react-app my-app $ cd my-app/ $ yarn buildS3で公開するデプロイようのバケットを作成
バケット名は任意の値を入力。
※ Route53の独自ドメインを割り当てたい場合は、ドメイン名と同じ値を入力すること。
ドメイン名はアカウント関係なく、プライマリーなので。。。リージョンは思いがなければ「アジアパシフィック(東京)」を選択。
入力したら「次へ」を押下。
オプションの設定は特に行わないので、さらに「次へ」を押下。
「パブリックアクセスをすべてブロック」のチェックボックスを外す。
バケットの作成は完了。
S3の設定を変更
Static website hostingを選択し、「このバケットを使用してウェブサイトをホストする」を押下。
インデックスドキュメントに「index.html」と入力し「保存」を押下。
次に、「アクセス権限」を選択し、バケットポリシーを押下。
バケットポリシーには下記文字列を入力する。
[BucketName]には今回作成したバケット名を入力すること。{ "Version": "2012-10-17", "Statement": [ { "Sid": "PublicReadGetObject", "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::[BucketName]/*" } ] }ソースコードをアップロード
AWS CLIでやった方がかっこいいけど、手動で。
先ほど作成したReactPJの配下に「build」というディレクトリができているので、開く。そして、配下のファイルを一括選択して、S3の「概要」めがけてドラッグ&ドロップ。
そしてアップロード。
これで、完了。
確認
StaticWebsiteHostingに記載のあるエンドポイントのURLにアクセス。
http://[BucketName].s3-website-ap-northeast-1.amazonaws.com/
でも可。
無事ブラウザで表示。まとめ
身内で公開してキャッキャうふふするのであればこれでOKかな?と思う。
次は独自ドメイン化してみようかな。
- 投稿日:2020-07-12T00:15:17+09:00
Dynamo DB ベストプラクティス(個人用メモ)
参考サイト
DynamoDBのテーブルを1つだけにする設計のコツ(考え方編)
https://mizumotok.hatenablog.jp/entry/2019/08/13/172430DynamoDBのテーブルを1つだけにする設計のコツ(汎用的手法編)
https://mizumotok.hatenablog.jp/entry/2019/08/14/175525多対多の関係を管理するためのベストプラクティス
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/bp-relational-modeling.htmlDynamoDB でリレーショナルデータをモデル化するためのベストプラクティス
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/bp-relational-modeling.htmlグローバルセカンダリインデックス
ローカルセカンダリインデックス(スパースインデックス推奨)Partition Keyに一番重要そうなテーブルのプライマリキー、Sort Keyにそのテーブルとリレーショナルなテーブルのプライマリキーを入れることで実現できます。Partition KeyとSort Keyだけを見るとテーブルの関係性を示しています。この設計手法を隣接関係のリスト設計パターン
⇒個別の設計用テーブルと、参照用の結合テーブルを用意するのがよさそう