🚛

IDCFクラウドからAWSへWEBサーバーの移管を行った際経験になった色々(備忘録)

2023/07/14に公開

他クラウドサービスからAWSへWEBサーバーを移管

直近、Webサーバーを他クラウドからAWSへ移管する機会がありましたので、そこで体験した諸々について書き残す事にいたしました。

もっと「こうしたい」「こう出来ばよかった」という所は私の中でも多くあるのと合わせて、今回は備忘録的な意味合いが強く箇条書きに近いですが、眺める程度にお付き合いいただければ幸いです。


事の起こり・状況

・リスティング広告をアウトソーシングしている会社管理だったWebサーバーを諸々の事情で自社管理・運用する事となった
・冗長構成や負荷増時のアクセスを見込んでクラウド且つ、現状最も理解のあるAWSを選択
・2-3週間で移管を済ませたいとの注文
・作業者は私と移管前の環境を管理いただいていたインフラ担当様の計2人


私のスキルレベル

・事業会社の情シス。AWSサービスについての座学的知識は有
・ミドルウェアやDNS周りについてはまだまだ勉強不足
・実務でAWSを触る機会は今までなかった
・IaCが好き

ちなみに一緒に作業くださった担当様はAWSはAssociate程度のサービス理解は既にお持ちで、技術記事を読めば理解し作成が可能、ミドルウェアやDNS周りなどの広範にご経験のおありな方でした。

→私が主にインスタンスの外側、インスタンス内の設定等はお願いする形としました。


移管前のサーバー周りの構成をヒアリング

IDCFクラウドを利用 
・インスタンスタイプはhighcpu.L8 仮想CPU:4CPU、メモリ容量:8GB、ストレージ:115GB
・3ドメインのコンテンツが同じインスタンス内にある。
・内2ドメインについてはSSLの更新期限が切れている状況。
・ルートドメインにアクセスがあった場合wwwサブドメインにリダイレクトするプログラムがあるとの事。
・postfix、mysqlserver、postgresql serverが動いている。
・AutoScalingのような拡張性は無し、冗長構成はあるがコールドスタンバイ。
・パートナー企業の運用監視プランを契約中。


今回私が経験した事まとめ

メイン

・運用監視会社の選定・契約締結
・会社のクレカでAWSアカウントを新規作成
・クラスメソッド社メンバーズへの加入手続きと上記アカウント転入作業
・AWS SA様 技術者様への技術相談
・ドメインレジストラの変更
・新サーバー環境構築
・旧サーバーで進行中作業に新サーバーの差分発生となる部分の洗い出し
・コスパ面から削減可能な部分の判断

その他

・広告のレポーティングや電話番号出し分けに使う各ツールの機能と継続するかの判断に関わる情報整理
・GA、GTM、広告アカウントの作成、権限付与に関わる作業諸々


構成図とテンプレート

構成図

テンプレート

CloudFormationテンプレート
sample.yml
AWSTemplateFormatVersion: "2010-09-09"
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - 
        Label:
          default: Stack(CloudFormation)
        Parameters:
          - PJPrefix
          - Environment
      - 
        Label:
          default: VPC
        Parameters:
          - VPCCidrBlock
      - 
        Label:
          default: PublicSubnet
        Parameters:
          - PublicSubnet1CidrBlock
          - PublicSubnet2CidrBlock
      - 
        Label:
          default: ProtectedSubnet(forWebServer)
        Parameters:
          - ProtectedSubnet1CidrBlock
          - ProtectedSubnet2CidrBlock
      - 
        Label:
          default: Instance
        Parameters:
          - InstanceType
          - ImageId
          - DesiredCapacity
          - MinSize
          - MaxSize
          - TargetValue
      - 
        Label:
          default: Route53
        Parameters:
          - DomainNameforexample1
          - DomainNameforexample2
          - DomainNameforexample3
          - MXResourceRecord
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Parameters:
  PJPrefix:
    Type: String

  Environment:
    Type: String
    Default: prd

  VPCCidrBlock:
    Type: String
    Default: 10.1.0.0/16

  PublicSubnet1CidrBlock:
    Type: String
    Default: 10.1.0.0/24

  PublicSubnet2CidrBlock:
    Type: String
    Default: 10.1.1.0/24

  ProtectedSubnet1CidrBlock:
    Type: String
    Default: 10.1.2.0/24

  ProtectedSubnet2CidrBlock:
    Type: String
    Default: 10.1.3.0/24

  InstanceType:
    Type: String
    Default: m5.large

  ImageId:
    Type: String
    Default: ami-xxxxxxxxxxxxxxxxx
    AllowedValues:

  DesiredCapacity:
    Type: String
    Default: 1

  MinSize:
    Type: String
    Default: 1

  MaxSize:
    Type: String
    Default: 2

  TargetValue:
    Type: String
    Default: 90

  DomainNameforexample1:
    Type: String
    Default: example1.com
    AllowedValues:
      - example1.com

  DomainNameforexample2:
    Type: String
    Default: example2.com
    AllowedValues:
      - example2.com

  DomainNameforexample3:
    Type: String
    Default: example.com
    AllowedValues:
      - example.com

  MXResourceRecord:
    Type: String
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------#
Resources:
  # ------------------------------------------------------------#
  # Role
  # ------------------------------------------------------------#
