LoginSignup
2
3

More than 1 year has passed since last update.

AWS CloudFormationのテンプレートをLayer別に作成してクロススタック参照を作ってみた。その3 [ Application-Layer編 ]

Last updated at Posted at 2022-05-27

前回のまでのおさらい

前回のSeculity-Layer編では通信のポートを制御するセキュリティグループを作成した。最後となる今回はEC2など主要な構成をテンプレートで構築していく。

概要

テンプレートと言われる構築環境をコード化させたファイルに記載し、スタックと呼ばれる作業を行うことでGUI上で作成した状況と同じ環境が自動的に作成される。1つのテンプレートに全ての環境を記載することも可能だが、AWSからは各レイヤー別に分けることがベストプラクティスと言われている。

今回は以下3つのLayerに分けて作成する。

  • Network Layer  
  • Security Layer
  • Application Layer   ←今回はここ!

クロススタック参照

各レイヤー別にテンプレートを作成し1つの環境構築を行うためには、テンプレート間の互換性が必要になる。そのためにはクロススタック参照を使用して他レイヤーの値を参照できるようにコードに'組込関数'と呼ばれる関数を活用していく作業がポイント。

構成図

ここまで進めて完成となる。
Application.drawio.png

Application-Layer

このレイヤーでEC2、RDS、ELBのテンプレートを作成していく。
VPC関連を作成した時と同様に、Parametersで引用する数値を予め設定しておく

2022年5月に新機能として、CloudFormationでkeypairを作成できるようになった。
今回は旧式のやり方で進めているが、自身でも準備が出来たら投稿予定!
参考記事 : CloudFormationでキーペアの作成/削除が可能になった

Parameters

Application-Layer.yaml
AWSTemplateFormatVersion: 2010-09-09
Description: Creation of EC2 Related(for EC2,RDS,ALB)

  
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Live03 Prefix
        Parameters:
          - PJPrefix
      - Label:
          default: EC2 Configuration
        Parameters:
          - KeyName
          - EC2InstanceName
          - EC2InstanceAMI
          - EC2InstanceType
          - EC2InstanceVolumeType
          - EC2InstanceVolumeSize
      - Label:
          default: RDS Configuration
        Parameters:
          - DBInstanceName          #スタックの名前
          - MySQLMajorVersion       #MysqlのメジャーVer
          - MySQLMinorVersion       #MysqlのマイナーVer
          - DBInstanceClass         #インスタンスクラス
          - DBInstanceStorageSize   #ストレージサイズ
          - DBInstanceStorageType   #ストレージタイプ
          - DBName                  #データベースの名前
          - DBMasterUserName        #マスターユーザー名
          - DBPassword              #パスワード
      - Label:
          default: InternetALB Configuration
        Parameters:
          - InternetALBName

    ParameterLabels:
    # for EC2
      InternetALBName:
        default: InternetALBName
      KeyName:
        default: KeyPairName
      EC2InstanceName:
        default: EC2 Name
      EC2InstanceAMI:
        default: EC2 AMI
      EC2InstanceType:
        default: EC2 InstanceType
      EC2InstanceVolumeType:
        default: EC2 VolumeType
      EC2InstanceVolumeSize:
        default: EC2 VolumeSize

    # for RDS
      DBInstanceName:
        default: DBInstanceName
      MySQLMajorVersion:
        default: MySQLMajorVersion
      MySQLMinorVersion:
        default: MySQLMinorVersion
      DBInstanceClass:
        default: DBInstanceClass
      DBInstanceStorageSize:
        default: DBInstanceStorageSize
      DBInstanceStorageType:
        default: DBInstanceStorageType
      DBName:
        default: DBName
      DBMasterUserName:
        default: DBUserName
      DBPassword:
        default: DBPassword

#---------------------------------------------------
# input Parameters
#---------------------------------------------------   
Parameters:
  PJPrefix:
    Type: String
    Default: live03

# EC2
  KeyName:
    Description: input EC2 Keyname
    Type: AWS::EC2::KeyPair::KeyName
    Default: live03-keypair

  EC2InstanceName:
    Type: String
    Default: web

  EC2InstanceAMI:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2

  EC2InstanceType:
    Type: String
    Default: t2.micro

  EC2InstanceVolumeType:
    Type: String
    Default: gp2
  
  EC2InstanceVolumeSize:
    Type: String
    Default: "30"

