20201216のAWSに関する記事は30件です。

CodePipelineがGHEをサポートすることになったよ〜

はじめに

CodePipelineについて

CodePipelineとは

  • 開発、テスト、デプロイまでのプロセスを自動化するサービスです。

概念

  • パイプラインは、ソフトウェアの変更がリリースプロセスをどのように通過するかを記述するワークフロー構造です。
  • 各パイプラインは一連のStageで構成されています。

Stage

  • ステージは、ソースコードが構築され、テストが実行されるビルドステージである場合もあれば、コードをランタイム環境にデプロイするデプロイステージの場合もあります。
  • 各ステージは、連続または並列のアクションで構成されています。

Action

  • アクションは、アプリケーションコードに対して実行される一連の操作であり、アクションがパイプライン内で指定されたポイントで実行されるように設定されます。
  • 例えばこれには、インスタンスにアプリケーションをデプロイするためのアクションなどが含まれます。

使用できるAction

  • CodePipelineのActionの種類は source、 build、 test、 deploy、 approval、および invoke(呼び出し)があります。
Actionの種類 有効なアクションプロバイダー
source Amazon S3
Amazon ECR
CodeCommit
CodeStarSourceConnection (ビットバケット、 GitHub、 GHE)
build CodeBuild
カスタム CloudBees
カスタム Jenkins
カスタム TeamCity
Test CodeBuild
AWS Device Farm
カスタム BlazeMeter
ThirdParty GhostInspector
カスタム Jenkins
ThirdParty マイクロフォーカス StormRunner 負荷
ThirdParty ヌーボラ
ThirdParty ランスコープ
Deploy Amazon S3
AWS CloudFormation
CodeDeploy
Amazon ECS
Amazon ECS (Blue/Green) (これは CodeDeployToECS アクションです)
Elastic Beanstalk
AWS AppConfig
AWS OpsWorks
AWS Service Catalog
Amazon Alexa
カスタム XebiaLabs
approval(承認) 手動
invoke(呼び出し) AWS Lambda
AWS Step Functions

CodePipeline の一部のアクションタイプは、限定された AWS リージョンでの使用になるそうです。

CodePipelineでのパイプラインおよびステージ構造の要件

  • 以下の要件を満たしていればCodePipelineを使用できます。

1.パイプラインに少なくとも 2 つのステージを含める必要がある。

2.パイプラインの最初のステージには、少なくとも 1 つのソースアクションが含まれている必要がある。

3.ソースアクションは、パイプラインの最初のステージにのみ含める。

4.各パイプラインのいずれかのステージには、必ずソースアクション以外のアクションを含めること。

5.パイプライン内の全てのステージ名は必ず一意であること。

参考

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

CodePipelineがGHEをサポートすることになった

はじめに

CodePipelineについて

CodePipelineとは

  • 開発、テスト、デプロイまでのプロセスを自動化するサービスです。

概念

  • パイプラインは、ソフトウェアの変更がリリースプロセスをどのように通過するかを記述するワークフロー構造です。
  • 各パイプラインは一連のStageで構成されています。

Stage

  • ステージは、ソースコードが構築され、テストが実行されるビルドステージである場合もあれば、コードをランタイム環境にデプロイするデプロイステージの場合もあります。
  • 各ステージは、連続または並列のアクションで構成されています。

Action

  • アクションは、アプリケーションコードに対して実行される一連の操作であり、アクションがパイプライン内で指定されたポイントで実行されるように設定されます。
  • 例えばこれには、インスタンスにアプリケーションをデプロイするためのアクションなどが含まれます。

使用できるAction

  • CodePipelineのActionの種類は source、 build、 test、 deploy、 approval、および invoke(呼び出し)があります。
Actionの種類 有効なアクションプロバイダー
source Amazon S3
Amazon ECR
CodeCommit
CodeStarSourceConnection (ビットバケット、 GitHub、 GHE)
build CodeBuild
カスタム CloudBees
カスタム Jenkins
カスタム TeamCity
Test CodeBuild
AWS Device Farm
カスタム BlazeMeter
ThirdParty GhostInspector
カスタム Jenkins
ThirdParty マイクロフォーカス StormRunner 負荷
ThirdParty ヌーボラ
ThirdParty ランスコープ
Deploy Amazon S3
AWS CloudFormation
CodeDeploy
Amazon ECS
Amazon ECS (Blue/Green) (これは CodeDeployToECS アクションです)
Elastic Beanstalk
AWS AppConfig
AWS OpsWorks
AWS Service Catalog
Amazon Alexa
カスタム XebiaLabs
approval(承認) 手動
invoke(呼び出し) AWS Lambda
AWS Step Functions

CodePipeline の一部のアクションタイプは、限定された AWS リージョンでの使用になるそうです。

CodePipelineでのパイプラインおよびステージ構造の要件

  • 以下の要件を満たしていればCodePipelineを使用できます。

1.パイプラインに少なくとも 2 つのステージを含める必要がある。

2.パイプラインの最初のステージには、少なくとも 1 つのソースアクションが含まれている必要がある。

3.ソースアクションは、パイプラインの最初のステージにのみ含める。

4.各パイプラインのいずれかのステージには、必ずソースアクション以外のアクションを含めること。

5.パイプライン内の全てのステージ名は必ず一意であること。

参考

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

【AWS】Kinesisを大体理解する

Kinesisとは

ストリームデータを収集・処理するためのフルマネージドサービス群
・Amazon Kinesis Streams
・Amazon Kinesis Firehose
・Amazon Kinesis Analytics

Amazon Kinesis Streams

ストリームデータを処理するためのアプリケーションを独自に構築

※下記参考資料から抜粋
20180110_AWS-BlackBelt-Kinesis-14.jpg

野菜の仕分けに例えてみるとこんな感じでしょうか・・・
適当メモ-1.jpg

Firehoseは後日追記します。

参考資料

サービス別資料集

免責

学習用です。誤った記述がある可能性があります。正しい内容を知りたい場合は参考資料へ。。。

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

API Gateway と Lambda と Dynamo

はじめに

前回のものに API Gateway を繋いでみます。こんな感じがゴール

image.png

API Gateway

基本

GUI がかなり直感的でなので頭使わずに使える感じです

  1. エンドポイントのリソース /project 作って
  2. リソースにメソッド GET PUT POST DELETE 作って
  3. メソッド毎にLambda のコード project にマッピングするだけ

image.png

忘れがちなのが、APIの変更は「デプロイ」を押してデプロイしないといけないこと。これでとりあえず動くはず

image.png

APIキー

APIキーを使うときはちょっと面倒

  1. リソースの各メソッド毎にAPIキーでの認証を必須にする
  2. 「使用料プラン」を作成する(ここでAPIに対するレートリミットなどを設定)
  3. 「ステージ」を作成する(開発用とか商用とかそういう意味のステージのことで、ここでは dev_1 とした)
  4. 「APIキー」「使用料プラン」「ステージ」を関連づける

image.png

変更の反映に数分かかることもあるようですが、これでAPIキーを付けてコールできるはず

curl -s -X GET -H "x-api-key: your-api-key-here" https://<project-id>.execute-api.us-east-2.amazonaws.com/dev_1/projects | jq

Lambda 統合プロキシ

「Lambda 統合プロキシ」にチェックを入れるだけでHTTPリクエストに含まれる様々なパラメターがそのまま Lambda に渡される。

image.png

例えば、HTTPメソッドが POST なのか GET なのかが引き渡されるので Lambda 側でわかる.

こんな風に読んだ時に

curl -s -X GET -H "x-api-key:your-api-key-here" https://<project-id>.execute-api.us-east-2.amazonaws.com/dev_1/projects?say=ciao -d '{"say": "hello"}'

Lambda に event として渡されるパラメータはこんな感じ

event
{
  "resource": "/projects",
  "path": "/projects",
  "httpMethod": "GET",
  "headers": {
    "accept": "*/*",
    "content-type": "application/x-www-form-urlencoded",
    "Host": "<project-id>.execute-api.us-east-2.amazonaws.com",
    "User-Agent": "curl/7.64.1",
    "X-Amzn-Trace-Id": "Root=1-5fd44390-5f78ee5a289e7e3a0355bec2",
    "x-api-key": "your-api-key",
    "X-Forwarded-For": "***.**.**.**",
    "X-Forwarded-Port": "443",
    "X-Forwarded-Proto": "https"
  },
  "multiValueHeaders": {
    "accept": [
      "*/*"
    ],
    "content-type": [
      "application/x-www-form-urlencoded"
    ],
    "Host": [
      "<project-id>.execute-api.us-east-2.amazonaws.com"
    ],
    "User-Agent": [
      "curl/7.64.1"
    ],
    "X-Amzn-Trace-Id": [
      "Root=1-5fd44390-5f78ee5a289e7e3a0355bec2"
    ],
    "x-api-key": [
      "<your-api-key>"
    ],
    "X-Forwarded-For": [
      "***.**.**.**"
    ],
    "X-Forwarded-Port": [
      "443"
    ],
    "X-Forwarded-Proto": [
      "https"
    ]
  },
  "queryStringParameters": {
    "say": "ciao"
  },
  "multiValueQueryStringParameters": {
    "say": [
      "ciao"
    ]
  },
  "pathParameters": null,
  "stageVariables": null,
  "requestContext": {
    "resourceId": "<resource-id>",
    "resourcePath": "/projects",
    "httpMethod": "GET",
    "extendedRequestId": "Xa9-iG9kCYcFU8Q=",
    "requestTime": "12/Dec/2020:04:14:08 +0000",
    "path": "/dev_1/projects",
    "accountId": "<account-id>",
    "protocol": "HTTP/1.1",
    "stage": "dev_1",
    "domainPrefix": "<project-id>",
    "requestTimeEpoch": 1607746448145,
    "requestId": "06499aaf-9168-49d7-b4da-2a0a3ad5d4ad",
    "identity": {
      "cognitoIdentityPoolId": null,
      "cognitoIdentityId": null,
      "apiKey": "<your-api-key>",
      "principalOrgId": null,
      "cognitoAuthenticationType": null,
      "userArn": null,
      "apiKeyId": "<api-key-id>",
      "userAgent": "curl/7.64.1",
      "accountId": null,
      "caller": null,
      "sourceIp": "***.**.**.**",
      "accessKey": null,
      "cognitoAuthenticationProvider": null,
      "user": null
    }, 
    "domainName": "<project-id>.execute-api.us-east-2.amazonaws.com",
    "apiId": "<api-id>"
  },
  "body": "{\"say\": \"hello\"}",
  "isBase64Encoded": false
}

クライアントからのデータ bodyevent['body'] でアクセスできるので Lambda 側の CRUD するコードを前回から少し変えて、こんな感じにしときます

require 'json'
require 'aws-sdk-dynamodb'

def add_project(table, body)
  table.put_item({ item: body })  
end

def delete_project(table, body)
  params = { table_name: table, key: { 'project-id': body['project-id'] } }
  begin
    table.delete_item(params)
  rescue Aws::DynamoDB::Errors::ServiceError => error
    puts error.message
  end
end

def update_project(table, body)
  params = {
    table_name: table,
    key: { 'project-id': body['project-id'] },
    attribute_updates: {
      name: {
        value: body['name'],
        action: "PUT"
      }
    }
  }
  table.update_item(params)  
end

def list_project(table)
  scan_output = table.scan({ limit: 50, select: "ALL_ATTRIBUTES" })

  scan_output.items.each do |item|
    keys = item.keys
    keys.each do |k|
      puts "#{k}: #{item[k]}"
    end
  end
end

def lambda_handler(event:, context:)

  http_method = event['httpMethod'] # このへんを追加
  body = JSON.parse(event['body'])  # このへんを追加

  dynamodb = Aws::DynamoDB::Resource.new(region: 'us-east-2')
  table = dynamodb.table('project')

  case http_method
    when 'GET'    then list_project(table)
    when 'PUT'    then update_project(table, body)
    when 'POST'   then add_project(table, body)
    when 'DELETE' then delete_project(table, body)
    else 0
  end

  { statusCode: 200, body: JSON.generate(list_project(table)) }

end

例えばこんな感じでリクエスト投げると、HTTPのメソッドをみて CRUD 処理をする。ちなみに jq しとくと見やすいのでおすすめ

curl -s -X POST -d '{ "project-id":101, "name":"Cycling", "is-default":false }' -H "x-api-key: <your-api-key-here>" https://<project-id>.execute-api.us-east-2.amazonaws.com/dev_1/projects | jq

うむ、動いたぞ

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

クールなグルーをブログル(Amazon DevOps Guru)

前書き

本記事はJapan APN Ambassador Advent Calendar 2020の23日目の記事です。
APN Ambassadorとは?については、これまでの記事で説明いただいているので割愛します。

私自身は、普段はMSP(24365の有人監視運用部隊、運用の自動化推進)とセキュリティのお仕事に従事しています。
巷に言われるDevSecOpsをロールとしても体現している形となります。
(実際には、ひとりでやっているわけではなく、強いメンバーたちによって実現されているわけですが)

さて、オンライン開催となった(現地に行きたかった。。。)今年のAWS re:Inventでも、いろいろ新しい発表がありました。

GCPのAnthosを彷彿とさせるGKE Anywhereや、どっちかいうとWorkspaces形式の方が需要があるんじゃないかと思う、EC2 Mac Instanceとかインパクトのある発表が続いています。
運用に携わる私が特に気になっているのが、「Amazon DevOps Guru」。

本日は、私自身の業務に今後大きなインパクトを与える可能性のある、「Amazon DevOps Guru」のプレビューを弄りながら、紹介と感想を書いてみたいと思います。

Amazon DevOps Guruとは?

一言で言えば、運用を補助するツールです。
メトリクスやイベントを機械学習にかけ、問題点の発見や解決のサポートを行ってくれます。
「20 年以上の Amazon と AWS の運用上の優秀性から得た機械学習を適用して、大規模で高可用性のアプリケーションを実行し、…」(FAQsから抜粋)
スゴい。。。

本家に簡潔に案内されているのでこちらを参照ください
https://docs.aws.amazon.com/devops-guru/latest/userguide/welcome.html

ちなみに、「Guru(グルー)」とは、IT系の間では、「超人的技術者」のような使い方をされています。
元々は、宗教的な単語で、指導者とか道士とかの意味らしいです。

How much?

でもお高いんでしょ?
って思ったものの、法外な金額ではなさそう。

https://aws.amazon.com/devops-guru/pricing/

Hands-On!

理解するには手を動かすのが一番!

検証用アプリケーションの生成

ってことで、Amazon DevOps Gureで解析する対象のアプリケーションを作ります。

作るといっても、aws-samples上に公開されているものを拝借します。
今回は以下を利用。
https://github.com/aws-samples/amazon-api-gateway-url-shortener

ほとんど手を動かすことなく、以下のような環境ができちゃいました。
Screenshot at Dec 16 21-13-34.png

手順は当該リポジトリのREADME.mdにて詳細に記載いただいているので、ここでは割愛します。
各AWSリソースはCloud Formation(CFn)から生成されます。ここでは、ReadMeに倣って、Stackを「URLShortener」と名付けました。

aws-samplesには他にも様々なサンプルが公開されているので、ドキュメントだけではしっくりこないときに活用するといいと思います。

いざ、Amazon DevOps Guru開始

マネジメントコンソールから、対象リージョンのAmazon DevOps Guruを有効化します。
2020/12/12現在、直接サービス画面には飛べません。
Amazon CodeGuruを経て遷移します。
(「DevOps Guru」、「CodeGuru」、間のスペースの有無の違いは何故だろう。。。)

canvas (1).png

有効化時に、Stack指定?それとも全部?と聞かれます。
ここでは、先ほど作ったStackを指定します。あとで選び直すことも可能です。

SNS通知などの機能もありますが、まずはこれだけで、Amazon DevOps GuruによるDevOps生活がスタートします。
しばらくすると、解析メトリクスに追加されていることが確認できます。
canvas (2).png

テスト

ポチポチっとしただけでDevOps Guruは働き出してくれたようなので、仕事ぶりを確認してみます。
運用上のインシデントを擬似的に起こすため、サンプル環境のDynamoDBを困らせてみます。
canvas (5).png

ConditionalCheckFailedを継続的に発生させてみます。
今回は、以下のようにダミーのURLを前提条件として、継続的なエラーを発生させました。

iyagarase.sh
#!/bin/sh

for i in {1..360}

do
    aws dynamodb update-item \
        --table-name URLShortener-LinkTable-XXXXXXXXXXX \
        --key '{ "id": { "S": "myqiita" }}' \
        --update-expression "set #seturl = :newval" \
        --expression-attribute-names '{ "#seturl": "url" }' \
        --expression-attribute-values '{ ":curval": { "S": "https://qiita.com/Hiroyama-YutakaXXX" }, ":newval": { "S": "https://qiita.com/Hiroyama-Yutaka" } }' \
        --condition-expression "#seturl = :curval" \
        --profile hogefuga

    sleep 10;
done

暫く後、DevOps Guruのコンソールを覗くと、インサイトの発生痕跡が!
(↑のスクリプト、1回では何もでなかったので、2回叩きました。この辺は機械学習の判定になるので、ケースバイケースな気がします。)
canvas (4).png

インサイトのリスト
Screenshot at Dec 16 21-35-58.png

インサイトの中身
canvas (6).png

グルー先生が、スタイリッシュに問題点を伝えてくれます。
MTTRも計測してくれるのがCool :)

まとめ

機能面とコスト、それぞれの観点で記載します。
※あくまでプレビュー版です。ネガティブなコメントは、GA版へのフィードバックとして。

機能

情報を収集し分析する、自身の環境にチューニング済みの完成された基盤をすでに持っているユーザーからすれば、目新らしいものではないかもしれません。
しかしながら、そうでないユーザーたち(ほとんどがそうだと思う)からすれば、基盤を構築、運用することなくオンデマンドに利用できるこのサービスは、非常に強力なツールになりそうです。
現状、DevOpsの"Dev"側へのメトリクスを中心に解析していそうで(完全な推測)、これだけでMSP事業者が不要になることはないですが、組み合わせることでより高度な運用の可能性を感じました。

例えば、SQS。DevOps Guru登場前は、一般的にはCWのメトリクスを静的に監視するか、ハートビート的に出し入れして生存確認するくらいが関の山だったと思います。
AWSの機関として動いているSQSのようなサービスについて、一般的なAWSユーザーからは到底収集できない膨大な学習データを元にしたプロアクティブなアラートは、これまでの機能では実現できなかっただろうと思います。

コスト

コスパとしては悪くないんじゃないかなと思っています。むしろ、破格。
(ベータ版のため、今後、対象となるメトリクスが増えると変わってくるかもですが)

さらに対象リソースを絞り込めるのはすごくありがたいです。
ベストプラクティスとして、機能や運用レベルごとにAWSアカウントを分けるのがベターとは理解しているものの、必ずしもDevOps Guruを導入したいアカウントのリソース全てを対象としたくないことはありうるので。

ただし、絞り込みは現状、CFnスタック単位のみっぽいです。
CDKやSAMを含むCFn派には問題ないですが、Terraform派の人は辛いかもです。

話は逸れますが、AWS Protonも、CFn+Jinjaな感じっぽいのでCFnをファーストチョイスにした方がいいんだろうか(Issueは早速上げられている模様)。
もともとCFn派の僕は無問題でしたが、タグでのリソース絞り込みなどTerraform派の救いも欲しいところ。
マネコン派の人はこれを機にIaCを検討しましょう。
CFn、SAM、CDK、Terraform、その他、AWSのIaCには選択肢が増えすぎて悩ましい。。。

また、従量課金について、このような間接的サービスには、予め上限を設定(突破後は機能を自動停止)できるとありがたいなぁと思いました。

参照

Amazon DevOps Guru
https://aws.amazon.com/devops-guru/
利用したサンプル環境
https://aws.amazon.com/blogs/compute/building-a-serverless-url-shortener-app-without-lambda-part-1/
DynamoDBでのConditionalCheckFailed
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html#WorkingWithItems.ReadingData
AWS ProtonロードマップのTerraformに関するIssue
https://github.com/aws/aws-proton-public-roadmap/issues/1

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

AWS UbuntuインスタンスでのUnreal Enigine4の起動にはまりまくったのでまとめ

はじめに

AWSのUbuntuインスタンスでUnreal Engine4を起動させようとしたところ、タイトル通りに泥沼にはまりました。
同じような人の手助けになればと思い、この記事を書きました。

2行でまとめ

AWS UbuntuインスタンスでUE4を使おうとしたところVulkan DriverやらOpenGLのエラーで起動できなかった
UE4のバージョンを4.22.1に下げ、インスタンスのグラフィックドライバを変更してOpenGLのバージョンを上げたところ無事起動した

必要なもの

  • OpenGLで起動するUnreal Engine4(今回は4.22.1で起動)
  • OpenGL4.3対応のグラフィックドライバ
  • AWS Ubuntu18.04インスタンス(今回はg4dn.4xlargeを使用)

何が起こったのか

AWSのUbuntuインスタンスでUE4を使おうとして、最新バージョンのUE4(確か4.25)をgithubからダウンロード
Linuxでの起動手順通りにコマンドを実行していったところ、UE4Editorの起動部分でエラーがでる

v.png
Vulkan Driverエラー? 初めて見た...

Vulkan DriverでなくOpenGLで起動させる方法があると知り、OpenGLで起動させようとしてみた
o.png
さっきVulkanは駄目って言ったのに今度はVulkanを使え...?

こんな感じで訳が分からなくなっていました

何が問題だったのか

  • UE4のビジュアライズ方式は4.23を境に変更になっていることを知らなかった

OpenGL ES2 は 4.23 で非推奨となり、4.24 で除去されました。

UnrealEngine公式ドキュメント

  • Ubuntuインスタンスではグラフィックドライバは手動で設定が必要なことを知らなかった

やったこと

  • OpenGL対応しているUE4をインストール
  • インスタンスのグラフィックドライバを変更

UE4をダウンロード

4.23で非推奨とのことだったので、バージョン4.22.1のUE4をダウンロードします。
通常のLinuxへのインストール手順を進めたあとグラフィックドライバの指定を変更するために、
TargeteRHIs=VULKAN_SMSの行をコメントアウトし、TargeteRHIs=GLSL_430のコメントを外して画像のようにします。

comment.png

OpenGLのバージョン確認

下記のコマンドを実行してOpenGLのバージョンを確認します。
OpenGL version stringの後の数字が4.3以下の場合は、グラフィックドライバを変更します。

$ glxinfo | grep "OpenGL version"
OpenGL version string: 1.4 (2.1 Mesa 7.7.1)

グラフィックドライバのインストール

今回はNVIDIAのドライバをインストールしました。

1.aptのリポジトリにNVIDIAのドライバのリポジトリを追加 
sudo add-apt-repository ppa:graphics-drivers/ppa

2.アップデート
sudo apt update

3.推奨ドライバを確認
ubuntu-drivers devices

4.ドライバのインストール
今回は推奨バージョンが455でした
sudo apt install nvidia-driver-455

5.再起動します
sudo reboot

6.指定されているグラフィックドライバを確認
nvidiaが返って来たら成功

$ prime-select query
nvidia

7../UE4Editorコマンドを実行すると、UE4Editorを起動することができる
スクリーンショット 2020-12-16 21.35.43.png

まとめ

  • UE4はバージョンによってVulkanとOpenGLのどちらを使用するのか違うのでよく確認しよう
  • OpenGLのバージョンが4.3以上になるグラフィックドライバを入れよう

参考文献

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

DynamoDB StreamsとKinesis Data Firehoseを使ったサーバーレスリアルタイムETL

何を書いた記事か

過去にDynamoDB Streams + Kinesis Data Firehose + Lambdaを用いたリアルタイムETLを検証した際のメモをこちらに転載します。

特にKinesis Data Firehoseの裏で動かすLambdaの実装に癖があったので、誰かの参考になれば幸いです。

前提

Webサービスなど展開していて、Database層にDynamoDBを採択している場合、そのデータを分析するための分析基盤構築手法として、Glueを用いたETLが一般的な選択肢になりうるのかなと思います。
最近DynamoDBのTableをS3にExportできる機能もGAになったので、フルダンプ+日時バッチのデータ分析としてはそのような手法も使えるかもしれません。

しかし、DynamoDB上にあるデータをなるべくリアルタイムに分析基盤に連携したい、最低限のETL処理も挟みたい、といったことを考えると、GlueやS3 Exportでは更新頻度やコストの面で優位に働かないケースがほとんどだと思います。

そこで有力な選択肢として上げられるのがDynamoDB StreamsとKinesis Data Firehoseを組み合わせる手法です。

実際にどのような構成で検証したのか、実装上ハマったポイントはどこか、など共有できればと思います。

構成

構成概要

作成した基盤の構成概要は下記です。

image.png

各リソースの役割

各リソースの役割を下記に記載します。

  • DynamoDB Streams
    • 後続のKinesis連携用Lambdaをキック
  • Lambda Function ①
    • DDB Streamsから同期実行
    • 受け取ったイベントをKinesisにそのまま流すのみ
  • Kinesis Data Firehose
    • Streaming Dataを受信したら一定時間Bufferingし、Transform用のLambdaを実行
    • Transform用Lambdaから返却されたデータをObjectとしてS3に保管
    • 保管先のPrefixとしてHive形式のPartitioningを指定
  • Lambda Function ②
    • Kinesis Firehoseが受け取ったデータを変換する
    • ETL処理を担当
    • Firehoseに戻す際は、csv形式のレコードをbase64にEncodeし、Statusなど所定のparamsを付与する必要がある(後述)
  • S3
    • KinesisによってBuffering・TransformされたStreaming Dataを格納
    • 格納PrefixはHive形式でPartitioning
  • Glue (Crawler)
    • S3に置かれたObjectに対してDataCatalogを生成
  • Athena
    • Glue Crawlerが生成したDataCatalogを用いて、S3上のデータに対してクエリを実行

特記ポイント

DynamoDB Streams → Lambdaの連携について

DynamoDB StreamsはTable単位で有効にすることができ、Subscriberとして選択できるリソースはLambdaのみです。
また、仕様上Lambdaは同期実行されることとなります。

そこで気になったのでStreamsの後ろで実行されるLambdaをわざと失敗する状態にして、
- Lambdaが失敗したとき、DynamoDB自体のINSERT/UPDATE/DELETEに影響はないか
- Lambda自体のリトライや異常終了時にどのような挙動になるか
を調べてみました。

前者の、DynamoDB自体の更新処理に関しては、Streamsの先のLambdaで失敗しても、問題なく完了することが確認できました(そうじゃないと怖くて使えない)

一方、後者のLambdaのリトライについて、確実にRaiseするLambdaを後続で動かして1レコード更新した際、永遠に際実行処理が繰り返されているように見えました。

image.png

いつまでもLambdaが終了しないので調べてみたところ、同期実行のLambdaはデフォルトで下記の設定になっていることがわかりました。

  • リトライ回数は10000回
  • 並列度は1

これでは、なんらかのエラーでStreams+Lambdaが失敗したとき、その失敗処理が10000回リトライして終了するまで、後続の更新処理がStreamingされないこととなり、運用上とても実用に耐えうる構成にはなりません。

そこで、下記の修正を加えることとしました。

  • EventSourceMappingからリトライ回数を調整(今回は3回)
  • 同時実行数を2以上の値に設定

この辺りは本番のトラフィック・データ更新頻度に合わせてチューニングする必要があります。
並列処理に関しては一点懸念があって、並列度を2以上にしたからと言って、どこかのシャードが詰まった時に、その後ろのStreaming Dataは確実に空いてる方のシャードに割り当てられるわけではないということです。
これは、並列実行時にどのシャードにタスクを配置するかは、Hash値によってランダムに決められるので、滞留しているシャードにタスクが配置されたら、結局後続のタスクは実行されず、対流を繰り返すこととなる仕様なので避けられず、好ましい実装としてはリトライ回数を小さく設定し、リトライが全て失敗した時に通知を出すようにするのがベストなのかなと考えました(意見が聞きたいです)。

Kinesis Data Firehoseの裏で実行するETL用Lambdaについて

Kinesis Data Firehoseの裏で呼び出すETL用のLambdaは、Kinesisの仕様をふんだんに取り入れた作りにする必要があります。
Lambda自体はBluePrint(Node.jsかPython)が提供されているので、それを参考に構成するのがいいと思いますが、一応気をつけた方がいいだろうと思ったポイントを記載しておきます。

  • Lambda自体のTimeoutは1min以上にする必要がある
  • Kinesis Data Firehoseから連携されたPayload Sizeが6MBを超えるときは、PayloadをFirehoseに戻す必要がある
    • さらに、その戻すRecord数が500を超える場合は分割する必要がある
  • Transform処理が完了したデータをFirehoseに戻す際、所定フォーマットにしたがった形式で、Dataの実態はbase64でencordする必要がある

Lambdaを構築する場合はこの辺り参考にしてみてください。
https://docs.aws.amazon.com/ja_jp/firehose/latest/dev/data-transformation.html

サンプルコードはこんな感じです。
試しにtransform関数の中で必要なレコードを抽出してフォーマット変換し、csvに直して返すような処理にしてみました。
ここに実際に必要なETL処理の本体を書くことになります。

Transform Lambda
import json
import boto3
import base64
from datetime import datetime

PAYLOAD_MAX_SIZE = 6000000
MAX_RECORD_COUNT = 500

