こんにちは。2024新人の加藤です。
今回はAWS上のCloudFormationと構成管理ツールのAnsibleを使用し、その便利さに感銘を受けたため、具体的に何を行って、どのような部分に感銘を受けたかご紹介したいと思います。
Ansibleとは
Ansibleとは、RedHat社が開発するシステム設定やソフトウェアの自動化を行う構成管理ツールです。
Ansibleにはコントロールノードと管理対象ノードが存在します。
コントロールノードに管理対象ノードのあるべき状態を記載した「Playbook」ファイルを作成し、「Inventory」ファイルに管理対象ノードの情報を記載します。「Inventory」と「Playbook」の内容は以下のようになっています。
- Inventory
 リモートホストやグループを管理するファイル
 ファイル名は「hosts」
- Playbook
 リモートホストの状態を定義するymlファイル
 Inventoryで管理されたグループやホスト単位で作成
 ファイル名は「main.yml」
また、Playbookファイルは「ロール」形式を取ることによって、効率的にファイル管理をすることができるようになります。ロールとは、Playbookファイルを複数に分割し、別のファイルとして実行・管理できる仕組みのことです。
構成概要
アプリケーションをEC2上にホストし、プライベートネットワーク環境のPCからそのEC2にアクセスしWebページが表示されるか確認します。EC2へのアプリケーションのホストは構成管理ツール(Ansible)を使用し、自動化します。
AWS CloudFormationで各リソースをIaC化し、ルートテンプレートから複数スタックを作成しています。
スタック構造は以下のようになっています。
- Application_Stack:親スタック
- Network_Stack:ネットワークリソースを作成する子スタック(Subnet、Route)
- Application_quickstart_Stack:アプリケーション実行に必要なリソースを作成する子スタック(IAM、SecurityGroup、Route53、ACM、ELB、EC2)
各リソースの詳細内容
各リソースの詳細内容は以下のようになっています。
| No. | カテゴリ | 項目 | 詳細 | 備考 | 
|---|---|---|---|---|
| 1 | アプリケーション | Spring Boot | WebアプリケーションはSpring Bootを使用して作成する ページ概要は以下の通りである 
 | |
| 2 | Ansible | Playbook | 以下のAnsible構成図を基に作成する 
 各ロールの役割を記載する linux_common_config: 
 web_ui_config: 
 | 今回はAnsibleをローカルホストから実行するためは、インベントリファイルは作成しない | 
| 3 | AWS | IAM | 以下の権限を付与する 
 | |
| 4 | VPC | サブネット:構成図を基に作成する ルートテーブル:作成したサブネットの関連付け、および Gateway へのルーティングを設定する | InternetGateway、NatGatewayは既に作成されているものを使用する | |
| 5 | SecurityGroup(ELB) | プライベートネットワーク環境(netskopeのグローバルIP)からアクセスできる設定にする | ||
| 6 | SecurityGroup(EC2) | ELBのみからアクセスできる設定にする | ||
| 7 | Route53 | 指定の共通ホストゾーンを利用し、サブドメインのホストゾーンを作成する 
 サブドメインのホストゾーンには以下を設定する 
 | 共通ホストゾーンのゾーン情報にサブドメインのNSレコード情報を登録する | |
| 8 | ACM | 東京リージョンに証明書を発行する(パブリック証明書をDNS検証にて発行) | ||
| 9 | ELB | Load balancer typeは「Application」とする | セキュリティ強化のため、アクセスログの有効化を実施する | |
| 10 | EC2 | インスタンスタイプは「t3.micro」とする AWS::CloudFormation::Init リソースで以下を実施する 
 UserDataで以下を実施する 
 | AMIは既に作成されている共通AMIを使用する | |
| 11 | S3 | 名前は「hrd-d0-s3b-katou」とする 2つの子スタックテンプレート(Network_Stack.yml、Application_quickstart_Stack)とAnsibleのPlaybookファイルを格納しておく | 
Ansible
AWS::CloudFormation::Init リソースでAnsibleをEC2上で実行することにより、スタック作成時にアプリケーションが自動で実行されるようになります。また、AnsibleのPlaybookファイルはS3に配置しておき、AWS::CloudFormation::Init リソースでS3からEC2にPlaybookファイルをダウンロードします。
Playbookファイル
# Playbookファイルのrolesを関連付け
- hosts: localhost
  become: true
  connection: local
  roles:
    - linux_common_config
    - web_ui_config
