0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS SAM 覚え書き(2)

Posted at

前回書いたメモAutoPublishAlias のことを書きましたが、もう少し工夫すれば Serverless Framework でやれていたこととほぼ同等のことが実現できるのではないかという目途が立ったので、追加で書きます。前回同様、役に立つ記事とかそういうのはよそを当たってください…。

AutoPublishAlias で Function URL が使えなくなったワケ

前回

FunctionUrlConfig AutoPublishAlias DeploymentPreference の 3 つを併用すると Function URL が (定義は残っているにもかかわらず) アクセスできなくなる

と書きましたが、これは自分の理解不足でした。

  • AutoPublishAlias DeploymentPreference の働きによって「古い Lambda バージョンを削除して、新しいバージョンを作成し、 Lambda のエイリアスに最新バージョンを登録する」という動きになる
  • FunctionUrlConfig の定義はあって Function URL も再発行されるものの、上記の手順の途中でおそらくパーミション (リソースベースポリシー)1 が消えてしまう

からであろうと推測しています。

で、おそらくこれは Function URL に限った話ではなく、 S3 や SNS のような「トリガーのための権限を Lambda のリソースベースポリシーで設定しなければならないサービス」ではみんな同じ話になるだろうと思います。(EventBridge ルールであればターゲットに identity ベースポリシーを割り当てられるので、ここの話は当てはまりません)

Serverless Framework と SAM の違い:エイリアスを意識する

Serverless Framework ではこの辺をキチンと面倒みてくれていたんでしょう。古いバージョンは消さずに新しいバージョンだけを作成し、各種トリガーは非修飾の $LATEST に対するものとし、必要なパーミションは再設定もしてくれていた。

SAM で AutoPublishAlias DeploymentPreference を付けただけでは、新旧バージョンの交代は瞬時にできたとしても「旧バージョンの削除」という要らない動作まで付いてくるので、そこをなんとかしなければなりません。template.yaml で AWS::Serverless::Function リソースに DeletionPolicy: Retain をつければ旧バージョンの削除は抑制できますが、それをすると今度は FunctionUrlConfig の設定が邪魔になります。

ポイントになるのは、Serverless Framework のときは気にしていなかった「エイリアス」を SAM では意識したほうがうまくいく、ということでしょうか。

バージョンがどれだけ変わろうと、呼び出したいのは AutoPublishAlias で作ってもらったエイリアスが指しているバージョン。であるならば、 AWS::Serverless::FunctionFunctionUrlConfig を付けるのではなくて、作成されるであろう「エイリアス」に対して明示的に AWS::Lambda::Url リソースを作成すればよい。パーミションも「エイリアス」に対して付与すればよい。

あとは、デプロイすればするほど旧バージョンが (Serverless Framework の場合と同様) 増え続けるのを何とかすればよい。残念ながら serverless-prune-plugin のような便利ツールは SAM にはなさそうですが、なければ作ればよい。… Copilot くんに尋ねてデバッグさせて、ものの 10 分くらいで使えそうな bash スクリプトもできました。これなら GitHub Actions で自動化することもできそう。

というわけでこうなった

  • 初回のデプロイの template.yaml (抜粋) - 手動で実行
Resources:
  MyFunction:
    Type: AWS::Serverless::Function
    DeletionPolicy: Retain
    Properties:
      CodeUri: src/
      Handler: index.handler
      AutoPublishAlias: current
    Metadata: # Manage esbuild properties
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Target: es2020
        Sourcemap: true
        EntryPoints:
          - index.ts
        External:
          - '@aws-sdk'
  • 2回目以降のデプロイの template.yaml (抜粋) - CI/CD で実行
Resources:
  MyFunction:
    Type: AWS::Serverless::Function
    DeletionPolicy: Retain
    Properties:
      CodeUri: src/
      Handler: index.handler
      AutoPublishAlias: current
      DeploymentPreference:
        Type: AllAtOnce
        Alarms:
          - !Ref AliasErrorMetricGreaterThanZeroAlarm
          - !Ref LatestVersionErrorMetricGreaterThanZeroAlarm
    Metadata: # Manage esbuild properties
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Target: es2020
        Sourcemap: true
        EntryPoints:
          - index.ts
        External:
          - '@aws-sdk'

  MyFunctionCurrentUrlConfig:
    Type: AWS::Lambda::Url
    DependsOn: MyFunctionAliascurrent # 上記で生成される AWS::Lambda::Alias リソース
    Properties:
      AuthType: AWS_IAM
      Cors:
        AllowOrigins:
          - '*'
      Qualifier: current
      TargetFunctionArn: !GetAtt MyFunction.Arn

  MyFunctionPermission:
    Type: AWS::Lambda::Permission
    DependsOn: MyFunctionAliascurrent # 上記で生成される AWS::Lambda::Alias リソース
    Properties:
      Action: lambda:InvokeFunctionUrl
      FunctionName: !GetAtt MyFunctionAliascurrent.AliasArn
      FunctionUrlAuthType: AWS_IAM
      # Function URL を CloudFront のオリジンとしてぶら下げようと思ってたので、こんな感じ
      Principal: cloudfront.amazonaws.com
      SourceArn: (CloudFront distribution の ARN)
  • 古くなった Lambda バージョンを掃除する bash スクリプト
#!/bin/bash

# 設定
REGION="ap-northeast-1"  # リージョンを指定
FUNCTION_NAME="YOUR_FUNCTION_NAME"  # Lambda 関数名を指定
KEEP_VERSIONS=3  # 残すバージョンの数

# すべてのバージョンをリスト
versions=$(aws lambda list-versions-by-function --function-name ${FUNCTION_NAME} --region ${REGION} --query 'Versions[].[Version]' --output text | grep -v "\$LATEST" | sort -n)

# 古いバージョンを削除
count=0
total_versions=$(echo ${versions} | wc -w)
delete_count=$((${total_versions} - ${KEEP_VERSIONS}))
for version in ${versions}; do
  if [ ${count} -lt ${delete_count} ]; then
    echo "Deleting version ${version}"
    aws lambda delete-function --function-name ${FUNCTION_NAME} --qualifier ${version} --region ${REGION}
    count=$((count+1))
  else
    break
  fi
done

echo "Cleanup completed!"

ここまで苦労するなら最初から CDK 使っちゃえば…

参考文献

  1. 追加・削除するときは aws lambda add-permission aws lambda remove-permission なのに、今の状態を確認するときは aws lambda get-policy ... 名前に統一性がないのが残念というかなんというか。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?