def transform(data):
    """
    データ変換関数
    """
    data['NewColumn'] = 'New Value'
    # Change Schema
    id = data.get('id').get('S')
    user_id = data.get('user_id').get('S')
    store_id = data.get('store_id').get('S')
    created = data.get('created').get('S')
    created = datetime.strptime(created, '%Y-%m-%dT%H:%M:%S').strftime('%Y-%m-%d %H:%M:%S')
    updated_at = data.get('updated_at').get('S')
    updated_at = datetime.strptime(updated_at, '%Y-%m-%dT%H:%M:%S').strftime('%Y-%m-%d %H:%M:%S')

    return_data = f'{id},{user_id},{store_id},{created},{updated_at}'
    print(return_data)

    return return_data


def proceed_records(records):
    """
    transform each data and yield each record
    """
    for record in records:
        record_id = record.get('recordId')
        data = json.loads(base64.b64decode(record.get('data')))
        # print("Raw Data : " + str(data))
        try:
            transformed_data = transform(data)
            result = 'Ok'
        except Exception as e:
            print(e)
            transformed_data = data
            result = 'ProcessingFailed'
        print("New Data : " + transformed_data)

        # proceeded_data = json.dumps(transformed_data) + '\n'
        proceeded_data = transformed_data + '\n'

        return_record = {
            "recordId": record_id,
            "result": result,
            "data": base64.b64encode(proceeded_data.encode('utf-8'))
        }

        yield return_record


def put_records_to_firehose(streamName, records, client):
    print('Trying to return record to firehose')
    print(f'Item count: {len(records)}')
    print(f'Record: {str(records)}')
    try:
        response = client.put_record_batch(DeliveryStreamName=streamName, Records=records)
    except Exception as e:
        # failedRecords = records
        errMsg = str(e)
        print(errMsg)


def lambda_handler(event, context):
    invocation_id = event.get('invocationId')
    event_records = event.get('records')
    # Transform Data
    records = list(proceed_records(event_records))

    # Check Data
    projected_size = 0 # Responseサイズが6MBを超えない様制御
    data_by_record_id = {rec['recordId']: _create_reingestion_record(rec) for rec in event['records']}
    total_records_to_be_reingested = 0
    records_to_reingest = []
    put_record_batches = []
    for idx, rec in enumerate(records):
        if rec['result'] != 'Ok':
            continue
        projected_size += len(rec['data']) + len(rec['recordId'])
        if projected_size > PAYLOAD_MAX_SIZE:
            """
            Lambda 同期呼び出しモードには、リクエストとレスポンスの両方について、
            ペイロードサイズに 6 MB の制限があります。
            https://docs.aws.amazon.com/ja_jp/firehose/latest/dev/data-transformation.html
            """
            print(f"Payload size has been exceeded over {PAYLOAD_MAX_SIZE/1000/1000}MB")
            total_records_to_be_reingested += 1
            records_to_reingest.append(
                _get_reingestion_record(data_by_record_id[rec['recordId']])
            )
            records[idx]['result'] = 'Dropped'
            del(records[idx]['data'])

        if len(records_to_reingest) == MAX_RECORD_COUNT:
            """
            Each PutRecordBatch request supports up to 500 records.
            https://docs.aws.amazon.com/firehose/latest/APIReference/API_PutRecordBatch.html
            """
            print(f'Records count has been exceeded over {MAX_RECORD_COUNT}')
            put_record_batches.append(records_to_reingest)
            records_to_reingest = []

    if len(records_to_reingest) > 0:
        # add the last batch
        put_record_batches.append(records_to_reingest)

    # iterate and call putRecordBatch for each group
    records_reingested_already = 0
    stream_arn = event['deliveryStreamArn']
    region = stream_arn.split(':')[3]
    stream_name = stream_arn.split('/')[1]
    if len(put_record_batches) > 0:
        client = boto3.client('firehose', region_name=region)
        for record_batch in put_record_batches:
            put_records_to_firehose(stream_name, record_batch, client)
            records_reingested_already += len(record_batch)
            print(f'Reingested {records_reingested_already}/{total_records_to_be_reingested} records out of {len(event["records"])}')
    else:
        print('No records to be reingested')


    # Return records to Firehose
    return_records = {
        'records': records
    }
    print(str(return_records))
    return return_records


# Transform method for temporary data
def _create_reingestion_record(original_record):
    return {'data': base64.b64decode(original_record['data'])}

def _get_reingestion_record(re_ingestion_record):
    return {'Data': re_ingestion_record['data']}

このLambdaに処理されたデータは、Kinesis Data Firehoseにバッファリングされたのち、所定の形式でPartitioningされたS3に出力されます。
例えば year=2020/month=12/day=16 みたいなキーに出力するようにFirehose側で設定して、そのキーの上位ディレクトリに対してGlue Crawlerを実行してDataCatalogを作成・更新するように構成すれば、ほぼリアルタイムでDynamoDBに入った更新情報がS3に連携され、そこに対してAthenaなどからクエリを実行できるようになります。

最後に

DynamoDBの更新データをリアルタイム性高くS3に連携し、クエリが実行できる状態にするためのパイプラインの一例について、構成と気をつけポイントを記載しました。

最近はAWSのデータパイプライン周りのUpdateが盛んなので、もしかしたらManaged AirFlowやGlue Elastic Viewsを用いてもっと簡単に構成することができるようになっているかもしれません。

その辺りは未検証なのでなんとも言えないのですが、上記の構成で検討されている誰かが同じところでハマらないよう、参考になれば幸いです。

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

Lambda+EFSで自然言語処理ライブラリ(GiNZA)使ってみる

背景

アドベントカレンダー用記事を書いていて、サイズが大きい自然言語処理ライブラリをLambdaで使う部分で技術的障壁が出てきている。そんな中、EFSにセットアップしたPythonライブラリをLambdaにimportする方法という記事を見つける。こちらの技術で要件が満たせそうなので試してみる。

関係する拙記事

背景で述べた技術的障壁を乗り越えるべく各種技術を検証した時の記事。
LambdaLayer用zipをCodeBuildでお手軽に作ってみる。
LambdaでDockerコンテナイメージ使えるってマジですか?(Python3でやってみる)

GiNZA とは

形態素解析を始めとして各種自然言語処理が出来るpythonライブラリ。spaCyの機能をラップしてる(はず)なのでその機能は使える。形態素解析エンジンにSudachiを使用したりもしている。

前提

リソース群は基本CloudFormationで作成。AWSコンソールからCloudFormationで、「スタックの作成」でCloudFormationのTemplateを読み込む形。すいませんが、CloudFormationの適用方法などは把握している方前提になります。

KeyPairの準備(無い場合)

後ほどのCloudFormationのパラメーター指定で必要になるので、AWSコンソールから作成しておく。もちろん、.sshフォルダへの配置など、sshログインの為の準備はしておく。(SSMでやれという話もあるが・・・)

VPCとかSubnetの準備(無い場合)

公式ページ AWS CloudFormation VPC テンプレート に記載のCloudFormationテンプレートを修正し、AWSコンソールから適用。
修正内容は以下の通り

  • 料金節約の為にPrivateSubnetとかNATを削除
  • 別のCloudFormationで使う値をExport
  • 実際にはリソース名など変更しています

修正後のVPC+SubnetのCloudFormation
# It's based on the following sample.
# https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/cloudformation-vpc-template.html
Description:  This template deploys a VPC, with a pair of public and private subnets spread
  across two Availability Zones. It deploys an internet gateway, with a default
  route on the public subnets. It deploys a pair of NAT gateways (one in each AZ),
  and default routes for them in the private subnets.

Parameters:
  EnvironmentName:
    Description: An environment name that is prefixed to resource names
    Type: String

  VpcCIDR:
    Description: Please enter the IP range (CIDR notation) for this VPC
    Type: String
    Default: 10.192.0.0/16

  PublicSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone
    Type: String
    Default: 10.192.10.0/24

  PublicSubnet2CIDR:
    Description: Please enter the IP range (CIDR notation) for the public subnet in the second Availability Zone
    Type: String
    Default: 10.192.11.0/24

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Ref EnvironmentName

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Ref EnvironmentName

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      CidrBlock: !Ref PublicSubnet1CIDR
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Subnet (AZ1)

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 1, !GetAZs  '' ]
      CidrBlock: !Ref PublicSubnet2CIDR
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Subnet (AZ2)

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Routes

  DefaultPublicRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet1

  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet2

  NoIngressSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: "no-ingress-sg"
      GroupDescription: "Security group with no ingress rule"
      VpcId: !Ref VPC

Outputs:
  VPC:
    Description: A reference to the created VPC
    Value: !Ref VPC
    Export:
      Name: "VPC"

  PublicSubnets:
    Description: A list of the public subnets
    Value: !Join [ ",", [ !Ref PublicSubnet1, !Ref PublicSubnet2 ]]
    Export:
      Name: "PublicSubnets"

  PublicSubnet1:
    Description: A reference to the public subnet in the 1st Availability Zone
    Value: !Ref PublicSubnet1
    Export:
      Name: "PublicSubnet1"

  PublicSubnet2:
    Description: A reference to the public subnet in the 2nd Availability Zone
    Value: !Ref PublicSubnet2
    Export:
      Name: "PublicSubnet2"

  NoIngressSecurityGroup:
    Description: Security group with no ingress rule
    Value: !Ref NoIngressSecurityGroup

EFS+EC2(AutoScaling)の準備

公式ページ Amazon Elastic File System サンプルテンプレート に記載のCloudFormationを修正し、AWSコンソールから適用。VPCなどを既存の物を使う場合、適宜修正お願いします。

修正内容は以下の通り

  • インスタンスタイプなど要らない部分削除
  • AMIのImageIDは直接指定する形に(ami-00f045aed21a55240:Amazon Linux 2 AMI 2.0.20201126.0 x86_64 HVM gp2を使用)
  • MountTargetを2つ(AZ分)に変更
  • 別のCloudFormationで使うMountTargetなどをExportして参照可能に
  • AccessPointのpathなど修正
  • 実際にはリソース名など変更しています

修正後のEFS+EC2(AutoScaling)CloudFormation
# https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/quickref-efs.html
AWSTemplateFormatVersion: '2010-09-09'
Description: This template creates an Amazon EFS file system and mount target and
  associates it with Amazon EC2 instances in an Auto Scaling group. **WARNING** This
  template creates Amazon EC2 instances and related resources. You will be billed
  for the AWS resources used if you create a stack from this template.
Parameters:
  InstanceType:
    Description: WebServer EC2 instance type
    Type: String
    Default: t3.small
    AllowedValues:
      - t3.nano
      - t3.micro
      - t3.small
      - t3.medium
      - t3.large
    ConstraintDescription: must be a valid EC2 instance type.
  AMIImageId:
    Type: String
    # Amazon Linux 2 AMI (HVM), SSD Volume Type
    Default: ami-00f045aed21a55240
  KeyName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: Name of an existing EC2 key pair to enable SSH access to the ECS
      instances
  AsgMaxSize:
    Type: Number
    Description: Maximum size and initial desired capacity of Auto Scaling Group
    Default: '1'
  SSHLocation:
    Description: The IP address range that can be used to connect to the EC2 instances
      by using SSH
    Type: String
    MinLength: '9'
    MaxLength: '18'
    Default: 221.249.116.206/32
    AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
    ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.
  VolumeName:
    Description: The name to be used for the EFS volume
    Type: String
    MinLength: '1'
    Default: efsvolume
  MountPoint:
    Description: The Linux mount point for the EFS volume
    Type: String
    MinLength: '1'
    Default: efsmountpoint
Mappings:
  AWSInstanceType2Arch:
    t3.nano:
      Arch: HVM64
    t3.micro:
      Arch: HVM64
    t3.small:
      Arch: HVM64
    t3.medium:
      Arch: HVM64
    t3.large:
      Arch: HVM64
  AWSRegionArch2AMI:
    ap-northeast-1:
      HVM64: ami-00f045aed21a55240
Resources:
  CloudWatchPutMetricsRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - ec2.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: "/"
  CloudWatchPutMetricsRolePolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: CloudWatch_PutMetricData
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Sid: CloudWatchPutMetricData
          Effect: Allow
          Action:
          - cloudwatch:PutMetricData
          Resource:
          - "*"
      Roles:
      - Ref: CloudWatchPutMetricsRole
  CloudWatchPutMetricsInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: "/"
      Roles:
      - Ref: CloudWatchPutMetricsRole
  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId:
        Fn::ImportValue: VPC
      GroupDescription: Enable SSH access via port 22
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: '22'
        ToPort: '22'
        CidrIp:
          Ref: SSHLocation
  MountTargetSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId:
        Fn::ImportValue: VPC
      GroupDescription: Security group for mount target
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: '2049'
        ToPort: '2049'
        CidrIp: 0.0.0.0/0
  FileSystem:
    Type: AWS::EFS::FileSystem
    Properties:
      PerformanceMode: generalPurpose
      FileSystemTags:
      - Key: Name
        Value:
          Ref: VolumeName
  MountTarget1:
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId:
        Ref: FileSystem
      SubnetId:
        Fn::ImportValue: PublicSubnet1
      SecurityGroups:
      - Ref: InstanceSecurityGroup
      - Ref: MountTargetSecurityGroup
  MountTarget2:
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId:
        Ref: FileSystem
      SubnetId:
        Fn::ImportValue: PublicSubnet2
      SecurityGroups:
      - Ref: InstanceSecurityGroup
      - Ref: MountTargetSecurityGroup
  EFSAccessPoint:
    Type: 'AWS::EFS::AccessPoint'
    Properties:
      FileSystemId: !Ref FileSystem
      RootDirectory:
        Path: "/"
  LaunchConfiguration:
    Type: AWS::AutoScaling::LaunchConfiguration
    Metadata:
      AWS::CloudFormation::Init:
        configSets:
          MountConfig:
          - setup
          - mount
        setup:
          packages:
            yum:
              nfs-utils: []
          files:
            "/home/ec2-user/post_nfsstat":
              content: !Sub |
                #!/bin/bash

                INPUT="$(cat)"
                CW_JSON_OPEN='{ "Namespace": "EFS", "MetricData": [ '
                CW_JSON_CLOSE=' ] }'
                CW_JSON_METRIC=''
                METRIC_COUNTER=0

                for COL in 1 2 3 4 5 6; do

                 COUNTER=0
                 METRIC_FIELD=$COL
                 DATA_FIELD=$(($COL+($COL-1)))

                 while read line; do
                   if [[ COUNTER -gt 0 ]]; then

                     LINE=`echo $line | tr -s ' ' `
                     AWS_COMMAND="aws cloudwatch put-metric-data --region ${AWS::Region}"
                     MOD=$(( $COUNTER % 2))

                     if [ $MOD -eq 1 ]; then
                       METRIC_NAME=`echo $LINE | cut -d ' ' -f $METRIC_FIELD`
                     else
                       METRIC_VALUE=`echo $LINE | cut -d ' ' -f $DATA_FIELD`
                     fi

                     if [[ -n "$METRIC_NAME" && -n "$METRIC_VALUE" ]]; then
                       INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
                       CW_JSON_METRIC="$CW_JSON_METRIC { \"MetricName\": \"$METRIC_NAME\", \"Dimensions\": [{\"Name\": \"InstanceId\", \"Value\": \"$INSTANCE_ID\"} ], \"Value\": $METRIC_VALUE },"
                       unset METRIC_NAME
                       unset METRIC_VALUE

                       METRIC_COUNTER=$((METRIC_COUNTER+1))
                       if [ $METRIC_COUNTER -eq 20 ]; then
                         # 20 is max metric collection size, so we have to submit here
                         aws cloudwatch put-metric-data --region ${AWS::Region} --cli-input-json "`echo $CW_JSON_OPEN ${!CW_JSON_METRIC%?} $CW_JSON_CLOSE`"

                         # reset
                         METRIC_COUNTER=0
                         CW_JSON_METRIC=''
                       fi
                     fi
                     COUNTER=$((COUNTER+1))
                   fi

                   if [[ "$line" == "Client nfs v4:" ]]; then
                     # the next line is the good stuff
                     COUNTER=$((COUNTER+1))
                   fi
                 done <<< "$INPUT"
                done

                # submit whatever is left
                aws cloudwatch put-metric-data --region ${AWS::Region} --cli-input-json "`echo $CW_JSON_OPEN ${!CW_JSON_METRIC%?} $CW_JSON_CLOSE`"
              mode: '000755'
              owner: ec2-user
              group: ec2-user
            "/home/ec2-user/crontab":
              content: "* * * * * /usr/sbin/nfsstat | /home/ec2-user/post_nfsstat\n"
              owner: ec2-user
              group: ec2-user
          commands:
            01_createdir:
              command: !Sub "mkdir /${MountPoint}"
        mount:
          commands:
            01_mount:
              command: !Sub >
                mount -t nfs4 -o nfsvers=4.1 ${FileSystem}.efs.${AWS::Region}.amazonaws.com:/ /${MountPoint}
            02_permissions:
              command: !Sub "chown ec2-user:ec2-user /${MountPoint}"
    Properties:
      AssociatePublicIpAddress: true
      ImageId:
        Ref: AMIImageId
      InstanceType:
        Ref: InstanceType
      KeyName:
        Ref: KeyName
      SecurityGroups:
      - Ref: InstanceSecurityGroup
      IamInstanceProfile:
        Ref: CloudWatchPutMetricsInstanceProfile
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash -xe
          yum install -y aws-cfn-bootstrap
          /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource LaunchConfiguration --configsets MountConfig --region ${AWS::Region}
          crontab /home/ec2-user/crontab
          /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource AutoScalingGroup --region ${AWS::Region}
  AutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    DependsOn:
    - MountTarget1
    - MountTarget2
    CreationPolicy:
      ResourceSignal:
        Timeout: PT15M
        Count:
          Ref: AsgMaxSize
    Properties:
      VPCZoneIdentifier:
        - Fn::ImportValue: PublicSubnet1
        - Fn::ImportValue: PublicSubnet2
      LaunchConfigurationName:
        Ref: LaunchConfiguration
      MinSize: '1'
      MaxSize:
        Ref: AsgMaxSize
      DesiredCapacity:
        Ref: AsgMaxSize
      Tags:
      - Key: Name
        Value: EFS FileSystem Mounted Instance
        PropagateAtLaunch: 'true'
Outputs:
  MountTargetID1:
    Description: Mount target ID
    Value:
      Ref: MountTarget1
  MountTargetID2:
    Description: Mount target ID
    Value:
      Ref: MountTarget2
  LambdaEFSArn:
    Description: File system Arn
    Value: !GetAtt FileSystem.Arn
    Export:
      Name: !Sub "LambdaEFSArn"
  LambdaEFSAccessPointArn:
    Description: File system AccessPointArn
    Value: !GetAtt EFSAccessPoint.Arn
    Export:
      Name: !Sub "LambdaEFSAccessPointArn"
  InstanceSecurityGroup:
    Description: A reference to the InstanceSecurityGroup
    Value: !Ref InstanceSecurityGroup
    Export:
      Name: "InstanceSecurityGroup"
  MountTargetSecurityGroup:
    Description: A reference to the MountTargetSecurityGroup
    Value: !Ref MountTargetSecurityGroup
    Export:
      Name: "MountTargetSecurityGroup"

EC2へログインしてモジュールインストール

EFSにセットアップしたPythonライブラリをLambdaにimportする方法をトレースさせて頂く。

ログイン

AWSコンソールからPublicIPを調べてssh。

ssh -i ~/.ssh/hogehoge-keypair.pem ec2-user@xx.yyy.xxx.zzz

マウント確認

コマンド実行
df -h
結果表示
Filesystem                                      Size  Used Avail Use% Mounted on
devtmpfs                                        469M     0  469M   0% /dev
tmpfs                                           479M     0  479M   0% /dev/shm
tmpfs                                           479M  388K  479M   1% /run
tmpfs                                           479M     0  479M   0% /sys/fs/cgroup
/dev/nvme0n1p1                                  8.0G  1.6G  6.5G  20% /
xx-yyyyyyyz.efs.ap-northeast-1.amazonaws.com:/  8.0E     0  8.0E   0% /efsmountpoint
tmpfs                                            96M     0   96M   0% /run/user/1000

/efsmountpoint にEFSがマウントされているのを確認。

Pythonなどのモジュールインストール

su にならないとginzaが上手くインストールできなかったのでその部分修正

sudo su -
cd /efsmountpoint
yum update
yum -y install gcc openssl-devel bzip2-devel libffi-devel
wget https://www.python.org/ftp/python/3.8.6/Python-3.8.6.tgz
tar xzf Python-3.8.6.tgz

cd Python-3.8.6
./configure --enable-optimizations
make altinstall

# check
python3.8 --version
pip3.8 --version

GiNZAインストール

pip3.8 install --upgrade --target lambda/ ginza==4.0.5

# 念のためフル権限にしておく
chmod 777 -R lambda/

※ここまででEC2は必要無くなります。AWSコンソールからEC2 => AutoScalingグループ => 対象のAutoScalingグループ選択 => グループの詳細 の「編集」で 「希望する容量」「最小キャパシティ」「最大キャパシティ」を全て0にしてインスタンスを終了。でないと不必要なお金がかかってしまうので注意!!!!

テスト用Lambdaを登録(メイン部分)

こちらのCloudFormationをAWSコンソールから適用。重要なのはインラインで記載されてるソースの以下部分。あと、FileSystemConfigs プロパティの設定。EFSを使うので、VPCに属するLambdaにしています。

ポイント部分
sys.path.append("/mnt/efs0/lambda")
EFSマウント指定部分
      FileSystemConfigs:
      - Arn:
          Fn::ImportValue: LambdaEFSAccessPointArn
        LocalMountPath: "/mnt/efs0"

テスト用LambdaのCloudFormation(Policy+Lambda)
AWSTemplateFormatVersion: '2010-09-09'
Description: Lambda test with EFS
Resources:
  LambdaRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: "LambdaRole"
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: "/"
      Policies:
      - PolicyName: "LambdaPolicy"
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - logs:CreateLogGroup
                - logs:CreateLogStream
                - logs:PutLogEvents
              Resource: "*"
            - Effect: Allow
              Action:
                - cloudwatch:GetMetricStatistics
              Resource: "*"
            - Effect: Allow
              Action:
                - dynamodb:GetRecords
                - dynamodb:GetItem
                - dynamodb:BatchGetItem
                - dynamodb:BatchWriteItem
                - dynamodb:DeleteItem
                - dynamodb:Query
                - dynamodb:Scan
                - dynamodb:PutItem
                - dynamodb:UpdateItem
              Resource: "*"

            - Effect: Allow
              Action:
                - ec2:CreateNetworkInterface
                - ec2:DescribeNetworkInterfaces
                - ec2:DeleteNetworkInterface
                - ec2:DescribeSecurityGroups
                - ec2:DescribeSubnets
                - ec2:DescribeVpcs
              Resource: "*"
            - Effect: Allow
              Action:
                - logs:CreateLogGroup
                - logs:CreateLogStream
                - logs:PutLogEvents
              Resource: "*"
            - Effect: Allow
              Action:
                - elasticfilesystem:ClientMount
                - elasticfilesystem:ClientWrite
                - elasticfilesystem:DescribeMountTargets
              Resource: "*"

  LambdaEFSTest:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: efstestlambda
      Handler: index.handler
      Runtime: python3.8
      Code:
        ZipFile: |
          import sys
          sys.path.append("/mnt/efs0/lambda")
          import json
          import spacy
          import logging
          from ginza import *

          logger = logging.getLogger()

          def handler(event, context):
              logger.info(context)
              target_text = event['text']
              nlp = spacy.load('ja_ginza')

              doc = nlp(target_text)
              morpheme_list = []
              for sent_idx, sent in enumerate(doc.sents):
                  for token_idx, tk in enumerate(sent):
                      wk_morpheme = {}
                      wk_morpheme['text'] = tk.text
                      wk_morpheme['dep'] = tk.dep_
                      wk_morpheme['pos'] = tk.pos_
                      wk_morpheme['tag'] = tk.tag_
                      morpheme_list.append(wk_morpheme)
              return morpheme_list
      FileSystemConfigs:
      - Arn:
          Fn::ImportValue: LambdaEFSAccessPointArn
        LocalMountPath: "/mnt/efs0"
      Description: Lambda test with EFS.
      MemorySize: 2048
      Timeout: 15
      Role: !GetAtt LambdaRole.Arn
      VpcConfig:
        SecurityGroupIds:
          - Fn::ImportValue: InstanceSecurityGroup
          - Fn::ImportValue: MountTargetSecurityGroup
        SubnetIds:
          - Fn::ImportValue: PublicSubnet1
          - Fn::ImportValue: PublicSubnet2

テストする

  • 「テスト」ボタンを押す
  • イベント名は適当に
  • {"text":"テストしてみる"} をテスト用Bodyに指定
  • 「作成」を押す
  • 元の画面に戻る。テストが作成されているのでその状態で「テスト」ボタンを押す。

image.png

成功!(2回目以降の実行なので622msになってます。1回目は4秒以上かかりました)

終わりに

いくつかの検討を経て、ようやくサーバーレスで自然言語処理が出来そうです(EFSはストレージなので許容します)。
LambdaコンテナもEFSとのマウントも今年の機能っぽいです。去年検討していたら諦めていた事になります。AWSの機能追加速度には目を見張るものがあります。すなわち日々キャッチアップが必要という事になる訳で。大変ですw

参考にさせて頂いた良記事

EFSにセットアップしたPythonライブラリをLambdaにimportする方法

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

AWS S3のデプロイ自動化 ~npm-scripts~

S3でホスティングしている、Webサイトのデプロイ手順の(ワンステップ)自動化にあたり、
npm-scriptsと、shell-scriptsでいい感じに出来ないかなと思い、
作成したpackage.jsonの内容を記します

前提

AWS CLI を使うでプロファイルをセッティング

package.jsonの用意

npmコマンドを使ってpackage.jsonを作成

$ npm init

以降のpackage.jsonの記述は見やすいように"scripts"の項目だけを記述します

generateコマンドの作成

ここでは仮にdistディレクトリを作成するコマンドを用意

./package.json
{
  "scripts": {
    "generate": "rm -rf dist && mkdir dist && touch dist/index.html"
  }
}

confirmコマンドの作成

デプロイの際にどこの環境の処理を実行するか改めて確認する

confirm.shの作成

touchコマンドでconfirm.shファイルを作成

$ touch confirm.sh

ファイルに以下の内容を書き込む

./confirm.sh
#!/bin/bash

ENV_NAME=$1

function ConfirmExecution() {
    echo "「${ENV_NAME}」環境のデプロイを実行しますか?"
    echo "実行する場合は「${ENV_NAME}」と入力してください"
    read input

    if [ -z $input ]; then
        ConfirmExecution
    elif [ $input = ${ENV_NAME} ]; then
        echo "スクリプトを実行します。"
    else
        echo "スクリプトを終了します。"
        exit 1
    fi
}

ConfirmExecution

package.jsonconfirm:stgコマンドを以下のように追記する
引数として実行する環境名STGを渡すように記述します

./package.json
{
  "scripts": {
    "generate": "rm -rf dist && mkdir dist && touch dist/index.html",
    "confirm:stg": "sh confirm.sh STG"
  }
}

uploadコマンドの作成

package.jsonupload:stgコマンドを以下のように追記する

./package.json
{
  "scripts": {
    "generate": "rm -rf dist && mkdir dist && touch dist/index.html",
    "confirm:stg": "sh confirm.sh STG",
    "upload:stg": "aws s3 sync --delete dist s3://stg-xxxxxx --profile testUser"
  }
}

AWS CLIのs3 syncコマンドでローカルのdistディレクトリから、
s3のstg-xxxxxxバケットにファイルをシンク(同期)します

その際、指定しているオプションは、それぞれ以下の内容になります

オプション 内容
--delete 同期元にない同期先のファイルを削除する
--profile 設定ファイルに登録されているプロファイル

upload:stgコマンドの内容をまとめると
testUserの権限で、ローカルのdistディレクトリからs3のstg-xxxxxxバケットに
ファイルを同期し、無くなったものは削除する」となります

clearコマンドの作成

package.jsonclear:stgコマンドを以下のように追記する

./package.json
{
  "scripts": {
    "generate": "rm -rf dist && mkdir dist && touch dist/index.html",
    "confirm:stg": "sh confirm.sh STG",
    "upload:stg": "aws s3 sync --delete dist s3://stg-xxxxxx --profile testUser",
    "clear:stg": "aws cloudfront create-invalidation --distribution-id XXXXXXXX --paths '/*' --profile testUser"
  }
}

AWS CLIのcloudfront create-invalidationコマンドで
該当のcloudfrontのキャッシュを削除します

その際、指定しているオプションは、それぞれ以下の内容になります

オプション 内容
--distribution-id 指定するcloudfrontディストリビューションのID
--paths キャッシュクリア対象のパス(リスト)
--profile 設定ファイルに登録されているプロファイル

clear:stgコマンドの内容をまとめると
testUserの権限で、IDがXXXXXXXXのディストリビューションで
全てのパスに該当するキャッシュをクリアする」となります

deployコマンドの作成

package.jsondeploy:stgコマンドを以下のように追記する

./package.json
{
  "scripts": {
    "generate": "rm -rf dist && mkdir dist && touch dist/index.html",
    "confirm:stg": "sh confirm.sh STG",
    "upload:stg": "aws s3 sync --delete dist s3://stg-xxxxxx --profile testUser",
    "clear:stg": "aws cloudfront create-invalidation --distribution-id XXXXXXXX --paths '/*' --profile testUser",
    "deploy:stg": "run-s confirm:stg generate upload:stg clear:stg"
  }
}