「linux_common_config」ロールのタスクファイル
# yumリポジトリのアップデート
- name: Update all packages
  yum:
    name: '*'
    update_only: yes
「web_ui_config」ロールのタスクファイル
# Java 17 Amazon Corretoをダウンロード
- name: Install Java 17 Amazon Correto
  yum:
    name: java-17-amazon-corretto
    state: installed
# JarファイルをS3バケットからローカルホストにダウンロード
- name: Copy Jar file from AWS S3 Bucket to localhost
  command: "aws s3 cp s3://hrd-d0-s3b-katou/web-ui-teamG-katou-0.0.1-SNAPSHOT.jar /tmp/web_app/"
# Jarファイルをローカルホストから実行
- name: Run command "java -jar"
  command: "java -jar /tmp/web_app/web-ui-teamG-katou-0.0.1-SNAPSHOT.jar"
AWS CloudFormation テンプレート
構成図と各リソースの詳細内容を基に作成したテンプレートが以下の3つです。
以下のルートテンプレート(Application_Stack)をCloudFormationでデプロイしてください。
子スタックはネスト構造化してあるので、ルートテンプレートを実行した段階で自動で生成されます。
Application_Stack
AWSTemplateFormatVersion: 2010-09-09
Description: Root stack for 2024 Cloud Training Program - TeamG Kato
Parameters:
  #TemplateNetworkのURLを記載
  TemplateNetworkUrl:
    Description: Network template Object URL
    Type: String
    Default: "https://hrd-d0-s3b-katou.s3.ap-northeast-1.amazonaws.com/Network_Stack.yml"
  #TemplateApplicationQuickstartのURLを記載
  TemplateApplicationQuickstartUrl:
    Description: Application Quickstart Template Object URL
    Type: String
    Default: "https://hrd-d0-s3b-katou.s3.ap-northeast-1.amazonaws.com/Application_quickstart_Stack.yml"
  #Tagsに使用するパラメータ
  SystemId:
    Description: System ID
    Type: String
    Default: "hrd"
  EnvironmentId:
    Description: Environment ID
    Type: String
    Default: "d0"
  DeveloperId:
    Description: Developer ID
    Type: String
    Default: "katou-y-test"
  #サブネット作成に使用するパラメータ
  VpcId:
    Type: String
    Description: VPC ID
    Default: "vpc-04ac6f2eb1c774c79"
  PublicSubnet1CidrBlock:
    Description : CIDR block for Public Subnet1
    Type: String
    Default: "10.0.225.0/24"
  PublicSubnet2CidrBlock:
    Description : CIDR block for Public Subnet2
    Type: String
    Default: "10.0.226.0/24"
  PrivateSubnet1CidrBlock:
    Description : CIDR block for Private Subnet1
    Type: String
    Default: "10.0.227.0/24"
  #ルートテーブル作成・ルート追加・ルートテーブルとサブネットの関連付けに使用するパラメータ
  InternetGatewayId:
    Type: String
    Description: Internet Gateway ID
    Default: "igw-0b1320c68c6590e15"
  NatGatewayId:
    Type: String
    Description: Nat Gateway ID
    Default: "nat-0be241ae2ac812138"
  #EC2インスタンス作成に使用するパラメータ
  InstanceType:
    Description: EC2 Instance Type
    Type: String
    Default: "t3.micro"
  #ホストゾーン登録・証明書に使用するパラメータ
  SubDomain:
    Description: FQDN of the certificate
    Type: String
    Default: "katou.training.aidiv1.com"
