EC2起動時にスクリプトを自動実行する方法 — User Dataで初回起動時にNginxをインストールする

EC2インスタンスを起動するたびに手動でSSH接続してNginxをインストールしていた経験があるなら、User Dataの仕組みを理解することで、その作業を完全に自動化できる。AMIからインスタンスを量産する場面や、Auto Scalingグループで新しいインスタンスが追加される場面では、この自動化が運用品質を左右する。

TL;DR — EC2 User Dataの要点

項目内容
実行タイミングインスタンスの初回起動時のみ(デフォルト動作)
実行ユーザーroot(sudoなしで実行される)
スクリプト形式シェルスクリプト(#!/bin/bash)またはcloud-init directive
文字数上限16 KB(Base64エンコード後)
ログ出力先/var/log/cloud-init-output.log
設定場所コンソール起動ウィザード / AWS CLI / Launch Template

User Dataの仕組みを理解する

EC2インスタンスが初回起動すると、cloud-initというデーモンがインスタンスメタデータサービス(IMDSv2)からUser Dataを取得し、実行する。このフローを理解しておかないと、「スクリプトを貼ったのに動かない」という状況で原因の見当がつかない。

sequenceDiagram participant HV as ハイパーバイザー participant OS as Linux OS participant CI as cloud-init participant IMDS as IMDS (169.254.169.254) participant SH as シェルスクリプト HV->>OS: インスタンス起動 OS->>CI: cloud-initデーモン起動 CI->>IMDS: GET /latest/user-data IMDS-->>CI: スクリプト内容を返す CI->>CI: shebang判定 (#!/bin/bash) CI->>SH: rootとして実行 SH->>SH: yum install nginx / systemctl start nginx SH-->>CI: 実行完了 CI->>OS: /var/log/cloud-init-output.log に記録
  1. インスタンス起動 — ハイパーバイザーがEC2インスタンスを起動し、OSが初期化される。
  2. cloud-initがIMDSにアクセスhttp://169.254.169.254/latest/user-dataエンドポイントからUser Dataを取得する。
  3. スクリプト判定 — 先頭行が#!/bin/bashなどのshebangであれば、シェルスクリプトとして実行する。#cloud-configであればYAML形式のcloud-init directiveとして処理する。
  4. root権限で実行 — スクリプトはrootとして実行されるため、sudoは不要。
  5. ログ記録 — 標準出力・標準エラーは/var/log/cloud-init-output.logに記録される。
cloud-initはLinuxディストリビューションに広く採用されているが、Amazon Linux 2、Amazon Linux 2023、Ubuntu、RHELではバージョンや動作に差異がある。特にAmazon Linux 2023はcloud-initの設定構造が変わっているため、古い記事のサンプルをそのまま流用する際は注意が必要だ。

EC2 User DataにNginxインストールスクリプトを設定する手順

EC2 User Dataを使ってNginxを自動インストールするには、コンソールからインスタンスを起動する際の設定画面、またはLaunch TemplateにスクリプトをペーストするだけでよいI。以下に両方の方法を示す。

方法1: AWSコンソールからインスタンス起動時に設定する

  1. AWSマネジメントコンソールで EC2 → インスタンス → インスタンスを起動 を選択する。
  2. AMI、インスタンスタイプ、キーペア、ネットワーク設定を通常通り選択する。
  3. 画面を下にスクロールし、「高度な詳細」セクションを展開する。
  4. 最下部にある 「ユーザーデータ」 テキストエリアに、以下のスクリプトをペーストする。
#!/bin/bash
yum update -y
yum install -y nginx
systemctl enable nginx
systemctl start nginx

上記はAmazon Linux 2向けのスクリプトだ。Ubuntuを使う場合はyumの代わりにapt-getを使う。

#!/bin/bash
apt-get update -y
apt-get install -y nginx
systemctl enable nginx
systemctl start nginx

スクリプトを貼り付けたら、そのまま「インスタンスを起動」ボタンをクリックする。インスタンスが起動完了した後、数分待ってからパブリックIPアドレスにブラウザでアクセスすると、NginxのデフォルトページがHTTPで表示される。

方法2: AWS CLIでUser Dataを指定してインスタンスを起動する

コンソール操作を自動化したい場合や、CI/CDパイプラインからインスタンスを起動する場合は、CLIで--user-dataオプションを使う。スクリプトはファイルとして渡すのが確実だ。

# スクリプトをファイルに保存する
cat <<'EOF' > /tmp/userdata.sh
#!/bin/bash
yum update -y
yum install -y nginx
systemctl enable nginx
systemctl start nginx
EOF

# EC2インスタンスを起動する
aws ec2 run-instances \
  --image-id ami-0abcdef1234567890 \
  --instance-type t3.micro \
  --key-name MyKeyPair \
  --security-group-ids sg-0123456789abcdef0 \
  --subnet-id subnet-0123456789abcdef0 \
  --user-data file:///tmp/userdata.sh \
  --region us-east-1

--image-idには実際のAMI IDを、--key-nameには既存のキーペア名を指定すること。AMI IDはリージョンとAMIの種類によって異なるため、AWS公式ドキュメントのAMI検索手順で確認する。

方法3: Launch TemplateにUser Dataを組み込む(推奨)

Auto ScalingグループやSpotインスタンスを使う場合、Launch TemplateにUser Dataを定義しておくのが運用上の標準だ。一度テンプレートに設定すれば、そのテンプレートから起動するすべてのインスタンスに同じスクリプトが適用される。

# User DataをBase64エンコードしてLaunch Templateを作成する
USER_DATA=$(base64 -w 0 /tmp/userdata.sh)

aws ec2 create-launch-template \
  --launch-template-name nginx-server-template \
  --version-description 'v1-nginx-install' \
  --launch-template-data "{\"ImageId\":\"ami-0abcdef1234567890\",\"InstanceType\":\"t3.micro\",\"UserData\":\"${USER_DATA}\"}" \
  --region us-east-1

CLIでLaunch Templateを作成する場合、User DataはBase64エンコードした文字列を渡す必要がある。コンソールからLaunch Templateを作成する場合は、コンソールが自動的にエンコードするため、プレーンテキストのスクリプトをそのまま貼り付けてよい。

スクリプトが実行されたか確認する方法

「インスタンスは起動したのにNginxが動いていない」という状況は、スクリプトのデバッグ不足が原因であることが多い。User Dataの実行ログを確認する手順を覚えておくことが重要だ。

graph TD A["インスタンス起動後
SSH接続"] --> B["cloud-init status を確認"] B --> C{"status: done?"} C -- "No / running" --> D["スクリプト実行中 or ハング
しばらく待つ"] C -- "Yes" --> E["cloud-init-output.log を確認"] E --> F{"エラーログあり?"} F -- "Yes" --> G["エラー内容を特定
ネットワーク/パッケージ等"] F -- "No" --> H["systemctl status nginx を確認"] H --> I{"Active: running?"} I -- "Yes" --> J["✅ 正常稼働"] I -- "No" --> K["journalctl -u nginx で詳細確認"]

ステップ1: cloud-init出力ログを確認する

インスタンスにSSH接続した後、最初に確認すべきファイルはこれだ。スクリプトの標準出力と標準エラーがすべてここに記録されている。パッケージのインストールに失敗していれば、そのエラーメッセージがここに残る。

sudo cat /var/log/cloud-init-output.log

ステップ2: cloud-initのステータスを確認する

ログを見る前に、cloud-init自体が正常に完了しているかを確認する。ステータスがrunningのままであれば、スクリプトがまだ実行中か、途中でハングしている可能性がある。

cloud-init status

正常に完了していればstatus: doneと表示される。

ステップ3: Nginxのサービス状態を確認する

cloud-initが正常終了しているのにNginxが動いていない場合、スクリプト内のコマンドが失敗している可能性がある。

systemctl status nginx

ステップ4: コンソールからシステムログを確認する(SSH接続できない場合)

SSH接続すら確立できない場合、EC2コンソールからシステムログを取得できる。これはcloud-initの初期化段階のエラーを確認する最後の手段だ。

aws ec2 get-console-output \
  --instance-id i-0123456789abcdef0 \
  --region us-east-1 \
  --output text

実際の現場で起きた誤診パターン

あるチームがAuto ScalingグループのLaunch TemplateにUser Dataを設定したが、新しく起動したインスタンスにNginxがインストールされていなかった。ログを確認するとcloud-init-output.logには正常終了の記録があり、スクリプト自体は実行されていた。

最初の仮説は「スクリプトの構文エラー」だったが、ローカルで同じスクリプトを実行すると問題なく動作した。次に「AMIの問題」を疑ったが、手動でSSH接続して同じコマンドを実行するとNginxはインストールできた。

実際の原因はセキュリティグループのアウトバウンドルールだった。インスタンスが起動した時点でアウトバウンドHTTPS(443番ポート)が制限されており、yum updateがパッケージリポジトリに接続できずタイムアウトしていた。cloud-init-output.logの末尾を注意深く読むと、Could not resolve hostというエラーが記録されていた。

スクリプトの実行は成功しているが、スクリプト内のコマンドが失敗しているというケースは、ログの先頭だけを確認していると見落とす。ログファイルを末尾まで読む習慣が重要だ。

User Dataスクリプトはroot権限で実行されるが、ネットワーク到達性はインスタンスのVPC設定とセキュリティグループに依存する。スクリプトが「動いているのに結果が出ない」場合、まずネットワーク層を疑う。

IAM権限 — S3からスクリプトを取得する場合

User Dataスクリプト自体の実行にIAMは不要だが、スクリプト内でAWS APIを呼び出す場合(例: S3からファイルをダウンロードする、Secrets Managerから認証情報を取得するなど)は、EC2インスタンスにIAMロールをアタッチする必要がある。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject"
      ],
      "Resource": "arn:aws:s3:::my-config-bucket/nginx/*"
    }
  ]
}

このポリシーをIAMロールにアタッチし、そのロールをEC2インスタンスプロファイルとして設定する。インスタンスプロファイルはインスタンス起動時に指定するか、Launch Templateに組み込む。

よくある失敗パターンと対処法

症状原因対処法
Nginxが起動していないアウトバウンド通信がブロックされyumが失敗セキュリティグループのアウトバウンドルールを確認する
cloud-init-output.logが空User Dataが正しく設定されていないインスタンスメタデータで確認: curl http://169.254.169.254/latest/user-data
2回目の起動後にスクリプトが実行されないデフォルトでは初回起動時のみ実行される再実行が必要な場合はcloud-initのモジュール設定を変更するか、別の仕組みを使う
スクリプトが途中で止まるインタラクティブな確認プロンプトが出ている-yフラグを各コマンドに付与する
文字化けや構文エラーコンソールのテキストエリアへのコピペで文字が変換されたCLIでfile://形式を使ってファイルから渡す

まとめとNext Steps — EC2 User Dataで自動化を始める

EC2 User Dataは、インスタンスの初回起動時に任意のシェルスクリプトを実行できる仕組みだ。Nginxのインストール程度であればコンソールのテキストエリアに直接貼り付けるだけで動作する。本番環境ではLaunch Templateにスクリプトを組み込み、Auto Scalingグループと組み合わせることで、インスタンスの追加・置き換えを完全に自動化できる。

