20200712のAWSに関する記事は23件です。

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

全体の処理の流れ
  1. データファイルをS3にアップロード
  2. データをインポート
  3. 学習モデルの作成
  4. 予測の作成
  5. 予測結果をエクスポート
  6. 結果ファイルをS3からダウンロード
DatasetGroupを作成するには CreateDatasetGroup を使う。
main.go
func 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.go
func 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.go
func 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.go
func 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.go
func 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.go
func 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.go
func 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時間程度の処理時間がかかりました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SAAに合格するためにセルフマネジメントをTrelloでしてみました

タイトルの通り、先日SAA-C02を受験し合格しました。
ですが、本記事の主題としてはSAAそのものではなく、どう学習を計画し、その進捗を測るかということです。
私の場合、学習って何かしらでモチベートしないと結局中途半端になりがちなので、何かいい方法はないかなと思っていました。
そう考えた時、Trelloで必要なアクションを管理するのってピッタリなんじゃね?と思い、Trelloを活用してみたら結構ハマりました。

まずはSAA取得に関する諸々情報

以上。※もうSAAの話はでません笑

Trelloの活用

ステータスを定義しましょう

業務を推進する上でも同じことですが、タスクの進捗を計測するにはステータスの定義は重要です。
ここが曖昧だとボードに映るタスクの進捗は実態を映していません。要は甘い判断基準で今この辺〜っていうのと厳しい判断基準で今この辺!っていうのが混在してしまいます。
今回のボードには以下のステータスを用意しました。

  1. ToDo・・・やらなければいけない項目
  2. NiceToHave・・・優先度は低いが余裕があればやるとなお良い項目
  3. InProgress・・・進行中の項目
  4. Understand・・・アクションが完了した項目
  5. Complete・・・問題演習で正答した項目

なぜUnderstandCompleteがあるのか?勉強って「このページ読んだ!」と「このページの内容理解した!」って違いますよね?
これを表現したかったのです。一旦読んだぜ!見終わったぜ!みたいなのはUnderstand、これの問題解けたぜ!は理解したってことでCompleteにしました。

学びはカードのアクティビティログに連ねる

学習していると当然、これメモっときたいなあってことがあります。Udemyにはメモ機能あるし、ノートに手書きのメモも取れるし〜ってあっちこっちにナレッジが点在するのって基本よろしくないと思っています。そこで、何か気づきがあればとにかく、関連するカードのアクティビティログに書き連ねました。これで、「この時なんか気になることあったよな〜」って時にはとにかくTrelloを確認すればいい状態を作りました。

カードの粒度

いくつかの教材を使っているので、当然「UdemyのS3」「参考書のS3」「BlackbeltのS3」ってな具合にS3に関するそれぞれのコンテンツがあります。この単位でカードを作って進捗を管理したいので、カードの粒度は教材×AWSサービスにしました。ただ、カードの名前を「XXXのXXX」ってするのは見た目的にごちゃごちゃするので、教材軸はラベル機能を使って分類しています。赤のラベルはUdemy、黒はBlackbelt、黄色は参考書って具合です。他にもハンズオンしてみる時は緑のラベルでそのアクティビティのカードを作ったりなどもしています。

実際のボードの様子

以下のような感じでTrelloを活用していました。個人的に大事にしていたのは何よりもカードの総数です。試験に合格するって絶対的にこれだけの学習をこなさなければならないっていうラインがあると思っているので、それをカードの数で測定していました。
その数のなかで、ToDoの割合やCompleteの割合をなんとなく見て、今こんくらいかなあ〜っていうのを管理していました。管理していたといっても個人でやってる世界の話なので、かなりざっくりふわっとですが。。。
※どうでもいいですが背景を山にしたのは、山を登り切って合格を掴もうぜ!っていう意味です笑
スクリーンショット 2020-07-12 23.22.07.png

割とおすすめ

ツールを使って進捗を管理するのは試験勉強において結構オススメな気がしています。今回のSAA取得は学習期間大体1ヶ月ですが、決められた(あるいは決めた)タイムボックスの中で成果を出すのってやっぱり予実の管理が重要だと感じます。
この取り組みを通して、Doneの定義やカンバンボードの活用などのスクラム開発なんかに使えそうなエッセンスもある気がしていて、けっこううまみの多い活動のように思いました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

東京リージョンのAWS SESで、SMTP認証を使ってメール送信をする

