Twitter
Facebook
Hatena
手動で作ってしまったAWSリソースをテンプレート化して管理する方法<準備編>

AWSリソースを一時的に利用するために手動で作成するケースは、よくあることだと思います。そして、そのリソースが思いのほか長く使われ続け、定期的にメンテナンスが必要になる、または他のAWS環境でも必要になる、といった経験もあるのではないでしょうか。
AWSリソースがAWS CloudFormationやAWS CDKなどでテンプレート化(コード化)して管理されていれば、改修や他のAWSアカウントへの流用は容易です。しかし、手動で作成した場合は、設定の齟齬が無いように確認しながら1から作成することになるため、作業負荷がかかります。
そのような場合に備えて、本コラムでは、手動で作ってしまったAWSリソースをテンプレート化して管理する方法をご紹介します。

今回は準備編として、手動で作成したAWSリソースを、AWS CloudFormationに取り込むための準備作業の手順を解説します。

AWSリソースのテンプレート化

AWSには、AWS CloudFormationと呼ばれるAWSリソースをテンプレート化し、テンプレート通りに自動で構築するサービスが存在します。基本的には、AWSリソースの初回の構築時にテンプレート化して、継続的に構築・管理していくことを前提としたサービスでした。近年のアップデートで、手動で作成済みのAWSリソースもテンプレート化して管理できる機能が追加されました。
AWSリソースをテンプレート化することにより、アプリケーション開発と同様にCI/CD(Continuous Integration/Continuous Delivery)を利用した効率的な環境デプロイを実現できます。将来的な作業負荷や人為的ミスの削減、開発スピードの向上などのメリットを得るには、まずはAWSリソースのテンプレート化を行うとよいでしょう。

AWSリソースのテンプレート化の手順

AWS CloudFormationを利用してAWSリソースのテンプレート化を行う手順を解説します。以下のAWSサービスとテンプレート形式の使用を前提とします。

<テンプレート化するAWSサービス>
・ Amazon API Gateway
・ AWS Lambda(IAM Role込み)

<作成するテンプレート形式>
・ AWS Serverless Application Model(以降、SAM)

<図1 構成図>

<動作環境>
・ Microsoft Windows [Version 10.0.19045.3448]
・ Python 3.9.10
・ aws-cli/2.1.32 Python/3.8.8 Windows/10 exe/AMD64 prompt/off
・ SAM CLI, version 1.90.0

<作業手順>
準備編(本コラム)
手順1 手動でAmazon API GatewayとAWS Lambdaを作成する
手順2 AWS Lambdaのソースコードを格納するAmazon S3のパスを取得する
手順3 Amazon API Gateway とAWS LambdaのテンプレートをSAM形式で作成する
手順4 AWSリソースをAWS CloudFormationに取り込むための定義ファイルを作成する

実践編
手順5 Amazon API Gateway とAWS LambdaをAWS CloudFormationに取り込む
手順6 AWS CloudFormationに取り込んだAWSリソースを更新する

手順1 手動でAmazon API Gateway とAWS Lambdaを作成する

AWSマネジメントコンソールもしくはAWS CLIを利用してAmazon API GatewayとAWS Lambdaを作成します。作成したAmazon API GatewayとAWS Lambdaを統合させて、任意のステージで1回以上デプロイさせておきます。

手順2 AWS Lambdaのソースコードを格納するAmazon S3のパスを取得する