# RDS
  DBInstanceName:
    Type: String
    Default: RDS

  MySQLMajorVersion:
    Type: String
    Default: "5.7"
    AllowedValues: [ "5.5", "5.6", "5.7" ]

  MySQLMinorVersion:
    Type: String
    Default: "22"

  DBInstanceClass:
    Type: String
    Default: db.t3.micro

  DBInstanceStorageSize:
    Type: String
    Default: "20"

  DBInstanceStorageType:
    Type: String
    Default: gp2

  DBName:
    Type: String
    Default: db

  DBMasterUserName:
    Type: String
    Default: dbuser  # 自身の設定した値を挿入
    NoEcho: true
    MinLength: 1
    MaxLength: 16
    AllowedPattern: "[a-zA-Z][a-zA-Z0-9]*"
    ConstraintDescription: must begin with a letter and contain only alphanumeric characters.

  DBPassword: 
    Default: dbpassword  # 自身の設定した値を挿入
    NoEcho: true
    Type: String
    MinLength: 8
    MaxLength: 41
    AllowedPattern: "[a-zA-Z0-9]*"
    ConstraintDescription: must contain only alphanumeric characters.

# ALB
  InternetALBName:
    Type: String
    Default: Cfn

# S3
  S3BucketName:
    Type: String
    Default: cfn202705

Resources:
# ------------------------------------------------------------#
#  IAM Role for EC2
# ------------------------------------------------------------# 
  EC2IAMRole: 
    Type: "AWS::IAM::Role"
    Properties: 
      RoleName: !Sub "${PJPrefix}-${EC2InstanceName}-role" 
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - Effect: Allow
            Principal: 
              Service: 
                - "ec2.amazonaws.com"
            Action: 
              - "sts:AssumeRole"
      Path: "/"
      ManagedPolicyArns: 
        - "arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM"
        - "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess"

  EC2InstanceProfile: 
    Type: "AWS::IAM::InstanceProfile"
    Properties: 
      Path: "/"
      Roles: 
        - Ref: EC2IAMRole
      InstanceProfileName: !Sub "${PJPrefix}-${EC2InstanceName}-profile"


EC2

ALBを使用しELB経由で負荷分散させる仕様にするためEC2は2台(1a /1c)作成する。
各IDはParametersから組込関数を用いて引用。

Application-Layer.yaml
Resources:
#---------------------------------------------------
# EC2の作成(public-subnet内)
#---------------------------------------------------
  CfnEC21a:
    Type: AWS::EC2::Instance
    Properties:
      SubnetId: !ImportValue nstack-PublicSubnet1a
      ImageId: !Ref EC2InstanceAMI
      InstanceType: !Ref EC2InstanceType
      KeyName: !Ref KeyName
      InstanceInitiatedShutdownBehavior: stop
      Tenancy: default
      BlockDeviceMappings: 
        - DeviceName: /dev/xvda
          Ebs:
            VolumeType: !Ref EC2InstanceVolumeType
            VolumeSize: !Ref EC2InstanceVolumeSize
      SecurityGroupIds:
        - !ImportValue sstack-Sg-WEB
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-${EC2InstanceName}-1a"
  
  CfnEC21c:
    Type: AWS::EC2::Instance
    Properties:
      SubnetId: !ImportValue nstack-PublicSubnet1c
      ImageId: !Ref EC2InstanceAMI
      InstanceType: !Ref EC2InstanceType
      KeyName: !Ref KeyName
      InstanceInitiatedShutdownBehavior: stop
      Tenancy: default
      BlockDeviceMappings: 
        - DeviceName: /dev/xvda
          Ebs:
            VolumeType: !Ref EC2InstanceVolumeType
            VolumeSize: !Ref EC2InstanceVolumeSize
      SecurityGroupIds:
        - !ImportValue sstack-Sg-WEB
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-${EC2InstanceName}-1c"

RDS

AZ-1aとAZ-1cに設置しMulti-AZ構成とする。