Resources:
  #Network_Stackが管理するリソースのスタック
  TemplateNetworkStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Ref TemplateNetworkUrl
      Parameters:
        SystemId: !Ref SystemId
        EnvironmentId: !Ref EnvironmentId
        DeveloperId: !Ref DeveloperId
        VpcId: !Ref VpcId
        CidrBlockForPublicSubnet1: !Ref PublicSubnet1CidrBlock
        CidrBlockForPublicSubnet2: !Ref PublicSubnet2CidrBlock
        CidrBlockForPrivateSubnet1: !Ref PrivateSubnet1CidrBlock
        InternetGatewayId: !Ref InternetGatewayId
        NatGatewayId: !Ref NatGatewayId
  #Application_quickstart_Stackが管理するリソースのスタック
  TemplateApplicationQuickstartStack:
    DependsOn: TemplateNetworkStack
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Ref TemplateApplicationQuickstartUrl
      Parameters:
        SystemId: !Ref SystemId
        EnvironmentId: !Ref EnvironmentId
        DeveloperId: !Ref DeveloperId
        VpcId: !Ref VpcId
        PublicSubnet1Id: !GetAtt TemplateNetworkStack.Outputs.PublicSubnet1Id
        PublicSubnet2Id: !GetAtt TemplateNetworkStack.Outputs.PublicSubnet2Id
        PrivateSubnet1Id: !GetAtt TemplateNetworkStack.Outputs.PrivateSubnet1Id
        CidrBlockForPrivateSubnet1: !Ref PrivateSubnet1CidrBlock
        InstanceType: !Ref InstanceType
        SubDomain: !Ref SubDomain
Network_Stack
AWSTemplateFormatVersion: 2010-09-09
Description: Network stack for 2024 Cloud Training Program - TeamG Kato Resource - Subnet, Route
Parameters:
  #Tagsに使用するパラメータ
  SystemId:
    Description: System ID
    Type: String
  EnvironmentId:
    Description: Environment ID
    Type: String
  DeveloperId:
    Description: Developer ID
    Type: String
  #サブネット作成に使用するパラメータ
  VpcId:
    Type: String
    Description: VPC ID
  CidrBlockForPublicSubnet1:
    Description : CIDR block for Public Subnet1
    Type: String
  CidrBlockForPublicSubnet2:
    Description : CIDR block for Public Subnet2
    Type: String
  CidrBlockForPrivateSubnet1:
    Description : CIDR block for Private Subnet1
    Type: String
  #ルートテーブル作成・ルート追加・ルートテーブルとサブネットの関連付けに使用するパラメータ
  InternetGatewayId:
    Type: String
    Description: Internet Gateway ID
  NatGatewayId:
    Type: String
    Description: Nat Gateway ID
