CloudFormation

マネジメントコンソールで同じ構成を毎回画面ポチポチする工数を削減したくて、CloudFormationで自動化したかった

クロススタック分割

詳細はAWS備忘録記事3を参考にすること
参考
https://aws.amazon.com/jp/blogs/news/webinar-bb-aws-cloudformation-2020/

シングルAZを作成するCloudFormation

ネットワーク構成図

コード

参考
はじめてのAWS CloudFormationチュートリアル
SSMセッションマネージャーでコンソールアクセスできるEC2をCloudFormationで自動作成する方法

AWSTemplateFormatVersion: "2010-09-09"
Description: CloudTech single-az-ec2-template

#----------------------------------------------------------------
# パラメーター
# パラメーターとはプログラミングで例えると変数みたいなもの
#----------------------------------------------------------------
Parameters:
  # プロジェクト名を入力する。各リソースのNameタグの値となる。
  EnvironmentName:
    Description: single-az-ec2-template
    Type: String

  # VPCのCIDRレンジ
  VpcCIDR:
    Description: Please enter the IP range (CIDR notation) for this VPC
    Type: String
    Default: 10.0.0.0/16

  # パブリックサブネット1
  PublicSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone
    Type: String
    Default: 10.0.1.0/24

  # プライベートサブネット1
  PrivateSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone
    Type: String
    Default: 10.0.2.0/24

  # EC2インスタンスタイプ
  InstanceType:
    Type: String
    Default: t2.micro
    AllowedValues:
      - t2.micro
      - t2.nano
    Description: Select EC2 instance type.
#----------------------------------------------------------------
# リソース
#----------------------------------------------------------------
Resources:

#----------------------
# VPC作成
#----------------------
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      # 何の設定かわからないが、基本的にtrueにしても良さそう
      EnableDnsSupport: true
      EnableDnsHostnames: false
      Tags:
        - Key: Name
          Value: !Ref EnvironmentName

#----------------------
# Publicサブネット1作成
#----------------------
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      CidrBlock: !Ref PublicSubnet1CIDR
      # サブネットで起動されたインスタンスが起動時にパブリック IP アドレスを設定するかどうか
      # Publicサブネットなのでtrueにする
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Subnet (AZ1)

#----------------------
# Privateサブネット1作成
#----------------------
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs  '' ]
      CidrBlock: !Ref PrivateSubnet1CIDR
      # Privateサブネットなのでtrueにする
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Private Subnet (AZ1)

#----------------------
# IGW作成
#----------------------
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Ref EnvironmentName

#----------------------
# IGWをVPCにアタッチする
#----------------------
  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

#----------------------
# ルートテーブル(Publicサブネット用)作成
#----------------------
  # ルートテーブル(Publicサブネット用)作成
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Routes

  # 上記のルートテーブルに、下記レコードを追加
  DefaultPublicRoute:
    Type: AWS::EC2::Route
    # リソースの作成順序は、IGWがVPCにアタッチされた後とする
    DependsOn: InternetGatewayAttachment
    # カラム
    Properties:
      RouteTableId: !Ref PublicRouteTable
      # すべてネットワーク
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  # Publicサブネット1に、上記ルートテーブルを紐付ける
  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      # ルートテーブル(Publicサブネット用)
      RouteTableId: !Ref PublicRouteTable
      # Publicサブネット1
      SubnetId: !Ref PublicSubnet1

#----------------------
# ルートテーブル(Privateサブネット用)作成
#----------------------
  # ルートテーブル(Privateサブネット用)を作成
  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Private Routes

  # Privateサブネット1に、上記ルートテーブルを紐付ける
  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      SubnetId: !Ref PrivateSubnet1

#----------------------
# セキュリティグループを作成する
#----------------------
  # PublicサブネットのEC2にアタッチするセキュリティグループを定義する
  PublicSecurityGroup:
    Type: AWS::EC2::SecurityGroup # 公開セキュリティグループのリソースを定義します。
    Properties:
      GroupDescription: Allow SSH or HTTP or HTTPS
      VpcId: !Ref VPC
      # セキュリティグループのインバウンドルールを設定
      SecurityGroupIngress:
        # SSH
        # - IpProtocol: tcp #プロトコル
        #  FromPort: 22 #開始ポート
        #  ToPort: 22 #終了ポート
        #  CidrIp: 0.0.0.0/0 # 許可する送信元CIDR IPアドレス範囲
        # HTTP
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
        # HTTPS
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
        # ポート3000を許可
        #- IpProtocol: tcp
        #  FromPort: 3000
        #  ToPort: 3000
        #  CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: public-sg

  # PrivateサブネットのEC2にアタッチするセキュリティグループを定義する
  PrivateSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow SSH
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: private-sg
      SecurityGroupIngress:
        # SSH
        - IpProtocol: tcp
          FromPort: '22'
          ToPort: '22'
          CidrIp: 0.0.0.0/0

#----------------------
# PublicサブネットのEC2作成
#----------------------
  # 新しいキーペアを作成する。SSMに保存される
  NewKeyPair:
    Type: 'AWS::EC2::KeyPair'
    Properties:
      KeyName: KeyPair20230806

  # Publicサブネットに配置するEC2を作成する
  PublicSubnet1EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      # AMIはAmazonLinux2
      ImageId: "ami-0e25eba2025eea319"
      # インスタンスタイプは、t2.micro
      InstanceType: !Ref InstanceType
      # キーペア
      KeyName: !Ref NewKeyPair
      #SubnetId: !Ref PublicSubnet1
      #SecurityGroupIds: !Ref PublicSecurityGroup
      NetworkInterfaces:
        - SubnetId: !Ref PublicSubnet1
          GroupSet:
            - !Ref PublicSecurityGroup
          # パブリック IP の自動割り当て
          AssociatePublicIpAddress: true
          DeviceIndex : 0
      # ストレージを設定
      BlockDeviceMappings:
        # この値はAMIによって異なる場合があるため、適切な値に変更する必要があるかもしれません
        - DeviceName: "/dev/xvda"
          Ebs:
            VolumeType: "gp2"
            VolumeSize: 8
      # SSM経由でSSH接続出来るようにするIAMロールをアタッチする
      IamInstanceProfile:
        Ref: SessionManagerIamInstanceProfile
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Subnet EC2Instance

  # PublicサブネットのEC2インスタンスをSSM経由でSSH接続出来るようにする
  SsmSessionManagerIamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Service:
                - 'ec2.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      Path: '/'
      RoleName: 'SsmSessionManagerIamRole'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

  SessionManagerIamInstanceProfile:
    Type: 'AWS::IAM::InstanceProfile'
    Properties:
      Path: '/'
      Roles:
        - !Ref SsmSessionManagerIamRole

  # EC2にEIPをつける
  ElasticIpForPublicSubnet1EC2Instance:
    Type: AWS::EC2::EIP
    Properties:
      InstanceId: !Ref PublicSubnet1EC2Instance

#----------------------
# PublicサブネットのEC2作成
#----------------------
  # Privateサブネットに配置するEC2を作成する
  PrivateSubnet1EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      # AMIはAmazonLinux2
      ImageId: "ami-0e25eba2025eea319"
      # インスタンスタイプは、t2.micro
      InstanceType: !Ref InstanceType
      # キーペア
      KeyName: !Ref NewKeyPair
      SubnetId: !Ref PrivateSubnet1
      # ストレージを設定
      BlockDeviceMappings:
        # この値はAMIによって異なる場合があるため、適切な値に変更する必要があるかもしれません
        - DeviceName: "/dev/xvda"
          Ebs:
            VolumeType: "gp2"
            VolumeSize: 8
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Private Subnet EC2Instance
      # セキュリティグループ
      SecurityGroupIds:
        - !Ref PrivateSecurityGroup

PublicSubnet1EC2InstanceにSSM経由でログイン

前提
AWS CLIがインストール済みである

IAMユーザのアクセスキーを発行する

マネジメントコンソールからIAM>ユーザー>{IAMユーザ}>アクセスキーを作成
から「アクセスキーID」と「シークレットアクセスキー」を発行して、控える。
あとで、
aws configreを設定する際に必要

参考
【AWS Systems Manager】AWS CLIを用いてSSM経由でEC2インスタンスにアクセスしてみた

AWS CLI 用の Session Manager プラグインをインストールする

参考
 AWS CLI 用の Session Manager プラグインをインストールする

MacBook-Pro ~ %
curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/mac/session-manager-plugin.pkg" -o "session-manager-plugin.pkg"
sudo installer -pkg session-manager-plugin.pkg -target /
sudo ln -s /usr/local/sessionmanagerplugin/bin/session-manager-plugin /usr/local/bin/session-manager-plugin

AWS configre とSSM経由でPublicSubnet1EC2Instanceにログイン

# AWS configre 設定
# プロファイルは複数あるので、確認も兼ねて毎回指定した方が良い
MacBook-Pro ~ % aws configure --profile {任意なプロファイル名}
AWS Access Key ID: {アクセスキーID}
AWS Secret Access Key: {シークレットアクセスキー}
Default region name: {利用しているリージョン}
Default output format: {jsonもしくはyaml}

# SSM経由でPublicSubnet1EC2Instanceにログイン
aws ssm start-session --target {EC2インスタンスID} --profile {任意なプロファイル名}

# 下記表示になれば成功
sh-4.2$ 

シングルAZ + プライベートサブネットのEC2にSSM接続する

説明

EC2インスタンスのAMIがAmazon Linux2なんで、SSM Agentはプリインストールされている

4:EC2インスタンスにAmazonSSMManagedInstanceCoreポリシーが付与されたIAMロールをアタッチする

5:EC2インスタンスを作成する

6〜8:VPCエンドポイントを作成する
AWSサービス:com.amasonaws.ap-northeast-1.ssm
AWSサービス:com.amasonaws.ap-northeast-1.ssmmessages
AWSサービス:com.amasonaws.ap-northeast-1.ec2messages

VPCとプライベートサブネットに紐付けること
予め作成したセキュリティグループをアタッチすること

AWSTemplateFormatVersion: "2010-09-09"

Description: SSM Demo

#======================
# パラメーター
#======================
Parameters:

# VPCのCIDRレンジ
  VpcCIDR:
    Description: Please enter the IP range (CIDR notation) for this VPC
    Type: String
    Default: 172.31.0.0/16

# プライベートサブネット1
  PrivateSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone
    Type: String
    Default: 172.31.1.0/24

  KeyName:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone
    Type: AWS::EC2::KeyPair::KeyName

# 環境変数
  EnvironmentName:
    Description: Evname-tag
    Type: String
    Default: TEST

  Ec2ImageId:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2

  Ec2InstanceType:
    Type: String
    Default: t2.micro

#======================
# リソース
#======================

Resources:

#======================
##### VPC作成 #####
#======================
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Ref EnvironmentName

# プライベートサブネット1を作成
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs  '' ]
      CidrBlock: !Ref PrivateSubnet1CIDR
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Private Subnet (AZ1)

# ルートテーブル(プライベートサブネット用)作成
  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Private Routes

  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      SubnetId: !Ref PrivateSubnet1

#======================
##### EC2インスタンス用IAMロール #####
#======================
  EC2Role:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: [ec2.amazonaws.com]
            Action: ['sts:AssumeRole']
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

  EC2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref EC2Role

#======================
##### EC2作成 #####
#======================
  DemoInstance:
    Type: 'AWS::EC2::Instance'
    Properties: 
      ImageId: !Ref Ec2ImageId
      InstanceType: !Ref Ec2InstanceType
      AvailabilityZone: !GetAtt PrivateSubnet1.AvailabilityZone
      KeyName: !Ref KeyName
      NetworkInterfaces:
        - DeviceIndex: 0
          AssociatePublicIpAddress: false
          DeleteOnTermination: true
          SubnetId: !Ref PrivateSubnet1
          GroupSet: 
            - !Ref DemoSecurityGroup
      IamInstanceProfile: !Ref EC2InstanceProfile  # この行を追加

  DemoSecurityGroup:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      VpcId: !Ref VPC
      GroupDescription: SG to allow SSH access via port 22
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '22'
          ToPort: '22'
          CidrIp: '0.0.0.0/0'
      Tags:
        - Key: Name
          Value: SSH-SG

# ... [既存のテンプレート内容] ...

#======================
##### VPCエンドポイントの追加 #####
#======================

  VPCEndpointSSM:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      ServiceName: com.amazonaws.ap-northeast-1.ssm
      VpcId: !Ref VPC
      SubnetIds:
        - !Ref PrivateSubnet1 # ここで紐づけるサブネットを指定
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref VPCEndpointSecurityGroup
      VpcEndpointType: Interface


  VPCEndpointSSMMessages:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      ServiceName: com.amazonaws.ap-northeast-1.ssmmessages
      VpcId: !Ref VPC
      SubnetIds:
        - !Ref PrivateSubnet1 # ここで紐づけるサブネットを指定
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref VPCEndpointSecurityGroup
      VpcEndpointType: Interface

  VPCEndpointEC2Messages:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      ServiceName: com.amazonaws.ap-northeast-1.ec2messages
      VpcId: !Ref VPC
      SubnetIds:
        - !Ref PrivateSubnet1 # ここで紐づけるサブネットを指定
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref VPCEndpointSecurityGroup
      VpcEndpointType: Interface

# VPCエンドポイント用のセキュリティグループ
  VPCEndpointSecurityGroup:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      VpcId: !Ref VPC
      GroupDescription: 'VPC Endpoint Security Group'
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
      SecurityGroupEgress:
        - IpProtocol: -1
          FromPort: 0
          ToPort: 65535
          CidrIp: 0.0.0.0/0

AWSマネジメントコンソールからSSM接続する

CLIが開けば成功

Lambda ハンズオン

デモ1

必要であれば動画参照