次のステップとして、以下を検討する:

  • 複数のインスタンスに同じ設定を適用する場合は、AWS Systems Manager State ManagerAWS CloudFormationとの組み合わせを検討する。
  • より複雑な設定管理が必要な場合は、User DataからAnsibleやChefを呼び出す構成も一般的だ。
  • 公式ドキュメント: EC2 User Data — AWS公式ドキュメント

用語集

用語説明
User DataEC2インスタンスの起動時に実行されるスクリプトまたはcloud-init設定。コンソールまたはCLIで指定する。
cloud-initLinuxインスタンスがUser Dataを取得・実行するためのデーモン。多くのLinuxディストリビューションで標準採用されている。
IMDS (Instance Metadata Service)インスタンス内部から169.254.169.254でアクセスできるメタデータエンドポイント。User Dataもここから取得される。
Launch TemplateEC2インスタンスの起動設定をテンプレート化したもの。User DataやIAMロールなどを含め、Auto Scalingグループで再利用できる。
インスタンスプロファイルEC2インスタンスにIAMロールを関連付けるためのコンテナ。インスタンス上で動くアプリケーションがAWS APIを呼び出す際に使用される。

Related Posts

コメント

このブログの人気の投稿

EC2 SSH接続タイムアウトの原因と修正方法 — セキュリティグループのインバウンドルール完全ガイド

S3パブリックアクセス拒否の原因と解決策:バケットレベルの「Block Public Access」が優先される仕組み

EC2インスタンスIDをメタデータから取得する方法 — IMDSv2が安全な理由