Resources:
  #パブリックサブネット1の作成
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VpcId
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      CidrBlock: !Ref CidrBlockForPublicSubnet1
      Tags:
        - Key: Name
          Value: !Sub ${SystemId}-${EnvironmentId}-sub-pub1-${DeveloperId}
        - Key: Cost
          Value: !Sub ${SystemId}-${EnvironmentId}-${DeveloperId}
        - Key: Env
          Value: !Sub ${SystemId}-${EnvironmentId}
  #パブリックサブネット2の作成
  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VpcId 
      AvailabilityZone: !Select [ 1, !GetAZs '' ]
      CidrBlock: !Ref CidrBlockForPublicSubnet2
      Tags:
        - Key: Name
          Value: !Sub ${SystemId}-${EnvironmentId}-sub-pub2-${DeveloperId}
        - Key: Cost
          Value: !Sub ${SystemId}-${EnvironmentId}-${DeveloperId}
        - Key: Env
          Value: !Sub ${SystemId}-${EnvironmentId}
  #プライベートサブネット1の作成
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VpcId
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      CidrBlock: !Ref CidrBlockForPrivateSubnet1
      Tags:
        - Key: Name
          Value: !Sub ${SystemId}-${EnvironmentId}-sub-pri1-${DeveloperId}
        - Key: Cost
          Value: !Sub ${SystemId}-${EnvironmentId}-${DeveloperId}
        - Key: Env
          Value: !Sub ${SystemId}-${EnvironmentId}
  #ルートテーブルの作成
  RouteTableForPublicSubnet1:
    Type: AWS::EC2::RouteTable
    Properties: 
      VpcId: !Ref VpcId
      Tags:
        - Key: Name
          Value: !Sub ${SystemId}-${EnvironmentId}-rtb-pub1-${DeveloperId}
        - Key: Cost
          Value: !Sub ${SystemId}-${EnvironmentId}-${DeveloperId}
        - Key: Env
          Value: !Sub ${SystemId}-${EnvironmentId}
  RouteTableForPublicSubnet2:
    Type: AWS::EC2::RouteTable
    Properties: 
      VpcId: !Ref VpcId
      Tags:
        - Key: Name
          Value: !Sub ${SystemId}-${EnvironmentId}-rtb-pub2-${DeveloperId}
        - Key: Cost
          Value: !Sub ${SystemId}-${EnvironmentId}-${DeveloperId}
        - Key: Env
          Value: !Sub ${SystemId}-${EnvironmentId}
  RouteTableForPrivateSubnet1:
    Type: AWS::EC2::RouteTable
    Properties: 
      VpcId: !Ref VpcId
      Tags:
        - Key: Name
          Value: !Sub ${SystemId}-${EnvironmentId}-rtb-pri1-${DeveloperId}
        - Key: Cost
          Value: !Sub ${SystemId}-${EnvironmentId}-${DeveloperId}
        - Key: Env
          Value: !Sub ${SystemId}-${EnvironmentId}
  #ルートテーブルへのルート追加
  InternetGatewayRouteForPublicSubnet1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref RouteTableForPublicSubnet1
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGatewayId
  InternetGatewayRouteForPublicSubnet2:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref RouteTableForPublicSubnet2
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGatewayId
  NatGatewayRouteForPrivateSubnet1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref RouteTableForPrivateSubnet1
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref NatGatewayId
  #ルートテーブルとサブネットの関連付け
  PublicSubnet1Association:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet1
      RouteTableId: !Ref RouteTableForPublicSubnet1
  PublicSubnet2Association:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet2
      RouteTableId: !Ref RouteTableForPublicSubnet2
  PrivateSubnet1Association:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet1
      RouteTableId: !Ref RouteTableForPrivateSubnet1
Outputs:
  PublicSubnet1Id:
    Description: Public Subnet 1 ID
    Value: !Ref PublicSubnet1
  PublicSubnet2Id:
    Description: Public Subnet 2 ID
    Value: !Ref PublicSubnet2
  PrivateSubnet1Id:
    Description: Private Subnet 1 ID
    Value: !Ref PrivateSubnet1
Application_quickstart_Stack
AWSTemplateFormatVersion: 2010-09-09
Description: Application Quickstart Stack for 2024 Cloud Training Program - TeamG Kato Resource - IAM, SecurityGroup, Route53, ACM, ELB, EC2
Parameters:
  #Tagsに使用するパラメータ
  SystemId:
    Description: System ID
    Type: String
  EnvironmentId:
    Description: Environment ID
    Type: String
  DeveloperId:
    Description: Developer ID
    Type: String
  #各種ネットワーク設定パラメータ
  VpcId:
    Description: VPC ID
    Type: String
  PublicSubnet1Id:
    Description: Public Subnet 1 ID
    Type: String
  PublicSubnet2Id:
    Description: Public Subnet 2 ID
    Type: String
  PrivateSubnet1Id:
    Description: Private Subnet 1 ID
    Type: String
  CidrBlockForPrivateSubnet1:
    Description : CIDR block for Private Subnet1
    Type: String
  #EC2インスタンス作成に使用するパラメータ
  InstanceType:
    Description: EC2 Instance Type
    Type: String
  #ホストゾーン登録・証明書に使用するパラメータ
  SubDomain:
    Description: FQDN of the certificate
    Type: String
