kakakakakku blog

Weekly Tech Blog: Keep on Learning!

AWS Chatbot で AWS Lambda 関数の集約したロググループからログを取得する

AWS Lambda 関数の Errors メトリクスなどを Amazon CloudWatch Alarm でモニタリングして,エラー発生時に Amazon SNS と AWS Chatbot を組み合わせて Slack に通知すると Show error logs ボタンと Show logs ボタンが表示される✅ そして AWS Chatbot に権限を与えておくと,Amazon CloudWatch Logs から関連したログを Slack 上で取得できる.エラー発生時に迅速にエラー詳細を把握できるのは便利だと思う👌

Amazon CloudWatch Logs ロググループに注意する

便利な Show error logs ボタンと Show logs ボタンは AWS Lambda 関数のデフォルトの Amazon CloudWatch Logs ロググループ /aws/lambda/xxx を前提に作られている点は注意しておくと良いと思う.ドキュメントには直接明記されていなかった(見つけられなかった)けど,挙動からそう判断した.

具体的には,2023年11月にリリースされた AWS Lambda 関数の「高度なログ制御機能」を活用して,任意の Amazon CloudWatch Logs ロググループに集約している AWS Lambda 関数でボタンを押すと I can't get the logs for the CloudWatch Alarm sandbox-errors-alarm for you because I cannot find the log group /aws/lambda/sandbox for sandbox. というエラーが出て使えなかった(sandbox-errors-alarm は Amazon CloudWatch Alarm 名 / sandbox は AWS Lambda 関数名).

aws.amazon.com

@aws コマンドを使う

ボタンを押すよりも面倒ではあるけど,ワークアラウンドとして Slack 上で直接 @aws logs filter-log-events コマンドを実行すれば,簡易的に AWS Lambda 関数のエラー詳細を把握できる.AWS Chatbot から You can also run the query directly using the following command としてコマンド例を出してくれていたため,参考にしながら作ってみた.

ポイントを箇条書きで載せておく📝

  • --log-group-name に Amazon CloudWatch Logs ロググループを指定する
  • --log-stream-name-prefix に Amazon CloudWatch Logs ログストリームを指定する
  • --start-time--end-time に Unix Timestamp (Milliseconds) を指定する
@aws logs filter-log-events --region ap-northeast-1 --log-group-name aggregated-function-logs --log-stream-name-prefix 2024/04/12/sandbox[$LATEST] --start-time 1712883000000 --end-time 1712883300000

その他のオプションも活用する場合は CLI ドキュメントを参考で👌

awscli.amazonaws.com

そして @aws コマンドを使って AWS Chatbot 経由でログを取得できた.

まとめ

AWS Lambda 関数で任意の Amazon CloudWatch Logs ロググループに集約してる場合は Slack 上で AWS Chatbot の Show error logs ボタンと Show logs ボタンが使えず,今回は @aws コマンドを使ってワークアラウンドを試してみた.とは言え,正直エラー発生時に @aws コマンドを作るのは面倒ではあるため,本格的に実現するなら「カスタム Lambda アクション (Custom Lambda Action)」を実装する必要がありそう.あと @aws コマンドの引数が多くなければ「カスタム CLI アクション (Custom CLI Action)」を使う案もありそう.

docs.aws.amazon.com

docs.aws.amazon.com

関連記事

kakakakakku.hatenablog.com

PyTorch Tutorials「(optional) Exporting a Model from PyTorch to ONNX and Running it using ONNX Runtime」を試した

PyTorch のチュートリアル「(optional) Exporting a Model from PyTorch to ONNX and Running it using ONNX Runtime」を試した❗️

pytorch.org

PyTorch に低解像度の画像を高解像度の画像に変換する「超解像モデル」のサンプルがあって,今回のチュートリアルではそのモデルを ONNX (Open Neural Network eXchange) にエクスポートして,ONNX Runtime で推論する流れをサクッと体験できる.完全に入門者の僕にピッタリの内容だった👌

github.com

学べたこと

PyTorch のモデルは torch.onnx.export() 関数で ONNX にエクスポートできる.

pytorch.org

そして onnxruntime.InferenceSession クラスの run() 関数で推論できる.今回のチュートリアルでは providersCPUExecutionProvider を指定しているけど,CUDA (Compute Unified Device Architecture) 環境があれば CUDAExecutionProvider を指定することもできる.

ort_session = onnxruntime.InferenceSession("super_resolution.onnx", providers=["CPUExecutionProvider"])

onnxruntime.ai

実行結果

PyTorch モデルを ONNX モデルにエクスポートしてから推論した.今回は猫画像🐱を超解像にする内容だった.

実行前

実行後

その他

チュートリアルの範囲外ではあるけど,Netron を使うと ONNX モデルを可視化できる.

netron.app

今回エクスポートした super_resolution.onnx を可視化してみた💡