SAM形式のテンプレートには、AWS Lambdaのソースコードのパスを指定する箇所がありますので、事前にAmazon S3にアップロードしてそのパスを取得する必要があります。
作成したAWS Lambdaのソースコードをzipファイル化し、そのファイルを任意のAmazon S3のバケットにアップロードします。バケットはパスを取得するために一時的に利用するだけなので、公開設定済みのバケットは除きどのようなバケットを利用しても問題ありません。バケットへのアップロードが完了したら、Amazon S3のパス(S3 URI s3://から始まるエンドポイント)をメモしておきます。

手順3 Amazon API Gateway とAWS LambdaのテンプレートをSAM形式で作成する

テンプレートの元ファイルの取得
テンプレートの元となるファイルを取得します。AWS Lambdaの管理画面のエクスポート機能[AWS SAM ファイルのダウンロード]でダウンロードできます。

<図2 関数のエクスポート画面(AWS マネジメントコンソール - AWS Lambdaの詳細)>

[AWS SAM ファイルのダウンロード]ボタンをクリックすると、{Lambda関数名}.yamlのファイルが取得できます。SAM形式のテンプレートの基礎となるファイルです。
今回筆者がダウンロードしたファイルは、“blog-api”のテンプレートファイルでした。

<図3 ダウンロードしたテンプレートの内容(blog-api.yaml)>

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: An AWS Serverless Specification template describing your function.
Resources:
  blogapi:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: .
      Description: ''
      MemorySize: 128
      Timeout: 3
      Handler: lambda_function.lambda_handler
      Runtime: python3.9
      Architectures:
        - x86_64
      EventInvokeConfig:
        MaximumEventAgeInSeconds: 21600
        MaximumRetryAttempts: 2
      EphemeralStorage:
        Size: 512
      Events:
        Api1:
          Type: Api
          Properties:
            Path: /blog-api
            Method: ANY
      RuntimeManagementConfig:
        UpdateRuntimeOn: Auto
      SnapStart:
        ApplyOn: None
      PackageType: Zip
      Policies:
        Statement:
          - Effect: Allow
            Action:
              - logs:CreateLogGroup
            Resource: arn:aws:logs:ap-northeast-1:{AWSAccountId}:*
          - Effect: Allow
            Action:
              - logs:CreateLogStream
              - logs:PutLogEvents
            Resource:
              - >-
                arn:aws:logs:ap-northeast-1:{AWSAccountId}:log-group:/aws/lambda/blog-api:*

テンプレートファイルの内容は、AWSマネジメントコンソールから入力済みの設定値を利用して作成したものです。

テンプレートの内容
このファイルは、AWSリソースが最小限のものしか書かれていないので、このままではAWS CloudFormationに取り込むためには利用できません。
そのため、以下のように内容を一部修正します。

<図4 修正後のテンプレートの内容(blog-api.yaml)>

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: An AWS Serverless Specification template describing your function.
Resources:
  # ① apiブロックの追加
  api:
    Type: AWS::Serverless::Api
    DeletionPolicy: Retain
    Properties:
      Name: blog-api-API
      StageName: default
  blogapi:
    Type: AWS::Serverless::Function
    # ② DeletionPolicyの追加
    DeletionPolicy: Retain
    Properties:
      # ③ FunctionNameの追加
      FunctionName: blog-api
      # ④ CodeUriの修正
      CodeUri: s3://{S3BucketName} /{ソースファイル群名}.zip
      Description: ""
      MemorySize: 128
      Timeout: 3
      Handler: lambda_function.lambda_handler
      Runtime: python3.9
      Architectures:
        - x86_64
      # ⑤ EventInvokeConfigの削除
      EphemeralStorage:
        Size: 512
      Events:
        Api1:
          Type: Api
          Properties:
            Path: /blog-api
            Method: ANY
            # ⑥ apiの参照追加
            RestApiId: !Ref api
      RuntimeManagementConfig:
        UpdateRuntimeOn: Auto
      SnapStart:
        ApplyOn: None
      PackageType: Zip
      # ⑦ roleの参照追加
      Role: !GetAtt role.Arn
  # ⑧ roleブロックの追加+blogapiブロック含まれていたPoliciesの内容複製
  role:
    Type: AWS::IAM::Role
    DeletionPolicy: Retain
    Properties:
      RoleName: blog-api-role-5df6uzqc
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: AWSLambdaBasicExecutionRole-991a598d-fd7d-4567-9ebc-4e5a03f7c487
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                Resource: arn:aws:logs:ap-northeast-1:{AWSAccountId}:*
              - Effect: Allow
                Action:
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource:
                  - >-
                    arn:aws:logs:ap-northeast-1:{AWSAccountId}:log-group:/aws/lambda/blog-api:*

テンプレートの修正箇所
修正箇所は以下の通りです。
① apiブロックの追加
リソースタイプ“AWS::Serverless::Api”のブロックを追加し、作成したAmazon API GatewayとStageを指定します。(後述の<表2 パラメーター項目、<図11 RestApiId/StageNameの確認(AWS マネジメントコンソール)>参照)

② DeletionPolicyの追加
BlogapiにDeletionPolicyを追加し“Retain”を設定します。作成したAWSリソースが、AWS CloudFormationのロールバック処理や意図しない削除操作などで誤って削除されないようにするため、AWSリソースを保持し続ける設定にします。これは、すべてのリソースに適用します。

③ FunctionNameの追加
PropertiesにFunctionNameを追加し、テンプレートの関数“blog-api”を設定します。
FunctionNameを指定しない場合、別名のAWS Lambda関数が作成される恐れがあります。

④ CodeUriの修正
CodeUriに、手順2 で取得したAWS Lambdaのソースコードを格納するAmazon S3のパスを設定します。

⑤ EventInvokeConfigの削除
EventInvokeConfigを削除します。非同期のAWS Lambda関数の呼び出しオプション項目ですが、AWS CloudFormationに取り込む場合に指定できないため、設定は不要です。

⑥ apiの参照追加
apiの参照を追加し、「① apiブロックの追加」で設定したAmazon API GatewayがAWS Lambdaに紐づくことを定義します。

⑦ roleの参照追加
AWS Lambda関数を作成すると、IAM Roleが自動的に作成されます。そのため、自動的に作成されたIAM RoleをSAMテンプレート内で定義し、そのIAM Roleが紐づくように指定します。(IAM Roleの定義は⑧参照)

⑧ roleブロックの追加+blogapiブロックに含まれていたPoliciesの内容複製
AWS Lambda関数に適用するIAM RoleとIAM Policyを追加します。
IAM Policyは、ダウンロードしたテンプレートファイルをそのまま利用します。(後述の<表2 パラメーター項目>、<図13 RoleName/ Idの確認(AWS マネジメントコンソール)>参照)

以上で、SAM形式のテンプレートファイルが作成できました。

手順4 AWSリソースをAWS CloudFormationに取り込むための定義ファイルを作成する

定義ファイルの内容
AWS CloudFormationにAWSリソースを取り込む、つまり、AWS CloudFormationスタックにAWSリソースをインポートするには、予めインポート対象のリソースの定義ファイルを用意する必要があります。

まずは、IAM Roleをインポートする場合の定義ファイルの例と、その内容を紹介します。
<図5 インポートするリソースの定義ファイルの例(IAM Role)>

[
	{
		"ResourceType": "AWS::IAM::Role",
		"LogicalResourceId": "role",
		"ResourceIdentifier": {
			"RoleName": "blog-api-role-5df6uzqc"
		}
	}
]

<表1 定義項目一覧>

定義する項目(物理名)内容
ResourceTypeAWSリソースタイプ
LogicalResourceIdAWS CloudFomation内で利用するAWSリソースの論理名(論理ID)
ResourceIdentifierAWSリソースを特定するために必要な識別子

変更セットの作成
上記の情報を元に、Amazon API GatewayとAWS Lambdaも同様にリソースを定義します。テンプレートには最低限のリソースタイプしか記述されておらず、テンプレートファイルから情報を得ることができません。そこで、必要なリソースタイプを調べるために、一旦仮でAWS CloudFormationの変更セットを作成して、定義が必要なリソース群を確認します。

変更セットの作成は、作成したテンプレートファイルを利用し、ローカル環境で以下のコマンドを実行します。

<図6 AWS CLI 実行コマンド(ローカル環境)>

aws cloudformation create-change-set --stack-name blog-api-stack --change-set-name blog-api-change-set --change-set-type CREATE --template-body file://blog-api.yaml --capabilities CAPABILITY_AUTO_EXPAND CAPABILITY_NAMED_IAM

コマンドが成功すると以下のような出力が表示されます。

<図7 AWS CLI 実行コマンド結果(ローカル環境)>

{
    "Id": "arn:aws:cloudformation:ap-northeast-1:{AWSAccountId}:changeSet/blog-api-change-set/e6e357cc-1468-4c58-b6ee-5220738927e3",
    "StackId": "arn:aws:cloudformation:ap-northeast-1:{AWSAccountId}:stack/blog-api-stack/128758c0-6408-11ee-a173-0aa3b2e67511"
}

AWS CloudFormationの管理画面に作成したスタック(“blog-api-stack”)および変更セット(“blog-api-change-set”)が表示されますので、変更セットの内容を確認します。

変更セットの確認

<図8 AWS CloudFormationの管理画面(AWS マネジメントコンソール)>

AWSマネジメントコンソールで作成したスタック(“blog-api-stack”)を選択し、[変更セット]タブを表示すると、変更セット(“blog-api-change-set”)が表示されます。変更セットを選択すると、[変更]タブに今回インポートが必要なリソースが表示されます。SAMはもともとAWS CloudFormationをより簡潔にしたテンプレートのため、テンプレートに明記していないリソースも必要に応じて作成されます。これを見ると、手順3で作成したテンプレートファイルに記述されたリソースタイプよりも多くのリソースが必要であることがわかります。

<図9 A変更セット(“blog-api-change-set”)のリソース確認(AWS マネジメントコンソール)>

インポートするために必要なリソース情報がわかりましたので、実際に定義ファイルを作成します。

定義ファイルの作成
<図10 変更セットを元に作成したインポートリソースの定義ファイル:import.json>

[
	{
		"ResourceType": "AWS::ApiGateway::Deployment",
		"LogicalResourceId": "apiDeployment0ecacc82b7",
		"ResourceIdentifier": {
			"RestApiId": "5nsz9o8309",
			"DeploymentId": "fytuoj"
		}
	},
	{
		"ResourceType": "AWS::ApiGateway::Stage",
		"LogicalResourceId": "apidefaultStage",
		"ResourceIdentifier": {
			"RestApiId": "5nsz9o8309",
			"StageName": "default"
		}
	},
	{
		"ResourceType": "AWS::ApiGateway::RestApi",
		"LogicalResourceId": "api",
		"ResourceIdentifier": {
			"RestApiId": "5nsz9o8309"
		}
	},
	{
		"ResourceType": "AWS::Lambda::Permission",
		"LogicalResourceId": "blogapiApi1Permissiondefault",
		"ResourceIdentifier": {
			"FunctionName": "blog-api",
			"Id": "lambda-6f94398f-a44b-4a8d-80cc-3b033c325198"
		}
	},
	{
		"ResourceType": "AWS::Lambda::Function",
		"LogicalResourceId": "blogapi",
		"ResourceIdentifier": {
			"FunctionName": "blog-api"
		}
	},
	{
		"ResourceType": "AWS::IAM::Role",
		"LogicalResourceId": "role",
		"ResourceIdentifier": {
			"RoleName": "blog-api-role-5df6uzqc"
		}
	}
]

ResourceTypeとLogicalResourceIdは図9のAWS CloudFormationの管理画面に表示されている内容をそのまま転記して問題ありません。
ResourceIdentifierは、AWSリソースに応じて定義する識別子が異なります。今回は予め調査して必要なパラメーター項目を表2に記載しています。こちらを参考に、それぞれの値を取得してインポートファイルに定義します。

<表2 パラメーター項目>

パラメーター項目内容
RestApiIdAmazon API GatewayのAPI ID AWS Lambdaのトリガー設定画面に表示されているAmazon API GatewayのARNより取得
StageNameAmazon API Gatewayのステージ名 AWS Lambdaのトリガー設定画面より取得
DeploymentIdAmazon API GatewayのデプロイメントID AWS CLIを利用して最新のデプロイメントIDを取得
FunctionName:Lambda関数名 手動で作成した際に利用した名前を取得
IdLambda関数に付与するリソースベースのポリシーのステートメントID
RoleNameLambda関数に紐づくIAM Role名

<図11 RestApiId/StageNameの確認画面(AWS マネジメントコンソール)>
RestApiId:API endpoint(今回は“5nsz9o8309”)
StageName:Stage(今回は“default”)

<図12 DeploymentIdの確認コマンド(ローカル環境)>
実行するコマンド
RestApiId(今回は“5nsz9o8309”)を使用する

aws apigateway get-deployments --rest-api-id 5nsz9o8309

<図13 実行結果(ローカル環境)>
DeploymentId:id(今回は“fytuoj”)

{
    "items": [
        {
            "id": "fytuoj",
            "description": "Created by AWS Lambda",
            "createdDate": "2023-10-04T05:32:19+00:00"
        }
    ]
}

<図14 RoleName/ Idの確認(AWS マネジメントコンソール)>
RoleName:ロール名(今回は“blog-api-role-5df6uzqc”)
Id:ステートメントID(今回は“lambda-6f94398f-a44b-4a8d-80cc-3b033c325198”)

以上でインポートに必要な定義ファイルの作成が完了しました。
仮で作成した変更セットは以降の手順では使用しませんので、AWS CloudFormationの管理画面から削除します。

準備編では、手動で作成したAWSリソースをAWS CloudFormationに取り込むための準備作業の手順を解説しました。

実践編では、AWSリソースをAWS CloudFormationに取り込んで、更新する手順を解説します。

富士ソフトのAWS関連サービスについて、詳しくはこちら
アマゾンウェブサービス(AWS)

 

 

この記事の執筆者

松井 美佳Mika Matsui

エリア事業本部
西日本支社 インテグレーション&ソリューション部
第1技術グループ
リーダー / エキスパート

AWS クラウド