ここで使うrun-sコマンドはnpmモジュールのnpm-run-allを利用する

$ npm i npm-run-all

このdeploy:stgコマンドの実行により、ここまでに書いてきた、
confirm:stggenerateupload:stgclear:stg
を直列処理で順に実行する

結果

以下のコマンドを叩くことで、

$ npm run deploy:stg

こういう対話が行われ

「STG」環境のデプロイを実行しますか?
実行する場合は「STG」と入力してください
STG
スクリプトを実行します。

対象ファイルがgenerate(生成)され、
生成されたファイルをs3にファイルシンク(同期)し、
cloudfrontのキャッシュをクリア(削除)する
という一連の流れをワンステップのコマンド入力で自動化できました

STG環境以外に本番で使う場合、プレビュー環境で使う場合は、それぞれ
confirm:liveconfirm:predeploy:livedeploy:pre
という感じでそれぞれコピー、内容調整すれば良いです

上のデプロイにプラスして、最後に作業の終了を
slackに通知するコマンドを入れても良いですね

複数のプロジェクトに参加している場合は、
プロジェクトを跨いで仕事をしているうちにアップロード場所間違いや、
削除ファイル漏れなど間違いが起きかねません
デプロイの自動化が設定出来たら、安心ですね

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

インターナル通信でAurora MySQLからCloudSQLへレプリケーションする方法

概要

本記事では、AuroraMySQLからCloudSQLへインターナル通信にてレプリケーションする方法を紹介します。GCPのネットワーク周りで結構ハマりましたので参考になればと思います。

手順

1. AWSのVPC並びにGCPのネットワークの作成

まず、AWSとGCPそれぞれでVPCが無いと始まらないのでそれらの作成を行います。なお本記事ではAWS側をCloudFormationで、GCP側をTerrafromにて記述しています。

  • AWS
cfn.yml
Resources:
  EC2VPC:
    Type: 'AWS::EC2::VPC'
    Properties:
      CidrBlock: !Ref VPCCidrBlock
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: 'default'
      Tags:
        - Key: 'Name'
          Value: !Sub ${AWS::StackName}
        - Key: 'Scope'
          Value: !Ref GlobalEnvironment
  EC2DHCPOptions:
    Type: 'AWS::EC2::DHCPOptions'
    Properties:
      DomainName: !Sub '${GlobalEnvironment}.${GlobalPrefix}.internal ${AWS::Region}.compute.internal'
      DomainNameServers:
        - 'AmazonProvidedDNS'
      Tags:
        - Key: 'Name'
          Value: !Sub ${AWS::StackName}-dhcp-options
        - Key: 'Scope'
          Value: !Ref GlobalEnvironment
  EC2VPCDHCPOptionsAssociation:
    Type: 'AWS::EC2::VPCDHCPOptionsAssociation'
    Properties:
      DhcpOptionsId: !Ref EC2DHCPOptions
      VpcId: !Ref EC2VPC
  • GCP
// VPC
resource "google_compute_network" "vpc" {
  name                    = "vpc"
  auto_create_subnetworks = false
  routing_mode            = "GLOBAL"
}

2. AWS側サブネットとAuroraの準備

続いてprimaryとなるAurora MySQLの作成を行います。レプリケーションができるように以下のRDSのパラメータグループのオプションを指定しています。

Parameters:
  binlog_format: 'ROW'
  gtid-mode: 'ON'
  enforce_gtid_consistency: 'ON'

以下がサブネットとAurora作成のためのテンプレートです。

Resources:
# Subnet
  EC2SubnetPrivateAZ1:
    Type: 'AWS::EC2::Subnet'
    Properties:
      AvailabilityZone:
        Fn::Select:
          - 0
          - Fn::GetAZs: !Ref AWS::Region
      CidrBlock: !Ref IPCidrBlockPrivateAZ1
      MapPublicIpOnLaunch: false
      Tags:
        - Key: 'Name'
          Value: !Sub ${GlobalPrefix}_${GlobalEnvironment}_private_az1
        - Key: 'Scope'
          Value: !Ref GlobalEnvironment
      VpcId: !Ref EC2VPC
  EC2RouteTablePrivateAZ1:
    Type: 'AWS::EC2::RouteTable'
    Properties:
      VpcId: !Ref EC2VPC
      Tags:
        - Key: 'Name'
          Value: !Sub ${GlobalPrefix}_${GlobalEnvironment}_route_table_private_az1
        - Key: 'Scope'
          Value: !Ref GlobalEnvironment
  EC2SubnetRouteTableAssociationPrivateAZ1:
    Type: 'AWS::EC2::SubnetRouteTableAssociation'
    Properties:
      RouteTableId: !Ref EC2RouteTablePrivateAZ1
      SubnetId: !Ref EC2SubnetPrivateAZ1
  EC2RoutePrivateAZ1NatGateway:
    Type: 'AWS::EC2::Route'
    Properties:
      DestinationCidrBlock: '0.0.0.0/0'
      NatGatewayId: !Ref EC2NatGatewayAZ1
      RouteTableId: !Ref EC2RouteTablePrivateAZ1
  ## AZ2
  EC2SubnetPrivateAZ2:
    Type: 'AWS::EC2::Subnet'
    Properties:
      AvailabilityZone:
        Fn::Select:
          - 1
          - Fn::GetAZs: !Ref AWS::Region
      CidrBlock: !Ref IPCidrBlockPrivateAZ2
      MapPublicIpOnLaunch: false
      Tags:
        - Key: 'Name'
          Value: !Sub ${GlobalPrefix}_${GlobalEnvironment}_private_az2
        - Key: 'Scope'
          Value: !Ref GlobalEnvironment
      VpcId: !Ref EC2VPC
  EC2RouteTablePrivateAZ2:
    Type: 'AWS::EC2::RouteTable'
    Properties:
      VpcId: !Ref EC2VPC
      Tags:
        - Key: 'Name'
          Value: !Sub ${GlobalPrefix}_${GlobalEnvironment}_route_table_private_az2
        - Key: 'Scope'
          Value: !Ref GlobalEnvironment
  EC2SubnetRouteTableAssociationPrivateAZ2:
    Type: 'AWS::EC2::SubnetRouteTableAssociation'
    Properties:
      RouteTableId: !Ref EC2RouteTablePrivateAZ2
      SubnetId: !Ref EC2SubnetPrivateAZ2
  EC2RoutePrivateAZ2NatGateway:
    Type: 'AWS::EC2::Route'
    Properties:
      DestinationCidrBlock: '0.0.0.0/0'
      NatGatewayId: !Ref EC2NatGatewayAZ2
      RouteTableId: !Ref EC2RouteTablePrivateAZ2

# RDS
IAMRoleRDSEnhancedMonitoring:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: 'Allow'
          Principal:
            Service: 'monitoring.rds.amazonaws.com'
          Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole'
  EC2SecurityGroupDatabase:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      GroupDescription: !Sub '${AWS::StackName}-database'
      SecurityGroupIngress:
        - SourceSecurityGroupId: !Ref MyHomeIp
          FromPort: 3306
          ToPort: 3306
          IpProtocol: 'tcp'
      Tags:
        - Key: 'Name'
          Value: !Sub '${AWS::StackName}-database'
        - Key: 'Scope'
          Value: !Ref GlobalEnvironment
      VpcId: !Ref EC2VPC
  RDSDBClusterParameterGroup:
    Type: 'AWS::RDS::DBClusterParameterGroup'
    Properties:
      Description: !Sub 'cluster parameter group for ${AWS::StackName}'
      Family: 'aurora-mysql5.7'
      Parameters:
        binlog_format: 'ROW'
        gtid-mode: 'ON'
        enforce_gtid_consistency: 'ON'
      Tags:
        - Key: 'Scope'
          Value: !Ref GlobalEnvironment
  RDSDBParameterGroup:
    Type: 'AWS::RDS::DBParameterGroup'
    Properties:
      Description: !Sub 'parameter group for ${AWS::StackName}'
      Family: 'aurora-mysql5.7'
      Tags:
        - Key: 'Scope'
          Value: !Ref GlobalEnvironment
  RDSDBSubnetGroup:
    Type: 'AWS::RDS::DBSubnetGroup'
    Properties:
      DBSubnetGroupDescription: !Sub 'db subnet group for ${AWS::StackName}'
      SubnetIds:
        - !Ref EC2SubnetPrivateAZ1
        - !Ref EC2SubnetPrivateAZ2
      Tags:
        - Key: 'Scope'
          Value: !Ref GlobalEnvironment
  RDSDBCluster:
    Type: 'AWS::RDS::DBCluster'
    DeletionPolicy: 'Snapshot'
    Properties:
      AvailabilityZones:
        - !Select [0, !GetAZs '']
        - !Select [1, !GetAZs '']
      BackupRetentionPeriod: 35
      DBClusterParameterGroupName: !Ref RDSDBClusterParameterGroup
      DBSubnetGroupName: !Ref RDSDBSubnetGroup
      Engine: 'aurora-mysql'
      EngineVersion: '5.7'
      KmsKeyId: 'alias/aws/rds'
      MasterUsername: 'root'
      MasterUserPassword: 'password'
      Port: 3306
      PreferredBackupWindow: '19:00-19:30' # JST 05:00-05:30
      PreferredMaintenanceWindow: 'Thu:20:00-Thu:20:30' # JST FRI 04:00-04:30
      StorageEncrypted: true
      Tags:
        - Key: 'Scope'
          Value: !Ref GlobalEnvironment
      VpcSecurityGroupIds:
        - !Ref EC2SecurityGroupDatabase
  RDSDBInstanceFirst:
    Type: 'AWS::RDS::DBInstance'
    DeletionPolicy: 'Delete'
    Properties:
      AllowMajorVersionUpgrade: false
      AutoMinorVersionUpgrade: false
      AvailabilityZone: !Select [0, !GetAZs '']
      CopyTagsToSnapshot: true
      DBClusterIdentifier: !Ref RDSDBCluster
      DBInstanceClass: 'db.r5.large'
      Engine: 'aurora-mysql'
      MonitoringInterval: 15
      MonitoringRoleArn: !GetAtt IAMRoleRDSEnhancedMonitoring.Arn
      PreferredMaintenanceWindow: 'Thu:20:00-Thu:20:30' # JST FRI 04:00-04:30
      PubliclyAccessible: false
      Tags:
        - Key: 'Scope'
          Value: !Ref GlobalEnvironment
      DBParameterGroupName: !Ref RDSDBParameterGroup
  RDSDBInstanceSecond:
    Type: 'AWS::RDS::DBInstance'
    DeletionPolicy: 'Delete'
    Properties:
      AllowMajorVersionUpgrade: false
      AutoMinorVersionUpgrade: false
      AvailabilityZone: !Select [1, !GetAZs '']
      CopyTagsToSnapshot: true
      DBClusterIdentifier: !Ref RDSDBCluster
      DBInstanceClass: 'db.r5.large'
      Engine: 'aurora-mysql'
      MonitoringInterval: 15
      MonitoringRoleArn: !GetAtt IAMRoleRDSEnhancedMonitoring.Arn
      PreferredMaintenanceWindow: 'Thu:20:00-Thu:20:30' # JST FRI 04:00-04:30
      PubliclyAccessible: false
      Tags:
        - Key: 'Scope'
          Value: !Ref GlobalEnvironment
      DBParameterGroupName: !Ref RDSDBParameterGroup

4. AWSとGCPをプライベート接続

AWSとGCPを専用線で結びます。AWSのDirectConnectとGCPのInterConnectを利用することでそれが可能です。今回は個人レベルではそれらを用意することは不可能なのでVPNで代用します。AWSとGCPでVPNを結ぶ方法は以下の記事をご参照ください。

https://qiita.com/katsuyan/items/94eb005c06c517c0c84a

5. GCPのgoogle-managed-services-vpcのCIDRを指定する

CloudSQLはgoogle-managed-services-vpcと言うgoogleのマネージドVPCに作成されます。そこにVPCピアリングする形で、自分たちで作ったネットワークと接続します。そのため、自分たちで作ったサブネットの中にCloudSQLを作成するといったことができません。しかし、CloudSQLのIPがどのCIDRから決まるのかがわかっていないと、AWS側のセキュリティグループの許可設定をCloudSQLが立ち上がるたびに設定する必要が出てきてしまいます。

そこでgoogle-managed-services-vpcのアドレスを設定することでgoogle-managed-services-vpcで作られるサブネットのCIDRを指定します。このアドレスを指定することで、指定したIPレンジからCloudSQLのIPが決まります。VPCのPrivate service connectionで、「google-managed-services-vpc-{プロジェクト名}」というNameでCIDRを指定することで設定が可能です。

resource "google_compute_global_address" "private_ip_alloc_google_managed_service" {
  name          = "google-managed-services-vpc-projectname"
  purpose       = "VPC_PEERING"
  address_type  = "INTERNAL"
  prefix_length = 24
  network       = google_compute_network.vpc.id
  address       = "10.165.2.0"
}

Google_Cloud_Platformのコピー.png

6. AWS側で上記で固定したIPレンジからの通信を許可する

次にAWS側でCloudSQLが作られるgoogle-managed-services-vpcのIPレンジからの通信を許可します。通信を許可するために、ルートテーブルとセキュリティグループの設定を行います。

EC2RoutePrivateDatabaseVGWGCPCloudSQLSubentAZ1:
    Type: 'AWS::EC2::Route'
    Properties:
      DestinationCidrBlock: '10.165.2.0/24'
      GatewayId: !Ref VGW
      RouteTableId: !Ref EC2RouteTablePrivateAZ1

  EC2RoutePrivateDatabaseVGWGCPCloudSQLSubentAZ2:
    Type: 'AWS::EC2::Route'
    Properties:
      DestinationCidrBlock: '10.165.2.0/24'
      GatewayId: !Ref VGW
      RouteTableId: !Ref EC2RouteTablePrivateAZ2

更新

EC2SecurityGroupDatabase:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      GroupDescription: !Sub '${AWS::StackName}-database'
      SecurityGroupIngress:
        - SourceSecurityGroupId: !Ref EC2SecurityGroupAllowSSHFromOffice
          FromPort: 3306
          ToPort: 3306
          IpProtocol: 'tcp'
        - CidrIp: !Ref IPGCPPrivateServiceDev
          FromPort: 3306
          ToPort: 3306
          IpProtocol: 'tcp'
        - CidrIp: !Ref IPGCPSubnetZozoDataPoolDev
          FromPort: 3306
          ToPort: 3306
          IpProtocol: 'tcp'
        - CidrIp: '10.165.2.0/24'
          FromPort: 3306
          ToPort: 3306
          IpProtocol: 'tcp'
      Tags:
        - Key: 'Name'
          Value: !Sub '${AWS::StackName}-database'
        - Key: 'Scope'
          Value: !Ref GlobalEnvironment
      VpcId: !Ref EC2VPC

詳しくは以下の記事をご参照ください。

https://qiita.com/shiozaki/items/2aa3c249f0399d6b779e

7. 仮のCloudSQL用の作成

「CloudSQLはgoogle-managed-services-vpcと言うgoogleのマネージドVPCに作成されます。そこにVPCピアリングする形で、自分たちで作ったネットワークと接続します。」と説明しましたが、そのVPCが作られるのは初めてCloudSQLを作成したタイミングになります。

レプリケーション用のインスタンスの作成前にCloudSQLが置かれるVPCを作成しておきたいため一度仮のCloudSQLを立ち上げて起きます。VPCが作られたあとは作ったインスタンスは不要になるため削除します。(別の方法が有りましたら教えて下さい。)

Create_MySQL_instance_-_zozo-datapool-dev_-_Google_Cloud_Platform.png

↓が作られる

Peering_connection_details_–_VPC_network_–_zozo-datapool-dev_–_Google_Cloud_Platform.png

8. CloudSQLのネットワークにAWSへのルートを伝搬する

CloudSQLからAWSのAurora MySQLへ通信するためには、CloudSQLのVPCへAWSへのルートが設定されている必要があります。そこで、作成されたVPC Peeringの設定から「Export custom routes」を有効にする必要があります。「Export custom routes」を指定することでCloudSQL側のVPCへルートが伝搬されます。また、CloudSQL側のVPC Peeringの設定で「Import custom routes」が有効になっている必要がありますが、これはデフォルトで有効になっているようでした。

9. AWSへCloudSQLへのルートを伝搬させる

今度はCloudSQLへのルートをAWSへ伝搬させます。これは、Cloud Routerの設定に伝搬させたいCIDRをCUSTOM IP RANGESに指定することで伝搬させることができます。

resource "google_compute_router" "router" {
  name    = "router"
  network = google_compute_network.vpc.id
  bgp {
    asn               = var.router_google_asn
    advertise_mode    = "CUSTOM"
    advertised_groups = ["ALL_SUBNETS"]
  }
  advertised_ip_ranges {
      range = "10.165.2.0/24"
    }
  region = "asia-northeast1"
}

Router_details_–_Hybrid_Connectivity_–_zozo-datapool-dev_–_Google_Cloud_Platform.png

9. AuroraにCNAMEを貼る

CloudSQLのレプリケーションを作成する場合の制約として、host名を60文字以内にする必要があります。ただし、Auroraの書き込みエンドポイントのhost名は大体の場合60文字を超えてしまいます。そこでAuroraの書き込みエンドポイントへCNAMEを貼って、60文字以内のFQDNを設定します。

10. CloudSQLからレプリケーションする

続いて以下の手順に沿ってレプリケーションを行います。

https://cloud.google.com/sql/docs/mysql/replication/replication-from-external

ただしTerraformがv1.1に対応していないため、ここではv1の手順も混ぜながら構築していきます。

https://cloud.google.com/sql/docs/mysql/replication/replication-from-external_v1

ソース表現インスタンスの作成

まず、ソース表現インスタンスの作成を行います。Terraformでは、host名にFQDNが指定できないためAPIを利用します。

{
  "name": "replication-bo-test",
  "region": "us-central1",
  "databaseVersion": "MYSQL_5_7",
  "onPremisesConfiguration": {
    "hostPort": "hostname:3306"
  }
}
ACCESS_TOKEN="$(gcloud auth print-access-token)"
curl --header "Authorization: Bearer ${ACCESS_TOKEN}" --header 'Content-Type: application/json' --data @./external_mysql.json -X POST https://www.googleapis.com/sql/v1beta4/projects/{project-name}/instances 

レプリケーション用ユーザの作成と初期ダンプ

続いて、Aurora MySQLにてレプリケーション用のユーザの作成します。また、ダンプファイルを作成しGCSへ保存します。やり方についてはドキュメントをご参照ください。

https://cloud.google.com/sql/docs/mysql/replication/replication-from-external

レプリケーション

最後に上記で作成したソース表現インスタンスに対してレプリケーションの作成を行います。

resource "google_sql_database_instance" "replica-instance" {
  name                 = "replica-instance" # TODO
  database_version     = "MYSQL_5_7"
  master_instance_name = "replication" # TODO
  region               = "us-central1"

  replica_configuration {
    failover_target = false
    username = "repuser"
    password = "password"
    dump_file_path = "gs://bucket/dump.sql.gz"
  }

  settings {
    tier = "db-n1-standard-1"
    database_flags {
      name = "character_set_server"
      value = "utf8mb4"
    }
    ip_configuration {
      private_network = google_compute_network.vpc.id
    }
  }
}

まとめ

以上のようにインターナル通信でAurora MySQLからCloudSQLへレプリする方法について紹介しました。GCPのネットワークは知らないとハマりどころが多いので参考になれば幸いです。

参考

以下の記事を参考にさせていただきました。ありがとうございあました!!

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

amplify pushする顔も三度まで

POLアドベントカレンダー16日目担当、3度目のゲバラです。 
15日目担当まーさん@takahashik0422記事もぜひお読みください!

ある日の出来事

私「よし!開発環境にamplify pushしよ!」

Amplify CLI「Parameters: [unauthRoleName] do not exist in the template」

私「CLIのバージョン上げるか」

Amplify CLI「一緒のエラーやで」

私「ファッ!?」

amplify pushができなくなる

amplify pushはAmplifyバックエンド環境の設定をAmplify CLIでローカルからプッシュするコマンドです。
これができないとバックエンド環境を変更することができません。マジで困るやつです。
gihub pushできないとの同じくらい困ります。いやそれ以上です。
CLIは結構バグっていることが多いのでバージョンアップするとよくなることがありますがダメだった。

amplify pushすると以下のようなエラーが。。。

Parameters: [unauthRoleName] do not exist in the template

issueを見てみると
https://github.com/aws-amplify/amplify-cli/issues/2519

以下の手順を実施すると解決するそう

  1. amplify/backend/api/(アプリ名)/buildフォルダを削除
  2. amplify api gql-compileコマンドを実行

amplify api gql-compileはapiのスキーマをコンパイルするためのコマンドです。このコマンドはamplify pushでも動いているはずなので1.のあとにpush実行しても問題ないはずです。
無事amplify pushできました。

ある日の出来事2

私「よし!開発環境にamplify pushしよ」

Amplify CLI「Parameters: [hostedUIProviderCreds] must have values

私「おいおい困ったやつだな、CLIのバージョン上げるか」

Amplify CLI「一緒のエラーやで」

私「ヒョッ!?」

amplify pushができなくなる2回目

2回目です。泣きそう。
CLIは結構バグっていることが多いのでバージョンアップするとよくなることがありますがダメだった(2回目)。

Parameters: [hostedUIProviderCreds] must have values

これまたissueを見てみると
https://github.com/aws-amplify/amplify-cli/issues/6021

  • amplify pullをしてクラウドとリモードを同期してください

とのこと。
なんか間違ったことしてしまったのかも。
無事amplify pushできました。

ある日の出来事3

私「よし!開発環境にamplify pushしよ!」

Amplify CLI「Unexpected token u in JSON at position 0

私「おいおいおいおいおいおい困ったやつだな、CLIのバージョン上げるか。。。。。。」

Amplify CLI「jsonString' argument missing or empty

私「。。。。。。」

amplify pushができなくなる3回目

泣いた。

CLIは結構バグっていることが多いのでバージョンアップするとよくなることがありますがダメだった(3回目)。

An error occurred when pushing the resources to the cloud
Unexpected token u in JSON at position 0
An error occurred during the push operation: Unexpected token u in JSON at position 0

CLIのバージョンを上げてみると

'jsonString' argument missing or empty An error occurred during the push operation: 'jsonString' argument missing or empty

issueをみると同じように困っている人がいました。

https://github.com/aws-amplify/amplify-cli/issues/6097

今はclosedされてますが、見たときはopenになっていて調査中でした。
ただCLIが修正されてもそれでは解決せず、手動で操作が必要なところが出てきました。

やるべきことは、S3バケットから#current-cloud-backend.zipファイルをダウンロードします。このフォルダに#current-cloud-backend/api//buildにあるcloudformation-template.jsonファイルを追加します。それを再度ZipしてS3バケットにアップロードします。

その後、pushが動作するようになります。お役に立てたかどうか教えてください。
https://github.com/aws-amplify/amplify-cli/issues/6097#issuecomment-741896687

ダウンロードしてファイル追加して再アップロードか。。。。仕方ない。。。。
これでなんとかamplify push成功するかに見えましたがまたエラー。。。。

Parameters: [unauthRoleName] do not exist in the template

1回目と一緒のエラーですが同じ対応でも直りませんでした。
1回目のissueを追ってみると、どうやらamplify/backend/api/(アプリ名)/paramters.jsonに以下を追加しないといけない模様。

{
    "authRoleName": {
        "Ref": "AuthRoleName"
    },
    "unauthRoleName": {
        "Ref": "UnauthRoleName"
    }
}

無事amplify pushできました。
一件落着ならぬ三件落着

ただ、3回目に関しては、毎回手動でファイルをアップロードするとか無理です。しんどいです。助けてください。この対応さらっと書いてますが、三つとも12月中に発生して切り分けやら調査でめっちゃ時間持ってかれました。issueにこれはまれなコンステレーションで発生しますとか書いてあるのがツラい。でもAmplify使えねーってなるのも嫌なのでどうにかしたい。CLIのソースを覗くときがやってきそうです。
皆さんもどうかお気をつけください。

17日目はデザイナーおださん@odagiri_24です!ご期待ください!

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

AWS 一つのEBに複数ドメイン割り当てる方法

A.com ← 割り当てたいドメイン
ORG.com ← 本来のEBのドメイン

Route53 (A.comをcloudfrontに向けて設定)
↓
cloudfront (オリジン:ORG.comに向けて、CNAMEでA.comを設定。ACMもA.comのものを使う)
↓
EB (ドメイン設定はORG.com)

順番的には下から構築していけば楽なはず

EB(というかEC2)側で、phpだったら$_SERVER["SERVER_NAME"]すると "A.com" が取得出来ます。

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

AWSとGCPをつなげるVPNの設定をCloudFormationとTerraformで行う

概要

AWS-GCP間のVPNの接続をCloudFormation(AWS)とTerraform(GCP)を利用して構成する方法について紹介します。Terraformだけで構成したほうがきれいですが、普段AWSはCloudFormationで構成しているため統一性を持たせるためAWS側はCloudFormationを利用しました。

本記事で構築したVPNはすでに破棄済みです。

VPNの構成

以下の記事で紹介されている「高可用性(HA)VPN接続 パターン4」をTerraformとCloudFormationを利用して作成します。

https://dev.classmethod.jp/articles/aws_gcp_vpn/

最終形

AWS側(CloudFormation)

cfn.yml
Parameters:
  MainRouteTableID:
    Type: 'String'
    Default: 'rtb-xxxxxxxxxxxxxx'
  IPCustomerGateway1:
    Type: 'String'
    Default: '35.242.58.51'
    AllowedPattern: '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$'
  IPCustomerGateway2:
    Type: 'String'
    Default: '35.220.56.158'
    AllowedPattern: '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$'

Resources:
  VGW:
    Type: AWS::EC2::VPNGateway
    Properties:
      AmazonSideAsn: 64512
      Type: ipsec.1
  AttachVPNGateway1:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref 'EC2VPC'
      VpnGatewayId: !Ref 'VGW'
  EnableVGWPropagation1:
    Type: AWS::EC2::VPNGatewayRoutePropagation
    DependsOn: AttachVPNGateway1
    Properties:
      RouteTableIds:
      - !Ref 'MainRouteTableID'
      VpnGatewayId: !Ref 'VGW'

  CustomerGateway1:
    Type : AWS::EC2::CustomerGateway
    Properties:
      Type: "ipsec.1"
      BgpAsn: "65000"
      IpAddress: !Ref "IPCustomerGateway1"
      Tags:
        - Key: 'Name'
          Value: !Sub ${AWS::StackName}-customer-gateway1
  VPNConnection1:
    Type: AWS::EC2::VPNConnection
    Properties:
      CustomerGatewayId: !Ref 'CustomerGateway1'
      StaticRoutesOnly: false
      Type: "ipsec.1"
      VpnGatewayId: !Ref 'VGW'
      Tags:
        - Key: 'Name'
          Value: !Sub ${AWS::StackName}-Connection1

  AttachVPNGateway2:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref 'EC2VPC'
      VpnGatewayId: !Ref 'VGW'
  EnableVGWPropagation2:
    Type: AWS::EC2::VPNGatewayRoutePropagation
    DependsOn: AttachVPNGateway2
    Properties:
      RouteTableIds:
      - !Ref 'MainRouteTableID'
      VpnGatewayId: !Ref 'VGW'

  CustomerGateway2:
    Type : AWS::EC2::CustomerGateway
    Properties:
      Type: "ipsec.1"
      BgpAsn: "65000"
      IpAddress: !Ref "IPCustomerGateway2"
      Tags:
        - Key: 'Name'
          Value: !Sub ${AWS::StackName}-customer-gateway2
  VPNConnection2:
    Type: AWS::EC2::VPNConnection
    Properties:
      CustomerGatewayId: !Ref 'CustomerGateway2'
      StaticRoutesOnly: false
      Type: "ipsec.1"
      VpnGatewayId: !Ref 'VGW'
      Tags:
        - Key: 'Name'
          Value: !Sub ${AWS::StackName}-Connection2

GCP側(Terraform)

variables.tf
variable "router_google_asn" {
  default = 65000
}

variable "router_peer_asn" {
  default = 64512
}

variable "aws_outside_ip_vgw1" {
  default = "13.114.3.172"
}

variable "aws_outside_ip_vgw2" {
  default = "52.199.177.210"
}

variable "aws_outside_ip_vgw3" {
  default = "13.112.92.171"
}

variable "aws_outside_ip_vgw4" {
  default = "18.182.165.1"
}

variable "aws_inside_ip_cgw1" {
  default = "169.254.185.158"
}

variable "aws_inside_ip_cgw2" {
  default = "169.254.41.166"
}

variable "aws_inside_ip_cgw3" {
  default = "169.254.133.50"
}

variable "aws_inside_ip_cgw4" {
  default = "169.254.187.230"
}

variable "aws_inside_ip_vgw1" {
  default = "169.254.185.157"
}

variable "aws_inside_ip_vgw2" {
  default = "169.254.41.165"
}

variable "aws_inside_ip_vgw3" {
  default = "169.254.133.49"
}

variable "aws_inside_ip_vgw4" {
  default = "169.254.187.229"
}

variable "pre_shared_key1" {
  default = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}

variable "pre_shared_key2" {
  default = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
}