# Role
  EC2Role:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - ec2.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
        - arn:aws:iam::aws:policy/AmazonS3FullAccess
        - arn:aws:iam::aws:policy/AmazonSESFullAccess
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-${Environment}-role-for-ec2

# InstanceProfile
  EC2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
        - !Ref EC2Role
  # ------------------------------------------------------------#
  # HostedZone
  # ------------------------------------------------------------#
  HostedZoneforexample1:
    Type: AWS::Route53::HostedZone
    Properties: 
      Name: !Ref DomainNameforexample1

  HostedZoneforexample2:
    Type: AWS::Route53::HostedZone
    Properties: 
      Name: !Ref DomainNameforexample2

  HostedZoneforexample3:
    Type: AWS::Route53::HostedZone
    Properties: 
      Name: !Ref DomainNameforexample3
  # ------------------------------------------------------------#
  # RecordSetGroup
  # ------------------------------------------------------------#
  AliasRecordexample1:
    Type: AWS::Route53::RecordSetGroup
    Properties:
      HostedZoneId: !Ref HostedZoneforexample1
      RecordSets:
        - Name: !Ref DomainNameforexample1
          AliasTarget: 
            DNSName: !GetAtt LoadBalancer.DNSName
            HostedZoneId: !GetAtt LoadBalancer.CanonicalHostedZoneID
          Type: A
        - Name: !Sub www.${DomainNameforexample1}
          AliasTarget: 
            DNSName: !GetAtt LoadBalancer.DNSName
            HostedZoneId: !GetAtt LoadBalancer.CanonicalHostedZoneID
          Type: A
        - Name: !Ref DomainNameforexample1
          TTL: 300
          ResourceRecords:
            - !Ref MXResourceRecord
          Type: MX
        - Name: !GetAtt EmailIdentityforexample1.DkimDNSTokenName1
          TTL: 300
          ResourceRecords:
              - !GetAtt EmailIdentityforexample1.DkimDNSTokenValue1
          Type: CNAME
        - Name: !GetAtt EmailIdentityforexample1.DkimDNSTokenName2
          TTL: 300
          ResourceRecords:
              - !GetAtt EmailIdentityforexample1.DkimDNSTokenValue2
          Type: CNAME
        - Name: !GetAtt EmailIdentityforexample1.DkimDNSTokenName3
          TTL: 300
          ResourceRecords:
              - !GetAtt EmailIdentityforexample1.DkimDNSTokenValue3
          Type: CNAME
        - Name: !Ref DomainNameforexample1
          TTL: 300
          ResourceRecords:
              - '"v=spf1 include:amazonses.com ~all"'
          Type: TXT

  AliasRecordexample2:
    Type: AWS::Route53::RecordSetGroup
    Properties:
      HostedZoneId: !Ref HostedZoneforexample2
      RecordSets:
        - Name: !Ref DomainNameforexample2
          AliasTarget: 
            DNSName: !GetAtt LoadBalancer.DNSName
            HostedZoneId: !GetAtt LoadBalancer.CanonicalHostedZoneID
          Type: A
        - Name: !Sub www.${DomainNameforexample2}
          AliasTarget: 
            DNSName: !GetAtt LoadBalancer.DNSName
            HostedZoneId: !GetAtt LoadBalancer.CanonicalHostedZoneID
          Type: A
        - Name: !Ref DomainNameforexample2
          TTL: 300
          ResourceRecords:
            - !Ref MXResourceRecord
          Type: MX
        - Name: !GetAtt EmailIdentityforexample2.DkimDNSTokenName1
          TTL: 300
          ResourceRecords:
              - !GetAtt EmailIdentityforexample2.DkimDNSTokenValue1
          Type: CNAME
        - Name: !GetAtt EmailIdentityforexample2.DkimDNSTokenName2
          TTL: 300
          ResourceRecords:
              - !GetAtt EmailIdentityforexample2.DkimDNSTokenValue2
          Type: CNAME
        - Name: !GetAtt EmailIdentityforexample2.DkimDNSTokenName3
          TTL: 300
          ResourceRecords:
              - !GetAtt EmailIdentityforexample2.DkimDNSTokenValue3
          Type: CNAME
        - Name: !Ref DomainNameforexample2
          TTL: 300
          ResourceRecords:
              - '"v=spf1 include:amazonses.com ~all"'
          Type: TXT

  AliasRecordexample3:
    Type: AWS::Route53::RecordSetGroup
    Properties:
      HostedZoneId: !Ref HostedZoneforexample3
      RecordSets:
        - Name: !Ref DomainNameforexample3
          AliasTarget: 
            DNSName: !GetAtt LoadBalancer.DNSName
            HostedZoneId: !GetAtt LoadBalancer.CanonicalHostedZoneID
          Type: A
        - Name: !Sub www.${DomainNameforexample3}
          AliasTarget: 
            DNSName: !GetAtt LoadBalancer.DNSName
            HostedZoneId: !GetAtt LoadBalancer.CanonicalHostedZoneID
          Type: A
        - Name: !Ref DomainNameforexample3
          TTL: 300
          ResourceRecords:
            - !Ref MXResourceRecord
          Type: MX
        - Name: !GetAtt EmailIdentityforexample3.DkimDNSTokenName1
          TTL: 300
          ResourceRecords:
              - !GetAtt EmailIdentityforexample3.DkimDNSTokenValue1
          Type: CNAME
        - Name: !GetAtt EmailIdentityforexample3.DkimDNSTokenName2
          TTL: 300
          ResourceRecords:
              - !GetAtt EmailIdentityforexample3.DkimDNSTokenValue2
          Type: CNAME
        - Name: !GetAtt EmailIdentityforexample3.DkimDNSTokenName3
          TTL: 300
          ResourceRecords:
              - !GetAtt EmailIdentityforexample3.DkimDNSTokenValue3
          Type: CNAME
        - Name: !Ref DomainNameforexample3
          TTL: 300
          ResourceRecords:
              - '"v=spf1 include:amazonses.com ~all"'
          Type: TXT
  # ------------------------------------------------------------#
  # WebACL
  # ------------------------------------------------------------#
  WebACL:
    Type: AWS::WAFv2::WebACL
    Properties:
      Scope: REGIONAL
      Name: !Sub ${PJPrefix}-${Environment}-webacl
      VisibilityConfig:
        CloudWatchMetricsEnabled: true
        MetricName: !Sub ${PJPrefix}-${Environment}-webaclmetric
        SampledRequestsEnabled: true
      DefaultAction:
        Allow: {}
      Rules:
        - Name: !Sub ${PJPrefix}-${Environment}-AWSManagedRulesBotControlRuleSet
          Priority: 0
          OverrideAction:
            None: {}
          Statement: 
            ManagedRuleGroupStatement: 
              VendorName: AWS
              Name: AWSManagedRulesBotControlRuleSet
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            MetricName: AWSManagedRulesCommonRuleSetMetric
            SampledRequestsEnabled: true
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-${Environment}-webacl
  # ------------------------------------------------------------#
  # WebACLAssociation
  # ------------------------------------------------------------#
  WebACLAssociation:
    Type: AWS::WAFv2::WebACLAssociation
    Properties: 
      ResourceArn: !Ref LoadBalancer
      WebACLArn: !GetAtt WebACL.Arn
  # ------------------------------------------------------------#
  # Bucket
  # ------------------------------------------------------------#
  Bucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    Properties:
      BucketName: !Sub ${PJPrefix}-${Environment}-bucket-${AWS::AccountId}
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-${Environment}-bucket-${AWS::AccountId}
  # ------------------------------------------------------------#
  # EmailIdentity
  # ------------------------------------------------------------#
  EmailIdentityforexample1:
    DependsOn: HostedZoneforexample1
    Type: AWS::SES::EmailIdentity
    Properties: 
      EmailIdentity: !Ref DomainNameforexample1

  EmailIdentityforexample2:
    DependsOn: HostedZoneforexample2
    Type: AWS::SES::EmailIdentity
    Properties: 
      EmailIdentity: !Ref DomainNameforexample2

  EmailIdentityforexample3:
    DependsOn: HostedZoneforexample3
    Type: AWS::SES::EmailIdentity
    Properties: 
      EmailIdentity: !Ref DomainNameforexample3
  # ------------------------------------------------------------#
  # Certificate
  # ------------------------------------------------------------#
  Certificateforexample1:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref DomainNameforexample1
      DomainValidationOptions:
        - DomainName: !Ref DomainNameforexample1
          HostedZoneId: !Ref HostedZoneforexample1
      SubjectAlternativeNames:
        - !Sub "*.${DomainNameforexample1}"
      ValidationMethod: DNS

  Certificateforexample2:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref DomainNameforexample2
      DomainValidationOptions:
        - DomainName: !Ref DomainNameforexample2
          HostedZoneId: !Ref HostedZoneforexample2
      SubjectAlternativeNames:
        - !Sub "*.${DomainNameforexample2}"
      ValidationMethod: DNS

  Certificateforexample3:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref DomainNameforexample3
      DomainValidationOptions:
        - DomainName: !Ref DomainNameforexample3
          HostedZoneId: !Ref HostedZoneforexample3
      SubjectAlternativeNames:
        - !Sub "*.${DomainNameforexample3}"
      ValidationMethod: DNS
  # ------------------------------------------------------------#
  # VPC
  # ------------------------------------------------------------#
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCidrBlock
      InstanceTenancy: default
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-${Environment}-VPC
  # ------------------------------------------------------------#
  # InternetGateway
  # ------------------------------------------------------------#
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties: 
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-${Environment}-InternetGateway
  # ------------------------------------------------------------#
  # VPCGatewayAttachment
  # ------------------------------------------------------------#