Application-Layer.yaml
  CfnRDS: 
    Type: "AWS::RDS::DBInstance"
    Properties:       
      Engine: MySQL
      EngineVersion: !Sub "${MySQLMajorVersion}.${MySQLMinorVersion}"
      Port: 3306
      DBInstanceClass: !Ref DBInstanceClass
      AllocatedStorage: !Ref DBInstanceStorageSize
      StorageType: !Ref DBInstanceStorageType
      DBName: !Ref DBName
      MasterUsername: !Ref DBMasterUserName
      MasterUserPassword: !Ref DBPassword
      DBSubnetGroupName: !Ref DBSubnetGroup
      PubliclyAccessible: false
      PreferredBackupWindow: "18:00-18:30"
      PreferredMaintenanceWindow: "sat:19:00-sat:19:30"
      AutoMinorVersionUpgrade: false
      VPCSecurityGroups:
        - !ImportValue (スタック名)-Sg-RDS
      CopyTagsToSnapshot: true
      BackupRetentionPeriod: 7
      Tags: 
        - Key: "Name"
          Value: !Ref DBInstanceName
    DeletionPolicy: "Delete"
    
  # RDS SubnetGroupの作成
  DBSubnetGroup: 
    Type: "AWS::RDS::DBSubnetGroup"
    Properties: 
      DBSubnetGroupName: DBSubnetGroupName
      DBSubnetGroupDescription: create for RDS
      SubnetIds: 
        - !ImportValue (スタック名)-PrivateSubnet1a
        - !ImportValue (スタック名)-PrivateSubnet1c

ALB

SubnetはPublic1a/1cに設定し、最終的にNginx経由でロードバランサーに接続するので80番ポートを指定。

Application-Layer.yaml
  CfnALB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      IpAddressType: ipv4
      Name: CfnALB
      Scheme: internet-facing
      SecurityGroups:
        - !ImportValue (スタック名)-Sg-ALB
      Subnets:
        - !ImportValue (スタック名)-PublicSubnet1a
        - !ImportValue (スタック名)-PublicSubnet1c
      Tags:
        - Key: Name
          Value: CfnALB

  # ALBのターゲットグループの指定
  ALBTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: ALBTargetGroup
      Port: 80
      Protocol: HTTP
      Targets:
        - Id:
            Ref: CfnEC21a
          Port: 80
        - Id:
            Ref: CfnEC21c
          Port: 80
      VpcId: !ImportValue (スタック名)-VPCID

  # ALBのリスナーの指定
  ALBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn:
            !Ref ALBTargetGroup
      LoadBalancerArn:
        !Ref CfnALB
      Port: 80
      Protocol: HTTP

S3

PublicAccessBlockConfigurationの設定で全てtrueにするとパブリックアクセスが全てブロックされる。

Application-Layer.yaml
  SampleS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref S3BucketName
      AccessControl: Private
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True
      VersioningConfiguration:
        Status: Enabled

これで3つのレイヤーに分けて作成したテンプレートの作成が完了となる。

終わりに

慣れるまでは、テンプレート同士の整合性の調整やエラー解消にかなり手間取ってしまった。
ここからAWS CLIを使用してターミナルから操作、完了できれば尚よしだが、今は一旦ここをゴールとする。

参考にさせて頂いた記事

エラー事例

テンプレートを構築していく中で、多くのエラーに遭遇。
備忘録を兼ねて事象と解決方法をメモとして残しておく。

テンプレート作成を行う段階で遭遇したエラーと解決方法[主にcfn-lintで事前に検知]

[事象1] cfn-lint E1001: Top level template section VPCEndpoint is not valid
[解決法] 作成したコードの深さの設定ミス:論理IDなどを設定するTOPの位置の深さに設定ミスがあり。

[事象2] cfn-lint E0000: Duplicate resource found "ParameterLabels"
[解決法] コード内容が重複していると検出されるエラー。1つ削除して解消させる。

[事象3] cfn-lint E2015: Default should be allowed by AllowedPattern
[解決法] インプットパラメーターで設定している値の表記ミス、正しく表記できていない。

[事象4] cfn-lint E1012: Ref CfnEC2 not found as a resource or parameter
[解決法] インプットパラメーターで設定している値の表記ミス、正しく表記できていない。

[事象5] Unresolved resource dependencies [ALBSecurityGroup] in the Outputs block of the template
[解決法] !Ref関数で指定している引用元の論ID名に相違がある。

[事象6] No export named sstack-PrivateSubnet1a found. Rollback requested by user
[解決法] クロススタックで参照している値のtypoミス

[事象7] Bucket name should not contain uppercase characters
[解決法] Paraetersに記載しているS3のバケット名に大文字を使用していたため(大文字禁止)

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3