2
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を使って不特定多数の猫に誕生日を祝われたい(リファクタリング編)

Last updated at Posted at 2024-12-17

はじめに

皆さん、こんにちは。
今年はありがたいことに、Japan AWS Top Engineers 2024 に選出されました。また、Top EngineersのAdvent Calendarがあるという情報を発見し、記念に参加することにしました。

偶然にも私は12月17日が誕生日なので、この日を投稿日としてエントリーしました。
今回は、 殺伐とした日々の生活に少しでも癒やしを与えるような題材 にしました。
ということで、作成されたのがこちらの記事です。

AWSを使って不特定多数の猫に誕生日を祝われたい(タイムアタック編)

よく見るとあんまりAWSに関係がありません。このままだと期中にTop Engineerをはく奪されてしまいそうなので、AWSに寄せたリファクタリングをかけようと思います。

① アプリケーション部分のIaC化
② アプリケーションリリースのCI/CD化
③ パイプラインのIaC化

前回同様、 「記事を書きながら実装をタイムアタック形式で行う」 という追い込みルールで進めたいと思います。

今回は120分でリファクタリングを書けるという目標にします!(結局タイムアタック)
またもや、21時を過ぎてから開始します。
(ここまで20分...)

作成したもの

① アプリケーション部分のIaC化

CloudFormationが好きなので、ネイティブCfn yamlで書いていきます。
対象は以下の通り。

  • Lambda
  • StateMachine

IaCジェネレーターに遷移します。
スキャンします。長いなぁ...

image.png

スキャンが終わりました。

image.png

テンプレートを作っていきます。

image.png

LambdaとStateMachineを選んだだけなのに、IAMやSchedulerまでレコメンドしてくれました。
とてもありがたいです。

image.png

テンプレートができました。アカウントIDとAPIキーを削除したり、名前を変えたり、順序を変えたり、余計なものを削除したyamlがこちらです。

コードを見る
/iac/template.yaml
Resources:
  ################################
  # Lambda and IAM
  ################################
  # meowAdvice function
  LambdaFunctionMeowAdvice:
    UpdateReplacePolicy: Retain
    Type: AWS::Lambda::Function
    DeletionPolicy: Retain
    Properties:
      MemorySize: 128
      Description: ''
      TracingConfig:
        Mode: PassThrough
      Timeout: 10
      RuntimeManagementConfig:
        UpdateRuntimeOn: Auto
      Handler: lambda_function.lambda_handler
      Code: ../functions/meowAdvice/
      Role: !GetAtt IAMRoleLFMeowAdvice.Arn
      FileSystemConfigs: []
      FunctionName: meowAdvice
      Runtime: python3.12
      PackageType: Zip
      LoggingConfig:
        LogFormat: Text
        LogGroup: /aws/lambda/meowAdvice
      Environment:
        Variables:
          DEEPL_AUTH_KEY: '{{resolve:ssm-secure:/Test/DEEPL_AUTH_KEY}}'
          LINE_USER_ID: '{{resolve:ssm-secure:/Test/LINE_USER_ID}}'
          GEMINI_API_KEY: '{{resolve:ssm-secure:/Test/GEMINI_API_KEY}}'
          LINE_ACCESS_TOKEN: '{{resolve:ssm-secure:/Test/LINE_ACCESS_TOKEN}}'
      EphemeralStorage:
        Size: 512
      Architectures:
        - x86_64

  # role for meowAdvice function
  IAMRoleLFMeowAdvice:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::Role
    DeletionPolicy: Retain
    Properties:
      Path: /service-role/
      ManagedPolicyArns:
        - !Ref IAMManagedPolicyLFMeowAdvice
      MaxSessionDuration: 3600
      RoleName: IAMRoleLFMeowAdvice
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
  IAMManagedPolicyLFMeowAdvice:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::ManagedPolicy
    DeletionPolicy: Retain
    Properties:
      ManagedPolicyName: IAMManagedPolicyLFMeowAdvice
      Path: /service-role/
      Description: ''
      Groups: []
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*
            Action: logs:CreateLogGroup
            Effect: Allow
          - Resource:
              - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/meowAdvice:*
            Action:
              - logs:CreateLogStream
              - logs:PutLogEvents
            Effect: Allow
      Roles: []
      Users: []

  # findCatsGroup function
  LambdaFunctionFindCatsGroup:
    UpdateReplacePolicy: Retain
    Type: AWS::Lambda::Function
    DeletionPolicy: Retain
    Properties:
      MemorySize: 128
      Description: ''
      TracingConfig:
        Mode: PassThrough
      Timeout: 3
      RuntimeManagementConfig:
        UpdateRuntimeOn: Auto
      Handler: lambda_function.lambda_handler
      Code: ../functions/findCatsGroup/
      Role: !GetAtt IAMRoleLFFindCatsGroup.Arn
      FileSystemConfigs: []
      FunctionName: findCatsGroup
      Runtime: python3.12
      PackageType: Zip
      LoggingConfig:
        LogFormat: Text
        LogGroup: /aws/lambda/findCatsGroup
      RecursiveLoop: Terminate
      EphemeralStorage:
        Size: 512
      Architectures:
        - x86_64

  # role for findCatsGroup function
  IAMRoleLFFindCatsGroup:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::Role
    DeletionPolicy: Retain
    Properties:
      Path: /service-role/
      ManagedPolicyArns:
        - !Ref IAMManagedPolicyLFFindCatsGroup
      MaxSessionDuration: 3600
      RoleName: IAMRoleLFFindCatsGroup
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
  IAMManagedPolicyLFFindCatsGroup:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::ManagedPolicy
    DeletionPolicy: Retain
    Properties:
      ManagedPolicyName: IAMManagedPolicyLFFindCatsGroup
      Path: /service-role/
      Description: ''
      Groups: []
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*
            Action: logs:CreateLogGroup
            Effect: Allow
          - Resource:
              - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/findCatsGroup:*
            Action:
              - logs:CreateLogStream
              - logs:PutLogEvents
            Effect: Allow
      Roles: []
      Users: []

  ################################
  # StateMachne
  ################################
  # statemachine GetMeowAdvicesFlow
  StateMachineGetMeowAdvicesFlow:
    UpdateReplacePolicy: Retain
    Type: AWS::StepFunctions::StateMachine
    DeletionPolicy: Retain
    Properties:
      DefinitionS3Location: ../statemachine/getMeowAdvicesFlow.asl.json
      EncryptionConfiguration:
        Type: AWS_OWNED_KEY
      LoggingConfiguration:
        IncludeExecutionData: false
        Level: 'OFF'
      StateMachineName: GetMeowAdvicesFlow
      RoleArn: !GetAtt IAMRoleSFGetMeowAdvicesFlow.Arn
      Tags: []
      StateMachineType: STANDARD
      TracingConfiguration:
        Enabled: false

  # role for GetMeowAdvicesFlow
  IAMRoleSFGetMeowAdvicesFlow:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::Role
    DeletionPolicy: Retain
    Properties:
      Path: /service-role/
      ManagedPolicyArns:
        - !Ref IAMManagedPolicyExecuteFunction
        - !Ref IAMManagedPolicyXray
      MaxSessionDuration: 3600
      RoleName: IAMRoleSFGetMeowAdvicesFlow
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: states.amazonaws.com

  IAMManagedPolicyExecuteFunction:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::ManagedPolicy
    DeletionPolicy: Retain
    Properties:
      ManagedPolicyName: IAMManagedPolicyExecuteFunction
      Path: /service-role/
      Description: Allow AWS Step Functions to invoke Lambda functions on your behalf
      Groups: []
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Resource:
              - !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:meowAdvice:*
              - !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:findCatsGroup:*
            Action:
              - lambda:InvokeFunction
            Effect: Allow
          - Resource:
              - !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:meowAdvice
              - !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:findCatsGroup
            Action:
              - lambda:InvokeFunction
            Effect: Allow
      Roles: []
      Users: []

  IAMManagedPolicyXray:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::ManagedPolicy
    DeletionPolicy: Retain
    Properties:
      ManagedPolicyName: IAMManagedPolicyXray
      Path: /service-role/
      Description: Allow AWS Step Functions to call X-Ray daemon on your behalf
      Groups: []
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Resource:
              - '*'
            Action:
              - xray:PutTraceSegments
              - xray:PutTelemetryRecords
              - xray:GetSamplingRules
              - xray:GetSamplingTargets
            Effect: Allow
      Roles: []
      Users: []

  ################################
  # Scheduler
  ################################
  # scheduler
  SchedulerExecGetMeowAdvicesFlow:
    UpdateReplacePolicy: Retain
    Type: AWS::Scheduler::Schedule
    DeletionPolicy: Retain
    Properties:
      GroupName: default
      ScheduleExpression: cron(0 0 17 12 ? *)
      Target:
        Arn: !Ref StateMachineGetMeowAdvicesFlow
        RetryPolicy:
          MaximumEventAgeInSeconds: 86400
          MaximumRetryAttempts: 185
        RoleArn: !GetAtt IAMRoleEBScheduler.Arn
      Description: cats give advice.
      State: ENABLED
      FlexibleTimeWindow:
        Mode: 'OFF'
      ScheduleExpressionTimezone: Asia/Tokyo
      Name: ExecGetMeowAdvicesFlowScheduler

  IAMRoleEBScheduler:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::Role
    DeletionPolicy: Retain
    Properties:
      Path: /service-role/
      ManagedPolicyArns:
        - !Ref IAMManagedPolicyEBScheduler
      MaxSessionDuration: 3600
      RoleName: IAMRoleEBScheduler
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Condition:
              StringEquals:
                aws:SourceAccount: !Sub '${AWS::AccountId}'
            Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: scheduler.amazonaws.com

  IAMManagedPolicyEBScheduler:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::ManagedPolicy
    DeletionPolicy: Retain
    Properties:
      ManagedPolicyName: IAMManagedPolicyEBScheduler
      Path: /service-role/
      Description: ''
      Groups: []
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Resource:
              - !Ref SchedulerExecGetMeowAdvicesFlow
            Action:
              - states:StartExecution
            Effect: Allow
      Roles: []
      Users: []

