FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

TerraformでAWS Devサーバを構築する手順をしらべる (5. Terraformの自動セットアップ, フロントエンドサーバ側)

user_dataとは

user_dataは、EC2インスタンス起動時に実行されるスクリプトである。Terraformのaws_instanceリソースでuser_dataパラメータを指定すると、インスタンスの初回起動時にそのスクリプトが自動実行される。

resource "aws_instance" "frontend" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.frontend_instance_type
  
  user_data = <<-EOF
#!/bin/bash
echo "Hello from user_data"
EOF
}

この例では、インスタンス起動時に「Hello from user_data」が出力される。実際の運用では、パッケージのインストール、サービスの設定、ファイルシステムのマウントなど、より複雑な処理を記述する。

フロントエンドサーバーの自動セットアップフロー

フロントエンドサーバーでは、以下の処理が自動実行される:

  1. ホスト名の設定
  2. パッケージのインストール
  3. EFSのマウント
  4. Slurmコントローラーのインストールと設定
  5. Munge keyの生成と共有
  6. EBSボリュームのフォーマットとマウント
  7. NFSサーバーの設定

1. ホスト名の設定

まず、インスタンスのホスト名を設定する。これにより、後続の処理でホスト名を参照できるようになる。

hostnamectl set-hostname frontend-server
echo "127.0.0.1 frontend-server" >> /etc/hosts
echo "$(hostname -I | awk '{print $1}') frontend-server" >> /etc/hosts

hostnamectlでホスト名を設定し、/etc/hostsにエントリを追加する。これにより、frontend-serverというホスト名で自分自身を参照できるようになる。

2. パッケージのインストール

次に、必要なパッケージを一括インストールする。Terraformの変数を使って、インストールするパッケージのリストを定義している。

apt-get update -y
PACKAGES="${join(" ", var.common_packages)}"
apt-get install -y $PACKAGES

var.common_packagesには、以下のようなパッケージが含まれている:

  • amazon-efs-utils: EFSをマウントするためのユーティリティ
  • nfs-common: NFSクライアント機能
  • build-essential: コンパイルに必要なツール
  • git, curl, wget: 開発・運用に必要なツール
  • emacs: エディタ
  • docker.io: Docker
  • make: ビルドツール

エラーハンドリングも実装している。一括インストールが失敗した場合、個別にインストールを試みる:

apt-get install -y $PACKAGES || {
    echo "ERROR: Package installation failed!"
    echo "Trying to install packages individually..."
    for pkg in ${join(" ", var.common_packages)}; do
        apt-get install -y "$pkg" || echo "WARNING: $pkg failed"
    done
}

3. EFSのマウント

EFS(Elastic File System)は、複数のEC2インスタンス間で共有できるファイルシステムである。Slurmの設定ファイルやMunge keyを共有するために使用する。

EFSのマウントには、マウントターゲットが準備できるまで待つ必要がある。そのため、最大5分間待機するループを実装している:

EFS_ID="${aws_efs_file_system.work.id}"
MAX_WAIT_EFS=300
ELAPSED_EFS=0

while [ $ELAPSED_EFS -lt $MAX_WAIT_EFS ]; do
    if mount -t efs -o tls $EFS_ID:/ /mnt/work 2>/dev/null; then
        echo "EFS mounted successfully at /mnt/work"
        break
    fi
    sleep 10
    ELAPSED_EFS=$((ELAPSED_EFS + 10))
done

マウントが成功したら、/etc/fstabにエントリを追加して、再起動後も自動マウントされるようにする:

if ! grep -q "/mnt/work" /etc/fstab; then
    echo "$EFS_ID:/ /mnt/work efs _netdev,tls 0 0" >> /etc/fstab
    mount -a
fi

grepで既存のエントリを確認してから追加することで、重複追加を防いでいる。

4. Slurmコントローラーのインストールと設定

Slurmは、HPC(High Performance Computing)環境でジョブスケジューリングを行うソフトウェアである。フロントエンドサーバーでは、Slurmコントローラー(slurmctld)をインストールする。

apt-get install -y slurm-wlm slurmctld munge

Slurmでは、認証にMungeというソフトウェアを使用する。Munge keyを生成し、EFS経由でワーカーノードと共有する:

