AWS CDKによる【EC2】の構築

皆様こんにちは。
AWS CDKを利用してマルチAZ3層アーキテクチャ構築をしていきます。
この記事ではEC2の作成を行います。

目次はこちら

1.EC2とは

Amazon EC2 (Elastic Compute Cloud) は、Amazon Web Services (AWS) が提供する仮想サーバーを簡単に作成、管理できるサービスです。

概要

  • 仮想サーバー:EC2 インスタンスは、仮想マシンを AWS のインフラ上で実行することができます。これにより、ユーザーは物理的なハードウェアを管理することなく、計算リソースを利用できます。

  • スケーラビリティ:必要に応じてインスタンスを簡単にスケールアップまたはスケールダウンすることができます。これにより、トラフィックや負荷の変動に応じたリソース管理が可能です。

  • 多様なインスタンスタイプ:EC2 は、さまざまなユースケースに対応するために、異なる CPU、メモリ、ストレージ、ネットワーク性能を持つ多様なインスタンスタイプを提供しています。

  • 弾力性と高可用性:複数のアベイラビリティゾーン(AZ)にインスタンスをデプロイすることで、高可用性を確保できます。また、Auto Scaling を使用して、インスタンス数を動的に調整することも可能です。

  • セキュリティ:セキュリティグループやネットワーク ACL を使用して、インスタンスへのアクセス制御を設定できます。また、IAM ロールを使用して、EC2 インスタンスが他の AWS サービスにアクセスするための権限を管理できます。

  • コスト効率:使用した分だけ支払う従量課金制を採用しており、需要に応じてコストを最適化できます。また、スポットインスタンスやリザーブドインスタンスを利用することで、さらにコストを削減することが可能です。

主な機能

  • Elastic Block Store (EBS):高パフォーマンスなブロックストレージを提供し、データの永続化やスナップショットによるバックアップが可能です。

  • Elastic IP アドレス:固定 IP アドレスを使用することで、インスタンスの再起動や停止後も同じ IP アドレスを使用できます。

  • Amazon Machine Image (AMI):インスタンスのテンプレートとして使用できる仮想マシンイメージを作成、共有できます。

  • Auto Scaling:特定の条件に基づいて、自動的にインスタンス数を調整する機能です。これにより、負荷の変動に柔軟に対応できます。

  • CloudWatch:インスタンスの監視やログの収集、アラームの設定が可能です。

Amazon EC2 を使用することで、企業はインフラストラクチャの運用や管理の負担を軽減し、迅速にアプリケーションの展開やスケーリングが可能になります。
詳細は公式ドキュメントを参照ください。

2.目的

  • 各AZのプライベートサブネットに配置
  • SSMで接続可能にする
  • Webサーバーとして機能させる(Apacheサーバー)
  • RDSへの接続を可能にする
  • ユーザーデータにて各種インストールやEFSへのマウントを行う

3.構成図

4.全体構築ソースコード

        # IAM インスタンスプロファイルの作成
        self.instance_profile = iam.CfnInstanceProfile(self, "kitaya-instance-profile",
            roles=[ec2_role.role_name]
        )

        # ユーザーデータ#1号機
        user_data_script_01 = """#!/bin/bash
# Secrets Managerからシークレットを取得
SECRET=$(aws secretsmanager get-secret-value --secret-id kitaya-db-Secret --query SecretString --output text)

# 環境変数の設定
export EFS_DNS="EFSのDNS名"
export MYSQL_HOST="RDSのDNS名"
export MYSQL_USER=$(echo "$SECRET" | jq -r .username)
export MYSQL_PASSWORD=$(echo "$SECRET" | jq -r .password)
export MYSQL_DBNAME="データベース名"

# 更新を行う
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
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|<Directory "/var/www/html">|<Directory "/mnt/efs">|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 <<EOF > /etc/httpd/conf.d/php.conf
<FilesMatch \.php$>
  SetHandler "proxy:unix:/var/run/php-fpm/www.sock|fcgi://localhost/"
</FilesMatch>
EOF

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

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

# PHPファイルの作成(変更箇所あり)
cat <<EOF > /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 "<meta charset='UTF-8'>";

// タイトルを表示
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 <<EOF > /mnt/efs/healthcheck.php
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Health Check</title>
</head>
<body>
  <h1>ALB Health Check Passed</h1>
</body>
</html>
EOF

# CloudWatchエージェントのインストール
yum install -y amazon-cloudwatch-agent

# MySQLデータベースの設定(CREATE TABLE)
mysql -h $MYSQL_HOST -u $MYSQL_USER -p$MYSQL_PASSWORD <<EOF
USE kitaya_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
  ('Alice', 'Smith', 'alice.smith@example.com', '2023-01-15', 60000.00, 1),
  ('Bob', 'Johnson', 'bob.johnson@example.com', '2023-02-20', 55000.00, 2),
  ('Charlie', 'Williams', 'charlie.williams@example.com', '2023-03-25', 65000.00, 1),
  ('David', 'Brown', 'david.brown@example.com', '2023-04-30', 70000.00, 2),
  ('Eva', 'Garcia', 'eva.garcia@example.com', '2023-05-05', 58000.00, 1);

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

ユーザーデータ#2号機

# ユーザーデータ#2号機
user_data_script_02 = """#!/bin/bash
# 環境変数の設定
export EFS_DNS="EFSのDNS名"