CI/CD化を見越して、LambdaとStepFunctionsは別ソースを指定しています。これでインフラとアプリケーションのソースコードを分離できます。

image.png

加えて、Lambdaの環境変数にはSystemsManagerのParameter Store値を指定します。

/iac/template.yaml -> LambdaFunctionMeowAdvice
  # meowAdvice function
  LambdaFunctionMeowAdvice:
    UpdateReplacePolicy: Retain
    Type: AWS::Lambda::Function
    DeletionPolicy: Retain
    Properties:
      MemorySize: 128
      Description: ''
      TracingConfig:
        Mode: PassThrough
      Timeout: 10
      RuntimeManagementConfig:
        UpdateRuntimeOn: Auto
      Handler: lambda_function.lambda_handler
+     Code: ../functions/meowAdvice/
      Role: !GetAtt IAMRoleLFMeowAdvice.Arn
      FileSystemConfigs: []
      FunctionName: meowAdvice
      Runtime: python3.12
      PackageType: Zip
      LoggingConfig:
        LogFormat: Text
        LogGroup: /aws/lambda/meowAdvice
      Environment:
        Variables:
+         DEEPL_AUTH_KEY: '{{resolve:ssm-secure:/Test/DEEPL_AUTH_KEY}}'
+         LINE_USER_ID: '{{resolve:ssm-secure:/Test/LINE_USER_ID}}'
+         GEMINI_API_KEY: '{{resolve:ssm-secure:/Test/GEMINI_API_KEY}}'
+         LINE_ACCESS_TOKEN: '{{resolve:ssm-secure:/Test/LINE_ACCESS_TOKEN}}'
      EphemeralStorage:
        Size: 512
      Architectures:
        - x86_64
/iac/template.yaml -> StateMachineGetMeowAdvicesFlow
  # statemachine GetMeowAdvicesFlow
  StateMachineGetMeowAdvicesFlow:
    UpdateReplacePolicy: Retain
    Type: AWS::StepFunctions::StateMachine
    DeletionPolicy: Retain
    Properties:
+     DefinitionS3Location: ../statemachine/getMeowAdvicesFlow.asl.json
      EncryptionConfiguration:
        Type: AWS_OWNED_KEY
      LoggingConfiguration:
        IncludeExecutionData: false
        Level: 'OFF'
      StateMachineName: GetMeowAdvicesFlow
      RoleArn: !GetAtt IAMRoleSFGetMeowAdvicesFlow.Arn
      Tags: []
      StateMachineType: STANDARD
      TracingConfiguration:
        Enabled: false

ここまでで80分かかっていました。目標はあと40分です...!

② アプリケーションリリースのCI/CD化

GitHubリポジトリを作成します。
まだ空です。

image.png

topengineer-advent-calendar-2024-meow
├ LICENSE
└ README.md

このご時世に、masterブランチで作ってしまいました。。
後で変えるとして、yamlとlambdaのソースコードをコミットします。

image.png

topengineer-advent-calendar-2024-meow
+ ├ functions
+ │ ├ findCatsGroup
+ │ │  └ lambda_function.py
+ │ └ meowAdvice
+ │    └ lambda_function.py
+ ├ iac
+ │ └ template.yaml
  ├ LICENSE
  └ README.md

statemachineの設定をコンソールから取得します。

image.png

この時、Lambda関数のARNはDefinitionSubstitutionsを利用して外部jsonに引き渡します。
CloudFormationのyamlでは、StateMachineのDefinitionSubstitutionsをこのように指定します。

/iac/template.yaml -> StateMachineGetMeowAdvicesFlow
  # statemachine GetMeowAdvicesFlow
  StateMachineGetMeowAdvicesFlow:
    UpdateReplacePolicy: Retain
    Type: AWS::StepFunctions::StateMachine
    DeletionPolicy: Retain
    Properties:
      DefinitionS3Location: ../statemachine/getMeowAdvicesFlow.asl.json
+     DefinitionSubstitutions:
+       LambdaFunctionMeowAdviceArn: !GetAtt LambdaFunctionMeowAdvice.Arn
+       LambdaFunctionFindCatsGroupArn: !GetAtt LambdaFunctionFindCatsGroup.Arn
      EncryptionConfiguration:
        Type: AWS_OWNED_KEY
      LoggingConfiguration:
        IncludeExecutionData: false
        Level: 'OFF'
      StateMachineName: GetMeowAdvicesFlow
      RoleArn: !GetAtt IAMRoleSFGetMeowAdvicesFlow.Arn
      Tags: []
      StateMachineType: STANDARD
      TracingConfiguration:
        Enabled: false

StateMachineの定義である外部jsonは、.asl.json拡張子で定義します。
マネジメントコンソール上のDefault形式はJSONataになっていましたので、そのままJSONataで記述します。
CloudFormationから受け取る変数は、以下のハイライトのように設定します。
(後で出てきますが、この記述は間違っていますので、、Gitを参照ください)

/statemachine/getMeowAdvicesFlow.asl.json
{
  "Comment": "A description of my state machine",
  "StartAt": "猫の群れを探す",
  "States": {
    "猫の群れを探す": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Output": "{% $states.result.Payload %}",
      "Arguments": {
+       "FunctionName": "{$LambdaFunctionFindCatsGroupArn}",
        "Payload": "{% $states.input %}"
      },
      "Retry": [
        {
          "ErrorEquals": [
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException",
            "Lambda.TooManyRequestsException"
          ],
          "IntervalSeconds": 1,
          "MaxAttempts": 3,
          "BackoffRate": 2,
          "JitterStrategy": "FULL"
        }
      ],
      "Next": "猫の群れに入る"
    },
    "猫の群れに入る": {
      "Type": "Map",
      "ItemProcessor": {
        "ProcessorConfig": {
          "Mode": "INLINE"
        },
        "StartAt": "それぞれの猫にお祝いの言葉を言ってもらう",
        "States": {
          "それぞれの猫にお祝いの言葉を言ってもらう": {
            "Type": "Task",
            "Resource": "arn:aws:states:::lambda:invoke",
            "Output": "{% $states.result.Payload %}",
            "Arguments": {
+             "FunctionName": "{$LambdaFunctionMeowAdviceArn}"
            },
            "Retry": [
              {
                "ErrorEquals": [
                  "Lambda.ServiceException",
                  "Lambda.AWSLambdaException",
                  "Lambda.SdkClientException",
                  "Lambda.TooManyRequestsException"
                ],
                "IntervalSeconds": 1,
                "MaxAttempts": 3,
                "BackoffRate": 2,
                "JitterStrategy": "FULL"
              }
            ],
            "End": true
          }
        }
      },
      "End": true,
      "Items": "{% $states.input.myCats %}",
      "MaxConcurrency": 3
    }
  },
  "QueryLanguage": "JSONata"
}

statemachineのjsonファイルをGitに入れます。

image.png

topengineer-advent-calendar-2024-meow
  ├ functions
  │ ├ findCatsGroup
  │ │  └ lambda_function.py
  │ └ meowAdvice
  │    └ lambda_function.py
  ├ iac
  │ └ template.yaml
+ ├ statemachine
+ │ └ getMeowAdvicesFlow.asl.json
  ├ LICENSE
  └ README.md

この状態で、CloudFormation CLIを使ってIaCからリソースを作成してみます。
準備として、CloudShellを起動し、git clone を実行します。

コマンドはこのページを参照して作成しました。コマンドを実行します。

aws cloudformation package --template-file ./iac/template.yaml --s3-bucket siruko-cloudformation-templetes --output-template-file packaged-template.yaml

作成されたyamlがこちらです。別ファイルで定義したLambdaのソースコードや、StepFunctionsの定義jsonが指定したS3にアップロードされ、そのURLが指定されています。