# アタッチメント
  VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties: 
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC
  # ------------------------------------------------------------#
  # Subnet
  # ------------------------------------------------------------#
# Public
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Ref PublicSubnet1CidrBlock
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs ]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-${Environment}-PublicSubnet1

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Ref PublicSubnet2CidrBlock
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 1, !GetAZs ]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-${Environment}-PublicSubnet2

# Protected
  ProtectedSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Ref ProtectedSubnet1CidrBlock
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs ]
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-${Environment}-ProtectedSubnet1

  ProtectedSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Ref ProtectedSubnet2CidrBlock
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 1, !GetAZs ]
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-${Environment}-ProtectedSubnet2

  # ------------------------------------------------------------#
  # RouteTable
  # ------------------------------------------------------------#
# Public
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-${Environment}-PublicRouteTable

# Protected
  ProtectedRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-${Environment}-ProtectedRouteTable1

  ProtectedRouteTable2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-${Environment}-ProtectedRouteTable2
  # ------------------------------------------------------------#
  # Route
  # ------------------------------------------------------------#
# Public
  PublicRoute:
    DependsOn: 
      - InternetGateway
      - VPCGatewayAttachment
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

# Protected
  ProtectedRoute1: 
    DependsOn: 
      - InternetGateway
      - VPCGatewayAttachment
    Type: AWS::EC2::Route
    Properties: 
      RouteTableId: !Ref ProtectedRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NATGateway1

  ProtectedRoute2: 
    DependsOn: 
      - InternetGateway
      - VPCGatewayAttachment
    Type: AWS::EC2::Route
    Properties: 
      RouteTableId: !Ref ProtectedRouteTable2 
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NATGateway2
  # ------------------------------------------------------------#
  # SubnetRouteTableAssociation
  # ------------------------------------------------------------#