# 更新を行う
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
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|<Directory "/var/www/html">|<Directory "/mnt/efs">|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 <<EOF > /etc/httpd/conf.d/php.conf
<FilesMatch \.php$>
  SetHandler "proxy:unix:/var/run/php-fpm/www.sock|fcgi://localhost/"
</FilesMatch>
EOF

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

# PHPファイルの作成(変更箇所あり)
cat <<EOF > /mnt/efs/index02.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 "<meta charset='UTF-8'>";

// タイトルを表示
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

# CloudWatchエージェントのインストール
yum install -y amazon-cloudwatch-agent
"""

        # Webサーバ #1(EC2)の作成
        self.instance_01 = ec2.CfnInstance(self, "kitaya-ec2-2a",
            image_id="ami-045f2d6eeb07ce8c0",
            instance_type="t3.micro",
            key_name="kitaya-kp-ec2",
            iam_instance_profile=self.instance_profile.ref,
            network_interfaces=[{
                "subnetId": private_subnet_az1.ref,
                "associatePublicIpAddress": False,
                "deviceIndex": "0",
                "privateIpAddress": "192.168.1.134",
                "groupSet": [ec2_sg.security_group_id]
            }],
            block_device_mappings=[
                {
                    "deviceName": "/dev/xvda",
                    "ebs": {
                        "volumeSize": 8,
                        "volumeType": "gp3",
                        "iops": 3000,
                        "throughput": 125,
                        "encrypted": True,
                        "deleteOnTermination": True,
                    }
                }
            ],
            user_data=Fn.base64(user_data_script_01)
        )
        Tags.of(self.instance_01).add("Name", "kitaya-ec2-2a")

        # Webサーバ #2(EC2)の作成
        self.instance_02 = ec2.CfnInstance(self, "kitaya-ec2-2b",
            image_id="ami-045f2d6eeb07ce8c0",
            instance_type="t3.micro",
            key_name="kitaya-kp-ec2",
            iam_instance_profile=self.instance_profile.ref,
            network_interfaces=[{
                "subnetId": private_subnet_az2.ref,
                "associatePublicIpAddress": False,
                "deviceIndex": "0",
                "privateIpAddress": "192.168.1.155",
                "groupSet": [ec2_sg.security_group_id]
            }],
            block_device_mappings=[
                {
                    "deviceName": "/dev/xvda",
                    "ebs": {
                        "volumeSize": 8,
                        "volumeType": "gp3",
                        "iops": 3000,
                        "throughput": 125,
                        "encrypted": True,
                        "deleteOnTermination": True,
                    }
                }
            ],
            user_data=Fn.base64(user_data_script_02)
        )
        Tags.of(self.instance_02).add("Name", "kitaya-ec2-2b")

5.ソースコード詳細

5-1.IAM インスタンスプロファイルの作成

インスタンスプロファイルはIAMロールのコンテナであり、インスタンスの起動時にEC2インスタンスにロール情報を渡すために使用できます。
マネジメントコンソールでEC2にアタッチする際にはインスタンスプロファイルは自動的に作成されるため意識することはありません。
IaCでの構築の場合は作成必須の項目となります。

"CfnInstanceProfile"を使用

論理ID(テンプレート内で一意)の指定をしたのち、詳細のプロパティを指定しています。

プロパティは下記

使用するプロパティ 設定値 説明
roles [ec2_role.role_name] インスタンスプロファイルに関連付ける IAM ロールのリストです。このリストには、少なくとも1つの IAM ロールが含まれている必要があります。今回は事前に作成したものを指定します。

ソースコード

        # IAM インスタンスプロファイルの作成
        self.instance_profile = iam.CfnInstanceProfile(self, "kitaya-instance-profile",
            roles=[ec2_role.role_name]
        )

5-2.ユーザーデータの作成

ユーザーデータとは、EC2インスタンスの起動時に一度だけ実行されるスクリプトやコマンドを指定するためのメカニズムです。ユーザーデータを使用して、インスタンスの起動時に自動的にソフトウェアをインストールしたり、設定を行ったりすることができます。

ユーザーデータの内容は下記です。
1号機と2号機でユーザーデータを少し変えています。
共用のものについては1号機でのみ実行しています。

1号機、2号機それぞれで実行

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

共用のため1号機のみで実行

  • データベースのパスワードをsecret managerより取得
  • データベースへのログイン情報を記載したPHPファイルの作成
  • ALBヘルスチェック用のPHPファイルの作成
  • データベースのテーブル作成

ソースコード

        # ユーザーデータ#1号機
        user_data_script_01 = """#!/bin/bash