コードを見る
/iac/template.yaml
Resources:
  LambdaFunctionMeowAdvice:
    UpdateReplacePolicy: Retain
    Type: AWS::Lambda::Function
    DeletionPolicy: Retain
    Properties:
      MemorySize: 128
      Description: ''
      TracingConfig:
        Mode: PassThrough
      Timeout: 10
      RuntimeManagementConfig:
        UpdateRuntimeOn: Auto
      Handler: lambda_function.lambda_handler
      Code:
        S3Bucket: siruko-cloudformation-templetes
        S3Key: 802afc518ce90d2fbd6d590b3150333d
      Role:
        Fn::GetAtt:
        - IAMRoleLFMeowAdvice
        - Arn
      FileSystemConfigs: []
      FunctionName: meowAdvice
      Runtime: python3.12
      PackageType: Zip
      LoggingConfig:
        LogFormat: Text
        LogGroup: /aws/lambda/meowAdvice
      Environment:
        Variables:
          DEEPL_AUTH_KEY: '{{resolve:ssm-secure:/Test/DEEPL_AUTH_KEY}}'
          LINE_USER_ID: '{{resolve:ssm-secure:/Test/LINE_USER_ID}}'
          GEMINI_API_KEY: '{{resolve:ssm-secure:/Test/GEMINI_API_KEY}}'
          LINE_ACCESS_TOKEN: '{{resolve:ssm-secure:/Test/LINE_ACCESS_TOKEN}}'
      EphemeralStorage:
        Size: 512
      Architectures:
      - x86_64
  IAMRoleLFMeowAdvice:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::Role
    DeletionPolicy: Retain
    Properties:
      Path: /service-role/
      ManagedPolicyArns:
      - Ref: IAMManagedPolicyLFMeowAdvice
      MaxSessionDuration: 3600
      RoleName: IAMRoleLFMeowAdvice
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Action: sts:AssumeRole
          Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
  IAMManagedPolicyLFMeowAdvice:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::ManagedPolicy
    DeletionPolicy: Retain
    Properties:
      ManagedPolicyName: IAMManagedPolicyLFMeowAdvice
      Path: /service-role/
      Description: ''
      Groups: []
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Resource:
            Fn::Sub: arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*
          Action: logs:CreateLogGroup
          Effect: Allow
        - Resource:
          - Fn::Sub: arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/meowAdvice:*
          Action:
          - logs:CreateLogStream
          - logs:PutLogEvents
          Effect: Allow
      Roles: []
      Users: []
  LambdaFunctionFindCatsGroup:
    UpdateReplacePolicy: Retain
    Type: AWS::Lambda::Function
    DeletionPolicy: Retain
    Properties:
      MemorySize: 128
      Description: ''
      TracingConfig:
        Mode: PassThrough
      Timeout: 3
      RuntimeManagementConfig:
        UpdateRuntimeOn: Auto
      Handler: lambda_function.lambda_handler
      Code:
        S3Bucket: siruko-cloudformation-templetes
        S3Key: 39a90f217524dd9b5d2ee8c7fb0d1485
      Role:
        Fn::GetAtt:
        - IAMRoleLFFindCatsGroup
        - Arn
      FileSystemConfigs: []
      FunctionName: findCatsGroup
      Runtime: python3.12
      PackageType: Zip
      LoggingConfig:
        LogFormat: Text
        LogGroup: /aws/lambda/findCatsGroup
      RecursiveLoop: Terminate
      EphemeralStorage:
        Size: 512
      Architectures:
      - x86_64
  IAMRoleLFFindCatsGroup:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::Role
    DeletionPolicy: Retain
    Properties:
      Path: /service-role/
      ManagedPolicyArns:
      - Ref: IAMManagedPolicyLFFindCatsGroup
      MaxSessionDuration: 3600
      RoleName: IAMRoleLFFindCatsGroup
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Action: sts:AssumeRole
          Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
  IAMManagedPolicyLFFindCatsGroup:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::ManagedPolicy
    DeletionPolicy: Retain
    Properties:
      ManagedPolicyName: IAMManagedPolicyLFFindCatsGroup
      Path: /service-role/
      Description: ''
      Groups: []
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Resource:
            Fn::Sub: arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*
          Action: logs:CreateLogGroup
          Effect: Allow
        - Resource:
          - Fn::Sub: arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/findCatsGroup:*
          Action:
          - logs:CreateLogStream
          - logs:PutLogEvents
          Effect: Allow
      Roles: []
      Users: []
  StateMachineGetMeowAdvicesFlow:
    UpdateReplacePolicy: Retain
    Type: AWS::StepFunctions::StateMachine
    DeletionPolicy: Retain
    Properties:
      DefinitionS3Location:
        Bucket: siruko-cloudformation-templetes
        Key: 2f7f170dc9785b84b19ca51282777ef2
      DefinitionSubstitutions:
        LambdaFunctionMeowAdviceArn:
          Fn::GetAtt:
          - LambdaFunctionMeowAdvice
          - Arn
        LambdaFunctionFindCatsGroupArn:
          Fn::GetAtt:
          - LambdaFunctionFindCatsGroup
          - Arn
      EncryptionConfiguration:
        Type: AWS_OWNED_KEY
      LoggingConfiguration:
        IncludeExecutionData: false
        Level: 'OFF'
      StateMachineName: GetMeowAdvicesFlow
      RoleArn:
        Fn::GetAtt:
        - IAMRoleSFGetMeowAdvicesFlow
        - Arn
      Tags: []
      StateMachineType: STANDARD
      TracingConfiguration:
        Enabled: false
  IAMRoleSFGetMeowAdvicesFlow:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::Role
    DeletionPolicy: Retain
    Properties:
      Path: /service-role/
      ManagedPolicyArns:
      - Ref: IAMManagedPolicyExecuteFunction
      - Ref: IAMManagedPolicyXray
      MaxSessionDuration: 3600
      RoleName: IAMRoleSFGetMeowAdvicesFlow
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Action: sts:AssumeRole
          Effect: Allow
          Principal:
            Service: states.amazonaws.com
  IAMManagedPolicyExecuteFunction:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::ManagedPolicy
    DeletionPolicy: Retain
    Properties:
      ManagedPolicyName: IAMManagedPolicyExecuteFunction
      Path: /service-role/
      Description: Allow AWS Step Functions to invoke Lambda functions on your behalf
      Groups: []
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Resource:
          - Fn::Sub: arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:meowAdvice:*
          - Fn::Sub: arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:findCatsGroup:*
          Action:
          - lambda:InvokeFunction
          Effect: Allow
        - Resource:
          - Fn::Sub: arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:meowAdvice
          - Fn::Sub: arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:findCatsGroup
          Action:
          - lambda:InvokeFunction
          Effect: Allow
      Roles: []
      Users: []
  IAMManagedPolicyXray:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::ManagedPolicy
    DeletionPolicy: Retain
    Properties:
      ManagedPolicyName: IAMManagedPolicyXray
      Path: /service-role/
      Description: Allow AWS Step Functions to call X-Ray daemon on your behalf
      Groups: []
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Resource:
          - '*'
          Action:
          - xray:PutTraceSegments
          - xray:PutTelemetryRecords
          - xray:GetSamplingRules
          - xray:GetSamplingTargets
          Effect: Allow
      Roles: []
      Users: []
  SchedulerExecGetMeowAdvicesFlow:
    UpdateReplacePolicy: Retain
    Type: AWS::Scheduler::Schedule
    DeletionPolicy: Retain
    Properties:
      GroupName: default
      ScheduleExpression: cron(0 0 17 12 ? *)
      Target:
        Arn:
          Ref: StateMachineGetMeowAdvicesFlow
        RetryPolicy:
          MaximumEventAgeInSeconds: 86400
          MaximumRetryAttempts: 185
        RoleArn:
          Fn::GetAtt:
          - IAMRoleEBScheduler
          - Arn
      Description: cats give advice.
      State: ENABLED
      FlexibleTimeWindow:
        Mode: 'OFF'
      ScheduleExpressionTimezone: Asia/Tokyo
      Name: ExecGetMeowAdvicesFlowScheduler
  IAMRoleEBScheduler:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::Role
    DeletionPolicy: Retain
    Properties:
      Path: /service-role/
      ManagedPolicyArns:
      - Ref: IAMManagedPolicyEBScheduler
      MaxSessionDuration: 3600
      RoleName: IAMRoleEBScheduler
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Condition:
            StringEquals:
              aws:SourceAccount:
                Fn::Sub: ${AWS::AccountId}
          Action: sts:AssumeRole
          Effect: Allow
          Principal:
            Service: scheduler.amazonaws.com
  IAMManagedPolicyEBScheduler:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::ManagedPolicy
    DeletionPolicy: Retain
    Properties:
      ManagedPolicyName: IAMManagedPolicyEBScheduler
      Path: /service-role/
      Description: ''
      Groups: []
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Resource:
          - Ref: SchedulerExecGetMeowAdvicesFlow
          Action:
          - states:StartExecution
          Effect: Allow
      Roles: []
      Users: []

