AWS SAM テンプレートに既存リソースをインポートする手順を確認する

AWS SAM テンプレートへのリソースインポートは意外と大変? 転ばぬ先の杖として事前に確認してみました。
2023.01.06

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

ちょっとしたサーバレスアプリケーションを作るとき、AWS SAM は便利です。
AWS SAM のテンプレートは AWS CloudFormation テンプレートの拡張機能なので、リソースのインポートも簡単にできると思ったのですが、ひと手間必要だったのでその手順を紹介したいと思います。

背景

  • AWS SAM で Lambda 関数を作成する際、テンプレートに記載しなくても CloudWatch Logs のロググループが作成されます。
  • テンプレートに記載しない場合、このロググループは AWS SAM のリソースとして管理されないので、スタックを削除してもロググループが残り続けます。
  • AWS SAM の管理対象ではないリソースに対して、変更を加えたい場合は別の手段で行う必要があり、管理面で非効率です。
  • CloudFormation のようにリソースをインポートして、関連リソースを管理できるようにしたいですよね。

※ 今回は、CloudWatch Logs を対象にしていますが、リソースのインポート方法自体は他のリソースでも変わらないので、作業手順の参考にしていただければと思います。

00-sam-import-image

サンプルの Lambda 関数を AWS SAM で作成する

最初に AWS SAM で単純な 「Lambda 関数だけ」 を作成します。

sam init \
    --runtime python3.9 \
    --name blog-test-function \
    --app-template hello-world \
    --package-type Zip
cd blog-test-function
  • template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  blog-test-function

  Sample SAM Template for blog-test-function

Globals:
  Function:
    Timeout: 3

Resources:
  BlogTestFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.9
      Architectures:
        - x86_64
      AutoPublishAlias: dev
  • Lambda 関数
    • Lambda の動作は今回の件とは関係ないので、コードの中身は何でも構いません。
import json

def lambda_handler(event, context):
    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "hello world",
        }),
    }
  • ビルド & デプロイ
sam build
sam package \
    --output-template-file packaged.yaml \
    --s3-bucket [アーティファクト用の S3 バケット名]
    
sam deploy \
    --template-file packaged.yaml \
    --stack-name blog-sam-import-stack \
    --s3-bucket [アーティファクトがある S3 バケット名] \
    --capabilities CAPABILITY_NAMED_IAM \
    --no-fail-on-empty-changeset

CloudWatch Logs のログは Lambda 関数が実行されないと生成されないので、一度 Lambda 関数を実行しておきます。

01-lambda-test

Lambda 関数を実行することで CloudWatch のロググループが生成されて、ログストリームができていることが確認できます。 今回デフォルトで生成されたロググループの名前は、/aws/lambda/blog-sam-import-stack-BlogTestFunction-6qrGlr8YueM0 となっていました。

02-associated-cloudwath-logs

ロググループの名前の構造は、/aws/lambda/<Lambda 関数の名前> となっています。Lambda のコンソールから確認してみると関数と同じ名前であることが分かります。

03-lambda-function-name

SAM テンプレートを使ってインポートしてみる(失敗するパターン)

次に、AWS SAM テンプレートに CloudWatch Logs のロググループをインポートしてみたいと思います。

CloudFormation 単体でリソースをインポートする手順として考えると、SAM テンプレートをそのままインポートすればいいように思います。しかし、この方法ではインポートに失敗します。

試しに先程作成した SAM テンプレート(template.yaml)をインポートしてみます。

06-import-resource-as-sam-template

そのまま「Next」をクリックして進めます。

07-identify-resource

AWS SAM で使った template.yaml をアップロードします。

08-specify-template

ファイルをアップロードすると画像のようにエラーとなってしまいました。

09-import-error

これは、CloudFormation コンソールでは AWS SAM で利用される AWS::Serverless トランスフォームセクションがサポートされないためです。詳細は下記に記載されていますので、参考にして下さい。

サポート対象の一覧は下記に記載があります。

リソースインポートの手順

AWS SAM のテンプレートをそのまま使ってインポートすることはできないことが分かりました。
AWS SAM のテンプレートにリソースをインポートするには、下記の作業を実施します。

  • インポートしたいリソースを追記した CloudFormation テンプレートファイル template.yml を用意
  • インポートするリソース情報を記載したファイル(リソースインポートファイル) import.txt を作成
  • 上記2つのファイルを使って、AWS CLI で CloudFormation の「変更セット」を作成
  • 作成された「変更セット」を適用してスタックにインポート

公式の情報では次のページが参考になります。(先程掲載したものと同じです)

CloudFormation テンプレートを用意

最初に、インポートしたいリソースを追記したテンプレートを用意します。
すでに AWS SAM テンプレートがありますが、これは CloudFormation のテンプレートを拡張したものなので、純粋な CloudFormation テンプレートとして用意します。

用意するといってもゼロから作成する必要は無く、すでにデプロイ済みのスタックから取得しましょう。CloudFormation のコンソールからコピペします。

05-cloudformation-template

このテンプレートに、CloudWatch Logs のリソースを追記したものが下記になります。 25 〜 29 行目に追記しています。 (15 行目の CodeUri は環境に応じて異なるので伏せ字にしています。)

このファイルを適当な作業フォルダに template.yml として保存しておきます。 このファイルは、リソースをインポートするために使うものなので、AWS SAM で使用した template.yaml とは別ファイルとして作成して下さい。

27 行目に記載している通り、インポートするリソースは、CFnテンプレート内で 「DeletionPolicy」属性を指定する必要がある点に注意して下さい。

また、LogGroupName で指定しているロググループの名前は、インポートしたい既存のグループ名になるようにしておきます。