関連チュートリアル

pytorch.org

Amazon API Gateway の Lambda オーソライザーで "User is not authorized to access this resource" と出たら

Amazon API Gateway の Lambda オーソライザー(旧カスタムオーソライザー)を使ってアクセス制御をするときに,Authorization ヘッダーは正しいはずなのに {"Message":"User is not authorized to access this resource"} というエラーが出てしまう場合,Lambda オーソライザーの設定「認可のキャッシュ (Authorization caching)」に関係してる場合がある💡

前提

今回はサンプルとして Amazon API Gateway に /users リソースを追加して POSTGET をサポートする.そしてどちらにも Lambda オーソライザー(トークンタイプ)によるアクセス制御を設定しておく👌

/users (POST)
/users (GET)

そして,Lambda オーソライザーの実装はドキュメントに載っている Python のサンプルコードをそのまま使う📝実装としては簡易的で Authorization ヘッダーに allow と設定すれば OK という仕組みになっている.

docs.aws.amazon.com

ポイントはこの Lambda オーソライザーの実装では以下のようなポリシーが生成されるところ💡(POST の場合)

Allow

{
    "principalId": "user",
    "policyDocument": {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Action": "execute-api:Invoke",
                "Effect": "Allow",
                "Resource": "arn:aws:execute-api:ap-northeast-1:111111111111:xxxxxxxxxx/Prod/POST/users"
            }
        ]
    },
    "context": {
        "stringKey": "stringval",
        "numberKey": 123,
        "booleanKey": true
    }
}

Deny

{
    "principalId": "user",
    "policyDocument": {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Action": "execute-api:Invoke",
                "Effect": "Deny",
                "Resource": "arn:aws:execute-api:ap-northeast-1:111111111111:xxxxxxxxxx/Prod/POST/users"
            }
        ]
    },
    "context": {
        "stringKey": "stringval",
        "numberKey": 123,
        "booleanKey": true
    }
}

動作確認

Amazon API Gateway の /users リソースに POST リクエストを送信した後すぐに GET リクエストを送信する.すると {"Message":"User is not authorized to access this resource"} というエラーが返ってくる🔥キャッシュの仕組みを理解してないと「なぜー?」となってしまう.キャッシュの TTL はデフォルトで「300秒」に設定されている👀

$ curl -X POST -H 'Authorization: allow' ${ENDPOINT}/users
$ curl -X GET -H 'Authorization: allow' ${ENDPOINT}/users
{"Message":"User is not authorized to access this resource"}

対策

選択肢は大きく2つあると思う👌

選択肢1

Lambda オーソライザーで生成するポリシーの条件を緩和する選択肢がある.AWS re:Post の記事にも Resource/*/* にすれば OK という解決策が紹介されている💡もちろんワイルドカードで許可できない場合もあると思うし,最小権限の原則を考慮すると闇雲にワイルドカードっていう判断が危険なこともあると思う.

repost.aws

ちなみに前に紹介記事を書いた Powertools for AWS Lambda (Python)Event Source Data ClassesAPIGatewayAuthorizerResponse を使ってポリシーを生成する場合,allow_all_routes() を使うと以下のようにワイルドカードでポリシーが生成される.allow_route() を使うと HTTP メソッド・リソースを細かく指定できる.

{
    "principalId": "user",
    "policyDocument": {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Action": "execute-api:Invoke",
                "Effect": "Allow",
                "Resource": "arn:aws:execute-api:ap-northeast-1:111111111111:xxxxxxxxxx/Prod/*/*"
            }
        ]
    }
}

kakakakakku.hatenablog.com

選択肢2

次にキャッシュの TTL を短くする(もしくはキャッシュを無効化する)選択肢がある.キャッシュによる影響はなくなるけど,毎回 Lambda オーソライザーを呼び出すことになるため,パフォーマンスなどの観点で検討が必要になる.

デフォルトで 300 秒、API 所有者が 0~3600 の範囲で設定可能。

docs.aws.amazon.com

CloudFormation の IaC ジェネレーター機能をサクッと試せる「IaC Generator Workshop」

2024年2月にリリースされた AWS CloudFormation の「IaC ジェネレーター機能」を使うとマネジメントコンソール・AWS CLI などを使って作った(作ってしまった)リソースをスキャンして,自動的に AWS CloudFormation テンプレート化 (YAML / JSON) できる❗️もちろんそのまま AWS CloudFormation スタックにリソースをインポートすることもできる.

aws.amazon.com

今までも AWS CloudFormation のインポート機能はあったけど,リソース設定と見比べながら(そしてドリフト機能を活用しながら)テンプレートを書くのはそこそこ大変さがあったと思う.IaC ジェネレーター機能を使う流れは以下のブログにも載っていて,今回紹介するワークショップの内容にも似ている👀

aws.amazon.com

IaC Generator Workshop