それでは、このままCloudShellでCloudFormationを実行してみましょう。

aws cloudformation deploy --template-file /home/cloudshell-user/topengineer-advent-calendar-2024-meow/packaged-template.yaml --stack-name testMeowStack

はい、エラーです。

Failed to create the changeset: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state: For expression "Status" we matched expected path: "FAILED" Status: FAILED. Reason: Circular dependency between resources: [IAMManagedPolicyEBScheduler, IAMRoleEBScheduler, SchedulerExecGetMeowAdvicesFlow]

相互参照してしまっているようですね。IaCジェネレーターにも可愛いところがあります。
と思ったら、リファクタリングでStateMachineのArnを!Ref関数に変えたところでした。

/iac/template.yaml -> IAMManagedPolicyEBScheduler
  IAMManagedPolicyEBScheduler:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::ManagedPolicy
    DeletionPolicy: Retain
    Properties:
      ManagedPolicyName: IAMManagedPolicyEBScheduler
      Path: /service-role/
      Description: ''
      Groups: []
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Resource:
-             - !Ref SchedulerExecGetMeowAdvicesFlow
+             - !Sub arn:aws:states:${AWS::Region}:${AWS::AccountId}:stateMachine:GetMeowAdvicesFlow
            Action:
              - states:StartExecution
            Effect: Allow
      Roles: []
      Users: []

もう一度実行します。

aws cloudformation package --template-file ./iac/template.yaml --s3-bucket siruko-cloudformation-templetes --output-template-file packaged-template.yaml
aws cloudformation deploy --template-file /home/cloudshell-user/topengineer-advent-calendar-2024-meow/packaged-template.yaml --stack-name testMeowStack

はい、エラーです。

Failed to create the changeset: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state: For expression "Status" we matched expected path: "FAILED" Status: FAILED. Reason: Parameters: [ssm-secure:/Test/DEEPL_AUTH_KEY ssm-secure:/Test/GEMINI_API_KEY ssm-secure:/Test/LINE_ACCESS_TOKEN ssm-secure:/Test/LINE_USER_ID] cannot be found.

SystemsManagerのParameter Store値を作っていませんでした。凡ミスですね。
作成します。

image.png

GMT表記のため少し分かりづらいですが、日本時間では23:12です。
開始から2時間が経過しつつあります。少しテンションが下がったので、ウイスキーを用意します。
もう一度実行します。

aws cloudformation deploy --template-file /home/cloudshell-user/topengineer-advent-calendar-2024-meow/packaged-template.yaml --stack-name testMeowStack

はい、エラーです。

Failed to create the changeset: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state: For expression "Status" we matched expected path: "FAILED" Status: FAILED. Reason: Requires capabilities : [CAPABILITY_NAMED_IAM]

これはもはやエラーではありません。カスタム名を持つIAMリソースをCloudFormationで作成する際のパラメータが不足しているだけです。

- aws cloudformation deploy --template-file /home/cloudshell-user/topengineer-advent-calendar-2024-meow/packaged-template.yaml --stack-name testMeowStack
+ aws cloudformation deploy --template-file /home/cloudshell-user/topengineer-advent-calendar-2024-meow/packaged-template.yaml --stack-name testMeowStack --capabilities CAPABILITY_NAMED_IAM

はい、エラーです。

Failed to create the changeset: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state: For expression "Status" we matched expected path: "FAILED" Status: FAILED. Reason: SSM Secure reference is not supported in: [AWS::Lambda::Function/Properties/Environment/Variables/GEMINI_API_KEY,AWS::Lambda::Function/Properties/Environment/Variables/LINE_ACCESS_TOKEN,AWS::Lambda::Function/Properties/Environment/Variables/DEEPL_AUTH_KEY,AWS::Lambda::Function/Properties/Environment/Variables/LINE_USER_ID]

Lambdaの環境変数はセキュア文字列がサポートされてないんですね。すっかり忘れていました。plaintextに変更します。
image.png

CloudFormationのyamlも更新します。

/iac/template.yaml -> LambdaFunctionMeowAdvice
  # meowAdvice function
  LambdaFunctionMeowAdvice:
    UpdateReplacePolicy: Retain
    Type: AWS::Lambda::Function
    DeletionPolicy: Retain
    Properties:
      MemorySize: 128
      Description: ''
      TracingConfig:
        Mode: PassThrough
      Timeout: 10
      RuntimeManagementConfig:
        UpdateRuntimeOn: Auto
      Handler: lambda_function.lambda_handler
      Code: ../functions/meowAdvice/
      Role: !GetAtt IAMRoleLFMeowAdvice.Arn
      FileSystemConfigs: []
      FunctionName: meowAdvice
      Runtime: python3.12
      PackageType: Zip
      LoggingConfig:
        LogFormat: Text
        LogGroup: /aws/lambda/meowAdvice
      Environment:
        Variables:
-         DEEPL_AUTH_KEY: '{{resolve:ssm-secure:/Test/DEEPL_AUTH_KEY}}'
-         LINE_USER_ID: '{{resolve:ssm-secure:/Test/LINE_USER_ID}}'
-         GEMINI_API_KEY: '{{resolve:ssm-secure:/Test/GEMINI_API_KEY}}'
-         LINE_ACCESS_TOKEN: '{{resolve:ssm-secure:/Test/LINE_ACCESS_TOKEN}}'
+         DEEPL_AUTH_KEY: '{{resolve:ssm:/Test/DEEPL_AUTH_KEY}}'
+         LINE_USER_ID: '{{resolve:ssm:/Test/LINE_USER_ID}}'
+         GEMINI_API_KEY: '{{resolve:ssm:/Test/GEMINI_API_KEY}}'
+         LINE_ACCESS_TOKEN: '{{resolve:ssm:/Test/LINE_ACCESS_TOKEN}}'
      EphemeralStorage:
        Size: 512
      Architectures:
        - x86_64

もう一度実行します。

aws cloudformation package --template-file ./iac/template.yaml --s3-bucket siruko-cloudformation-templetes --output-template-file packaged-template.yaml
aws cloudformation deploy --template-file /home/cloudshell-user/topengineer-advent-calendar-2024-meow/packaged-template.yaml --stack-name testMeowStack --capabilities CAPABILITY_NAMED_IAM

成功しました!!

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - testMeowStack

image.png
image.png

これにてIaC化は完了しました。

② アプリケーションリリースのCI/CD化

次に、GitHubのpushをトリガーに起動するCI/CDパイプラインを作っていきます。
今回はこのようなCodePipelineを作成しました。

image.png

Source

GitHubからソースコードを取得します。
mainブランチに反応するように設定しました。

image.png

Package

前述の aws cloudformation package コマンドをCodeBuildで実行します。
Packageの結果で生成されるpackaged-template.yamlは、BuildArtifactに格納されます。

image.png

ビルドプロジェクトの設定は以下の通りです。

image.png

buildspec.yamlはGitHubのパスを指定しておきます。現時点では未作成です。

image.png

CreateChangeset

aws cloudformation package コマンドの結果であるpackaged-template.yamlから、CloudFormationの変更セットを作成します。

image.png

Approval

誤リリース抑止用に、変更セットの作成が成功したら一旦ストップし、手動承認を待ちます。

image.png

ExecuteChangeset

変更セットを実行します。これでLambda, StateMachine, EventBridge Schedulerがデプロイされます。

image.png

それでは、buildspec.yamlを作成します。

version: 0.2

phases:
  install:
    commands:
      - echo Start package...
  build:
    commands:
      - echo Packageing...
      - aws cloudformation package 
        --template-file ./iac/template.yaml 
        --s3-bucket siruko-cloudformation-templetes
        --output-template-file packaged-template.yaml
      - ls -l packaged-template.yaml
artifacts:
  files:
    - packaged-template.yaml

GitHubにbuildspec.yamlをcommitしpushします。

image.png

topengineer-advent-calendar-2024-meow
  ├ functions
  │ ├ findCatsGroup
  │ │  └ lambda_function.py
  │ └ meowAdvice
  │    └ lambda_function.py
  ├ iac
+ │ ├ buldspec.yaml
  │ └ template.yaml
  ├ statemachine
  │ └ getMeowAdvicesFlow.asl.json
  ├ LICENSE
  └ README.md

いよいよ、パイプラインを動かします。
パイプラインはmainブランチに反応するようため、mainブランチを作成します。

git checkout -b main origin/master
git push origin head

動き始めました。

image.png

色々ありましたが、なんとか成功しました!

  • buildspec.yamlのファイル名を間違えていた
  • CodeBuildの権限が不足していた
  • mainブランチの変更に反応しない
  • CloudFormation Stackの権限が足りない
  • 変更セット名の大文字小文字を間違えた
  • IaCジェネレータのデフォルトのDeletionPolicyがRetainのため削除が面倒だった(愚痴です)

image.png

意気揚々とStep Functionsを動かしたところ、エラー…!

image.png

