#author("2019-03-19T23:32:56+00:00","","")
#author("2019-09-05T12:02:58+00:00","","")
#mynavi(AWSメモ)
#setlinebreak(on);

* 目次 [#d56f3755]
#contents
- 関連
-- [[AWS CloudFormationメモ]]
-- [[CloudFormationでカスタムドメイン対応の API Gateway を作成する]]
- 参考
-- https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md

* template.yml [#xcc16348]
#html(<div style="padding-left: 10px;">)

** Lambda単体 [#nd9938d3]
#html(<div style="padding-left: 10px;">)

AWS::Lambda::Function リソースを使用して定義した場合、&color(red){コード変更だけ行った場合に aws cloudformation deploy}; では更新されない(※)ので、素直に AWS::Serverless変換 を使用した方が良さそう。
※「No updates are to be performed」と怒られる。( cloudformation update-stack でも同様 )
※ aws lambda update-function-code であれば多分大丈夫。

参考
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/transform-aws-serverless.html
https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction

#mycode2(){{
AWSTemplateFormatVersion : '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Sample Lambda

Resources:
  CloudFormationLambda2:
    Type: "AWS::Serverless::Function"
    Properties:
      FunctionName: CloudFormationLambda2
      Description: "CloudFormationで作成したLambda2(AWS::Serverless変換)"
      Runtime: python3.6
      Handler: index.handler
      # CodeUri に指定したパス配下がアップロードされる
      # ※S3のPATHを指定する事も可能。(自分でS3にアップロードする必要あり)
      # ※省略した場合はカレント配下のファイルが全てアップロード対象となる。
      CodeUri: ./src
      MemorySize: 128
      Timeout: 60
      Role: !GetAtt CloudFormationLambda2Role.Arn
  CloudFormationLambda2Role:
    Type: "AWS::IAM::Role"
    Properties: 
      RoleName: CloudFormationLambda2Role
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service: "lambda.amazonaws.com"
            Action: "sts:AssumeRole"
      Policies:
        - PolicyName: "CloudFormationLambda2Policy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: "*"
}}
#html(</div>)

** Lambda & API Gateway (AWS::Serverless 変換を利用する場合) [#ddf93482]
#html(<div style="padding-left: 10px;">)

AWS::Serverless 変換を使用しない場合と比べるとかなりシンプルに書ける。

#mycode2(){{
AWSTemplateFormatVersion : '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Test serverless application.

Resources:

  SampleApi:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: CloudFormationApiLambda2
      Description: "CloudFormationApiLambda2"
      Handler: index.handler
      Runtime: python3.6
      CodeUri: ./src
      Events:
        ListApi:
          Type: Api 
          Properties:
            Path: /
            Method: get 
        GetApi:
          Type: Api 
          Properties:
            Path: /{id}
            Method: get 
      Role: !GetAtt CloudFormationApiLambda2Role.Arn
  CloudFormationApiLambda2Role:
    Type: "AWS::IAM::Role"
    Properties: 
      RoleName: CloudFormationApiLambda2Role
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service: "lambda.amazonaws.com"
            Action: "sts:AssumeRole"
      Policies:
        - PolicyName: "CloudFormationApiLambda2Policy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: "*" 
Outputs:
  SampleBookApiUri:
    # 作成したAPIのURIをエクスポートしておく(AWS::Serverless::Function を使用した場合、ステージは Stage,Prod の2つが作成される)
    # ※AWS::Serverless変換を使用した場合の RestApi は "ServerlessRestApi" という論理名で定義されるので、これを使用してAPIのIDを取得する事が可能。
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
    Export:
      Name: CloudFormationApiLambda2Uri
}}
※実際の運用ではカスタムドメインを使用してベースパスマッピングを設定する事になるが、ここでは割愛する。(関連: [[CloudFormationでカスタムドメイン対応の API Gateway を作成する]])
#html(</div>)