TL;DR

  • 周知のとおり(?)、AWS SESが東京リージョンで使えるようになりました
  • 東京リージョンでSMTP認証情報を使う時のパスワードはシークレットアクセスキーを変換して使います
    • 米国東部(バージニア北部)リージョンなら署名バージョン2で変換
    • アジアパシフィック (東京)リージョンなら署名バージョン4で変換

Amazon SES 東京リージョン対応のお知らせ

Amazon SESとSMTP

Amazon SESでSMTP送信を行うためには、SMTP認証用のIAMユーザーを作成する必要があります。

Amazon SES SMTP 認証情報の取得

SMTP認証で使うユーザー名とパスワードは、作成したIAMユーザーのアクセスキーID、シークレットアクセスキーを変換したものになります。

ここで作成したIAMユーザーを使い、smtp-cliを使ってメール送信を行ってみたいと思います。

smtp-cli

この時、パスワードに使用する情報に注意します。

IAMユーザーは、Terraformで作成します。

環境

今回使用した環境は、こちらです。

$ terraform version
Terraform v0.12.28
+ provider.aws v2.70.0

$ ./smtp-cli --version
smtp-cli version 3.10

AWSのクレデンシャルは、環境変数で指定します。

export AWS_ACCESS_KEY_ID=xxxxx
export AWS_SECRET_ACCESS_KEY=xxxxx
export AWS_DEFAULT_REGION=ap-northeast-1

IAMユーザーの作成

最初に、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
}

Resource: aws_iam_access_key

Terraformを使ってIAMユーザーを作り、アクセスキーIDを作成すると、SMTP認証用のパスワードが得られるので、こちらを使用しましょう。

$ terraform apply

Outputに関しては、terraform.tfstateを参照…。

メールアドレスの検証とメール送信を行う

続いて、Amazon SESのメールアドレス検証を行い、サンドボックス状態にします。

E メールアドレスの検証

米国東部(バージニア北部)リージョンで試す

まず最初に、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の説明を見ると

Resource: aws_iam_access_key

署名バージョン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認証時のパスワード形式が違うみたいなので、覚えておきましょう…。

参考

署名バージョン 4 署名プロセス

署名バージョン 2 の署名プロセス

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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サーバの操作(レコードの追加)ができること

イメージ図

スライド1.PNG

他の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です。

スライド6.PNG

 続いて、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がちゃんとあるか確認
ls 

EC2側(Apache)の設定はこれで完了です。
Apache ではWebブラウザーからアクセスがあった際にデフォルトで表示させる場所が「/var/www/html」のため、HTMLファイルはそこに作成しております。

それでは、ちゃんとApacheの設定ができているかWebブラウザーで確認しましょう。
http://"Elastic IP" でアクセスして以下のように画面表示されればOKです。
 ※仮にElastic IPが123.123.123.123 ならhttp://123.123.123.123 へアクセス
スライド8.PNG

最後にIPアドレスではなく、ドメイン名でアクセスできるようにDNSの設定をしていきます。
今回ドメインは無料のFrrenom で取得しましたので、FreenomのDNSを使いますがDNSサーバの設定部分はご自身の環境に読み替えてください。
※とりあえずAレコードにEC2のElastic IPを登録できればOKです
Frrenomにログインし、Freenom DNSの画面にAレコードとして、Erastic IPを登録すればOKです。
スライド10.PNG
スライド11.PNG

DNSの伝播に時間がかかるため、20分程度経ってから「http://"取得したドメイン"」でアクセスしてみましょう。
IPアドレスでアクセスしたときと同じ画面がでれば、これですべての設定が完了です!

おわりに

作ってみるとAWS以外のApacheやらDNSやらも知識がなくて、勉強しないと...という気分になりました。
分からないことを格安な仮想サーバおよび無料サービスを使って、実際に手を動かしながら検証できるというは非常にありがたいですね。
今回はWebページのSSL化はしませんでしたが実際にWebページを運用するとなると、最近は常時SSL化は当たり前になってきているので、別途SSL化する構成についても記事も書こうと思います。

参考ページ

記事を書く上で参考させて頂いたページです。
今更聞けない!Webサーバーの仕組みと構築方法
【Freenom】取得したドメインのDNS設定~Aレコードの登録手順~

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.tf

00_main.tf はプロバイダ定義しか書いていないので、テキトーに設定しておく。

01_variables.tf には、内部で参照するALBとターゲットグループのリソース名を定義しておく。

01_variables.tf
variable "prefix" {
  default = "ACMTest"
}

locals{
  alb_name = "${var.prefix}-ALB"
  tg_name  = "${var.prefix}-TG"
}

02_data_sources.tf も、内部で使うデータリソースを定義するだけ。