# Public
  PublicSubnet1Assoc:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet1
      RouteTableId: !Ref PublicRouteTable

  PublicSubnet2Assoc:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet2
      RouteTableId: !Ref PublicRouteTable

# Protected
  ProtectedSubnet1Assoc: 
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: 
      SubnetId: !Ref ProtectedSubnet1
      RouteTableId: !Ref ProtectedRouteTable1

  ProtectedSubnet2Assoc: 
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: 
      SubnetId: !Ref ProtectedSubnet2
      RouteTableId: !Ref ProtectedRouteTable2
  # ------------------------------------------------------------#
  # NatGateway
  # ------------------------------------------------------------#
  NATGateway1:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt EIPforNATGAteway1.AllocationId 
      SubnetId: !Ref PublicSubnet1
      Tags: 
        - Key: Name
          Value: !Sub ${PJPrefix}-${Environment}-NATGateway1

  NATGateway2: 
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt EIPforNATGateway2.AllocationId
      SubnetId: !Ref PublicSubnet2
      Tags: 
        - Key: Name
          Value: !Sub ${PJPrefix}-${Environment}-NATGateway2
  # ------------------------------------------------------------#
  # VPCEndpoint
  # ------------------------------------------------------------#
  VPCESSM:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref VPCESecurityGroup
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
      SubnetIds:
      - !Ref ProtectedSubnet1
      - !Ref ProtectedSubnet2
      VpcEndpointType: Interface
      VpcId: !Ref VPC

  VPCESSMMessages:  
    Type: AWS::EC2::VPCEndpoint
    Properties:
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref VPCESecurityGroup
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
      SubnetIds:
      - !Ref ProtectedSubnet1
      - !Ref ProtectedSubnet2
      VpcEndpointType: Interface
      VpcId: !Ref VPC 

  VPCEEC2Messages:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref VPCESecurityGroup
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2messages
      SubnetIds:
      - !Ref ProtectedSubnet1
      - !Ref ProtectedSubnet2
      VpcEndpointType: Interface
      VpcId: !Ref VPC

  VPCES3:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      RouteTableIds:
        - !Ref PublicRouteTable
        - !Ref ProtectedRouteTable1
        - !Ref ProtectedRouteTable2
      ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
      VpcEndpointType: Gateway
      VpcId: !Ref VPC
  # ------------------------------------------------------------#
  # EIP
  # ------------------------------------------------------------#
  EIPforNATGAteway1:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-${Environment}-EIPforNATGAteway1

  EIPforNATGateway2:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-${Environment}-EIPforNATGateway2
  # ------------------------------------------------------------#
  # LoadBalancer
  # ------------------------------------------------------------#
  LoadBalancer:
    DependsOn: 
      - InternetGateway
      - VPCGatewayAttachment
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties: 
      Name: !Sub ${PJPrefix}-${Environment}-LoadBalancer
      Scheme: internet-facing
      Type: application
      LoadBalancerAttributes: 
        - Key: deletion_protection.enabled
          Value: false
        - Key: idle_timeout.timeout_seconds
          Value: 60
      SecurityGroups:
        - !Ref LoadBalancerSecurityGroup
      Subnets: 
        - !Ref PublicSubnet1
        - !Ref PublicSubnet2
      Tags: 
        - Key: Name
          Value: !Sub ${PJPrefix}-${Environment}-LoadBalancer
  # ------------------------------------------------------------#
  # Listener
  # ------------------------------------------------------------#
  ListenerHTTP: 
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      Port: 80
      Protocol: HTTP
      DefaultActions:
        - Type: redirect
          RedirectConfig:
            Host: "#{host}"
            Path: "/#{path}"
            Port: 443
            Protocol: "HTTPS"
            Query: "#{query}"
            StatusCode: HTTP_301
      LoadBalancerArn: !Ref LoadBalancer

  ListenerHTTPSforexample1:
    DependsOn: Certificateforexample1
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties: 
      Port: 443
      Protocol: HTTPS
      Certificates:
        - CertificateArn: !Ref  Certificateforexample1
      DefaultActions:
        - TargetGroupArn: !Ref TargetGroup
          Type: forward
      LoadBalancerArn: !Ref LoadBalancer
  # ------------------------------------------------------------#
  # ListenerCertificate
  # ------------------------------------------------------------#
  ListenerCertificatesforexample2:
    DependsOn: Certificateforexample2
    Type: AWS::ElasticLoadBalancingV2::ListenerCertificate
    Properties:
      Certificates:
        - CertificateArn: !Ref Certificateforexample2
      ListenerArn: !Ref ListenerHTTPSforexample1

  ListenerCertificatesforexample3:
    DependsOn: Certificateforexample3
    Type: AWS::ElasticLoadBalancingV2::ListenerCertificate
    Properties:
      Certificates:
        - CertificateArn: !Ref Certificateforexample3
      ListenerArn: !Ref ListenerHTTPSforexample1
  # ------------------------------------------------------------#
  # TargetGroup
  # ------------------------------------------------------------#
  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties: 
      VpcId: !Ref VPC
      Name: !Sub ${PJPrefix}-${Environment}-TargetGroup
      Protocol: HTTPS
      ProtocolVersion: HTTP1
      Port: 443
      HealthCheckEnabled: true
      HealthCheckProtocol: HTTPS
      HealthCheckPath: /
      HealthCheckPort: traffic-port
      HealthyThresholdCount: 5
      UnhealthyThresholdCount: 2
      HealthCheckTimeoutSeconds: 5
      HealthCheckIntervalSeconds: 30
      IpAddressType: ipv4
      Matcher: 
        HttpCode: 200
      Tags: 
        - Key: Name
          Value: !Sub ${PJPrefix}-${Environment}-TargetGroup
  # ------------------------------------------------------------#
  # AutoScalingGroup
  # ------------------------------------------------------------#
  AutoScalingGroupforWebServer:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      AutoScalingGroupName: !Sub ${PJPrefix}-${Environment}-AutoScalingGroupforWebServer
      VPCZoneIdentifier:
        - !Ref ProtectedSubnet1
        - !Ref ProtectedSubnet2
      LaunchTemplate:
        LaunchTemplateId: !Ref LaunchTemplateforWebServer
        Version: !GetAtt LaunchTemplateforWebServer.LatestVersionNumber
      TargetGroupARNs:
        - !Ref TargetGroup
      DesiredCapacity: !Ref DesiredCapacity
      MinSize: !Ref MinSize
      MaxSize: !Ref MaxSize
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-${Environment}-EC2
          PropagateAtLaunch: true
  # ------------------------------------------------------------#
  # LaunchTemplate
  # ------------------------------------------------------------#
  LaunchTemplateforWebServer:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: !Sub ${PJPrefix}-${Environment}-LaunchTemplateforWebServer
      LaunchTemplateData:
        BlockDeviceMappings:
          - DeviceName: /dev/sda1
            Ebs:
              VolumeType: gp3
              VolumeSize: 30
              DeleteOnTermination: true
        TagSpecifications:
        - ResourceType: instance
          Tags:
          - Key: Name
            Value: !Sub ${PJPrefix}-${Environment}-Instance
        KeyName: !Ref KeyPair
        ImageId: !Ref ImageId
        InstanceType:  !Ref InstanceType
        IamInstanceProfile:
          Arn: !GetAtt EC2InstanceProfile.Arn
        NetworkInterfaces: 
        - AssociatePublicIpAddress: false
          DeviceIndex: 0
          Groups:
            - !Ref EC2SecurityGroup
  # ------------------------------------------------------------#
  # ScalingPolicy
  # ------------------------------------------------------------#
  ScalingPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AutoScalingGroupName: !Ref AutoScalingGroupforWebServer
      EstimatedInstanceWarmup: 120
      PolicyType: TargetTrackingScaling
      TargetTrackingConfiguration:
        PredefinedMetricSpecification:
          PredefinedMetricType: ASGAverageCPUUtilization
        TargetValue: !Ref TargetValue
  # ------------------------------------------------------------#
  # KeyPair
  # ------------------------------------------------------------#
  KeyPair:
    Type: AWS::EC2::KeyPair
    Properties: 
      KeyFormat: pem
      KeyName: !Sub ${PJPrefix}-${Environment}-KeyPair
      KeyType: rsa
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-${Environment}-KeyPair
  # ------------------------------------------------------------#
  # SecurityGroup
  # ------------------------------------------------------------#
  VPCESecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VPC
      GroupName: !Sub ${PJPrefix}-${Environment}-sg-vpcendpoint
      GroupDescription: for VPCE for Session Manager and More.
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-${Environment}-SecurityGroup-VPCE

# Ingress
  VPCESecurityGroupIngressEC2SecurityGroup443:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      Description: Allow port 443 access from EC2.
      FromPort: 443
      ToPort: 443
      IpProtocol: tcp
      SourceSecurityGroupId: !Ref EC2SecurityGroup
      GroupId: !GetAtt VPCESecurityGroup.GroupId

  VPCESecurityGroupIngressfromEC2SecurityGroup587:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      Description: Allow port 587 access from EC2.
      FromPort: 587
      ToPort: 587
      IpProtocol: tcp
      SourceSecurityGroupId: !Ref EC2SecurityGroup
      GroupId: !GetAtt VPCESecurityGroup.GroupId
#≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
  LoadBalancerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VPC
      GroupName: !Sub ${PJPrefix}-${Environment}-SecurityGroup-LoadBalancer
      GroupDescription: for LoadBalancer
      Tags: 
        - Key: Name
          Value: !Sub ${PJPrefix}-${Environment}-SecurityGroup-LoadBalancer

# Ingress
  LoadBalancerSecurityGroupIngressfromInternet80: # これがないと80->443へリダイレクトするリスナーが上手くいかなかった為
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      Description: Allow port 80 access from Any.
      FromPort: 80
      ToPort: 80
      IpProtocol: tcp
      CidrIp: 0.0.0.0/0
      GroupId: !GetAtt LoadBalancerSecurityGroup.GroupId

  LoadBalancerSecurityGroupIngressfromInternet443:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      Description: Allow port 443 access from Any.
      FromPort: 443
      ToPort: 443
      IpProtocol: tcp
      CidrIp: 0.0.0.0/0
      GroupId: !GetAtt LoadBalancerSecurityGroup.GroupId
#≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
  EC2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VPC
      GroupName: !Sub ${PJPrefix}-${Environment}-SecurityGroup-EC2
      GroupDescription: for EC2
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-${Environment}-SecurityGroup-EC2

  EC2SecurityGroupIngressfromLoadBalancerSecurityGroup443:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      Description: Allow port 443 access from LoadBalancer.
      FromPort: 443
      ToPort: 443
      IpProtocol: tcp
      SourceSecurityGroupId: !GetAtt LoadBalancerSecurityGroup.GroupId
      GroupId: !GetAtt EC2SecurityGroup.GroupId



方針と補足

とにかく時間がない為、一旦「問題なく動く」を目指し、その後ベストプラクティスに近づけていく事にする。

・本来は最低限CloudFrontは作成したかったが相談の結果、期待する動作にならない場合に試行錯誤する時間がないので一旦後回しにする事に。
・ALB→EC2間の通信は80でもよかったのが、SSLの切れていたサイトについてオレオレ証明書を発行し443で通信。
・EC2権限に一旦フルを与えている点・バケットポリシーなどの制限は今後追加予定。
・Autoscalingにした兼ね合い、インスタンス内にメールやDBがあると、スケール後と差分が出てしまう為、顧客とのやりとりに利用するメールはGmailに変更。RDSPostgresqlは検討したが、格納しているデータは二股でSalesforceとメールでも飛ぶようになっている為、運用の調整後廃止。
・メールはSESを利用して送信。VPCEのIngressルールには25ではなく587を許可。(※参考OP25B)
・インスタンス内にルートドメインアクセスをwwwでリダイレクトさせるプログラムが存在するが、削除している時間はない為、ありきでACMを設定。80でのアクセスは443にリスナーでリダイレクト。
・S3は作業用で作成しているが、作業者がS3→EC2の二段階作業が面倒になる事等を想定し結局キーペアも作成。
・EC2はサイズ検討の結果、m5.largeでスタート。AmiはPHP7.4のサポートの兼ね合いでAlmaLinux X86_64をサブスクライブし、ミドルウェアやコンテンツを設定したものを事前作成し利用。


課題と対処

EC2はスケールさせるか

スケールさせるのであれば、インスタンス内のメールとDBはインスタンス間の差分になり、期待する動作にならない。外出しか廃止が必要。

DB→運用を調整し廃止
メールGmailに変更。(メール送信機能はSESと絡めて残す。)

最初はコスト面を考え、ALBも無しで現在のように障害発生時・CPU負荷があがった際などアラームを設定したり運用監視会社に再起動のみ依頼して後は手動、或いは以下の記事ようにする事も考えましたが、結果やめました。

https://qiita.com/naotinn74/items/3b2ff7bfd1c5a659d387


PHPのVerが5である事が発覚、これを機に7.4に変更する事に

それに対応して、ベースイメージはPHP7のサポートが2029年まであるらしいAlmaLinux8系が良いのではとの話になり、最初にarmをサブスクリプションするもphpのインストールで以下エラーが発生。

Error: Failed to download metadata for repo 'remi-modular': Cannot prepare internal mirrorlist: Status code: 403 for http://cdn.remirepo.net/enterprise/8/modular/aarch64/mirror

調べてみるとremiは現在armに対応していない
https://blog.remirepo.net/pages/Config-en
とのことでX86-64をサブスクライブし直し事なきを得た。


SES周りで経験した事

MXレコードと、TXTでSPFレコードを作成
CustomMailFromDomainは利用せず

