CloudFormationでEC2を自動構築&nginxのセットアップをする

スポンサーリンク
ハンズオン
スポンサーリンク

はじめに

クラウド環境でインフラを自動構築する方法として、AnsibleやTerraformなどのツールがよく知られていますが、AWSにはCloudFormationというクラウドネイティブな自動化手段が用意されています。

CloudFormationでサービスの配備をするのはイメージが付く方もいると思いますが、CloudFormation内でセットアップスクリプトを定義して、起動したEC2でスクリプトを実行することができます。

今回は「CloudFormationでEC2を自動構築&セットアップする」というテーマで、Amazon Linux 2023のEC2インスタンスを最小構成で配備し、nginxのインストール・セットアップやCloudWatch Agentの設定までを一括で行う方法を紹介します。

「AWSらしいインフラ自動化の方法ってどんな感じなんだろう?」そんな疑問を持つ方に向けて、テンプレートと実行例を交えながら、CloudFormationの活用方法をわかりやすく解説します。

事前準備

以下の環境を準備してください。

  • AWSアカウント
  • AWS CLIの実行環境(必要な権限設定を含む)
  • インターネットへのアクセスが可能なVPC

環境構成

CloudFormationで以下の環境を配備します。

テンプレート

配備するリソース一覧

配備するリソースは以下になります。

リソース 説明
IAMロール  EC2インスタンスにアタッチするIAMロールの作成
(CloudWatchやSSMの権限を付与)
インスタンスプロファイル IAMロールをEC2に渡すためのインスタンスプロファイル
セキュリティグループ アウトバウンドHTTPS、インバウンドHTTPを許可
EC2インスタンス 指定したVPC/サブネットにEC2を配備
AWS::CloudFormation::Initで初回実行用のスクリプトを定義
nginxのアクセスログとエラーログをCloudWatchログに転送するよう設定

テンプレートの説明

テンプレートは以下を利用します。

AWSTemplateFormatVersion: '2010-09-09'
Description: EC2 with CloudFormation Init (Amazon Linux 2023 + nginx + CloudWatch Logs)

Parameters:
  # VPC ID to launch the EC2 instance into
  VpcId:
    Type: AWS::EC2::VPC::Id
    Description: VPC ID to launch the EC2 instance into

  # Subnet ID within the specified VPC
  SubnetId:
    Type: AWS::EC2::Subnet::Id
    Description: Subnet ID within the specified VPC

  # SSH key pair name for logging into EC2 (must be pre-created)
  KeyName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: SSH Key for EC2

  # EC2 instance type (based on cost and performance)
  InstanceType:
    Type: String
    Default: t2.micro
    AllowedValues: [t2.micro, t3.micro, t3.small]
    Description: EC2 Instance Type

  # Log group name to be created in CloudWatch Logs
  LogGroupName:
    Type: String
    Default: ec2-cloudwatch-loggroup
    Description: CloudWatch Logs group name

Resources:
  # Create CloudWatch Logs log group
  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Ref LogGroupName
      RetentionInDays: 7

  # Create IAM role to attach to EC2 (grants CloudWatch Agent and SSM permissions)
  InstanceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: [ec2.amazonaws.com]
            Action: ['sts:AssumeRole']
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

  # Create instance profile to pass the IAM role to EC2
  InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles: [!Ref InstanceRole]
      Path: /

  # Create security group (allow HTTP inbound and HTTPS outbound)
  EC2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow HTTP inbound and HTTPS outbound
      VpcId: !Ref VpcId
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
      SecurityGroupEgress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0

  # Create EC2 instance
  EC2Instance:
    Type: AWS::EC2::Instance
    Metadata:
      AWS::CloudFormation::Init:
        config:
          packages:
            yum:
              nginx: []
              amazon-cloudwatch-agent: []
          files:
            # AWS CLI config file
            /root/.aws/config:
              content: !Sub |
                [default]
                region = ${AWS::Region}
                output = json
              mode: '000600'
              owner: root
              group: root
            # CloudWatch Agent config file
            /opt/aws/amazon-cloudwatch-agent/bin/config.json:
              content: !Sub |
                {
                  "logs": {
                    "logs_collected": {
                      "files": {
                        "collect_list": [
                          {
                            "file_path": "/var/log/nginx/access.log",
                            "log_group_name": "${LogGroupName}",
                            "log_stream_name": "nginx-access-{instance_id}",
                            "timestamp_format": "%d/%b/%Y:%H:%M:%S %z"
                          },
                          {
                            "file_path": "/var/log/nginx/error.log",
                            "log_group_name": "${LogGroupName}",
                            "log_stream_name": "nginx-error-{instance_id}",
                            "timestamp_format": "%Y/%m/%d %H:%M:%S"
                          }
                        ]
                      }
                    }
                  }
                }
              mode: '000644'
              owner: root
              group: root
            # Script to list S3 buckets and log the output
            /usr/local/bin/setup.sh:
              content: !Sub |
                #!/bin/bash
                echo "[`date '+%Y-%m-%d %H:%M:%S'`] Listing S3 buckets" >> /var/log/script.log
                aws s3 ls >> /var/log/script.log 2>&1
                echo "[`date '+%Y-%m-%d %H:%M:%S'`] Done" >> /var/log/script.log
              mode: '000755'
              owner: root
              group: root
          commands:
            # Start CloudWatch Agent
            01_start_cw_agent:
              command: |
                /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
                  -a fetch-config \
                  -m ec2 \
                  -c file:/opt/aws/amazon-cloudwatch-agent/bin/config.json \
                  -s
            # Run setup script
            02_run_script:
              command: /usr/local/bin/setup.sh
          services:
            sysvinit:
              nginx:
                enabled: true
                ensureRunning: true
    Properties:
      InstanceType: !Ref InstanceType
      KeyName: !Ref KeyName
      IamInstanceProfile: !Ref InstanceProfile
      ImageId: !Sub '{{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64}}'
      SubnetId: !Ref SubnetId
      SecurityGroupIds:
        - !Ref EC2SecurityGroup
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          dnf update -y
          /opt/aws/bin/cfn-init -v \
            --stack ${AWS::StackName} \
            --resource EC2Instance \
            --region ${AWS::Region}

