- 投稿日:2020-12-16T23:40:19+09:00
CodePipelineがGHEをサポートすることになったよ〜
はじめに
- この記事は、KDDI Engineer Advent Calendar 2020の17日目の記事となります。
- 2020/9/30にAWS CodePipeline が GitHub Enterprise Server のサポートを開始するという発表がありましたが、このサポートによりCI/CDの仕組みについて見直そうとする方も多いのではないでしょうか。
- 本記事では、上記のような人、そもそもCodePipelineについて何だろうとなっている人のために、CodePipelineについてまとめたものとなっています。
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.パイプライン内の全てのステージ名は必ず一意であること。
参考
- 投稿日:2020-12-16T23:40:19+09:00
CodePipelineがGHEをサポートすることになった
はじめに
- この記事は、KDDI Engineer Advent Calendar 2020の17日目の記事となります。
- 2020/9/30にAWS CodePipeline が GitHub Enterprise Server のサポートを開始するという発表がありましたが、このサポートによりCI/CDの仕組みについて見直そうとする方も多いのではないでしょうか。
- 本記事では、上記のような人、そもそもCodePipelineについて何だろうとなっている人のために、CodePipelineについてまとめたものとなっています。
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.パイプライン内の全てのステージ名は必ず一意であること。
参考
- 投稿日:2020-12-16T23:32:46+09:00
【AWS】Kinesisを大体理解する
- 投稿日:2020-12-16T22:28:53+09:00
API Gateway と Lambda と Dynamo
はじめに
前回のものに API Gateway を繋いでみます。こんな感じがゴール
API Gateway
基本
GUI がかなり直感的でなので頭使わずに使える感じです
- エンドポイントのリソース
/project
作って- リソースにメソッド
GET
PUT
POST
DELETE
作って- メソッド毎にLambda のコード
project
にマッピングするだけ忘れがちなのが、APIの変更は「デプロイ」を押してデプロイしないといけないこと。これでとりあえず動くはず
APIキー
APIキーを使うときはちょっと面倒
- リソースの各メソッド毎にAPIキーでの認証を必須にする
- 「使用料プラン」を作成する(ここでAPIに対するレートリミットなどを設定)
- 「ステージ」を作成する(開発用とか商用とかそういう意味のステージのことで、ここでは
dev_1
とした)- 「APIキー」「使用料プラン」「ステージ」を関連づける
変更の反映に数分かかることもあるようですが、これで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 | jqLambda 統合プロキシ
「Lambda 統合プロキシ」にチェックを入れるだけでHTTPリクエストに含まれる様々なパラメターがそのまま Lambda に渡される。
例えば、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 }クライアントからのデータ
body
はevent['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うむ、動いたぞ
- 投稿日:2020-12-16T22:03:48+09:00
クールなグルーをブログル(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ほとんど手を動かすことなく、以下のような環境ができちゃいました。
手順は当該リポジトリの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」、間のスペースの有無の違いは何故だろう。。。)有効化時に、Stack指定?それとも全部?と聞かれます。
ここでは、先ほど作ったStackを指定します。あとで選び直すことも可能です。SNS通知などの機能もありますが、まずはこれだけで、Amazon DevOps GuruによるDevOps生活がスタートします。
しばらくすると、解析メトリクスに追加されていることが確認できます。
テスト
ポチポチっとしただけでDevOps Guruは働き出してくれたようなので、仕事ぶりを確認してみます。
運用上のインシデントを擬似的に起こすため、サンプル環境のDynamoDBを困らせてみます。
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回叩きました。この辺は機械学習の判定になるので、ケースバイケースな気がします。)
グルー先生が、スタイリッシュに問題点を伝えてくれます。
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
- 投稿日:2020-12-16T22:03:47+09:00
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の起動部分でエラーがでるVulkan DriverでなくOpenGLで起動させる方法があると知り、OpenGLで起動させようとしてみた
さっきVulkanは駄目って言ったのに今度はVulkanを使え...?こんな感じで訳が分からなくなっていました
何が問題だったのか
- UE4のビジュアライズ方式は4.23を境に変更になっていることを知らなかった
OpenGL ES2 は 4.23 で非推奨となり、4.24 で除去されました。
- Ubuntuインスタンスではグラフィックドライバは手動で設定が必要なことを知らなかった
やったこと
- OpenGL対応しているUE4をインストール
- インスタンスのグラフィックドライバを変更
UE4をダウンロード
4.23で非推奨とのことだったので、バージョン4.22.1のUE4をダウンロードします。
通常のLinuxへのインストール手順を進めたあとグラフィックドライバの指定を変更するために、
TargeteRHIs=VULKAN_SMS
の行をコメントアウトし、TargeteRHIs=GLSL_430
のコメントを外して画像のようにします。
![]()
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 nvidia7.
./UE4Editor
コマンドを実行すると、UE4Editorを起動することができる
まとめ
- UE4はバージョンによってVulkanとOpenGLのどちらを使用するのか違うのでよく確認しよう
- OpenGLのバージョンが4.3以上になるグラフィックドライバを入れよう
参考文献
- 投稿日:2020-12-16T21:57:56+09:00
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を組み合わせる手法です。
実際にどのような構成で検証したのか、実装上ハマったポイントはどこか、など共有できればと思います。
構成
構成概要
作成した基盤の構成概要は下記です。
各リソースの役割
各リソースの役割を下記に記載します。
- 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レコード更新した際、永遠に際実行処理が繰り返されているように見えました。
いつまでも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を用いてもっと簡単に構成することができるようになっているかもしれません。
その辺りは未検証なのでなんとも言えないのですが、上記の構成で検討されている誰かが同じところでハマらないよう、参考になれば幸いです。
- 投稿日:2020-12-16T19:56:48+09:00
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 NoIngressSecurityGroupEFS+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 --versionGiNZAインストール
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に指定- 「作成」を押す
- 元の画面に戻る。テストが作成されているのでその状態で「テスト」ボタンを押す。
成功!(2回目以降の実行なので622msになってます。1回目は4秒以上かかりました)
終わりに
いくつかの検討を経て、ようやくサーバーレスで自然言語処理が出来そうです(EFSはストレージなので許容します)。
LambdaコンテナもEFSとのマウントも今年の機能っぽいです。去年検討していたら諦めていた事になります。AWSの機能追加速度には目を見張るものがあります。すなわち日々キャッチアップが必要という事になる訳で。大変ですw参考にさせて頂いた良記事
- 投稿日:2020-12-16T19:43:02+09:00
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.json
にconfirm:stg
コマンドを以下のように追記する
引数として実行する環境名STG
を渡すように記述します./package.json{ "scripts": { "generate": "rm -rf dist && mkdir dist && touch dist/index.html", "confirm:stg": "sh confirm.sh STG" } }uploadコマンドの作成
package.json
にupload: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.json
にclear: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.json
にdeploy: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:stg
、generate
、upload:stg
、clear:stg
を直列処理で順に実行する結果
以下のコマンドを叩くことで、
$ npm run deploy:stg
こういう対話が行われ
「STG」環境のデプロイを実行しますか? 実行する場合は「STG」と入力してください STG スクリプトを実行します。対象ファイルが
generate
(生成)され、
生成されたファイルをs3
にファイルシンク(同期)し、
cloudfront
のキャッシュをクリア(削除)する
という一連の流れをワンステップのコマンド入力で自動化できましたSTG環境以外に本番で使う場合、プレビュー環境で使う場合は、それぞれ
confirm:live
、confirm:pre
、deploy:live
、deploy:pre
という感じでそれぞれコピー、内容調整すれば良いです上のデプロイにプラスして、最後に作業の終了を
slackに通知するコマンドを入れても良いですね複数のプロジェクトに参加している場合は、
プロジェクトを跨いで仕事をしているうちにアップロード場所間違いや、
削除ファイル漏れなど間違いが起きかねません
デプロイの自動化が設定出来たら、安心ですね
- 投稿日:2020-12-16T19:14:46+09:00
インターナル通信でAurora MySQLからCloudSQLへレプリケーションする方法
概要
本記事では、AuroraMySQLからCloudSQLへインターナル通信にてレプリケーションする方法を紹介します。GCPのネットワーク周りで結構ハマりましたので参考になればと思います。
手順
1. AWSのVPC並びにGCPのネットワークの作成
まず、AWSとGCPそれぞれでVPCが無いと始まらないのでそれらの作成を行います。なお本記事ではAWS側をCloudFormationで、GCP側をTerrafromにて記述しています。
- AWS
cfn.ymlResources: 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 RDSDBParameterGroup4. 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" }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が作られたあとは作ったインスタンスは不要になるため削除します。(別の方法が有りましたら教えて下さい。)
↓が作られる
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" }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のネットワークは知らないとハマりどころが多いので参考になれば幸いです。
参考
以下の記事を参考にさせていただきました。ありがとうございあました!!
- 投稿日:2020-12-16T19:03:39+09:00
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 templateissueを見てみると
https://github.com/aws-amplify/amplify-cli/issues/2519以下の手順を実施すると解決するそう
- amplify/backend/api/(アプリ名)/buildフォルダを削除
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 0CLIのバージョンを上げてみると
'jsonString' argument missing or empty An error occurred during the push operation: 'jsonString' argument missing or emptyissueをみると同じように困っている人がいました。
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 template1回目と一緒のエラーですが同じ対応でも直りませんでした。
1回目のissueを追ってみると、どうやらamplify/backend/api/(アプリ名)/paramters.jsonに以下を追加しないといけない模様。{ "authRoleName": { "Ref": "AuthRoleName" }, "unauthRoleName": { "Ref": "UnauthRoleName" } }無事
amplify push
できました。
一件落着ならぬ三件落着ただ、3回目に関しては、毎回手動でファイルをアップロードするとか無理です。しんどいです。助けてください。この対応さらっと書いてますが、三つとも12月中に発生して切り分けやら調査でめっちゃ時間持ってかれました。issueにこれはまれなコンステレーションで発生しますとか書いてあるのがツラい。でもAmplify使えねーってなるのも嫌なのでどうにかしたい。CLIのソースを覗くときがやってきそうです。
皆さんもどうかお気をつけください。17日目はデザイナーおださん@odagiri_24です!ご期待ください!
- 投稿日:2020-12-16T18:52:48+09:00
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" が取得出来ます。
- 投稿日:2020-12-16T18:45:20+09:00
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.ymlParameters: 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}-Connection2GCP側(Terraform)
variables.tfvariable "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.tfresource "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.tfvariable "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を次のステップで利用するためメモします。
3. AWSリソースの作成
先程メモしたIPを以下の
IPCustomerGateway1
とIPCustomerGateway2
にそれぞれ設定します。また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}-Connection24. 作成したVPN接続の設定情報をダウンロード
作成した2つのVPN接続の設定情報をダウンロードします。AWSのコンソールからVPC → サイト間のVPN接続の画面で作成したVPN接続を選択し設定のダウンロードからダウンロードします。ベンダーはGenericを選択します。
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.tfvariable "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.tfresource "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 status
がBGP established
に変われば成功です。AWS
また、AWSについてもVPN接続のTunnel Detailsのステータスが4つともアップになっていれば成功です。
まとめ
今回はAWSとGCPをつなげるVPNの設定をCloudFormationとTerraformで行う方法を紹介しました。コピペで使ってもらえれば幸いです。
参考
本構成は以下の記事を大変参考にさせていただきました。
- 投稿日:2020-12-16T18:00:31+09:00
AmazonLightsailで最強なWordPress環境を手に入れる
やりたいこと
WordPressを使ってお手軽かつ、負荷にも耐えられるWEBサイトを構築したい。が、サーバを何台も用意してとか大袈裟なことはしたくなく、極力手間なく安く楽にやりたい。あとAWS上で全て構築したい。
システム構成
- Amazon LightsailでWordPressの管理を行う。
- Amazon Lightsailで起動したインスタンスでは配信せず、S3に静的ファイルを出力してそれをCloudFront経由で配信する。
構築手順
1.AmazonLightsailにてWordPress環境構築
プラットフォーム:
Linux/Unix
、アプリ+OS:WordPress
を選択し、任意のリソース名を入れて作成。ステータスが実行中になるまで待つ。最小インスタンス1ヶ月$3.5♪(2020/12現在)
数クリックだけで楽々構築♪2.WordPressの表示確認
- IPアドレスでアクセスし、上記のようなサンプルページが表示されるか確認
- 管理画面のアカウントを取得するためにLightsail専用のターミナルを起動し、以下コマンドを打ちユーザ名とパスワードを取得
cat /home/bitnami/bitnami_credentials- サンプルページ右下の
Manage
から管理画面にログインできるか確認3.S3のバケット作成
S3にて静的ページを保存しておく、非公開バケットを作成する。バケット名は全てのユーザ間でユニークにする必要があり、また作成後は変更ができないので、よく考えて命名すること。
ブロックパブリックアクセスは新しいアクセスコントロールリスト (ACL) を介して付与されたバケットとオブジェクトへのパブリックアクセスをブロックする
のみオフにして、後はオンにしておく。ブロックパブリックアクセスの説明はホント分かりにくい...
4.CloudFrontの設定
Webサイトを配信するためのCloudFrontの設定を以下の手順で行う。
Create Distribution
をクリック- Webの
Get Started
をクリック- 以下の通り設定
- Origin Domain Name:
作成したS3のバケット指定
- Restrict Bucket Access:
Yes
- Origin Access Identity:
Create a New Identity
- Grant Read Permissions on Bucket:
Yes, Update Bucket Policy
- Default Root Object:
index.html
- 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)のインストール
- WordPressの管理画面の、
Plugins
->AddNew
から StaticPress を検索し、インストールPlugins
->Installed Plugins
から StaticPress を探し、Activate
で活性化S3へアップロードするプラグイン(StaticPressS3)のインストール
- GitHub( https://github.com/megumiteam/staticpress-s3 )にアクセスし、ZIPファイルをダウンロード
- WordPressの管理画面の、
Plugins
->AddNew
->Upload Plugin
からダウンロードしたZIPファイルを選択しインストールPlugins
->Installed Plugins
から StaticPress S3 を探し、Activate
で活性化静的ファイルの出力先とAWSのアクセスキーの設定
- WordPressの管理画面の、
StaticPress
->StaticPress Options
を開き、以下を入力
- Static URL:
https://xxxxxxxxxxxxx.cloudfront.net/
- Save DIR (Document root):
/opt/bitnami/apps/wordpress/htdocs/static/
Save Changes
を押して保存- 続いて、その下に表示されている以下を入力
- AWS Access Key:
xxxxxxxxxxxxxxxxxxxx
- AWS Secret Key:
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- AWS Regin:
AP_NORTHEAST_1
- S3 Bucket:
設定したS3バケット名
Save Changes
を押して保存7.S3へ静的ファイル出力
- WordPressの管理画面の
StaticPress
のページからRebuild
ボタンを押下- Endが表示されるまで待つ
- 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 -> 任意のドメインに変更すれば完璧。
こういったサービスは、少し毛嫌いしていたが、思った以上に使い勝手良かったので、今後使う機会ありそう!
- 投稿日:2020-12-16T17:49:08+09:00
AWS CloudShell で Amazon EKS の作業環境を整える
AWS その2 Advent Calendar 2020 の10日目が空いていたので登録させていただきました。
はじめに
AWS re:Invent 2020 の Werner Vogels Keynote で AWS ClouShell が発表されました。
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 アイコンをクリックするだけです。
- とりあえず、ぱっと思いついたものを入れています
- バージョンなどは適宜読み替えてください。
- クラスターと 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/binyum でインストールするパッケージなどは永続ストレージ (
$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>&1kubectl 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簡単ですが、以上です。
- 投稿日:2020-12-16T17:13:20+09:00
【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」用に作成したターゲットグループを割り当てます所感
本当に奇行だと思いますし、やらないと思いますが、
・サーバ構築するのめんどい ・ただ動くのみたいだけだからひとまず開発環境として作ってみる ・動いた ・プログラム問題ないな、じゃあ捨てますとかしたいときにいいと思います。
- 投稿日:2020-12-16T15:36:35+09:00
AWS ECS vs. Kubernetes 〜コンテナオーケストレーションプラットフォームの比較〜
はじめに
コンテナを使用することで大きな利点を与えてくれる一方で、その環境を増やすことは、それと同時に難しい選択を迫られるということでもあります。私たちは、自分たちの状況に最適なオーケストレーションツールは何か、そしてシステムをどのように監視するかについて考えていく必要があります。 Dockerはコンテナーランタイムの標準ですが、コンテナーオーケストレーションツールから選択するオプションは複数あります。これらのリーダーは、AmazonのElastic ContainerServiceとCNCFのKubernetesです。実際、調査によると、企業の83%がコンテナオーケストレーションソリューションとしてKubernetesを使用しているのに対し、ECSは24%にとどまっています。
この記事では、MetricFireのコミュニティメンバーがKubernetesとAWSECSの両方について解説していきます。モニタリングの観点から、GrafanaとPrometheusはKubernetesに大きく利点があり、Kubernetesのサポートが多数利用可能ですが、AWSECSを使用することについてに議論は続けられるべきです。無料トライアルでプラットフォームにアクセスすると、両方のオーケストレーションプラットフォームから送信されたメトリックを試してみることができます。
この記事では、ECSとKubernetesを比較対照して、ユーザーがどちらを使用するかを決定できる判断材料を提供していきます。
コンテナオーケストレーションにKubernetesを使用する利点
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を使用する利点
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にご連絡ください。喜んでお手伝いさせていただきます。 ビデオ通話でのデモを予約するか、無料トライアルにサインオンしてください。カスタマーサポートのチャットボックスから直接お話しいただけます。
- 投稿日:2020-12-16T14:36:16+09:00
Alexaでサーバーやデータベースの起動・停止制御をしたい
はじめに
こんにちは。NTTドコモの矢吹です。
私のチームでは、ドコモの大規模データをストリーム処理しており、AWSを利用してシステムを開発しています。そのため、開発環境だけでも結構な費用になります。しかし、節約のためにサーバーやデータベースをこまめに停止したり起動するのは面倒くさいし、つい忘れてしまいがちです。そこで、Alexaを使って「アレクサ、開発環境でデータベース停止しておいて」という風に制御できれば何かと便利ですよね。ついでに、これで節約できれば「最近費用が予想より大幅に上振れしてる、アカン・・」と頭を抱えている上司に恩を売ることもできます。
そこで、今回はLambdaやAlexa Skills Kitなど触ったことのない素人が勉強がてら、AlexaでAWSリソースの起動・停止制御を行うスキル開発に取り組んでみます。
尚、「平日の勤務開始時間に起動して終了時間に停止するcronを書けばいいじゃん」「サーバーレスで開発しろよ」などの意見はごもっともですが、ここでは受け付けないこととします。目標
「アレクサ、開発環境でWebサーバー起動して」
「アレクサ、開発環境でデータベース停止しておいて」
といった感じで、音声だけでAWSリソースの起動・停止制御を行う。準備するもの
対象とする人
- Alexaスキルの開発の流れをざっくり掴みたい人
- AlexaでAWSのリソース制御 (EC2やRDSの起動・停止)を行いたい人
参考にした資料
下記の資料を大変参考にさせていただきました。
- Alexa Skills Kit(ASK) ドキュメント
- Alexa Skills Kit SDK for Python
- Amazon Echo (Alexa) のSkillの開発に必要な基本概念を押さえる
- AlexaスキルをPython/Lambdaで実装する
実装
Alexaスキルを作るには、音声入力のインターフェースの作成とリクエスト内容に応じて処理を行うバックエンドの実装が必要です。インターフェースの作成はAlexa開発者コンソールでWeb画面を操作しながら行います。バックエンドはPythonで実装し、Lambdaで実行するようにしたいと思います。
インターフェースの作成
まずは、Alexa開発者コンソールでインターフェースを作成していきます。今回は日本語のスキルでAWSアカウントのLambdaでホスティングしたいため、下図のように選択し、スキルを作成します。
以上で基本的なテンプレートが作成されるので、呼び出し名やIntent, Slotなどの設定をしていきます。
Invocation Name
Alexaが作成したスキルに応答できるようにInvocation Name(呼び出し時のキーワード)を設定します。「開発環境でWebサーバー止めて」のように呼び出したいため、「開発環境」をキーワードとして設定しました。
Intent
次にIntentを作成します。ドキュメントでは、Intentは下記のように説明されています。
少しわかりづらいですが、自分なりに触ったりして解釈した結果、ある目的の音声リクエストを認識するための機能とすると腑に落ちました。Intentが音声リクエストを正しく認識できるように発話サンプルを定義してあげる必要があります。「Webサーバー止めて」「データベース起動して」のような発話が考えられますが、全て網羅するのは大変です。なので、
{resource}を{action}して
のように発話サンプルに引数を与えることができると何かと便利です。この引数のことをSlotと呼びます。今回は下図のようにResouceControlという名前のIntentを作成しました。発話サンプルは考えられるバリエーションをたくさん作ってあげると認識率が上がります。
Slot
次に先ほど説明したSlotを作成します。まずはSlot Typeのタブに移動し、以下のように
resource
を定義します。後のバックエンドの実装でこの値とリソースIDを紐付けて制御できるようにします。
続いて
action
を定義します。今回はリソースの起動と停止を行いたいので以下のようにしました。類義語も登録するとより汎用性が高くなります。
そして、再びIntentのタブに戻り、今定義したSlot TypeとIntent Slotを紐付けます。
これでインターフェイスの実装は一通り終わりました。ページ上部にある、Save Model と Build Model のボタンを押してモデルをの保存とビルドを行います。非常に簡単ですね。
バックエンドの実装
次にバックエンドの実装を行います。今回はせっかくなので先日発表されたLambdaのコンテナイメージサポート を試してみたいと思います。
フォルダ構成は下記のようになります。alexa/ ├── Dockerfile ├── app.py └── resource.jsonAWSが提供する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.pyimport 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ロールを作成し、それを使用するようにします。
関数作成後、LambdaのARNをコピーして再びAlexa開発者コンソールに戻り、下記のようにエンドポイントの設定を行います。
Lambdaの設定画面に戻り、下記のようにトリガーを設定します。
以上で実装は完了です。Alexa開発者コンソールに戻り、作成したスキルの動作確認をしましょう。
動作確認
テキスト入力による動作確認
テストタブに移動し、下記のようにスキルの動作確認を行うことができます。正しく動作していそうですね。AWSコンソールで確認したところ、きちんとデータベースは起動されていました。
音声入力による動作確認
・・・自分が滑舌悪いこと忘れてました。
対象とする人(改)
- Alexaスキルの開発の流れをざっくり掴みたい人
- AlexaでAWSのリソース制御 (EC2やRDSの起動・停止)を行いたい人
- 滑舌が良い人
おわりに
Alexaスキルの開発を一通り体験してみましたが、IntentやSlotなどの概念さえを理解できれば意外と簡単に作れるんだなというのが作ってみての所感です。また、滑舌が悪い人には音声インターフェースは扱いづらいなということを改めて実感できました。ここまで作っといてアレですが、このスキルは使わずにシェルスクリプトを書いて実行するようにしようかなと思います。
- 投稿日:2020-12-16T13:42:25+09:00
【IT初心者向け】AWSとは?を解説【AWSの特徴をイメージ】
はじめに
IT初心者「AWSって何なの?どんなことができるのか分からない。。。AWS使えるとどんな仕事ができるんだろう??」
このような悩み、疑問に答えます。
本記事の内容
・「AWSとは?」を解説
・AWS活用事例何となくAWSのイメージをつかみたいという方向けにまとめています。
記事の信頼性
筆者はAWS経験5年程度です。AWS資格は5冠達成しました。
現在は大規模ECサイトのAWS運用を任されるようになっています。「AWSとは?」を解説
「AWSとは」でググると1件目に表示されるAWS公式ページに上記の悩みを解決できそうなことが書いてありました。
AWSとは(公式ページ)AWSはスタートアップ、大企業、政府機関まで業種・業態問わず広く採用されているクラウドプラットフォームです。
※クラウドプラットフォーム=アプリケーション実行やデータ保存ができる基盤を、WEB上で展開しているもの
※イノベーション=ビジネスや人々のライフスタイルに革新を起こすこと本記事ではこのAWS公式ページを抜粋しながら解説していきます。
AWS利用イメージ
・パソコンのブラウザから使いたいシステムをクリックすると使える(従量課金=使った分だけの課金)
・AWSには多くのサービスがあり、サービスを組み合わせてシステムを実現する
(よくビルディングブロックと呼ばれる)
AWSのサービス・機能数
豊富な機能がある理由はユーザの要望を反映していった結果とのことです。
実際に使ってて「今までできなかったことができるようになってる!!」というのはよくありますね。AWSは他社のクラウドプロバイダと比較して最もサービス数が多いと謳っています。
AWSとよく比較されるMicrosoft Azure、Google Cloud Platform(GCP)もこの考え方は同様なので、3社の機能的な違いはほぼ無くなっていくと言われています。ベンダー(パートナー)の質が高い
頼れるパートナーが多いというのはAWS利用開始の際に大きなメリットになります。
AWS導入をベンダーに任せるのであれば、まずは日本の APN プレミアパートナーから検討してみるのが良いでしょう。セキュリティに不安はない
今はそうでもないかもしれませんが、昔はクラウドはセキュリティが不安だと言われていましたので、セキュリティ水準の高さについて触れておきます。
AWSは最も高いセキュリティを要求されると思われる軍隊や銀行の要件を満たす設計なので、
一般的な企業でセキュリティ関連で困ることはありません。
※政治的な理由(「怖いのでクラウドは使わない方針でいく!」などの謎文化含む)に困らされることはありました。AWSのクラウド業界での立ち位置
・ガートナーのマジッククワドラントについて
ガートナーはIT分野のトレンドを発信している会社です。
マジッククワドラントは「特定市場におけるテクノロジ・プロバイダーを位置付け」したものです。
AWSは「リーダー」ですが、これはガートナーから「ビジョンを実行できており、将来のポジションを確立しています。」という評価を受けたことになります。AWS活用事例
ここまでで「AWSとは」の部分を解説してきましたが、これだけだとまだ具体的なイメージがつかめないと思います。
ここからは活用事例の紹介で、事例を見ていくとAWSで実現されていることやそのメリットが分かってきます。
AWS公式ページではスタートアップからエンタープライズ、公共事業まで様々な業界での事例が紹介されています。
その中でも面白いと思った事例を載せています。Formula One インサイト
F1 がデータ(スピード、位置、ハンドル角度など)を収集し、活用して意思決定するのにAWSのAIサービスを利用しています。
「カーパフォーマンススコア」の算出でファン向けにデータが活用されています。リアルタイムに秒間110万件以上のデータを処理しているそうです。
参考:F1、人工知能を用いて車両開発状況やマシンパフォーマンスをリアルタイムで可視化
エンタープライズ
Expedia
・旅行に関するオンライン予約を扱うウェブサイト、アプリ運営
・営業利益: 9.03億アメリカ合衆国ドルミッションクリティカル(障害や誤作動などが許されない)なアプリがAWSへ移行されています。
エンタープライズ(大企業)でミッションクリティカルなアプリがAWSで実現されているということからもAWSの高い信頼性を理解できると思います。
導入効果で大きかったのはアプリケーションリリース速度の向上で20分で行われるようになったと言っています。Intuit
米国で圧倒的シェアのクラウド会計ソフト会社
収益: 67.84億アメリカ合衆国ドル・オートスケールで拡張性を実現
・税制の変化への対応を迅速に実施(CI/CD)
・データレイクはセキュリティ、コンプライアンス性の高い情報を扱う(HIPAA)
・機械学習は必要なリソースの予測に利用スタートアップ
airbnb
宿泊施設・民宿を貸し出す人向けのウェブサイトを運営。
ある時から成長の速度が加速度的に増加したそうです。
それでも5人でシステム運用できているのはAWSの拡張性を利用できているおかげだと言っています。
mapbox
カスタマイズ自在・リアルタイムな地図情報が表示可能な開発者向け地図システムを提供しています。
※Yahoo MAPが2019年にMapboxを使用開始したようです。
https://map.yahoo.co.jp/blog/archives/20191015_map_mapbox.htmlLyft
収益: 36.16億アメリカ合衆国ドル
配車サービスを提供しています。
※日本ではUberがおすすめだがアメリカではLyftが断然安いとのことです。
参考:Uber(ウーバー)とLyft(リフト)の違いは?アメリカなら、Lyftが断然お得でおすすめ!それに対応する為にオートスケール導入やデータベース変更などを実施していったそうです。
Nextdoor
近隣地域向けのプライベートソーシャルネットワーク
ご近所SNSと呼ばれています。日本版も2019年あたりにローンチ
参考:地域SNSのネクストドア、巨大広告ビジネスをひそかに構築:検証した住所情報を駆使して公共
FINRA(金融業界規制当局)
Financial Industry Regulatory Authorityの頭文字をとったもので、投資家保護や証券取引の透明性の確保、不正行為の摘発などを目的に、米国において証券会社などの行動を監視・規制する組織。政府機関ではなく非営利の民間協会として運営されている。
参考:証券用語解説集20ペタバイトを超える数兆のレコードデータの監視しているといいます。
AWSを利用することで下記の様なことを実現しています。
・Secure and Auditable:安全性と監査性
・Scale & Elasticity:拡張性
・Years Ahead- Constantly Innovating:先を見据えて-常に革新し続ける
米国食品医薬品局 (FDA)
連邦食品・医薬品・化粧品法を根拠とし、医療品規制、食の安全を責務とする団体です。
※アメリカ合衆国保健福祉省(Department of Health and Human Services, HHS)配下の政府機関
AWS利用によりこれまで5か年計画だったIT戦略が6か月で解決できるようになったと言っているのは印象的でした。
毎年数百万の有害事象(薬物を投与された患者に生じた好ましくない徴候)レポートを処理していて、AWSによって様々な課題が解決されています。
例えば、管理すべき大量の情報を紙からデータ化してWebで検索できるようにした事例が紹介されていました。
さいごに
AWSの特徴(運用性、拡張性、信頼性、セキュリティ、コストにメリットがあること)をイメージ頂けたでしょうか?
AWS詳しいエンジニアはまだまだ少ない印象です。
本記事を読んだことでAWSに興味を持っていただける方増えたら嬉しいです。※AWS興味持った方には下記の記事も参考になるかと思いましたので、参考にしてみてください。
【AWS初心者向け】AWS学習方法まとめ【15時間で達成できる】
- 投稿日:2020-12-16T12:59:53+09:00
1−1.WEB系エンジニアになるためのインプット(準備)
はじめに
メーカ系SIer企業で働いているのですが、少し物足りなさを感じております。
そこで副業の一環として、まずはWEB系エンジニアを目指すために「web系エンジニアになろう」(著:勝又健太)を読了しました。
まずは基礎を固めるためにインプットを行うが、併せて内容をアウトプットするために本記事を作成していく。学習フロー
既にコンピュータサイエンスの基礎知識やクラウドサービスであるAWSの知見を有しているので、下記のように進める。
- Linux基礎
- JavaScript基礎
- RDBとSQL基礎
- GitとGitHub基礎
- Ruby基礎
- 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基礎のアウトプット記事を掲載する。
- 投稿日:2020-12-16T12:56:58+09:00
【AWS】CloudshellでAssumeRoleできるCredential設定を書く
本日発表の期待のサービス
会社ではマルチアカウント運用しているので、早速Assume/credenatial周りを確認してみたTL;TD
- .aws/credentialファイルを作成して、こんな感じに記述する
[test-assume] role_arn = arn:aws:iam::xxxxxxxxxxx:role/test-asusme credential_source = EcsContainerAWSCLIのAsusmeRoleの仕組み
- credentialファイルに定義しておくと自動でProfile選択時にやってくれる
- https://awscli.amazonaws.com/v2/documentation/api/latest/topic/config-vars.html#using-aws-iam-roles
- 今回だとcredential_sourceを使えば行けそう *
aws sts assume-role
のコマンドを打って認証情報取得してもいいけど、上の仕組みの方が楽です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テストすることはなくなるかなー。安全になるし、嬉しいです
- 投稿日:2020-12-16T12:42:14+09:00
【ズボラ】簡単にS3署名付きURLを発行したい【手抜き】
経緯
?「非公開S3バケットのデータを期間限定で公開したい」
俺「aws-cli叩いてpresign発行したら?」
?「コマンド叩きたくないでござる」
俺「・・・あ!マネコンから開いたら5分限定で見れたはず!」
?「ネ申!」
俺「・・コマンド叩いたら良いのに」という流れがあったので、S3署名付きURL発行の手抜き版です。
前提
- すでにS3バケットは非公開で作成済み
- S3触れるIAMアカウントでデータも保存されている
- そのアカウントの持ち主はコマンド嫌い
手順①:メニューから開く
S3のマネジメントコンソールにて当該ファイルを選んで、アクションメニューから「開く」を選択
手順②:見れた!
手順③:URL共有
この閲覧時のURLが実は5分限定で見れるという。。
(めちゃくちゃ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入っているので環境準備も楽だと思います。
参考
AWS CLI コマンドリファレンス
【小ネタ】AWS CLIでS3のPre-Signed URLを生成できるようになっていました!
- 投稿日:2020-12-16T12:33:33+09:00
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.ymlversion: 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.rulesetHow it worksにあるサンプルコードではjsonですが、yamlに置き換えたebs_volume_template.ymlとebs_volume_template.rulesetをCodeCommitにpush。
ビルドを実行し、3件のエラーが発生することを確認。AWS::EC2::Instanceで試してみる
CFnテンプレートの作成
サンプルではEBSだが、実運用ではEC2 Instanceで作成することが多い。同様のチェックをEC2 Instanceでも試してみる。
適当なEC2を作成するCFnテンプレートを作成。ec2_template.ymlResources: 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-XXXXXXXXXImageIdなど単純な比較となる項目は自動生成でも問題ないが、BlockDeviceMappingsなどの多階層の項目の値をチェックには使えそうにないですね。
リスト、かつ他階層の項目のため、ワイルドカードを利用し、以下のようなルールセットを作成ec2_template.rulesetlet encryption_flag = true AWS::EC2::Instance BlockDeviceMappings.*.Ebs.Encrypted == %encryption_flag AWS::EC2::Instance BlockDeviceMappings.*.Ebs.VolumeSize <= 100実行結果の確認
チェック対象のテンプレート、ルールセットが作成できたため、CodeCommitのリポジトリへpush。
チェック対象のテンプレートが変更になったため、buildspec.ymlの一部を修正。buildspec.ymlversion: 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件になっている。)
原因調査、対応
ドキュメントをもう一度確認してみたところ、Wildcard Semanticsに比較時の注意点書いてますね。
先ほどの記載ではいづれかがtrueならば結果OKとなってしまうため、全てfalseではないという条件へ修正。ec2_template.rulesetAWS::EC2::Instance BlockDeviceMappings.*.Ebs.Encrypted != false AWS::EC2::Instance BlockDeviceMappings.*.Ebs.VolumeSize <= 100実行し、Encryptedのエラーが発生することを確認。
Encryptedが指定されていないデバイスもエラーにしたい
「DeviceName: /dev/sda3」は「Encrypted」が指定されていないが、先ほどの結果ではOKと見なされている。
暗号化されていないディスクが作成されてしまうため、未指定も結果NGとなることが望ましい。
項目がない場合結果NGとなるよう--strict-checksオプションを指定するようbuildspec.ymlを修正。buildspec.ymlversion: 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まとめ
実際に使ってみないとわからないことが多いので、とりあえず試すことは重要ですね。
また、現状ではサンプルでは1テンプレート、1rulesetになってますが、実運用を考慮した際、テンプレートごとにrulesetを作成することは現実的ではありません。
どの単位でCFnテンプレートを作成するのかにもよるが、Strict Checks用のrulesetとそれ以外のrulesetの2つで運用するのがベターなのかな?
ここら辺は運用しながら自分なりの答えを見つけたいと思います。最後に今回作成したテンプレート、ルールセット、buildspec.ymlは以下となります。
ec2_template.ymlResources: 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-XXXXXXXXXXXXXXXXXec2_template.rulesetAWS::EC2::Instance BlockDeviceMappings.*.Ebs.Encrypted != false AWS::EC2::Instance BlockDeviceMappings.*.Ebs.VolumeSize <= 100buildspec.ymlversion: 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
- 投稿日:2020-12-16T12:22:21+09:00
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 を起動するためのボタンです。
このアイコンをクリックすると、ブラウザタブが新しく開き、初期設定が行われます。
そして、以下のようなガイドが表示されるので、適宜「Do not show again」にチェックを入れるなどして、Closeボタンをクリックします。
右上にある Actions はドロップダウンメニューになっています。
タブレイアウトを整えたり、ファイルのダウンロードやアップロードが行えます。
また、CloudShellの再起動や、ホームディレクトリの削除もここから行えます。
歯車アイコンでは、画面表示やコピペ時の注意表示をするかどうかの設定も行えますので、適宜設定をするとよいでしょう。
また、CloudShellの再起動や、ホームディレクトリの削除もここから行えます。
というわけで、つかってみた。
まずは基本的な使い方であると思われる、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.2AWS 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さらに、 Exit + x を入力し、 hanoi と入力すると。。。
ハノイの塔を眺めることができます。
同様に、Exit + x を入力し、 gomoku とすると。。。
五目並べで遊ぶことができます。矢印キーで移動し、スペースキーで石を置く場所を決めますが、全然勝てません。
おまけ
現状は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 を使うことでこういった作業からも解放され、インスタンスや関数の管理や始末をしなくてもよくなりました。
しかも、追加費用なしで利用可能なんて、驚きです。この機能、待ってました!
記載されている会社名、製品名、サービス名、ロゴ等は各社の商標または登録商標です。
- 投稿日:2020-12-16T10:51:10+09:00
【React/Go】ユーザー認証をCognito + Amplifyで構築してみた ~ユーザ登録/削除編~
はじめに
Reactで作成したWebアプリケーションのユーザー認証部分をCognito + Amplifyフレームワークで構築してみました。構築の基本部分については「【React】ユーザー認証をCognito + Amplifyで構築してみた」の構築準備編と構築完成編をご覧ください。
本記事は、アプリケーションからユーザーを登録、削除する方法についてまとめています。完成画面
今回は、アプリケーションからユーザーを登録すると、ユーザープールとDBそれぞれにユーザーが登録されて、画面には「ユーザーを登録しました。」というアラートが出力されるようにします。
[Submit]をクリックすると↓↓↓
DB(userテーブル)※一部抜粋
+----+-----------+------------------+ | id | user_name | email | +----+-----------+------------------+ | 2 | test | test@example.com | +----+-----------+------------------+方法検討
要件
構築方法を考えるにあたり、条件は以下の通りです。
- 静的コンテンツをS3に置いている
- アプリケーション部分はLambda + RDS Proxy + RDSで実装している
- ユーザーデータはCognitoユーザープール以外に、RDSに保存している
- NATゲートウェイはコストが高いので使いたくない
現在の構成図(ユーザー認証付加前)
ユーザー認証付加前のアプリケーション部分の構成図は下記の通りです。
VPC Lambdaによる弊害
ここで、LambdaをVPC内に設置していることで、Cognitoにアクセスできないことに気付きました。パブリックサブネットに置いているんだから、アクセスできると勝手に思っていました。
AWS開発者ガイドによると、次のように説明されています。
プライベートリソースにアクセスするには、関数をプライベートサブネットに接続します。関数にインターネットアクセスが必要な場合は、ネットワークアドレス変換 (NAT) を使用します。関数をパブリックサブネットに接続しても、インターネットアクセスやパブリック IP アドレスは提供されません。
Lambda関数をパブリックサブネットに接続しても、インターネットアクセスやパブリック IP アドレスは提供されないんです。NATゲートウェイを使用する場合にもLambda関数はプライベートサブネットに置くべきだそうです。パブリックサブネットにLambdaを置いておくメリットはなさそうなので、VPC Lambdaはプライベートサブネットに置きましょう!!!
結論
この条件に沿ってアプリケーションの登録、削除処理を考えた結果、VPC Lambdaをプライベートサブネットに移動させ、NATゲートウェイは使いたくないので、強引にLambdaからLambdaを呼び出すことにしました。
シーケンス図
シーケンス図を書くと次のようになります。
構成図(ユーザー認証付加後)
構成は下図の通りになりました。
手順
下記の流れで進めていきます。
- RDSを更新するLambda関数:Lambda(VPC) の作成
- Cognitoを更新するLambda関数:Lambda(非VPC) の作成
- API Gatewayの作成
- フロントの実装
ユーザーを登録する
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.gopackage 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.gopackage 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.jsimport 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
を使用します。実行結果
無事冒頭の完成画面のように動くようになりました!
おわりに
LambdaからLambdaを実行することで、NATゲートウェイを使わずにCognitoとDBの両方にユーザーを登録することができました!今考えると、Cognitoユーザープールに登録するのはAmplifyでAdmin Queries APIを使うようにして、DBに保存するのは既存のようにLambdaを呼び出すようにするのでも良かったかなとも思います!
次回は、サインインページにある、使用しないアカウント作成ボタンを消したいと思います!
- 投稿日:2020-12-16T10:45:56+09:00
【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時間)を忘れがちです。
月末の夜間に動かしたいにも関わらず、月初の昼間に動いてしまってアラートが挙がる!といったことの無いよう、設定する際には必ずこのマニュアルを見ることにしました。
- 投稿日:2020-12-16T10:01:25+09:00
ゲームの分散ビルド on AWS
AWS & Game Advent Calendar 2020の16日目の記事です。
はじめに
ゲーム業界ではかねてより分散ビルドソフトウェアが使われており、古くはdistcc(今でも使っている会社はあるのでしょうか)、近年ではSN-DBS(SIEのハード向け)やIncrediBuildが使われるケースが多いかと思います。
このIncrediBuildですが、公式サイトにもAWSに対応していると書かれているものの具体的にどうすれば使える様になるかイマイチ周知されていません。
そこで本記事ではIncrediBuildのビルドノード(Agent)としてAWSのEC2インスタンスを追加する事が出来る「IncrediBuild Cloud」を利用するまでの最短手順を追ってみます。手順
IncrediBuild Cloudを使うのに必要な手順は以下の通りです。
- 動作環境を整える
- IncrediBuild Cloudのセットアップ
- インスタンスの起動確認
- ビルド確認
えらいシンプルですが、これらを順を追って説明します。
動作環境を整える
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のライセンスについては説明を省いていますが、当然必要になります。
詳細はライセンス販売会社にご確認下さい。
- 投稿日:2020-12-16T09:21:01+09:00
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→ こちらも同じく変化なし
解決した方法
Amplify > アプリの設定 > Admin UI management の Access control settings から 「Invite users」 でユーザを作成する
以下のURLにアクセスしログインすると、CLIとの連携に失敗した旨とコマンドが表示される
https://ap-northeast-1.admin.amplifyapp.com/admin/hoge/prod/verify/
指定されたコマンドを実行する
% amplify configure --appId hoge --envName prod Opening link: https://ap-northeast-1.admin.amplifyapp.com/admin/hoge/prod/verify/ ✔ Successfully received Amplify Admin tokens.成功。
これで正常に動きました。参考
- 投稿日:2020-12-16T08:42:57+09:00
AWS CloudShellで遊んでみる
AWS CloudShellが発表されました。AWSコンソールからプリセットされたシェル環境を操作できるようになっています。
基本的な使い方は公式で。
https://aws.amazon.com/blogs/aws/aws-cloudshell-command-line-access-to-aws-resources/
AWSコンソールにアクセスするとヘッダ部分にCloudShellへのアクセスボタンが表示されるようになってます。
アクセスしたらこんな案内が表示されました。
- 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.9homeフォルダ以外のデータは残るか?
アクセスしているhomeフォルダはデータが残るようですが、それ以外のフォルダはデータが残るのか、以下の手順で確認。
- テキストファイルを作成(/home/cloudshell-user/test.txtと/text_root.txtを作成)
- 一度タブをクローズ
- 再度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に対するアクセスなんかがサポートされるようになれば、さらに用途は広がりそうですし、また一つ便利になる機能がリリースされました。
- 投稿日:2020-12-16T05:59:11+09:00
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パートが自動生成されました。
画面上部「Download composition」よりMIDIデータでダウンロードする事が出来る為、こちらからダウンロードし、ここから自前のDTMソフトへ読み込み、簡単な修正を行っていきます。
修正前の全体は以下のようになっていました。
— ABC (@ABC47696092) December 15, 2020リズムに統一感がないのと、エフェクトも何も加工されていないので少し聞きづらいですね‥
これから1パートずつ分析と修正を行い、曲のパートとして聞きやすいように加工していきます。■piano
まずは基本となるpianoパートですが、こちらサンプルそのままですのでmidiノートは特に修正していません。
音色はアコースティックなピアノ音源とエレクトリックピアノを準備しました。
※midiノートとは
MIDI(ミディ、Musical Instrument Digital Interface)
電子楽器の演奏データを機器間で転送・共有するための共通規格になります。
ノートとは、どの場所でどの音の高さかなどの情報です。■drum
ドラムパートに関してもエレクトリックな音源へ差し替えをしました。
midiノートの無駄な重複を削除し、小説頭の認識が間違っていたので全体を1拍後ろへずらしました。
— ABC (@ABC47696092) December 15, 2020■guiter
ギターパートはまず全体的なヨレが目立ったのでmidiノート全体に16分のクオンタイズを行い、多すぎるmidiノートを一部削除しました。
音色はギターのままで残したかったですが、馴染みが悪かったためマリンバの音色へ変更。
リズムの雰囲気が分かりづらい為、リズミカルに演奏されるように、付点のディレイを設定しました。
— ABC (@ABC47696092) December 15, 2020※クオンタイズとは
MIDIシーケンサの機能の一つで演奏データのタイミングのズレを補正する効果の事。■bass
ベースは基本的には単音の楽器だと思いますが生成されたものには、和音が多く含まれていたので不要なmidiノートを削除しました。
— ABC (@ABC47696092) December 15, 2020■pad
修正なしの状態でも使えそうでしたが、一応クオンタイズし、音色をさらに丸いものへ変更しました。
— ABC (@ABC47696092) December 15, 2020■簡単に構成を作ってみる
上記、修正した各パートをそれっぽく並べてみました。
— ABC (@ABC47696092) December 15, 2020以下、DTMの画面。
緑のトラックがオリジナルでオレンジのトラックが編集後のトラックになります。
■まとめ
サンプルメロディから拡張したての状態では、様々なノイズがあり聞きづらかったですが少し整えるだけでしっかり音楽になっている事がわかりました。
今回は既存のモデルを使用しましたが、トレーニングした独自のものを使用する事はもちろん可能です。
midiが不自然な位置に配置されない、また不協和音を発生させないようトレーニングできれば、作曲の一部アイディアとしても機能させる事が出来るのではと思いました。