AWSTemplateFormatVersion: '2010-09-09'
Description: AWS CloudFormation template with S3 bucket and Lambda function, including event notification setup.

Resources:
  # Lambda関数を作成
  S3PythonLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: s3-python-lambda-test
      Handler: "index.lambda_handler"
      Runtime: python3.7
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: |
          import json
          def lambda_handler(event, context):
              # AWS default lambda function code
              return {
                  'statusCode': 200,
                  'body': json.dumps('Hello from Lambda!')
              }

  # Lambda関数にアタッチするIAMロールを作成。IAMロール名は、「LambdaExecutionRole」である
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      # AWSのLambdaサービスはここで作成したIAMロールの権限を使用できるという宣言
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      # 付与するポリシーを定義
      Policies:
        - PolicyName: LambdaBasicExecution
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              # Lambda関数がCloudWatchに記録するアクションを許可する
              - Effect: Allow
                Action:
                  
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: arn:aws:logs:*:*:*
              # S3がLambdaに通知するアクションを許可する
              - Effect: Allow
                Action:
                  - s3:PutBucketNotification
                Resource: !GetAtt S3Bucket.Arn

  # S3バケットを作成
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: s3bucket-s3-python-lambda-test
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true

  # S3バケットの権限に権限を与える。与える権限の内容は、S3からLambda関数に対してイベントをトリガーするという権限
  S3BucketPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref S3PythonLambdaFunction
      Action: "lambda:InvokeFunction"
      Principal: "s3.amazonaws.com"
      SourceArn: !GetAtt S3Bucket.Arn

  # S3バケットからLambda関数へのイベント通知の設定をCloudFormationで直接行うことはできないため、代わりにカスタムリソースを作成する
  NotificationLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: NotificationLambdaFunctionTest
      Handler: "index.lambda_handler"
      Runtime: python3.7
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: |
          import boto3
          def lambda_handler(event, context):
              s3 = boto3.client('s3')
              response = s3.put_bucket_notification_configuration(
                  Bucket=event['ResourceProperties']['Bucket'],
                  NotificationConfiguration={'LambdaFunctionConfigurations': [{
                      'LambdaFunctionArn': event['ResourceProperties']['LambdaArn'],
                      'Events': ['s3:ObjectCreated:*']
                  }]})
              return response

  # カスタムリソースを作成
  CustomResourceForS3Notification:
    Type: AWS::CloudFormation::CustomResource
    Properties:
      ServiceToken: !GetAtt NotificationLambdaFunction.Arn
      Bucket: !Ref S3Bucket
      LambdaArn: !GetAtt S3PythonLambdaFunction.Arn

# エクスポート
Outputs:
  LambdaFunctionARN:
    Description: The ARN of the Lambda function
    Value: !GetAtt S3PythonLambdaFunction.Arn
  S3BucketName:
    Description: The name of the S3 Bucket
    Value: !Ref S3Bucket

デモ2

必要であれば動画参照

AWSTemplateFormatVersion: '2010-09-09'
Description: AWS CloudFormation template with S3 bucket and Lambda function, including event notification setup.

Resources:
  # Lambda関数を作成
  S3PythonLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: s3-python-lambda-dlq
      Handler: "index.lambda_handler"
      Runtime: python3.7
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: |
          import json
          def lambda_handler(event, context):
              # エラーを発生させるためのコードを追加
              raise Exception('エラー')
              # AWS default lambda function code
              return {
                  'statusCode': 200,
                  'body': json.dumps('Hello from Lambda!')
              }
      DeadLetterConfig:
        TargetArn: !GetAtt LambdaDeadLetterQueue.Arn

  # Lambda関数にアタッチするIAMロールを作成。IAMロール名は、「LambdaExecutionRole」である
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      # AWSのLambdaサービスはここで作成したIAMロールの権限を使用できるという宣言
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      # 付与するポリシーを定義
      Policies:
        - PolicyName: LambdaBasicExecution
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              # Lambda関数がCloudWatchに記録するアクションを許可する
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: arn:aws:logs:*:*:*
              # S3がLambdaに通知するアクションを許可する
              - Effect: Allow
                Action:
                  - s3:PutBucketNotification
                Resource: !GetAtt S3Bucket.Arn
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSQSFullAccess

  # DLQを作成
  LambdaDeadLetterQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: lambda-dead-queue

  # S3バケットを作成
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: s3bucket-s3-python-lambda-dlq
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true

  # S3バケットの権限に権限を与える。与える権限の内容は、S3からLambda関数に対してイベントをトリガーするという権限
  S3BucketPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref S3PythonLambdaFunction
      Action: "lambda:InvokeFunction"
      Principal: "s3.amazonaws.com"
      SourceArn: !GetAtt S3Bucket.Arn

  # S3バケットからLambda関数へのイベント通知の設定をCloudFormationで直接行うことはできないため、代わりにカスタムリソースを作成する
  NotificationLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: NotificationLambdaFunctionTestDlq
      Handler: "index.lambda_handler"
      Runtime: python3.7
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: |
          import boto3
          def lambda_handler(event, context):
              s3 = boto3.client('s3')
              response = s3.put_bucket_notification_configuration(
                  Bucket=event['ResourceProperties']['Bucket'],
                  NotificationConfiguration={'LambdaFunctionConfigurations': [{
                      'LambdaFunctionArn': event['ResourceProperties']['LambdaArn'],
                      'Events': ['s3:ObjectCreated:*']
                  }]})
              return response

  # カスタムリソースを作成
  CustomResourceForS3Notification:
    Type: AWS::CloudFormation::CustomResource
    Properties:
      ServiceToken: !GetAtt NotificationLambdaFunction.Arn
      Bucket: !Ref S3Bucket
      LambdaArn: !GetAtt S3PythonLambdaFunction.Arn

# エクスポート
Outputs:
  LambdaFunctionARN:
    Description: The ARN of the Lambda function
    Value: !GetAtt S3PythonLambdaFunction.Arn
  S3BucketName:
    Description: The name of the S3 Bucket
    Value: !Ref S3Bucket

結果

Lambdaで例外が発生しているログ

Lambdaを実行したが、例外が発生し、失敗内容がSQSに到達し、メッセージをポーリング(取得)するとSQSにメッセージが出現すれば成功

デモ3

チュートリアル: API Gateway で Lambda を使用する をコード化する

AWSTemplateFormatVersion: '2010-09-09'
Description: Lambda, API Gateway, and DynamoDB Example

Resources:
  MyDynamoDBTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: MyDynamoDBTable
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1

  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: LambdaDynamoDBAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - dynamodb:GetItem
                  - dynamodb:PutItem
                  - dynamodb:UpdateItem
                  - dynamodb:DeleteItem
                  - dynamodb:Scan
                  - dynamodb:Query
                Resource: !GetAtt MyDynamoDBTable.Arn

  MyLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          # Lambda function code goes here
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Runtime: nodejs18.x

  ApiGatewayRestApi:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Name: MyAPI

  ApiGatewayResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId: !Ref ApiGatewayRestApi
      ParentId: !GetAtt ApiGatewayRestApi.RootResourceId
      PathPart: myresource


  MyLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !GetAtt MyLambdaFunction.Arn
      Principal: apigateway.amazonaws.com
      SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiGatewayRestApi}/Prod/POST/myresource

  ApiGatewayMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref ApiGatewayRestApi
      ResourceId: !Ref ApiGatewayResource
      HttpMethod: POST
      AuthorizationType: NONE
      Integration:
        IntegrationHttpMethod: POST
        Type: AWS_PROXY
        Uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambdaFunction.Arn}/invocations

  ApiGatewayDeployment:
    Type: 'AWS::ApiGateway::Deployment'
    DependsOn: ApiGatewayMethod
    Properties:
      RestApiId: !Ref ApiGatewayRestApi
      StageName: 'Prod'


Outputs:
  ApiGatewayInvokeURL:
    Description: URL to invoke the API Gateway
    Value: !Sub https://${ApiGatewayRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/myresource
  DynamoDBTableName:
    Description: Name of the DynamoDB Table
    Value: !Ref MyDynamoDBTable

lambdaのコード
ざっくりDynamoDBのテーブル似対してCRUDするコード

const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const { DynamoDBDocumentClient, PutCommand, GetCommand, UpdateCommand, DeleteCommand } = require('@aws-sdk/lib-dynamodb');

const ddbClient = new DynamoDBClient({ region: "ap-northeast-1" });
const ddbDocClient = DynamoDBDocumentClient.from(ddbClient);

const tablename = "MyDynamoDBTable";

module.exports.handler = async (event, context) => {
    const operation = event.operation;
    if (!event.payload) {
        return { error: "Payload is undefined" };
    }

    event.payload.TableName = tablename;

    if (operation === 'echo'){
        return event.payload;
    } else { 
        switch (operation) {
            case 'create':
                await ddbDocClient.send(new PutCommand(event.payload));
                break;
            case 'read':
                var table_item = await ddbDocClient.send(new GetCommand(event.payload));
                console.log(table_item);
                break;
            case 'update':
                await ddbDocClient.send(new UpdateCommand(event.payload));
                break;
            case 'delete':
                await ddbDocClient.send(new DeleteCommand(event.payload));
                break;
            default:
                return `Unknown operation: ${operation}`;
        }
    }
};

失敗原因がわかるまでは、APIGateWayはAWSマネジメントコンソールから直接APIを作成する。

CloudFormatinoから作成したAPIGateWayからでは、下記JSONをリクエストしてもDynamoDBにレコードがCreateできない。Lambdaもしくは、AWSマネジメントコンソールで直接下記JSONを実行するとDynamoDBにレコードが作成される。恐らくAPIGateWayとLambdaの権限で何かが失敗している。
もしくは、下記CloudFormationテンプレートファイルを基に作成
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/services-apigateway-template.html

{
  "operation": "create",
  "payload": {
    "Item": {
      "id": "1234ABCD",
      "number": 5
    }
  }
}

総合

AWSのRDSのマネジメント画面にはどんな項目があるのかをコード化前に把握する用

ハンズオン1

構成

  1. VPC作成
  2. パブリックサブネット1作成
  3. パブリックサブネット2作成
  4. プライベートサブネット1作成
  5. プライベートサブネット2作成
  6. インターネットゲートウェイを作成
  7. ルートテーブルを作成 (パブリックサブネット専用)
  8. ルートテーブルを作成(プライベートサブネット専用)
  9. セキュリティグループ作成(パブリックサブネットのEC2用)
  10. EC2インスタンスを作成
  11. セキュリティグループ作成(RDS用)
  12. サブネットグループ作成
  13. RDSインスタンスを作成

CloudFormationテンプレート

  • クロススタック分割
    • network-stack-template.yaml
    • security-stack-template.yaml
    • application-stack-template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'hands-on1'
#----------------------------------------------------------------
# パラメーター
#----------------------------------------------------------------
Parameters:
  # プロジェクト名を入力する。各リソースのNameタグで参照する
  EnvironmentName:
    Description: hands-on1-template
    Type: String

  # VPCのCIDRレンジ
  VpcCIDR:
    Description: Please enter the IP range (CIDR notation) for this VPC
    Type: String
    Default: 10.0.0.0/21

  # パブリックサブネット1
  PublicSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone
    Type: String
    Default: 10.0.0.0/24

  # パブリックサブネット2
  PublicSubnet2CIDR:
    Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone
    Type: String
    Default: 10.0.1.0/24

  # プライベートサブネット1
  PrivateSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone
    Type: String
    Default: 10.0.2.0/24

  # プライベートサブネット2
  PrivateSubnet2CIDR:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone
    Type: String
    Default: 10.0.3.0/24

#----------------------------------------------------------------
# リソース
#----------------------------------------------------------------
Resources:
#----------------------
# VPC作成
#----------------------
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      # 何の設定かわからないが、基本的にtrueにしても良さそう
      EnableDnsSupport: true
      EnableDnsHostnames: false
      Tags:
        - Key: Name
          Value: !Ref EnvironmentName
#----------------------
# Publicサブネット作成
#----------------------
  # パブリックサブネット1
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      # AZ-1a
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref PublicSubnet1CIDR
      # サブネットで起動されたインスタンスが起動時にパブリック IP アドレスを設定するかどうか
      # Publicサブネットなのでtrueにする
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Subnet

  # パブリックサブネット2
  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      # AZ-1c
      AvailabilityZone: ap-northeast-1c
      CidrBlock: !Ref PublicSubnet2CIDR
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Subnet
#----------------------
# Privateサブネット作成
#----------------------
  #プライベートサブネット1
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      # AZ-1a
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref PrivateSubnet1CIDR
      # Privateサブネットなのでfalseにする
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Private Subnet

  #プライベートサブネット2
  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      # AZ-1c
      AvailabilityZone: ap-northeast-1c
      CidrBlock: !Ref PrivateSubnet2CIDR
      # Privateサブネットなのでfalseにする
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Private Subnet

#----------------------
# IGW作成
#----------------------
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Ref EnvironmentName

#----------------------
# IGWをVPCにアタッチする
#----------------------
  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

#----------------------
# ルートテーブル作成 (Publicサブネット用)
#----------------------
  # ルートテーブル(Publicサブネット用)作成
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Routes

  # 上記のルートテーブルに、レコードを追加する
  DefaultPublicRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      # IGW用のレコードを追加する
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  # Publicサブネット1に、上記ルートテーブルを関連付ける
  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet1

#----------------------
# RDSサブネットグループ作成
#----------------------
  RdsSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: "Subnet Group for RDS instances"
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
      DBSubnetGroupName: my-rds-subnet-group
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName} RDS Subnet Group"

#----------------------
# SSMロール
#----------------------
  SsmSessionManagerIamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Service:
                - 'ec2.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      Path: '/'
      RoleName: 'SsmSessionManagerIamRole'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

  SessionManagerIamInstanceProfile:
    Type: 'AWS::IAM::InstanceProfile'
    Properties:
      Path: '/'
      Roles:
        - !Ref SsmSessionManagerIamRole