この IaC ジェネレーター機能をサクッと試せるワークショップ「IaC Generator Workshop」も公開されていて,試してみた💡

実は2024年2月にワークショップも公開されていて,すぐに試そうと思ったけど,環境セットアップで使うスクリプト infrastructure-script.sh にアクセスすると Access Denied になってしまっていて進めなかった.1ヶ月ほど定期的に確認しても直ってなくて諦めていたけど,最近確認したら修正されていたので,改めてワークショップの内容を確認しておくことにした.

catalog.workshops.aws

ワークショップの内容としては,Amazon VPC 関連リソースと Amazon EC2 インスタンスをスクリプト (AWS CLI) で作って,リソースを AWS CloudFormation の「IaC ジェネレーター機能」でテンプレート化する.以下はスクリプトを実行したときに出力されたリソース情報一覧(既に削除済).

These are the resources created, you will need these ids later in the workshop.
Key Pair:  iacgenerator
VPC ID: vpc-00e0e3957e53ef021
Subnet ID: subnet-05a5cc381ac3dcce6
Internet Gateway ID: igw-0e98de8f97252af56
Route Table ID: rtb-0cff1f90fa8f83671
Security Group ID: sg-0c5db16219d23efd4
Instance ID: i-0c543696db799acf8

そして IaC ジェネレーター機能では,選択したリソースに関連するリソースも自動的に含めることができる.今回のワークショップだと AWS::EC2::NetworkAclAWS::EC2::SubnetNetworkAclAssociation などは「関連リソース」に含まれていた.

IaC Generator Workshop: テンプレート化

ちなみにテンプレート化したときに「リソース警告」も出ていた.AWS::EC2::InstanceAWS::EC2::Subnet など IaC ジェネレーター機能でサポートしてるリソースでもプロパティレベルでサポート対象外になっていることもありそうだった.このあたりは影響が出るかどうか確認しつつ,必要ならテンプレートを編集してからインポートすることもできる👌

docs.aws.amazon.com

IaC Generator Workshop: リソース警告

そして,最後はテンプレートをそのまま AWS CloudFormation スタックにインポートする.ちなみに今回のワークショップではテンプレート化するときに DeletionPolicy: Delete を指定するようになっているため,ワークショップ終了後のお掃除はインポートしたスタックを削除すれば良いという流れになっていた(ちなみに僕の環境だと一部の VPC リソースが削除失敗になってしまった💨).

IaC Generator Workshop: リソースをインポート

まとめ

もしまだ AWS CloudFormation の IaC ジェネレーター機能を試してなくて気になっていれば,1時間以内でサクッと終わるワークショップ「IaC Generator Workshop」がおすすめ❗️

関連ドキュメント

docs.aws.amazon.com

GitHub Actions でワークフローの同時実行を防ぐ concurrency 設定

GitHub Actions ではデフォルトの挙動として同じワークフローの複数のジョブを同時実行できる.無駄に待つ必要がないという意味ではメリットがあるけど,ワークフローによっては同時実行したくないこともあると思う.

GitHub Actions でワークフローが複数トリガーされてしまって慌てて止めたという経験もあったりする😅例えばワークフローの実行時間が長く,完了する前に次のコミットをプッシュしてしまったり,ワークフローの実行が完了する前にプルリクエストをマージしてしまったり💨

concurrency 設定

GitHub Actions ではコンカレンシー (concurrency) という設定があって,ワークフローの同時実行を制御できる.今回はワークフローレベルで試すけど,ジョブレベルで細かく制御することもできる❗️個人的にはとりあえず設定しておいても良さそうかなと思う.

docs.github.com

concurrency サンプル1(待機)

以下の例では GitHub ドキュメントを参考に concurrency.group${{ github.workflow }}-${{ github.ref }} を設定したため concurrency-refs/pull/1/merge のような名前になり「プルリクエストレベルで」同時実行を制御することになる💡そして concurrency.cancel-in-progressfalse を設定しているため,後続のトリガーはキャンセルにならず実行待機になってから実行される🛑

name: concurrency

on:
  workflow_dispatch:
  push:
    branches:
      - develop
  pull_request:
    branches:
      - develop

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: false

jobs:
  sleep:
    runs-on: ubuntu-latest
    steps:
      - name: Sleep
        run: sleep 60

concurrency サンプル2(キャンセル)

concurrency.cancel-in-progresstrue を設定すると,実行中のワークフローをキャンセルして最新のワークフローを実行できる🙆‍♂ 例えばデプロイ関連のワークフローであれば最終的に後に実行されたワークフローでデプロイされるため,ワークフローの無駄な実行を回避できて良いと思う👌

name: concurrency

on:
  workflow_dispatch:
  push:
    branches:
      - develop
  pull_request:
    branches:
      - develop

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  sleep:
    runs-on: ubuntu-latest
    steps:
      - name: Sleep
        run: sleep 60