** Lambda & API Gateway (AWS::Serverless 変換を利用しない場合) [#j5ee9a92]
#html(<div style="padding-left: 10px;">)

上記にも記載したが、AWS::Lambda::Function リソースを使用して定義した場合、&color(red){コード変更だけ行った場合に cloudformation deploy}; では更新されない為、
Lambda自体の定義は AWS::Serverless::Function を使用し、API部分のみ AWS::Serverless変換をしない形で書いてみた。

#mycode2(){{
AWSTemplateFormatVersion : '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Sample API Gateway template.

Parameters:
  StageName:
    Type: "String"
    Default: "dev"

Resources:

  # Lambda本体
  SampleFunction1:
    Type: "AWS::Serverless::Function"
    Properties:
      FunctionName: SampleFunction1
      Runtime: python3.6
      Handler: index.handler
      CodeUri: ./src
      MemorySize: 128
      Timeout: 60
      Role: !GetAtt SampleFunction1Role.Arn

  # Lambdaのロール
  SampleFunction1Role:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: SampleFunction1Role
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service: "lambda.amazonaws.com"
            Action: "sts:AssumeRole"
      Policies:
        - PolicyName: "SampleFunction1Policy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: "*"

  # RestApiの定義
  # (AWS::ApiGateway::Resource と AWS::ApiGateway::Method に分けて書くこともできるが、
  #  ここでは Body プロパティに全て定義した。)
  SampleRestApi1:
    Type: 'AWS::ApiGateway::RestApi'
    Properties:
      Body:
        swagger: '2.0'
        info:
          version: '1.0'
          title: !Ref 'AWS::StackName'
        paths:
          /:  
            get:
              # Lambdaプロキシ統合をONにする場合は x-amazon-apigateway-integration オブジェクトを使用して type: aws_proxy を指定する
              # (参考) https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-swagger-extensions-integration.html
              x-amazon-apigateway-integration:
                httpMethod: POST  # 統合リクエストで使用されるHTTP メソッド。Lambdaの場合はPOSTを指定
                type: aws_proxy   # Lambdaプロキシ統合
                uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SampleFunction1.Arn}/invocations"
              responses: {}
          '/{id}':
            get:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SampleFunction1.Arn}/invocations"
              responses: {}

  # API GatewayへのLambda実行許可
  SampleRestApi1Permission:
    Type: 'AWS::Lambda::Permission'
    Properties:
      Action: 'lambda:invokeFunction'
      Principal: apigateway.amazonaws.com
      FunctionName: !Ref SampleFunction1
      Principal: "apigateway.amazonaws.com"  # SourceArn でURI毎に個別定義する事もできる
      #SourceArn: !Sub
      #  - >-
      #    arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/{id}
      #  - __Stage__: Prod
      #    __ApiId__: !Ref SampleRestApi1

  # デプロイ対象ステージの作成
  # (無くても通る。不要?)
  #SampleRestApi1ProdStage:
  #  Type: 'AWS::ApiGateway::Stage'
  #  Properties:
  #    DeploymentId: !Ref SampleRestApi1Deploy
  #    RestApiId: !Ref SampleRestApi1
  #    StageName: !Ref StageName

  # ステージへのデプロイ
  SampleRestApi1Deploy:
    Type: 'AWS::ApiGateway::Deployment'
    #DependsOn: "SampleApiMethod"
    Properties:
      RestApiId: !Ref SampleRestApi1
      StageName: !Ref StageName

Outputs:
  SampleRestApi1Uri:
    Export:
      Name: SampleRestApi1Uri
    Value: !Sub "https://${SampleRestApi1}.execute-api.${AWS::Region}.amazonaws.com/${StageName}/"
}}

#html(</div>)

#html(</div>)

* デプロイ用シェル [#x58d7da5]
#html(<div style="padding-left: 10px;">)

デプロイ処理をシェルやMakefileにしておくと、開発、運用ともに楽。

build.sh
#mycode2(){{
#!/bin/bash

MODE=$1

echo ""

# テンプレートファイル
TEMPLATE=template.yml

# スタック名
cd `dirname $0`
STACK_NAME=`basename \`pwd\``
if [ -e ".git" ]; then
    STACK_NAME=`cat .git/config | grep url | head -1 | awk -F"/" '{print $NF}'`
fi
STACK_NAME=`echo $STACK_NAME | sed 's/_/-/g' | sed 's/Repo$/Stack/'`
echo "Stack: ${STACK_NAME}"

# アカウントIDの取得
ACCOUNT_ID=`aws sts get-caller-identity | grep Account | awk '{print $2}' | sed -e "s/[^0-9]//g"`

# スタック作成時のイベント確認
if [ "${MODE}" == "desc" ]; then
    aws cloudformation describe-stack-events --stack-name $STACK_NAME
    exit 0
fi

# 削除
if [ "${MODE}" == "del" ]; then
    aws cloudformation delete-stack --stack-name $STACK_NAME
    aws cloudformation wait stack-delete-complete --stack-name $STACK_NAME
    exit 0
fi

# S3バケットがない場合は作る(バケット名は世界で唯一である必要がある為、末尾にアカウントID等を付与しておく)
BUCKET_NAME=my-cloudformation-templates-${ACCOUNT_ID}
BUCKET_COUNT=`aws s3api list-buckets | grep -e "\"${BUCKET_NAME}\"" | wc -l`
if [ "${BUCKET_COUNT}" == "0" ]; then
    echo aws s3api create-bucket --create-bucket-configuration '{"LocationConstraint": "ap-northeast-1"}' --bucket $BUCKET_NAME
fi

# ソースコードをzip圧縮しS3にアップロード
# (Serverless変換を使用する場合、package 時に自動でソースのzip圧縮とS3アップロードを行ってくれる為、自分で行なう必要はない)
#cd src && zip ../src.zip * && cd ../
#aws s3api put-object --bucket $BUCKET_NAME --key ${STACK_NAME}.zip --body src.zip
#rm -rf src.zip

# 検証&パッケージング&デプロイ(成功時は作成したAPIのURIを表示する)
aws cloudformation validate-template --template-body file://${TEMPLATE} \
  && aws cloudformation package --template-file $TEMPLATE --s3-bucket $BUCKET_NAME --output-template-file packaged-template.yml \
  && aws cloudformation deploy --template-file packaged-template.yml --stack-name $STACK_NAME --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
  && echo "" \
  && echo "### Exported Value ###" \
  && aws cloudformation describe-stacks --stack-name $STACK_NAME \
       | awk 'BEGIN{key=""}{ if ($1 == "\"OutputKey\":") key=$2; if ($1 == "\"OutputValue\":") print key" : "$2 }' \
       | sed 's/[",]//g' \
  && echo "######################/" \
  && echo ""
}}
#html(</div>)

* デプロイと動作確認 [#bf71a038]
#html(<div style="padding-left: 10px;">)
#myterm2(){{
# デプロイ
./build.sh

# スタック状況確認
./build.sh desc

# アンデプロイ
./build.sh del
}}
#html(</div>)


トップ   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS