😆

CloudFormation道 ~Part2~

2021/08/01に公開

はじめに

CloudFormation道~Part1~では、CloudFormationの概要を解説しました。今回は簡単なインフラ構成を用いてCloudFormationのテンプレートの記載方法や、リソースの作成、変更、削除の解説をしていきます。

テンプレート

テンプレート形式

CloudFormationのテンプレートの記載形式として、JSONとYAML形式が使用することができます。私はテンプレートにコメントが記載できることなどを考慮してYAML形式をお勧めします。(JSONは一から記述するものではないと筆者は思っております。笑)

テンプレートの要素

テンプレートの雛形です。

---
AWSTemplateFormatVersion: "2010-09-09"
Description:
  String
Metadata:
  template metadata
Parameters:
  set of parameters
Rules:
  set of rules
Mappings:
  set of mappings
Conditions:
  set of conditions
Transform:
  set of transforms
Resources:
  set of resources
Outputs:
  set of outputs
  • AWSTemplateFormationVersion
    CloudFormationのテンプレートのバージョンを表します。最新テンプレートのバージョンは"2010-09-09"となっており、テンプレートの一番最初に記載します。
  • Description
    テンプレートの詳細情報や説明を記載します。主にどんなリソースを作成するのか簡単に記載することが多いです。
  • Metadata
    テンプレートに関する追加情報を記載します。私はAWS::CloudFormation::Interfaceといったメタデータキーを使用することで、テンプレート内のパラメータの順番を制御していました。
  • Parameters
    テンプレートで使用する変数や値をスタック作成前にユーザに入力を求めることで、カスタム値を利用することができます。入力したパラメータの値はテンプレート内で参照することができます。
  • Mappings
    Key:valueでマッピングされた入力データを定義します。アカウントごとやリージョンごとに使用する値を分けたい時に使用します。
  • Conditions
    条件名と条件判断内容を記載します。のちに記載するResourcesセクションで使用し、例えば本番環境のみ作成したいリソースがあった場合に、parametersで入力した環境情報を利用することで、本番環境のみリソースを作成することができます。
  • Transform
    サーバレスアプリケーションや定型コンテンツを挿入するための、マクロを指定します。AWS SAMでよく使用されるパラメータです。
  • Resources
    CloudFormationのテンプレートで必須のパラメータになっており、構築したいリソースについての情報を記載していきます。
  • Outputs
    スタック構築後にAWS CloudFormationから出力させたい(URLやDNS名など)値を記載していきます。

テンプレートの作成、スタックの作成/更新/削除

構築するインフラ構成図

今回CloudFormationで作成していく簡単なインフラ構成図です。VPC内にPublic subnetを構築し、public subnet内にEC2インスタンスを構築します。構築したEC2インスタンスにSSHでログインするまでをゴールとしたいので、EC2にアタッチするセキュリティグループを作成します。また、インターネットと通信するためにInternetGatawayを構築し、VPCにアタッチします。

構築するリソース

インフラ構成図から今回の構築に必要なリソースを洗い出します。

  • VPC
  • subnet
  • internetgateway
  • routetable
  • securitygroup
  • ec2
  • iam
    今回はこれらのリソースを「ネットワーク」、「セキュリティグループ」、「EC2」と分けてテンプレートを作成していきました。スタックの依存関係も上記の順番になるようにしています。

テンプレート作成

  • create-network-resouce.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: "Create VPC Subnet RouteTable InternetGateWay"

Mappings:
  NetWork:
    CidrBlock:
      VPC: "10.0.0.0/16"
      PublicSubnet1a: "10.0.0.0/24"
      InternetGateway: "0.0.0.0/0"
  Tag:
    Name:
      VPC: "MyVPC"
      PublicSubnet1a: "MyPublicSubnet1a"
      RouteTable: "MyRouteTable"
      InternetGateway: "MyInternetGateway"
  
Resources:
###################################################################
# VPC Counfiguration
###################################################################
  myVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !FindInMap [ NetWork, CidrBlock, VPC ]
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: !FindInMap [ Tag, Name, VPC ]

###################################################################
# Subnet Counfiguration
###################################################################
  PublicSubnet1a:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 0 
        - !GetAZs
          Ref: 'AWS::Region'
      VpcId: !Ref myVPC
      CidrBlock: !FindInMap [ NetWork, CidrBlock, PublicSubnet1a ]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !FindInMap [ Tag, Name, PublicSubnet1a ]

###################################################################
# Create InternetGateWay
###################################################################
  MyInternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !FindInMap [ Tag, Name, InternetGateway ]
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref myVPC
      InternetGatewayId: !Ref MyInternetGateway

###################################################################
# RouteTable Counfiguration
###################################################################
  # Routetable
  RouteTableforPublicSubnet:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref myVPC
      Tags:
        - Key: Name
          Value: !FindInMap [ Tag, Name, RouteTable ]

  PublicSubnet1aRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet1a
      RouteTableId: !Ref RouteTableforPublicSubnet

  # Routing rule
  Route01:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref RouteTableforPublicSubnet
      DestinationCidrBlock: !FindInMap [ NetWork, CidrBlock, InternetGateway ]
      GatewayId: !Ref MyInternetGateway

Outputs:
  MyVPCID:
    Description: "MyVPC LogicalID"
    Value: !Ref myVPC
    Export: 
      Name: !Sub ${AWS::StackName}-VPCID

  MyVPCCidrBlock:
    Description: "MyVPC CidrBlock"
    Value: !GetAtt myVPC.CidrBlock
    Export: 
      Name: !Sub ${AWS::StackName}-VPCCidrBlock

  PublicSubnet1aID:
    Description: "PublicSubnet 1a LogicalID"
    Value: !Ref PublicSubnet1a
    Export: 
      Name: !Sub ${AWS::StackName}-Pub1aID
  • create-security-group.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: "Create SecurityGroup"

Mappings:
  SecurityGroup:
    ForEC2:
      GroupName: "testsecuritygroup"
      GroupDescription: "Test Security Group"
  Tag:
    Name:
      SecurityGroup: "EC2SecurityGroup"
  
Resources:
###################################################################
# SecurityGroup Counfiguration
###################################################################
  EC2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !FindInMap [ SecurityGroup,  ForEC2, GroupName]
      GroupDescription: !FindInMap [ SecurityGroup,  ForEC2, GroupDescription]
      VpcId: !ImportValue create-network-resource-VPCID
      Tags:
        - Key: Name
          Value: !FindInMap [ Tag,  Name, SecurityGroup]

  SGBaseIngress01:
   Type: AWS::EC2::SecurityGroupIngress
   Properties:
      GroupId: !GetAtt EC2SecurityGroup.GroupId
      IpProtocol: tcp
      FromPort: 22
      ToPort: 22
      CidrIp: 0.0.0.0/0

Outputs:
  EC2SecurityGroupID:
    Description: "EC2SecutiryGroup LogicalID"
    Value: !Ref EC2SecurityGroup
    Export: 
      Name: !Sub ${AWS::StackName}-EC2SecurityGroup
  • create-ec2.yaml
AWSTemplateFormatVersion: "2010-09-09" 
Description: "Create EC2"

Parameters:
  InstanceTypeParameter:
    Type: String
    Default: t2.micro

Mappings:
  EC2:
    ap-northeast-1:
      ImageId: "ami-09ebacdc178ae23b7"
    KeyPair:
      Name: "instanceaccess"
  Tag:
    Name:
      EC2: "TestEC2"
  
Resources:
###################################################################
# EC2 Counfiguration
###################################################################
  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      KeyName: !FindInMap [ EC2, KeyPair, Name ]
      BlockDeviceMappings: 
      - DeviceName: /dev/xvda
        Ebs: 
          DeleteOnTermination: false
          VolumeType: gp2
          VolumeSize: 8
      DisableApiTermination: false
      ImageId: !FindInMap [ EC2, !Ref 'AWS::Region', ImageId ]
      InstanceType: !Ref InstanceTypeParameter
      Monitoring: false
      Tenancy: default
      NetworkInterfaces: 
        - AssociatePublicIpAddress: true
          DeleteOnTermination: true
          DeviceIndex: "0"
          SubnetId: !ImportValue create-network-resource-Pub1aID
          GroupSet:
            - !ImportValue create-secutiry-group-EC2SecurityGroup
      IamInstanceProfile: !Ref MyInstanceProfile
      Tags:
        - Key: Name
          Value: !FindInMap [ Tag, Name, EC2 ]