jsonの記述が間違っていました。

/statemachine/getMeowAdvicesFlow.asl.json
{
  "Comment": "A description of my state machine",
  "StartAt": "猫の群れを探す",
  "States": {
    "猫の群れを探す": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Output": "{% $states.result.Payload %}",
      "Arguments": {
-       "FunctionName": "{$LambdaFunctionFindCatsGroupArn}",
+       "FunctionName": "${LambdaFunctionFindCatsGroupArn}",
        "Payload": "{% $states.input %}"
      },
      "Retry": [
        {
          "ErrorEquals": [
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException",
            "Lambda.TooManyRequestsException"
          ],
          "IntervalSeconds": 1,
          "MaxAttempts": 3,
          "BackoffRate": 2,
          "JitterStrategy": "FULL"
        }
      ],
      "Next": "猫の群れに入る"
    },
    "猫の群れに入る": {
      "Type": "Map",
      "ItemProcessor": {
        "ProcessorConfig": {
          "Mode": "INLINE"
        },
        "StartAt": "それぞれの猫にお祝いの言葉を言ってもらう",
        "States": {
          "それぞれの猫にお祝いの言葉を言ってもらう": {
            "Type": "Task",
            "Resource": "arn:aws:states:::lambda:invoke",
            "Output": "{% $states.result.Payload %}",
            "Arguments": {
-             "FunctionName": "{$LambdaFunctionMeowAdviceArn}"
+             "FunctionName": "${LambdaFunctionMeowAdviceArn}"
            },
            "Retry": [
              {
                "ErrorEquals": [
                  "Lambda.ServiceException",
                  "Lambda.AWSLambdaException",
                  "Lambda.SdkClientException",
                  "Lambda.TooManyRequestsException"
                ],
                "IntervalSeconds": 1,
                "MaxAttempts": 3,
                "BackoffRate": 2,
                "JitterStrategy": "FULL"
              }
            ],
            "End": true
          }
        }
      },
      "End": true,
      "Items": "{% $states.input.myCats %}",
      "MaxConcurrency": 3
    }
  },
  "QueryLanguage": "JSONata"
}

再度パイプラインからリリースを行い、Step Functionsを手動で実行したところ、成功しました!

image.png

もう2:50です。
ここまで120分を目標としていましたが、実際には終わらず330分かかってしまいました。
本日はここまでとします…。

③ パイプラインのIaC化

1週間以上間が空いてしまいましたが、前回の続きを進めます。
最後のリファクタリング作業として、パイプラインをIaC化し、可搬性や再現性を確保します。
本日も遅めの時間(21:55)スタートなので、タイムアタックです。目標は120分!

IaCジェネレータ

CloudFormationのIaCジェネレータを使用して、パイプラインのyamlを取得します。

image.png

テンプレートを作成していきます。
ここで前回の反省を活かし、削除ポリシーと置換ポリシーを「削除」に設定します。

image.png

スキャンしたリソースにパイプラインがありません。
どうやら2024年12月時点では、IaCジェネレータはCodePipelineに対応していないようです。
早くも暗雲が立ち込めました。

image.png

パイプラインをテンプレートから作成するオプションを使うことで、テンプレートの自動生成を試みます。GitHub接続は作成済なので、サクサク進みます。

image.png
image.png
image.png
image.png

この結果、なんとパイプラインのyamlテンプレートが作成されました。

image.png

このテンプレートを元に、手動で作成したCodePipelineと同じものを作成していきます。
結局、ほぼ一から作ることになりました。

  • GitHub接続のARNを反映
  • Stack名を修正
  • ChangeSet名を修正
  • 各ステージ(Build、CreateChangeSet、Approval、ExecuteChangeSet)の追加
  • CodeBuildリソースの追加
  • 各ロール(CodePipeline、CodeBuild、CreateChangeSet、ExecuteChangeSet)の追加

リファクタリング済のyamlは以下です。