#----------------------
# エクスポート
#----------------------
Outputs:
  VPCId:
    Value: !Ref VPC
    Description: "VPC ID"
    Export:
      Name: !Sub "${EnvironmentName}-VPCId"

  PublicSubnetId:
    Value: !Ref PublicSubnet1
    Description: "PublicSubnet1"
    Export:
      Name: !Sub "${EnvironmentName}-PublicSubnet1Id"

  RdsSubnetGroupId:
    Value: !Ref RdsSubnetGroup
    Description: "RDS Subnet Group Name"
    Export:
      Name: !Sub "${EnvironmentName}-RdsSubnetGroupId"

  SessionManagerIamInstanceProfile:
    Value: !Ref SessionManagerIamInstanceProfile
    Description: "SessionManagerIamInstanceProfile"
    Export:
      Name: !Sub "${EnvironmentName}-SessionManagerIamInstanceProfile"
AWSTemplateFormatVersion: '2010-09-09'
Description: 'hands-on1'
#----------------------------------------------------------------
# パラメーター
#----------------------------------------------------------------
Parameters:
  EnvironmentName:
    Description: hands-on1-template
    Type: String
#----------------------------------------------------------------
# リソース
#----------------------------------------------------------------
Resources:

#----------------------
# セキュリティグループ作成
#----------------------
  # EC2用のセキュリティグループ
  WebSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: Web-SG-1
      GroupDescription: "Description"
      VpcId: !ImportValue
        'Fn::Sub': '${EnvironmentName}-VPCId'
      #インバウンドルール
      SecurityGroupIngress:
        # SSH
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
      #アウトバウンドルール
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: Web-sg1

  RdsSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: RDS-SG-1
      GroupDescription: "Description"
      VpcId: !ImportValue
        'Fn::Sub': '${EnvironmentName}-VPCId'
      SecurityGroupIngress:
        # Replace with your specific IP range or remove the CidrIp property to allow access from anywhere
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          SourceSecurityGroupId: !Ref WebSecurityGroup
      Tags:
        - Key: Name
          Value: RDS-SG-1

#----------------------
# エクスポート
#----------------------
Outputs:
  WebSecurityGroup:
    Value: !Ref WebSecurityGroup
    Description: "Web Security Group ID"
    Export:
      Name: !Sub "${EnvironmentName}-WebSecurityGroupId"

  RdsSecurityGroup:
    Value: !Ref RdsSecurityGroup
    Description: "Security Group ID for RDS access"
    Export:
      Name: !Sub "${EnvironmentName}-RdsSecurityGroupId"
AWSTemplateFormatVersion: '2010-09-09'
Description: 'hands-on1'
#----------------------------------------------------------------
# パラメーター
#----------------------------------------------------------------
Parameters:
  # EC2インスタンスタイプ
  InstanceType:
    Type: String
    Default: t2.micro
    AllowedValues:
      - t2.micro
      - t2.nano
    Description: Select EC2 instance type.

  EnvironmentName:
    Description: hands-on1-template
    Type: String
#----------------------------------------------------------------
# リソース
#----------------------------------------------------------------
Resources:

#----------------------
# EC2作成
#----------------------
  PublicSubnet1EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      # AMIは、AmazonLinux2
      ImageId: "ami-0e25eba2025eea319"
      # インスタンスタイプは、t2.micro
      InstanceType: !Ref InstanceType
      # 自動割当パプリックIPを有効にする場合はNetworkInterfacesを記載する必要がある
      NetworkInterfaces:
        - SubnetId: !ImportValue
            'Fn::Sub': '${EnvironmentName}-PublicSubnet1Id'
          GroupSet:
            - !ImportValue
              'Fn::Sub': '${EnvironmentName}-WebSecurityGroupId'
          # 自動割当パプリックIPを有効
          AssociatePublicIpAddress: true
          DeviceIndex: 0
      # ストレージを設定
      BlockDeviceMappings:
        - DeviceName: "/dev/xvda"
          Ebs:
            VolumeType: "gp2"
            VolumeSize: 8
      # SSM経由でSSH接続出来るようにするIAMロールをアタッチする
      IamInstanceProfile: !ImportValue
        'Fn::Sub': '${EnvironmentName}-SessionManagerIamInstanceProfile'
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName} Public Subnet EC2Instance"

  # EC2にEIPをつける
  ElasticIpForPublicSubnet1EC2Instance:
    Type: AWS::EC2::EIP
    Properties:
      InstanceId: !Ref PublicSubnet1EC2Instance


#----------------------
# RDS作成
#----------------------
  MyRDSInstance:
    Type: AWS::RDS::DBInstance
    Properties:
      # データベースエンジン
      Engine: mysql
      # マスターユーザー名
      MasterUsername: wordpress
      # ストレージの割り当て (GB)
      AllocatedStorage: 20
      # マスターパスワード(実際の環境ではセキュアな方法で提供する)
      MasterUserPassword: "ChangeMe1234"
      # DBインスタンスクラス
      DBInstanceClass: db.t2.micro
      # データベース名
      DBName: wordpress
      # VPCの設定(Outputsから参照)
      DBSubnetGroupName: !ImportValue
        'Fn::Sub': '${EnvironmentName}-RdsSubnetGroupId'
      # パブリックアクセス無効
      PubliclyAccessible: false
      # セキュリティグループ
      VPCSecurityGroups:
        - !ImportValue
          'Fn::Sub': '${EnvironmentName}-RdsSecurityGroupId'
      # スタンバイインスタンスを作成しない(シングルAZ)
      MultiAZ: false

wordpressインストール

# プロファイル一覧を表示する
aws configure list-profiles

# プロファイル未設定の場合は、プロファイルを設定する
aws configure --profile adminuser

# SSM経由でPublicSubnet1EC2Instanceにログイン
aws ssm start-session --target {EC2インスタンスID} --profile {任意なプロファイル名}

# 下記表示になれば成功
sh-4.2$ 

sh-4.2$ sudo su -

[root@ ~]# amazon-linux-extras install php7.2 -y
[root@ ~]# yum -y install mysql httpd php-mbstring php-xml gd php-gd

[root@ ~]# systemctl enable httpd.service
[root@ ~]# systemctl start httpd.service

[root@ ~]# wget http://ja.wordpress.org/latest-ja.tar.gz ~/
[root@ ~]# tar zxvf ~/latest-ja.tar.gz
[root@ ~]# cp -r ~/wordpress/* /var/www/html/
[root@ ~]# chown apache:apache -R /var/www/html
データベース名wordpress
ユーザー名wordpress
パスワード{cloudformation(パラメーターストア)等で指定したパスワード}
データベースのホスト名RDSのエンドポイント
テーブルの接頭辞wp_

下記になれば成功

ハンズオン2

AWSマネジメントコンソールのターゲットグループにはどんな項目があるのかを把握する用、コード化前に

ターゲットグループ

ALB作成画面

構成

14.セキュリティグループ作成(ALB用)
15.セキュリティグループWebに対してソースを更新する。更新値は、上記で作成したALBを参照する。
16.パブリックサブネット2の中にEC2インスタンスを作成
17.ALBターゲットグループ、ALBリスナー、ALBを作成
18.プライベートサブネット1で作成したRDSに対してマルチAZオプションを有効化する

CloudFormationテンプレート

  • クロススタック分割
    • network-stack-template.yaml
    • security-stack-template.yaml
    • application-stack-template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'hands-on1'
#----------------------------------------------------------------
# パラメーター
#----------------------------------------------------------------
Parameters:
  # プロジェクト名を入力する。各リソースのNameタグで参照する
  EnvironmentName:
    Description: hands-on1-template
    Type: String

  # VPCのCIDRレンジ
  VpcCIDR:
    Description: Please enter the IP range (CIDR notation) for this VPC
    Type: String
    Default: 10.0.0.0/21

  # パブリックサブネット1
  PublicSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone
    Type: String
    Default: 10.0.0.0/24

  # パブリックサブネット2
  PublicSubnet2CIDR:
    Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone
    Type: String
    Default: 10.0.1.0/24

  # プライベートサブネット1
  PrivateSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone
    Type: String
    Default: 10.0.2.0/24

  # プライベートサブネット2
  PrivateSubnet2CIDR:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone
    Type: String
    Default: 10.0.3.0/24

#----------------------------------------------------------------
# リソース
#----------------------------------------------------------------
Resources:
  #----------------------
  # VPC作成
  #----------------------
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      # 何の設定かわからないが、基本的にtrueにしても良さそう
      EnableDnsSupport: true
      EnableDnsHostnames: false
      Tags:
        - Key: Name
          Value: !Ref EnvironmentName
  #----------------------
  # Publicサブネット作成
  #----------------------
  # パブリックサブネット1
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      # AZ-1a
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref PublicSubnet1CIDR
      # サブネットで起動されたインスタンスが起動時にパブリック IP アドレスを設定するかどうか
      # Publicサブネットなのでtrueにする
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Subnet

  # パブリックサブネット2
  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      # AZ-1c
      AvailabilityZone: ap-northeast-1c
      CidrBlock: !Ref PublicSubnet2CIDR
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Subnet
  #----------------------
  # Privateサブネット作成
  #----------------------
  #プライベートサブネット1
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      # AZ-1a
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref PrivateSubnet1CIDR
      # Privateサブネットなのでfalseにする
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Private Subnet

  #プライベートサブネット2
  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      # AZ-1c
      AvailabilityZone: ap-northeast-1c
      CidrBlock: !Ref PrivateSubnet2CIDR
      # Privateサブネットなのでfalseにする
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Private Subnet

  #----------------------
  # IGW作成
  #----------------------
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Ref EnvironmentName

  #----------------------
  # IGWをVPCにアタッチする
  #----------------------
  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

  #----------------------
  # ルートテーブル作成 (Publicサブネット用)
  #----------------------
  # ルートテーブル(Publicサブネット用)作成
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Routes

  # 上記のルートテーブルに、レコードを追加する
  DefaultPublicRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      # IGW用のレコードを追加する
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  # Publicサブネット1に、上記ルートテーブルを関連付ける
  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet1

  # Publicサブネット2に、パブリックサブネット用ルートテーブルを関連付ける
  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet2

  #----------------------
  # RDSサブネットグループ作成
  #----------------------
  RdsSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: "Subnet Group for RDS instances"
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
      DBSubnetGroupName: my-rds-subnet-group
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName} RDS Subnet Group"

  #----------------------
  # SSMロール
  #----------------------
  SsmSessionManagerIamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Service:
                - 'ec2.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      Path: '/'
      RoleName: 'SsmSessionManagerIamRole'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

  SessionManagerIamInstanceProfile:
    Type: 'AWS::IAM::InstanceProfile'
    Properties:
      Path: '/'
      Roles:
        - !Ref SsmSessionManagerIamRole

#----------------------
# エクスポート
#----------------------
Outputs:
  VPCId:
    Value: !Ref VPC
    Description: "VPC ID"
    Export:
      Name: !Sub "${EnvironmentName}-VPCId"

  PublicSubnet1Id:
    Value: !Ref PublicSubnet1
    Description: "PublicSubnet1"
    Export:
      Name: !Sub "${EnvironmentName}-PublicSubnet1Id"

  PublicSubnet2Id:
    Value: !Ref PublicSubnet2
    Description: "PublicSubnet2"
    Export:
      Name: !Sub "${EnvironmentName}-PublicSubnet2Id"

  RdsSubnetGroupId:
    Value: !Ref RdsSubnetGroup
    Description: "RDS Subnet Group Name"
    Export:
      Name: !Sub "${EnvironmentName}-RdsSubnetGroupId"

  SessionManagerIamInstanceProfile:
    Value: !Ref SessionManagerIamInstanceProfile
    Description: "SessionManagerIamInstanceProfile"
    Export:
      Name: !Sub "${EnvironmentName}-SessionManagerIamInstanceProfile"
AWSTemplateFormatVersion: '2010-09-09'
Description: 'hands-on1'
#----------------------------------------------------------------
# パラメーター
#----------------------------------------------------------------
Parameters:
  EnvironmentName:
    Description: hands-on1-template
    Type: String
#----------------------------------------------------------------
# リソース
#----------------------------------------------------------------
Resources:

#----------------------
# セキュリティグループ作成
#----------------------
  # EC2用のセキュリティグループ
  WebSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: Web-SG-1
      GroupDescription: "Description"
      VpcId: !ImportValue
        'Fn::Sub': '${EnvironmentName}-VPCId'
      #インバウンドルール
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          SourceSecurityGroupId: !Ref LbSecurityGroup
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          SourceSecurityGroupId: !Ref LbSecurityGroup
      #アウトバウンドルール
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: Web-sg1

  #RDS用セキュリティグループ
  RdsSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: RDS-SG-1
      GroupDescription: "Description"
      VpcId: !ImportValue
        'Fn::Sub': '${EnvironmentName}-VPCId'
      SecurityGroupIngress:
        # Replace with your specific IP range or remove the CidrIp property to allow access from anywhere
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          SourceSecurityGroupId: !Ref WebSecurityGroup
      Tags:
        - Key: Name
          Value: RDS-SG-1

  # ALB用のセキュリティグループ
  LbSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: LB-SG-1
      GroupDescription: "Security Group for ALB"
      VpcId: !ImportValue
        'Fn::Sub': '${EnvironmentName}-VPCId'
      SecurityGroupIngress:
        # HTTP
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
        # HTTPS
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: LB-SG-1