02_data_sources.tf
data "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.tf
resource "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_keyalgorithm = "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すれば

キャプチャ6.PNG

といった感じで、ACMに証明書が登録される。

これで、ALBのDNS名にhttps://~でアクセスすれば、いつもの「信頼されていないサイト」の画面が出るので、terraform apply したディレクトリに出力されている https_test_root.crt の内容を、このあたりのサイトを参考にしつつ、ルート証明書に登録すれば良い。

これで、エラー画面を経由せずにHTTPS化したALBにアクセスできたぞ!
※危険なのでProduction環境では絶対にやらないように。最初に書いた通り、素直にACMのプライベートCA使うのが楽だよ!

その他

正常性確認が終わった後のゴミ掃除は以下のサイトを参考にすればOK。
(何回もトライ&エラーで消したりしていた)

Windows PCに追加した証明書の削除 - Windows Tips

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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との接続を行うところでエンドポイントの設定の反映が時間がかかったようで最初エラーになりました。
    • インスタンス起動の時など時間がかかるポイントではコンソールで反映されてもちょっと待った方がよさそうです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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つ以上のアベイラビリティゾーンを使う。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS MediaLive データ転送料金試算例

東京リージョンの1GB当たりのデータ転送量

/月 USD/GB
1GB ~ 10 TB まで 0.114
次の 40 TB 0.089
次の 100 TB 0.086
150 超 0.084

5Mbps、30分の配信を10人が視聴した場合のデータ転送量概算

( 5Mbps ÷ 8 ) × 1800(秒) × 10(人) = 11250MB
11250MB ÷ 1024 = 10.9GB
10.9GB × 0.114 = 1.24
1.24USD

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コロナ中、約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初心者でもあるので、見やすいスライドのつくり方などあれば教えてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

InvalidSmsRoleTrustRelationshipException

すぐにたどり着く解決策がないので、ユーザープール作り直しが吉。

[document Page] TInvalidSmsRoleTrustRelationshipException

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 などで分析可能な状態にする アーカイブされたログを分析する (アクセス件数など)

サービス間の連携を図示したものは下記となります。
00_ec2→logs→s3→athena.png

設定の流れ

EC2 (nginx ログ) → CloudWatch Logs の設定

EC2 上の nginx ログを CloudWatch Logs に出力します。下記記事を参考に、ひとまず access_log のみ転送するように設定します。

参考 : AWS CloudWatch LogsエージェントでAmazon EC2上のNginxのaccess.log , error.log , php-fpm error.log , Linuxのmessages , secureログを収集する

