【CloudFormation】AWSマルチAZ3層アーキテクチャの構築_EC2

皆様、お世話になっております。鈴木と申します。
今回はAWS CloudFormationを使用してEC2を作成していきたいと思います。

1.目次

目次はこちら

2.要件

  • プライベートサブネットに配置
  • Amazon Linux2023を使用
  • EBSはgp3を使用
  • キーペアを付与
  • SSMより操作
  • Webサーバーとして機能させる(Apacheサーバー)
  • RDSへのログインを可能とする
  • ユーザーデータにてインストールやEFSへのマウントを行う

3.EC2について

EC2

EC2(Amazon Elastic Compute Cloud)は、Amazon Web Services(AWS)が提供するクラウドコンピューティングサービスで、仮想サーバー(インスタンス)を必要に応じて起動し、管理することができます。

  • 仮想サーバー:
    EC2では仮想サーバーを「インスタンス」と呼びます。これにより、物理サーバーに依存せずにスケーラブルなコンピューティングリソースを提供します。

  • オンデマンドスケーリング:
    必要に応じてインスタンスを追加・削除でき、リソースのスケーリングが簡単です。

  • 柔軟なインスタンス:
    異なる種類のインスタンスがあり、計算能力、メモリ、ストレージのニーズに応じて選択できます。

  • 価格モデル:

    • オンデマンドインスタンス: 使用した分だけの課金。
    • リザーブドインスタンス: 長期間の利用を前提に割引価格で提供。
    • スポットインスタンス: 空き容量に応じて安価に提供される一時的なインスタンス。
  • セキュリティ:

    • 仮想プライベートクラウド(VPC)を使用してネットワークを分離し、セキュリティグループやネットワークACLでアクセス制御が可能。
  • 自動化:

    • Auto ScalingElastic Load Balancingを利用してトラフィックに応じてインスタンスの数を自動で調整。

ユースケース

  • ウェブアプリケーションのホスティング: 高い可用性とスケーラビリティを提供。
  • データ処理: 大量のデータを処理するための計算リソース。
  • 開発・テスト環境の構築: 短期間でインスタンスを立ち上げ、すぐに利用開始。

EC2は、AWSの強力なコンピューティングリソースを提供するサービスで、スケーラブルで柔軟な仮想サーバーの管理が可能です。多様なインスタンスの選択肢や価格モデルにより、さまざまなニーズに応じたクラウドコンピューティング環境を構築できます。

4.構成図

file

  • SSM経由でEC2にアクセス(HTTPS)できるようにすること。
  • RDSへのログインも可能にすること。
  • EFSにファイルを格納して共有できるようにすること。
    事前にキーペア・セキュリティグループ、IAMインスタンスプロファイルは作成済みです。
    セキュリティグループとIAMインスタンスプロファイルの設定は下記です。

    4-1.セキュリティグループ

【インバウンド】

  • ALB ↔ EC2(80・HTTP)
  • SSM ↔ EC2(443・HTTPS)
  • EFS ↔ EC2(2049・EFS)
  • RDS ↔ EC2(3306・MYSQL)

【アウトバウンド】

  • 全許可

4-2.IAMインスタンスプロファイル

  • AmazonSSMManagedInstanceCore(SSM用のポリシー)
  • CloudWatchAgentAdminPolicy(Cloudwatch用のポリシー)
    →下記ではCloudwatchの設定は割愛しております。

5.ソースコード全体

YAML形式にて記述しております。

AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation template to create an EC2 instance with specified configurations

Parameters:
  ImageId:
    Description: The ID of the Amazon Machine Image (AMI) to use
    Type: String
    Default: 

  InstanceType:
    Description: The EC2 instance type
    Type: String
    Default: 

  KeyName:
    Description: The EC2 Key Pair to allow SSH access to the instance
    Type: AWS::EC2::KeyPair::KeyName
    Default: 

  SubnetId:
    Description: The Subnet ID for the instance
    Type: AWS::EC2::Subnet::Id
    Default: 

  SecurityGroupId:
    Description: The Security Group ID for the instance
    Type: AWS::EC2::SecurityGroup::Id
    Default: 

  InstanceProfile:
    Description: The IAM instance profile for the instance
    Type: String
    Default: 