template.yml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: 'blog-test-function

  Sample SAM Template for blog-test-function

  '
Globals:
  Function:
    Timeout: 3
Resources:
  BlogTestFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: s3://xxxxx/xxxxxx
      Handler: app.lambda_handler
      Runtime: python3.9
      Architectures:
      - x86_64
      AutoPublishAlias: dev
    Metadata:
      SamResourceId: BlogTestFunction

  # インポートするリソースを追記
  LambdaFuncLogGroup:
    Type: AWS::Logs::LogGroup
    DeletionPolicy: Retain
    Properties:
      LogGroupName: !Sub /aws/lambda/${BlogTestFunction}

リソースインポートファイルを用意

次に、下記のような内容でリソースインポートファイルを作成します。

  • ResourceType: インポートしたいリソースのリソースタイプ
    • 今回は AWS::Logs::LogGroup
  • LogicalResourceId: インポートしたいリソースの論理ID
    • 先程の template.ymlLambdaFuncLogGroup としているのでコレを指定
  • ResourceIdentifier: インポートしたいリソースの識別名
    • インポートしたいリソースである CloudWatch Logs のロググループ名を指定

この内容で、import.txt というファイル名で 先程のtemplate.yml と同じ作業ディレクトリに保存します。

import.txt

[
    {
        "ResourceType": "AWS::Logs::LogGroup",
        "LogicalResourceId":
            "LambdaFuncLogGroup"
        ,
        "ResourceIdentifier": {
            "LogGroupName":"/aws/lambda/blog-sam-import-stack-BlogTestFunction-6qrGlr8YueM0"
        }
    }
]

ここまでで、作業ディレクトリには以下の 2 ファイルがある状態になっています。

  • template.yml
  • import.txt

変更セットの作成

ファイルの準備ができたので、CloudFormation の「変更セット」を作成します。 変更セットの作成では、作成した2つのファイルを利用するので、AWS CLI で行います。 これまでと同じ作業ディレクトリで下記のコマンドを実行します。

--stack-name にはインポートしたいスタックの名前を指定します。

aws cloudformation create-change-set \
    --stack-name blog-sam-import-stack \
    --change-set-name import-test-set \
    --resources-to-import file://import.txt \
    --change-set-type IMPORT \
    --template-body file://template.yml \
    --capabilities CAPABILITY_IAM

これで変更セットが無事作成できました。

10-create-changeset

上記の 「import-test-set」 をクリックして確認すると、追加したロググループが表示されていることが分かります。

11-list-changes

変更セットを適用してスタックにインポート

最後にこの「変更セット」を実行してロググループをインポートします。
(ロググループは DeletionPolicy: Retain と指定しているのでリソースは作成されず、指定のスタックにインポートされるという訳です)

12-execute-change-set

そのまま「Execute change set」をクリックして実行します。

13-behavior-prov-failure

スタックの「Event」タブで状態が 「IMPORT_COMPLETE」 になればインポート完了です。

14-import-complete

次の画像ように、CloudWatch のロググループのタグ情報も更新されて、CloudFormation(AWS SAM)の管理対象になったことが確認できました。

インポート前は何もタグがありません。

23-before-cloudwatch-logs-tags

インポート後は3つのタグがセットされています。

24-after-cloudwatch-logs-tags

AWS SAM からスタックを更新して確認してみる

これでリソースのインポートができたので、AWS SAM を使って対象のロググループの設定を更新してみましょう。

AWS SAM のテンプレートを下記のように更新します。(25 〜 29 行目)
ロググループのリソースを追加していますが、RetentionInDays: 30 も記載している点がポイントです。

デフォルトで作成されたロググループはログの保持期限の設定が無く「無期限」になっています。インポートしたリソースが AWS SAM の管理対象になっていれば、「1ヶ月」に更新されることを期待したものになります。

(インポート用の template.yml と同様に、LogGroupName で指定しているロググループの名前をインポートされた既存のグループ名になるようにしておきましょう。)

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  blog-test-function

  Sample SAM Template for blog-test-function

Globals:
  Function:
    Timeout: 3

Resources:
  BlogTestFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.9
      Architectures:
        - x86_64
      AutoPublishAlias: dev

  # CloudWatch Logs for Lambda
  # 明示的にCloudWatch Logsを作成してretentionを指定
  LambdaFuncLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /aws/lambda/${BlogTestFunction}
      RetentionInDays: 30

一応、変更前の保持期限を確認してみます。

aws logs describe-log-groups \
    --log-group-name-prefix \
        /aws/lambda/blog-sam-import-stack-BlogTestFunction-6qrGlr8YueM0 \
    |jq '{Retention: .logGroups[].retentionInDays}'

null で無期限となっていますね。

{
  "Retention": null
}

ちなみに、AWS CLI では null と表示されますが、コンソール上では Never expire と表示されます。

21-before-retention

この状態で、AWS SAM を使ってスタックを更新してから再度、保持期間を確認してみます。

aws logs describe-log-groups \
    --log-group-name-prefix \
        /aws/lambda/blog-sam-import-stack-BlogTestFunction-6qrGlr8YueM0 \
    |jq '{Retention: .logGroups[].retentionInDays}'

正しく「1ヶ月」に変わりました。

{
  "Retention": 30
}

以上で、AWS SAM のテンプレートに既存リソースをインポートできたことが確認できました。

最後に

AWS SAM でも簡単にリソースのインポートができるかと思いましたが、意外と手間がかかることが分かりました。
手順を事前に確認しておけば、何かあったときに慌てずに対応できますね。

以上です。

参考リンク