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」が出力される。実際の運用では、パッケージのインストール、サービスの設定、ファイルシステムのマウントなど、より複雑な処理を記述する。
フロントエンドサーバーの自動セットアップフロー
フロントエンドサーバーでは、以下の処理が自動実行される:
- ホスト名の設定
- パッケージのインストール
- EFSのマウント
- Slurmコントローラーのインストールと設定
- Munge keyの生成と共有
- EBSボリュームのフォーマットとマウント
- 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: Dockermake: ビルドツール
エラーハンドリングも実装している。一括インストールが失敗した場合、個別にインストールを試みる:
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で設定を再読み込みし、変更を反映させる。