SendgridのEvent WebhookログをAWSに連携する
sendgridを利用しており送信履歴をログに残したい要件がありました。
対応する機能としてSendgridではWebhook機能があり、
メールを送信や開封等のイベントを
指定のエンドポイントに送信できます。
サンプルの構成
実際の構成はどのようになるかと言うと、
公式のブログにサンプル構成があり、
こちらがとても参考になりました。
ただ、何点か構成を変更したい点がありました。
構築したい構成
上記サンプルとの違いは下記です
ログはS3に格納する
数量が膨大になりそうでしたので、より料金単価が安いS3にしました。
Lambdaを利用しない(apigatewayとs3を直接連携)
背景として、今回のケースではメールのイベントログを格納したいだけであり、
何か加工等の特別な処理をさせたい訳でなかったため、
Lambdaを利用する必要がありませんでした。
Lambda経由でS3に格納しても要件実現はできてしまうのですが、
特に、Lambdaのスケーラビリティを心配したくないことや、
ソース管理の必要があるといったデメリットを抱えてしまうため
利用しない形としました。
CDKで書きたい
サンプルで書いてみました。
apigateway周りでもう少し細かい設定を付与したいですが、
最低限のものとお考えください。
import { Stack, StackProps, RemovalPolicy, aws_apigateway as apigateway, aws_s3 as s3, aws_iam as iam, aws_certificatemanager as acm, aws_route53 as route53, aws_cognito as cognito, aws_route53_targets as targets, } from 'aws-cdk-lib'; import { Construct } from 'constructs'; const scopeName = 'activity'; type Props = { hostedZoneId: string; domainName: string; domainPrefix: string; bucketName: string; } & StackProps; export class SendgridWebhookS3Stack extends Stack { constructor(scope: Construct, id: string, props: Props) { super(scope, id, props); const { hostedZoneId, domainName, domainPrefix, bucketName } = props; const bucket = new s3.Bucket(this, 'Bucket', { bucketName: bucketName, removalPolicy: RemovalPolicy.DESTROY, }); const fullAccessScope = new cognito.ResourceServerScope({ scopeName: '*', scopeDescription: 'Full access' }); const pool = new cognito.UserPool(this, 'Pool', { removalPolicy: RemovalPolicy.DESTROY, }); const resourceServer = pool.addResourceServer('ResourceServer', { identifier: scopeName, scopes: [fullAccessScope], }); pool.addDomain('CognitoDomain', { cognitoDomain: { domainPrefix: domainPrefix, }, }); pool.addClient('appClient', { generateSecret: true, oAuth: { flows: { clientCredentials: true, }, scopes: [cognito.OAuthScope.resourceServer(resourceServer, fullAccessScope)], }, }); const auth = new apigateway.CognitoUserPoolsAuthorizer(this, 'Authorizer', { cognitoUserPools: [pool], }); const restApiRole = new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), path: '/', }); bucket.grantReadWrite(restApiRole); const hostedZone = route53.HostedZone.fromHostedZoneAttributes(this, 'HostedZone', { hostedZoneId: hostedZoneId, zoneName: domainName, }); const certificate = new acm.Certificate(this, 'Certificate', { domainName: `*.${domainName}`, validation: acm.CertificateValidation.fromDns(hostedZone), }); const api = new apigateway.RestApi(this, 'RestAPI', { domainName: { domainName: `${domainPrefix}.${domainName}`, certificate: certificate, }, // disableExecuteApiEndpoint: true, defaultCorsPreflightOptions: { allowOrigins: apigateway.Cors.ALL_ORIGINS, allowMethods: ['POST', 'OPTIONS'], statusCode: 200, }, endpointConfiguration: { types: [apigateway.EndpointType.REGIONAL], }, }); const items = api.root.addResource('logs'); const prefix = items.addResource('{prefix}'); // オブジェクトをアップロードするための PUT メソッドを作成する prefix.addMethod( 'POST', new apigateway.AwsIntegration({ service: 's3', integrationHttpMethod: 'PUT', // アップロード先を指定する path: `${bucket.bucketName}/{prefix}/{fileName}`, options: { credentialsRole: restApiRole, passthroughBehavior: apigateway.PassthroughBehavior.WHEN_NO_MATCH, requestParameters: { // メソッドリクエストのパスパラメータを統合リクエストのパスパラメータにマッピングする 'integration.request.path.prefix': 'method.request.path.prefix', 'integration.request.path.fileName': 'context.requestId', }, integrationResponses: [ { statusCode: '200', responseParameters: { 'method.response.header.Timestamp': 'integration.response.header.Date', 'method.response.header.Content-Length': 'integration.response.header.Content-Length', 'method.response.header.Content-Type': 'integration.response.header.Content-Type', 'method.response.header.Access-Control-Allow-Headers': "'Content-Type,Authorization'", 'method.response.header.Access-Control-Allow-Methods': "'OPTIONS,POST,PUT'", 'method.response.header.Access-Control-Allow-Origin': "'*'", }, }, { statusCode: '400', selectionPattern: '4\\d{2}', responseParameters: { 'method.response.header.Access-Control-Allow-Headers': "'Content-Type,Authorization'", 'method.response.header.Access-Control-Allow-Methods': "'OPTIONS,POST,PUT'", 'method.response.header.Access-Control-Allow-Origin': "'*'", }, }, { statusCode: '500', selectionPattern: '5\\d{2}', responseParameters: { 'method.response.header.Access-Control-Allow-Headers': "'Content-Type,Authorization'", 'method.response.header.Access-Control-Allow-Methods': "'OPTIONS,POST,PUT'", 'method.response.header.Access-Control-Allow-Origin': "'*'", }, }, ], }, }), { authorizer: auth, authorizationScopes: [`${scopeName}/*`], requestParameters: { 'method.request.path.prefix': true, 'method.request.path.fileName': true, }, methodResponses: [ { statusCode: '200', responseParameters: { 'method.response.header.Timestamp': true, 'method.response.header.Content-Length': true, 'method.response.header.Content-Type': true, 'method.response.header.Access-Control-Allow-Headers': true, 'method.response.header.Access-Control-Allow-Methods': true, 'method.response.header.Access-Control-Allow-Origin': true, }, }, { statusCode: '400', responseParameters: { 'method.response.header.Access-Control-Allow-Headers': true, 'method.response.header.Access-Control-Allow-Methods': true, 'method.response.header.Access-Control-Allow-Origin': true, }, }, { statusCode: '500', responseParameters: { 'method.response.header.Access-Control-Allow-Headers': true, 'method.response.header.Access-Control-Allow-Methods': true, 'method.response.header.Access-Control-Allow-Origin': true, }, }, ], } ); new route53.ARecord(this, 'ARecod', { zone: hostedZone, recordName: `${domainPrefix}.${hostedZone.zoneName}`, target: route53.RecordTarget.fromAlias(new targets.ApiGateway(api)), }); } }
結果確認
作成されたcognitoからclientIdとsecretを取得し、
下記の要領でリクエスト実行します。
<>で囲んだ部分はcdkで指定した値に読み替えてください。
DOMAIN=https://<domainPrefix>.auth.ap-northeast-1.amazoncognito.com APP_CLIENT_ID=xxxxxx APP_CLIENT_SECRET=xxxxx curl -s -X POST ${DOMAIN}/oauth2/token \ -H "Authorization: Basic $(echo -n ${APP_CLIENT_ID}:${APP_CLIENT_SECRET} | base64 )" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=${APP_CLIENT_ID}" curl -X POST 'https://<domainPrefix>.<domainName>/logs/<prefix>/' \ --header "Authorization: Bearer xxxxxxxxxxx" \ --data '{"message":"Hello"}'
S3にログが格納されていることを確認できました。
S3内ではprefixで指定した文字列の配下にログが格納されます。
下記はprefixにsampleを指定した場合の例です。
おまけで、SendgridではSubuserという機能があり、
論理単位でログの出力を複数設定することができます。
そのため、特定用途のメールのみprefixを分けて
ログ出力するといったことも可能で下。
sendgrid.kke.co.jp
AWS configとsecurityhubの連携機能を試してみた
先月にsecurityhubとconfigの新しい連携機能が発表されており、
挙動が気になっていたので試してみました。
何が変わったのか
config ruleで検知した違反をsecurityhubに自動連携してくれるようになりました。
これまでは、configで検知した違反をsecurityhubに連携する場合
下記のような形で、連携の仕組みを自作する必要がありました。
これからは自動的にconfigの結果をsecurityhubに取り込んでくれます。
動作を確認してみる
configで検知した内容がSecurityhubにインポートされるときに、
どのように取り込まれるのか、各パターンを試してみました。
No | 前提条件 workflow status |
前提条件 compliance status |
configチェック時の リソース状態 |
検証結果 |
---|---|---|---|---|
1 | new | PASSED | PASSED | 何も起きない |
2 | new | PASSED | FAILED | compliance statsuがFAILEDになる |
3 | new | FAILED | PASSED | compliance statsuがPASSEDになる |
4 | new | FAILED | FAILED | 何も起きない |
5 | suppress | PASSED | PASSED | 何も起きない |
6 | suppress | PASSED | FAILED | compliance statsuがFAILEDになる |
7 | suppress | FAILED | PASSED | compliance statsuがPASSEDになる |
8 | suppress | FAILED | FAILED | 何も起きない |
9 | notified | PASSED | PASSED | 何も起きない |
10 | notified | PASSED | FAILED | compliance statsuがFAILEDになる Workflow statusがNewになる |
11 | notified | FAILED | PASSED | compliance statsuがPASSEDになる |
12 | notified | FAILED | FAILED | 何も起きない |
13 | resoleved | PASSED | PASSED | 何も起きない |
14 | resoleved | PASSED | FAILED | compliance statusはFAILEDになる Workflow statusがNewになる |
15 | resoleved | FAILED | PASSED | compliance statsuがPASSEDになる |
16 | resoleved | FAILED | FAILED | 何も起きない |
17 | なし | なし | FAILED | Newとして新しいfindignsが登録される |
表の見方補足
上記の表の見方が分かりにくいかもと思いましたので補足します。
例として、下記のsecurityhub findignsがあったとします。
それぞれのステータスは下記です。
workflow status:New
compliance status:PASSED
この例は表のNo1,2の前提条件が該当します。
上記前提条件をもとに、configがリソースを検査し、
その結果がPASSEDの場合はNo1、
FAIEDの場合はNo2という見方となります
検証してみて
検証して見て、securityhub standardは
config ruleに置き換えて良いのではと思いました。
docs.aws.amazon.com
securityhub standardは便利なのですが
個人的には下記が不便な点でした。
securityhubのimportイベントの発生頻度が多い
securityhub standardでは検知内容が放置された(上記No4)場合に
importイベントが定期的に発生します。
これは良し悪しあるのですが、
イベントを通知条件によっては何度も通知が来ます。
configの場合は、ルールの評価結果に変動がない場合には
securityhubに連携が行われない(=importイベントが発生しない)形となります。
個人差あると思うのですが、私にとっては、
変更がある = イベント通知されるという流れが直感的でしたので
configの方がより馴染みました。
securityhub standardに新規追加がある場合、ルールが有効化された状態で追加される
securityhub standardは定期的に更新があり、
ルールが追加されることがあります。
aws.amazon.com
このような形で、ある日ルールが有効化状態で追加されます。
そのため、追加されたルールが不要な場合は、
いちいち無効化作業が必要でした。
configの場合は、ルールを追加しない限り検知項目は増えないため、
ルール追加のタイミングを自分達で選択できます。
これから
上記の形でsecurityhub standardを利用せず、
configだけで運用する形が取れそうでしたらやってみて、
ブログネタ的なものがあれば継続して書いて見たいと思います。
Organizationやsecurityhub、config周り詳しい方、
より良い方法や発見がありましたらコメントで教えて頂けると嬉しいです。
serverlessテスト手法(エミュレータなし)について書いてみる
このブログはAWS LambdaとServerless Advent Calendar 2021の16日目です。
サーバレスのテスト手法は様々なパターンがあり、
私も勉強会で情報収集するようにしていました。
ただ、結局のところ何が良いのかよく悩んでいたところではありました。
そんな折、AWSのAtsushi Fukui (@afukui) | Twitterさんが
以前勉強会で、ローカルでの検証方法を語られていたり、
(該当部分に時間合わせてますが、この勉強会面白かったので、ぜひ全体見てください)
また、本アドベントカレンダーの5日目の
kensh (@_kensh) | Twitterさんのyoutube動画での
エミュレーションなしのテスト方法が
割と普段自分が実施している方法に近いのではと思ったことから
私の理解まとめと、具体的なコードを公開してみようと思いました。
本カレンダーではサーバレスやlambdaに詳しい方が
たくさんおられると思うので私の理解が間違っていたり、
もっとこうした方が良いといった
フィードバックやツッコミが頂けるかもという期待もあります。
ぜひお待ちしています!
サンプル
本記事での説明をより分かりやすくするため
サンプルでSAMのpython3.7のソースを公開しました。
github.com
dockerfile等はvscodeのremote containerで
動作確認した時のものですので、
remote containerをお使いの方は是非ご利用ください。
コードを説明
lambdaのコードから見ます。
lambda起動時に実行されるlambda_handlerとは別の関数を書きます。
サンプルコードではfunction_sampleが該当します。
ここでは単純に2つの引数を足し算するだけです。
lambdaのコード
app.py
import os .......(略) def function_sample(num1: int, num2: int) -> int: res = num1 + num2 return res def lambda_handler(event, context): print(function_sample(3,4)) return { "statusCode": 200 }
テストコード
テストコードでは、上記関数(function_sample)を呼び出します。
tests/unit/test_hello.py
from functions.hello_world import app as app_hello .......(略) def test_function_sample(): assert app_hello.function_sample(2, 3) == 5
テスト実行
deployはせず、ローカルでpytestを実行する形になります。
pytest -k test_function_sample
こちらが、kensh (@_kensh) | Twitterさんの下記スライドの
エミュレータなしの方法に近いのかと思います。
samやcdkの場合、buildやdeployをすると時間がかかってしまいますが
ローカルでソース変更してpytest実行するだけであれば
すぐに実行できます。
こうしてfunction_sampleが完成したら、
lambda_handlerからfunction_sampleを呼び出します。
サンプルのこの部分です
メインのロジック部分はこの方法で書いて
おおよそのバグを潰しておいて、
単純なコードだけではテストが難しい部分
(他サービスとの連携テストや権限周りのテストなど)は
deployして実際の環境で実施する形となります。
補足と考え方
ソース修正からテスト実行までの時間が短いことの価値は
Takuto Wada (@t_wada) | Twitterさんの
講演が分かりやすいと思います。
youtu.be
また、テスタビリティ向上のため、
外部との接続部分と、処理のロジックを切り分けるような話は
下記が分かりやすいと思いました。
こちらもサンプルコードを公開されています。
qiita.com
実は、この記事を見て、その分かりやすさに感動し、
自分も何か同じカレンダーで記事書いてみたい思い、
本記事を書いてみようと思いました。
あと理想と現実のバランスの取り方という点で、
以前クラメソさんで開催されていた勉強会がありがたかったです。
記事こちらです。
logmi.jp
また、本カレンダー6日目でhotswap的な手法も紹介されており、
最近使えるようになった手法で、こちらも開発段階では重宝しそうと思いました。
qiita.com
さいごに
もっと良い方法があれば是非知りたいのでコメント頂けると嬉しいです。
またカレンダー後半も楽しみにしています!
ありがとうございました。
DevOps Guru for RDS を触ってみた
こちらJapan APN Ambassador Advent Calendar 2021の11日目の記事です。
今年もre:Inventでたくさん発表がありましたね。
発表の中で気になった DevOps Guru for RDS を触ってみたので
ブログ書きたいと思います。
準備
こちらサポートされているのが現時点で
AuroraのみのようなのでAuroraを立ち上げます。
docs.aws.amazon.com
立ち上がってしばらくしてからAuroraに負荷をかけてみます。
※DevOps Guru for RDSは機械学習による異常検知なので、
作成後すぐに負荷をかけても異常検知されない可能性があります。
私も適当に負荷をかけていたのですが、
すぐには検知されず、
下記で異常を検知させることができました。
host="xxxxxxxxxxxxxx.rds.amazonaws.com" mysqlslap --delimiter=";" -u admin -p -h ${host} -P 3306 mydb --auto-generate-sql --concurrency=200 --number-of-queries=10000 --auto-generate-sql-write-number=2000 --number-int-cols=2 --number-char-cols=3
検知結果を見てみる
上記方法でAuroraに負荷かけ、異常が検出されたので見てみました。
insightsの一覧に表示されます。
また、この検知はeventBridgeやSNSと連携が可能です。
今回はwriteのlatencyが異常だと検知されました。
詳細を開くと、重要度や開始時間が書かています。
インシデント対応の時には、報告の際に、この種の情報が必要になるので
まとめてくれているのは嬉しいですね。
関連するメトリクスも提案表示してくれる
グラフを確認すると、関連するメトリクスもまとめてくれていました。
今回はwriteLatencyで検知されていたのですが、
同タイミングでコネクション数も増やしていたため、
これも怪しいのでは?ということで並べて表示してくれているようでした。
AWS関連の操作もまとめてくれる
また、Relevant eventsとして、
直近で実施したAWS関連の操作もピックアップしてくれていました。
例えば私は、この検証をする前に、
別の検証でcloudformation stackを削除していたのですが、
それも関連しそうなイベントとして一覧表示してくれていました。
Performance insightも併用
また、こちらは以前からあった機能ですが、 RDSのPerformance insightと併せて、 原因となるqueryの候補を確認できます。
試してみて
DevOps Guru for RDSを使ってみた感覚としては
RDSのメトリクスにanomaly detectionがかけられているような感覚でした。
また、パフォーマンス面で何かあった時に
簡単に見たいものが揃っていて、率直に嬉しいサービスと思いました。
私は前職でOracleで性能問題の対応をしたことがあったのですが、
その時こんなツールがあったら良かったなと昔を思い出してしまいました。
DBの理解には@odakeiji1さんの書籍に大変お世話になりました。
特にお気に入りはこの本です。
www.amazon.co.jp
今はAWSに所属されているようで、
これからも大いに学ばせて頂きます。
twitter.com
Ambassadorとして楽しく記事を書かせて頂きました。
ありがとうございました!
amplify studioを触ってみた
re:invent 2021で発表のあったamplify studioを触ってみました。
概要としては、figmaというサービスと連携し
GUIベースでコンポーネントを
作成できるようになったようです。
今回は試しにカードのcomponetを作ってみました
figmaでアカウント作成
まずはfigmaでアカウント作成し UI figma kitを利用可能なよう設定します。 www.figma.com
figmaで適当なコンポーネントを作成
figmaで適当なコンポーネントを作成してみます。
figma上でコンポーネント化しておくことで
amplify側にコンポーネントとして連携できるようになるようなので
自分が作成したいコンポーネントが出来上がったら
create componetしておくと良いと思います。
figmaとamplifyを連携
コンポーネントを作成したら、
それをamplify側に持ってきます。
awsコンソールから、
amplify studio ( 旧名 amplify admin UI)を有効化し
amplify studioにログインしておきます
UI Libraryが追加されていますので選択します。 そしてfigmaと連携するために、 figmaでURLを取得し、Amplify側に入力します。
初期でサンプルで作成されているコンポーネントも含めて
連携するかどうかを聞かれますので、
必要に応じてRejectかAcceptを選択します
あとはamplifyでamplify pullすれば
上記で作成したコンポーネントのコードが
自動作成されダウンロードされます。
実際に作成されたコード
内部が気になったのでコードを見てみました。
@aws-amplify/ui-react自体がText等のコンポーネントを持っており、
propsで、位置等の情報を指定可能なよう作成されているようでした。
/*************************************************************************** * The contents of this file were generated with Amplify FrontendManager. * * Please refrain from making any modifications to this file. * * Any changes to this file will be overwritten when running amplify pull. * **************************************************************************/ /* eslint-disable */ import React from "react"; import { getOverrideProps } from "@aws-amplify/ui-react/internal"; import { Icon, Text, View } from "@aws-amplify/ui-react"; export default function Component1(props) { const { Title, text, overrides: overridesProp, ...rest } = props; const overrides = { ...overridesProp }; return ( <View width="449px" padding="0px 0px 0px 0px" position="relative" height="264px" {...rest} {...getOverrideProps(overrides, "View")} > <View padding="0px 0px 0px 0px" backgroundColor="rgba(245.00000059604645,245.00000059604645,245.00000059604645,1)" top="0px" left="0px" width="449px" position="absolute" height="264px" {...getOverrideProps(overrides, "View.View[0]")} ></View> <View padding="0px 0px 0px 0px" backgroundColor="rgba(25.999998450279236,188.00003439188004,254.00000005960464,0.2199999988079071)" top="0px" left="0px" width="449px" position="absolute" height="63px" {...getOverrideProps(overrides, "View.View[1]")} ></View> <Icon pathData="M96 19C96 29.4934 74.5097 38 48 38C21.4903 38 0 29.4934 0 19C0 8.50659 21.4903 0 48 0C74.5097 0 96 8.50659 96 19Z" viewBox={{ minX: 0, minY: 0, width: 96, height: 38 }} color="rgba(174.00000482797623,179.000004529953,183.00000429153442,1)" top="12px" left="339px" width="96px" position="absolute" height="38px" {...getOverrideProps(overrides, "View.Icon[0]")} ></Icon> <Text padding="0px 0px 0px 0px" color="rgba(0,0,0,1)" textAlign="left" display="flex" justifyContent="flex-start" fontFamily="Roboto" top="15px" left="27px" fontSize="30px" lineHeight="35.15625px" position="absolute" fontWeight="400" direction="column" children="Title" {...getOverrideProps(overrides, "View.Text[0]")} ></Text> <Text padding="0px 0px 0px 0px" color="rgba(0,0,0,1)" textAlign="left" display="flex" justifyContent="flex-start" fontFamily="Roboto" top="89px" left="47px" fontSize="30px" lineHeight="35.15625px" position="absolute" fontWeight="400" direction="column" children="text...." {...getOverrideProps(overrides, "View.Text[1]")} ></Text> </View> ); }
こちらのコードは上書きしたとしても、
再度amplify pullをすると上書きされてしまうようです。
ドキュメントをみると、上書きされたくないなら、
適当にrenameして管理すればできると書かれていました。
https://docs.amplify.aws/console/uibuilder/override/
Amplify studioでコンポーネントを操作してみる
Amplify studioを利用してPropsを
指定できるようなので試してみました。
例えば、子要素のtextで、labelを与えることで
テキスト表示を変えることができました。
また、条件によってpropsを変えることもできるようです。
例えば親コンポーネントに与えたプロパティtypeの値がnotificationの場合は
backgroundColorを青にする
それ以外(alert)だったら赤にする
といった設定も可能なようでした。
やってみて
正直なところ私はfigmaが初めてで、若干GUI部分の操作に戸惑いました。
ただ、figmaに慣れれば、割と感覚的に
作業できるようになるのかと思いました。
またamplifyはreinvent前にもupdateが多くあったり
amplify studioにも、他にも機能があるようなので、
引き続き使ってみて記事書ければと思います。
最近のAmplify Updatesを紹介するTweetを連投します!
— Jaga@Amplify (@jagaimogmog) 2021年11月25日
疑問やツッコミあれば気軽にリプください🙏#AWSAmplifyJP
Amazon CodeGuruハンズオン(JAWS-UG千葉)に参加しました
Amazon CodeGuruハンズオン(JAWS-UG千葉)に参加しました。
千葉と言いつつも、オンライン開催をされていて全国から参加し放題です。
今回はCodeGuruのハンズオンがあり、
なんとAWS所属の方々もスピーカー参加されておりました。
(しかもBlackbeltと同じkanasugiさん!)
またハンズオンのシナリオが公式のworkshopかと思うほど
きれいで学びになったのでブログでシェアできればと思い書いてみます。
参考資料
ハンズオンの資料公開OKをもらったので
資料のリンクを貼っておきます。
この資料があればハンズオンできますので興味ある人はぜひ。
drive.google.com
ハンズオン内で、コピペしたい箇所のテキストはこちら
drive.google.com
資料前半の説明部分はBlackbeltに似た説明があるので
併せて聴くと良いと思います。
www.youtube.com
CodeGuru機能概要
今回のハンズオンではCodeGuruの下記2つの機能を使いました。
CodeGuru Reviewer
こちらはソースのレビューを実施し、改善箇所を指摘してくれる機能です。
今回のシナリオではCodeCommitでPRをして、
その際にCodeGuruがレビューをしてくれる流れになっています。
また、今回のハンズオンでは実施していませんが、
既存のGithub等のリポジトリをレビューをかけるようなこともできます。
CodeGuru Profiler
こちらは実稼働しているアプリケーションの実績をもとに、
どのコード部分がリソース消費に影響を与えているかを解析する機能です。
本ハンズオンでは、EC2上でAgentが動作し、
EC2の稼働状況のデータがCodeGuru Profilerに送られ、
その結果を見るシナリオがあります。
ハンズオンの概要
ハンズオンは2部構成で、Part1はCICDのパイプラインを作成します。
cloud9からcode系サービスを利用し、EC2にdeployできる環境を作ります。
こちらは前準備のようなもので、cfnを使って展開します。
Part2で主役のCodeGuruを利用します。
初期のサンプルソースではCodeGuruから指摘が出るのですが、
その指摘部分を改善修正することで、
より指摘が少なくなる、といった具合で
ハンズオンのシナリオが組まれています。
CodeGuru Reviewerを使ってみて
ハンズオン用のサンプルソースでは予め指摘が出るようになっていました。
例えば、AWSのベストプラクティスに反する箇所の指摘
(アクセスキー使わないでRole使って)だったり、
一般的なJavaの指摘をしてくれます。
指摘内容は理解しやすいよう
ほぼ全てリンクが付与されていました。
AWSの使い方に関する指摘は、AWSの公式ページのリンクが付与されています。
CodeGuru Profilerを使ってみて
Profilerは動作すると、下記のようなステータスが見れます。
リソースの利用状況を可視化でき、
例えば上記でVisualize CPUを押すと下記のような画面に遷移します。
情報てんこ盛りですが、
libraryのコード(緑色)と自分のコード(青色)をひとめで区別できたり、
推奨事項を確認できたりと
実際の稼働状況から得られる情報が良い感じで集約されています。
便利ですね。
PythonでもCodeGuru Reviewerを使ってみた
ハンズオンは上記のような流れですが、
pythonでもやってみたかったので
ハンズオン終わった後に、CodeGuru Reviewerで
自分のgithub上のpythonのソースをレビューしてみました。
温度感を表すために、下記1個だけ指摘のケースを書きます。
learn moreのリンク先はpythonの公式ドキュメントで下記でした。
docs.python.org
指摘内容としてはget()の第二引数にNoneをわざわざ指定する必要ないよ、といった指摘でした。
修正してPRすると、指摘が解消されていることが確認できました。
このように一般的なベストプラクティスのような部分を指摘してくれるので良いですね。
これに慣れると、人間は業務ロジックのような
本来の部分に集中できるのかなと思います。
修正前
eventArn = event.get('arn', None)
修正後
eventArn = event.get('arn')
さいごに
今回ハンズオン実施頂いた主催者の方々、ありがとうございました!
JAWS-UG千葉支部のハンズオン、
いつも細部まで綺麗で、よく勉強させてもらっています、ありがとうございます。
今後も参加させてもらいます!
AWSの基礎を学ぼう Gateway Load Balancerに参加しました
AWSの基礎を学ぼう Gateway Load Balancerに参加しました
AWSの亀田さんが、ほぼ毎週開催されている
いろんなサービスを触ってみる勉強会です。
今週はGateway Load Balancer でした。
awsbasics.connpass.com
作成した環境
こんな感じの構成をハンズオンで構築できました。
VPCが4つ存在し若干細々しているので、コメントを付けてみます
- 左側のApplicationのVPCから通信がスタートするとします
- Internet Out時にTransit Gatewayを経由し、右側のVPC(security用)に通信します
- 右側のVPCでsecurityのcheckをします。このcheckをsecurity applianceのEC2が実施しており、
そこへの負荷分散でGateway Load Balancerが登場します - securityのcheckを通過したら上のVPCを通じてinternet outします
最終的な通信のイメージは下記のような流れです。
cloudformationを見てみる
実際のハンズオンではcloudformation templateをご提供頂きました。
Gateway Load Balancerや内部のネットワーク構成を
どう構築されているかcfnを見てみました。
※実際のハンズオンでは手順とcloudformation templateをご提供頂いたのですが、
全部公開はNGで、部分的には載せて良いですよ言って頂けたので一部だけ載せさせて頂きます。
Gateway Load Balancer
ALBとほぼ同じでした
GWLB: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Name: gwlb-lab Type: gateway Subnets: - !Ref SecurityApplianceSubnet1 - !Ref SecurityApplianceSubnet2 Tags: - Key: Name Value: gwlb-lab
ターゲットグループでEC2を登録しています
GWLBTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Name: gwlb-target Port: 6081 Protocol: GENEVE TargetGroupAttributes: - Key: deregistration_delay.timeout_seconds Value: 20 VpcId: !Ref SecurityVPC HealthCheckPort: 22 HealthCheckProtocol: TCP TargetType: instance Targets: - Id: !Ref ApplianceAZ1EC2Instance - Id: !Ref ApplianceAZ2EC2Instance Tags: - Key: Name Value: gwlb-target
VPC Endpoint
VPC Endpointを作成しています
ただ、VPC Endpointには直接loadbalancerの紐付けがされていませんでした。
GWLBEAZ1: Type: AWS::EC2::VPCEndpoint Properties: VpcId: !Ref SecurityVPC ServiceName: !GetAtt GWLBESerivceName.Data VpcEndpointType: GatewayLoadBalancer SubnetIds: - !Ref SecurityApplianceSubnet1 GWLBEAZ2: Type: AWS::EC2::VPCEndpoint Properties: VpcId: !Ref SecurityVPC ServiceName: !GetAtt GWLBESerivceName.Data VpcEndpointType: GatewayLoadBalancer SubnetIds: - !Ref SecurityApplianceSubnet2
代わりにVPC endpointサービスでGatewayLoadBalancerと紐付けをしていました。
GWLBEService: Type: AWS::EC2::VPCEndpointService Properties: GatewayLoadBalancerArns: - !Ref GWLB AcceptanceRequired: false
そして、VPC endpointサービスと
VPC endopointを紐づけているようでした。
こちらにカスタムリソースが利用されておりました
GWLBESerivceName: DependsOn: GWLBEService Type: Custom::DescribeVpcEndpointServiceConfigurations Properties: ServiceToken: !GetAtt DescribeGWLBEService.Arn Input: !Ref GWLBEService
customリソースを利用してvpc endpoitのサービス名を取得しているようです
そして、取得したサービス名でVPC endpointとendpoint serviceを
紐づけているようです。
こうすることで2つのAZに存在するVPC endpointを1つのendpoint serviceに
まとめているようでした。
こんな作り方できるのかととても勉強になりました。
DescribeGWLBEService: Type: AWS::Lambda::Function Properties: Handler: "index.handler" Role: !GetAtt - DescribeGWLBEServiceLambdaExecutionRole - Arn Code: ZipFile: | import boto3 import cfnresponse import json import logging import time def handler(event, context): time.sleep(600) logger = logging.getLogger() logger.setLevel(logging.INFO) responseData = {} responseStatus = cfnresponse.FAILED logger.info('Received event: {}'.format(json.dumps(event))) if event["RequestType"] == "Delete": responseStatus = cfnresponse.SUCCESS cfnresponse.send(event, context, responseStatus, responseData) if event["RequestType"] == "Create": try: VpceServiceId = event["ResourceProperties"]["Input"] except Exception as e: logger.info('VPC Endpoint Service Id retrival failure: {}'.format(e)) try: ec2 = boto3.client('ec2') except Exception as e: logger.info('boto3.client failure: {}'.format(e)) try: response = ec2.describe_vpc_endpoint_service_configurations( Filters=[ { 'Name': 'service-id', 'Values': [VpceServiceId] } ] ) except Exception as e: logger.info('ec2.describe_vpc_endpoint_service_configurations fa: {}'.format(e)) ServiceName = response['ServiceConfigurations'][0]['ServiceName'] logger.info('service name: {}'.format(ServiceName)) responseData['Data'] = ServiceName responseStatus = cfnresponse.SUCCESS cfnresponse.send(event, context, responseStatus, responseData) Runtime: python3.7 Timeout: 900
ありがとうございました!
Gateway load balanerは初めて触ったのですが、
それ以外にも上記のように普段触ることないNW構成を触ることができ
とても勉強になりました
ありがとうございました!