variable "pre_shared_key3" {
  default = "cccccccccccccccccccccccccccccccc"
}

variable "pre_shared_key4" {
  default = "dddddddddddddddddddddddddddddddd"
}
vpn.tf
resource "google_compute_ha_vpn_gateway" "vpn_gateway" {
  provider = google-beta
  name = "vpn-gateway"
  network = google_compute_network.vpc.id
  region = "asia-northeast1"
}

resource "google_compute_router" "router" {
  name    = "router"
  network = google_compute_network.vpc.id
  bgp {
    asn               = var.router_google_asn
    advertise_mode    = "CUSTOM"
    advertised_groups = ["ALL_SUBNETS"]
  }
  region = "asia-northeast1"
}

// 対AWS用のVPNゲートウェイの設定(AWS側の設定が完了後に設定)
resource "google_compute_external_vpn_gateway" "vpn_interface" {
  provider = google-beta
  name = "aws-gateway"
  redundancy_type = "FOUR_IPS_REDUNDANCY"
  interface {
    id = 0
    ip_address = var.aws_outside_ip_vgw1
  }
  interface {
    id = 1
    ip_address = var.aws_outside_ip_vgw2
  }
  interface {
    id = 2
    ip_address = var.aws_outside_ip_vgw3
  }
  interface {
    id = 3
    ip_address = var.aws_outside_ip_vgw4
  }
}

resource "google_compute_vpn_tunnel" "vpn_tunnel1" {
  provider = google-beta
  name = "vpn-tunnel1"
  shared_secret = var.pre_shared_key1
  vpn_gateway = google_compute_ha_vpn_gateway.vpn_gateway.self_link
  vpn_gateway_interface = 0
  peer_external_gateway = google_compute_external_vpn_gateway.vpn_interface.self_link
  peer_external_gateway_interface = 0
  router = google_compute_router.router.name
  ike_version = 1
  region = "asia-northeast1"
}
resource "google_compute_router_interface" "interface1" {
  provider = google-beta
  name = "${google_compute_vpn_tunnel.vpn_tunnel1.name}-interface1"
  router = google_compute_router.router.name
  ip_range = "${var.aws_inside_ip_cgw1}/30"
  vpn_tunnel = google_compute_vpn_tunnel.vpn_tunnel1.name
  region = "asia-northeast1"
}
resource "google_compute_router_peer" "peer1" {
  provider = google-beta
  name = "${google_compute_vpn_tunnel.vpn_tunnel1.name}-peer1"
  router = google_compute_router.router.name
  peer_ip_address = var.aws_inside_ip_vgw1
  peer_asn = var.router_peer_asn
  interface = google_compute_router_interface.interface1.name
  region = "asia-northeast1"
}

resource "google_compute_vpn_tunnel" "vpn_tunnel2" {
  provider = google-beta
  name = "vpn-tunnel2"
  shared_secret = var.pre_shared_key2
  vpn_gateway = google_compute_ha_vpn_gateway.vpn_gateway.self_link
  vpn_gateway_interface = 0
  peer_external_gateway = google_compute_external_vpn_gateway.vpn_interface.self_link
  peer_external_gateway_interface = 1
  router = google_compute_router.router.name
  ike_version = 1
  region = "asia-northeast1"
}
resource "google_compute_router_interface" "interface2" {
  provider = google-beta
  name = "${google_compute_vpn_tunnel.vpn_tunnel2.name}-interface2"
  router = google_compute_router.router.name
  ip_range = "${var.aws_inside_ip_cgw2}/30"
  vpn_tunnel = google_compute_vpn_tunnel.vpn_tunnel2.name
  region = "asia-northeast1"
}
resource "google_compute_router_peer" "peer2" {
  provider = google-beta
  name = "${google_compute_vpn_tunnel.vpn_tunnel2.name}-peer2"
  router = google_compute_router.router.name
  peer_ip_address = var.aws_inside_ip_vgw2
  peer_asn = var.router_peer_asn
  interface = google_compute_router_interface.interface2.name
  region = "asia-northeast1"
}

resource "google_compute_vpn_tunnel" "vpn_tunnel3" {
  provider = google-beta
  name = "vpn-tunnel3"
  shared_secret = var.pre_shared_key3
  vpn_gateway = google_compute_ha_vpn_gateway.vpn_gateway.self_link
  vpn_gateway_interface = 1
  peer_external_gateway = google_compute_external_vpn_gateway.vpn_interface.self_link
  peer_external_gateway_interface = 2
  router = google_compute_router.router.name
  ike_version = 1
  region = "asia-northeast1"
}
resource "google_compute_router_interface" "interface3" {
  provider = google-beta
  name = "${google_compute_vpn_tunnel.vpn_tunnel3.name}-interface3"
  router = google_compute_router.router.name
  ip_range = "${var.aws_inside_ip_cgw3}/30"
  vpn_tunnel = google_compute_vpn_tunnel.vpn_tunnel3.name
  region = "asia-northeast1"
}
resource "google_compute_router_peer" "peer3" {
  provider = google-beta
  name = "${google_compute_vpn_tunnel.vpn_tunnel3.name}-peer3"
  router = google_compute_router.router.name
  peer_ip_address = var.aws_inside_ip_vgw3
  peer_asn = var.router_peer_asn
  interface = google_compute_router_interface.interface3.name
  region = "asia-northeast1"
}

resource "google_compute_vpn_tunnel" "vpn_tunnel4" {
  provider = google-beta
  name = "vpn-tunnel4"
  shared_secret = var.pre_shared_key4
  vpn_gateway = google_compute_ha_vpn_gateway.vpn_gateway.self_link
  vpn_gateway_interface = 1
  peer_external_gateway = google_compute_external_vpn_gateway.vpn_interface.self_link
  peer_external_gateway_interface = 3
  router = google_compute_router.router.name
  ike_version = 1
  region = "asia-northeast1"
}
resource "google_compute_router_interface" "interface4" {
  provider = google-beta
  name = "${google_compute_vpn_tunnel.vpn_tunnel4.name}-interface4"
  router = google_compute_router.router.name
  ip_range = "${var.aws_inside_ip_cgw4}/30"
  vpn_tunnel = google_compute_vpn_tunnel.vpn_tunnel4.name
  region = "asia-northeast1"
}
resource "google_compute_router_peer" "peer4" {
  provider = google-beta
  name = "${google_compute_vpn_tunnel.vpn_tunnel4.name}-peer4"
  router = google_compute_router.router.name
  peer_ip_address = var.aws_inside_ip_vgw4
  peer_asn = var.router_peer_asn
  interface = google_compute_router_interface.interface4.name
  region = "asia-northeast1"
}

反映手順

AWSリソースとGCPリソースに関してお互いに依存関係が存在するため、反映の順番を工夫する必要があります。以下でその反映の手順を紹介します。

1. GCPのVPN GatewayとCloud Routerを作成

最初にGCP側のVPN GatewayとCloud Routerを作成します。以下をterraform applyすることで作成可能です。

variables.tf
variable "router_google_asn" {
  default = 65000
}
vpn.tf
// VPC
resource "google_compute_network" "vpc" {
  name                    = "vpc"
  auto_create_subnetworks = false
  routing_mode            = "GLOBAL"
}

resource "google_compute_ha_vpn_gateway" "vpn_gateway" {
  provider = google-beta
  name = "vpn-gateway"
  network = google_compute_network.vpc.id
  region = "asia-northeast1"
}

resource "google_compute_router" "router" {
  name    = "router"
  network = google_compute_network.vpc.id
  bgp {
    asn               = var.router_google_asn
    advertise_mode    = "CUSTOM"
    advertised_groups = ["ALL_SUBNETS"]
  }
  region = "asia-northeast1"
}

2. 作成されたVPN GatewayのGlobal IPをメモ

作られたVPN GatewayのIPを次のステップで利用するためメモします。

Google_Cloud_Platform.png

3. AWSリソースの作成

先程メモしたIPを以下のIPCustomerGateway1IPCustomerGateway2 にそれぞれ設定します。またMainRouteTableID にVPCのメインルートテーブルを変数として設定します(自動取得する方法があったら教えて下さい)。なお、本記事ではVPNが作られている状態を前提として説明を進めます。

この状態で変更セットを作成しCFnを反映することでAWS側で必要なリソースを作成します。リソースに関しては以下のテンプレートをご参照ください。

Parameters:
  MainRouteTableID:
    Type: 'String'
    Default: 'rtb-0115ded3e67eb5833'
  IPCustomerGateway1:
    Type: 'String'
    Default: '35.242.58.51'
    AllowedPattern: '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$'
  IPCustomerGateway2:
    Type: 'String'
    Default: '35.220.56.158'
    AllowedPattern: '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$'

Resources:
  VGW:
    Type: AWS::EC2::VPNGateway
    Properties:
      AmazonSideAsn: 64512
      Type: ipsec.1
  AttachVPNGateway1:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref 'EC2VPC'
      VpnGatewayId: !Ref 'VGW'
  EnableVGWPropagation1:
    Type: AWS::EC2::VPNGatewayRoutePropagation
    DependsOn: AttachVPNGateway1
    Properties:
      RouteTableIds:
      - !Ref 'MainRouteTableID'
      VpnGatewayId: !Ref 'VGW'

  CustomerGateway1:
    Type : AWS::EC2::CustomerGateway
    Properties:
      Type: "ipsec.1"
      BgpAsn: "65000"
      IpAddress: !Ref "IPCustomerGateway1"
      Tags:
        - Key: 'Name'
          Value: !Sub ${AWS::StackName}-customer-gateway1
  VPNConnection1:
    Type: AWS::EC2::VPNConnection
    Properties:
      CustomerGatewayId: !Ref 'CustomerGateway1'
      StaticRoutesOnly: false
      Type: "ipsec.1"
      VpnGatewayId: !Ref 'VGW'
      Tags:
        - Key: 'Name'
          Value: !Sub ${AWS::StackName}-Connection1

  AttachVPNGateway2:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref 'EC2VPC'
      VpnGatewayId: !Ref 'VGW'
  EnableVGWPropagation2:
    Type: AWS::EC2::VPNGatewayRoutePropagation
    DependsOn: AttachVPNGateway2
    Properties:
      RouteTableIds:
      - !Ref 'MainRouteTableID'
      VpnGatewayId: !Ref 'VGW'

  CustomerGateway2:
    Type : AWS::EC2::CustomerGateway
    Properties:
      Type: "ipsec.1"
      BgpAsn: "65000"
      IpAddress: !Ref "IPCustomerGateway2"
      Tags:
        - Key: 'Name'
          Value: !Sub ${AWS::StackName}-customer-gateway2
  VPNConnection2:
    Type: AWS::EC2::VPNConnection
    Properties:
      CustomerGatewayId: !Ref 'CustomerGateway2'
      StaticRoutesOnly: false
      Type: "ipsec.1"
      VpnGatewayId: !Ref 'VGW'
      Tags:
        - Key: 'Name'
          Value: !Sub ${AWS::StackName}-Connection2

4. 作成したVPN接続の設定情報をダウンロード

作成した2つのVPN接続の設定情報をダウンロードします。AWSのコンソールからVPC → サイト間のVPN接続の画面で作成したVPN接続を選択し設定のダウンロードからダウンロードします。ベンダーはGenericを選択します。

VPN_接続___VPC_Management_Console.png

5. 設定情報から必要なものをGCPのTerraformに当てはめて反映

以下の対応表を元に、variables.tfの各変数を埋めます。1~4まで変数がありますが、それぞれの接続ごとに設定します。

aa aa
aws_outside_ip_vgw Outside IP Addresses → Customer Gateway
aws_inside_ip_cgw Inside IP Addresses → Customer Gateway
aws_inside_ip_vgw Inside IP Addresses → Virtual Private Gateway
pre_shared_key Pre-Shared Key

変数がセットできたら、terraform applyでリソースを作成します。こちらについても作られるリソースはテンプレートをご参照ください。

variables.tf
variable "router_peer_asn" {
  default = 64512
}

variable "aws_outside_ip_vgw1" {
  default = "13.114.3.172"
}

variable "aws_outside_ip_vgw2" {
  default = "52.199.177.210"
}

variable "aws_outside_ip_vgw3" {
  default = "13.112.92.171"
}

variable "aws_outside_ip_vgw4" {
  default = "18.182.165.1"
}

variable "aws_inside_ip_cgw1" {
  default = "169.254.185.158"
}

variable "aws_inside_ip_cgw2" {
  default = "169.254.41.166"
}

variable "aws_inside_ip_cgw3" {
  default = "169.254.133.50"
}

variable "aws_inside_ip_cgw4" {
  default = "169.254.187.230"
}

variable "aws_inside_ip_vgw1" {
  default = "169.254.185.157"
}

variable "aws_inside_ip_vgw2" {
  default = "169.254.41.165"
}

variable "aws_inside_ip_vgw3" {
  default = "169.254.133.49"
}

variable "aws_inside_ip_vgw4" {
  default = "169.254.187.229"
}

variable "pre_shared_key1" {
  default = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}

variable "pre_shared_key2" {
  default = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
}

variable "pre_shared_key3" {
  default = "cccccccccccccccccccccccccccccccc"
}

variable "pre_shared_key4" {
  default = "dddddddddddddddddddddddddddddddd"
}
vpn.tf
resource "google_compute_external_vpn_gateway" "vpn_interface" {
  provider = google-beta
  name = "aws-gateway"
  redundancy_type = "FOUR_IPS_REDUNDANCY"
  interface {
    id = 0
    ip_address = var.aws_outside_ip_vgw1
  }
  interface {
    id = 1
    ip_address = var.aws_outside_ip_vgw2
  }
  interface {
    id = 2
    ip_address = var.aws_outside_ip_vgw3
  }
  interface {
    id = 3
    ip_address = var.aws_outside_ip_vgw4
  }
}

resource "google_compute_vpn_tunnel" "vpn_tunnel1" {
  provider = google-beta
  name = "vpn-tunnel1"
  shared_secret = var.pre_shared_key1
  vpn_gateway = google_compute_ha_vpn_gateway.vpn_gateway.self_link
  vpn_gateway_interface = 0
  peer_external_gateway = google_compute_external_vpn_gateway.vpn_interface.self_link
  peer_external_gateway_interface = 0
  router = google_compute_router.router.name
  ike_version = 1
  region = "asia-northeast1"
}
resource "google_compute_router_interface" "interface1" {
  provider = google-beta
  name = "${google_compute_vpn_tunnel.vpn_tunnel1.name}-interface1"
  router = google_compute_router.router.name
  ip_range = "${var.aws_inside_ip_cgw1}/30"
  vpn_tunnel = google_compute_vpn_tunnel.vpn_tunnel1.name
  region = "asia-northeast1"
}
resource "google_compute_router_peer" "peer1" {
  provider = google-beta
  name = "${google_compute_vpn_tunnel.vpn_tunnel1.name}-peer1"
  router = google_compute_router.router.name
  peer_ip_address = var.aws_inside_ip_vgw1
  peer_asn = var.router_peer_asn
  interface = google_compute_router_interface.interface1.name
  region = "asia-northeast1"
}

resource "google_compute_vpn_tunnel" "vpn_tunnel2" {
  provider = google-beta
  name = "vpn-tunnel2"
  shared_secret = var.pre_shared_key2
  vpn_gateway = google_compute_ha_vpn_gateway.vpn_gateway.self_link
  vpn_gateway_interface = 0
  peer_external_gateway = google_compute_external_vpn_gateway.vpn_interface.self_link
  peer_external_gateway_interface = 1
  router = google_compute_router.router.name
  ike_version = 1
  region = "asia-northeast1"
}
resource "google_compute_router_interface" "interface2" {
  provider = google-beta
  name = "${google_compute_vpn_tunnel.vpn_tunnel2.name}-interface2"
  router = google_compute_router.router.name
  ip_range = "${var.aws_inside_ip_cgw2}/30"
  vpn_tunnel = google_compute_vpn_tunnel.vpn_tunnel2.name
  region = "asia-northeast1"
}
resource "google_compute_router_peer" "peer2" {
  provider = google-beta
  name = "${google_compute_vpn_tunnel.vpn_tunnel2.name}-peer2"
  router = google_compute_router.router.name
  peer_ip_address = var.aws_inside_ip_vgw2
  peer_asn = var.router_peer_asn
  interface = google_compute_router_interface.interface2.name
  region = "asia-northeast1"
}

resource "google_compute_vpn_tunnel" "vpn_tunnel3" {
  provider = google-beta
  name = "vpn-tunnel3"
  shared_secret = var.pre_shared_key3
  vpn_gateway = google_compute_ha_vpn_gateway.vpn_gateway.self_link
  vpn_gateway_interface = 1
  peer_external_gateway = google_compute_external_vpn_gateway.vpn_interface.self_link
  peer_external_gateway_interface = 2
  router = google_compute_router.router.name
  ike_version = 1
  region = "asia-northeast1"
}
resource "google_compute_router_interface" "interface3" {
  provider = google-beta
  name = "${google_compute_vpn_tunnel.vpn_tunnel3.name}-interface3"
  router = google_compute_router.router.name
  ip_range = "${var.aws_inside_ip_cgw3}/30"
  vpn_tunnel = google_compute_vpn_tunnel.vpn_tunnel3.name
  region = "asia-northeast1"
}
resource "google_compute_router_peer" "peer3" {
  provider = google-beta
  name = "${google_compute_vpn_tunnel.vpn_tunnel3.name}-peer3"
  router = google_compute_router.router.name
  peer_ip_address = var.aws_inside_ip_vgw3
  peer_asn = var.router_peer_asn
  interface = google_compute_router_interface.interface3.name
  region = "asia-northeast1"
}

resource "google_compute_vpn_tunnel" "vpn_tunnel4" {
  provider = google-beta
  name = "vpn-tunnel4"
  shared_secret = var.pre_shared_key4
  vpn_gateway = google_compute_ha_vpn_gateway.vpn_gateway.self_link
  vpn_gateway_interface = 1
  peer_external_gateway = google_compute_external_vpn_gateway.vpn_interface.self_link
  peer_external_gateway_interface = 3
  router = google_compute_router.router.name
  ike_version = 1
  region = "asia-northeast1"
}
resource "google_compute_router_interface" "interface4" {
  provider = google-beta
  name = "${google_compute_vpn_tunnel.vpn_tunnel4.name}-interface4"
  router = google_compute_router.router.name
  ip_range = "${var.aws_inside_ip_cgw4}/30"
  vpn_tunnel = google_compute_vpn_tunnel.vpn_tunnel4.name
  region = "asia-northeast1"
}
resource "google_compute_router_peer" "peer4" {
  provider = google-beta
  name = "${google_compute_vpn_tunnel.vpn_tunnel4.name}-peer4"
  router = google_compute_router.router.name
  peer_ip_address = var.aws_inside_ip_vgw4
  peer_asn = var.router_peer_asn
  interface = google_compute_router_interface.interface4.name
  region = "asia-northeast1"
}

6. 確認

GCP

反映後しばらく待って、Bgp session statusBGP established に変われば成功です。

Google_Cloud_Platform-2.png

AWS

また、AWSについてもVPN接続のTunnel Detailsのステータスが4つともアップになっていれば成功です。

VPN_接続___VPC_Management_Console-2.png

VPN_接続___VPC_Management_Console-3.png

まとめ

今回はAWSとGCPをつなげるVPNの設定をCloudFormationとTerraformで行う方法を紹介しました。コピペで使ってもらえれば幸いです。

参考

本構成は以下の記事を大変参考にさせていただきました。

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

AmazonLightsailで最強なWordPress環境を手に入れる

やりたいこと

WordPressを使ってお手軽かつ、負荷にも耐えられるWEBサイトを構築したい。が、サーバを何台も用意してとか大袈裟なことはしたくなく、極力手間なく安く楽にやりたい。あとAWS上で全て構築したい。

システム構成

AmazonLightsail-small.png

  • Amazon LightsailでWordPressの管理を行う。
  • Amazon Lightsailで起動したインスタンスでは配信せず、S3に静的ファイルを出力してそれをCloudFront経由で配信する。

構築手順

1.AmazonLightsailにてWordPress環境構築

create.png

プラットフォーム: Linux/Unix 、アプリ+OS: WordPress を選択し、任意のリソース名を入れて作成。ステータスが実行中になるまで待つ。

最小インスタンス1ヶ月$3.5♪(2020/12現在)
数クリックだけで楽々構築♪

2.WordPressの表示確認

sample.png

  1. IPアドレスでアクセスし、上記のようなサンプルページが表示されるか確認
  2. 管理画面のアカウントを取得するためにLightsail専用のターミナルを起動し、以下コマンドを打ちユーザ名とパスワードを取得
    cat /home/bitnami/bitnami_credentials
    
  3. サンプルページ右下のManageから管理画面にログインできるか確認

3.S3のバケット作成

S3にて静的ページを保存しておく、非公開バケットを作成する。バケット名は全てのユーザ間でユニークにする必要があり、また作成後は変更ができないので、よく考えて命名すること。
ブロックパブリックアクセスは 新しいアクセスコントロールリスト (ACL) を介して付与されたバケットとオブジェクトへのパブリックアクセスをブロックする のみオフにして、後はオンにしておく。

ブロックパブリックアクセスの説明はホント分かりにくい...

4.CloudFrontの設定

Webサイトを配信するためのCloudFrontの設定を以下の手順で行う。

  1. Create Distribution をクリック
  2. Webの Get Started をクリック
  3. 以下の通り設定
    1. Origin Domain Name:作成したS3のバケット指定
    2. Restrict Bucket Access:Yes
    3. Origin Access Identity:Create a New Identity
    4. Grant Read Permissions on Bucket:Yes, Update Bucket Policy
    5. Default Root Object:index.html
  4. Done!

S3バケットポリシーにCloudFront経由でのアクセス設定が自動で設定される。

5.S3へのアクセス権限設定

WordPressのインスタンスからS3へアップロードするためのユーザをIAMで作成する。
アクセスキーとシークレットキーは必ず保存する。
作成後、以下のポリシーをインラインにて追加する。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect":"Allow",
            "Action":[
                "s3:ListAllMyBuckets"
            ],
            "Resource":"arn:aws:s3:::*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:PutObjectAcl",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::(バケット名)",
                "arn:aws:s3:::(バケット名)/*"
            ]
        }
    ]
}

6.WordPressのプラグイン設定

静的ファイルを出力するプラグイン(StaticPress)のインストール

  1. WordPressの管理画面の、 Plugins -> AddNew から StaticPress を検索し、インストール
  2. Plugins -> Installed Plugins から StaticPress を探し、 Activate で活性化