if [ ! -f /etc/munge/munge.key ]; then
    dd if=/dev/urandom bs=1 count=1024 > /etc/munge/munge.key
    chmod 600 /etc/munge/munge.key
    chown munge:munge /etc/munge/munge.key
fi

# Copy Munge key to EFS (for worker use)
mkdir -p /mnt/work/slurm
cp /etc/munge/munge.key /mnt/work/slurm/munge.key
chmod 644 /mnt/work/slurm/munge.key

次に、Slurmの設定ファイルを生成する。ここで注意が必要なのは、Terraformの変数展開とbashの変数展開を区別することである:

CONTROLLER_IP=$(hostname -I | awk '{print $1}')

cat > /etc/slurm/slurm.conf <<SLURM_EOF
ClusterName=slurm-cluster
ControlMachine=frontend-server
ControlAddr=$${CONTROLLER_IP}
...
SLURM_EOF

$${CONTROLLER_IP}のように$$でエスケープすることで、Terraformがこれをbash変数として解釈するようになる。${CONTROLLER_IP}と書くと、TerraformがこれをTerraform変数として解釈してエラーになる。

生成した設定ファイルをEFSにコピーし、ワーカーノードでも使用できるようにする:

cp /etc/slurm/slurm.conf /mnt/work/slurm/slurm.conf

最後に、MungeとSlurmコントローラーのサービスを起動する:

systemctl enable munge
systemctl start munge
systemctl enable slurmctld
systemctl start slurmctld

5. EBSボリュームのフォーマットとマウント

1TBのEBSボリュームをフロントエンドサーバーにアタッチし、NFS経由でジョブサーバーと共有する。EBSボリュームのアタッチには時間がかかるため、デバイスが検出されるまで待つ必要がある。

MAX_WAIT_EBS=300
ELAPSED_EBS=0
DEVICE=""

while [ -z "$DEVICE" ] && [ $ELAPSED_EBS -lt $MAX_WAIT_EBS ]; do
    # Check for NVMe devices (modern instances)
    for nvme_dev in /dev/nvme[1-9]n1; do
        if [ -e "$nvme_dev" ]; then
            # Check if it's not the root device
            if ! mountpoint -q "$nvme_dev" 2>/dev/null; then
                DEVICE="$nvme_dev"
                break
            fi
        fi
    done
    
    # Check for xvd devices (older instances)
    if [ -z "$DEVICE" ] && [ -e /dev/xvdf ]; then
        DEVICE="/dev/xvdf"
    fi
    
    if [ -z "$DEVICE" ]; then
        sleep 5
        ELAPSED_EBS=$((ELAPSED_EBS + 5))
    fi
done

このスクリプトでは、NVMeデバイス(/dev/nvme1n1など)と従来型デバイス(/dev/xvdf)の両方をチェックしている。インスタンスタイプによってデバイス名が異なるため、動的に検出する必要がある。

デバイスが見つかったら、フォーマットしてマウントする:

if ! blkid $DEVICE > /dev/null 2>&1; then
    echo "Formatting $DEVICE with ext4..."
    mkfs.ext4 -F $DEVICE
fi

mkdir -p /mnt/shared
if ! grep -q "/mnt/shared" /etc/fstab; then
    echo "$DEVICE /mnt/shared ext4 defaults,nofail 0 2" >> /etc/fstab
fi

mount $DEVICE /mnt/shared || mount -a

blkidでファイルシステムの存在を確認し、存在しない場合のみフォーマットする。これにより、既にフォーマット済みのボリュームを再フォーマットすることを防ぐ。

6. NFSサーバーの設定

EBSボリュームをNFS経由でジョブサーバーと共有するため、NFSサーバーを設定する:

apt-get install -y nfs-kernel-server

# Configure NFS export
if ! grep -q "/mnt/shared" /etc/exports; then
    echo "/mnt/shared *(rw,sync,no_subtree_check,no_root_squash)" >> /etc/exports
fi

exportfs -ra
systemctl enable nfs-kernel-server
systemctl restart nfs-kernel-server

/etc/exportsにエクスポート設定を追加し、NFSサーバーを起動する。exportfs -raで設定を再読み込みし、変更を反映させる。