[/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 にログが転送されます。
01_CloudWatchLogs.png

CloudWatch Logs → S3 の設定

CloudWatch Logs を S3 にエクスポートします。なお、あらかじめ「logs.xxx.yyy.zzz」というバケットを作成済みとします (「xxx.yyy.zzz」は任意のドメイン名を示しています。適時 読み替えてください)。

下記記事を参考にしながら、設定を進めていきます。

参考 : CloudWatch LogsのS3エクスポート

まずはバケット「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 にエクスポート」を選択します。
02_CloudWatchLogs2.png

下記のように入力し、ロググループを S3 にエクスポートします。エクスポート単位 (=S3 上のオブジェクト単位) は 1 日単位としています。

項目 説明
開始 特定日付の最初の時間 (00:00) を指定します
終了 特定日付の最後の時間 (23:59) を指定します
S3 バケット名 あらかじめ準備したバケット名 (今回の場合は「logs.xxx.yyy.zzz」) を指定します
S3 バケットプレフィックス バケット配下に「log-export」というフォルダを作成し、その下にエクスポートします。「year=」や「month=」などは Athena のパーティションを意識したフォルダ構造です。Athena のパーティションについては後述します

03_CloudWatchLogs3.png

上記でエクスポート後、S3 に下記のとおりエクスポートされます。なお、ログは gzip で圧縮された形で出力されます。Athena は gzip 圧縮済みのファイルも読み込めるため、特に後続の作業に問題ありません。逆に、読み込みサイズが小さいほど Athena の料金が低くなるため、コスト管理の面ではありがたいです。
04_S3_01.png

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」を選択します。
05_Athena_01.png

Step1 は下記のとおり設定します。

項目 説明
Database 任意のデータベース名を指定します。今回は「server_log」としています
Table Name 任意のアクセス名を指定します。今回は「access_log」としています
Location of Input Data Set S3 バケットの URL を指定します。今回は「s3://logs.xxx.yyy.zzz/log-export/」です。URL の最後は必ずスラッシュを含めるようにしてください

06_Athena_02.png

Step2 は下記のとおり設定します。

項目 説明
Data Format 「Apache Web Logs」を指定します
Regex ^\S+ (\S+) \S+ \S+ \[([^\[]+)\] "(\w+) (\S+) (\S+)" (\d+) (\d+) "([^"]+)" "([^"]+)".*$」を設定します。CloudWatch Logs 経由で出力しているログのため、参考記事に比べて先頭に「\S+」が増えています

なお、正規表現 (Regex) の検証に関しては下記サイトが役立ちます。
参考 : Regular Expression Test Drive

07_Athena_03.png

Step3 では「Bulk add columns」ボタンを押下し、下記項目を入力して一括で列を設定します。

ip                STRING,
time_local        STRING,
method            STRING,
uri               STRING,
protocol          STRING,
status            int,
bytes_sent        int,
referer           STRING,
user_agent        STRING

08_Athena_04.png

最後の Step 4 では下記のとおりパーティションを設定します。パーティションは、S3 バケットのフォルダ構成に合わせて設定します。

Column Name Column type
year int
month int
day int

09_Athena_05.png

設定完了後、「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 <> '' で空白行を排除しています

検索結果は下記となります。
10_Athena_06.png

まとめ

以上、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データを可視化

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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の構成図で表現してみます。
Qiita_AWS構成図.png

ネットワークの作成&設定

VPCの作成

まずは、AWS上に仮想ネットワーク(VPC)を構築します。
なお、リージョンは米国東部(オハイオ)リージョンを選択しています。 理由は東京リージョンよりもオハイオリージョンのほうがAWS各種サービスの利用料金を安く抑えることができるためです。
Screen Shot 2020-06-27 at 12.25.07 PM.png

インターネットゲートウェイのアタッチ

インターネットからVPC内の各種リソースに接続することを可能にするため、作成したVPCにインターネットゲートウェイをアタッチします。
Screen Shot 2020-06-27 at 12.33.01 PM.png

サブネットの作成

実際にコンテナを実行するサブネットをVPC内に作成します。今回は2つのサブネットをそれぞれ異なるアベイラビリティゾーン(AZ)に作成します。
Screen Shot 2020-06-27 at 12.46.34 PM.png

ルートテーブルの設定

作成したサブネットのルートテーブルにインターネットゲートウェイを追加します。
なお、「0.0.0.0」はデフォルトルートを指します。
Screen Shot 2020-06-27 at 12.56.36 PM.png

作業用インスタンスの作成

EC2インスタンスの起動

構成図には含まれていませんが、Dockerイメージを作成しECRにイメージをプッシュするための作業用インスタンスを起動します。インスタンス起動時は自動割り当てパブリックIPを有効し、セキュリティグループはSSH接続できるようにポート22をインバウンドルールに追加します。
なお、今回はCentOSのイメージからインスタンスを起動します。
Screen Shot 2020-06-27 at 1.35.11 PM.png

各種パッケージのインストール

作成したインスタンスに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 dokcer

Dockerの再起動まで完了したら、一度サーバーから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          375MB

ECRのリポジトリにDockerイメージをプッシュ

AWSが提供しているコンテナイメージレジストリのElastic Container Registry(ECR)にEC-CUBEのDokcerイメージを保管するリポジトリを新規作成します。なお、リポジトリ名は作成したEC-CUBEのDockerイメージと同じ名前です。
Screen Shot 2020-06-28 at 5.32.37 PM.png

次に作成したECRのリポジトリにEC-CUBEのDockerイメージをプッシュします。「プッシュコマンドの表示」をクリックし、表示されるコマンドを作業用インスタンスで実行していきます。
Screen Shot 2020-06-28 at 5.45.29 PM.png

# 認証トークンを取得し、作成したレジストリに対して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を選択しています。
Screen Shot 2020-07-04 at 10.50.00 PM.png
Screen Shot 2020-07-04 at 10.54.08 PM.png
Screen Shot 2020-07-04 at 10.58.11 PM.png

タスク定義

ECSでコンテナを実行するにはタスクを定義する必要があります。なお、起動タイプはクラスターと同様にAWS Fargateを選択します。
Screen Shot 2020-07-04 at 11.16.12 PM.png
Screen Shot 2020-07-05 at 8.13.54 AM.png

タスク定義の名前を入力し、CPUやメモリのタスクサイズを指定します。
Screen Shot 2020-07-05 at 8.24.12 AM.png
Screen Shot 2020-07-05 at 8.24.24 AM.png

「コンテナの追加」をクリックし、コンテナ名とECRに保管しているDockerイメージのURIを入力します。また、ポートマッピングに80番を追加します。
Screen Shot 2020-07-05 at 8.59.36 AM.png
Screen Shot 2020-07-05 at 9.01.07 AM.png
Screen Shot 2020-07-05 at 9.02.34 AM.png

タスク定義が作成されると以下の画面が表示されます。
Screen Shot 2020-07-05 at 9.07.00 AM.png

ロードバランサーの作成

タスク(コンテナ)に対するHTTPトラフィックを分散するために必要なApplication Load Balancer(ALB)をEC2のコンソールから作成します。
Screen Shot 2020-07-12 at 8.43.38 AM.png
Screen Shot 2020-07-12 at 8.46.01 AM.png

リスナーにはHTTPプロトコルと80番ポートを指定します。
Screen Shot 2020-07-12 at 8.47.57 AM.png

作成したVPCおよびサブネットを指定します。
Screen Shot 2020-07-12 at 8.50.56 AM.png
Screen Shot 2020-07-12 at 8.53.34 AM.png

80番ポートに対するトラフィックを許可するセキュリティグループを設定します。
Screen Shot 2020-07-12 at 8.56.06 AM.png

ルーティングのターゲットグループの設定でIPをダーゲットの種類として選択します。
Screen Shot 2020-07-12 at 9.25.35 AM.png
Screen Shot 2020-07-12 at 9.26.46 AM.png
Screen Shot 2020-07-12 at 9.27.15 AM.png

ロードバランサーが問題なく作成されると以下のような画面が表示されます。
Screen Shot 2020-07-12 at 9.27.37 AM.png

サービスの定義

必要となるタスク数(コンテナ数)を維持するためにサービスを定義します。
Screen Shot 2020-07-12 at 9.39.23 AM.png

起動タイプにFargateを選択し、作成したタスク定義およびクラスターを指定します。
なお、今回はタスク数を3にしています。(同じコンテナが3つ立ち上がります。)
Screen Shot 2020-07-12 at 1.14.31 PM.png
Screen Shot 2020-07-12 at 1.14.46 PM.png
Screen Shot 2020-07-12 at 1.15.30 PM.png

ネットワーク構成の設定で作成したVPCとサブネットを指定します。
また、パブリックIPの自動割り当てを「enabled」にします。
Screen Shot 2020-07-12 at 1.21.47 PM.png

ロードバランシングの設定画面で「Application Load Balancer」を選択し、作成したロードバランサーを指定します。
Screen Shot 2020-07-12 at 1.23.12 PM.png

Screen Shot 2020-07-12 at 1.23.30 PM.png
Screen Shot 2020-07-12 at 1.24.30 PM.png

Screen Shot 2020-07-12 at 1.27.41 PM.png

サービス定義が完了すると以下のような画面が表示されます。
Screen Shot 2020-07-12 at 1.29.37 PM.png

動作確認

コンテナの動作確認

サービスが作成できたら、タスクが起動していることを確認します。
Screen Shot 2020-07-12 at 2.54.40 PM.png

それぞれのタスクに割り振られているパブリックIPにブラウザからアクセスするとEC-CUBEのトップページが表示されます。(今回はわかりやすいようにEC-CUBEのショップ名をコンテナごとに変えています。)
Screen Shot 2020-07-12 at 2.55.29 PM.png
Screen Shot 2020-07-12 at 2.59.28 PM.png

ロードバランサーの動作確認

これらのコンテナはALBによるロードバランシングが適応されているので、ALBに割り当てられているDNS名からでもアクセスすることができます。
Screen Shot 2020-07-12 at 3.07.50 PM.png

画面をリロードすると別のコンテナにHTTPトラフィックが切り替わります。
Screen Shot 2020-07-12 at 3.10.14 PM.png
Screen Shot 2020-07-12 at 3.10.26 PM.png
Screen Shot 2020-07-12 at 3.10.48 PM.png

コンテナのセッション情報について

ここまでの手順で「ECSでコンテナを起動し、ALBでHTTPのトラフィックを分散する」ことができましたが、現時点ではセッション情報がそれぞれのコンテナに保持されている状態のため、ユーザー認証等ができません。EC-CUBEの認証画面でIDとパスワードを入力し「ログイン」を押しても、トラフィックが別のコンテナに振り分けられてしまうため、ユーザー認証を通過することができません。
上記の事象については2つの解決策があります。

ALBのステッキーセッションを有効にする。

ElastiCacheでセッション情報を管理する。

感想

この記事では「ECSでコンテナを起動し、ALBでHTTPのトラフィックを分散する」を実際にやってみました。ECSやALBはAWSコンソールから操作できるため、想像していたよりも簡単にコンテナを起動しトラフィックを分散することができました。特にECSはAWSが提供しているコンテナオーケストレーションサービスということもあり、AWS各種サービスと比較的連携しやすいという印象を受けました。
機会があれば次回はElastic Kubernetes Service(EKS)も触ってみようと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSで学びサーバとネットワークの基本1: なんでサブネットマスクに分けるのか?

ネットワークは割り当てられたCIDRブロックを、そのまま使わず、さらに小さなCIDRブロックに分割して利用する
細分化したCIDRブロック=「サブネット」と言う。

なぜ、サブネットマスクに分けるのか?

サブネットに分割すると、その部分でネットワークを分けることができる。

理由1:物理的な隔離

社内LANを構築する時、フロアごとで別のサブネットに分けたい、部署でサブネットを分けたいというような物理的に分けたいことがある。
サブネットに分けておくと、万が一、どこかのサブネットが障害が起こした時も、違うところに、その影響が出にくい。

理由2:セキュリティ上の理由

サブネットを分ければ、それぞれに対して、別のネットワークの設定ができる。
社内にサーバを持っている場合は、「サーバー群だけを別のサブネットにして、そのサブネットとの通信を監視したり一部しかデータを通さないように構成したりすることでセキュリティを高める」とか、「インターネットに接続するサーバーだけを別のサブネットにして、社内LANから隔離する」という構成も、よくある。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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-image

QUARKUS - 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はtarunzipができないので色々入れておきます。
全部必要かわからないですが、これぐらい入れておきました。

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-maven

mvnのバージョン確認

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.propertiesquarkus.lambda.handlerにて設定します。

resources/application.properties
quarkus.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というファイルも生成されるので、ハンドラー名や環境変数はこちらを参考にしました。

関数を新規作成します。
ランタイムをユーザー独自のブートストラップを提供するとします。

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen).png

関数を作成したら、プログラムをアップロードします。
関数コードのところのアクションから.zipファイルをアップロードを選び、function.zipを選択します。

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen) (1).png

ハンドラーはnot.used.in.provided.runtimeとなります。

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen) (2).png

環境変数にDISABLE_SIGNAL_HANDLERSを追加し値をtrueとします。

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen) (4).png

ここまでで設定は完了です。以下のJSONをインプットにテスト実行してみましょう。

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen) (3).png

{
  "name": "Bill",
  "greeting": "hello"
}

動作結果がこちら。無事動きました。

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen) (5).png

AWS SDK を追加

AWS SDKを使用するには単純にpom.xmlに追加するだけでなく、いくつか設定が必要です。

手順1. SSL通信の有効化

resources/application.propertiesに以下の内容を追加します。(公式ドキュメント的にはデフォルトで有効って書いてある気もしますが)

resources/application.properties
quarkus.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.socacertsをコピーします。この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# 

事前準備したbootstrapcacertslibsunec.soが含まれているのがわかります。

終わりに

通常のJavaランタイムよりも、コールドスタートが早くなる検証結果はこちらで確認ください。

QuarkusがJava Lambdaを救う!?
https://qiita.com/moritalous/items/4de31a66edac728ba088

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 CLI

dockerのインストールはlocalテストのみ
deploy目的のみであれば利用しなくてもよい
(それでも個別にAPIGateway、Lambdaをデプロイしてテストするより楽)

ただし、docker Toolboxを以前利用していた環境(WinHomeでwsl2利用)だとdockerのエラーになりがち
環境変数削除で復帰(docker-machineの更新はなくても可)
対処法参考:
https://www.nuits.jp/entry/docker-could-not-read-ca-certificate

python用仮想環境を作成

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 --guided

Step1. 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 Example

Step2. Build your application
開発したコードおよび依存モジュールを.aws-sam\build以下に収集

cd sample-sam
sam build

Step3. 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.json 

5. AWS Toolkit for Visual Studio Code

  1. VS CodeにAWS Toolkitをインストール
  2. AWS ExplorerからLambda呼び出し(on AWS)できる
  3. ローカルで実行する場合はCodeLensのRun Localy/Debug Localy

その他メモ

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

はじめての AWS Amplify - ①初回設定を5分で終わらせるCloudFormationテンプレート

はじめに

AWS Amplify Console は、 静的ウェブサイトを簡単かつ迅速にホスティングすることのできるAWSサービス です。Webアプリの構築やデプロイを行うための CI/CDワークフローも提供される ことから、React、Angular、Vue、Ember などの シングルページアプリケーション (SPA) を 簡単にデプロイ することができます。また、AWS CodeCommit などの コードリポジトリと接続することで、コードコミットのたびにデプロイを実行 することもできます。

thumbnail

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テンプレートを実行することが可能となります。

cloudformation-launch-stack

作成される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 SNS

Amazon SNS

AWS CodeCommitAWS 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リポジトリに指定 します。また、masterdevelop の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 ConsoleAWS 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)」です。

amplify_codecommit_topic.png

この 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ワークフローを構築することができました

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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
endef

API Gateway → Lambda 同期実行版

API GatewayとLambdaを作成します。
以下、Parameters内のlambda-roleというところを、ご自身のIAM Role名に変更してください。

apigateway.yml
AWSTemplateFormatVersion: 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: Value

API Gateway → Lambda 非同期実行版

非同期の場合、DLQという便利な機能が使えるので、それも付け足しておきます。

以下、Parameters内のlambda-roleというところを、ご自身のIAM Role名に変更してください。

apigateway.yml
AWSTemplateFormatVersion: 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: 30

API 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: 30

API 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 VPC

API 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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

インフラ素人の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. 参考にした、学習教材

試験勉強の為に、使用した学習教材としては、下記の教材を使用しました。

オンライン上のコンテンツ

  1. AWSサービス別の資料 - 別名Black Belt

こちらでは、AWSの各種サービスを網羅的に解説しています。スライドの情報量はかなり多いです。付随するYoutubeの動画などもありますが、長いものでは一つの動画で1時間ほどのものもあります。

最終的には、こちらも確認した方が良いですが、最初のつかみとしては、Udemyなどのオンライン講座の方が簡潔にまとめている為、オススメです。

自分はBlack Beltはそれほど詳しく確認していなかったため、最終的にはもう少し整理しておけば良かったなと後悔しています。

  1. Udemyの講座 - ハンズオン形式

Udemyの講座では、講義だけではなく、実際に手を動かしながら、ハンズオン形式で学べるものが多い為、動画を見ながら、手を動かしてサービスに対する理解を深めることができます。

英語では、こちら講座がオススメです。筆者はこちらの講座を使用しましたが、全てのサービスのハンズオンも用意されており、講義も重要なポイントをまとめているため、

・サービス内容の把握
・ハンズオンでの実践理解

としてオススメです。

AWS Certified Solutions Architect - Associate 2020

日本語では、他の合格者の話を聞いている限りでは、こちらの講座がオススメです。
AWS 認定ソリューションアーキテクト – アソシエイト試験突破講座

これらのサービスで、講義、ハンズオン形式で理解を深めた後は、模擬問題などを解きながら試験に備えるという形になるかと思います。その際には、筆者が作成した、オンライン上の有料の模擬問題などを500問題ほどまとめて掲載している、下記のアプリをお使い頂くのも、良いかと思います。

筆者が試験対策をする中で使用した模擬問題で、役に立った問題を全てまとめて、アプリに掲載しております。カテゴリー別に問題を出題できる機能などもあるため、苦手分野の克服などにも使用していただけます。

英語版からの翻訳などを含むオンライン上で有料で売られている模擬問題を全て掲載している為、それらの問題にかかる費用を節約できます。ただでさえ、高い試験料を払わなければいけない為、少しでも受験者の役に立てればと思い、今回作成しました。

クラウドプラクティショナー、ソリューションアーキテクトアソシエイト対策のアプリを現状公開しており、SAA対策は、一番上のオレンジ色のアイコンのアプリになります。

AWS試験対策アプリ

iOS
AWS認定ソリューションアーキテクト – 試験対策アプリ

Android
AWS 認定ソリューションアーキテクト – アソシエイト試験突破講座

4. まとめ

冒頭でも述べましたが、試験の結果は642点とあと少し足りませんでした。知識としては上記のUdemyなどを使用して網羅的に学習したのですが、問題演習が足りなかったかなと思っています。

本試験中にも感じたのですが、問題の傾向としてはオンライン上にある問題と似ていると感じました。その為、試験合格のみを目的とするならハンズオンメインで学習するよりも問題に慣れてしまう方が合格できる可能性は高くなると思います。

少しでも、参考になりましたら、幸いです。質問などございましたら、コメント欄からお願いします!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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で引数つきのエイリアスを設定する

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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: String

Service 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: CloudWatchSynthetics

S3 Bucket

参考) AWS::S3::Bucket - AWS CloudFormation

  SyntheticsResultsBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref BucketName
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true

Synthetics 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: true

SNS Topic

Alarm の通知先トピック

参考) AWS::SNS::Topic - AWS CloudFormation

  AlarmTopic:
    Type: AWS::SNS::Topic

CloudWatch 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 までは削除されないようなので、手動で削除する必要がある。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[AWS]S3でReact.jsを超爆速簡易型Webホスティングする

Reactを勉強中、それとなくおもしろおかしいWebページをスクラッチで作成することができたので、メンバーに自慢公開した。

今までメンバーに自慢公開する際には、Heroku、GitHub ioなどを使用していたが、S3で爆速にできることが判明したので備忘録として残す。

はじめに

ドメインとの紐付けやSSL対応などは実施しておりません。
なので公開する際には十分に気をつけてください。

ReactPjの準備

公式ドキュメント参照。

$ npx create-react-app my-app
$ cd my-app/
$ yarn build

S3で公開するデプロイようのバケットを作成

S3のコンソールから「バケットを作成する」を押下。
スクリーンショット 2020-07-12 0.04.24.png

バケット名は任意の値を入力。
※ Route53の独自ドメインを割り当てたい場合は、ドメイン名と同じ値を入力すること。
 ドメイン名はアカウント関係なく、プライマリーなので。。。

リージョンは思いがなければ「アジアパシフィック(東京)」を選択。
スクリーンショット 2020-07-12 0.06.50.png

入力したら「次へ」を押下。
オプションの設定は特に行わないので、さらに「次へ」を押下。
スクリーンショット 2020-07-12 0.08.58.png

「パブリックアクセスをすべてブロック」のチェックボックスを外す。
スクリーンショット 2020-07-12 0.10.40.png

「次へ」を押下し、問題がなければ「バケットを作成」を押下
スクリーンショット 2020-07-12 0.12.42.png

バケットの作成は完了。

S3の設定を変更

先ほど作成したバケットの「プロパティ」を選択。
スクリーンショット 2020-07-12 0.18.03.png

Static website hostingを選択し、「このバケットを使用してウェブサイトをホストする」を押下。
インデックスドキュメントに「index.html」と入力し「保存」を押下。
スクリーンショット 2020-07-12 0.17.12.png

次に、「アクセス権限」を選択し、バケットポリシーを押下。
バケットポリシーには下記文字列を入力する。
[BucketName]には今回作成したバケット名を入力すること。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::[BucketName]/*"
        }
    ]
}

スクリーンショット 2020-07-12 0.21.49.png
「保存」を押下したら、S3バケットの設定は完了。

ソースコードをアップロード

AWS CLIでやった方がかっこいいけど、手動で。
先ほど作成したReactPJの配下に「build」というディレクトリができているので、開く。

そして、配下のファイルを一括選択して、S3の「概要」めがけてドラッグ&ドロップ。
スクリーンショット 2020-07-12 0.29.57.png
そしてアップロード。
スクリーンショット 2020-07-12 0.30.58.png

これで、完了。

確認

StaticWebsiteHostingに記載のあるエンドポイントのURLにアクセス。
http://[BucketName].s3-website-ap-northeast-1.amazonaws.com/
でも可。
スクリーンショット 2020-07-12 0.34.30.png
無事ブラウザで表示。

まとめ

身内で公開してキャッキャうふふするのであればこれでOKかな?と思う。
次は独自ドメイン化してみようかな。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dynamo DB ベストプラクティス(個人用メモ)

参考サイト

DynamoDBのテーブルを1つだけにする設計のコツ(考え方編)
https://mizumotok.hatenablog.jp/entry/2019/08/13/172430

DynamoDBのテーブルを1つだけにする設計のコツ(汎用的手法編)
https://mizumotok.hatenablog.jp/entry/2019/08/14/175525

多対多の関係を管理するためのベストプラクティス
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/bp-relational-modeling.html

DynamoDB でリレーショナルデータをモデル化するためのベストプラクティス
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/bp-relational-modeling.html

グローバルセカンダリインデックス
ローカルセカンダリインデックス(スパースインデックス推奨)

Partition Keyに一番重要そうなテーブルのプライマリキー、Sort Keyにそのテーブルとリレーショナルなテーブルのプライマリキーを入れることで実現できます。Partition KeyとSort Keyだけを見るとテーブルの関係性を示しています。この設計手法を隣接関係のリスト設計パターン

⇒個別の設計用テーブルと、参照用の結合テーブルを用意するのがよさそう

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む