コードを見る
/iac/codepipeline.yaml
Resources:
  ################################
  # S3 Bucket
  ################################
  CodePipelineArtifactsBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub GetMeowAdvicesArtifactBucket-${AWS::AccountId}

  ################################
  # CodePipeline
  ################################
  CodePipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      ArtifactStore:
        Location: !Ref CodePipelineArtifactsBucket
        Type: S3
      ExecutionMode: QUEUED
      Name: GetMeowAdvicesAppPipeline
      PipelineType: V2
      RoleArn: !GetAtt CodePipelineRole.Arn
      Stages:
        ################################
        # Get Source from GitHub
        ################################
        - Name: Source
          Actions:
            - Name: CodeConnections
              ActionTypeId:
                Category: Source
                Owner: AWS
                Provider: CodeStarSourceConnection
                Version: 1
              Configuration:
                ConnectionArn: !Sub arn:aws:codeconnections:${AWS::Region}:${AWS::AccountId}:connection/6a7eb867-8d4f-400e-b124-494d8874c175
                FullRepositoryId: higurashit/topengineer-advent-calendar-2024-meow
                BranchName: main
              OutputArtifacts:
                - Name: SourceAtf

        ################################
        # Package CloudFormation template
        ################################
        - Name: Build
          Actions:
            - Name: BuildByCodeBuild
              ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: 1
              Configuration:
                ProjectName: !Ref CodeBuildProjectForPackageCfnTemplate
              InputArtifacts:
                - Name: SourceAtf
              OutputArtifacts:
                - Name: BuildAtf
              Region: !Ref AWS::Region
              Namespace: BuildByCodeBuild

        ################################
        # Create CloudFormation changeset
        ################################
        - Name: CreateChangeset
          Actions:
            - InputArtifacts:
                - Name: BuildAtf
              Name: CreateChangeset
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Version: 1
                Provider: CloudFormation
              OutputArtifacts:
                - Name: BdOutput
              Configuration:
                ActionMode: CHANGE_SET_REPLACE
                ChangeSetName: GetMeowAdvicesAppStackChangeSet
                RoleArn: !GetAtt CreateChangesetRole.Arn
                Capabilities: CAPABILITY_NAMED_IAM
                StackName: GetMeowAdvicesAppStack
                TemplatePath: BuildAtf::packaged-template.yaml

        ################################
        # Manual Approbal
        ################################
        - Name: Approval
          Actions:
            - Name: ApproveExecuteChangeset
              ActionTypeId:
                Category: Approval
                Owner: AWS
                Version: 1
                Provider: Manual

        ################################
        # Execte CloudFormation changeset
        ################################
        - Name: Deploy
          Actions:
            - Name: ExecuteChangeset
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Version: 1
                Provider: CloudFormation
              Configuration:
                ActionMode: CHANGE_SET_EXECUTE
                ChangeSetName: GetMeowAdvicesAppStackChangeSet
                RoleArn: !GetAtt ExecuteChangesetRole.Arn
                StackName: GetMeowAdvicesAppStack

  ################################
  # CodeBuild
  ################################
  CodeBuildProjectForPackageCfnTemplate:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: GetMeowAdvicesBuild
      Source:
        BuildSpec: ./iac/buildspec.yml
        InsecureSsl: false
        Type: CODEPIPELINE
      Artifacts:
        EncryptionDisabled: false
        Name: CodeBuildProjectForPackageCfnTemplate
        Packaging: NONE
        Type: CODEPIPELINE
      Cache:
        Type: NO_CACHE
      Environment:
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux-x86_64-standard:5.0
        ImagePullCredentialsType: CODEBUILD
        PrivilegedMode: false
        Type: LINUX_CONTAINER
      ServiceRole: !GetAtt CodeBuildRole.Arn
      TimeoutInMinutes: 15
      QueuedTimeoutInMinutes: 480
      EncryptionKey: !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:alias/aws/s3
      BadgeEnabled: false
      LogsConfig:
        CloudWatchLogs:
          Status: ENABLED
        S3Logs:
          Status: DISABLED
          EncryptionDisabled: false

  ################################
  # IAM Role
  ################################
  # CodePipeline
  CodePipelineRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesAppPipelineRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesAppPipelineRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - s3:Abort*
                  - s3:DeleteObject*
                  - s3:GetBucket*
                  - s3:GetObject*
                  - s3:List*
                  - s3:PutObject
                  - s3:PutObjectLegalHold
                  - s3:PutObjectRetention
                  - s3:PutObjectTagging
                  - s3:PutObjectVersionTagging
                Resource:
                  - !GetAtt CodePipelineArtifactsBucket.Arn
                  - !Join
                    - /
                    - - !GetAtt CodePipelineArtifactsBucket.Arn
                      - '*'
              - Effect: Allow
                Action:
                  - s3:PutObjectAcl
                  - s3:PutObjectVersionAcl
                Resource: !Join
                  - /
                  - - !GetAtt CodePipelineArtifactsBucket.Arn
                    - '*'

  # CreateChangeset
  CreateChangesetRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesAppPipelineCreateChangesetRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: cloudformation.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesAppPipelineCreateChangesetRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - cloudformation:CreateChangeSet
                Resource:
                  - !Sub arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/GetMeowAdvicesAppStack/*
              - Effect: Allow
                Action: s3:*
                Resource: '*'

  # ExecuteChangesetRole
  ExecuteChangesetRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesAppPipelineExecuteChangesetRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: cloudformation.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesAppPipelineExecuteChangesetRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Resource: '*'
                Action:
                  - iam:*
              - Effect: Allow
                Resource: !Sub arn:aws:iam::${AWS::AccountId}:role/*
                Action:
                  - iam:PassRole
              - Effect: Allow
                Resource: '*'
                Action:
                  - states:*
                  - logs:*
                  - cloudwatch:*
                  - lambda:*
                  - events:*
                  - s3:*

  # CodeBuild
  CodeBuildRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesBuildRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: codebuild.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesBuildRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - codebuild:BatchPutCodeCoverages
                  - codebuild:BatchPutTestCases
                  - codebuild:CreateReport
                  - codebuild:CreateReportGroup
                  - codebuild:UpdateReport
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource:
                  - !Sub arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:report-group/GetMeowAdvicesBuild-*
                  - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/GetMeowAdvicesBuild
                  - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/GetMeowAdvicesBuild:*
              - Effect: Allow
                Action:
                  - cloudformation:*
                  - codepipeline:*
                  - iam:PassRole
                  - s3:*
                Resource: '*'
              - Effect: Allow
                Action:
                  - iam:PassRole
                Resource: !Sub arn:aws:iam::${AWS::AccountId}:role/*

バックアップを取ってリソースを消していきます。

image.png
image.png

作成したyamlをCloudFormationで流していきます。

image.png
image.png

ここにチェックを入れて次へ

image.png

変更セットを作ります。一発でまあまあいい感じです。

image.png
image.png

はい、エラーです。S3バケットに大文字を使っています。焦っています。

Resource handler returned message: "Bucket name should not contain uppercase characters" (RequestToken: 574a6ebb-83fb-9a8d-ff01-5a319135ef8d, HandlerErrorCode: GeneralServiceException)

/iac/codepipeline.yaml -> CodePipelineArtifactsBucket
  CodePipelineArtifactsBucket:
    Type: AWS::S3::Bucket
    Properties:
-     BucketName: !Sub GetMeowAdvicesArtifactBucket-${AWS::AccountId}
+     BucketName: !Sub get-meow-advices-artifact-bucket-${AWS::AccountId}

再実行したところ、エラーが発生しました。ロール名が被っていました。
コンソールからロールを削除して再実行します。現在、2時間が経過しました…。

Resource handler returned message: "Resource of type 'AWS::IAM::Role' with identifier 'GetMeowAdvicesAppPipelineRole' already exists." (RequestToken: 329744bb-d197-9e75-301d-a8c5c9e69d73, HandlerErrorCode: AlreadyExists)

奇跡的にうまくいきました!リソースも作成されています。
image.png

image.png

パイプラインの実行

Sourceステージでエラーが起きていました。パイプラインのロール不足が起きています。

Unable to use Connection: arn:aws:codeconnections:ap-northeast-1:xxxxxxxxxxxx:connection/6a7eb867-8d4f-400e-b124-494d8874c175. The provided role does not have sufficient permissions.

/iac/codepipeline.yaml -> CodePipelineRole
  CodePipelineRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesAppPipelineRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesAppPipelineRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - s3:Abort*
                  - s3:DeleteObject*
                  - s3:GetBucket*
                  - s3:GetObject*
                  - s3:List*
                  - s3:PutObject
                  - s3:PutObjectLegalHold
                  - s3:PutObjectRetention
                  - s3:PutObjectTagging
                  - s3:PutObjectVersionTagging
                Resource:
                  - !GetAtt CodePipelineArtifactsBucket.Arn
                  - !Join
                    - /
                    - - !GetAtt CodePipelineArtifactsBucket.Arn
                      - '*'
              - Effect: Allow
                Action:
                  - s3:PutObjectAcl
                  - s3:PutObjectVersionAcl
                Resource: !Join
                  - /
                  - - !GetAtt CodePipelineArtifactsBucket.Arn
                    - '*'
+             - Effect: Allow
+               Action: codestar-connections:*
+               Resource: !Sub arn:aws:codeconnections:${AWS::Region}:${AWS::AccountId}:connection/6a7eb867-8d4f-400e-b124-494d8874c175

CloudFormationからパイプラインを更新し、再実行します。Sourceステージはクリアし、Buildステージでエラーとなりました。
image.png

パイプラインロールにまだ権限が足りないようです。

Error calling startBuild: User: arn:aws:sts::xxxxxxxxxxxx:assumed-role/GetMeowAdvicesAppPipelineRole/1734015821781 is not authorized to perform: codebuild:StartBuild on resource: arn:aws:codebuild:ap-northeast-1:xxxxxxxxxxxx:project/GetMeowAdvicesBuild because no identity-based policy allows the codebuild:StartBuild action (Service: AWSCodeBuild; Status Code: 400; Error Code: AccessDeniedException; Request ID: 847ed4de-d80c-4870-a73c-cc0b680e7f88; Proxy: null)

CodeBuildの実行権限を付与します。Buildジョブが増えた時のためにResourceは多少緩めにしています。

/iac/codepipeline.yaml -> CodePipelineRole
  CodePipelineRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesAppPipelineRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesAppPipelineRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - s3:Abort*
                  - s3:DeleteObject*
                  - s3:GetBucket*
                  - s3:GetObject*
                  - s3:List*
                  - s3:PutObject
                  - s3:PutObjectLegalHold
                  - s3:PutObjectRetention
                  - s3:PutObjectTagging
                  - s3:PutObjectVersionTagging
                Resource:
                  - !GetAtt CodePipelineArtifactsBucket.Arn
                  - !Join
                    - /
                    - - !GetAtt CodePipelineArtifactsBucket.Arn
                      - '*'
              - Effect: Allow
                Action:
                  - s3:PutObjectAcl
                  - s3:PutObjectVersionAcl
                Resource: !Join
                  - /
                  - - !GetAtt CodePipelineArtifactsBucket.Arn
                    - '*'
              - Effect: Allow
                Action: codestar-connections:*
                Resource: !Sub arn:aws:codeconnections:${AWS::Region}:${AWS::AccountId}:connection/6a7eb867-8d4f-400e-b124-494d8874c175
+             - Effect: Allow
+               Action: codebuild:*
+               Resource: !Sub arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:project/GetMeowAdvices*

CloudFormationからパイプラインを更新し、再実行します。
またもやBuildステージでエラーが発生しました。ファイル名の拡張子が誤っていました。

[Container] 2024/12/12 15:09:14.517515 Phase context status code: YAML_FILE_ERROR Message: stat /codebuild/output/src489700486/src/iac/buildspec.yml: no such file or directory

/iac/codepipeline.yaml -> CodeBuildProjectForPackageCfnTemplate
  CodeBuildProjectForPackageCfnTemplate:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: GetMeowAdvicesBuild
      Source:
-       BuildSpec: ./iac/buildspec.yml
+       BuildSpec: ./iac/buildspec.yaml
        InsecureSsl: false
        Type: CODEPIPELINE
      Artifacts:
        EncryptionDisabled: false
        Name: CodeBuildProjectForPackageCfnTemplate
        Packaging: NONE
        Type: CODEPIPELINE
      Cache:
        Type: NO_CACHE
      Environment:
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux-x86_64-standard:5.0
        ImagePullCredentialsType: CODEBUILD
        PrivilegedMode: false
        Type: LINUX_CONTAINER
      ServiceRole: !GetAtt CodeBuildRole.Arn
      TimeoutInMinutes: 15
      QueuedTimeoutInMinutes: 480
      EncryptionKey: !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:alias/aws/s3
      BadgeEnabled: false
      LogsConfig:
        CloudWatchLogs:
          Status: ENABLED
        S3Logs:
          Status: DISABLED
          EncryptionDisabled: false

CloudFormationからパイプラインを更新し、再実行します。
Buildフェーズは通過しましたが、CreateChangeSetステージでエラーが発生しました。

image.png

パイプラインロールにまだ権限が足りないようです。

User: arn:aws:sts::xxxxxxxxxxxx:assumed-role/GetMeowAdvicesAppPipelineRole/1734016376890 is not authorized to perform: cloudformation:DescribeStacks on resource: arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:stack/GetMeowAdvicesAppStack/cc187d20-b266-11ef-957c-06871864c701 because no identity-based policy allows the cloudformation:DescribeStacks action (Service: AmazonCloudFormation; Status Code: 403; Error Code: AccessDenied; Request ID: 3ea878d6-0e03-4b1a-8e38-9f600c477739; Proxy: null)

User: arn:aws:sts::xxxxxxxxxxxx:assumed-role/GetMeowAdvicesAppPipelineRole/1734016838437 is not authorized to perform: iam:PassRole on resource: arn:aws:iam::xxxxxxxxxxxx:role/GetMeowAdvicesAppPipelineCreateChangesetRole because no identity-based policy allows the iam:PassRole action (Service: AmazonCloudFormation; Status Code: 403; Error Code: AccessDenied; Request ID: 5a0d8225-a907-4137-8ebc-7d4439909586; Proxy: null)

アプリ側のスタック操作権限と、CreateChangesetとExecuteChangeset用のロールへのパスロール権限を与えます。

/iac/codepipeline.yaml -> CodePipelineRole
  CodePipelineRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesAppPipelineRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesAppPipelineRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - s3:Abort*
                  - s3:DeleteObject*
                  - s3:GetBucket*
                  - s3:GetObject*
                  - s3:List*
                  - s3:PutObject
                  - s3:PutObjectLegalHold
                  - s3:PutObjectRetention
                  - s3:PutObjectTagging
                  - s3:PutObjectVersionTagging
                Resource:
                  - !GetAtt CodePipelineArtifactsBucket.Arn
                  - !Join
                    - /
                    - - !GetAtt CodePipelineArtifactsBucket.Arn
                      - '*'
              - Effect: Allow
                Action:
                  - s3:PutObjectAcl
                  - s3:PutObjectVersionAcl
                Resource: !Join
                  - /
                  - - !GetAtt CodePipelineArtifactsBucket.Arn
                    - '*'
              - Effect: Allow
                Action: codestar-connections:*
                Resource: !Sub arn:aws:codeconnections:${AWS::Region}:${AWS::AccountId}:connection/6a7eb867-8d4f-400e-b124-494d8874c175
              - Effect: Allow
                Action: codebuild:*
                Resource: !Sub arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:project/GetMeowAdvices*
+             - Effect: Allow
+               Action: cloudformation:*
+               Resource:
+                 - !Sub arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/GetMeowAdvicesAppStack
+                 - !Sub arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/GetMeowAdvicesAppStack/*
+             - Effect: Allow
+               Action: iam:PassRole
+               Resource: !Sub arn:aws:iam::${AWS::AccountId}:role/GetMeowAdvicesAppPipeline*

CloudFormationからパイプラインを更新し、再実行します。
まだCreateChangeSetでエラーが出ています。Systems ManagerのParameter Storeへのアクセス権限が足りないようです。

User: arn:aws:sts::xxxxxxxxxxxx:assumed-role/GetMeowAdvicesAppPipelineCreateChangesetRole/AWSCloudFormation is not authorized to perform: ssm:GetParameters on resource: arn:aws:ssm:ap-northeast-1:xxxxxxxxxxxx:parameter/Test/LINE_ACCESS_TOKEN because no identity-based policy allows the ssm:GetParameters action (Service: AWSSimpleSystemsManagement; Status Code: 400; Error Code: AccessDeniedException; Request ID: 7d2d28b5-d969-496f-aa16-8eb1726bec52; Proxy: null)

名前空間を/Testにしたのを後悔しつつ、権限を追加します。

image.png

/iac/codepipeline.yaml -> CreateChangesetRole
  CreateChangesetRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesAppPipelineCreateChangesetRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: cloudformation.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesAppPipelineCreateChangesetRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: cloudformation:CreateChangeSet
                Resource: !Sub arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/GetMeowAdvicesAppStack/*
              - Effect: Allow
                Action: s3:*
                Resource: '*'
+             - Effect: Allow
+               Action: ssm:GetParameters
+               Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/Test/*

CloudFormationからパイプラインを更新し、再実行します。
CreateChangeSetが成功しました!

image.png

Approvalステージのレビューウインドウで何をしたら良いかわからないため、レビュー用リンクとメッセージを設定します。
レビュー用リンクからは変更セットに遷移できるようしましょう。

image.png

以下が「変更セットのタブかつ、Stack名を絞り込んだ状態」のURLです。

https://ap-northeast-1.console.aws.amazon.com/cloudformation/home?region=ap-northeast-1#/stacks/changesets?filteringText=GetMeowAdvicesAppStack
/iac/codepipeline.yaml -> CodePipeline -> Approval stage
        - Name: Approval
          Actions:
            - Name: ApproveExecuteChangeset
              ActionTypeId:
                Category: Approval
                Owner: AWS
                Version: 1
                Provider: Manual
+             Configuration:
+               ExternalEntityLink: !Sub https://${AWS::Region}.console.aws.amazon.com/cloudformation/home?region=${AWS::Region}#/stacks/changesets?filteringText=GetMeowAdvicesAppStack
+               CustomData: レビュー用URLから変更セットを確認し、リリース承認をお願いします。

CloudFormationからパイプラインを更新し、再実行します。

image.png

レビュー用URLをクリックすると、スタック一覧画面が表示されます。
URLで指定したスタック名で絞り込まれ、スタックが未選択の状態です。
画面左側のスタック名をクリックします。

image.png

変更セットが出てきました。

image.png

アプリ側のtemplate.yamlは修正していないため、「変更はありません」と表記されています。

image.png

CodePipelineの画面に戻り、承認して先に進みます。

image.png

成功しました!!8日ぶりのリリースです。

image.png

image.png

アプリのリファクタリング

ここまでで目標120分に対し180分かかっていますが、せっかくCI/CDパイプラインができたのでリファクタリングをします。
誕生日を祝ってもらうアプリから、いつでもアドバイスをもらえるアプリに生まれ変わりました。
happybirthday系の変数名などを直していきます。

/function/meowAdvice/lambda_function.py
- happyMeowthdayToLine(imeowge, f'{admeowce}\n{admeowce_jp}')
+ postAdmeowceToLine(imeowge, f'{admeowce}\n{admeowce_jp}')
/function/meowAdvice/lambda_function.py
- def happyMeowthdayToLine(imeowge, meowssage):
-   _msg = f'Happy Meowthday!!\n{meowssage}'
    _url = 'https://api.line.me/v2/bot/message/push'
+ def postAdmeowceToLine(imeowge, meowssage):
+   _msg = f'あなたにアドバイスをあげるにゃん。\n{meowssage}'
    _url = 'https://api.line.me/v2/bot/message/push'
/statemachine/getMeowAdvicesFlow.asl.json
    "猫の群れに入る": {
      "Type": "Map",
      "ItemProcessor": {
        "ProcessorConfig": {
          "Mode": "INLINE"
        },
-       "StartAt": "それぞれの猫にお祝いの言葉を言ってもらう",
+       "StartAt": "それぞれの猫にアドバイスをもらう",
        "States": {
-         "それぞれの猫にお祝いの言葉を言ってもらう": {
+         "それぞれの猫にアドバイスをもらう": {
            "Type": "Task",
            "Resource": "arn:aws:states:::lambda:invoke",
            "Output": "{% $states.result.Payload %}",
            "Arguments": {
              "FunctionName": "${LambdaFunctionMeowAdviceArn}"
            },

GitHubでmainブランチにマージをします。

image.png

パイプラインが反応しません。。
今日は一旦忘れて、パイプラインを手動で実行します。

image.png

変更セットにLambda関数とStateMachineが出てきました。

image.png

Approval -> ExecuteChangesetを実行するとエラーが発生しました。
ExecuteChangesetの実行には、CreateChangesetRoleにiam:GetRoleが必要ということです。よくわかりませんが、わかりました。

Unable to retrieve Arn attribute for AWS::IAM::Role, with error message User: arn:aws:sts::xxxxxxxxxxxx:assumed-role/GetMeowAdvicesAppPipelineCreateChangesetRole/AWSCloudFormation is not authorized to perform: iam:GetRole on resource: role IAMRoleLFMeowAdvice because no identity-based policy allows the iam:GetRole action (Service: Iam, Status Code: 403, Request ID: 3c8e24b2-d466-4984-8da1-f407afb1c3c6)

Unable to retrieve Arn attribute for AWS::IAM::Role, with error message User: arn:aws:sts::xxxxxxxxxxxx:assumed-role/GetMeowAdvicesAppPipelineCreateChangesetRole/AWSCloudFormation is not authorized to perform: iam:GetRole on resource: role IAMRoleLFFindCatsGroup because no identity-based policy allows the iam:GetRole action (Service: Iam, Status Code: 403, Request ID: f06bc86a-77fb-47c2-8751-8fc94defa548)

/iac/codepipeline.yaml -> CreateChangesetRole
  CreateChangesetRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesAppPipelineCreateChangesetRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: cloudformation.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesAppPipelineCreateChangesetRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: cloudformation:CreateChangeSet
                Resource: !Sub arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/GetMeowAdvicesAppStack/*
              - Effect: Allow
                Action: ssm:GetParameters
                Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/Test/*
+             - Effect: Allow
+               Action: iam:GetRole
+               Resource: '*'

再度実行しても、エラーが発生しました。
ExecuteChangesetの実行には、CreateChangesetRoleにlambda:UpdateFunctionCodeの権限が必要とのことです。

Resource handler returned message: "User: arn:aws:sts::xxxxxxxxxxxx:assumed-role/GetMeowAdvicesAppPipelineCreateChangesetRole/AWSCloudFormation is not authorized to perform: lambda:UpdateFunctionCode on resource: arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:meowAdvice because no identity-based policy allows the lambda:UpdateFunctionCode action (Service: Lambda, Status Code: 403, Request ID: e757ea4a-4b98-4a7b-ae89-b5a3695d3603)" (RequestToken: fb31b61b-519e-baef-f803-d5912c53122a, HandlerErrorCode: AccessDenied)

/iac/codepipeline.yaml -> CreateChangesetRole
  CreateChangesetRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesAppPipelineCreateChangesetRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: cloudformation.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesAppPipelineCreateChangesetRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: cloudformation:CreateChangeSet
                Resource: !Sub arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/GetMeowAdvicesAppStack/*
              - Effect: Allow
                Action: ssm:GetParameters
                Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/Test/*
              - Effect: Allow
                Action: iam:GetRole
                Resource: '*'
+             - Effect: Allow
+               Action: lambda:UpdateFunctionCode
+               Resource: '*'

次はS3とStep Functionsでエラーが発生しました。
S3はbuildspec.yamlのpackageコマンドで指定した配置先S3バケットがテスト用のものになっていました。
パイプラインのアーティファクト用バケットに変更します。

Resource handler returned message: "Your access has been denied by S3, please make sure your request credentials have permission to GetObject for siruko-cloudformation-templetes/44ac053ec6f3eb15ffed38be576c18b8.

/iac/codepipeline.yaml -> CodePipeline -> Build stage
        - Name: Build
          Actions:
            - Name: BuildByCodeBuild
              ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: 1
              Configuration:
+               EnvironmentVariables: !Sub
+                 - |
+                   [{
+                     "name": "bucket",
+                     "value": "${CodePipelineArtifactsBucket}",
+                     "type": "PLAINTEXT"
+                   }]
+                 - Env: !Ref CodePipelineArtifactsBucket
                ProjectName: !Ref CodeBuildProjectForPackageCfnTemplate
              InputArtifacts:
                - Name: SourceAtf
              OutputArtifacts:
                - Name: BuildAtf
              Region: !Ref AWS::Region
              Namespace: BuildByCodeBuild
/iac/buildspec.yaml
version: 0.2

phases:
  install:
    commands:
      - echo Start package...
      - echo ${bucket}
  build:
    commands:
      - echo Packageing...
      - aws cloudformation package
        --template-file ./iac/template.yaml
-       --s3-bucket siruko-cloudformation-templetes
+       --s3-bucket ${bucket}
        --output-template-file packaged-template.yaml
      - ls -l packaged-template.yaml
      - cat packaged-template.yaml | grep ${bucket}
artifacts:
  files:
    - packaged-template.yaml

次はStep Functionsの更新権限が無いとエラーが発生しました。
どうやらCreateChangesetRoleの方にリソースの作成権限、更新権限、削除権限を付けないといけないようです。

Resource handler returned message: "User: arn:aws:sts::xxxxxxxxxxxx:assumed-role/GetMeowAdvicesAppPipelineCreateChangesetRole/AWSCloudFormation is not authorized to perform: states:UpdateStateMachine on resource: arn:aws:states:ap-northeast-1:xxxxxxxxxxxx:stateMachine:GetMeowAdvicesFlow because no identity-based policy allows the states:UpdateStateMachine action

/iac/codepipeline.yaml -> CreateChangesetRole
  CreateChangesetRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesAppPipelineCreateChangesetRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: cloudformation.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesAppPipelineCreateChangesetRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: cloudformation:CreateChangeSet
                Resource: !Sub arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/GetMeowAdvicesAppStack/*
              - Effect: Allow
                Action: ssm:GetParameters
                Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/Test/*
+             - Effect: Allow
+               Action:
+                 - iam:*
+                 - lambda:*
+                 - states:*
+                 - scheduler:*
+               Resource: '*'
+             - Effect: Allow
+               Action:
+                 - s3:GetObject*
+               Resource:
+                 - !GetAtt CodePipelineArtifactsBucket.Arn
+                 - !Join
+                   - /
+                   - - !GetAtt CodePipelineArtifactsBucket.Arn
+                     - '*'

反対に、ExecutionChangesetには各リソースを操作する権限は不要なので、最小権限にしておきます。

/iac/codepipeline.yaml -> ExecuteChangesetRole
  ExecuteChangesetRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesAppPipelineExecuteChangesetRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: cloudformation.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesAppPipelineExecuteChangesetRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: cloudformation:ExecuteChangeSet
                Resource: !Sub arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/GetMeowAdvicesAppStack/*

これでExecuteChangesetが正常終了し、パイプラインが成功するようになったので、Step Functionsを動かしてみます。

image.png

image.png

はい、今日も4時間かかりました。。
タイムアタックは2時間以内にしたいですね。お疲れ様でした。


ここから3日目に入ります。
まず、GitHub接続は、GitHub側で承認が必要のようです。

image.png

承認後に払い出された接続ARNに差し替えると、自動でパイプラインが起動するようになりました。

/iac/codepipeline.yaml -> CodePipeline -> Source stage
        - Name: Source
          Actions:
            - Name: CodeConnections
              ActionTypeId:
                Category: Source
                Owner: AWS
                Provider: CodeStarSourceConnection
                Version: 1
              Configuration:
+               ConnectionArn: !Sub arn:aws:codeconnections:us-east-1:${AWS::AccountId}:connection/866c95b8-75a9-4c3b-bec9-1f31ed5b9b31
                FullRepositoryId: higurashit/topengineer-advent-calendar-2024-meow
                BranchName: main
              OutputArtifacts:
                - Name: SourceAtf

image.png

おまけ

呼び出した際に0匹となって寂しいことがあったのと、Mapのループが無機質だったのでfindCatsGroup関数を以下の通り改善します。

/function/findCatsGroup/lambda_function.py
import random

def lambda_handler(event, context):

    names = [
        'Alice', 'Bob', 'Carol', 'Charlie', 'Dave', 'Ellen', 'Eve', 'Frank', 'Isaac', 'Ivan',
        'Justin', 'Mallory', 'Marvin', 'Mallet', 'Matilda', 'Oscar', 'Pat', 'Peggy', 'Victor',
        'Plod', 'Steve', 'Trent', 'Trudy', 'Walter', 'Zoe', 'Darwin'
    ]
    my_cats = []
    my_cats_length = random.randrange(6) + 1 # 1~7匹
    
    for cat in range(my_cats_length):
        name = pick_name(names, my_cats)
        my_cats.append({'name': name})

    return {
        "my_cats": my_cats
    }

def pick_name(names, ignores):
    name = random.choice(names)
    if name in ignores:
        return pick_name(names, ignores)
    return name
/function/meowAdvice/lambda_function.py
~~~~
def lambda_handler(event, context):
+   # 猫の名前を取得
+   name = get_name(event)

    # 猫画像取得
    imeowge = get_imeowge()

    # アドバイス取得
    admeowce = get_admeowce()
    admeowce_jp = get_admeowce_jp(admeowce)
    admeowce_jp = translate_meow(admeowce_jp)

+   # メッセージ
+   meowssage = f'{name}{admeowce_jp} ({admeowce}) 」'

    # LINEに送信
-   post_admeowce_to_line(imeowge, f'{admeowce}\n{admeowce_jp}')
+   post_admeowce_to_line(imeowge, meowssage)

+def get_name(event):
+   default_cat_name = 'Cat'
+   return event.key('name', default_cat_name)
~~~~

StateMachineを手動実行します。
Victorという名前の1匹の猫が見つかりました。

image.png

LINEを見ると、Victorからアドバイスがきています!
Victor、いいキャラしてますね。

image.png

はい、ここまででリファクタリング(?)は一旦完了とします。
Gitにタグを打ち、次のアイデアが降りてくるのを待つことにします。

所要時間

目標を240分に設定していたのですが、実際には640分かかってしまいました。

  • 序章:20分
  • ① アプリケーション部分のIaC化:60分
  • ② アプリケーションリリースのCI/CD化:250分
  • ③ パイプラインのIaC化:180分
  • アプリのリファクタリング:60分
  • GitHub対応:10分
  • おまけ:60分

全てにおいて見立てより遅いです。
リリースパイプラインの作成とIaC化で半分以上かかっています。うん、反省。

まとめ

これで予約投稿します。
久しぶりに作業すると、思わぬところでハマりますね。アウトプットしないと忘れてしまうということを、改めて実感できた良い機会でした。

私個人の所感として、タイムアタックは、

  • 適当でも良いのでとにかくアウトプットする
  • 時間をあまり使わない

という2点から、投稿のモチベーションを保つのに役立つと考えました。
今後もタイムアタックを仕掛けながら、このアプリを大きくしていこうと思います。
あとはもうちょいAWSの深淵を覗かないとな、、と思いました。

それでは皆さん、良いクリスマスを!!

2
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
2
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?