# Secrets Managerからシークレットを取得
SECRET=$(aws secretsmanager get-secret-value --secret-id kitaya-db-Secret --query SecretString --output text)

# 環境変数の設定
export EFS_DNS="EFSのDNS名"
export MYSQL_HOST="RDSのDNS名"
export MYSQL_USER=$(echo "$SECRET" | jq -r .username)
export MYSQL_PASSWORD=$(echo "$SECRET" | jq -r .password)
export MYSQL_DBNAME="データベース名"

# 更新を行う
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
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|<Directory "/var/www/html">|<Directory "/mnt/efs">|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 <<EOF > /etc/httpd/conf.d/php.conf
<FilesMatch \.php$>
  SetHandler "proxy:unix:/var/run/php-fpm/www.sock|fcgi://localhost/"
</FilesMatch>
EOF

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

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

# PHPファイルの作成(変更箇所あり)
cat <<EOF > /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 "<meta charset='UTF-8'>";

// タイトルを表示
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 <<EOF > /mnt/efs/healthcheck.php
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Health Check</title>
</head>
<body>
  <h1>ALB Health Check Passed</h1>
</body>
</html>
EOF

# CloudWatchエージェントのインストール
yum install -y amazon-cloudwatch-agent

# MySQLデータベースの設定(CREATE TABLE)
mysql -h $MYSQL_HOST -u $MYSQL_USER -p$MYSQL_PASSWORD <<EOF
USE kitaya_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
  ('Alice', 'Smith', 'alice.smith@example.com', '2023-01-15', 60000.00, 1),
  ('Bob', 'Johnson', 'bob.johnson@example.com', '2023-02-20', 55000.00, 2),
  ('Charlie', 'Williams', 'charlie.williams@example.com', '2023-03-25', 65000.00, 1),
  ('David', 'Brown', 'david.brown@example.com', '2023-04-30', 70000.00, 2),
  ('Eva', 'Garcia', 'eva.garcia@example.com', '2023-05-05', 58000.00, 1);

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

        # ユーザーデータ#2号機
        user_data_script_02 = """#!/bin/bash
# 環境変数の設定
export EFS_DNS="EFSのDNS名"

# 更新を行う
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
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|<Directory "/var/www/html">|<Directory "/mnt/efs">|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 <<EOF > /etc/httpd/conf.d/php.conf
<FilesMatch \.php$>
  SetHandler "proxy:unix:/var/run/php-fpm/www.sock|fcgi://localhost/"
</FilesMatch>
EOF

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

# PHPファイルの作成(変更箇所あり)
cat <<EOF > /mnt/efs/index02.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 "<meta charset='UTF-8'>";

// タイトルを表示
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

# CloudWatchエージェントのインストール
yum install -y amazon-cloudwatch-agent

5-3.EC2インスタンスの作成

EC2インスタンスを作成します。

"ec2.CfnInstance"を使用を使用して作成

論理ID(テンプレート内で一意)の指定をしたのち、詳細のプロパティを指定しています。

プロパティは下記

使用するプロパティ 設定値 説明
image_id "ami-045f2d6eeb07ce8c0" AmazonMachineImage(AMI)のID。このAMIを使用してインスタンスを起動します。
instance_type "t3.micro" EC2インスタンスタイプ。t3.microは小規模なインスタンスです。
key_name "kitaya-kp-ec2" インスタンスにアクセスするためのキーペアの名前です。
iam_instance_profile self.instance_profile.ref インスタンスにアタッチするIAMインスタンスプロファイルの参照です。
network_interfaces 下記にて詳細説明 ネットワークインターフェースの設定
block_device_mappings 下記にて詳細説明 EBSの設定
user_data Fn.base64(ソースコード参照) インスタンスの起動時に実行するユーザーデータスクリプトを指定。

network_interfaces

使用するプロパティ 設定値 説明
subnetId ソースコード参照 ネットワークインターフェースが所属するサブネットのID。
associatePublicIpAddress False パブリックIPアドレスを割り当てるかどうかの設定。
deviceIndex "0" ネットワークインターフェースのデバイスインデックス。
privateIpAddress ソースコード参照 ネットワークインターフェースに割り当てるプライベートIPアドレス。
groupSet [ec2_sg.security_group_id] ネットワークインターフェースに関連付けるセキュリティグループのIDリスト。

block_device_mappings

使用するプロパティ 設定値 説明
deviceName "/dev/xvda" ブロックデバイスの名前。
ebs.volumeSize 8 EBSボリュームのサイズ(GB)。
ebs.volumeType "gp3" EBSボリュームのタイプ。gp3は汎用SSD。
ebs.iops 3000 EBSボリュームのIOPS(Input/OutputOperationsPerSecond)。
ebs.throughput 125 EBSボリュームのスループット(MB/s)。
ebs.encrypted True EBSボリュームが暗号化されているかどうかの設定。
ebs.deleteOnTermination True インスタンス終了時にEBSボリュームを削除するかどうかの設定。

"Tags.of()"メソッドを使用してNameタグをつけます(書式は下記)

Tags.of("リソース名").add("キー", "値")

ソースコード

        # Webサーバ #1(EC2)の作成
        self.instance_01 = ec2.CfnInstance(self, "kitaya-ec2-2a",
            image_id="ami-045f2d6eeb07ce8c0",
            instance_type="t3.micro",
            key_name="kitaya-kp-ec2",
            iam_instance_profile=self.instance_profile.ref,
            network_interfaces=[{
                "subnetId": private_subnet_az1.ref,
                "associatePublicIpAddress": False,
                "deviceIndex": "0",
                "privateIpAddress": "192.168.1.134",
                "groupSet": [ec2_sg.security_group_id]
            }],
            block_device_mappings=[
                {
                    "deviceName": "/dev/xvda",
                    "ebs": {
                        "volumeSize": 8,
                        "volumeType": "gp3",
                        "iops": 3000,
                        "throughput": 125,
                        "encrypted": True,
                        "deleteOnTermination": True,
                    }
                }
            ],
            user_data=Fn.base64(user_data_script_01)
        )
        Tags.of(self.instance_01).add("Name", "kitaya-ec2-2a")

        # Webサーバ #2(EC2)の作成
        self.instance_02 = ec2.CfnInstance(self, "kitaya-ec2-2b",
            image_id="ami-045f2d6eeb07ce8c0",
            instance_type="t3.micro",
            key_name="kitaya-kp-ec2",
            iam_instance_profile=self.instance_profile.ref,
            network_interfaces=[{
                "subnetId": private_subnet_az2.ref,
                "associatePublicIpAddress": False,
                "deviceIndex": "0",
                "privateIpAddress": "192.168.1.155",
                "groupSet": [ec2_sg.security_group_id]
            }],
            block_device_mappings=[
                {
                    "deviceName": "/dev/xvda",
                    "ebs": {
                        "volumeSize": 8,
                        "volumeType": "gp3",
                        "iops": 3000,
                        "throughput": 125,
                        "encrypted": True,
                        "deleteOnTermination": True,
                    }
                }
            ],
            user_data=Fn.base64(user_data_script_02)
        )
        Tags.of(self.instance_02).add("Name", "kitaya-ec2-2b")

6.検証

Webページの表示等、検証についてはALB構築にて行います。

7.感想

EC2の構築はインスタンスの設定のみではなく、ユーザーデータの作成など考慮する点が多く時間がかかった。
データベースなどの知識も少し学習できてよかった。

Last modified: 2024-08-14

Author