Resources:
  #IAMロールを作成
  IAMRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${SystemId}-${EnvironmentId}-iam-ec2-${DeveloperId}
      #どのリソースに対してどんなアクションをするかを記載
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      #アクセス権限ポリシーを記載
      ManagedPolicyArns: 
        - "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
        - "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
      Tags:
        - Key: Name
          Value: !Sub ${SystemId}-${EnvironmentId}-iam-ec2-${DeveloperId}
        - Key: Cost
          Value: !Sub ${SystemId}-${EnvironmentId}-${DeveloperId}
        - Key: Env
          Value: !Sub ${SystemId}-${EnvironmentId}
  #インスタンスプロファイルの作成
  InstanceProfile:
    Type: 'AWS::IAM::InstanceProfile'
    Properties:
      Path: '/'
      Roles:
        - !Ref IAMRole # IAMロールへの参照
  #セキュリティグループ(ELB)の作成
  SecurityGroupForELB:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub ${SystemId}-${EnvironmentId}-sec-elb-${DeveloperId}
      GroupDescription: Security Group for ELB
      VpcId: !Ref VpcId
      Tags:
        - Key: Name
          Value: !Sub ${SystemId}-${EnvironmentId}-sec-elb-${DeveloperId}
        - Key: Cost
          Value: !Sub ${SystemId}-${EnvironmentId}-${DeveloperId}
        - Key: Env
          Value: !Sub ${SystemId}-${EnvironmentId}
  #インバウンドルール・アウトバンドルールを追加
  #netskopeのグローバルIPアドレスからELBへのHTTPS通信
  SecurityGroupIngress1HTTPSForELB:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref SecurityGroupForELB
      IpProtocol: tcp
      FromPort: 443
      ToPort: 443
      CidrIp: xxx.xxx.xxx.0/xx
  #netskopeのグローバルIPアドレスからELBへのHTTPS通信
  SecurityGroupIngress2HTTPSForELB:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref SecurityGroupForELB
      IpProtocol: tcp
      FromPort: 443
      ToPort: 443
      CidrIp: xxx.xxx.xxx.0/xx
  #netskopeのグローバルIPアドレスからELBへのHTTPS通信
  SecurityGroupIngress3HTTPSForELB:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref SecurityGroupForELB
      IpProtocol: tcp
      FromPort: 443
      ToPort: 443
      CidrIp: xxx.xxx.xxx.0/xx
  #プライベートサブネットからELBへの8080通信
  SecurityGroupIngressCustomTCP8080ForELB:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref SecurityGroupForELB
      IpProtocol: tcp
      FromPort: 8080
      ToPort: 8080
      CidrIp: !Ref CidrBlockForPrivateSubnet1
  #セキュリティグループ(EC2)の作成
  SecurityGroupForEC2:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub ${SystemId}-${EnvironmentId}-sec-ec2-${DeveloperId}
      GroupDescription: Security Group for EC2
      VpcId: !Ref VpcId
      Tags:
        - Key: Name
          Value: !Sub ${SystemId}-${EnvironmentId}-sec-ec2-${DeveloperId}
        - Key: Cost
          Value: !Sub ${SystemId}-${EnvironmentId}-${DeveloperId}
        - Key: Env
          Value: !Sub ${SystemId}-${EnvironmentId}
  #ELBからEC2への8080通信
  SecurityGroupIngressCustomTCP8080ForEC2:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref SecurityGroupForEC2
      IpProtocol: tcp
      FromPort: 8080
      ToPort: 8080
      SourceSecurityGroupId: !Ref SecurityGroupForELB
  #EC2インスタンスの作成
  EC2:
    Type: AWS::EC2::Instance
    #Metadataの記述
    Metadata: 
      AWS::CloudFormation::Init:
        configSets:
          Default:
            - InstallAnsible
            - CreateDirectory
            - Download
            - RunAnsible
        #Ansibleをインストールするためのデータ
        InstallAnsible:
          packages:
            yum:
              ansible: []
        #各種ディレクトリを作成するための実行コマンドデータ
        CreateDirectory:
          commands: 
            mkdirWebApp:
              command: "mkdir -p /tmp/web_app"
            mkdirAnsible:
              command: "mkdir -p /tmp/ansible"
            mkdirLinuxCommonConfig:
              command: "mkdir -p /tmp/ansible/roles/linux_common_config/tasks"
            mkdirWebUiConfig:
              command: "mkdir -p /tmp/ansible/roles/web_ui_config/tasks"
        #AnsibleのPlaybookをS3バケットからダウンロードするためのデータ
        Download:
          commands:
            downloadWebUiStartup:
              command: aws s3 cp s3://hrd-d0-s3b-katou/ansible/web_ui_startup.yml /tmp/ansible/
            downloadLinuxCommonConfig:
              command: aws s3 cp s3://hrd-d0-s3b-katou/ansible/roles/linux_common_config/tasks/main.yml /tmp/ansible/roles/linux_common_config/tasks/
            downloadWebUiConfig:
              command: aws s3 cp s3://hrd-d0-s3b-katou/ansible/roles/web_ui_config/tasks/main.yml /tmp/ansible/roles/web_ui_config/tasks/
        #AnsibleにおけるPlaybookの実行コマンドデータ
        RunAnsible: 
          commands: 
            runWebUiStartup: 
              command: "ansible-playbook -c local /tmp/ansible/web_ui_startup.yml"
    Properties:
      ImageId: ami-0cb4639ca5eb232fc
      InstanceType: !Ref InstanceType
      SubnetId: !Ref PrivateSubnet1Id
      SecurityGroupIds:
        - !Ref SecurityGroupForEC2
      IamInstanceProfile: !Ref InstanceProfile
      Tags:
        - Key: Name
          Value: !Sub ${SystemId}-${EnvironmentId}-ec2-${DeveloperId}
        - Key: Cost
          Value: !Sub ${SystemId}-${EnvironmentId}-${DeveloperId}
        - Key: Env
          Value: !Sub ${SystemId}-${EnvironmentId}
      #UserDataの記述
      UserData:
        Fn::Base64: !Sub |
             #!/bin/bash
             yum install -y aws-cfn-bootstrap
             /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource EC2 --configsets Default --region ${AWS::Region}
  #ELBの作成
  ApplicationLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: !Sub ${SystemId}-${EnvironmentId}-elb-${DeveloperId}
      Subnets:
        - !Ref PublicSubnet1Id
        - !Ref PublicSubnet2Id
      SecurityGroups:
        - !Ref SecurityGroupForELB
      Scheme: internet-facing
      Type: application
      LoadBalancerAttributes:
        - Key: access_logs.s3.enabled
          Value: true
        - Key: access_logs.s3.bucket
          Value: lms-d0-s3-logcollection
      Tags:
        - Key: Name
          Value: !Sub ${SystemId}-${EnvironmentId}-elb-${DeveloperId}
        - Key: Cost
          Value: !Sub ${SystemId}-${EnvironmentId}-${DeveloperId}
        - Key: Env
          Value: !Sub ${SystemId}-${EnvironmentId}
  #ELBのリスナー設定
  ListenerForELB:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref ApplicationLoadBalancer
      Port: 443
      Protocol: HTTPS
      Certificates:
        - CertificateArn: !Ref Certificate
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref TargetGroup
  #ターゲットグループ作成
  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties: 
      VpcId: !Ref VpcId
      Name: !Sub ${SystemId}-${EnvironmentId}-trg-${DeveloperId}
      Protocol: HTTP
      Port: 8080
      HealthCheckProtocol: HTTP
      HealthCheckPort: 8080
      HealthCheckPath: "/actuator/health"
      HealthCheckTimeoutSeconds: 10
      HealthCheckIntervalSeconds: 20
      HealthyThresholdCount: 3
      UnhealthyThresholdCount: 2
      Targets: 
        - Id: !Ref EC2
          Port: 8080 
      TargetType: instance
      Tags:
        - Key: Name
          Value: !Sub ${SystemId}-${EnvironmentId}-trg-${DeveloperId}
        - Key: Cost
          Value: !Sub ${SystemId}-${EnvironmentId}-${DeveloperId}
        - Key: Env
          Value: !Sub ${SystemId}-${EnvironmentId}
  #Route53のホストゾーン作成
  HostedZone:
    Type: AWS::Route53::HostedZone
    Properties:
      HostedZoneConfig: 
        Comment: "Subdomain of training.aidiv1.com"
      Name: !Ref SubDomain
      HostedZoneTags: 
        - Key: Name
          Value: !Sub ${SystemId}-${EnvironmentId}-r53-${DeveloperId}
        - Key: Cost
          Value: !Sub ${SystemId}-${EnvironmentId}-${DeveloperId}
        - Key: Env
          Value: !Sub ${SystemId}-${EnvironmentId}
  #ホストゾーンへのレコード登録
  #ELBのAレコード登録
  ARecordSetForELB:
    Type: AWS::Route53::RecordSet
    Properties:
      HostedZoneId: !Ref HostedZone
      Name: !Ref SubDomain
      Type: A
      AliasTarget:
        HostedZoneId: !GetAtt ApplicationLoadBalancer.CanonicalHostedZoneID
        DNSName: !GetAtt ApplicationLoadBalancer.DNSName
  #training.aidiv1.comへのNSレコード登録
  NSRecordSetForParentDomain:
    Type: AWS::Route53::RecordSet
    Properties:
      HostedZoneId: Z037543234RUL8GGC5CBK
      Name: !Ref SubDomain
      Type: NS
      TTL: 300
      ResourceRecords: !GetAtt HostedZone.NameServers
  #Certificate Manager
  Certificate:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref SubDomain
      ValidationMethod: DNS
      DomainValidationOptions:
        - DomainName: !Ref SubDomain
          HostedZoneId: !Ref HostedZone
      Tags:
        - Key: Name
          Value: !Sub ${SystemId}-${EnvironmentId}-acm-${DeveloperId}
        - Key: Cost
          Value: !Sub ${SystemId}-${EnvironmentId}-${DeveloperId}
        - Key: Env
          Value: !Sub ${SystemId}-${EnvironmentId}