S3へアップロードするプラグイン(StaticPressS3)のインストール

  1. GitHub( https://github.com/megumiteam/staticpress-s3 )にアクセスし、ZIPファイルをダウンロード
  2. WordPressの管理画面の、 Plugins -> AddNew -> Upload Plugin からダウンロードしたZIPファイルを選択しインストール
  3. Plugins -> Installed Plugins から StaticPress S3 を探し、 Activate で活性化

静的ファイルの出力先とAWSのアクセスキーの設定

staticpress_s3_option.png

  1. WordPressの管理画面の、 StaticPress -> StaticPress Options を開き、以下を入力
    1. Static URL:https://xxxxxxxxxxxxx.cloudfront.net/
    2. Save DIR (Document root):/opt/bitnami/apps/wordpress/htdocs/static/
  2. Save Changes を押して保存
  3. 続いて、その下に表示されている以下を入力
    1. AWS Access Key:xxxxxxxxxxxxxxxxxxxx
    2. AWS Secret Key:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    3. AWS Regin:AP_NORTHEAST_1
    4. S3 Bucket:設定したS3バケット名
  4. Save Changes を押して保存

7.S3へ静的ファイル出力

staticpress_rebuild.png

  1. WordPressの管理画面の StaticPress のページから Rebuild ボタンを押下
  2. Endが表示されるまで待つ
  3. 2回目以降は差分のみアップロードされる

8.CloudFrontのURLで確認

https://xxxxxxxxxxxxx.cloudfront.net/ にアクセスし、サンプルページが閲覧できるか確認する。
CloudFront経由で閲覧できるようになるには数時間かかる場合があり、S3に307リダイレクトされエラーとなる場合は少し待ってアクセスしてみる。

考察

  • 前段にCloudFront+S3を持ってきたことでWordPressの管理画面を公開領域から切り離すことができ、管理画面へのアタックを防ぐことができた(要以下ファイアウォール)。また、そのおかげでインスタンスサイズは最小で十分かと。
    https://aws.amazon.com/jp/blogs/compute/enhancing-site-security-with-new-lightsail-firewall-features/
  • Lightsail = ステージング環境CloudFront+S3 = 本番環境 で使えそう。
  • Lightsail専用のWEB上で操作できるターミナルが付いているが、vi(vim)の動作が少し怪しいため、何か編集作業をする際は、秘密鍵をダウンロードして手元でsshしたほうが良さそう。ちょっと調べるとかなら全然使える。
  • インスタンスに設定する静的IPは料金に含まれているので使ったほうが良さげ。
  • インスタンスを都度停止して節約できると思ったが、Lightsailの場合、起動も停止も料金は変わらないので意味がなかった。
  • ルート以外での /アクセス は 自動で index.html に読み替えないので、対応が必要になる(S3をWebホスティングすれば解決するが、全開放することになる)。
  • エラーページが微妙。
  • 静的ファイルは不要なものまでS3へ転送されるので、ここは任意のものが選べるプラグインを作ったほうが良さそう。
  • CloudFront -> 任意のドメインに変更すれば完璧。

こういったサービスは、少し毛嫌いしていたが、思った以上に使い勝手良かったので、今後使う機会ありそう!

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

AWS CloudShell で Amazon EKS の作業環境を整える

AWS その2 Advent Calendar 2020 の10日目が空いていたので登録させていただきました。

はじめに

AWS re:Invent 2020 の Werner Vogels Keynote で AWS ClouShell が発表されました。

:rocket: AWS CloudShell – Command-Line Access to AWS Resources
https://aws.amazon.com/jp/blogs/aws/aws-cloudshell-command-line-access-to-aws-resources/

AWS CloudShellは AWS マネジメントコンソールから直接起動可能なブラウザベースのシェルです。
シェルとしては Bash, PowerShell, Z shell が使用でき、AWS CLI やその他の主要な開発言語を
サポートするツール類が事前セットアップされています。

他社クラウドサービスでは既に同様の機能が提供されていたので、
待望のサービスだった方も多いのではないでしょうか。

事前セットアップ済みツールについては以下のドキュメントに記載がありますが、
kubectl 等がセットアップされていないため、EKS 用の作業環境を
自分で準備しようという内容になります。

AWS CloudShell compute environment: specifications and software
https://docs.aws.amazon.com/cloudshell/latest/userguide/vm-specs.html

シェル環境への追加ソフトウェアのインストール自体はサポートされますが、
責任共有モデルに則り、ユーザー自身の責任で管理する必要があります。

各種ツールのインストール

CloudShell の起動はマネージドコンソール上の ClouShell アイコンをクリックするだけです。
image.png

  • とりあえず、ぱっと思いついたものを入れています
  • バージョンなどは適宜読み替えてください。
  • クラスターと IAM ユーザー/ロールの紐付けは事前に設定されている前提で割愛します。
  • Docker が使いたい場合は、素直に Cloud9 にしましょう。

インストール先は $HOME/.local/bin としています。
セッション間で保持される永続ストレージが、$HOMEであるためです。
(詳細は後半の CloudShell の注意点を参照)

# ディレクトリ作成
mkdir -p $HOME/.local/bin
cd $HOME/.local/bin

# kubectl
curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.18.13/bin/linux/amd64/kubectl
chmod +x kubectl

# context の作成
aws eks update-kubeconfig --name <YOUR_CLUSTER_NAME>

# eksctl
curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
sudo mv /tmp/eksctl $HOME/.local/bin

# helm
export VERIFY_CHECKSUM=false
curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
sudo mv /usr/local/bin/helm $HOME/.local/bin

yum でインストールするパッケージなどは永続ストレージ ($HOME)に配置できないため、
新規セッションの都度、インストールする必要があります。
.bash_profile にコマンドを書いておくことで CloudShell 起動時に
自動でインストールすることは可能です。

kubectl completion を使いたいので bash-completion をインストールします。

$HOME/.bash_profile
# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
        . ~/.bashrc
fi

# User specific environment and startup programs

PATH=$PATH:$HOME/.local/bin:$HOME/bin

export PATH

# 起動時にインストール
sudo yum install -y bash-completion > /dev/null 2>&1

kubectl completion の設定は永続ストレージに保存できます。

kubectl completion bash >  $HOME/.bash_completion

これで CloudShell でも補完がききます!

CloudShell の注意点

使用にあたっていくつか注意点を記載します。

永続ストレージ

CloudShell はリージョン毎に 1 GB の永続ストレージを使用することができます。
永続ストレージはホームディレクトリ ($HOME) にあり、プライベートです。
(ユーザー間で共有されません。)
この領域のみがセッション間で保持されることが保証されています。
ホームディレクトリ以外に保存したソフトウェア等は、セッション終了時に維持さません。
また最後のセッション終了後から 120 日経過すると永続ストレージのデータは削除されます。

CloudShell にアクセスするための権限

他のサービス同様、対象の IAM ユーザー/ロールに CloudShell のアクセス権限を
明示的に付与する必要があります。
AWS 管理ポリシーの AWSCloudShellFullAccess を使用するのが最も簡単ですが、
CloudShell 経由のファイルアプロード/ダンロードを制限したい場合などは
以下のようなカスタムポリシーを使用できます。

{
    "Version": "2012-10-17",
    "Statement": [{
        "Sid": "CloudShellUser",
        "Effect": "Allow",
        "Action": [
            "cloudshell:*"
        ],
        "Resource": "*"
    }, {
        "Sid": "DenyUploadDownload",
        "Effect": "Deny",
        "Action": [
            "cloudshell:GetFileDownloadUrls",
            "cloudshell:GetFileUploadUrls"
        ],
        "Resource": "*"
    }]
}

CloudShell 内から AWS サービスにアクセスするための権限

AWS マネジメントコンソールのサインインに使用した IAM 認証情報を自動で利用します。
つまり、操作する IAM ユーザー (フェデレーションの場合は IAM ロール) に
対象 AWS サービスへの明示的なアクセス許可が必要です。

参考

AWS CloudShell - User Guide
https://docs.aws.amazon.com/cloudshell/latest/userguide/welcome.html

簡単ですが、以上です。

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

【AWS ELB】 ELBやDockerを使って、EC2にWEBサーバ用のドメインを二つ持たせる奇行

はじめに

「ELBを使って、EC2にWEBサーバ用のドメインを二つ持たせる」
つまり、「ELBのリスナーでWEBサーバ用のドメインを二つ登録し、同じEC2(WEBサーバ)をターゲットグループのターゲットとする」

という、ほとんど奇行ですねということを行ったのでその時のメモを残します。
あくまでこういうこともできるんだくらいな温度感で・・・はい、ご了承ください。(笑)

内容

以下のような構成にしました。

・ELBのリスナールールに二つのドメインを割り当てて、ターゲットグループで80と8080をルーティングするように設定
・Dockerでnginxを二つ起動させておきます

「ELB」→「EC2」→「Docker」→「nginx」:80ポートで待ち構えている  (nginx1とします。)
                         →「nginx」:8080ポートで待ち構えている(nginx2とします。)

そこで、

・「nginx1」には「https://hoge.com」でアクセスできるように・・・
・「nginx2」には「https://fuga.com」でアクセスできるように・・・

のようにします。

ドメインを二つ用意する必要あるので、サブドメイン切るなど行ってください。

うーん、それにしても奇行ですねー。

奇行までの環境構築

VPCやEC2、ELBの構築、ドメイン設定などは以下をご参照ください。
わかりやすく解説とともに構築までの流れを展開されています。

・0から始めるAWS入門:概要
 → https://qiita.com/hiroshik1985/items/6433d5de97ac55fedfde
・0から始めるAWS入門①:VPC編
 → https://qiita.com/hiroshik1985/items/9de2dd02c9c2f6911f3b
・0から始めるAWS入門②:EC2編
 → https://qiita.com/hiroshik1985/items/f078a6a017d092a541cf
・0から始めるAWS入門③:ELB編
 → https://qiita.com/hiroshik1985/items/ffda3f2bdb71599783a3

奇行

1. ターゲットグループを作成

一つ目の「nginx1」用のターゲットグループとして、

・ターゲットグループ構築画面で、「Instances」→「ターゲットグループ名記入」→「ポート80」→「作成したVPC」→あとは良しなに→「Next」を選択します
・Available instancesに構築したEC2を選択し、「Include as pending below」→「Create target group」を選択します

二つ目の「nginx2」用のターゲットグループとして、

・ターゲットグループ構築画面で、「Instances」→「ターゲットグループ名記入」→「ポート8080」→「作成したVPC」→あとは良しなに→「Next」を選択します
・Available instancesに構築したEC2を選択し、「Include as pending below」→「Create target group」を選択します

2. リスナーのルールに2つのドメインを追加

一つ目の「nginx1」用のリスナールールとして

・ロードバランサー構築画面で、対象のロードバランサーを選択し、「リスナー」タブを選択します
・ルール部分の「ルールの表示/編集」を選択します
・ルールの挿入を行い、条件に「ホストヘッダー...」を選択し、値に「WEBサーバドメイン」を入力します。
・アクションの追加を選択し、「nginx1」用に作成したターゲットグループを割り当てます

二つ目の「nginx2」用のリスナールールとして

・ロードバランサー構築画面で、対象のロードバランサーを選択し、「リスナー」タブを選択します
・ルール部分の「ルールの表示/編集」を選択します
・ルールの挿入を行い、条件に「ホストヘッダー...」を選択し、値に「WEBサーバドメイン」を入力します。
・アクションの追加を選択し、「nginx2」用に作成したターゲットグループを割り当てます

所感

本当に奇行だと思いますし、やらないと思いますが、

・サーバ構築するのめんどい
・ただ動くのみたいだけだからひとまず開発環境として作ってみる
・動いた
・プログラム問題ないな、じゃあ捨てます

とかしたいときにいいと思います。

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

AWS ECS vs. Kubernetes 〜コンテナオーケストレーションプラットフォームの比較〜

image.png

はじめに

コンテナを使用することで大きな利点を与えてくれる一方で、その環境を増やすことは、それと同時に難しい選択を迫られるということでもあります。私たちは、自分たちの状況に最適なオーケストレーションツールは何か、そしてシステムをどのように監視するかについて考えていく必要があります。 Dockerはコンテナーランタイムの標準ですが、コンテナーオーケストレーションツールから選択するオプションは複数あります。これらのリーダーは、AmazonのElastic ContainerServiceとCNCFのKubernetesです。実際、調査によると、企業の83%がコンテナオーケストレーションソリューションとしてKubernetesを使用しているのに対し、ECSは24%にとどまっています。

この記事では、MetricFireのコミュニティメンバーがKubernetesとAWSECSの両方について解説していきます。モニタリングの観点から、GrafanaとPrometheusはKubernetesに大きく利点があり、Kubernetesのサポートが多数利用可能ですが、AWSECSを使用することについてに議論は続けられるべきです。無料トライアルでプラットフォームにアクセスすると、両方のオーケストレーションプラットフォームから送信されたメトリックを試してみることができます。

この記事では、ECSとKubernetesを比較対照して、ユーザーがどちらを使用するかを決定できる判断材料を提供していきます。

コンテナオーケストレーションにKubernetesを使用する利点

image.png

1. ロックインなしの完全にオープンなソース

Kubernetesは、コンテナオーケストレーション戦略を再構築することなく、オンプレミスまたはクラウドの両方で使用できます。 ソフトウェアは完全にオープンソースであり、従来のソフトウェアライセンス料を負担することなく再展開できます。 パブリッククラウドとプライベートクラウドの両方でKubernetesクラスターを実行して、パブリックリソースとプライベートリソースの間に仮想化のレイヤーを提供することもできます。

2. 強力な柔軟性

さらに、収益を生み出す重要なアプリケーションがある場合、Kubernetesは、効率とスケーラビリティの必要性を犠牲にすることなく、高可用性の要件を満たすための優れた方法です。 Kubernetesを使用すると、ワークロードのスケーリング方法をきめ細かく制御できます。 これにより、より強力なプラットフォームに切り替える必要がある場合に、ECSまたはその他のコンテナサービスによるベンダーロックインを回避できます。

3. 高可用性:

Kubernetesは、アプリケーションとインフラストラクチャの両方の可用性に取り組むように設計されているため、本番環境にコンテナをデプロイする際に不可欠です。

  • ヘルスチェックと自己修復: Kubernetesは、ノードとコンテナーのヘルスを常にチェックすることで、コンテナー化されたアプリケーションを障害から保護します。 Kubernetesは、自己修復と自動置換も提供します。エラーが原因でコンテナまたはポッドがクラッシュした場合、Kubernetesが対応します。

  • トラフィックルーティングと負荷分散: トラフィックルーティングは適切なコンテナにリクエストを送信します。 Kubernetesには、複数のポッドに負荷を分散するためのロードバランサーも組み込まれているため、停止、ピーク時または偶発的なトラフィック、バッチ処理に対応するために、リソースをすばやく(再)分散できます。 外部ロードバランサーを使用することも可能です。

4. ワークロードのスケーラビリティ:

Kubernetesは、インフラストラクチャリソースの使用において効率的であることが知られており、スケーリングの目的でいくつかの便利な機能を提供します。

  • 水平インフラストラクチャのスケーリング:Kubernetesは個々のサーバーレベルで動作し、水平スケーリングを実装します。 新しいサーバーは簡単に追加または削除できます。

  • 自動スケーリング:自動スケーリングを使用すると、CPU使用率またはその他のアプリケーション提供のメトリックに基づいて、実行中のコンテナーの数を自動的に変更できます。

  • 手動スケーリング:コマンドまたはインターフェースを使用して、実行中のコンテナーの数を手動でスケーリングできます。

  • レプリケーションコントローラー:レプリケーションコントローラーは、クラスターで指定された数の同等のポッド(コンテナーのグループ)が実行されていることを確認します。 ポッドが多すぎる場合、ReplicationControllerは余分なポッドを終了します。 数が少なすぎると、より多くのポッドが開始されます。

5. 展開用に設計:

コンテナ化の主な利点の1つは、ソフトウェアの構築、テスト、およびリリースのプロセスを高速化できることです。 Kubernetesはデプロイ用に設計されており、いくつかの便利な機能を提供します。

  • 自動ロールアウトとロールバック: アプリの新しいバージョンをロールアウトしたり、構成を更新したりしますか? Kubernetesは、ロールアウト中にコンテナの状態を監視しながら、ダウンタイムなしでそれを処理します。失敗した場合、自動的にロールバックします。

  • カナリアデプロイメント: カナリアデプロイメントを使用すると、新しいデプロイメントをスケールアップすると同時に以前のデプロイメントをスケールダウンする前に、以前のバージョンと並行して新しいデプロイメントを本番環境でテストできます。

  • プログラミング言語とフレームワークのサポート:Kubernetesは、Java、Go、.Netなどの幅広いプログラミング言語とフレームワークをサポートしています。Kubernetesは、追加のプログラミング言語とフレームワークを維持している開発コミュニティからも多くのサポートを受けています。アプリケーションをコンテナで実行できる場合は、Kubernetesで正常に実行されるはずです。

6. サービスの発見可能性:

すべてのサービスが相互に通信する予測可能な方法を持っていることが重要です。ただし、Kubernetes内では、コンテナが何度も作成および破棄されるため、特定のサービスが特定の場所に永続的に存在しない場合があります。これは従来、各コンテナの場所を追跡するために、何らかのサービスレジストリを作成するか、アプリケーションロジックに適合させる必要があることを意味していました。 Kubernetesには、ポッドをグループ化し、サービス検出を簡素化するネイティブサービスの概念があります。 Kubernetesは各ポッドにIPアドレスを提供し、ポッドの各セットにDNS名を割り当ててから、セット内のポッドへのトラフィックを負荷分散します。これにより、サービス検出をコンテナレベルから抽象化できる環境が作成されます。

7. アプリケーションデプロイメントの一部としてのネットワークポリシー:

デフォルトでは、Kubernetesのすべてのポッドが相互に通信できます。クラスター管理者はネットワークポリシーを宣言的に適用でき、これらのポリシーは特定のポッドまたは名前空間へのアクセスを制限できます。基本的なネットワークポリシーの制限は、特定のポッドの出力および入力機能にも指定するポッドまたは名前空間の名前を指定するだけで適用できます。

8. 長期的なソリューション:

Kubernetesの台頭を考えると、多くのクラウドプロバイダーは、R&Dの焦点をレガシーオプションよりもマネージドKubernetesサービスの拡張にシフトしています。長期的なIT戦略を策定する際は、Kubernetesを検討してください。

9. 進行中の開発:

最初のリリース後すぐに、Kubernetesは非常に大規模で活発なコミュニティを獲得しました。現在、フォーチュン500企業で働くエンジニアから個々の開発者やエンジニアまで、約2000人のGithubの貢献者がおり、新しい機能が絶えずリリースされています。大規模で多様なユーザーコミュニティも、質問に答え、コラボレーションを促進するために介入します。

10. 活気のあるコミュニティ:

Kubernetesは、CNCFのような主要な企業や機関の支援を受けている、幅広いモジュール式のオープンソース拡張機能を備えたアクティブなコミュニティです。数千人の開発者と多くの大企業がKubernetesに貢献しており、最新のソフトウェアインフラストラクチャに最適なプラットフォームとなっています。これはまた、コミュニティが積極的に協力しているだけでなく、現代の問題を簡単に解決するための機能を構築していることも意味します。

AWS Elastic ContainerServiceを使用する利点

image.png

1. 従来のECS:

Amazon EC2コンピューティングを搭載-クラウド上でDockerコンテナーを簡単に実行する方法として、2015年にリリースされました。 従来のECSでは、コンテナーのEC2コンピューティングオプションを基本的に制御できます。 この柔軟性は、ワークロードを実行するインスタンスタイプを選択できることを意味します。 また、それらのEC2インスタンスでのアクティビティのモニタリングとロギングに使用される他のAWSサービスに接続します。

2. Fargate ECS:

一方Fargate ECSでは、基盤となるEC2コンピューティングを管理せずにコンテナーを実行する方法として、2017年にリリースされました。 代わりに、Fargateは必要なCPUとメモリの要件を自動的に計算します。 通常、Fargateは、ワークロードをすばやく稼働させる必要があり、基礎となる計算オプションの計算や把握に煩わされたくない場合に適したオプションです。

3. 小さなワークロードに適している:

大幅な拡張が予想されない小さなワークロードを実行する予定の場合は、ECSが適しています。 タスク定義は、登録と理解が容易です。

4. それほど複雑でないアプリケーションアーキテクチャ:

少数のマイクロサービスのみで構成され、多かれ少なかれ独立して動作するアプリケーションがあり、アーキテクチャ全体がそれほど複雑ではない場合(つまり、外部依存関係が多くないか、可動部分が多すぎる) 、それならECSは最初から良い候補です。

5. より簡単な学習曲線:

Kubernetesには急な学習曲線があります。これが、従来のKubeadmやKOPSフレーバーと比較してHostedkubernetesの提供が成功する主な理由です。 さらに、ECSファーゲートのような製品を使用すると、基盤となるホストについて心配する必要さえありません。 AWSがすべてを処理します。

6. より簡単なモニタリングとロギング:

ECSはAWSCloudwatchのモニタリングとロギングとシームレスに統合されます。 ECSでコンテナワークロードを実行している場合、コンテナワークロードの可視性を構成するために追加の作業は必要ありません。

Kubernetes採用への課題

Kubernetesランドスケープを把握する:

エンドツーエンドソリューションを提供するには、他のさまざまなテクノロジーやサービスを含める必要があるため、Kubernetesランドスケープを理解することはそれを開始するための重要な要素です。しかし、各補足技術の状態は大きく異なります。たとえば、一部のソリューションはUnixが最高の時代にまでさかのぼりますが、他のソリューションは1年未満であり、商業的な採​​用とサポートが少ないものです。したがって、実装に安全に含めることができるものを理解することに加えて、各コンポーネントがより大きなソリューションにどのように適合するかを理解する必要があります。これに関する情報やドキュメントはたくさんありますが、散在していて蒸留するのは困難です。その結果、特定の仕事に最適なソリューションを見つけることは困難です。使用するソリューションを特定した後でも、これらをサービスとして提供し、継続的に管理する方法について、しっかりとした計画が必要です。

機能とプロジェクトの違いを理解する:

課題はそれだけではありません。プロジェクトのライフサイクルを管理する方法に関するアドバイスを見つけることは役立ちますが、Kubernetes機能とKubernetesコミュニティプロジェクトを区別する際に発生する混乱を解決することはできません。 Kubernetesのようなオープンソーステクノロジーの優れている点は、ユーザーのコミュニティが革新的な用途を作成して共有できることです。しかし、その同じ利点はまた、水を濁らせる可能性があります。特別利益団体はコアKubernetesに追加される機能を開発できますが、他のスタンドアロンプ​​ロジェクトはコアの外にあります。個々の開発者またはベンダーによって提供されたプロジェクトは、プライムタイムの準備ができていない可能性があります。実際、多くは開発のさまざまな段階にあります。 (注:GitHubにない場合は、Kubernetesの公式機能ではありません。)

Kubernetes以上の知識を身に付ける:

この混乱はすべて、ソリューションの提供の複雑さによって悪化します。 Kubernetes自体は洗練されています。しかし、組織は、分散データストアをサービスとして提供するなど、他の複雑なソリューションも提供したいと考えています。これらすべてのサービスを組み合わせて管理することで、課題をさらに拡大できます。 Kubernetesのエキスパートである必要があるだけでなく、エンドツーエンドサービスの一部として提供するすべてのことに熟練している必要があります。

Kubernetesの管理は困難

Kubernetesを使用して運用することと、管理することは別です。 Kubernetesで得られるのはKubernetesだけなので、Kubernetesの管理は主に手動の演習です。プラットフォームにはそれを実行するための機能が付属していないため、Kubernetes自体にリソースを運用する方法を理解する必要があります。簡単なことではありません。
企業のニーズに対応する場合は、Kubernetesをセキュリティで強化し、既存のインフラストラクチャと統合する必要があります。アップグレード、パッチ、その他のインフラストラクチャ固有の管理タスクの処理に加えて、Kubernetesを効果的に運用およびスケーリングするには、適切な知識、専門知識、プロセス、およびツールが必要です。 Kubernetesをさまざまな業種やさまざまなユーザーにサービスとして提供するには、次の管理上の課題を解決する必要があることを考慮してください。

  • 複数のKubernetesクラスターを管理するのは困難: 複数のKubernetesクラスターを管理すると、内部チームにさらにプレッシャーがかかり、多大な時間とリソースの投資が必要になります。

  • Kubernetesの監視とトラブルシューティングは困難: 他の複雑なシステムと同様に、問題が発生する可能性があります。 Kubernetesを使用すると、大規模なテストができず、問題が発生した場合の修正が難しく、アップグレードを自動化できません。

  • 大規模なサポートが難しい: 企業組織にとって、規模は非常に重要です。 しかし、すぐに使用できるKubernetesでスケールをサポートするのは困難です。 複数のクラスターのバージョン管理は時間がかかり、困難であり、特別なリソース計画が必要です。 さらに、プラットフォームを真にスケーリングするには、構成スクリプトを手動で実行する必要があります。

まとめ

Kubernetesは、いくつかの欠点はありますが、コンテナオーケストレーションの競争で依然としてリードしていることは、今では非常に明確になっているはずです。正直に言ってしまえば、Kubernetesが明らかに勝者です。 Kubernetesはコンテナオーケストレーションの事実上の標準になり、大小の組織はそれを採用するためにリソースを多額に投資しています。

Amazon ECSは良いオプションですが、多くの場合で不十分です。 適切なツールチェーンを使用すると、Kubernetesの採用はシームレスであるだけでなく、真にクラウドネイティブになるため、将来にも役立ちます。 Kuberentesクラスターとデプロイされたワークロードの可観測性を非常に簡単にするソリューションを数多く提供しています。 監視とログ記録のすべてのニーズについて、必要な場合はお気軽にMetricFireにご連絡ください。喜んでお手伝いさせていただきます。 ビデオ通話でのデモを予約するか、無料トライアルにサインオンしてください。カスタマーサポートのチャットボックスから直接お話しいただけます。

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

Alexaでサーバーやデータベースの起動・停止制御をしたい

はじめに

こんにちは。NTTドコモの矢吹です。
私のチームでは、ドコモの大規模データをストリーム処理しており、AWSを利用してシステムを開発しています。そのため、開発環境だけでも結構な費用になります。しかし、節約のためにサーバーやデータベースをこまめに停止したり起動するのは面倒くさいし、つい忘れてしまいがちです。そこで、Alexaを使って「アレクサ、開発環境でデータベース停止しておいて」という風に制御できれば何かと便利ですよね。ついでに、これで節約できれば「最近費用が予想より大幅に上振れしてる、アカン・・」と頭を抱えている上司に恩を売ることもできます。
そこで、今回はLambdaやAlexa Skills Kitなど触ったことのない素人が勉強がてら、AlexaでAWSリソースの起動・停止制御を行うスキル開発に取り組んでみます。
尚、「平日の勤務開始時間に起動して終了時間に停止するcronを書けばいいじゃん」「サーバーレスで開発しろよ」などの意見はごもっともですが、ここでは受け付けないこととします。

目標

「アレクサ、開発環境でWebサーバー起動して」
「アレクサ、開発環境でデータベース停止しておいて」
といった感じで、音声だけでAWSリソースの起動・停止制御を行う。

準備するもの

対象とする人

  • Alexaスキルの開発の流れをざっくり掴みたい人
  • AlexaでAWSのリソース制御 (EC2やRDSの起動・停止)を行いたい人

参考にした資料

下記の資料を大変参考にさせていただきました。

実装

Alexaスキルを作るには、音声入力のインターフェースの作成とリクエスト内容に応じて処理を行うバックエンドの実装が必要です。インターフェースの作成はAlexa開発者コンソールでWeb画面を操作しながら行います。バックエンドはPythonで実装し、Lambdaで実行するようにしたいと思います。

インターフェースの作成

まずは、Alexa開発者コンソールでインターフェースを作成していきます。今回は日本語のスキルでAWSアカウントのLambdaでホスティングしたいため、下図のように選択し、スキルを作成します。
スクリーンショット 2020-12-14 23.33.22.png

テンプレートはスクラッチを選択します。
スクリーンショット 2020-12-14 23.42.11.png

以上で基本的なテンプレートが作成されるので、呼び出し名やIntent, Slotなどの設定をしていきます。

Invocation Name

Alexaが作成したスキルに応答できるようにInvocation Name(呼び出し時のキーワード)を設定します。「開発環境でWebサーバー止めて」のように呼び出したいため、「開発環境」をキーワードとして設定しました。
スクリーンショット 2020-12-14 23.46.49.png

Intent

次にIntentを作成します。ドキュメントでは、Intentは下記のように説明されています。
image.png

少しわかりづらいですが、自分なりに触ったりして解釈した結果、ある目的の音声リクエストを認識するための機能とすると腑に落ちました。Intentが音声リクエストを正しく認識できるように発話サンプルを定義してあげる必要があります。「Webサーバー止めて」「データベース起動して」のような発話が考えられますが、全て網羅するのは大変です。なので、
{resource}を{action}して
のように発話サンプルに引数を与えることができると何かと便利です。この引数のことをSlotと呼びます。今回は下図のようにResouceControlという名前のIntentを作成しました。発話サンプルは考えられるバリエーションをたくさん作ってあげると認識率が上がります。
スクリーンショット 2020-12-15 9.46.20.png

Slot

次に先ほど説明したSlotを作成します。まずはSlot Typeのタブに移動し、以下のようにresourceを定義します。後のバックエンドの実装でこの値とリソースIDを紐付けて制御できるようにします。
スクリーンショット 2020-12-15 10.15.18.png

続いてactionを定義します。今回はリソースの起動と停止を行いたいので以下のようにしました。類義語も登録するとより汎用性が高くなります。
スクリーンショット 2020-12-15 10.16.04.png

そして、再びIntentのタブに戻り、今定義したSlot TypeとIntent Slotを紐付けます。
pic7.png

これでインターフェイスの実装は一通り終わりました。ページ上部にある、Save Model と Build Model のボタンを押してモデルをの保存とビルドを行います。非常に簡単ですね。

バックエンドの実装

次にバックエンドの実装を行います。今回はせっかくなので先日発表されたLambdaのコンテナイメージサポート を試してみたいと思います。
フォルダ構成は下記のようになります。

alexa/
├── Dockerfile
├── app.py
└── resource.json

AWSが提供するLambda用のPythonイメージを使用します。
Alexaスキル開発用のライブラリ(ask-sdk)のみ追加でインストールします。また、起動後はhandlerが呼ばれるようにします。

Dockerfile.
FROM public.ecr.aws/lambda/python:3.8

RUN pip3 install --upgrade pip && \
    pip3 install ask-sdk==1.15.0

COPY  app.py resource.json ./

CMD ["app.handler"]

制御したいリソースの名前とIDを下記のように記載します。各リソースのIDはAWSコンソール等などから確認して入力してください。このファイルはロジック部分で読み込んで使用します。

resource.json
{
  "ウェブサーバー": "your_web_server_id" ,
  "api サーバー": "your_api_server_id" ,
  "データベース": "your_db_cluster_id"
}

次にロジック部分です。公式ドキュメント のコードをコピペしたものがベースとなっています。実装の流れとしては、LaunchRequest(呼び出し名のみのリクエスト)やIntentRequest(先ほど定義したカスタムIntentや組み込みのCancelAndStopIntentなど、Intent付きのリクエスト)、SessionEndedRequest(会話終了のリクエスト)などが呼ばれた際に行う処理やアレクサに喋らせる内容などを実装していきます。

app.py
import json
from ask_sdk_core.dispatch_components import AbstractRequestHandler
from ask_sdk_core.dispatch_components import AbstractExceptionHandler
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_core.skill_builder import SkillBuilder
from ask_sdk_core.utils import get_slot_value_v2, is_intent_name, is_request_type
from ask_sdk_model import Response
from ask_sdk_model.ui import SimpleCard
import boto3


sb = SkillBuilder()


def get_resource_id(resource_name):
    with open('resource.json') as f:
        resource_list = json.load(f)
    return resource_list[resource_name]


class LaunchRequestHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return is_request_type('LaunchRequest')(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        speech_text = 'どのAWSリソースを起動/停止しますか?'

        handler_input.response_builder.speak(speech_text).set_card(
            SimpleCard('AWS', speech_text)).set_should_end_session(
            False)
        return handler_input.response_builder.response


class ResourceControlHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):  # type: (HandlerInput) -> bool
        return is_intent_name('ResourceControl')(handler_input)

    def handle(self, handler_input):  # type: (HandlerInput) -> Union[None, Response]
        action = get_slot_value_v2(handler_input=handler_input, slot_name='action').value
        resource_name = get_slot_value_v2(handler_input=handler_input, slot_name='resource').value
        print(f'action: {action}')
        print(f'resource_name: {resource_name}')

        start_message = f'{resource_name}を起動しました'
        already_started_message = f'{resource_name}はすでに起動しています'
        stop_message = f'{resource_name}を停止しました'
        already_stopped_message = f'{resource_name}はすでに停止しています'
        end_session = True

        if resource_name in ['ウェブサーバー', 'api サーバー']:
            ec2 = boto3.client('ec2')
            ec2_status = ec2.describe_instances(InstanceIds=[get_resource_id(resource_name)])\
                ["Reservations"][0]["Instances"][0]['State']['Name']
            if action == '起動':
                if ec2_status == 'running' or ec2_status == 'pending':
                    speech_text = already_started_message
                else:
                    ec2.start_instances(InstanceIds=[get_resource_id(resource_name)])
                    speech_text = start_message
            elif action == '停止':
                if ec2_status == 'stopping' or ec2_status == 'stopped':
                    speech_text = already_stopped_message
                else:
                    ec2.stop_instances(InstanceIds=[get_resource_id(resource_name)])
                    speech_text = stop_message
            else:
                speech_text = f'{resource_name}をどうしますか?もう一回言ってください'
                end_session = False
        elif resource_name == 'データベース':
            rds = boto3.client('rds')
            if action == '起動':
                print('Start RDS')
                try:
                    rds.start_db_cluster(DBClusterIdentifier=get_resource_id('データベース'))
                    speech_text = start_message
                except Exception as e:
                    print(e)
                    speech_text = '起動に失敗しました。データベースはすでに起動しているかもしれません。'
            elif action == '停止':
                try:
                    rds.stop_db_cluster(DBClusterIdentifier=get_resource_id('データベース'))
                    speech_text = stop_message
                except Exception as e:
                    print(e)
                    speech_text = '停止に失敗しました。データベースはすでに停止しているかもしれません。'
            else:
                speech_text = f'{resource_name}をどうしますか?もう一回言ってください'
                end_session = False
        else:
            speech_text = 'チョットナニイッテルカワカリマセン。'
            end_session = False

        handler_input.response_builder.speak(speech_text).set_card(
            SimpleCard('Control AWS Resource', speech_text)).set_should_end_session(end_session)
        return handler_input.response_builder.response


class HelpIntentHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return is_intent_name('AMAZON.HelpIntent')(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        speech_text = '例えば、web サーバーを起動して、と言って見てください'

        handler_input.response_builder.speak(speech_text).ask(speech_text).set_card(
            SimpleCard('Control AWS Resource', speech_text))
        return handler_input.response_builder.response


class CancelAndStopIntentHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return is_intent_name('AMAZON.CancelIntent')(handler_input) or is_intent_name('AMAZON.StopIntent')(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        speech_text = 'さようなら'

        handler_input.response_builder.speak(speech_text).set_card(
            SimpleCard('Control AWS Resource', speech_text))
        return handler_input.response_builder.response


class SessionEndedRequestHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return is_request_type('SessionEndedRequest')(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        # クリーンアップロジックをここに追加します

        return handler_input.response_builder.response


class AllExceptionHandler(AbstractExceptionHandler):

    def can_handle(self, handler_input, exception):
        # type: (HandlerInput, Exception) -> bool
        return True

    def handle(self, handler_input, exception):
        # type: (HandlerInput, Exception) -> Response
        # CloudWatch Logsに例外を記録する
        print(exception)

        speech = 'すみません、わかりませんでした。もう一度言ってください。'
        handler_input.response_builder.speak(speech).ask(speech)
        return handler_input.response_builder.response


sb.add_request_handler(LaunchRequestHandler())
sb.add_request_handler(ResourceControlHandler())
sb.add_request_handler(HelpIntentHandler())
sb.add_request_handler(CancelAndStopIntentHandler())
sb.add_request_handler(SessionEndedRequestHandler())
sb.add_exception_handler(AllExceptionHandler())

handler = sb.lambda_handler()

コードを見てわかる通り、ResourceControlのIntentを処理するためのクラス(ResourceControlHandler)の実装がメインとなります(その他はほとんどコピペ)。このクラスでは、リクエストのactionとresourceのSlot値を取り出し、値に応じて処理を変えるようにしています。例えば、resourceがウェブサーバーやAPIサーバーの場合、ec2クライアントを呼び出してactionの値に応じて起動や停止の操作をします。
また、喋らせる内容はspeech_textに設定します。正常終了したため会話を終了したい、もしくはリクエストがおかしいので聞き返して会話を継続したい、などはend_sessionの値で制御します。最後にspeech_textやend_sessionなどの値でレスポンス内容を組み立ててアレクサに喋らせるための値を返します。こちらも簡単ですね。
実装が完了したら、コンテナイメージをビルドしてECRにプッシュします。(割愛)

Lambdaの設定

次にLambda関数を作っていきます。今回はランタイムとしてコンテナを利用するため、コンテナイメージを選択し、関数名と先ほどECRにプッシュしたイメージのURIを指定します。アクセス権限はLambdaがEC2やRDSなどのリソースを操作できるように適切なIAMロールを作成し、それを使用するようにします。
pic9.png

関数作成後、LambdaのARNをコピーして再びAlexa開発者コンソールに戻り、下記のようにエンドポイントの設定を行います。
pic10.png

Lambdaの設定画面に戻り、下記のようにトリガーを設定します。
pic11.png

以上で実装は完了です。Alexa開発者コンソールに戻り、作成したスキルの動作確認をしましょう。

動作確認

テキスト入力による動作確認

テストタブに移動し、下記のようにスキルの動作確認を行うことができます。正しく動作していそうですね。AWSコンソールで確認したところ、きちんとデータベースは起動されていました。

スクリーンショット 2020-12-16 12.58.40.png

音声入力による動作確認

「開発環境でAPIサーバー停止して」と言ってみます。
スクリーンショット 2020-12-16 13.13.10.png

・・・自分が滑舌悪いこと忘れてました。

対象とする人(改)

  • Alexaスキルの開発の流れをざっくり掴みたい人
  • AlexaでAWSのリソース制御 (EC2やRDSの起動・停止)を行いたい人
  • 滑舌が良い人

おわりに

Alexaスキルの開発を一通り体験してみましたが、IntentやSlotなどの概念さえを理解できれば意外と簡単に作れるんだなというのが作ってみての所感です。また、滑舌が悪い人には音声インターフェースは扱いづらいなということを改めて実感できました。ここまで作っといてアレですが、このスキルは使わずにシェルスクリプトを書いて実行するようにしようかなと思います。

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

【IT初心者向け】AWSとは?を解説【AWSの特徴をイメージ】

はじめに

IT初心者「AWSって何なの?どんなことができるのか分からない。。。AWS使えるとどんな仕事ができるんだろう??」

このような悩み、疑問に答えます。

本記事の内容

・「AWSとは?」を解説
・AWS活用事例

何となくAWSのイメージをつかみたいという方向けにまとめています。

記事の信頼性

筆者はAWS経験5年程度です。AWS資格は5冠達成しました。
現在は大規模ECサイトのAWS運用を任されるようになっています。

「AWSとは?」を解説

「AWSとは」でググると1件目に表示されるAWS公式ページに上記の悩みを解決できそうなことが書いてありました。
AWSとは(公式ページ)

image.png

AWSはスタートアップ、大企業、政府機関まで業種・業態問わず広く採用されているクラウドプラットフォームです。
※クラウドプラットフォーム=アプリケーション実行やデータ保存ができる基盤を、WEB上で展開しているもの
※イノベーション=ビジネスや人々のライフスタイルに革新を起こすこと

本記事ではこのAWS公式ページを抜粋しながら解説していきます。

AWS利用イメージ

・パソコンのブラウザから使いたいシステムをクリックすると使える(従量課金=使った分だけの課金)
image.png

・AWSには多くのサービスがあり、サービスを組み合わせてシステムを実現する
(よくビルディングブロックと呼ばれる)
image.png

AWSのサービス・機能数

image.png

豊富な機能がある理由はユーザの要望を反映していった結果とのことです。
実際に使ってて「今までできなかったことができるようになってる!!」というのはよくありますね。

AWSは他社のクラウドプロバイダと比較して最もサービス数が多いと謳っています。
AWSとよく比較されるMicrosoft Azure、Google Cloud Platform(GCP)もこの考え方は同様なので、3社の機能的な違いはほぼ無くなっていくと言われています。

ベンダー(パートナー)の質が高い

image.png

頼れるパートナーが多いというのはAWS利用開始の際に大きなメリットになります。
AWS導入をベンダーに任せるのであれば、まずは日本の APN プレミアパートナーから検討してみるのが良いでしょう。

セキュリティに不安はない

image.png

今はそうでもないかもしれませんが、昔はクラウドはセキュリティが不安だと言われていましたので、セキュリティ水準の高さについて触れておきます。
AWSは最も高いセキュリティを要求されると思われる軍隊や銀行の要件を満たす設計なので、
一般的な企業でセキュリティ関連で困ることはありません。
※政治的な理由(「怖いのでクラウドは使わない方針でいく!」などの謎文化含む)に困らされることはありました。

AWSのクラウド業界での立ち位置

image.png

・ガートナーのマジッククワドラントについて
ガートナーはIT分野のトレンドを発信している会社です。
マジッククワドラントは「特定市場におけるテクノロジ・プロバイダーを位置付け」したものです。
AWSは「リーダー」ですが、これはガートナーから「ビジョンを実行できており、将来のポジションを確立しています。」という評価を受けたことになります。

参考:マジッククワドラントについて

AWS活用事例

ここまでで「AWSとは」の部分を解説してきましたが、これだけだとまだ具体的なイメージがつかめないと思います。
ここからは活用事例の紹介で、事例を見ていくとAWSで実現されていることやそのメリットが分かってきます。
AWS公式ページではスタートアップからエンタープライズ、公共事業まで様々な業界での事例が紹介されています。
その中でも面白いと思った事例を載せています。

image.png

Formula One インサイト

F1 がデータ(スピード、位置、ハンドル角度など)を収集し、活用して意思決定するのにAWSのAIサービスを利用しています。

image.png

「カーパフォーマンススコア」の算出でファン向けにデータが活用されています。リアルタイムに秒間110万件以上のデータを処理しているそうです。
image.png

参考:F1、人工知能を用いて車両開発状況やマシンパフォーマンスをリアルタイムで可視化

エンタープライズ

Expedia

・旅行に関するオンライン予約を扱うウェブサイト、アプリ運営
・営業利益: 9.03億アメリカ合衆国ドル

ミッションクリティカル(障害や誤作動などが許されない)なアプリがAWSへ移行されています。
エンタープライズ(大企業)でミッションクリティカルなアプリがAWSで実現されているということからもAWSの高い信頼性を理解できると思います。
導入効果で大きかったのはアプリケーションリリース速度の向上で20分で行われるようになったと言っています。

Intuit

米国で圧倒的シェアのクラウド会計ソフト会社
収益: 67.84億アメリカ合衆国ドル

image.png

・オートスケールで拡張性を実現
・税制の変化への対応を迅速に実施(CI/CD)
・データレイクはセキュリティ、コンプライアンス性の高い情報を扱う(HIPAA)
・機械学習は必要なリソースの予測に利用

スタートアップ

airbnb

宿泊施設・民宿を貸し出す人向けのウェブサイトを運営。
ある時から成長の速度が加速度的に増加したそうです。
image.png

それでも5人でシステム運用できているのはAWSの拡張性を利用できているおかげだと言っています。

mapbox

カスタマイズ自在・リアルタイムな地図情報が表示可能な開発者向け地図システムを提供しています。
※Yahoo MAPが2019年にMapboxを使用開始したようです。
https://map.yahoo.co.jp/blog/archives/20191015_map_mapbox.html

システムイメージはこんな感じです。
image.png

Lyft

収益: 36.16億アメリカ合衆国ドル

配車サービスを提供しています。
※日本ではUberがおすすめだがアメリカではLyftが断然安いとのことです。
参考:Uber(ウーバー)とLyft(リフト)の違いは?アメリカなら、Lyftが断然お得でおすすめ!

どこのサービスもそうなのかもしれませんが、ユーザ数は急増。
image.png

それに対応する為にオートスケール導入やデータベース変更などを実施していったそうです。
image.png

最終的には様々なAWSサービスを利用するに至ったとのこと。
image.png

Nextdoor

近隣地域向けのプライベートソーシャルネットワーク
ご近所SNSと呼ばれています。

日本版も2019年あたりにローンチ
参考:地域SNSのネクストドア、巨大広告ビジネスをひそかに構築:検証した住所情報を駆使して

こちらもやはり急増案件です。
image.png

公共

FINRA(金融業界規制当局)

Financial Industry Regulatory Authorityの頭文字をとったもので、投資家保護や証券取引の透明性の確保、不正行為の摘発などを目的に、米国において証券会社などの行動を監視・規制する組織。政府機関ではなく非営利の民間協会として運営されている。
参考:証券用語解説集

20ペタバイトを超える数兆のレコードデータの監視しているといいます。
AWSを利用することで下記の様なことを実現しています。
・Secure and Auditable:安全性と監査性
・Scale & Elasticity:拡張性
・Years Ahead- Constantly Innovating:先を見据えて-常に革新し続ける
image.png

米国食品医薬品局 (FDA)

連邦食品・医薬品・化粧品法を根拠とし、医療品規制、食の安全を責務とする団体です。
※アメリカ合衆国保健福祉省(Department of Health and Human Services, HHS)配下の政府機関
AWS利用によりこれまで5か年計画だったIT戦略が6か月で解決できるようになったと言っているのは印象的でした。
毎年数百万の有害事象(薬物を投与された患者に生じた好ましくない徴候)レポートを処理していて、AWSによって様々な課題が解決されています。
例えば、管理すべき大量の情報を紙からデータ化してWebで検索できるようにした事例が紹介されていました。
image.png

さいごに

AWSの特徴(運用性、拡張性、信頼性、セキュリティ、コストにメリットがあること)をイメージ頂けたでしょうか?
AWS詳しいエンジニアはまだまだ少ない印象です。
本記事を読んだことでAWSに興味を持っていただける方増えたら嬉しいです。

※AWS興味持った方には下記の記事も参考になるかと思いましたので、参考にしてみてください。
【AWS初心者向け】AWS学習方法まとめ【15時間で達成できる】

AWSエンジニアとは?なるには?仕事・スキル・年収・将来性

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

1−1.WEB系エンジニアになるためのインプット(準備)

はじめに

メーカ系SIer企業で働いているのですが、少し物足りなさを感じております。
そこで副業の一環として、まずはWEB系エンジニアを目指すために「web系エンジニアになろう」(著:勝又健太)を読了しました。
まずは基礎を固めるためにインプットを行うが、併せて内容をアウトプットするために本記事を作成していく。

学習フロー

既にコンピュータサイエンスの基礎知識やクラウドサービスであるAWSの知見を有しているので、下記のように進める。

  1. Linux基礎
  2. JavaScript基礎
  3. RDBとSQL基礎
  4. GitとGitHub基礎
  5. Ruby基礎
  6. Ruby on Rails基礎

以降はポートフォリオ作成を進めたい。

教材

基本的には勤め先で提供されているUdemy for businessを活用する。
※勤め先には感謝です・・・

例えば、Linux基礎では下記教材で学びを進めていく。
【5日でできる】はじめての Linux 入門(LPIC Level1対応)
(講師:井上 博樹 (Hiroki Inoue))

環境

PCはMacBookAirを利用している。

  • MacBook Air (Retina, 13-inch, 2019)
  • プロセッサー:1.6 GHz デュアルコアIntel Core i5
  • メモリ:8 GB 2133 MHz LPDDR3

また、Linux環境が必要である場合、VirtualBoxの利用も考えられるが、PC(Macを利用)が重たくなるのでAWSのEC2を活用した。
例えば、Linux基礎で使う環境は下記とする。

  • リージョン:アジアパシフィック (東京)ap-northeast-1
  • AMI:Ubuntu Server 20.04 LTS (HVM), SSD Volume Type
  • インスタンスタイプ:t2.micro

最後に

まずはLinux基礎を固めるためにインプットを行い、次はLinux基礎のアウトプット記事を掲載する。

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

【AWS】CloudshellでAssumeRoleできるCredential設定を書く

本日発表の期待のサービス
会社ではマルチアカウント運用しているので、早速Assume/credenatial周りを確認してみた

TL;TD

  • .aws/credentialファイルを作成して、こんな感じに記述する
[test-assume]
role_arn = arn:aws:iam::xxxxxxxxxxx:role/test-asusme
credential_source = EcsContainer

AWSCLIのAsusmeRoleの仕組み

cloudshellでは?

環境変数を調べてみると中身がECSであることがわかる。

[cloudshell-user@ip-10-0-109-14 ~]$ export
declare -x AWS_CONTAINER_AUTHORIZATION_TOKEN="4KvGlp2UOabY3yGBXQqizTgQXXXXXXXXXXXXXXX"
declare -x AWS_CONTAINER_CREDENTIALS_FULL_URI="http://localhost:1338/latest/meta-data/container/security-credentials"
declare -x AWS_DEFAULT_REGION="ap-northeast-1"
declare -x AWS_EXECUTION_ENV="CloudShell"
declare -x AWS_REGION="ap-northeast-1"
declare -x HISTCONTROL="ignoredups"
declare -x HISTSIZE="1000"
declare -x HOME="/home/cloudshell-user"
declare -x HOSTNAME=""
declare -x LC_ALL="en_US.UTF-8"
declare -x LESSOPEN="||/usr/bin/lesspipe.sh %s"
declare -x LOGNAME="cloudshell-user"
declare -x LS_COLORS="rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:"
declare -x MAIL="/var/spool/mail/cloudshell-user"
declare -x NODE_PATH="/usr/lib/node_modules"
declare -x OLDPWD
declare -x PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/cloudshell-user/.local/bin:/home/cloudshell-user/bin"
declare -x PWD="/home/cloudshell-user"
declare -x SHLVL="1"
declare -x TERM="linux"
declare -x USER="cloudshell-user"
declare -x WSEP_TTY="true"
declare -x maxNumberOfSessions="10"
declare -x serverSocket="/aws/mde/.controller/mde.sock"

なので、credential_sourceをEcsContainer にしてみると、、見事成功

[test-assume]
role_arn = arnの:aws:iam::xxxxxxxxxxx:role/test-asusme
credential_source = EcsContainer
[cloudshell-user@ip-10-0-109-14 ~]$ aws sts get-caller-identity
{
    "UserId": "AIDA3XXXXXXXXXXXXX",
    "Account": "XXXXXXXXXXXX",
    "Arn": "arn:aws:iam::XXXXXXXXXXXXXX:user/willco21"
}
cloudshell-user@ip-10-0-109-14 ~]$ aws sts get-caller-identity --profile test-asssume
{
    "UserId": "AROAXXXXXXXXXXXXXX:botocore-session-1608088390",
    "Account": "XXXXXXXXXXXX",
    "Arn": "arn:aws:sts::XXXXXXXXXXXXXX:assumed-role/test-assume/botocore-session-1608088390"
}
(編集済み)

感想

  • 基本的に欲しい機能はそろっている感じです。Assumeroleは不安でしたが問題なくできてよかったです
  • 今後は自分のアクセスキーを発行してAWSAPIテストすることはなくなるかなー。安全になるし、嬉しいです
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【ズボラ】簡単にS3署名付きURLを発行したい【手抜き】

経緯

?「非公開S3バケットのデータを期間限定で公開したい」
俺「aws-cli叩いてpresign発行したら?」
?「コマンド叩きたくないでござる」
俺「・・・あ!マネコンから開いたら5分限定で見れたはず!」
?「ネ申!」
俺「・・コマンド叩いたら良いのに」

という流れがあったので、S3署名付きURL発行の手抜き版です。

前提

  • すでにS3バケットは非公開で作成済み
  • S3触れるIAMアカウントでデータも保存されている
  • そのアカウントの持ち主はコマンド嫌い

手順①:メニューから開く

S3のマネジメントコンソールにて当該ファイルを選んで、アクションメニューから「開く」を選択
03.PNG

手順②:見れた!

ファイルがもちろん開きますよね?
01.PNG

手順③:URL共有

この閲覧時のURLが実は5分限定で見れるという。。
04.PNG
(めちゃくちゃURL長い・・・汗)

aws cliで設定する場合

コンソールでコマンド叩いて設定するならaws cliなら以下のように叩きます。

aws s3 presign --exipres-in 秒数 s3://バケット名/データ名

例) ウルトラマンのように3分間戦うなら!
aws s3 presign --expires-in 180 s3://handson-bucketname-origin/test.txt
 ↓
https://handson-bucketname-origin.s3.amazonaws.com/test.txt?AWSAccessKeyId=xxxxxxxxxx&Signature=xxxxxxxxxxxxxxxxxxx%3D&Expires=16080xxxxx

一部マスクしてます。

備考

AWS CloudShellが2020/12/16に発表されましたが、そこならaws cli入っているので環境準備も楽だと思います。
05.PNG

参考

AWS CLI コマンドリファレンス
【小ネタ】AWS CLIでS3のPre-Signed URLを生成できるようになっていました!

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

CodeCommit + CodeBuildでAWS CloudFormation Guardを試してみた

はじめに

UL Systems Advent Calendar 2020 の18日目です。

CloudFormationテンプレートでEC2を作成した際、うっかりEBSの暗号化を忘れてしまい作り直しになった経験はあるのではないでしょうか。
暗号化以外にもプロジェクトルールで決まっている設定が漏れていた。なんてこともあるのではないでしょうか。
レビューでこれらの漏れを防ぐことも重要ですが、できれば自動でチェックする仕組みが欲しいですよね?

そんな時に使えるコマンドラインツールに、AWS CloudFormation Guardがあります。
AWSによる紹介ブログはこちら

どんなものなのか、実際に確かめてみました。

実行環境構築

ローカルマシンで実行するだけでは面白くないため、CodeCommit + CodeBuildで実行できる環境にしました。
ソースプロバイダにCodeCommitを、buildspec.ymlに以下の内容を指定していしたCodeBuildプロジェクトを作成。

buildspec.yml
version: 0.2

phases:
  install:
    commands:
       - wget https://github.com/aws-cloudformation/cloudformation-guard/releases/download/1.0.0/cfn-guard-linux-1.0.0.tar.gz
       - tar -xvf cfn-guard-linux-1.0.0.tar.gz
  build:
    commands:
       - ./cfn-guard-linux/cfn-guard --version
       - ./cfn-guard-linux/cfn-guard check -t ebs_volume_template.yml -r ebs_volume_template.ruleset

How it worksにあるサンプルコードではjsonですが、yamlに置き換えたebs_volume_template.ymlとebs_volume_template.rulesetをCodeCommitにpush。
ビルドを実行し、3件のエラーが発生することを確認。

result1.png

AWS::EC2::Instanceで試してみる

CFnテンプレートの作成

サンプルではEBSだが、実運用ではEC2 Instanceで作成することが多い。同様のチェックをEC2 Instanceでも試してみる。
適当なEC2を作成するCFnテンプレートを作成。

ec2_template.yml
Resources:
  NewInstance1:
    Type: AWS::EC2::Instance
    Properties:
      DisableApiTermination: true
      SubnetId: subnet-XXXXXXXXX
      ImageId: ami-00f045aed21a55240
      InstanceInitiatedShutdownBehavior: stop
      InstanceType: t2.micro
      BlockDeviceMappings:
        - DeviceName: /dev/sda1
          Ebs:
            VolumeSize: 500
            VolumeType: gp2
            Encrypted: false
        - DeviceName: /dev/sda2
          Ebs:
            VolumeSize: 50
            VolumeType: gp2
            Encrypted: false
        - DeviceName: /dev/sda3
          Ebs:
            VolumeSize: 10
            VolumeType: gp2
      KeyName: sample-key
      Monitoring: false
      SecurityGroupIds:
        - sg-XXXXXXXXXXXXXXXXX

ルールセットの作成

1からルールセットを記載するのは面倒なので、Rulegenコマンドでの自動生成を試してみた。

> cfn-guard rulegen sample-project/ec2_template.yml
AWS::EC2::Instance BlockDeviceMappings == [{"DeviceName":"/dev/sda1","Ebs":{"Encrypted":false,"VolumeSize":500,"VolumeType":"gp2"}},{"DeviceName":"/dev/sda2","Ebs":{"Encrypted":false,"VolumeSize":50,"VolumeType":"gp2"}},{"DeviceName":"/dev/sda3","Ebs":{"VolumeSize":10,"VolumeType":"gp2"}}]
AWS::EC2::Instance DisableApiTermination == true
AWS::EC2::Instance ImageId == ami-00f045aed21a55240
AWS::EC2::Instance InstanceInitiatedShutdownBehavior == stop
AWS::EC2::Instance InstanceType == t2.micro
AWS::EC2::Instance KeyName == sample-key
AWS::EC2::Instance Monitoring == false
AWS::EC2::Instance SecurityGroupIds == ["sg-XXXXXXXXXXXXXXXXX"]
AWS::EC2::Instance SubnetId == subnet-XXXXXXXXX

ImageIdなど単純な比較となる項目は自動生成でも問題ないが、BlockDeviceMappingsなどの多階層の項目の値をチェックには使えそうにないですね。
リスト、かつ他階層の項目のため、ワイルドカードを利用し、以下のようなルールセットを作成

ec2_template.ruleset
let encryption_flag = true

AWS::EC2::Instance BlockDeviceMappings.*.Ebs.Encrypted == %encryption_flag
AWS::EC2::Instance BlockDeviceMappings.*.Ebs.VolumeSize <= 100

実行結果の確認

チェック対象のテンプレート、ルールセットが作成できたため、CodeCommitのリポジトリへpush。
チェック対象のテンプレートが変更になったため、buildspec.ymlの一部を修正。

buildspec.yml
version: 0.2

phases:
  install:
    commands:
       - wget https://github.com/aws-cloudformation/cloudformation-guard/releases/download/1.0.0/cfn-guard-linux-1.0.0.tar.gz
       - tar -xvf cfn-guard-linux-1.0.0.tar.gz
  build:
    commands:
       - ./cfn-guard-linux/cfn-guard --version
       - ./cfn-guard-linux/cfn-guard check -t ec2_template.yml -r ec2_template.ruleset

実行したところ、暗号化チェックでエラーが発生していない。
(failed because [Encrypted]~のメッセージがなく、失敗ケースが1件になっている。)
result2.png

原因調査、対応

ドキュメントをもう一度確認してみたところ、Wildcard Semanticsに比較時の注意点書いてますね。
先ほどの記載ではいづれかがtrueならば結果OKとなってしまうため、全てfalseではないという条件へ修正。

ec2_template.ruleset
AWS::EC2::Instance BlockDeviceMappings.*.Ebs.Encrypted != false
AWS::EC2::Instance BlockDeviceMappings.*.Ebs.VolumeSize <= 100

実行し、Encryptedのエラーが発生することを確認。

result3.png

Encryptedが指定されていないデバイスもエラーにしたい

「DeviceName: /dev/sda3」は「Encrypted」が指定されていないが、先ほどの結果ではOKと見なされている。
暗号化されていないディスクが作成されてしまうため、未指定も結果NGとなることが望ましい。
項目がない場合結果NGとなるよう--strict-checksオプションを指定するようbuildspec.ymlを修正。

buildspec.yml
version: 0.2

phases:
  install:
    commands:
       - wget https://github.com/aws-cloudformation/cloudformation-guard/releases/download/1.0.0/cfn-guard-linux-1.0.0.tar.gz
       - tar -xvf cfn-guard-linux-1.0.0.tar.gz
  build:
    commands:
       - ./cfn-guard-linux/cfn-guard --version
       - ./cfn-guard-linux/cfn-guard check -t ec2_template.yml -r ec2_template.ruleset --strict-checks

ビルド実行を行い、望む結果が得られたことを確認
result4.png

まとめ

実際に使ってみないとわからないことが多いので、とりあえず試すことは重要ですね。

また、現状ではサンプルでは1テンプレート、1rulesetになってますが、実運用を考慮した際、テンプレートごとにrulesetを作成することは現実的ではありません。

どの単位でCFnテンプレートを作成するのかにもよるが、Strict Checks用のrulesetとそれ以外のrulesetの2つで運用するのがベターなのかな?
ここら辺は運用しながら自分なりの答えを見つけたいと思います。

最後に今回作成したテンプレート、ルールセット、buildspec.ymlは以下となります。

ec2_template.yml
Resources:
  NewInstance1:
    Type: AWS::EC2::Instance
    Properties:
      DisableApiTermination: true
      SubnetId: subnet-XXXXXXXXX
      ImageId: ami-00f045aed21a55240
      InstanceInitiatedShutdownBehavior: stop
      InstanceType: t2.micro
      BlockDeviceMappings:
        - DeviceName: /dev/sda1
          Ebs:
            VolumeSize: 500
            VolumeType: gp2
            Encrypted: false
        - DeviceName: /dev/sda2
          Ebs:
            VolumeSize: 50
            VolumeType: gp2
            Encrypted: false
        - DeviceName: /dev/sda3
          Ebs:
            VolumeSize: 10
            VolumeType: gp2
      KeyName: sample-key
      Monitoring: false
      SecurityGroupIds:
        - sg-XXXXXXXXXXXXXXXXX
ec2_template.ruleset
AWS::EC2::Instance BlockDeviceMappings.*.Ebs.Encrypted != false
AWS::EC2::Instance BlockDeviceMappings.*.Ebs.VolumeSize <= 100
buildspec.yml
version: 0.2

phases:
  install:
    commands:
       - wget https://github.com/aws-cloudformation/cloudformation-guard/releases/download/1.0.0/cfn-guard-linux-1.0.0.tar.gz
       - tar -xvf cfn-guard-linux-1.0.0.tar.gz
  build:
    commands:
       - ./cfn-guard-linux/cfn-guard --version
       - ./cfn-guard-linux/cfn-guard check -t ec2_template.yml -r ec2_template.ruleset --strict-checks
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS CloudShell を早速使ってみました

こんにちは。
本記事は、株式会社日立システムズのアドベントカレンダーの12/17の記事です。

2020/12/16に公開になった、AWS CloudShell を早速使ってみました。

https://aws.amazon.com/jp/blogs/aws/aws-cloudshell-command-line-access-to-aws-resources/

現時点で利用可能なリージョンは以下の通りです。

  • US East (N. Virginia)
  • US East (Ohio)
  • US West (Oregon)
  • Europe (Ireland)
  • Asia Pacific (Tokyo)

AWS マネージメントコンソールへログインし、上記のリージョンを指定すると、画面右上あたりに以下のようなアイコンが表示されていると思います。これが、AWS CloudShell を起動するためのボタンです。
image.png

このアイコンをクリックすると、ブラウザタブが新しく開き、初期設定が行われます。
そして、以下のようなガイドが表示されるので、適宜「Do not show again」にチェックを入れるなどして、Closeボタンをクリックします。
image.png

利用可能な状態になると以下の画像のような状態になります。
image.png

右上にある Actions はドロップダウンメニューになっています。
image.png
タブレイアウトを整えたり、ファイルのダウンロードやアップロードが行えます。
また、CloudShellの再起動や、ホームディレクトリの削除もここから行えます。
image.png
image.png

歯車アイコンでは、画面表示やコピペ時の注意表示をするかどうかの設定も行えますので、適宜設定をするとよいでしょう。
また、CloudShellの再起動や、ホームディレクトリの削除もここから行えます。
image.png

というわけで、つかってみた。

まずは基本的な使い方であると思われる、AWS CLI を確認、実行してみます。

AWS CLI のバージョンを確認する

[cloudshell-user@ip-10-0-15-222 ~]$ aws --version
aws-cli/2.0.58 Python/3.7.3 Linux/4.14.209-160.335.amzn2.x86_64 exec-env/CloudShell exe/x86_64.amzn.2

AWS CLI バージョン2系がインストールされていることがわかります。

AWS CLI を実行してみる

[cloudshell-user@ip-10-0-182-67 ~]$ aws ec2 describe-instances
{
    "Reservations": [
        {
            "Groups": [],
            "Instances": [
                {
                    "AmiLaunchIndex": 0,
                    "ImageId": "ami-00f045aed21a55240",
(以下省略)

この試してみた環境に存在しているEC2インスタンスの情報が取得できました。

その他、環境回りの確認やちょっと特殊?な使い方をしてみた。

カーネルなどの確認

まずは、 system-release を確認します。

[cloudshell-user@ip-10-0-92-23 ~]$ cat /etc/system-release
Amazon Linux release 2 (Karoo)

つづいて、 uname コマンドでも確認します。

[cloudshell-user@ip-10-0-15-222 ~]$ aws --version
aws-cli/2.0.58 Python/3.7.3 Linux/4.14.209-160.335.amzn2.x86_64 exec-env/CloudShell exe/x86_64.amzn.2

サービスリリースの記事の通り、Amazon Linux 2 ベースで動いていることがわかりました。

ストレージの確認

df コマンドでストレージ容量などを確認します。

[cloudshell-user@ip-10-0-15-222 ~]$ df -h
Filesystem      Size  Used Avail Use% Mounted on
overlay          30G   11G   17G  40% /
tmpfs            64M     0   64M   0% /dev
shm             2.0G     0  2.0G   0% /dev/shm
tmpfs           2.0G     0  2.0G   0% /sys/fs/cgroup
/dev/xvdcz       30G   11G   17G  40% /aws/mde
/dev/loop0      976M  2.6M  907M   1% /home
tmpfs           2.0G     0  2.0G   0% /proc/acpi
tmpfs           2.0G     0  2.0G   0% /sys/firmware
tmpfs           2.0G     0  2.0G   0% /proc/scsi

/home が約 1GB なので記事にある up to 1GB のとおりですね。

その他いろいろ

sudo はできるか

[cloudshell-user@ip-10-0-92-23 ~]$ sudo date
Wed Dec 16 04:27:04 UTC 2020

できますね。

以下のようにやれば、半ば無理やりですが、 root にもなれます。

[cloudshell-user@ip-10-0-92-23 ~]$ sudo su -
-bash-4.2# whoami
root
-bash-4.2# 

インスタンスメタデータは取得できるか

[cloudshell-user@ip-10-0-92-23 ~]$ TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` \
> && curl -H "X-aws-ec2-metadata-token: $TOKEN" -v http://169.254.169.254/latest/meta-data/
curl: (7) Couldn't connect to server

接続できず、取得できないようです。

既にインストールされているパッケージ

yum list installed コマンドで確認できます。

[cloudshell-user@ip-10-0-92-23 ~]$ yum list installed

詳細は長いので、以下開いてください。

詳細を開く
Installed Packages
acl.x86_64                           2.2.51-14.amzn2                 @amzn2-core
amazon-linux-extras.noarch           1.6.12-1.amzn2                  @amzn2-core
audit-libs.x86_64                    2.8.1-3.amzn2.1                 @amzn2-core
basesystem.noarch                    10.0-7.amzn2.0.1                installed  
bash.x86_64                          4.2.46-34.amzn2                 @amzn2-core
bzip2-libs.x86_64                    1.0.6-13.amzn2.0.2              installed  
ca-certificates.noarch               2019.2.32-76.amzn2.0.3          @amzn2-core
chkconfig.x86_64                     1.7.4-1.amzn2.0.2               installed  
coreutils.x86_64                     8.22-24.amzn2                   installed  
cpio.x86_64                          2.11-28.amzn2                   @amzn2-core
cracklib.x86_64                      2.9.0-11.amzn2.0.2              @amzn2-core
cracklib-dicts.x86_64                2.9.0-11.amzn2.0.2              @amzn2-core
cryptsetup-libs.x86_64               1.7.4-4.amzn2                   @amzn2-core
curl.x86_64                          7.61.1-12.amzn2.0.2             @amzn2-core
cyrus-sasl-lib.x86_64                2.1.26-23.amzn2                 installed  
dbus.x86_64                          1:1.10.24-7.amzn2               @amzn2-core
dbus-libs.x86_64                     1:1.10.24-7.amzn2               @amzn2-core
device-mapper.x86_64                 7:1.02.146-4.amzn2.0.2          @amzn2-core
device-mapper-libs.x86_64            7:1.02.146-4.amzn2.0.2          @amzn2-core
diffutils.x86_64                     3.3-5.amzn2                     installed  
elfutils-default-yama-scope.noarch   0.176-2.amzn2                   @amzn2-core
elfutils-libelf.x86_64               0.176-2.amzn2                   installed  
elfutils-libs.x86_64                 0.176-2.amzn2                   @amzn2-core
emacs-filesystem.noarch              1:25.3-3.amzn2.0.2              @amzn2-core
expat.x86_64                         2.1.0-12.amzn2                  @amzn2-core
file-libs.x86_64                     5.11-36.amzn2.0.1               @amzn2-core
filesystem.x86_64                    3.2-25.amzn2.0.4                installed  
findutils.x86_64                     1:4.5.11-6.amzn2                installed  
fipscheck.x86_64                     1.4.1-6.amzn2.0.2               @amzn2-core
fipscheck-lib.x86_64                 1.4.1-6.amzn2.0.2               @amzn2-core
gawk.x86_64                          4.0.2-4.amzn2.1.2               installed  
gdbm.x86_64                          1:1.13-6.amzn2.0.2              installed  
git.x86_64                           2.23.3-1.amzn2.0.1              @amzn2-core
git-core.x86_64                      2.23.3-1.amzn2.0.1              @amzn2-core
git-core-doc.noarch                  2.23.3-1.amzn2.0.1              @amzn2-core
glib2.x86_64                         2.56.1-5.amzn2.0.1              @amzn2-core
glibc.x86_64                         2.26-37.amzn2                   @amzn2-core
glibc-common.x86_64                  2.26-37.amzn2                   @amzn2-core
glibc-langpack-en.x86_64             2.26-37.amzn2                   @amzn2-core
glibc-minimal-langpack.x86_64        2.26-37.amzn2                   @amzn2-core
gmp.x86_64                           1:6.0.0-15.amzn2.0.2            installed  
gnupg2.x86_64                        2.0.22-5.amzn2.0.4              installed  
gpgme.x86_64                         1.3.2-5.amzn2.0.2               installed  
gpm-libs.x86_64                      1.20.7-15.amzn2.0.2             @amzn2-core
grep.x86_64                          2.20-3.amzn2.0.2                installed  
groff-base.x86_64                    1.22.2-8.amzn2.0.2              @amzn2-core
gzip.x86_64                          1.5-10.amzn2                    @amzn2-core
info.x86_64                          5.1-5.amzn2                     installed  
iputils.x86_64                       20160308-10.amzn2.0.2           @amzn2-core
jq.x86_64                            1.5-1.amzn2.0.2                 @amzn2-core
keyutils-libs.x86_64                 1.5.8-3.amzn2.0.2               installed  
kmod.x86_64                          25-3.amzn2.0.2                  @amzn2-core
kmod-libs.x86_64                     25-3.amzn2.0.2                  @amzn2-core
krb5-libs.x86_64                     1.15.1-37.amzn2.2.2             installed  
less.x86_64                          458-9.amzn2.0.2                 @amzn2-core
libacl.x86_64                        2.2.51-14.amzn2                 installed  
libassuan.x86_64                     2.1.0-3.amzn2.0.2               installed  
libattr.x86_64                       2.4.46-12.amzn2.0.2             installed  
libblkid.x86_64                      2.30.2-2.amzn2.0.4              installed  
libcap.x86_64                        2.22-9.amzn2.0.2                installed  
libcap-ng.x86_64                     0.7.5-4.amzn2.0.4               @amzn2-core
libcom_err.x86_64                    1.42.9-19.amzn2                 @amzn2-core
libcrypt.x86_64                      2.26-37.amzn2                   @amzn2-core
libcurl.x86_64                       7.61.1-12.amzn2.0.2             @amzn2-core
libdb.x86_64                         5.3.21-24.amzn2.0.3             installed  
libdb-utils.x86_64                   5.3.21-24.amzn2.0.3             installed  
libedit.x86_64                       3.0-12.20121213cvs.amzn2.0.2    @amzn2-core
libevent.x86_64                      2.0.21-4.amzn2.0.3              @amzn2-core
libfdisk.x86_64                      2.30.2-2.amzn2.0.4              @amzn2-core
libffi.x86_64                        3.0.13-18.amzn2.0.2             installed  
libgcc.x86_64                        7.3.1-9.amzn2                   @amzn2-core
libgcrypt.x86_64                     1.5.3-14.amzn2.0.2              installed  
libgpg-error.x86_64                  1.12-3.amzn2.0.3                installed  
libicu.x86_64                        50.2-4.amzn2                    @amzn2-core
libidn.x86_64                        1.28-4.amzn2.0.2                @amzn2-core
libidn2.x86_64                       2.3.0-1.amzn2                   installed  
libmetalink.x86_64                   0.1.3-13.amzn2                  @amzn2-core
libmount.x86_64                      2.30.2-2.amzn2.0.4              installed  
libnghttp2.x86_64                    1.41.0-1.amzn2                  @amzn2-core
libpipeline.x86_64                   1.2.3-3.amzn2.0.2               @amzn2-core
libpwquality.x86_64                  1.2.3-5.amzn2                   @amzn2-core
libsecret.x86_64                     0.18.5-2.amzn2.0.2              @amzn2-core
libselinux.x86_64                    2.5-12.amzn2.0.2                installed  
libsemanage.x86_64                   2.5-11.amzn2                    @amzn2-core
libsepol.x86_64                      2.5-8.1.amzn2.0.2               installed  
libsmartcols.x86_64                  2.30.2-2.amzn2.0.4              @amzn2-core
libssh2.x86_64                       1.4.3-12.amzn2.2.3              @amzn2-core
libstdc++.x86_64                     7.3.1-9.amzn2                   @amzn2-core
libtasn1.x86_64                      4.10-1.amzn2.0.2                installed  
libtirpc.x86_64                      0.2.4-0.16.amzn2                @amzn2-core
libunistring.x86_64                  0.9.3-9.amzn2.0.2               installed  
libutempter.x86_64                   1.1.6-4.amzn2.0.2               @amzn2-core
libuuid.x86_64                       2.30.2-2.amzn2.0.4              installed  
libverto.x86_64                      0.2.5-4.amzn2.0.2               installed  
libxml2.x86_64                       2.9.1-6.amzn2.5.1               @amzn2-core
lua.x86_64                           5.1.4-15.amzn2.0.2              installed  
lz4.x86_64                           1.7.5-2.amzn2.0.1               @amzn2-core
make.x86_64                          1:3.82-24.amzn2                 @amzn2-core
man-db.x86_64                        2.6.3-9.amzn2.0.3               @amzn2-core
mariadb.x86_64                       1:5.5.68-1.amzn2                @amzn2-core
mariadb-libs.x86_64                  1:5.5.68-1.amzn2                @amzn2-core
ncurses.x86_64                       6.0-8.20170212.amzn2.1.3        installed  
ncurses-base.noarch                  6.0-8.20170212.amzn2.1.3        installed  
ncurses-libs.x86_64                  6.0-8.20170212.amzn2.1.3        installed  
nodejs.x86_64                        2:12.18.4-1nodesource           installed  
nspr.x86_64                          4.21.0-1.amzn2.0.2              installed  
nss.x86_64                           3.44.0-7.amzn2                  installed  
nss-pem.x86_64                       1.0.3-5.amzn2                   installed  
nss-softokn.x86_64                   3.44.0-8.amzn2                  installed  
nss-softokn-freebl.x86_64            3.44.0-8.amzn2                  installed  
nss-sysinit.x86_64                   3.44.0-7.amzn2                  installed  
nss-tools.x86_64                     3.44.0-7.amzn2                  installed  
nss-util.x86_64                      3.44.0-4.amzn2                  installed  
oniguruma.x86_64                     5.9.6-1.amzn2.0.3               @amzn2-core
openldap.x86_64                      2.4.44-22.amzn2                 @amzn2-core
openssh.x86_64                       7.4p1-21.amzn2.0.1              @amzn2-core
openssh-clients.x86_64               7.4p1-21.amzn2.0.1              @amzn2-core
openssl-libs.x86_64                  1:1.0.2k-19.amzn2.0.3           installed  
p11-kit.x86_64                       0.23.21-2.amzn2.0.1             @amzn2-core
p11-kit-trust.x86_64                 0.23.21-2.amzn2.0.1             @amzn2-core
pam.x86_64                           1.1.8-23.amzn2.0.1              @amzn2-core
pcre.x86_64                          8.32-17.amzn2.0.2               installed  
pcre2.x86_64                         10.23-2.amzn2.0.2               @amzn2-core
perl.x86_64                          4:5.16.3-294.amzn2              @amzn2-core
perl-Carp.noarch                     1.26-244.amzn2                  @amzn2-core
perl-Encode.x86_64                   2.51-7.amzn2.0.2                @amzn2-core
perl-Error.noarch                    1:0.17020-2.amzn2               @amzn2-core
perl-Exporter.noarch                 5.68-3.amzn2                    @amzn2-core
perl-File-Path.noarch                2.09-2.amzn2                    @amzn2-core
perl-File-Temp.noarch                0.23.01-3.amzn2                 @amzn2-core
perl-Filter.x86_64                   1.49-3.amzn2.0.2                @amzn2-core
perl-Getopt-Long.noarch              2.40-3.amzn2                    @amzn2-core
perl-Git.noarch                      2.23.3-1.amzn2.0.1              @amzn2-core
perl-HTTP-Tiny.noarch                0.033-3.amzn2                   @amzn2-core
perl-PathTools.x86_64                3.40-5.amzn2.0.2                @amzn2-core
perl-Pod-Escapes.noarch              1:1.04-294.amzn2                @amzn2-core
perl-Pod-Perldoc.noarch              3.20-4.amzn2                    @amzn2-core
perl-Pod-Simple.noarch               1:3.28-4.amzn2                  @amzn2-core
perl-Pod-Usage.noarch                1.63-3.amzn2                    @amzn2-core
perl-Scalar-List-Utils.x86_64        1.27-248.amzn2.0.2              @amzn2-core
perl-Socket.x86_64                   2.010-4.amzn2.0.2               @amzn2-core
perl-Storable.x86_64                 2.45-3.amzn2.0.2                @amzn2-core
perl-TermReadKey.x86_64              2.30-20.amzn2.0.2               @amzn2-core
perl-Text-ParseWords.noarch          3.29-4.amzn2                    @amzn2-core
perl-Time-HiRes.x86_64               4:1.9725-3.amzn2.0.2            @amzn2-core
perl-Time-Local.noarch               1.2300-2.amzn2                  @amzn2-core
perl-constant.noarch                 1.27-2.amzn2.0.1                @amzn2-core
perl-libs.x86_64                     4:5.16.3-294.amzn2              @amzn2-core
perl-macros.x86_64                   4:5.16.3-294.amzn2              @amzn2-core
perl-parent.noarch                   1:0.225-244.amzn2.0.1           @amzn2-core
perl-podlators.noarch                2.5.1-3.amzn2.0.1               @amzn2-core
perl-threads.x86_64                  1.87-4.amzn2.0.2                @amzn2-core
perl-threads-shared.x86_64           1.43-6.amzn2.0.2                @amzn2-core
pinentry.x86_64                      0.8.1-17.amzn2.0.2              installed  
popt.x86_64                          1.13-16.amzn2.0.2               installed  
powershell.x86_64                    7.0.3-1.rhel.7                  installed  
procps-ng.x86_64                     3.3.10-26.amzn2                 @amzn2-core
pth.x86_64                           2.0.7-23.amzn2.0.2              installed  
pygpgme.x86_64                       0.3-9.amzn2.0.2                 installed  
pyliblzma.x86_64                     0.5.3-11.amzn2.0.2              installed  
python.x86_64                        2.7.18-1.amzn2.0.2              @amzn2-core
python-iniparse.noarch               0.4-9.amzn2                     installed  
python-libs.x86_64                   2.7.18-1.amzn2.0.2              @amzn2-core
python-pycurl.x86_64                 7.19.0-19.amzn2.0.2             installed  
python-urlgrabber.noarch             3.10-9.amzn2.0.1                installed  
python2-rpm.x86_64                   4.11.3-40.amzn2.0.5             @amzn2-core
python3.x86_64                       3.7.9-1.amzn2.0.1               @amzn2-core
python3-libs.x86_64                  3.7.9-1.amzn2.0.1               @amzn2-core
python3-pip.noarch                   9.0.3-1.amzn2.0.2               @amzn2-core
python3-setuptools.noarch            38.4.0-3.amzn2.0.6              @amzn2-core
pyxattr.x86_64                       0.5.1-5.amzn2.0.2               installed  
qrencode-libs.x86_64                 3.4.1-3.amzn2.0.2               @amzn2-core
readline.x86_64                      6.2-10.amzn2.0.2                installed  
rpm.x86_64                           4.11.3-40.amzn2.0.5             @amzn2-core
rpm-build-libs.x86_64                4.11.3-40.amzn2.0.5             @amzn2-core
rpm-libs.x86_64                      4.11.3-40.amzn2.0.5             @amzn2-core
sed.x86_64                           4.2.2-5.amzn2.0.2               installed  
setup.noarch                         2.8.71-10.amzn2.0.1             installed  
shadow-utils.x86_64                  2:4.1.5.1-24.amzn2.0.2          @amzn2-core
shared-mime-info.x86_64              1.8-4.amzn2                     installed  
sqlite.x86_64                        3.7.17-8.amzn2.1.1              installed  
sudo.x86_64                          1.8.23-4.amzn2.2                @amzn2-core
system-release.x86_64                1:2-12.amzn2                    @amzn2-core
systemd.x86_64                       219-57.amzn2.0.12               @amzn2-core
systemd-libs.x86_64                  219-57.amzn2.0.12               @amzn2-core
tar.x86_64                           2:1.26-35.amzn2                 @amzn2-core
tmux.x86_64                          1.8-4.amzn2.0.1                 @amzn2-core
tzdata.noarch                        2020a-1.amzn2                   @amzn2-core
unzip.x86_64                         6.0-21.amzn2                    @amzn2-core
ustr.x86_64                          1.0.4-16.amzn2.0.3              @amzn2-core
util-linux.x86_64                    2.30.2-2.amzn2.0.4              @amzn2-core
vim-common.x86_64                    2:8.1.1602-1.amzn2              @amzn2-core
vim-enhanced.x86_64                  2:8.1.1602-1.amzn2              @amzn2-core
vim-filesystem.noarch                2:8.1.1602-1.amzn2              @amzn2-core
vim-minimal.x86_64                   2:8.1.1602-1.amzn2              installed  
wget.x86_64                          1.14-18.amzn2.1                 @amzn2-core
which.x86_64                         2.20-7.amzn2.0.2                @amzn2-core
xz-libs.x86_64                       5.2.2-1.amzn2.0.2               installed  
yum.noarch                           3.4.3-158.amzn2.0.4             @amzn2-core
yum-metadata-parser.x86_64           1.1.4-10.amzn2.0.2              installed  
yum-plugin-ovl.noarch                1.1.31-46.amzn2.0.1             installed  
yum-plugin-priorities.noarch         1.1.31-46.amzn2.0.1             installed  
zip.x86_64                           3.0-11.amzn2.0.2                @amzn2-core
zlib.x86_64                          1.2.7-18.amzn2                  installed  
zsh.x86_64                           5.7.1-6.amzn2.0.1               @amzn2-core

記事にある通り、jq や pip、 npm などがインストールされています。powershell もありました。
試しに PowerShell を実行してみました。

[cloudshell-user@ip-10-0-92-23 ~]$ /opt/microsoft/powershell/7/pwsh 
PowerShell 7.0.3
Copyright (c) Microsoft Corporation. All rights reserved.

https://aka.ms/powershell
Type 'help' to get help.

   A new PowerShell stable release is available: v7.1.0 
   Upgrade now, or check out the release page at:       
     https://aka.ms/PowerShell-Release?tag=v7.1.0       

PS /home/cloudshell-user> 

牛に何かつぶやかせる

cowsay というコマンドをインストールして、引数に与えた文字列をつぶやく牛のアスキーアートを表示するコマンドを実行してみます。

[cloudshell-user@ip-10-0-92-23 ~]$ git clone https://github.com/schacon/cowsay.git
Cloning into 'cowsay'...
remote: Enumerating objects: 64, done.
remote: Total 64 (delta 0), reused 0 (delta 0), pack-reused 64
Unpacking objects: 100% (64/64), done.
[cloudshell-user@ip-10-0-92-23 ~]$ cd cowsay/
[cloudshell-user@ip-10-0-92-23 cowsay]$ sudo ./install.sh 
===================
cowsay Installation
===================

(中略)

[cloudshell-user@ip-10-0-92-23 cowsay]$ cowsay "AWSCloudShell"
 _______________ 
< AWSCloudShell >
 --------------- 
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

emacs を使うぞ!

vi ではなく、 emacs 勢のための確認です。筆者が初めてテキストエディタに触れたのは emacs でした。

[cloudshell-user@ip-10-0-92-23 ~]$ sudo yum install emacs-nox

(中略)

[cloudshell-user@ip-10-0-92-23 ~]$ emacs

image.png

さらに、 Exit + x を入力し、 hanoi と入力すると。。。
ハノイの塔を眺めることができます。
image.png

同様に、Exit + x を入力し、 gomoku とすると。。。
五目並べで遊ぶことができます。

image.png

矢印キーで移動し、スペースキーで石を置く場所を決めますが、全然勝てません。

おまけ

現状はFAQにもある通り、 VPC 上のリソース(プライベートサブネットで稼働している EC2 インスタンスや RDS の DB インスタンスなど)に直接アクセスすることはできません。
しかし、以下の通り実行すると(半ば無理やりですが) EC2 インスタンスへ接続することができました。
このやり方を推奨するものではございません。
このやり方を参考にしていかなる損害等が発生しても筆者や当社は責任を負いません。ご了承ください。

上記を承知して詳細を開く

準備:
  • 接続したい EC2 インスタンスにパブリックIPアドレスを割り当てる
  • いうまでもなく、EC2 インスタンスが稼働する VPC に インターネットゲートウェイがアタッチされている
  • (!!!!危険)EC2 インスタンスに割り当てているセキュリティグループのインバウンドをSSHフルオープンにする(危険!!!!)
    • AWS CloudShell が利用するパブリックIPアドレスがすぐにわからないため。わかったら絞ることを推奨
  • EC2 インスタンスにログインする際、鍵を使うのであれば、 AWS CloudShell へアップロードし、適宜権限設定を行う

AWS CloudShell 上で ssh コマンドを実行する

[cloudshell-user@ip-10-0-182-67 ~]$ ssh -i key-file-name.pem ec2-user@***.***.***.***
The authenticity of host '***.***.***.*** (***.***.***.***)' can't be established.
ECDSA key fingerprint is SHA256:*********************************************.
ECDSA key fingerprint is MD5:*********************************************.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '***.***.***.***' (ECDSA) to the list of known hosts.

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
7 package(s) needed for security, out of 19 available
Run "sudo yum update" to apply all updates.
[ec2-user@ip-***-***-***-*** ~]$ 

と、このようにつなぐことはできました。
セキュリティ的にもよろしくないので技術的興味を満たす以外に実施するのをお勧めしません。
繰り返しになりますが、このやり方を推奨するものではございません。
繰り返しになりますが、このやり方を参考にしていかなる損害等が発生しても筆者や当社は責任を負いません。ご了承ください。

まとめ

ささっと AWS CLI や API を実行したい場合、これまでは ES2 インスタンスをささと作って SSM でつないだり、たたきたい API を実行するためだけの Lambda 関数を作るなどしていました。
この AWS CloudShell を使うことでこういった作業からも解放され、インスタンスや関数の管理や始末をしなくてもよくなりました。
しかも、追加費用なしで利用可能なんて、驚きです。

この機能、待ってました!

記載されている会社名、製品名、サービス名、ロゴ等は各社の商標または登録商標です。

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

【React/Go】ユーザー認証をCognito + Amplifyで構築してみた ~ユーザ登録/削除編~

はじめに

Reactで作成したWebアプリケーションのユーザー認証部分をCognito + Amplifyフレームワークで構築してみました。構築の基本部分については「【React】ユーザー認証をCognito + Amplifyで構築してみた」の構築準備編構築完成編をご覧ください。
本記事は、アプリケーションからユーザーを登録、削除する方法についてまとめています。

完成画面

今回は、アプリケーションからユーザーを登録すると、ユーザープールとDBそれぞれにユーザーが登録されて、画面には「ユーザーを登録しました。」というアラートが出力されるようにします。

スクリーンショット 2020-12-10 19.15.23.png

[Submit]をクリックすると↓↓↓

画面
スクリーンショット 2020-12-10 19.15.11.png

Cognitoユーザープール管理画面
スクリーンショット 2020-12-06 18.55.55.png

DB(userテーブル)※一部抜粋

+----+-----------+------------------+
| id | user_name | email            |
+----+-----------+------------------+
|  2 | test      | test@example.com |
+----+-----------+------------------+

方法検討

要件

構築方法を考えるにあたり、条件は以下の通りです。

  • 静的コンテンツをS3に置いている
  • アプリケーション部分はLambda + RDS Proxy + RDSで実装している
  • ユーザーデータはCognitoユーザープール以外に、RDSに保存している
  • NATゲートウェイはコストが高いので使いたくない

現在の構成図(ユーザー認証付加前)

ユーザー認証付加前のアプリケーション部分の構成図は下記の通りです。

Cognito-before.png

VPC Lambdaによる弊害

ここで、LambdaをVPC内に設置していることで、Cognitoにアクセスできないことに気付きました。パブリックサブネットに置いているんだから、アクセスできると勝手に思っていました。

AWS開発者ガイドによると、次のように説明されています。

プライベートリソースにアクセスするには、関数をプライベートサブネットに接続します。関数にインターネットアクセスが必要な場合は、ネットワークアドレス変換 (NAT) を使用します。関数をパブリックサブネットに接続しても、インターネットアクセスやパブリック IP アドレスは提供されません。

Lambda関数をパブリックサブネットに接続しても、インターネットアクセスやパブリック IP アドレスは提供されないんです。NATゲートウェイを使用する場合にもLambda関数はプライベートサブネットに置くべきだそうです。パブリックサブネットにLambdaを置いておくメリットはなさそうなので、VPC Lambdaはプライベートサブネットに置きましょう!!!

結論

この条件に沿ってアプリケーションの登録、削除処理を考えた結果、VPC Lambdaをプライベートサブネットに移動させ、NATゲートウェイは使いたくないので、強引にLambdaからLambdaを呼び出すことにしました。

シーケンス図

シーケンス図を書くと次のようになります。

スクリーンショット 2020-12-15 20.00.08.png

構成図(ユーザー認証付加後)

構成は下図の通りになりました。

Cognito-after (1).png

手順

下記の流れで進めていきます。

  1. RDSを更新するLambda関数:Lambda(VPC) の作成
  2. Cognitoを更新するLambda関数:Lambda(非VPC) の作成
  3. API Gatewayの作成
  4. フロントの実装

ユーザーを登録する

1. DBを更新するLambda関数:Lambda(VPC) の作成

Lambda(非VPC)の作成時につけるIAMロールにLambda(VPC)のarnが必要なので、先にLambda(VPC)から作成します。
VPC内に設置してRDSに情報を書き込むLambdaを作成していきます。このLambdaに関しては、RDSにデータが保存できれば良く、特に既存のLambdaと変わりないので割愛します。
祝GA‼︎【Go】Lambda + RDS 接続にRDS Proxyを使ってみたの「8. Lambda関数の作成」を参考に作成しました。

ソースコード

ソースコードはこのような感じです。
※↓クリックするとソースコードが見れます。

ソースコード
lambda_vpc.go
package main

import (
    "database/sql"
    "fmt"
    "github.com/aws/aws-lambda-go/lambda"
    _ "github.com/go-sql-driver/mysql"
    "os"
)

type MyEvent struct {
    UserName string `json:"userName"`
    Email    string `json:"email"`
}

// os.Getenv()でLambdaの環境変数を取得
var dbEndpoint = os.Getenv("dbEndpoint")
var dbUser = os.Getenv("dbUser")
var dbPass = os.Getenv("dbPass")
var dbName = os.Getenv("dbName")

func RDSConnect() (*sql.DB, error) {
    connectStr := fmt.Sprintf(
        "%s:%s@tcp(%s:%s)/%s?charset=%s",
        dbUser,
        dbPass,
        dbEndpoint,
        "3306",
        dbName,
        "utf8",
    )
    db, err := sql.Open("mysql", connectStr)
    if err != nil {
        return nil, err
    }
    return db, nil
}

func RDSProcessing(event MyEvent, db *sql.DB) (interface{}, error) {

    tx, err := db.Begin()
    if err != nil {
        return nil, err
    }
    defer tx.Rollback()

    // ユーザーテーブルに情報を登録
    stmt, err := tx.Prepare("INSERT INTO user(user_name, email) VALUES (?, ?) ")
    if err != nil {
        return nil, err
    }
    defer stmt.Close()

    if _, err := stmt.Exec(event.UserName, event.Email); err != nil {
        return nil, err
    }

    if err := tx.Commit(); err != nil {
        return nil, err
    }

    response := "正常に処理が完了しました。"
    return response, nil
}

func run(event MyEvent) (interface{}, error) {
    fmt.Println("RDS接続 start!")
    db, err := RDSConnect()
    if err != nil {
        fmt.Println("DBの接続に失敗しました。")
        panic(err.Error())
    }
    fmt.Println("RDS接続 end!")
    fmt.Println("RDS処理 start!")
    response, err := RDSProcessing(event, db)
    if err != nil {
        fmt.Println("DB処理に失敗しました。")
        panic(err.Error())
    }
    fmt.Println("RDS処理 end!")
    return response, nil
}

/**************************
   メイン
**************************/
func main() {
    lambda.Start(run)
}

2. Cognitoを更新するLambda関数:Lambda(非VPC) の作成

VPCの外に置いて、Cognitoユーザープールへの登録とRDSを更新するLambda(VPC)を実行するLambdaを作成していきます。

IAMロール

下記の2つの権限をつけたポリシーを作成してアタッチします。

  • Cognitoユーザープールにユーザーを登録/削除する権限
  • Lambda(VPC)を実行する権限
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "cognito-idp:AdminDeleteUser",
                "cognito-idp:AdminCreateUser"
            ],
            "Resource": "<Cognitoのarn>"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": "lambda:InvokeFunction",
            "Resource": "<Lambda(VPC)のarn>"
        }
    ]
}

ソースコード

lambda_no_vpc.go
package main

import (
    "encoding/json"
    "fmt"
    "os"

    "github.com/aws/aws-lambda-go/lambda"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/cognitoidentityprovider"
    "github.com/aws/aws-sdk-go/service/cognitoidentityprovider/cognitoidentityprovideriface"
    l "github.com/aws/aws-sdk-go/service/lambda"
    "github.com/aws/aws-sdk-go/service/lambda/lambdaiface"
)

type MyEvent struct {
    UserName string `json:"userName"`
    Email    string `json:"email"`
}

func AddCognitoUser(svc cognitoidentityprovideriface.CognitoIdentityProviderAPI, event MyEvent) error {

    // 登録時にユーザーにメール送信
    desiredDeliveryMediums := []*string{aws.String("EMAIL")}

    // メールアドレスとメールアドレス検証済みを設定
    userAttributes := []*cognitoidentityprovider.AttributeType{
        {
            Name:  aws.String("email"),
            Value: aws.String(event.Email),
        },
        {
            Name:  aws.String("email_verified"),
            Value: aws.String("true"),
        },
    }

    // ユーザープールの設定
    // os.Getenv()でLambdaの環境変数を取得
    userPoolId := aws.String(os.Getenv("userPoolId"))

    // ユーザー名の設定
    username := aws.String(event.UserName)

    // Inputの作成
    input := &cognitoidentityprovider.AdminCreateUserInput{}
    input.DesiredDeliveryMediums = desiredDeliveryMediums
    input.UserAttributes = userAttributes
    input.UserPoolId = userPoolId
    input.Username = username

    // 処理実行
    result, err := svc.AdminCreateUser(input)
    if err != nil {
        fmt.Println(err.Error())
        return err
    }

    fmt.Println(result)
    return nil
}

func AddDbUser(svc lambdaiface.LambdaAPI, event MyEvent) error {

    // ValidateLambdaに送る情報の作成
    jsonBytes, _ := json.Marshal(event)

    // Inputの作成
    input := &l.InvokeInput{}
    input.FunctionName = aws.String(os.Getenv("arn"))
    input.Payload = jsonBytes
    input.InvocationType = aws.String("RequestResponse") // 同期実行

    // 処理実行
    result, err := svc.Invoke(input)
    if err != nil {
        fmt.Println(err.Error())
        return err
    }

    fmt.Println(result)
    fmt.Println(string(result.Payload))

    return nil
}

func run(event MyEvent) (interface{}, error) {
    fmt.Println("Cognito登録 start!")
    // セッション作成
    csvc := cognitoidentityprovider.New(session.Must(session.NewSession()))

    if err := AddCognitoUser(csvc, event); err != nil {
        fmt.Println("ユーザー登録に失敗しました。")
        panic(err.Error())
    }
    fmt.Println("Cognito登録 end!")

    fmt.Println("db登録 start!")
    // セッションの作成
    lsvc := l.New(session.Must(session.NewSession()))

    if err := AddDbUser(lsvc, event); err != nil {
        fmt.Println("ユーザー登録に失敗しました。")
        panic(err.Error())
    }
    fmt.Println("db登録 end!")

    fmt.Println("end!")
    response := "正常に処理が完了しました。"
    return response, nil
}

/**************************
   メイン
**************************/
func main() {
    lambda.Start(run)
}

3. API Gatewayの作成

REST APIでPOSTメソッドを作成し、Lambda(非VPC)を紐付けます。
特に特別な設定は不要なので省略します。

4. フロントの実装

登録画面を作成します。今回は、ユーザー名とメールアドレスが必須項目なので、その2つを登録できる入力欄と登録ボタンを簡単に作成しています。登録が完了すると「ユーザーを登録しました。」というアラートが出ます。

axiosのインストール

API Gatewayを叩くのにaxiosを使うために、プロジェクトにaxiosを追加します。

$ yarn add axios

ソースコード

axiosの使い方はaxiosライブラリを使ってリクエストするを参考にしました。

RegistrationForm.js
import React from "react";
import axios from "axios";


function RegistrationForm() {
    const API_ADD_URL = "<API Gatewayで取得したURL>"
    const [userName, setUserName] = React.useState("");
    const [email, setEmail] = React.useState("");

    const handleNameChange = event => {
        setUserName(event.target.value);
    };

    const handleEmailChange = event => {
        setEmail(event.target.value);
    }

    const handleSubmit = event => {
        axios.post(API_ADD_URL, {userName: userName, email: email})
            .then((response) => {
                if(response.data === "正常に処理が完了しました。"){
                    alert("ユーザーを登録しました。")
                    console.log(response);
                } else {
                    throw Error(response.data.errorMessage)
                }
            }).catch((response) => {
            alert("登録に失敗しました。もう一度登録してください。");
            console.log(response);
        });
        event.preventDefault();
    }

    return (
        <div>
            <h2>ユーザー登録</h2>
            <form onSubmit={handleSubmit} >
                <label >
                    ユーザー名:
                    <input type="text" value={userName} onChange={handleNameChange} /><br/>
                </label>
                <label >
                    Eメール:
                    <input type="text" value={email} onChange={handleEmailChange} /><br/>
                </label>
                <input type="submit" value="Submit" />
            </form>
        </div>
    );
}

export default RegistrationForm;

ユーザーを削除する

ユーザーから削除する場合も、基本的に登録するのと同じです。ユーザープールから削除するにはSDKのAdminDeleteUserを使用します。

実行結果

無事冒頭の完成画面のように動くようになりました!

fh4o1-lwya2.gif

おわりに

LambdaからLambdaを実行することで、NATゲートウェイを使わずにCognitoとDBの両方にユーザーを登録することができました!今考えると、Cognitoユーザープールに登録するのはAmplifyでAdmin Queries APIを使うようにして、DBに保存するのは既存のようにLambdaを呼び出すようにするのでも良かったかなとも思います!
次回は、サインインページにある、使用しないアカウント作成ボタンを消したいと思います!

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

【Linux】【AWS Lambda】Cron形式の設定マニュアル

背景

WEBサイトのクローリング、サーバーの死活監視データベースのバックアップ など、ものごとを定期的に行なう際に、スケジュール実行を可能とするCron形式について、Linuxでの設定方法・設定例と、AWS Lambdaにおける設定方法や設定例をまとめます。

先人たちの知恵をお借りするなどして解決できたことを、この場をお借りして感謝するとともに、大変恐縮ですが自分のメモとしても、こちらへまとめておきます。

環境

  • AWS EC2 (Amazon Linux 2)
  • AWS Lambda
  • Python 3.7.9    ※2020/12/10時点のAmazon Linux2でのデフォルト
  • Django 3.1.3
  • PostgreSQL 11.5  ※同上
  • Nginx 1.12     ※同上
  • Gunicorn
  • PuTTY 0.74

1. Linux における設定方法・設定例

1-1. 形式

<分> <時> <日> <月> <曜日> <コマンド>

1-2. パラメータ

  • すべてのパラメータが必須です。
  • タイムゾーンはUTC(協定世界時)のみで変更不可。JST(日本標準時)として指定するには、UTCに対して「-9時間」とする(9時間を差し引く)必要があります。
  • 分未満(秒単位)の指定はサポートされていません。
No. フィールド ワイルドカード
1 0~59 「/」(スラッシュ)、「*」(アスタリスク)「-」(ハイフン)、「,」(カンマ)
2 0~23 「/」(スラッシュ)、「*」(アスタリスク)「-」(ハイフン)、「,」(カンマ)
3 1~31 「/」(スラッシュ)、「*」(アスタリスク)「-」(ハイフン)、「,」(カンマ)
4 1~12 または JAN~DEC 「/」(スラッシュ)、「*」(アスタリスク)「-」(ハイフン)、「,」(カンマ)
5 曜日 1~7 または SUN~SAT 「/」(スラッシュ)、「*」(アスタリスク)「-」(ハイフン)、「,」(カンマ)
6 コマンド 任意のコマンド (なし)

1-3. ワイルドカード

  • 分未満(秒単位)の指定はサポートされていません。
  • 実際に指定する際に、ワイルドカードに「」は記述不要です。
No. 文字 定義 設定例
1 「/」(スラッシュ) 増分を指定する <分>フィールドの0/10は、10分ごとに実行が発生する。5/15は、5・20・35・50分などを意味する。
2 「*」(アスタリスク) すべての値を指定する <日>フィールドで使用した場合、その月のすべての日が設定される。
3 「-」(ハイフン) 範囲を指定する 8-10 は、8・9および10が設定される。
4 「,」(カンマ) 追加の値を指定する SUN・MON・TUEは、それぞれ日曜日・月曜日・火曜日が設定される。

1-4. 設定例

crontab
# 毎日午前8時(UTC)に'backup.py'を実行する
 0 8 * * * source ~/venv_<プロジェクト名>/bin/activate; cd ~/venv_<プロジェクト名>/<プロジェクト名>; python manage.py backup > ~/cron.log 2>&1

# 毎日午後11時45分(UTC)にNginxをリロードする
 45 23 * * * sudo systemctl reload nginx.service

# 毎月1日の17時30分(UTC)にLet's EncryptのSSL証明書を更新する
 30 17 1 * * /home/<スーパーユーザー>/certbot/certbot-auto renew -q --renew-hook "/usr/bin/sysytemctl reload nginx.service"
# または
 30 17 1 * * /home/<スーパーユーザー>/certbot/certbot-auto renew -q --renew-hook "/usr/bin/sysytemctl reload nginx.service"

# 月曜~金曜(UTC)は5分ごとに固定IPへのpingを実行する
 0/5 * * MON-FRI * ping <Elastic IP>
# または
 0/5 * * 2-6 * ping <Elastic IP>

2. AWS Lambda における設定方法・設定例

2-1. 形式

cron <分> <時> <日> <月> <曜日> <年>

2-2. パラメータ

  • すべてのパラメータが必須です。
  • タイムゾーンはUTC(協定世界時)のみで変更不可。JST(日本標準時)として指定するには、UTCに対して「-9時間」とする(9時間を差し引く)必要があります。
  • 分未満(秒単位)の指定はサポートされていません。
  • 実際に指定する際に、ワイルドカードに「」は記述不要です。
No. フィールド ワイルドカード
1 0~59 「/」(スラッシュ)、「*」(アスタリスク)、「-」(ハイフン)、「,」(カンマ)
2 0~23 「W」、「L」、「/」(スラッシュ)、「?」(クエスチョンマーク)、「*」(アスタリスク)、「-(ハイフン)、「,」(カンマ)
3 1~31 「/」(スラッシュ)、「*」(アスタリスク)、「-」(ハイフン)、「,」(カンマ)
4 1~12 または JAN~DEC 「/」(スラッシュ)、「*」(アスタリスク)、「-」(ハイフン)、「,」(カンマ)
5 曜日 1~7 または SUN~SAT 「#」(シャープ)、「L」、「/」(スラッシュ)、「?」(クエスチョンマーク)、「*」(アスタリスク)、「-」(ハイフン)、「,」(カンマ)
6 1970~2199 「/」(スラッシュ)、「*」(アスタリスク)、「-」(ハイフン)、「,」(カンマ)

2-3. ワイルドカード

  • 分未満(秒単位)の指定はサポートされていません。
  • 実際に指定する際に、ワイルドカードに「」は記述不要です。
  • 日または週のどちらかの値は、「?」(クエスチョンマーク)である必要があります。
No. 文字 定義 設定例
1 「/」(スラッシュ) 増分を指定する <分>フィールドの0/10は、10分ごとに実行が発生する。5/15は、5・20・35・50分などを意味する。
2 「L」 『最後』を指定する ①<日>フィールドに指定された場合は、その月の末日が設定される。②<週>フィールドに指定された場合は、その週の最後の曜日(=土曜日)が設定される。
3 「W」 平日を指定する 日付とともに指定した場合(例:3/Wなど)、その月の3日に最も近い平日が設定される。3日が土曜日の場合は、その前日の金曜日に実行される。3日が日曜日の場合は、その翌日の月曜日に実行される。
4 「#」(シャープ) その月のn番目の日を指定する 4#3と指定した場合は、その月の第3水曜日が設定される。(※水曜日=週7日のうち4番目の曜日)
5 「*」(アスタリスク) すべての値を指定する <日>フィールドで使用した場合、その月のすべての日が設定される。
6 「?」(クエスチョンマーク) 値を指定しない 指定した別の値とともに設定される。例として、ある特定の日付を指定したが、その日が何曜日であっても実行する場合。
7 「-」(ハイフン) 範囲を指定する 8-10 は、8・9および10が設定される。
8 「,」(カンマ) 追加の値を指定する SUN・MON・TUEは、それぞれ日曜日・月曜日・火曜日が設定される。

2-4. 設定例

# 毎日午前8時(UTC)に実行する
cron(0 8 * * ? *)

# 毎日午後12時45分(UTC)に実行する
cron(45 12 * * ? *)

# 月曜~金曜の午後5時30分(UTC)に実行する
cron(30 17 ? * MON-FRI *)
# または
cron(30 17 ? * 2-6 *)

# 月曜~金曜(UTC)は5分ごとに実行する
cron(0/5 * ? * MON-FRI *)
# または
cron(0/5 * ? * 2-6 *)

(参考)

AWS公式サイト(Lambda):
Rate または Cron を使用したスケジュール式
ルールのスケジュール式 (英語)


(編集後記)

ついつい、JSTとUTCとの時差(▲9時間)を忘れがちです。
月末の夜間に動かしたいにも関わらず、月初の昼間に動いてしまってアラートが挙がる!といったことの無いよう、設定する際には必ずこのマニュアルを見ることにしました。

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

ゲームの分散ビルド on AWS

AWS & Game Advent Calendar 2020の16日目の記事です。

はじめに

ゲーム業界ではかねてより分散ビルドソフトウェアが使われており、古くはdistcc(今でも使っている会社はあるのでしょうか)、近年ではSN-DBS(SIEのハード向け)やIncrediBuildが使われるケースが多いかと思います。

このIncrediBuildですが、公式サイトにもAWSに対応していると書かれているものの具体的にどうすれば使える様になるかイマイチ周知されていません。
そこで本記事ではIncrediBuildのビルドノード(Agent)としてAWSのEC2インスタンスを追加する事が出来る「IncrediBuild Cloud」を利用するまでの最短手順を追ってみます。

手順

IncrediBuild Cloudを使うのに必要な手順は以下の通りです。

  1. 動作環境を整える
  2. IncrediBuild Cloudのセットアップ
  3. インスタンスの起動確認
  4. ビルド確認

えらいシンプルですが、これらを順を追って説明します。

動作環境を整える

IncrediBuildを普段使っている人は知っている事かと思いますが、AgentはCoordinatorとネットワーク通信が可能である必要があります。これはAWS上にAgentを追加する場合も同じです。

つまりIncrediBuild Cloudを動作させるには、

  • Agentを追加したいAWS上のネットワーク(VPC)と、社内に立てているCoordinatorが通信可能な環境である

ことが必要であり、この環境を作るには「VPNを張る」「専用線で繋ぐ」などでAWSと会社をローカルネットワークとして繋ぐ事になります。
ふつうの会社であれば情シス部門などに作業を依頼するかと思います。

今回は最短手順を追うので、一番手っ取り早い

  • Agentを追加したいVPCにCoordinatorを立てる

事で環境を整えます。
以下画像の通りt2.smallのWindowsインスタンスを起動してCoordinatorをインストールしました。
ここでCoordinatorが起動しているネットワークの情報(VPC ID, サブネットID)をメモしておきます(あとで使います)。

IncrediBuild Cloudのセットアップ

セットアップはCoordinator Monitor画面の「Add Cloud Cores」ボタンから行います。
まずここで注意ですが、このボタンが有効になるのはCoordinatorマシン上でCoordinator Monitorを起動した時「のみ」です。
普段仕事でビルドジョブを投げる用途にのみIncrediBuildを使ってる人は、社内にいるIncrediBuildの管理者に作業を依頼する事になるかと思います。

「Add Cloud Cores」ボタンを押すとブラウザで設定画面が開きます。
2020年12月現在は以下の様な感じですが、数ヶ月前とは見た目の色合いがけっこう変わってます。

「SCALE TO CLOUD NOW」を押すと以下の画面に進むので、左の「amazon web services」を選びます。

次の画面で「Yes, I DO.」を選び「Login」を押すとAWSユーザのアクセスキー/シークレットの入力を求められます。
アクセスキーはAWSでAdministrator権限を持つIAMユーザを作り、新しいアクセスキーを作成する事で発行できます。

ログインするとインスタンスの設定画面が出るので必要事項を入力します。
ポイントとなるのは以下の箇所です。

  • Resource Management
    • 「Use Private Network」を選び、Coordinatorが立っているVPC/サブネットを指定します
  • VMs
    • 「VM Type」でEC2のインスタンスタイプを指定します
    • 「No. of VMs in Pool」で同時に起動させるEC2の最大台数を決めます
    • 「Use Spot Instances」を有効にするとSpot Instanceを起動しようと試みます

インスタンスの起動確認

設定を終えてしばらくするとAgentが起動します。起動が完了するとCoordinatorの「Cloud Machines」にAgentが登録されます。

AWSのマネジメントコンソールからもEC2が起動している事を確認できます。

ビルド確認

Coordinatorに接続可能な別マシンからビルドジョブを投げて動作確認します。
今回は最短手順を追うので、手っ取り早くCoordinatorと同じVPCに別マシンを立て、VisualStudioとIncrediBuildクライアントを入れてビルドジョブマシンを作りました。

実際にビルドを実行した結果は以下の通りです。
Coordinatorの「Cloud Machines」に登録されたAgentにビルドジョブが分散された事が確認できました。

おわりに

今回はIncrediBuild Cloudを使うまでの最短手順を書きました。
IAM,EC2,VPCの基本がわかっていれば数時間で動作確認まで出来ると思います。

なおIncrediBuildのライセンスについては説明を省いていますが、当然必要になります。
詳細はライセンス販売会社にご確認下さい。

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

Amplify CLIで「No credentials found for appId」が発生する

初めに

amplify env checkout prod でenvを切り替える際に
No credentials found for appId: hogeというエラーが発生してしまい
環境が切り替えられなくなってしまいましたので、対処法をメモしておきたいと思います。

% amplify env checkout prod
⠋ Initializing your environment: prod
No credentials found for appId: hoge
If the appId is correct, try running amplify configure --appId hoge

※本記事のhogeは全て自分のappIdに置き換えてください

原因

AmplifyのAdmin UI managementをOnにしてしまったのが原因だと思われます。

試したこと

エラー内容で指定されたコマンドを実行

amplify configure --appId hoge

→ 一通り入力するが変化なし。

以下で挙げられているコマンドを実行

https://github.com/aws-amplify/amplify-cli/issues/6084

% amplify pull --appId hoge --envName prod
⠋ Fetching updates to backend environment: prod from the cloud.
No credentials found for appId: hoge
If the appId is correct, try running amplify configure --appId hoge

→ こちらも同じく変化なし

解決した方法

  1. Amplify > アプリの設定 > Admin UI management の Access control settings から 「Invite users」 でユーザを作成する
    image.png

  2. 以下のURLにアクセスしログインすると、CLIとの連携に失敗した旨とコマンドが表示される
    https://ap-northeast-1.admin.amplifyapp.com/admin/hoge/prod/verify/
    image.png

  3. 指定されたコマンドを実行する

% amplify configure --appId hoge --envName prod
Opening link: https://ap-northeast-1.admin.amplifyapp.com/admin/hoge/prod/verify/
✔ Successfully received Amplify Admin tokens.

成功。
これで正常に動きました。

参考

https://github.com/aws-amplify/amplify-cli/issues/6084

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

AWS CloudShellで遊んでみる

AWS CloudShellが発表されました。AWSコンソールからプリセットされたシェル環境を操作できるようになっています。

基本的な使い方は公式で。

https://aws.amazon.com/blogs/aws/aws-cloudshell-command-line-access-to-aws-resources/

AWSコンソールにアクセスするとヘッダ部分にCloudShellへのアクセスボタンが表示されるようになってます。

スクリーンショット 2020-12-16 8.07.11.png

アクセスしたらこんな案内が表示されました。

スクリーンショット 2020-12-16 8.01.23.png

  • AWS CLI, Python, Node.jsなどがプリセット
  • 1GBストレージを提供
  • homeディレクトリ配下のファイルは保持される

気になったことをいくつか調べたので列挙しておきます。

EC2のメタデータは取得できるのか?

パッと見た感じのコンソールはプライベートIPが振られていたりしたので、メタデータが取得できるか試してみましたが、さすがにだめでした。

Preparing your terminal...
Try these commands to get started:
aws help  or  aws <command> help  or  aws <command> --cli-auto-prompt
[cloudshell-user@ip-10-0-XX-XXX ~]$ 
[cloudshell-user@ip-10-0-XX-XXX ~]$ sudo curl http://169.254.169.254/latest/meta-data/
curl: (7) Couldn't connect to server

※公式ブログに書いてありましたね。内部通信はもう少し先なのか。
Network Access – Sessions can make outbound connections to the Internet, but do not allow any type of inbound connections. Sessions cannot currently connect to resources inside of private VPC subnets, but that’s also on the near-term roadmap.

PythonやNode.jsの環境について

Pythonは2系と3系、両方あるようです。

$ python --version
Python 2.7.18
$ node --version
v12.18.4
$ python3 --version
Python 3.7.9

homeフォルダ以外のデータは残るか?

アクセスしているhomeフォルダはデータが残るようですが、それ以外のフォルダはデータが残るのか、以下の手順で確認。

  1. テキストファイルを作成(/home/cloudshell-user/test.txtと/text_root.txtを作成)
  2. 一度タブをクローズ
  3. 再度CloudShellコンソールを開く
$ ls /home/cloudshell-user/
test.txt
$ ls /
aws  bin  boot  dev  etc  home  lib  lib64  local  media  mnt  opt  proc  root  run  sbin  srv  sys  text_root.txt  tmp  usr  var

この状態で一度ブラウザのタブを閉じて、再アクセスしてみました。

$ ls /home/cloudshell-user/
test.txt
$ ls /
aws  bin  boot  dev  etc  home  lib  lib64  local  media  mnt  opt  proc  root  run  sbin  srv  sys  text_root.txt  tmp  usr  var

ファイルがどちらも残ってますね。ある程度時間を空けたらインスタンスは破棄されるのでしょうか。どこかで確認したいですね。

pip3でpandasをインストールしてみました。sudoすればインストールできるようです。

$ sudo pip3 install pandas
WARNING: Running pip install with root privileges is generally not a good idea. Try `pip3 install --user` instead.
Collecting pandas
  Downloading https://files.pythonhosted.org/packages/fd/70/e8eee0cbddf926bf51958c7d6a86bc69167c300fa2ba8e592330a2377d1b/pandas-1.1.5-cp37-cp37m-manylinux1_x86_64.whl (9.5MB)
    100% |████████████████████████████████| 9.5MB 126kB/s 
Collecting numpy>=1.15.4 (from pandas)
  Downloading https://files.pythonhosted.org/packages/5e/f2/9e562074f835b9b1227ca156f787be4554ae6bbe293c064337c4153cc4c8/numpy-1.19.4-cp37-cp37m-manylinux1_x86_64.whl (13.4MB)
    100% |████████████████████████████████| 13.4MB 91kB/s 
Requirement already satisfied: python-dateutil>=2.7.3 in /usr/local/lib/python3.7/site-packages (from pandas)
Requirement already satisfied: pytz>=2017.2 in /usr/local/lib/python3.7/site-packages (from pandas)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.7/site-packages (from python-dateutil>=2.7.3->pandas)
Installing collected packages: numpy, pandas
Successfully installed numpy-1.19.4 pandas-1.1.5

$ pip3 freeze | grep pandas
pandas==1.1.5

で、一度タブを閉じて開き直した上で再度pip3のfreezeをしてみたところ、pandasは残っていました。

$ pip3 freeze | grep pandas
pandas==1.1.5

となるとやはり初期化されるタイミングか、あるいは初期化の仕方を知りたいですね。
これを実行しているコンテナごと再生成するようなやり方はないかな。

IAMが違えば環境は違う

当然と言えば当然ですが、異なるIAMユーザーからアクセスしたら異なる環境でした。安心。

Preparing your terminal...
Try these commands to get started:
aws help  or  aws <command> help  or  aws <command> --cli-auto-prompt
[cloudshell-user@ip-10-1-XXX-XX ~]$ 

AWS CLIを実行しているユーザを確認しましたが、作成したIAMでした。

$ aws sts get-caller-identity
{
    "UserId": "ABCDEFGHIJKLMNOPQRSTU",
    "Account": "123456789012",
    "Arn": "arn:aws:iam::123456789012:user/test"
}

ローカル環境に依存しない実行環境

まだまだやれることは制限されていそうですが、とりあえずIAMユーザーを作成すれば「何かパッと試したい」環境としては使えるのではないでしょうか。「PC変えたから実行環境整えなきゃ・・・」という悩みからは解放されそうです。

VPCに対するアクセスなんかがサポートされるようになれば、さらに用途は広がりそうですし、また一つ便利になる機能がリリースされました。

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

DeepComposer触ってみる

この記事は株式会社ナレッジコミュニケーションが運営する Amazon AI by ナレコム Advent Calendar 2020 の 16日目にあたる記事になります。

今回はDeepComposerを使用し、簡単な編曲を行ってみます。
元となる曲ですが、既に準備されているサンプルを使用します。

DeepComposerとは

DeepComposer は Generative AI を楽しみながら学べる、機械学習を搭載した世界初のキーボードです。
・敵対的生成ネットワーク(GAN)モデルのトレーニングと最適化を通して、オリジナルの音楽を作曲
・2019年の AWS re:Invent で発表
・物理的なキーボードだけではなく、仮想キーボードを用いて、どこでも作曲・学習可能
・実機の価格は 99 ドル

今回の記事で使う機材

DTMソフトはlogic pro Xを使用します。
midi鍵盤はAWS公式のものではなく市販のものですが、コンソール上で正常に演奏できる事を確認しました。

実際に使ってみる

AWSコンソールのDeepComposer内より「Music studio」を選択すれば最短でDeepComposerの機能を試す事ができます。
今回はこのMusic studioから生成できるもので簡単な楽曲を作成してみようと思います。
さっそく既存で用意されているサンプルよりトルコ行進曲を題材にします。
「Extend input melody」でトルコ行進曲にサンプルメロディ以外のパートを自動的に生成します。
Jazz、Pop、Rock、Symphonyなどの既にトレーニングされたモデルからジャンルを選ぶ事が可能です。
今回はRockを選択。

サンプルとなったピアノを元に、ドラム、ギター、ベース、パッドの4パートが自動生成されました。
DC_2.png

画面上部「Download composition」よりMIDIデータでダウンロードする事が出来る為、こちらからダウンロードし、ここから自前のDTMソフトへ読み込み、簡単な修正を行っていきます。

修正前の全体は以下のようになっていました。

リズムに統一感がないのと、エフェクトも何も加工されていないので少し聞きづらいですね‥
これから1パートずつ分析と修正を行い、曲のパートとして聞きやすいように加工していきます。

■piano

まずは基本となるpianoパートですが、こちらサンプルそのままですのでmidiノートは特に修正していません。
音色はアコースティックなピアノ音源とエレクトリックピアノを準備しました。
Logic pro_1.png

※midiノートとは
MIDI(ミディ、Musical Instrument Digital Interface)
電子楽器の演奏データを機器間で転送・共有するための共通規格になります。
ノートとは、どの場所でどの音の高さかなどの情報です。

■drum

ドラムパートに関してもエレクトリックな音源へ差し替えをしました。
midiノートの無駄な重複を削除し、小説頭の認識が間違っていたので全体を1拍後ろへずらしました。

■guiter

ギターパートはまず全体的なヨレが目立ったのでmidiノート全体に16分のクオンタイズを行い、多すぎるmidiノートを一部削除しました。
音色はギターのままで残したかったですが、馴染みが悪かったためマリンバの音色へ変更。
リズムの雰囲気が分かりづらい為、リズミカルに演奏されるように、付点のディレイを設定しました。

※クオンタイズとは
MIDIシーケンサの機能の一つで演奏データのタイミングのズレを補正する効果の事。

■bass

ベースは基本的には単音の楽器だと思いますが生成されたものには、和音が多く含まれていたので不要なmidiノートを削除しました。

■pad

修正なしの状態でも使えそうでしたが、一応クオンタイズし、音色をさらに丸いものへ変更しました。

■簡単に構成を作ってみる

上記、修正した各パートをそれっぽく並べてみました。

以下、DTMの画面。
緑のトラックがオリジナルでオレンジのトラックが編集後のトラックになります。
Logic pro_3.png

■まとめ

サンプルメロディから拡張したての状態では、様々なノイズがあり聞きづらかったですが少し整えるだけでしっかり音楽になっている事がわかりました。
今回は既存のモデルを使用しましたが、トレーニングした独自のものを使用する事はもちろん可能です。
midiが不自然な位置に配置されない、また不協和音を発生させないようトレーニングできれば、作曲の一部アイディアとしても機能させる事が出来るのではと思いました。

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