Resources:
  EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceType
      KeyName: !Ref KeyName
      SubnetId: !Ref SubnetId
      SecurityGroupIds:
        - !Ref SecurityGroupId
      ImageId: !Ref ImageId
      IamInstanceProfile: !Ref InstanceProfile
      DisableApiTermination: true
      InstanceInitiatedShutdownBehavior: stop
      Monitoring: true
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: 8
            VolumeType: gp3
            Iops: 3000
            DeleteOnTermination: true
      Tags:
        - Key: Name
          Value: naoki-ec2-2a
      UserData:
        #ユーザーデータを記載
Outputs:
  InstanceId:
    Description: The instance ID
    Value: !Ref EC2Instance

  InstancePrivateIp:
    Description: The private IP address of the instance
    Value: !GetAtt EC2Instance.PrivateIpAddress

6.ソースコード詳細

EC2の基本的なソースコードになります。
ユーザーデータは別でご説明致します。

使用するオプション 設定値 説明
Type AWS::EC2::Instance デフォルトの定義
InstanceType !Ref KeyName キーペアを指定
SubnetIds !Ref SubnetIds サブネットを指定
SecurityGroupIds !Ref SecurityGroupId セキュリティグループを指定
ImageId !Ref ImageId amiのイメージIDを指定
IamInstanceProfile !Ref InstanceProfile IAMインスタンスプロファイルをコンソールを指定
DisableApiTermination true 削除保護を有効化
InstanceInitiatedShutdownBehavior stop シャットダウン動作をストップに指定
Monitoring true Cloudwatchのモニタリングを有効化
Ebs 下記ソースコード参照 EBS詳細を記載(gp3)
Tags Key: Name
Value: naoki-ec2-2a
タグの値とキーを設定
UserData 下記ユーザーデータを参照 ユーザーデータを記載
  EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceType
      KeyName: !Ref KeyName
      SubnetId: !Ref SubnetId
      SecurityGroupIds:
        - !Ref SecurityGroupId
      ImageId: !Ref ImageId
      IamInstanceProfile: !Ref InstanceProfile
      DisableApiTermination: true
      InstanceInitiatedShutdownBehavior: stop
      Monitoring: true
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: 8
            VolumeType: gp3
            Iops: 3000
            DeleteOnTermination: true
      Tags:
        - Key: Name
          Value: naoki-ec2-2a
      UserData:
        #ユーザーデータを記載

7.ユーザーデータ

7-1.ユーザーデータについて

ユーザーデータは、EC2インスタンス起動時に自動的に実行されるスクリプトやコマンドを指定するための機能です。主に初期設定やソフトウェアのインストール、自動構成などに利用されます。ユーザーデータはインスタンスの初回起動時のみ実行され、再起動時には実行されません。

7-2.EC2(1号機用)