実行画面
CloudFormation上でルートテンプレート「Application_Stack」を実行します。
- AWSマネジメントコンソール上でCloudFormationを検索し、「スタックの作成」を押下します。 
- ルートテンプレート「Application_Stack」をアップロードします。 
- 適当なスタック名を入力し、下までスクロールし「次へ」を押下します。       
- 下までスクロールし、二つのチェックボックスにチェックを入れ、「次へ」を押下します。 
- 下までスクロールし、「送信」を押下するとルートテンプレートが実行されます。 
- 実行されると親スタックが作成され、子スタックが二つ作成されます。リソースが全て作成されると以下の画面のようになります。 
- プライベートネットワーク環境のPCから「https://katou.training.aidiv1.com」にアクセスします。以下のような画面が表示されたらWebアプリケーションの自動実行が成功となります。 
注意点
AnsibleとCloudFormationを使用した際に、個人的につまづいた点を共有します。
- AnsibleをS3からEC2へダウンロードする際に、Ansibleのディレクトリ構造を保持したままPlaybookファイルをダウンロードしてください。ディレクトリ構造が保たれていないと、適切なロールのタスクが実行されません。
- AnsibleのPlaybookファイルに「become: true」を記述していないと、権限がなくコマンドが実行されない場合があります。「become: true」は一時的にroot権限を与えるものです。
- Spring Bootアプリケーションのリッスンポート番号は8080なので、私のように「HTTPプロトコルを使用するのでポート番号は80」と決めつけてしまうと痛い目をみます。
- 共通ホストゾーンのゾーン情報にサブドメインのNSレコード情報を登録しないと、共通ホストゾーンからのサブドメインの名前解決がうまくいきません。
- Certificate Managerでサブドメインの証明書をリクエストする際に検証方法としてDNS検証を選んだ場合、CloudFormationではデフォルトで、サブドメインのホストゾーンにCNAMEゾーン情報が登録されます。
最後に
以前行ったハンズオンではAWSをマネジメントコンソール上から画面をポチポチしてインフラ構築をしていたので、一度リソースを削除してから同じインフラを構築しようとなるととても面倒でした。それがCloudFormationとAnsibleを利用することで何度でも同じインフラを構築することができ、なおかつ自動で行ってくれるので、一度コードを書いてしまえばとても楽にインフラを構築できることに感動いたしました。
次回の記事では案件で得たナレッジなどを共有できればと思います。

 
  
  
  
  