###################################################################
# IAM Counfiguration
###################################################################
  MyInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
        - !Ref EC2forIAMRole
    
  EC2forIAMRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
              - ec2.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Policies:
        - PolicyName: "root"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action: "*"
                Resource: "*"

Outputs:
  EC2InstanceID:
    Description: "EC2Instance LogicalID"
    Value: !Ref MyEC2Instance
    Export: 
      Name: !Sub ${AWS::StackName}-EC2ID

テンプレートファイルをS3に配置

テンプレートファイルを作成したら、S3に配置します。awscliからs3 cpコマンドを実行し、ローカルにあるテンプレートファイルをS3に転送します。

$ aws s3 cp create-ec2.yaml s3://my-devops/cloudformation/
upload: ./create-ec2.yaml to s3://my-devops/cloudformation/create-ec2.yaml
$ aws s3 cp create-network-resource.yaml s3://my-devops/cloudformation/
upload: ./create-network-resource.yaml to s3://my-devops/cloudformation/create-network-resource.yaml
$ aws s3 cp create-security-group.yaml s3://my-devops/cloudformation/
upload: ./create-security-group.yaml to s3://my-devops/cloudformation/create-security-group.yaml

スタックの作成

S3にテンプレートをアップロードできたら、今回はマネジメントコンソール上からスタックの作成を行います。

  • CloudFormationのマネジメントコンソールにアクセスし、スタックの作成をクリックします

  • 前提条件-テンプレートの準備は「テンプレートの準備完了」を選択します。テンプレートの指定は「Amazon S3 URL」を選択し、S3からテンプレートのオブジェクトURLをコピーアンドペーストします。

  • 作成するスタックの名前、パラメータを入力し、次へをクリックします。

  • タグはスタックのリソースに含めることができるタグを定義することができます。また、アクセス許可はスタックを作成する時にIAMロールを指定することで適切な権限でリソースの作成/変更/削除を行うことができます。今回は指定せず、IAMユーザの権限を利用します。スタックポリシー、ロールバック設定、通知オプション、スタックの作成オプションは設定せず、次へをクリックします。

  • 入力内容を確認したら、スタックの作成をクリックします。

  • スタックの作成が終了すると、テンプレートで指定したリソースが作成されます。

  • 出力タブには、テンプレート内でoutputで定義した項目が出力されています。出力されたエクスポート名はセキュリティグループのスタック作成や、EC2のスタック作成のテンプレートでインポートして利用することができます。

同じ容量でcreate-security-group.yamlとcreate-ec2.yamlのスタックを作成していきます。

SSHでサーバにアクセスする

EC2の作成が完了したら、EC2のマネジメントコンソール上から、パブリックIPv4アドレスを確認し、ローカルからSSHでログインします。

$ ssh -i ~/.ssh/instanceaccess.pem ec2-user@18.183.248.145
The authenticity of host '18.183.248.145 (18.183.248.145)' can't be established.
ECDSA key fingerprint is SHA256:d8C8qmLYykhwbrjpC2WvXb5Qe9LHLJd+A+K5UvjjxIM.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '18.183.248.145' (ECDSA) to the list of known hosts.

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
[ec2-user@ip-10-0-0-122 ~]$ 

無事作成したEC2インスタンスにログインすることができました。

変更セットの作成

今回は作成したEC2インスタンスのインスタンスタイプをt2.micro->t3.microにへ変更します。
作成したスタックを選択し、更新をクリックします。

現在のテンプレートを選択します。

instancetypeparameterをt2.micro->t3.microに変更します。

変更セットを確認して、OKなら更新をクリックする。

EC2インスタンスのマネジメントコンソールからインスタンスタイプがt3.microに変更されたことを確認する。

スタックの削除

削除するスタックを選択し、削除をクリックする。

終わりに

今回はCloudFormationのテンプレートの解説や、実際にスタックを作成/更新/削除する流れを説明しました。テンプレートも使いこなせるようになると、1ファイルで環境ごとに使いまわせたり、メンテナンス性が向上するのでたくさんテンプレートを書いてみてください。

Discussion