今回EC2を2台使用します。
1号機と2号機の別々のphpファイルを表示してタイトルを表示させます。

  #!/bin/bash

  # 環境変数の設定(変更してください)
  export EFS_DNS="EFSのDNS名"
  export MYSQL_HOST="RDSのARN"
  export MYSQL_USER="ユーザー名"
  export MYSQL_PASSWORD="RDSパスワード"

  # 更新を行う
  dnf update -y

  # Apache HTTPサーバーをインストール
  dnf install -y httpd

  # EFSファイルシステム用のディレクトリを作成
  mkdir -p /mnt/efs

  # EFSファイルシステムをマウント
  mount -t nfs4 "${EFS_DNS}:/" /mnt/efs

  # EFSのマウントを永続化
  grep -q "${EFS_DNS}:/ /mnt/efs nfs4 defaults,_netdev 0 0" /etc/fstab || echo "${EFS_DNS}:/ /mnt/efs nfs4 defaults,_netdev 0 0" >> /etc/fstab

  # MySQLクライアントのインストール
  dnf -y localinstall https://dev.mysql.com/get/mysql80-community-release-el9-1.noarch.rpm
  rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2023
  dnf -y install mysql-community-client

  # MySQLドライバーのインストール
  dnf -y localinstall https://dev.mysql.com/get/mysql80-community-release-el9-1.noarch.rpm
  rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2023

  # PHPとApacheのPHPモジュールのインストール
  dnf install -y php php-cli php-fpm php-mysqlnd php-json php-opcache php-gd php-xml php-mbstring
  sudo dnf update -y

  # Apache MPMをpreforkに設定
  echo "LoadModule mpm_prefork_module modules/mod_mpm_prefork.so" | sudo tee /etc/httpd/conf.modules.d/00-mpm.conf

  # Apacheの設定変更
  sed -i 's|DocumentRoot "/var/www/html"|DocumentRoot "/mnt/efs"|g' /etc/httpd/conf/httpd.conf
  sed -i 's|||g' /etc/httpd/conf/httpd.conf
  sed -i 's/index.html/index01.php/g' /etc/httpd/conf/httpd.conf

  # PHP-FPMの設定
  systemctl start php-fpm
  systemctl enable php-fpm

  # Apache設定にPHP-FPMの設定を追加
  cat < /etc/httpd/conf.d/php.conf

      SetHandler "proxy:unix:/var/run/php-fpm/www.sock|fcgi://localhost/"

  EOF

  # Apacheの起動と自動起動の設定
  systemctl start httpd
  systemctl enable httpd

  # データベース設定ファイルの作成(DB名だけ変更)
  cat < /mnt/efs/db_config.php
<?php
\$db_config = [
    'servername' => '$MYSQL_HOST',
    'username' => '$MYSQL_USER',
    'password' => '$MYSQL_PASSWORD',
    'dbname' => 'naoki_rds'
  ];
  ?>
  EOF

  # PHPファイルの作成
  cat < /mnt/efs/index01.php
<?php
include 'db_config.php';

// データベース接続の設定
\$servername = \$db_config['servername'];
\$username = \$db_config['username'];
\$password = \$db_config['password'];
\$dbname = \$db_config['dbname'];

// MySQLデータベースに接続
\$conn = new mysqli(\$servername, \$username, \$password, \$dbname);

// UTF-8でエンコーディングを設定
\$conn->set_charset("utf8");

  // 接続をチェックする
  if (\$conn->connect_error) {
      die("接続に失敗しました: " . \$conn->connect_error);
  }

  // employeesテーブルからデータを取得するクエリ
  \$sql = "SELECT * FROM employees";
  \$result = \$conn->query(\$sql);

  // クエリの実行をチェックする
  if (!\$result) {
      die("クエリの実行に失敗しました: " . \$conn->error);
  }

  // HTMLの文字コードを指定
  echo "";

  // タイトルを表示