SESで検証済みIDを登録するがEasyDKIMの認証が最大72時間かかるとの記述あり。
いつ終わるかわからないのは日中広告を打っている兼ね合い許容出来ないと思っていたが、結果6時間程度で3ドメイン全てが検証済みになった。

ここを短時間で検証完了にさせるには何か工夫で解決出来る方法などあるかが気になった。


作業者アカウントの作成・インスタンスへの接続方法

直前に発表されたEC2ConnectEndPointを利用したいと思ったが、複数サブネットに対応していない為大人しくSSMセッションマネージャーでVPCE×3を生やす事に。

User・Groupの作成、
・AmazonSSMFullAccess
・AmazonEC2FullAccess
の付与し、ローカルにAWS CLI v2とSession Manager pluginをインストールしてもらった上、

aws ssm start-session --target [インスタンスID]

で接続してもらった。

当初はEC2のAMIにもCLIを入れてS3経由でオブジェクトをcpして作業していただいていたが、結果ゆくゆくを考えてキーペア作成もする事に。

キーペアはCloudformationで作成した為、シークレットキーはSSMパラメータストアに格納された。
https://qiita.com/tsukamoto/items/1e0f3c8ecf4cba5cf485
値をコピーして自分でテキストエディタを使い.pemファイルを作成という流れ。


お名前.comへドメインを移管

Authcode というものを初めて知る。

話の流れで既に別のレジストラからお名前.comにドメインを移管してしまったが、確かRoute53だけでもいけたのではとAWSの技術者様から教えていただいた。

しかし、お名前.comからAmazon Route 53へドメインを移管するには

移行対象のドメインを現在のドメインレジストラに登録してから、少なくとも60日経っていること

との事。色々見ましたが、優先順位が低い為一旦見送りとしました。

ちなみに「指定期間内にWhois情報の登録をしてください」というメールが別の部の人間に届いていたのに気づかず、お名前.comでいうIcannHoldがかかり、1時間程度サイトに繋がらなくなるという経験もしました。(修正後すぐに伝播してよかったですが一瞬嫌な汗をかきました。)


国外IPを全てdenyしてしまうとGoogleやYahooの広告のボットに影響するかも

WAFのBotControleはよしなにやってくれるもののようだが、国外IPを遮断まですると問題があるか。

[参考] BotControl
https://dev.classmethod.jp/articles/aws-bot-control/

[参考] 海外IP遮断
https://www.netassist.ne.jp/techblog/25475/

一旦BotControlのみを設定したが、読み込んで問題がなさそうとわかれば追加していく事にした。


運用保守会社を比較・AWSのSAさんやパートナー企業に相談

どの単位で費用が発生するか等説明を受け、社内稟議を通し契約まで行いました。
詳細は割愛しますがこれらの流れや仕組みも勉強になりました。

同時にAWSのSAさんと技術者の方と三者でアーキテクチャについて相談。
リフト戦略、クラウドフロントを追加するメリットのおさらい、新しい世代のM系のインスタンスの検討について、WAFとRoute53の地理的なアクセス制御の細かい違い、CICDにしていく事、どこまでやるか、VPCEを普段停止しておくような節約は一般的か など質問し、アドバイスいただけた。


終わりに

備忘録+箇条書きで全く完成された内容ではありませんでしたが、ここからどうベストプラクティスなものに近づけていけるかという出発地点として書きました。

一旦、今のところは期間内に移動出来、正常動作している事に安堵していますが、「これってこうじゃない?」的なコメントをいただければとても嬉しいです。

お付き合いいただき有難うございました。


その他勉強になった技術記事

https://dev.classmethod.jp/articles/tsnote-alb-ip-001/
https://www.netassist.ne.jp/techblog/13280/
https://rurukblog.com/post/ssh-fingerprint/
https://qiita.com/taishin/items/25d91e1037d1bc80fb39
https://dev.classmethod.jp/articles/ec2-using-ami/
https://dev.classmethod.jp/articles/tsnote-can-not-boot-instance-survey/

Discussion