Outputs:
  InstanceId:
    Value: !Ref EC2Instance
    Description: ID of the created EC2 instance

  PublicDNS:
    Value: !GetAtt EC2Instance.PublicDnsName
    Description: Public DNS name of the EC2 instance

テンプレートの実行

テンプレートをローカルに保存し、以下のAWS CLIコマンドでCloudFormationを実行します。
テンプレートファイルの名前は「ec2-init-template.yaml」としてください。

aws cloudformation deploy --template-file ec2-init-template.yaml --stack-name MyEc2WithInitStack --capabilities CAPABILITY_NAMED_IAM --parameter-overrides VpcId=【VPCID】 SubnetId=【サブネットID】 KeyName=【キーペア名】 InstanceType=t2.micro LogGroupName=【ロググループ名】

以下でOutputsに指定したインスタンス情報を出力します。

aws cloudformation describe-stacks --stack-name MyEc2WithInitStack --query "Stacks[0].Outputs" --output table

-----------------------------------------------------------------------------------------------------------------
|                                                DescribeStacks                                                 |
+--------------------------------------+-------------+----------------------------------------------------------+
|              Description             |  OutputKey  |                       OutputValue                        |
+--------------------------------------+-------------+----------------------------------------------------------+
|  ID of the created EC2 instance      |  InstanceId |  i-0123456789abcdef0                                     |
|  Public DNS name of the EC2 instance |  PublicDNS  |  ec2-12-34-56-78.xx-xxxx-x.compute.amazonaws.com         |
+--------------------------------------+-------------+----------------------------------------------------------+

動作確認

OutputsのPublicDNSにアクセスします。アクセス確認はブラウザを利用します。
※curlでなどコマンドで確認してもいいです。

nginxの初期画面が表示されます。

CloudWatchログでアクセスログ/エラーログを確認できます。

環境の削除

以下のコマンドで配備したリソースを一括削除ができます。

aws cloudformation delete-stack --stack-name MyEc2WithInitStack

削除後、以下のコマンドで削除状況を確認できます。

aws cloudformation describe-stacks --stack-name MyEc2WithInitStack

削除ができていると以下のように指定したスタックIDのスタックが存在しない旨のエラーメッセージが出力されます。

An error occurred (ValidationError) when calling the DescribeStacks operation: Stack with id MyEc2WithInitStack does not exist

※StackStatusが「DELETE_IN_PROGRESS」である場合は削除中です。しばらくたってから再度確認してください。

まとめ

本記事ではCloudFormationを使い、EC2の配備+コマンド実行で、nginxのインストール/CloudWatchログにログの転送設定までを行いました。

この内容を応用することで、EC2の配備と他のミドルウェアのセットアップまで一括で可能となります。

補足として、OS再起動など、高度で複雑なセットアップについては本記事の内容は向いていません。
高度で複雑なセットアップには、「CodeDeploy」、「Systems Manager Automation」、「SSM Run Command」を利用することをおすすめします。

より実践的にAWSを学びたい方には、Udemyのオンラインコースの受講がおすすめです。
基礎からサービスごとの使い方、認定資格対策まで、幅広い内容がそろっており、ご自身のレベルや目的に合ったコースがきっと見つかります。
スキルアップやキャリアのステップアップを目指す方は、ぜひチェックしてみてください。
ITとソフトウェアの人気オンラインコース

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