echo " <h1>WEBサーバー 1号機 </h1> ";

  // データが取得できた場合、HTMLテーブルとして表示する
  if (\$result->num_rows > 0) {
      echo "<table border='1'>";
      echo "<tr><th>Employee ID</th><th>First Name</th><th>Last Name</th><th>Email</th><th>Hire Date</th><th>Salary</th><th>Department ID</th></tr>";
      while(\$row = \$result->fetch_assoc()) {
          echo "<tr>";
          echo "<td>".\$row["employee_id"]."</td>";
          echo "<td>".\$row["first_name"]."</td>";
          echo "<td>".\$row["last_name"]."</td>";
          echo "<td>".\$row["email"]."</td>";
          echo "<td>".\$row["hire_date"]."</td>";
          echo "<td>".\$row["salary"]."</td>";
          echo "<td>".\$row["department_id"]."</td>";
          echo "</tr>";
      }
      echo "</table>";
  } else {
      echo "データが見つかりませんでした";
  }

  // MySQL接続を閉じる
  \$conn->close();
  ?>
  EOF

  # ALBのヘルスチェック用のHTMLファイルの作成
  cat < /mnt/efs/healthcheck.php

      <title>Health Check</title>

      <h1>ALB Health Check Passed</h1>

  EOF

  # ファイアウォールの設定を変更し、HTTPトラフィックを許可
  if systemctl is-active --quiet firewalld; then
      firewall-cmd --permanent --add-service=http
      firewall-cmd --reload
  else
      iptables -I INPUT -p tcp --dport 80 -j ACCEPT
      service iptables save
  fi

  # MySQLデータベースの設定(CREATEとUSEのデータベース名変える)
  mysql -h $MYSQL_HOST -u $MYSQL_USER -p$MYSQL_PASSWORD <<EOF
  CREATE DATABASE IF NOT EXISTS naoki_rds; 
  USE naoki_rds;

  CREATE TABLE IF NOT EXISTS employees (
      employee_id INT AUTO_INCREMENT PRIMARY KEY,
      first_name VARCHAR(50) NOT NULL,
      last_name VARCHAR(50) NOT NULL,
      email VARCHAR(100),
      hire_date DATE,
      salary DECIMAL(10, 2),
      department_id INT
  );

  INSERT INTO employees (first_name, last_name, email, hire_date, salary, department_id)
  VALUES
      (&#039;Alice&#039;, &#039;Smith&#039;, &#039;alice.smith@example.com&#039;, &#039;2023-01-15&#039;, 60000.00, 1),
      (&#039;Bob&#039;, &#039;Johnson&#039;, &#039;bob.johnson@example.com&#039;, &#039;2023-02-20&#039;, 55000.00, 2),
      (&#039;Charlie&#039;, &#039;Williams&#039;, &#039;charlie.williams@example.com&#039;, &#039;2023-03-25&#039;, 65000.00, 1),
      (&#039;David&#039;, &#039;Brown&#039;, &#039;david.brown@example.com&#039;, &#039;2023-04-30&#039;, 70000.00, 2),
      (&#039;Eva&#039;, &#039;Garcia&#039;, &#039;eva.garcia@example.com&#039;, &#039;2023-05-05&#039;, 58000.00, 1);

  UPDATE employees
  SET salary = 55000.00
  WHERE employee_id = 1;
  EOF

簡単にやっていることを下記にてご説明致します。

  • ApacheやPHP、MYSQLドライバーなどのRDSへのログインやWEBサーバーとして機能させるために必要なものをインストール
  • PHPのモジュールの設定
  • Apache設定ファイルの編集
  • EFS用のファイルを作成して、マウントするように指定
  • 1,2号機共通のRDS情報のあるPHPファイルとALBヘルスチェック用のPHPファイルの作成
  • 1号機用のPHPファイルの作成
  • RDSにログインしてデータベースとテーブルの作成
    ※Cloudwatchエージェント系のインストールも実施しておりますが、説明は割愛致します。

以上になります。
これで1号機のWEBサーバーとしての準備は整いました。
下記はALB→EC2→NATの構成にして1号機の内容を表示した結果です。
file

またEc2にログインした中身は下記の通りです。
file

7-3.EC2(2号機用)

続いて2号機です。
すでにEFSにDBのパスワード等のPHPファイルとALBヘルスチェック用のPHPファイルは存在するため2号機用のPHPファイルの作成のみとEFSへのマウントを行います。

  #!/bin/bash

  # 環境変数の設定(変更してください)
  export EFS_DNS="EFSのDNS名"
  export MYSQL_HOST="RDSのARN"
  export MYSQL_USER="ユーザー名"
  export MYSQL_PASSWORD="RDSパスワード"

  # 更新を行う
  dnf update -y

  # Apache HTTPサーバーをインストール
  dnf install -y httpd

  # EFSファイルシステム用のディレクトリを作成
  mkdir -p /mnt/efs

  # EFSファイルシステムをマウント
  mount -t nfs4 "${EFS_DNS}:/" /mnt/efs

  # EFSのマウントを永続化
  grep -q "${EFS_DNS}:/ /mnt/efs nfs4 defaults,_netdev 0 0" /etc/fstab || echo "${EFS_DNS}:/ /mnt/efs nfs4 defaults,_netdev 0 0" >> /etc/fstab

  # MySQLクライアントのインストール
  dnf -y localinstall https://dev.mysql.com/get/mysql80-community-release-el9-1.noarch.rpm
  rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2023
  dnf -y install mysql-community-client

  # PHPとApacheのPHPモジュールのインストール
  dnf install -y php php-cli php-fpm php-mysqlnd php-json php-opcache php-gd php-xml php-mbstring
  sudo dnf update -y

  # Apache MPMをpreforkに設定
  echo "LoadModule mpm_prefork_module modules/mod_mpm_prefork.so" | sudo tee /etc/httpd/conf.modules.d/00-mpm.conf

  # Apacheの設定変更
  sed -i 's|DocumentRoot "/var/www/html"|DocumentRoot "/mnt/efs"|g' /etc/httpd/conf/httpd.conf
  sed -i 's|||g' /etc/httpd/conf/httpd.conf
  sed -i 's/index.html/index02.php/g' /etc/httpd/conf/httpd.conf

  # PHP-FPMの設定
  systemctl start php-fpm
  systemctl enable php-fpm

  # Apache設定にPHP-FPMの設定を追加
  cat < /etc/httpd/conf.d/php.conf

      SetHandler "proxy:unix:/var/run/php-fpm/www.sock|fcgi://localhost/"

  EOF

  # Apacheの起動と自動起動の設定
  systemctl start httpd
  systemctl enable httpd

  # PHPファイルの作成
  cat < /mnt/efs/index02.php
  set_charset("utf8");

  // 接続をチェックする
  if (\$conn->connect_error) {
      die("接続に失敗しました: " . \$conn->connect_error);
  }

  // employeesテーブルからデータを取得するクエリ
  \$sql = "SELECT * FROM employees";
  \$result = \$conn->query(\$sql);

  // クエリの実行をチェックする
  if (!\$result) {
      die("クエリの実行に失敗しました: " . \$conn->error);
  }

  // HTMLの文字コードを指定
  echo "";

  // タイトルを表示
echo " <h1>WEBサーバー 2号機 </h1> ";

  // データが取得できた場合、HTMLテーブルとして表示する
  if (\$result->num_rows > 0) {
      echo "<table border='1'>";
      echo "<tr><th>Employee ID</th><th>First Name</th><th>Last Name</th><th>Email</th><th>Hire Date</th><th>Salary</th><th>Department ID</th></tr>";
      while(\$row = \$result->fetch_assoc()) {
          echo "<tr>";
          echo "<td>".\$row["employee_id"]."</td>";
          echo "<td>".\$row["first_name"]."</td>";
          echo "<td>".\$row["last_name"]."</td>";
          echo "<td>".\$row["email"]."</td>";
          echo "<td>".\$row["hire_date"]."</td>";
          echo "<td>".\$row["salary"]."</td>";
          echo "<td>".\$row["department_id"]."</td>";
          echo "</tr>";
      }
      echo "</table>";
  } else {
      echo "データが見つかりませんでした";
  }

  // MySQL接続を閉じる
  \$conn->close();
  ?>
  EOF

  # ALBのヘルスチェック用のHTMLファイルの作成
  cat < /mnt/efs/healthcheck.php

      <title>Health Check</title>

      <h1>ALB Health Check Passed</h1>

  EOF

  # ファイアウォールの設定を変更し、HTTPトラフィックを許可
  if systemctl is-active --quiet firewalld; then
      firewall-cmd --permanent --add-service=http
      firewall-cmd --reload
  else
      iptables -I INPUT -p tcp --dport 80 -j ACCEPT
      service iptables save
  fi

簡単にやっていることを下記にてご説明致します。

  • ApacheやPHP、MYSQLドライバーなどのRDSへのログインやWEBサーバーとして機能させるために必要なものをインストール
  • PHPのモジュールの設定
  • Apache設定ファイルの編集
  • EFS用のファイルを作成して、マウントするように指定
  • 2号機用のPHPファイルの作成

上記で作成した2号機は下記のように表示されます。
file

また1号機で作成作成済みのEFSの中にあるファイルを共有して使用することが出来ます。
file

8.感想

ユーザーデータを使用することでヒューマンエラーを減らしたうえで、WEBサーバーとして素早く機能させることが出来ると実感できました。
1からやる場合は設定ファイルのバックアップはしっかりとったうえで作業しましょう。。

Last modified: 2024-08-14

Author