#----------------------
# エクスポート
#----------------------
Outputs:
  WebSecurityGroup:
    Value: !Ref WebSecurityGroup
    Description: "Web Security Group ID"
    Export:
      Name: !Sub "${EnvironmentName}-WebSecurityGroupId"

  RdsSecurityGroup:
    Value: !Ref RdsSecurityGroup
    Description: "Security Group ID for RDS access"
    Export:
      Name: !Sub "${EnvironmentName}-RdsSecurityGroupId"

  # ALB用セキュリティグループのエクスポート
  LbSecurityGroup:
    Description: "Security Group ID for ALB"
    Value: !Ref LbSecurityGroup
    Export:
      Name: !Sub "${EnvironmentName}-LbSecurityGroupId"
AWSTemplateFormatVersion: '2010-09-09'
Description: 'hands-on1'
#----------------------------------------------------------------
# パラメーター
#----------------------------------------------------------------
Parameters:
  # EC2インスタンスタイプ
  InstanceType:
    Type: String
    Default: t2.micro
    AllowedValues:
      - t2.micro
      - t2.nano
    Description: Select EC2 instance type.

  EnvironmentName:
    Description: hands-on1-template
    Type: String

#----------------------------------------------------------------
# リソース
#----------------------------------------------------------------
Resources:

#----------------------
# EC2作成
#----------------------
  PublicSubnet1EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      # AMIは、AmazonLinux2
      ImageId: "ami-0e25eba2025eea319"
      # インスタンスタイプは、t2.micro
      InstanceType: !Ref InstanceType
      # 自動割当パプリックIPを有効にする場合はNetworkInterfacesを記載する必要がある
      NetworkInterfaces:
        - SubnetId: !ImportValue
            'Fn::Sub': '${EnvironmentName}-PublicSubnet1Id'
          GroupSet:
            - !ImportValue
              'Fn::Sub': '${EnvironmentName}-WebSecurityGroupId'
          # 自動割当パプリックIPを有効
          AssociatePublicIpAddress: true
          DeviceIndex: 0
      # ストレージを設定
      BlockDeviceMappings:
        - DeviceName: "/dev/xvda"
          Ebs:
            VolumeType: "gp2"
            VolumeSize: 8
      # SSM経由でSSH接続出来るようにするIAMロールをアタッチする
      IamInstanceProfile: !ImportValue
        'Fn::Sub': '${EnvironmentName}-SessionManagerIamInstanceProfile'
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName} Public Subnet EC2Instance"
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          # Amazon Linux 2 に PHP、MySQL、Apache、WordPress をインストール
          amazon-linux-extras install -y php7.4
          yum install -y mysql httpd php-mbstring php-xml gd php-gd
          systemctl enable httpd.service
          systemctl start httpd.service
          wget https://ja.wordpress.org/latest-ja.tar.gz
          tar zxvf latest-ja.tar.gz
          cp -r wordpress/* /var/www/html/
          chown apache:apache -R /var/www/html/
  # EC2にEIPをつける
  ElasticIpForPublicSubnet1EC2Instance:
    Type: AWS::EC2::EIP
    Properties:
      InstanceId: !Ref PublicSubnet1EC2Instance

  # PublicSubnet2 に新しいEC2インスタンスを作成
  PublicSubnet2EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: "ami-0e25eba2025eea319"
      InstanceType: !Ref InstanceType
      NetworkInterfaces:
        - SubnetId: !ImportValue
            'Fn::Sub': '${EnvironmentName}-PublicSubnet2Id'
          GroupSet:
            - !ImportValue
              'Fn::Sub': '${EnvironmentName}-WebSecurityGroupId'
          AssociatePublicIpAddress: true
          DeviceIndex: 0
      BlockDeviceMappings:
        - DeviceName: "/dev/xvda"
          Ebs:
            VolumeType: "gp2"
            VolumeSize: 8
      IamInstanceProfile: !ImportValue
        'Fn::Sub': '${EnvironmentName}-SessionManagerIamInstanceProfile'
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName} Public Subnet 2 EC2Instance"
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          # Amazon Linux 2 に PHP、MySQL、Apache、WordPress をインストール
          amazon-linux-extras install -y php7.4
          yum install -y mysql httpd php-mbstring php-xml gd php-gd
          systemctl enable httpd.service
          systemctl start httpd.service
          wget https://ja.wordpress.org/latest-ja.tar.gz
          tar zxvf latest-ja.tar.gz
          cp -r wordpress/* /var/www/html/
          chown apache:apache -R /var/www/html/

  # PublicSubnet2 の新しいEC2インスタンスにEIPを割り当てる
  ElasticIpForPublicSubnet2EC2Instance:
    Type: AWS::EC2::EIP
    Properties:
      InstanceId: !Ref PublicSubnet2EC2Instance

#----------------------
# RDS作成
#----------------------
  MyRDSInstance:
    Type: AWS::RDS::DBInstance
    Properties:
      # データベースエンジン
      Engine: mysql
      # マスターユーザー名
      MasterUsername: wordpress
      # ストレージの割り当て (GB)
      AllocatedStorage: 20
      # マスターパスワード(実際の環境ではセキュアな方法で提供する)
      MasterUserPassword: "ChangeMe1234"
      # DBインスタンスクラス
      DBInstanceClass: db.t2.micro
      # データベース名
      DBName: wordpress
      # VPCの設定(Outputsから参照)
      DBSubnetGroupName: !ImportValue
        'Fn::Sub': '${EnvironmentName}-RdsSubnetGroupId'
      # パブリックアクセス無効
      PubliclyAccessible: false
      # セキュリティグループ
      VPCSecurityGroups:
        - !ImportValue
          'Fn::Sub': '${EnvironmentName}-RdsSecurityGroupId'
      # マルチAZ
      MultiAZ: true

#----------------------
# ALBのリスナーとターゲットグループ作成
#----------------------
  ALBTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 10
      HealthCheckPath: /readme.html
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 2
      UnhealthyThresholdCount: 2
      Port: 80
      Protocol: HTTP
      TargetType: instance
      VpcId: !ImportValue
        'Fn::Sub': '${EnvironmentName}-VPCId'
      # ターゲットの登録
      Targets:
        - Id: !Ref PublicSubnet1EC2Instance
        - Id: !Ref PublicSubnet2EC2Instance


  ALBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref ALBTargetGroup
      LoadBalancerArn: !Ref ApplicationLoadBalancer
      Port: 80
      Protocol: HTTP

  ApplicationLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: LB1
      Scheme: internet-facing
      Subnets:
        - !ImportValue
          'Fn::Sub': '${EnvironmentName}-PublicSubnet1Id'
        - !ImportValue
          'Fn::Sub': '${EnvironmentName}-PublicSubnet2Id'
      SecurityGroups:
        - !ImportValue
          'Fn::Sub': '${EnvironmentName}-LbSecurityGroupId'


#----------------------
# Outputs
#----------------------

ALBの振り分け動作確認

# プロファイル一覧を表示する
aws configure list-profiles

# SSM経由でPublicSubnet1EC2Instanceにログイン
aws ssm start-session --target {EC2インスタンスID} --profile {任意なプロファイル名}

# 下記表示になれば成功
sh-4.2$ 

sh-4.2$ sudo su -

sh-4.2$  cd /var/www/html/wp-admin
sh-4.2$  vi setup-config.php
【変更前】
<li><?php _e( 'Database name' ); ?></li>
【変更後】
<li>ターゲット2  <?php _e( 'Database name' ); ?></li>


// PublicSubnet2EC2Instanceも同じように

ALBのDNS名をURLに入力

ハンズオン3

構成

  1. VPC作成
  2. パブリックサブネット1作成
  3. パブリックサブネット2作成
  4. プライベートサブネット1作成
  5. プライベートサブネット2作成
  6. サブネットグループ作成(RDS用)
  7. インターネットゲートウェイ作成
  8. ルートテーブルを作成 (パブリックサブネット専用)
  9. ルートテーブルを作成(プライベートサブネット専用)
  10. セキュリティグループ作成(パブリックサブネットのEC2用)
  11. セキュリティグループ作成(RDS用)
  12. セキュリティグループ作成(ALB用)
  13. RDSインスタンスを作成
  14. IAMロールの作成
    下記をアタッチする
    ・SSM接続に必要なポリシーとEC2
    ・CloudWatchにログ送信を許可するポリシー
  15. 起動テンプレートを作成
  16. ALBターゲットグループ、ALBリスナー、ALBを作成
  17. AutoScalingGroupを作成
  18. SNSトッピクの作成
  19. CloudWatchの作成

CloudFormationテンプレート

AWSTemplateFormatVersion: '2010-09-09'
Description: 'hands-on1'
#----------------------------------------------------------------
# パラメーター
#----------------------------------------------------------------
Parameters:
  # プロジェクト名を入力する。各リソースのNameタグで参照する
  EnvironmentName:
    Description: hands-on1-template
    Type: String

  # VPCのCIDRレンジ
  VpcCIDR:
    Description: Please enter the IP range (CIDR notation) for this VPC
    Type: String
    Default: 10.0.0.0/21

  # パブリックサブネット1
  PublicSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone
    Type: String
    Default: 10.0.0.0/24

  # パブリックサブネット2
  PublicSubnet2CIDR:
    Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone
    Type: String
    Default: 10.0.1.0/24

  # プライベートサブネット1
  PrivateSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone
    Type: String
    Default: 10.0.2.0/24

  # プライベートサブネット2
  PrivateSubnet2CIDR:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone
    Type: String
    Default: 10.0.3.0/24

#----------------------------------------------------------------
# リソース
#----------------------------------------------------------------
Resources:
  #----------------------
  # VPC作成
  #----------------------
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      # 何の設定かわからないが、基本的にtrueにしても良さそう
      EnableDnsSupport: true
      EnableDnsHostnames: false
      Tags:
        - Key: Name
          Value: !Ref EnvironmentName
  #----------------------
  # Publicサブネット作成
  #----------------------
  # パブリックサブネット1
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      # AZ-1a
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref PublicSubnet1CIDR
      # サブネットで起動されたインスタンスが起動時にパブリック IP アドレスを設定するかどうか
      # Publicサブネットなのでtrueにする
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Subnet

  # パブリックサブネット2
  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      # AZ-1c
      AvailabilityZone: ap-northeast-1c
      CidrBlock: !Ref PublicSubnet2CIDR
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Subnet
  #----------------------
  # Privateサブネット作成
  #----------------------
  #プライベートサブネット1
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      # AZ-1a
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref PrivateSubnet1CIDR
      # Privateサブネットなのでfalseにする
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Private Subnet

  #プライベートサブネット2
  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      # AZ-1c
      AvailabilityZone: ap-northeast-1c
      CidrBlock: !Ref PrivateSubnet2CIDR
      # Privateサブネットなのでfalseにする
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Private Subnet

  #----------------------
  # IGW作成
  #----------------------
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Ref EnvironmentName

  #----------------------
  # IGWをVPCにアタッチする
  #----------------------
  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

  #----------------------
  # ルートテーブル作成 (Publicサブネット用)
  #----------------------
  # ルートテーブル(Publicサブネット用)作成
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Routes

  # 上記のルートテーブルに、レコードを追加する
  DefaultPublicRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      # IGW用のレコードを追加する
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  # Publicサブネット1に、上記ルートテーブルを関連付ける
  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet1

  # Publicサブネット2に、パブリックサブネット用ルートテーブルを関連付ける
  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet2

  #----------------------
  # RDSサブネットグループ作成
  #----------------------
  RdsSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: "Subnet Group for RDS instances"
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
      DBSubnetGroupName: my-rds-subnet-group
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName} RDS Subnet Group"

#----------------------
# エクスポート
#----------------------
Outputs:
  VPCId:
    Value: !Ref VPC
    Description: "VPC ID"
    Export:
      Name: !Sub "${EnvironmentName}-VPCId"

  PublicSubnet1Id:
    Value: !Ref PublicSubnet1
    Description: "PublicSubnet1"
    Export:
      Name: !Sub "${EnvironmentName}-PublicSubnet1Id"

  PublicSubnet2Id:
    Value: !Ref PublicSubnet2
    Description: "PublicSubnet2"
    Export:
      Name: !Sub "${EnvironmentName}-PublicSubnet2Id"

  RdsSubnetGroupId:
    Value: !Ref RdsSubnetGroup
    Description: "RDS Subnet Group Name"
    Export:
      Name: !Sub "${EnvironmentName}-RdsSubnetGroupId"
AWSTemplateFormatVersion: '2010-09-09'
Description: 'hands-on1'
#----------------------------------------------------------------
# パラメーター
#----------------------------------------------------------------
Parameters:
  EnvironmentName:
    Description: hands-on1-template
    Type: String
#----------------------------------------------------------------
# リソース
#----------------------------------------------------------------
Resources:

  #----------------------
  # セキュリティグループ作成
  #----------------------
  # EC2用のセキュリティグループ
  WebSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: Web-SG-1
      GroupDescription: "Description"
      VpcId: !ImportValue
        'Fn::Sub': '${EnvironmentName}-VPCId'
      #インバウンドルール
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          SourceSecurityGroupId: !Ref LbSecurityGroup
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          SourceSecurityGroupId: !Ref LbSecurityGroup
      #アウトバウンドルール
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: Web-sg1

  #RDS用セキュリティグループ
  RdsSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: RDS-SG-1
      GroupDescription: "Description"
      VpcId: !ImportValue
        'Fn::Sub': '${EnvironmentName}-VPCId'
      SecurityGroupIngress:
        # Replace with your specific IP range or remove the CidrIp property to allow access from anywhere
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          SourceSecurityGroupId: !Ref WebSecurityGroup
      Tags:
        - Key: Name
          Value: RDS-SG-1

  # ALB用のセキュリティグループ
  LbSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: LB-SG-1
      GroupDescription: "Security Group for ALB"
      VpcId: !ImportValue
        'Fn::Sub': '${EnvironmentName}-VPCId'
      SecurityGroupIngress:
        # HTTP
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
        # HTTPS
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: LB-SG-1

#----------------------
# エクスポート
#----------------------
Outputs:
  WebSecurityGroup:
    Value: !Ref WebSecurityGroup
    Description: "Web Security Group ID"
    Export:
      Name: !Sub "${EnvironmentName}-WebSecurityGroupId"

  RdsSecurityGroup:
    Value: !Ref RdsSecurityGroup
    Description: "Security Group ID for RDS access"
    Export:
      Name: !Sub "${EnvironmentName}-RdsSecurityGroupId"

  # ALB用セキュリティグループのエクスポート
  LbSecurityGroup:
    Description: "Security Group ID for ALB"
    Value: !Ref LbSecurityGroup
    Export:
      Name: !Sub "${EnvironmentName}-LbSecurityGroupId"
AWSTemplateFormatVersion: '2010-09-09'
Description: 'hands-on1'
#----------------------------------------------------------------
# パラメーター
#----------------------------------------------------------------
Parameters:
  # EC2インスタンスタイプ
  InstanceType:
    Type: String
    Default: t2.micro
    AllowedValues:
      - t2.micro
      - t2.nano
    Description: Select EC2 instance type.

  EnvironmentName:
    Description: hands-on1-template
    Type: String

#----------------------------------------------------------------
# リソース
#----------------------------------------------------------------
Resources:
  #----------------------
  # RDS作成
  #----------------------
  MyRDSInstance:
    Type: AWS::RDS::DBInstance
    Properties:
      # データベースエンジン
      Engine: mysql
      # マスターユーザー名
      MasterUsername: wordpress
      # ストレージの割り当て (GB)
      AllocatedStorage: 20
      # マスターパスワード(実際の環境ではセキュアな方法で提供する)
      MasterUserPassword: "ChangeMe1234"
      # DBインスタンスクラス
      DBInstanceClass: db.t2.micro
      # データベース名
      DBName: wordpress
      # VPCの設定(Outputsから参照)
      DBSubnetGroupName: !ImportValue
        'Fn::Sub': '${EnvironmentName}-RdsSubnetGroupId'
      # パブリックアクセス無効
      PubliclyAccessible: false
      # セキュリティグループ
      VPCSecurityGroups:
        - !ImportValue
          'Fn::Sub': '${EnvironmentName}-RdsSecurityGroupId'
      # マルチAZ
      MultiAZ: false

  #----------------------
  # IAMロール クロススタック参照ができない為、ここでIAMロールを作成する
  # ポリシー
  # ・SSMログインの為のポリシー
  # ・EC2のログをcloudwatchに送信する為のポリシー
  #----------------------
  SsmSessionManagerIamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Service:
                - 'ec2.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      Path: '/'
      RoleName: 'SsmSessionManagerIamRole'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
        - arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy

  SessionManagerIamInstanceProfile:
    Type: 'AWS::IAM::InstanceProfile'
    Properties:
      Path: '/'
      Roles:
        - !Ref SsmSessionManagerIamRole
  #----------------------
  # 起動テンプレート作成(EC2インスタンスの設計書を作成)
  #----------------------
  LaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: MyLaunchTemplate
      LaunchTemplateData:
        # AmazonLinux2
        ImageId: "ami-0e25eba2025eea319"
        InstanceType: !Ref InstanceType
        NetworkInterfaces:
          # 自動割当パプリックIPを有効
          - AssociatePublicIpAddress: true
            DeviceIndex: 0
            # セキュリティグループはこちらに指定
            Groups:
              - !ImportValue
                'Fn::Sub': '${EnvironmentName}-WebSecurityGroupId'
        # ストレージを設定
        BlockDeviceMappings:
          - DeviceName: "/dev/xvda"
            Ebs:
              VolumeType: "gp2"
              VolumeSize: 8
        # SSM経由でSSH接続出来るようにするIAMロールをアタッチする
        IamInstanceProfile:
          Arn: !GetAtt SessionManagerIamInstanceProfile.Arn
        UserData:
          Fn::Base64: !Sub |
            #!/bin/bash
            # AWS CLIをインストール
            yum install -y aws-cli
            # Amazon Linux 2 に PHP、MySQL、Apache、WordPress をインストール
            amazon-linux-extras install -y php7.4
            yum install -y mysql httpd php-mbstring php-xml gd php-gd
            systemctl enable httpd.service
            systemctl start httpd.service
            wget https://ja.wordpress.org/latest-ja.tar.gz
            tar zxvf latest-ja.tar.gz
            cp -r wordpress/* /var/www/html/
            chown apache:apache -R /var/www/html/
            #######################   cloudwatchでログ監視   ###########################
            # awslogs のインストール
            yum -y install awslogs
            # awslogs の設定
            sed -i 's/region = us-east-1/region = ap-northeast-1/' /etc/awslogs/awscli.conf
            cat <<EOT >> /etc/awslogs/awslogs.conf
            [HttpAccessLog]
            file = /var/log/httpd/access_log
            log_group_name = HttpAccessLog
            log_stream_name = {instance_id}
            datetime_format = %b %d %H:%M:%S
            [HttpErrorLog]
            file = /var/log/httpd/error_log
            log_group_name = HttpErrorLog
            log_stream_name = {instance_id}
            datetime_format = %b %d %H:%M:%S
            EOT
            systemctl enable awslogsd
            systemctl start awslogsd

  #----------------------
  # ALBターゲットグループ作成
  #----------------------
  ALBTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      VpcId: !ImportValue
        'Fn::Sub': '${EnvironmentName}-VPCId'
      HealthCheckPath: /readme.html
      HealthCheckProtocol: HTTP
      HealthCheckPort: '80'
      #ヘルスチェック間隔
      HealthCheckIntervalSeconds: 10
      #ヘルスチェックのタイムアウト時間
      HealthCheckTimeoutSeconds: 5
      #ヘルシーと見なすための連続成功回数
      HealthyThresholdCount: 2
      #アンヘルシーと見なすための連続失敗回数
      UnhealthyThresholdCount: 2
      Port: 80
      Protocol: HTTP
      TargetType: instance


  #----------------------
  # ALBリスナー作成
  #----------------------
  ALBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref ALBTargetGroup
      LoadBalancerArn: !Ref ApplicationLoadBalancer
      Port: 80
      Protocol: HTTP

  #----------------------
  # ALB作成
  #----------------------
  ApplicationLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: LB1
      Scheme: internet-facing
      Subnets:
        - !ImportValue
          'Fn::Sub': '${EnvironmentName}-PublicSubnet1Id'
        - !ImportValue
          'Fn::Sub': '${EnvironmentName}-PublicSubnet2Id'
      SecurityGroups:
        - !ImportValue
          'Fn::Sub': '${EnvironmentName}-LbSecurityGroupId'

  #----------------------
  # AutoScalingGroup作成
  #----------------------
  AutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      #起動テンプレート
      LaunchTemplate:
        LaunchTemplateId: !Ref LaunchTemplate
        Version: !GetAtt LaunchTemplate.LatestVersionNumber
      # AutoScalingグループにALBを紐付ける
      TargetGroupARNs:
        - !Ref ALBTargetGroup
      # 任意な名前
      AutoScalingGroupName: Test-AutoScaling1
      # 希望する容量
      DesiredCapacity: 2
      # 最小キャパシティ
      MinSize: 2
      # 最大キャパシティ
      MaxSize: 4
      VPCZoneIdentifier:
        - !ImportValue
          'Fn::Sub': '${EnvironmentName}-PublicSubnet1Id'
        - !ImportValue
          'Fn::Sub': '${EnvironmentName}-PublicSubnet2Id'
      # ヘルスチェックタイプ ELBの場合はELB
      HealthCheckType: ELB
      # ヘルスチェック感覚
      HealthCheckGracePeriod: 300

  #----------------------
  # SNSトピックの作成
  #----------------------
  NotificationTopic:
    Type: AWS::SNS::Topic
    Properties:
      Subscription:
        - Endpoint: dev.takakura0318@gmail.com
          Protocol: email
      TopicName: "NotificationTopic"

  #----------------------
  # CloudWatchアラーム(CPUが70%を超えた場合)
  #----------------------
  HighCpuAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmDescription: "Alarm when CPU exceeds 70%"
      # メトリクスの選択
      Namespace: AWS/EC2
      MetricName: CPUUtilization
      # 統計
      Statistic: Average
      # 集計期間
      Period: 300
      # 静的
      EvaluationPeriods: 1
      # 条件値
      Threshold: 70
      # 演算子:より大きい
      ComparisonOperator: GreaterThanThreshold
      # よくわからないけどざっくり名前みたいもの
      Dimensions:
        - Name: AutoScalingGroupName
          Value: !Ref AutoScalingGroup
      AlarmActions:
        - !Ref NotificationTopic
        - !Ref HighCpuScalingPolicy
      # 欠落データは見つかりませんで処理
      TreatMissingData: missing

  #----------------------
  # CloudWatchアラーム(CPUが30%以下の場合)
  #----------------------
  LowCpuAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmDescription: "Alarm when CPU falls below 30%"
      MetricName: CPUUtilization
      Namespace: AWS/EC2
      Statistic: Average
      Period: 300
      EvaluationPeriods: 1
      Threshold: 30
      ComparisonOperator: LessThanThreshold
      Dimensions:
        - Name: AutoScalingGroupName
          Value: !Ref AutoScalingGroup
      AlarmActions:
        - !Ref NotificationTopic
        - !Ref LowCpuScalingPolicy
      TreatMissingData: notBreaching

  #----------------------
  # AutoScalingグループのスケーリングポリシー(CPU使用率が高い場合)
  #----------------------
  HighCpuScalingPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AutoScalingGroupName: !Ref AutoScalingGroup
      # ポリシータイプ
      PolicyType: SimpleScaling
      # 1台追加
      ScalingAdjustment: 1
      AdjustmentType: ChangeInCapacity
      # 別のスケーリングアクティビティを許可するまでの秒数
      Cooldown: 30

  #----------------------
  # AutoScalingグループのスケーリングポリシー(CPU使用率が低い場合)
  #----------------------
  LowCpuScalingPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AutoScalingGroupName: !Ref AutoScalingGroup
      PolicyType: SimpleScaling
      ScalingAdjustment: -1
      AdjustmentType: ChangeInCapacity
      Cooldown: 30

#----------------------
# Outputs
#----------------------

動作確認

ALBの動作確認

# プロファイル一覧を表示する
aws configure list-profiles
# SSM経由でPublicSubnet1EC2Instanceにログイン
aws ssm start-session --target {EC2インスタンスID} --profile {任意なプロファイル名}

# 下記表示になれば成功
sh-4.2$ 
sh-4.2$ sudo su -
root$ cd /var/www/html/
root$  vi readme.html
【変更前】
<h2>First Things First</h2>

【変更後】
<h2>First Things First ターゲット1</h2>
----------------------------------------------
# ターゲット2であるEC2インスタンスも同様
sh-4.2$  vi readme.html
【変更前】
<h2>First Things First</h2>

【変更後】
<h2>First Things First ターゲット2</h2>

「ALBのDNS名/readme.html」にWebアクセス


Autoスケールの動作確認

現在の設定を確認

EC2インスタンスを1台終了にする

EC2を手動で停止したがEC2の合計数が少なくなったが、Autoスケールにより、EC2の合計数が維持されている


EC2のCPUが70%より大きい場合に、EC2が1つ追加されることを確認する

# プロファイル一覧を表示する
aws configure list-profiles
# SSM経由でPublicSubnet1EC2Instanceにログイン
aws ssm start-session --target {EC2インスタンスID} --profile {任意なプロファイル名}

sh-4.2$ 
sh-4.2$ sudo su -

//負荷をかけるコマンド
root$ yes >> /dev/null &
root$ yes >> /dev/null &
root$ yes >> /dev/null &
root$ yes >> /dev/null &

// CPUが90%超えていることを確認
root$ top
top - 00:53:47 up 20 min,  0 users,  load average: 4.16, 3.51, 1.92
Tasks: 111 total,   5 running,  65 sleeping,   0 stopped,   0 zombie
%Cpu(s): 99.3 us,

EC2が2台増えている

CPUが30%以下の動き

CPU30%以下になったので、EC2インスタンスが通常時の2台に戻っている

ログの確認
LaunchTemplateのUserDataで設定したAWSlogsの内容がログ出力されている

ハンズオン4

構成

  1. VPCを作成
  2. パブリックサブネット1作成
  3. パブリックサブネット2作成
  4. プライベートサブネット1作成
  5. プライベートサブネット2作成
  6. サブネットグループ作成(RDS用)
  7. インターネットゲートウェイ作成
  8. ルートテーブルを作成 (パブリックサブネット専用)
  9. ルートテーブルを作成(プライベートサブネット専用)
  10. セキュリティグループ作成(パブリックサブネットのEC2用)
  11. セキュリティグループ作成(RDS用)
  12. セキュリティグループ作成(ALB用)
  13. RDSインスタンスを作成
  14. IAMロールの作成
    下記をアタッチする
    ・SSM接続に必要なポリシーとEC2
    ・CloudWatchにログ送信を許可するポリシー
  15. 起動テンプレートを作成
  16. ALBターゲットグループ、ALBリスナー、ALBを作成
  17. AutoScalingGroupを作成
  18. SNSトッピクの作成
  19. CloudWatchの作成
    ・CPU使用率70%より大きいを監視するCloudWatchアラームを作成
    ・CPU使用率が30%以下を監視するCloudWatchアラームを作成
  20. AutoScalingGroupのスケーリングポリシーを設定する
    ・EC2を1台増やすスケーリングポリシーを追加
    ・EC2を1台減らすスケーリングポリシーを追加
  21. Route53でホストゾーンを作成する
  22. S3バケットを作成する
    Route53のフェイルオーバルーティング機能を使いたいので、バケット名は、ドメインと一致すること
  23. 21で作成したホストゾーンにレコードを追加する
    ・お名前.comで取得したドメインとALBのエンドポイントを紐付ける為のAレコードを追加(プライマリ)
    ・お名前.comで取得したドメインとS3のsorryページを紐付ける為のAレコードを追加(セカンダリ)

CloudFormationテンプレート

AWSTemplateFormatVersion: '2010-09-09'
Description: 'hands-on1'
#----------------------------------------------------------------
# パラメーター
#----------------------------------------------------------------
Parameters:
  # プロジェクト名を入力する。各リソースのNameタグで参照する
  EnvironmentName:
    Description: hands-on1-template
    Type: String

  # VPCのCIDRレンジ
  VpcCIDR:
    Description: Please enter the IP range (CIDR notation) for this VPC
    Type: String
    Default: 10.0.0.0/21

  # パブリックサブネット1
  PublicSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone
    Type: String
    Default: 10.0.0.0/24

  # パブリックサブネット2
  PublicSubnet2CIDR:
    Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone
    Type: String
    Default: 10.0.1.0/24

  # プライベートサブネット1
  PrivateSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone
    Type: String
    Default: 10.0.2.0/24

  # プライベートサブネット2
  PrivateSubnet2CIDR:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone
    Type: String
    Default: 10.0.3.0/24

#----------------------------------------------------------------
# リソース
#----------------------------------------------------------------
Resources:
  #----------------------
  # VPC作成
  #----------------------
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      # 何の設定かわからないが、基本的にtrueにしても良さそう
      EnableDnsSupport: true
      EnableDnsHostnames: false
      Tags:
        - Key: Name
          Value: !Ref EnvironmentName
  #----------------------
  # Publicサブネット作成
  #----------------------
  # パブリックサブネット1
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      # AZ-1a
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref PublicSubnet1CIDR
      # サブネットで起動されたインスタンスが起動時にパブリック IP アドレスを設定するかどうか
      # Publicサブネットなのでtrueにする
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Subnet

  # パブリックサブネット2
  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      # AZ-1c
      AvailabilityZone: ap-northeast-1c
      CidrBlock: !Ref PublicSubnet2CIDR
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Subnet
  #----------------------
  # Privateサブネット作成
  #----------------------
  #プライベートサブネット1
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      # AZ-1a
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref PrivateSubnet1CIDR
      # Privateサブネットなのでfalseにする
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Private Subnet

  #プライベートサブネット2
  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      # AZ-1c
      AvailabilityZone: ap-northeast-1c
      CidrBlock: !Ref PrivateSubnet2CIDR
      # Privateサブネットなのでfalseにする
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Private Subnet

  #----------------------
  # IGW作成
  #----------------------
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Ref EnvironmentName

  #----------------------
  # IGWをVPCにアタッチする
  #----------------------
  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

  #----------------------
  # ルートテーブル作成 (Publicサブネット用)
  #----------------------
  # ルートテーブル(Publicサブネット用)作成
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Routes

  # 上記のルートテーブルに、レコードを追加する
  DefaultPublicRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      # IGW用のレコードを追加する
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  # Publicサブネット1に、上記ルートテーブルを関連付ける
  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet1

  # Publicサブネット2に、パブリックサブネット用ルートテーブルを関連付ける
  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet2

  #----------------------
  # RDSサブネットグループ作成
  #----------------------
  RdsSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: "Subnet Group for RDS instances"
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
      DBSubnetGroupName: my-rds-subnet-group
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName} RDS Subnet Group"

  #----------------------
  # Route 53ホストゾーン作成
  #----------------------
  HostedZone:
    Type: AWS::Route53::HostedZone
    Properties:
      Name: aws-dev-lab-demo.shop

#----------------------
# エクスポート
#----------------------
Outputs:
  VPCId:
    Value: !Ref VPC
    Description: "VPC ID"
    Export:
      Name: !Sub "${EnvironmentName}-VPCId"

  PublicSubnet1Id:
    Value: !Ref PublicSubnet1
    Description: "PublicSubnet1"
    Export:
      Name: !Sub "${EnvironmentName}-PublicSubnet1Id"

  PublicSubnet2Id:
    Value: !Ref PublicSubnet2
    Description: "PublicSubnet2"
    Export:
      Name: !Sub "${EnvironmentName}-PublicSubnet2Id"

  RdsSubnetGroupId:
    Value: !Ref RdsSubnetGroup
    Description: "RDS Subnet Group Name"
    Export:
      Name: !Sub "${EnvironmentName}-RdsSubnetGroupId"

  HostedZoneId:
    Description: "The HostedZone ID"
    Value: !Ref HostedZone
    Export:
      Name: !Sub "${EnvironmentName}-HostedZoneId"
AWSTemplateFormatVersion: '2010-09-09'
Description: 'hands-on1'
#----------------------------------------------------------------
# パラメーター
#----------------------------------------------------------------
Parameters:
  EnvironmentName:
    Description: hands-on1-template
    Type: String
#----------------------------------------------------------------
# リソース
#----------------------------------------------------------------
Resources:

  #----------------------
  # セキュリティグループ作成
  #----------------------
  # EC2用のセキュリティグループ
  WebSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: Web-SG-1
      GroupDescription: "Description"
      VpcId: !ImportValue
        'Fn::Sub': '${EnvironmentName}-VPCId'
      #インバウンドルール
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          SourceSecurityGroupId: !Ref LbSecurityGroup
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          SourceSecurityGroupId: !Ref LbSecurityGroup
      #アウトバウンドルール
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: Web-sg1

  #RDS用セキュリティグループ
  RdsSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: RDS-SG-1
      GroupDescription: "Description"
      VpcId: !ImportValue
        'Fn::Sub': '${EnvironmentName}-VPCId'
      SecurityGroupIngress:
        # Replace with your specific IP range or remove the CidrIp property to allow access from anywhere
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          SourceSecurityGroupId: !Ref WebSecurityGroup
      Tags:
        - Key: Name
          Value: RDS-SG-1

  # ALB用のセキュリティグループ
  LbSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: LB-SG-1
      GroupDescription: "Security Group for ALB"
      VpcId: !ImportValue
        'Fn::Sub': '${EnvironmentName}-VPCId'
      SecurityGroupIngress:
        # HTTP
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
        # HTTPS
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: LB-SG-1

#----------------------
# エクスポート
#----------------------
Outputs:
  WebSecurityGroup:
    Value: !Ref WebSecurityGroup
    Description: "Web Security Group ID"
    Export:
      Name: !Sub "${EnvironmentName}-WebSecurityGroupId"

  RdsSecurityGroup:
    Value: !Ref RdsSecurityGroup
    Description: "Security Group ID for RDS access"
    Export:
      Name: !Sub "${EnvironmentName}-RdsSecurityGroupId"

  # ALB用セキュリティグループのエクスポート
  LbSecurityGroup:
    Description: "Security Group ID for ALB"
    Value: !Ref LbSecurityGroup
    Export:
      Name: !Sub "${EnvironmentName}-LbSecurityGroupId"
AWSTemplateFormatVersion: '2010-09-09'
Description: 'hands-on1'
#----------------------------------------------------------------
# パラメーター
#----------------------------------------------------------------
Parameters:
  # EC2インスタンスタイプ
  InstanceType:
    Type: String
    Default: t2.micro
    AllowedValues:
      - t2.micro
      - t2.nano
    Description: Select EC2 instance type.

  EnvironmentName:
    Description: hands-on1-template
    Type: String

#----------------------------------------------------------------
# リソース
#----------------------------------------------------------------
Resources:
  #----------------------
  # RDS作成
  #----------------------
  MyRDSInstance:
    Type: AWS::RDS::DBInstance
    Properties:
      # データベースエンジン
      Engine: mysql
      # マスターユーザー名
      MasterUsername: wordpress
      # ストレージの割り当て (GB)
      AllocatedStorage: 20
      # マスターパスワード(実際の環境ではセキュアな方法で提供する)
      MasterUserPassword: "ChangeMe1234"
      # DBインスタンスクラス
      DBInstanceClass: db.t2.micro
      # データベース名
      DBName: wordpress
      # VPCの設定(Outputsから参照)
      DBSubnetGroupName: !ImportValue
        'Fn::Sub': '${EnvironmentName}-RdsSubnetGroupId'
      # パブリックアクセス無効
      PubliclyAccessible: false
      # セキュリティグループ
      VPCSecurityGroups:
        - !ImportValue
          'Fn::Sub': '${EnvironmentName}-RdsSecurityGroupId'
      # マルチAZ
      MultiAZ: false

  #----------------------
  # IAMロール クロススタック参照ができない為、ここでIAMロールを作成する
  # ポリシー
  # ・SSMログインの為のポリシー
  # ・EC2のログをcloudwatchに送信する為のポリシー
  #----------------------
  SsmSessionManagerIamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Service:
                - 'ec2.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      Path: '/'
      RoleName: 'SsmSessionManagerIamRole'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
        - arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy

  SessionManagerIamInstanceProfile:
    Type: 'AWS::IAM::InstanceProfile'
    Properties:
      Path: '/'
      Roles:
        - !Ref SsmSessionManagerIamRole
  #----------------------
  # 起動テンプレート作成(EC2インスタンスの設計書を作成)
  #----------------------
  LaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: MyLaunchTemplate
      LaunchTemplateData:
        # AmazonLinux2
        ImageId: "ami-0e25eba2025eea319"
        InstanceType: !Ref InstanceType
        NetworkInterfaces:
          # 自動割当パプリックIPを有効
          - AssociatePublicIpAddress: true
            DeviceIndex: 0
            # セキュリティグループはこちらに指定
            Groups:
              - !ImportValue
                'Fn::Sub': '${EnvironmentName}-WebSecurityGroupId'
        # ストレージを設定
        BlockDeviceMappings:
          - DeviceName: "/dev/xvda"
            Ebs:
              VolumeType: "gp2"
              VolumeSize: 8
        # SSM経由でSSH接続出来るようにするIAMロールをアタッチする
        IamInstanceProfile:
          Arn: !GetAtt SessionManagerIamInstanceProfile.Arn
        UserData:
          Fn::Base64: !Sub |
            #!/bin/bash
            # AWS CLIをインストール
            yum install -y aws-cli
            # Amazon Linux 2 に PHP、MySQL、Apache、WordPress をインストール
            amazon-linux-extras install -y php7.4
            yum install -y mysql httpd php-mbstring php-xml gd php-gd
            systemctl enable httpd.service
            systemctl start httpd.service
            wget https://ja.wordpress.org/latest-ja.tar.gz
            tar zxvf latest-ja.tar.gz
            cp -r wordpress/* /var/www/html/
            chown apache:apache -R /var/www/html/
            #######################   cloudwatchでログ監視   ###########################
            # awslogs のインストール
            yum -y install awslogs
            # awslogs の設定
            sed -i 's/region = us-east-1/region = ap-northeast-1/' /etc/awslogs/awscli.conf
            cat <<EOT >> /etc/awslogs/awslogs.conf
            [HttpAccessLog]
            file = /var/log/httpd/access_log
            log_group_name = HttpAccessLog
            log_stream_name = {instance_id}
            datetime_format = %b %d %H:%M:%S
            [HttpErrorLog]
            file = /var/log/httpd/error_log
            log_group_name = HttpErrorLog
            log_stream_name = {instance_id}
            datetime_format = %b %d %H:%M:%S
            EOT
            systemctl enable awslogsd
            systemctl start awslogsd

  #----------------------
  # ALBターゲットグループ作成
  #----------------------
  ALBTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      VpcId: !ImportValue
        'Fn::Sub': '${EnvironmentName}-VPCId'
      HealthCheckPath: /readme.html
      HealthCheckProtocol: HTTP
      HealthCheckPort: '80'
      #ヘルスチェック間隔
      HealthCheckIntervalSeconds: 10
      #ヘルスチェックのタイムアウト時間
      HealthCheckTimeoutSeconds: 5
      #ヘルシーと見なすための連続成功回数
      HealthyThresholdCount: 2
      #アンヘルシーと見なすための連続失敗回数
      UnhealthyThresholdCount: 2
      Port: 80
      Protocol: HTTP
      TargetType: instance


  #----------------------
  # ALBリスナー作成
  #----------------------
  ALBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref ALBTargetGroup
      LoadBalancerArn: !Ref ApplicationLoadBalancer
      Port: 80
      Protocol: HTTP

  #----------------------
  # ALB作成
  #----------------------
  ApplicationLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: LB1
      Scheme: internet-facing
      Subnets:
        - !ImportValue
          'Fn::Sub': '${EnvironmentName}-PublicSubnet1Id'
        - !ImportValue
          'Fn::Sub': '${EnvironmentName}-PublicSubnet2Id'
      SecurityGroups:
        - !ImportValue
          'Fn::Sub': '${EnvironmentName}-LbSecurityGroupId'

  #----------------------
  # AutoScalingGroup作成
  #----------------------
  AutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      #起動テンプレート
      LaunchTemplate:
        LaunchTemplateId: !Ref LaunchTemplate
        Version: !GetAtt LaunchTemplate.LatestVersionNumber
      # AutoScalingグループにALBを紐付ける
      TargetGroupARNs:
        - !Ref ALBTargetGroup
      # 任意な名前
      AutoScalingGroupName: Test-AutoScaling1
      # 希望する容量
      DesiredCapacity: 2
      # 最小キャパシティ
      MinSize: 2
      # 最大キャパシティ
      MaxSize: 4
      VPCZoneIdentifier:
        - !ImportValue
          'Fn::Sub': '${EnvironmentName}-PublicSubnet1Id'
        - !ImportValue
          'Fn::Sub': '${EnvironmentName}-PublicSubnet2Id'
      # ヘルスチェックタイプ ELBの場合はELB
      HealthCheckType: ELB
      # ヘルスチェック感覚
      HealthCheckGracePeriod: 300

  #----------------------
  # SNSトピックの作成
  #----------------------
  NotificationTopic:
    Type: AWS::SNS::Topic
    Properties:
      Subscription:
        - Endpoint: dev.takakura0318@gmail.com
          Protocol: email
      TopicName: "NotificationTopic"

  #----------------------
  # CloudWatchアラーム(CPUが70%を超えた場合)
  #----------------------
  HighCpuAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmDescription: "Alarm when CPU exceeds 70%"
      # メトリクスの選択
      Namespace: AWS/EC2
      MetricName: CPUUtilization
      # 統計
      Statistic: Average
      # 集計期間
      Period: 300
      # 静的
      EvaluationPeriods: 1
      # 条件値
      Threshold: 70
      # 演算子:より大きい
      ComparisonOperator: GreaterThanThreshold
      # よくわからないけどざっくり名前みたいもの
      Dimensions:
        - Name: AutoScalingGroupName
          Value: !Ref AutoScalingGroup
      AlarmActions:
        - !Ref NotificationTopic
        - !Ref HighCpuScalingPolicy
      # 欠落データは見つかりませんで処理
      TreatMissingData: missing

  #----------------------
  # CloudWatchアラーム(CPUが30%以下の場合)
  #----------------------
  LowCpuAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmDescription: "Alarm when CPU falls below 30%"
      MetricName: CPUUtilization
      Namespace: AWS/EC2
      Statistic: Average
      Period: 300
      EvaluationPeriods: 1
      Threshold: 30
      ComparisonOperator: LessThanThreshold
      Dimensions:
        - Name: AutoScalingGroupName
          Value: !Ref AutoScalingGroup
      AlarmActions:
        - !Ref NotificationTopic
        - !Ref LowCpuScalingPolicy
      TreatMissingData: notBreaching

  #----------------------
  # AutoScalingグループのスケーリングポリシー
  # CPU使用率が70%より大きい場合はEC2を1台追加する
  #----------------------
  HighCpuScalingPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AutoScalingGroupName: !Ref AutoScalingGroup
      # ポリシータイプ
      PolicyType: SimpleScaling
      # 1台追加
      ScalingAdjustment: 1
      AdjustmentType: ChangeInCapacity
      # 別のスケーリングアクティビティを許可するまでの秒数
      Cooldown: 30

  #----------------------
  # AutoScalingグループのスケーリングポリシー
  # CPU使用率が30%以下の場合はEC2を1台削除する
  #----------------------
  LowCpuScalingPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AutoScalingGroupName: !Ref AutoScalingGroup
      PolicyType: SimpleScaling
      ScalingAdjustment: -1
      AdjustmentType: ChangeInCapacity
      Cooldown: 30

  #----------------------
  # S3バケットの作成
  #----------------------
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      # Route53にて、ALBと連携したフェイルオーバールーティング機能を使う為、バケット名はドメイン名を指定する必要がある
      BucketName: blog.aws-dev-lab-demo.shop
      PublicAccessBlockConfiguration:
        BlockPublicAcls: false
        IgnorePublicAcls: false
        BlockPublicPolicy: false
        RestrictPublicBuckets: false
      WebsiteConfiguration:
        IndexDocument: index.html
        ErrorDocument: error.html

  #バケットに権限付与
  BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref S3Bucket
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: 'PublicReadGetObject'
            Effect: 'Allow'
            Principal: '*'
            Action: 's3:GetObject'
            Resource: !Sub 'arn:aws:s3:::${S3Bucket}/*'

  #----------------------
  # 作成したホストゾーンにレコードを追加する
  #----------------------
  # フェイルオーバーのためのRoute 53レコードセットグループ
  MyRecordSetGroup:
    Type: AWS::Route53::RecordSetGroup
    Properties:
      HostedZoneId: !ImportValue
        'Fn::Sub': '${EnvironmentName}-HostedZoneId'
      RecordSets:
        # ALBに対するプライマリレコード
        - Name: "blog.aws-dev-lab-demo.shop"
          Type: A
          SetIdentifier: "Primary-ALB"
          Failover: "PRIMARY"
          HealthCheckId: !Ref ALBHealthCheck
          AliasTarget:
            HostedZoneId: !GetAtt ApplicationLoadBalancer.CanonicalHostedZoneID
            DNSName: !GetAtt ApplicationLoadBalancer.DNSName
            EvaluateTargetHealth: true
        # S3に対するセカンダリレコード
        - Name: "blog.aws-dev-lab-demo.shop"
          Type: A
          SetIdentifier: "Secondary-S3"
          Failover: "SECONDARY"
          AliasTarget:
            HostedZoneId: "Z2M4EHUR26P7ZW"
            # ここの値を仮にマネコンで設定する場合はプルダウン値にサジェストがある。そのサジェストにいずれかに合致する値を指定しないフェイルオーバされないので注意
            DNSName: !Sub "http://${S3Bucket}.s3-website-${AWS::Region}.amazonaws.com"
            EvaluateTargetHealth: false

  ALBHealthCheck:
    Type: AWS::Route53::HealthCheck
    Properties:
      HealthCheckConfig:
        # ALBのリッスンポート
        Port: 80
        Type: HTTP
        # ALBのヘルスチェックパス
        ResourcePath: "/readme.html"
        FullyQualifiedDomainName: !GetAtt ApplicationLoadBalancer.DNSName
        # ヘルスチェックのリクエスト間隔(秒)
        RequestInterval: 30
        # ヘルスチェックが失敗と判断されるまでの連続回数
        FailureThreshold: 3
#----------------------
# Outputs
#----------------------

動作確認

お名前.comの該当ドメインのネームサーバーを更新する。
更新値は、Route53のホストゾーン作成時に、自動作成されるNSレコードを参照する

S3バケットにsorryページ用の静的ファイルをアップロードする
・index.html
・image.png

EC2>AutoScalingグループの下記項目を変更する
・希望するキャパシティ
【変更前】
2
【変更後】
0

・最初キャパシティ
【変更前】
2
【変更後】
0

テストに入る前に事前に、http://blog.aws-dev-lab-demo.shop/readme.htmlにアクセスして、ページが正常に表示されることを確認すること

EC2インスタンスを終了させる

Route53>ヘルスチェックにて、Route53からプライマリ(ALB)に対するヘルスチェック結果がアンヘルシーであることを確認する

sorryページが表示されたら、Route53のフェイルオーバルーティングの動作確認は完了

ハンズオン5

構成

24.AWS Certificate Managerで証明書を取得する
25.ALBのリスナーを修正する
・HTTP通信の場合は、HTTPSにリダイレクトする

CloudFormationテンプレート

AWSTemplateFormatVersion: '2010-09-09'
Description: 'hands-on1'
#----------------------------------------------------------------
# パラメーター
#----------------------------------------------------------------
Parameters:
  # プロジェクト名を入力する。各リソースのNameタグで参照する
  EnvironmentName:
    Description: hands-on1-template
    Type: String

  # VPCのCIDRレンジ
  VpcCIDR:
    Description: Please enter the IP range (CIDR notation) for this VPC
    Type: String
    Default: 10.0.0.0/21

  # パブリックサブネット1
  PublicSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone
    Type: String
    Default: 10.0.0.0/24

  # パブリックサブネット2
  PublicSubnet2CIDR:
    Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone
    Type: String
    Default: 10.0.1.0/24

  # プライベートサブネット1
  PrivateSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone
    Type: String
    Default: 10.0.2.0/24

  # プライベートサブネット2
  PrivateSubnet2CIDR:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone
    Type: String
    Default: 10.0.3.0/24

#----------------------------------------------------------------
# リソース
#----------------------------------------------------------------
Resources:
  #----------------------
  # VPC作成
  #----------------------
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      # 何の設定かわからないが、基本的にtrueにしても良さそう
      EnableDnsSupport: true
      EnableDnsHostnames: false
      Tags:
        - Key: Name
          Value: !Ref EnvironmentName
  #----------------------
  # Publicサブネット作成
  #----------------------
  # パブリックサブネット1
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      # AZ-1a
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref PublicSubnet1CIDR
      # サブネットで起動されたインスタンスが起動時にパブリック IP アドレスを設定するかどうか
      # Publicサブネットなのでtrueにする
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Subnet

  # パブリックサブネット2
  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      # AZ-1c
      AvailabilityZone: ap-northeast-1c
      CidrBlock: !Ref PublicSubnet2CIDR
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Subnet
  #----------------------
  # Privateサブネット作成
  #----------------------
  #プライベートサブネット1
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      # AZ-1a
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref PrivateSubnet1CIDR
      # Privateサブネットなのでfalseにする
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Private Subnet

  #プライベートサブネット2
  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      # AZ-1c
      AvailabilityZone: ap-northeast-1c
      CidrBlock: !Ref PrivateSubnet2CIDR
      # Privateサブネットなのでfalseにする
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Private Subnet

  #----------------------
  # IGW作成
  #----------------------
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Ref EnvironmentName

  #----------------------
  # IGWをVPCにアタッチする
  #----------------------
  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

  #----------------------
  # ルートテーブル作成 (Publicサブネット用)
  #----------------------
  # ルートテーブル(Publicサブネット用)作成
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Routes

  # 上記のルートテーブルに、レコードを追加する
  DefaultPublicRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      # IGW用のレコードを追加する
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  # Publicサブネット1に、上記ルートテーブルを関連付ける
  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet1

  # Publicサブネット2に、パブリックサブネット用ルートテーブルを関連付ける
  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet2

  #----------------------
  # RDSサブネットグループ作成
  #----------------------
  RdsSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: "Subnet Group for RDS instances"
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
      DBSubnetGroupName: my-rds-subnet-group
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName} RDS Subnet Group"

  #----------------------
  # Route 53ホストゾーン作成
  #----------------------
  HostedZone:
    Type: AWS::Route53::HostedZone
    Properties:
      Name: aws-dev-lab-demo.shop

  #----------------------
  # AWS Certificate Manager(証明書)作成
  #----------------------
  Certificate:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: "blog.aws-dev-lab-demo.shop"
      # 検証方法
      ValidationMethod: DNS
      DomainValidationOptions:
        - DomainName: "blog.aws-dev-lab-demo.shop"
          HostedZoneId: !Ref HostedZone

#----------------------
# エクスポート
#----------------------
Outputs:
  VPCId:
    Value: !Ref VPC
    Description: "VPC ID"
    Export:
      Name: !Sub "${EnvironmentName}-VPCId"

  PublicSubnet1Id:
    Value: !Ref PublicSubnet1
    Description: "PublicSubnet1"
    Export:
      Name: !Sub "${EnvironmentName}-PublicSubnet1Id"

  PublicSubnet2Id:
    Value: !Ref PublicSubnet2
    Description: "PublicSubnet2"
    Export:
      Name: !Sub "${EnvironmentName}-PublicSubnet2Id"

  RdsSubnetGroupId:
    Value: !Ref RdsSubnetGroup
    Description: "RDS Subnet Group Name"
    Export:
      Name: !Sub "${EnvironmentName}-RdsSubnetGroupId"

  HostedZoneId:
    Description: "The HostedZone ID"
    Value: !Ref HostedZone
    Export:
      Name: !Sub "${EnvironmentName}-HostedZoneId"

  CertificateArn:
    Description: "The ARN of the SSL Certificate"
    Value: !Ref Certificate
    Export:
      Name: !Sub "${EnvironmentName}-CertificateArn"
AWSTemplateFormatVersion: '2010-09-09'
Description: 'hands-on1'
#----------------------------------------------------------------
# パラメーター
#----------------------------------------------------------------
Parameters:
  EnvironmentName:
    Description: hands-on1-template
    Type: String
#----------------------------------------------------------------
# リソース
#----------------------------------------------------------------
Resources:

  #----------------------
  # セキュリティグループ作成
  #----------------------
  # EC2用のセキュリティグループ
  WebSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: Web-SG-1
      GroupDescription: "Description"
      VpcId: !ImportValue
        'Fn::Sub': '${EnvironmentName}-VPCId'
      #インバウンドルール
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          SourceSecurityGroupId: !Ref LbSecurityGroup
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          SourceSecurityGroupId: !Ref LbSecurityGroup
      #アウトバウンドルール
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: Web-sg1

  #RDS用セキュリティグループ
  RdsSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: RDS-SG-1
      GroupDescription: "Description"
      VpcId: !ImportValue
        'Fn::Sub': '${EnvironmentName}-VPCId'
      SecurityGroupIngress:
        # Replace with your specific IP range or remove the CidrIp property to allow access from anywhere
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          SourceSecurityGroupId: !Ref WebSecurityGroup
      Tags:
        - Key: Name
          Value: RDS-SG-1

  # ALB用のセキュリティグループ
  LbSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: LB-SG-1
      GroupDescription: "Security Group for ALB"
      VpcId: !ImportValue
        'Fn::Sub': '${EnvironmentName}-VPCId'
      SecurityGroupIngress:
        # HTTP
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
        # HTTPS
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: LB-SG-1

#----------------------
# エクスポート
#----------------------
Outputs:
  WebSecurityGroup:
    Value: !Ref WebSecurityGroup
    Description: "Web Security Group ID"
    Export:
      Name: !Sub "${EnvironmentName}-WebSecurityGroupId"

  RdsSecurityGroup:
    Value: !Ref RdsSecurityGroup
    Description: "Security Group ID for RDS access"
    Export:
      Name: !Sub "${EnvironmentName}-RdsSecurityGroupId"

  # ALB用セキュリティグループのエクスポート
  LbSecurityGroup:
    Description: "Security Group ID for ALB"
    Value: !Ref LbSecurityGroup
    Export:
      Name: !Sub "${EnvironmentName}-LbSecurityGroupId"
AWSTemplateFormatVersion: '2010-09-09'
Description: 'hands-on1'
#----------------------------------------------------------------
# パラメーター
#----------------------------------------------------------------
Parameters:
  # EC2インスタンスタイプ
  InstanceType:
    Type: String
    Default: t2.micro
    AllowedValues:
      - t2.micro
      - t2.nano
    Description: Select EC2 instance type.

  EnvironmentName:
    Description: hands-on1-template
    Type: String

#----------------------------------------------------------------
# リソース
#----------------------------------------------------------------
Resources:
  #----------------------
  # RDS作成
  #----------------------
  MyRDSInstance:
    Type: AWS::RDS::DBInstance
    Properties:
      # データベースエンジン
      Engine: mysql
      # マスターユーザー名
      MasterUsername: wordpress
      # ストレージの割り当て (GB)
      AllocatedStorage: 20
      # マスターパスワード(実際の環境ではセキュアな方法で提供する)
      MasterUserPassword: "ChangeMe1234"
      # DBインスタンスクラス
      DBInstanceClass: db.t2.micro
      # データベース名
      DBName: wordpress
      # VPCの設定(Outputsから参照)
      DBSubnetGroupName: !ImportValue
        'Fn::Sub': '${EnvironmentName}-RdsSubnetGroupId'
      # パブリックアクセス無効
      PubliclyAccessible: false
      # セキュリティグループ
      VPCSecurityGroups:
        - !ImportValue
          'Fn::Sub': '${EnvironmentName}-RdsSecurityGroupId'
      # マルチAZ
      MultiAZ: false

  #----------------------
  # IAMロール クロススタック参照ができない為、ここでIAMロールを作成する
  # ポリシー
  # ・SSMログインの為のポリシー
  # ・EC2のログをcloudwatchに送信する為のポリシー
  #----------------------
  SsmSessionManagerIamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Service:
                - 'ec2.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      Path: '/'
      RoleName: 'SsmSessionManagerIamRole'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
        - arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy

  SessionManagerIamInstanceProfile:
    Type: 'AWS::IAM::InstanceProfile'
    Properties:
      Path: '/'
      Roles:
        - !Ref SsmSessionManagerIamRole
  #----------------------
  # 起動テンプレート作成(EC2インスタンスの設計書を作成)
  #----------------------
  LaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: MyLaunchTemplate
      LaunchTemplateData:
        # AmazonLinux2
        ImageId: "ami-0e25eba2025eea319"
        InstanceType: !Ref InstanceType
        NetworkInterfaces:
          # 自動割当パプリックIPを有効
          - AssociatePublicIpAddress: true
            DeviceIndex: 0
            # セキュリティグループはこちらに指定
            Groups:
              - !ImportValue
                'Fn::Sub': '${EnvironmentName}-WebSecurityGroupId'
        # ストレージを設定
        BlockDeviceMappings:
          - DeviceName: "/dev/xvda"
            Ebs:
              VolumeType: "gp2"
              VolumeSize: 8
        # SSM経由でSSH接続出来るようにするIAMロールをアタッチする
        IamInstanceProfile:
          Arn: !GetAtt SessionManagerIamInstanceProfile.Arn
        UserData:
          Fn::Base64: !Sub |
            #!/bin/bash
            # AWS CLIをインストール
            yum install -y aws-cli
            # Amazon Linux 2 に PHP、MySQL、Apache、WordPress をインストール
            amazon-linux-extras install -y php7.4
            yum install -y mysql httpd php-mbstring php-xml gd php-gd
            systemctl enable httpd.service
            systemctl start httpd.service
            wget https://ja.wordpress.org/latest-ja.tar.gz
            tar zxvf latest-ja.tar.gz
            cp -r wordpress/* /var/www/html/
            chown apache:apache -R /var/www/html/
            # HTTPSを認識するための設定をwp-config.phpに追加
            # wp-config-sample.phpをコピーしてwp-config.phpを作成
            cp /var/www/html/wp-config-sample.php /var/www/html/wp-config.php
            # HTTPSを認識するための設定をwp-config.phpに追加
            sed -i '/That'\''s all, stop editing! Happy publishing./i \
            if (isset($_SERVER['\''HTTP_X_FORWARDED_PROTO'\'']) && $_SERVER['\''HTTP_X_FORWARDED_PROTO'\''] === '\''https'\'') {\
              $_SERVER['\''HTTPS'\''] = '\''on'\'';\
              $_ENV['\''HTTPS'\''] = '\''on'\'';\
            }' /var/www/html/wp-config.php
            #######################   cloudwatchでログ監視   ###########################
            # awslogs のインストール
            yum -y install awslogs
            # awslogs の設定
            sed -i 's/region = us-east-1/region = ap-northeast-1/' /etc/awslogs/awscli.conf
            cat <<EOT >> /etc/awslogs/awslogs.conf
            [HttpAccessLog]
            file = /var/log/httpd/access_log
            log_group_name = HttpAccessLog
            log_stream_name = {instance_id}
            datetime_format = %b %d %H:%M:%S
            [HttpErrorLog]
            file = /var/log/httpd/error_log
            log_group_name = HttpErrorLog
            log_stream_name = {instance_id}
            datetime_format = %b %d %H:%M:%S
            EOT
            systemctl enable awslogsd
            systemctl start awslogsd

  #----------------------
  # ALBターゲットグループ作成
  #----------------------
  ALBTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      VpcId: !ImportValue
        'Fn::Sub': '${EnvironmentName}-VPCId'
      HealthCheckPath: /readme.html
      HealthCheckProtocol: HTTP
      HealthCheckPort: '80'
      #ヘルスチェック間隔
      HealthCheckIntervalSeconds: 10
      #ヘルスチェックのタイムアウト時間
      HealthCheckTimeoutSeconds: 5
      #ヘルシーと見なすための連続成功回数
      HealthyThresholdCount: 2
      #アンヘルシーと見なすための連続失敗回数
      UnhealthyThresholdCount: 2
      Port: 80
      Protocol: HTTP
      TargetType: instance


  #----------------------
  # ALBリスナー作成
  #----------------------
  #HTTP
  # 既存のHTTPリスナーをリダイレクト用に変更
  ALBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref ApplicationLoadBalancer
      Port: 80
      Protocol: HTTP
      DefaultActions:
        - Type: redirect
          RedirectConfig:
            Protocol: HTTPS
            Port: '443'
            StatusCode: HTTP_301

  # HTTPSリスナーを追加
  HttpsListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref ApplicationLoadBalancer
      Port: 443
      Protocol: HTTPS
      Certificates:
        - CertificateArn: !ImportValue
            'Fn::Sub': '${EnvironmentName}-CertificateArn'
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref ALBTargetGroup


  #----------------------
  # ALB作成
  #----------------------
  ApplicationLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: LB1
      Scheme: internet-facing
      Subnets:
        - !ImportValue
          'Fn::Sub': '${EnvironmentName}-PublicSubnet1Id'
        - !ImportValue
          'Fn::Sub': '${EnvironmentName}-PublicSubnet2Id'
      SecurityGroups:
        - !ImportValue
          'Fn::Sub': '${EnvironmentName}-LbSecurityGroupId'

  #----------------------
  # AutoScalingGroup作成
  #----------------------
  AutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      #起動テンプレート
      LaunchTemplate:
        LaunchTemplateId: !Ref LaunchTemplate
        Version: !GetAtt LaunchTemplate.LatestVersionNumber
      # AutoScalingグループにALBを紐付ける
      TargetGroupARNs:
        - !Ref ALBTargetGroup
      # 任意な名前
      AutoScalingGroupName: Test-AutoScaling1
      # 希望する容量
      DesiredCapacity: 2
      # 最小キャパシティ
      MinSize: 2
      # 最大キャパシティ
      MaxSize: 4
      VPCZoneIdentifier:
        - !ImportValue
          'Fn::Sub': '${EnvironmentName}-PublicSubnet1Id'
        - !ImportValue
          'Fn::Sub': '${EnvironmentName}-PublicSubnet2Id'
      # ヘルスチェックタイプ ELBの場合はELB
      HealthCheckType: ELB
      # ヘルスチェック感覚
      HealthCheckGracePeriod: 300

  #----------------------
  # SNSトピックの作成
  #----------------------
  NotificationTopic:
    Type: AWS::SNS::Topic
    Properties:
      Subscription:
        - Endpoint: dev.takakura0318@gmail.com
          Protocol: email
      TopicName: "NotificationTopic"

  #----------------------
  # CloudWatchアラーム(CPUが70%を超えた場合)
  #----------------------
  HighCpuAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmDescription: "Alarm when CPU exceeds 70%"
      # メトリクスの選択
      Namespace: AWS/EC2
      MetricName: CPUUtilization
      # 統計
      Statistic: Average
      # 集計期間
      Period: 300
      # 静的
      EvaluationPeriods: 1
      # 条件値
      Threshold: 70
      # 演算子:より大きい
      ComparisonOperator: GreaterThanThreshold
      # よくわからないけどざっくり名前みたいもの
      Dimensions:
        - Name: AutoScalingGroupName
          Value: !Ref AutoScalingGroup
      AlarmActions:
        - !Ref NotificationTopic
        - !Ref HighCpuScalingPolicy
      # 欠落データは見つかりませんで処理
      TreatMissingData: missing

  #----------------------
  # CloudWatchアラーム(CPUが30%以下の場合)
  #----------------------
  LowCpuAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmDescription: "Alarm when CPU falls below 30%"
      MetricName: CPUUtilization
      Namespace: AWS/EC2
      Statistic: Average
      Period: 300
      EvaluationPeriods: 1
      Threshold: 30
      ComparisonOperator: LessThanThreshold
      Dimensions:
        - Name: AutoScalingGroupName
          Value: !Ref AutoScalingGroup
      AlarmActions:
        - !Ref NotificationTopic
        - !Ref LowCpuScalingPolicy
      TreatMissingData: notBreaching

  #----------------------
  # AutoScalingグループのスケーリングポリシー
  # CPU使用率が70%より大きい場合はEC2を1台追加する
  #----------------------
  HighCpuScalingPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AutoScalingGroupName: !Ref AutoScalingGroup
      # ポリシータイプ
      PolicyType: SimpleScaling
      # 1台追加
      ScalingAdjustment: 1
      AdjustmentType: ChangeInCapacity
      # 別のスケーリングアクティビティを許可するまでの秒数
      Cooldown: 30

  #----------------------
  # AutoScalingグループのスケーリングポリシー
  # CPU使用率が30%以下の場合はEC2を1台削除する
  #----------------------
  LowCpuScalingPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AutoScalingGroupName: !Ref AutoScalingGroup
      PolicyType: SimpleScaling
      ScalingAdjustment: -1
      AdjustmentType: ChangeInCapacity
      Cooldown: 30

  #----------------------
  # S3バケットの作成
  #----------------------
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      # Route53にて、ALBと連携したフェイルオーバールーティング機能を使う為、バケット名はドメイン名を指定する必要がある
      BucketName: blog.aws-dev-lab-demo.shop
      PublicAccessBlockConfiguration:
        BlockPublicAcls: false
        IgnorePublicAcls: false
        BlockPublicPolicy: false
        RestrictPublicBuckets: false
      WebsiteConfiguration:
        IndexDocument: index.html
        ErrorDocument: error.html

  #バケットに権限付与
  BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref S3Bucket
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: 'PublicReadGetObject'
            Effect: 'Allow'
            Principal: '*'
            Action: 's3:GetObject'
            Resource: !Sub 'arn:aws:s3:::${S3Bucket}/*'

  #----------------------
  # 作成したホストゾーンにレコードを追加する
  #----------------------
  # フェイルオーバーのためのRoute 53レコードセットグループ
  MyRecordSetGroup:
    Type: AWS::Route53::RecordSetGroup
    Properties:
      HostedZoneId: !ImportValue
        'Fn::Sub': '${EnvironmentName}-HostedZoneId'
      RecordSets:
        # ALBに対するプライマリレコード
        - Name: "blog.aws-dev-lab-demo.shop"
          Type: A
          SetIdentifier: "Primary-ALB"
          Failover: "PRIMARY"
          HealthCheckId: !Ref ALBHealthCheck
          AliasTarget:
            HostedZoneId: !GetAtt ApplicationLoadBalancer.CanonicalHostedZoneID
            DNSName: !GetAtt ApplicationLoadBalancer.DNSName
            EvaluateTargetHealth: true
        # S3に対するセカンダリレコード
        - Name: "blog.aws-dev-lab-demo.shop"
          Type: A
          SetIdentifier: "Secondary-S3"
          Failover: "SECONDARY"
          AliasTarget:
            HostedZoneId: "Z2M4EHUR26P7ZW"
            # ここの値を仮にマネコンで設定する場合はプルダウン値にサジェストがある。そのサジェストにいずれかに合致する値を指定しないフェイルオーバされないので注意
            DNSName: !Sub "http://${S3Bucket}.s3-website-${AWS::Region}.amazonaws.com"
            EvaluateTargetHealth: false

  #----------------------
  # Route53→ALBに対する対するヘルスチェック
  #----------------------
  ALBHealthCheck:
    Type: AWS::Route53::HealthCheck
    Properties:
      HealthCheckConfig:
        # ALBのリッスンポート
        Port: 80
        Type: HTTP
        # ALBのヘルスチェックパス
        ResourcePath: "/readme.html"
        FullyQualifiedDomainName: !GetAtt ApplicationLoadBalancer.DNSName
        # ヘルスチェックのリクエスト間隔(秒)
        RequestInterval: 30
        # ヘルスチェックが失敗と判断されるまでの連続回数
        FailureThreshold: 3
#----------------------
# Outputs
#----------------------

動作確認

css崩れ解消されていれば成功。データベース接続エラーは、今回のハンズオンの本質じゃないので無視。仮に修正する場合は、wp-config.phpあたりをみなす

ハンズオン6

ACMでワイルドカード指定での証明書が取得できない原因は暇なときに調査する

コメント

タイトルとURLをコピーしました