AnsibleでEC2インスタンスを自動作成・管理するPlaybook設計入門|amazon.awsコレクションとec2_instanceモジュールの使い方

運営:リナックスマスター.JP(代表 宮崎智広)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > Ansible > AnsibleでEC2インスタンスを自動作成・管理するPlaybook設計入門|amazon.awsコレクションとec2_instanceモジュールの使い方
「AWSのEC2インスタンスを毎回マネジメントコンソールから手作業で作っている」「Terraformを覚えるのは大変だが、手作業での環境構築はもう限界だ」
こういった悩みを持つLinuxエンジニアは少なくありません。

実は、すでにAnsibleを使っているなら、同じPlaybookの構造でVPC設計からEC2インスタンスの作成・起動・停止・削除まで一気通貫で自動化できます。
amazon.awsコレクションを使えば、VPC・サブネット・セキュリティグループの構築からEC2インスタンスの管理まで、Terraformなしでもインフラのコード管理が始められるのです。

「コンソールから手動でリソースを作るたびに、開発・ステージング・本番の設定がいつの間にかバラバラになっていく」——この構成ドリフト問題を根本から解消できるのが、AnsibleによるAWSリソースの冪等管理です。

この記事では、AnsibleのEC2プロビジョニング設計を基礎から解説します。
認証設計・VPC/セキュリティグループの準備・Playbookの基本パターン・プロビジョニング直後の接続設計・動的インベントリの活用まで、現場で再現できるレベルで説明します。
動作確認環境: RHEL 9.4 / Amazon Linux 2023(コントロールノード)、Ansible 2.16、amazon.aws 8.x。

この記事のポイント

・amazon.aws.ec2_instanceモジュール1つでEC2を冪等に作成・管理できる
・VPC・サブネット・SGもAnsibleで構築しIDをregister変数で受け渡せる
・IAM認証はIAMロール・環境変数・Vaultから選択——Playbookに認証情報を書かない
・add_hostでプロビジョニング直後から設定Playbookを続けて流せる
・aws_ec2プラグインの動的インベントリで既存EC2を自動管理できる
・本番適用前に--checkオプションでドライランし変更内容を事前確認する
・pre_tasks/post_tasksでEC2作成の前後確認を構造化できる
・roleとして実装するとEC2プロビジョニングを複数プロジェクトで再利用できる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

AnsibleによるEC2プロビジョニングとは|Terraform・AWS CLIとの役割分担

Ansibleはもともとサーバーの設定を自動化するツールですが、amazon.awsコレクションを使えばVPC・セキュリティグループといったネットワーク基盤からEC2インスタンスの作成・管理(プロビジョニング)まで一貫して担えます。

コンソールやAWS CLIでリソースを個別に作成・変更していると、本番・ステージング・開発の設定がいつの間にかズレていく「構成ドリフト」が起きます。
AnsibleでEC2やネットワーク構成をコードとして管理すれば、Playbookが「あるべき状態」を定義する唯一の正本になり、誰が何を変更したかもgitのコミット履歴で追跡できます。

よく比較されるTerraformとの違いはこうです。

・Terraform: 状態ファイル(tfstate)でインフラ全体の状態を管理する。複雑なVPC設計・依存関係の解決が得意
・Ansible: Playbookの実行ごとに「現在の状態」を確認して冪等に動作する。すでにAnsibleを使っているチームがAWSリソース管理も一元化したい場合に向く
・AWS CLI: アドホックな操作に向くが冪等性がなく、同じコマンドを2度実行すると2台のEC2が立つ

「すでにAnsibleでLinuxサーバーを管理しているが、EC2の起動だけAWS CLIで手動でやっている」という現場は多いです。
そこにAnsibleのEC2プロビジョニングを組み込むと、インフラ構築から設定管理まで1本のPlaybookで完結させられます。
さらに一歩進めると、roleとして実装したEC2プロビジョニング処理を複数プロジェクトで再利用し、group_varsでインスタンスタイプやサブネットだけを差し替えるパターンも使えます。詳しくは「実践Playbook」セクションのRole化設計で解説します。

amazon.awsコレクションのインストールと認証設計

1. boto3・botocore・amazon.awsコレクションのインストール

ec2_instanceモジュールを使うにはPythonのAWS SDKであるboto3とbotocoreが必要です。
Ansibleと同じPython環境(通常はvenv内)にインストールしてください。

# boto3・botocoreをインストールする pip install boto3 botocore # amazon.awsコレクションをインストールする(バージョン8.x系) ansible-galaxy collection install amazon.aws # インストール確認 ansible-galaxy collection list | grep amazon.aws

コレクションのインストール先はデフォルトで です。
チームで環境を統一したい場合は に記載して で管理するのが現場の定石です。
バージョンを固定することで、コレクションのアップデートによる非互換を防げます。

# collections/requirements.yml — チームでバージョンを統一する --- collections: - name: amazon.aws version: ">=8.0.0,<9.0.0" # インストールコマンド ansible-galaxy collection install -r collections/requirements.yml

でboto3/botocoreのバージョンも固定する運用を推奨します。boto3のマイナーバージョンアップで挙動が変わったケースが実務でも確認されているためです。

2. AWS認証情報の設計|IAMロール・環境変数・Ansible Vault

認証情報をPlaybook内にベタ書きするのは厳禁です。以下の3つから現場に合った方法を選んでください。

・IAMロール(推奨): Ansibleコントロールノード自体がEC2なら、インスタンスにIAMロールを付けるだけで認証情報の設定が不要になる
・環境変数(CI/CDや開発環境向け): / / を設定する
・Ansible Vault(コントロールノードがEC2以外の場合): アクセスキーをVaultで暗号化してgroup_vars/allに定義する。Playbookへの直接記述は絶対に避けること

# 環境変数で認証する場合(.envファイルなどで管理する) export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY export AWS_DEFAULT_REGION=ap-northeast-1 # credentialsファイルのプロファイルを切り替える場合 export AWS_PROFILE=myproject-prod # Ansible Vaultで暗号化する場合(group_vars/all/vault.yml) # ansible-vault encrypt group_vars/all/vault.yml で暗号化してからコミットする aws_access_key_id: !vault | $ANSIBLE_VAULT;1.1;AES256 ...(暗号化された値)... aws_secret_access_key: !vault | $ANSIBLE_VAULT;1.1;AES256 ...(暗号化された値)... aws_region: ap-northeast-1

認証設定が正しく機能しているかは、AWS CLIで { "UserId": "AIDA3TJOM52SMOUGTQVIW", "Account": "797350620836", "Arn": "arn:aws:iam::797350620836:user/rakumail-ses-reader" } を実行して確認するのが最速です。

# 認証情報の疎通確認(AnsibleもbotocoreのSDKを使うため同じ結果になる) $ aws sts get-caller-identity { "UserId": "AIDAXXXXXXXXXXXXXXXX", "Account": "123456789012", "Arn": "arn:aws:iam::123456789012:role/ansible-ec2-role" }

Ansibleに必要なIAMポリシーの最小権限は以下です。「ec2:*」で全許可するのではなく、必要権限だけに絞るのが現場のセキュリティ基準です。

・(インスタンス起動)
・(一覧取得・状態確認)
・ / /
・(Nameタグ付与)
・ / / (VPC・サブネット・SGを作る場合)
・ / / (既存リソースの確認)
・ / / (IGW・ルートテーブルを管理する場合)

// IAMポリシーJSON例(EC2プロビジョニング用最小権限) { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ec2:RunInstances", "ec2:DescribeInstances", "ec2:StopInstances", "ec2:StartInstances", "ec2:TerminateInstances", "ec2:CreateTags", "ec2:DescribeVpcs", "ec2:DescribeSubnets", "ec2:DescribeSecurityGroups", "ec2:CreateVpc", "ec2:CreateSubnet", "ec2:CreateSecurityGroup", "ec2:DescribeInternetGateways", "ec2:CreateInternetGateway", "ec2:AttachInternetGateway", "ec2:CreateRouteTable", "ec2:CreateRoute", "ec2:AssociateRouteTable" ], "Resource": "*" } ] }

EC2起動前の準備|VPC・サブネット・セキュリティグループをAnsibleで作る

EC2インスタンスを起動するには、あらかじめVPC・サブネット・セキュリティグループが存在している必要があります。
マネジメントコンソールで手動作成した場合はIDをPlaybookにハードコードしがちですが、これらもAnsibleで管理するとリソースIDの依存関係がPlaybook内で完結し、別リージョンへの展開も変数の切り替えだけで対応できます。

1. VPCとサブネットの作成

# playbooks/create_network.yml --- - name: VPC・サブネット・セキュリティグループを作成する hosts: localhost connection: local gather_facts: false vars: aws_region: ap-northeast-1 env_name: production vpc_cidr: "10.0.0.0/16" public_subnet_cidr: "10.0.1.0/24" private_subnet_cidr: "10.0.2.0/24" tasks: - name: VPCを作成する amazon.aws.ec2_vpc_net: name: "{{ env_name }}-vpc" cidr_block: "{{ vpc_cidr }}" region: "{{ aws_region }}" tags: Environment: "{{ env_name }}" ManagedBy: ansible state: present register: vpc_result - name: パブリックサブネットを作成する amazon.aws.ec2_vpc_subnet: vpc_id: "{{ vpc_result.vpc.id }}" cidr: "{{ public_subnet_cidr }}" az: "{{ aws_region }}a" map_public_ip_on_launch: true tags: Name: "{{ env_name }}-public-1a" Tier: public state: present register: public_subnet_result - name: プライベートサブネットを作成する amazon.aws.ec2_vpc_subnet: vpc_id: "{{ vpc_result.vpc.id }}" cidr: "{{ private_subnet_cidr }}" az: "{{ aws_region }}a" map_public_ip_on_launch: false tags: Name: "{{ env_name }}-private-1a" Tier: private state: present register: private_subnet_result

で作成したVPCのIDを変数に保存し、後続のサブネット作成タスクに で渡します。これがPlaybook内でのリソースID連携の基本パターンです。
がパブリックサブネットとプライベートサブネットの境界を決める設定です。Playbookに明示することで、コンソールでの「うっかり変更」を設計レベルで防げます。

パブリックサブネットとプライベートサブネットの使い分けは以下が現場の標準です。

・パブリックサブネット: インターネットに直接通信できる。ELB・NAT Gateway・踏み台サーバーを置く
・プライベートサブネット: インターネットに直接通信できない。アプリサーバー・DBサーバーを置く

パブリックサブネットのEC2がインターネットに出るには、VPCにインターネットゲートウェイ(IGW)をアタッチし、ルートテーブルに のルートを設定する必要があります。

# インターネットゲートウェイを作成してVPCにアタッチする - name: インターネットゲートウェイを作成する amazon.aws.ec2_vpc_igw: vpc_id: "{{ vpc_result.vpc.id }}" region: "{{ aws_region }}" tags: Name: "{{ env_name }}-igw" Environment: "{{ env_name }}" state: present register: igw_result # パブリックサブネット用のルートテーブルを作成する - name: パブリックルートテーブルを作成する amazon.aws.ec2_vpc_route_table: vpc_id: "{{ vpc_result.vpc.id }}" region: "{{ aws_region }}" subnets: - "{{ public_subnet_result.subnet.id }}" routes: - dest: "0.0.0.0/0" gateway_id: "{{ igw_result.gateway_id }}" tags: Name: "{{ env_name }}-public-rtb" Environment: "{{ env_name }}" state: present

これらをセットで管理することで、VPC作成からインターネット疎通まで1本のPlaybookで一貫して構築できます。

2. セキュリティグループの設計|「1リソース=1SG」の原則

セキュリティグループの設計では「1リソース=1セキュリティグループ」が運用上の鉄則です。
「汎用SG」を1つ作って全インスタンスに付ける運用は、ルールが肥大化して誰も把握できなくなります。WebサーバーとDBサーバーは明示的に分離してください。

# Webサーバー用SG(HTTP/HTTPSを全許可) - name: Webサーバー用セキュリティグループを作成する amazon.aws.ec2_security_group: name: "{{ env_name }}-web-sg" description: "Web servers (HTTP/HTTPS inbound)" vpc_id: "{{ vpc_result.vpc.id }}" rules: - proto: tcp from_port: 80 to_port: 80 cidr_ip: "0.0.0.0/0" - proto: tcp from_port: 443 to_port: 443 cidr_ip: "0.0.0.0/0" rules_egress: - proto: all cidr_ip: "0.0.0.0/0" tags: Name: "{{ env_name }}-web-sg" Environment: "{{ env_name }}" state: present register: web_sg_result # DBサーバー用SG(Webサーバーからのみ3306を許可) - name: DBサーバー用セキュリティグループを作成する amazon.aws.ec2_security_group: name: "{{ env_name }}-db-sg" description: "Database (MySQL from web-sg only)" vpc_id: "{{ vpc_result.vpc.id }}" rules: - proto: tcp from_port: 3306 to_port: 3306 # IPアドレスではなくWebサーバーSGのIDを参照する group_id: "{{ web_sg_result.group_id }}" rules_egress: - proto: all cidr_ip: "0.0.0.0/0" tags: Name: "{{ env_name }}-db-sg" Environment: "{{ env_name }}" state: present

DBサーバーSGのインバウンドに を指定することで「Webサーバー用SGが付いたインスタンスからのみ3306番を許可」という設計になります。
IPアドレスでなくSGを参照するため、WebサーバーのIPが変わっても設定変更は不要です。これがAnsibleでインフラをコード化する大きな利点のひとつで、設定の「意図」がコードとして残ります。

これらのPlaybookを実行後、 や をgroup_vars経由でec2_instanceのPlaybookに渡すと、IDのハードコードをなくせます。

ec2_instanceモジュールの基本設計|AMI・インスタンスタイプ・VPCの設定パターン

1. 必須パラメータとstateによる冪等制御

ec2_instanceモジュールの主なパラメータを整理します。
パラメータ 説明 設定例
name インスタンス名(Nameタグ)冪等性のキー web-prod-01
image_id 起動するAMIのID(リージョンごとに異なる) ami-0abc12345678abcde
instance_type インスタンスタイプ t3.micro
key_name SSH接続用キーペア名(AWSに登録済みのもの) my-ec2-keypair
security_groups セキュリティグループ名またはID(リスト) ['web-sg']
vpc_subnet_id 起動先サブネットID subnet-0abc12345678
state 操作の種類(present/stopped/absent/terminated) present
region AWSリージョン ap-northeast-1
user_data インスタンス初回起動時に実行するcloud-initスクリプト "#!/bin/bash yum update -y"

の値で操作が変わります。

・present: インスタンスが存在しない場合は作成、停止中なら起動する
・stopped: 起動中なら停止する。存在しない場合は作成後に停止状態にする
・absent / terminated: インスタンスを削除する。存在しない場合は何もしない(冪等)

パラメータがNameタグに使われ、これが冪等性のキーになります。
同じのインスタンスがすでに起動中なら「changed: false」で何もしません。

2. 最小構成の動作確認Playbook

まず1台のEC2を起動する最小構成のPlaybookで動作を確認します。

# playbooks/provision_ec2.yml --- - name: EC2インスタンスを1台起動する(動作確認用) hosts: localhost connection: local gather_facts: false tasks: - name: Webサーバー用EC2インスタンスを作成・起動する amazon.aws.ec2_instance: name: "web-test-01" image_id: "ami-0a0000000000aaaaa" # Amazon Linux 2023 ap-northeast-1 instance_type: "t3.micro" key_name: "my-ec2-keypair" security_groups: - "default" vpc_subnet_id: "subnet-0abc12345678" state: present region: ap-northeast-1 tags: Environment: test Role: webserver register: ec2_result - name: 起動結果のIPアドレスを表示する ansible.builtin.debug: msg: "インスタンスID: {{ ec2_result.instances[0].instance_id }} プライベートIP: {{ ec2_result.instances[0].private_ip_address }}"

出力例(環境により異なります):

$ ansible-playbook playbooks/provision_ec2.yml PLAY [EC2インスタンスを1台起動する(動作確認用)] *** TASK [Webサーバー用EC2インスタンスを作成・起動する] *** changed: [localhost] TASK [起動結果のIPアドレスを表示する] *** ok: [localhost] => { "msg": "インスタンスID: i-0abc12345678abcde プライベートIP: 10.0.1.50" } PLAY RECAP *** localhost : ok=2 changed=1

2回目の実行ではになります。同じNameタグのインスタンスがすでに起動中のため、何もしない(冪等)動作を確認できます。

実践Playbook|開発・本番環境のEC2を冪等に管理する設計

1. group_varsで環境別パラメータを分離する

dev・prod環境でインスタンスタイプやサブネットが違う場合は、group_varsに分離するのが定石です。

# ディレクトリ構成 provision/ ├── playbooks/ │ └── provision_ec2.yml ├── group_vars/ │ ├── all.yml # 共通設定(AMI ID、リージョン、キーペア名) │ ├── development.yml # dev環境: t3.micro、devサブネット │ └── production.yml # prod環境: t3.medium、prodサブネット └── inventory/ └── localhost.yml

# group_vars/all.yml aws_region: ap-northeast-1 ec2_ami_id: "ami-0a0000000000aaaaa" # Amazon Linux 2023 ec2_key_name: "my-ec2-keypair" ec2_security_groups: - "web-sg" # group_vars/development.yml env_name: development ec2_instance_type: t3.micro ec2_subnet_id: "subnet-0dev123456789" ec2_allow_terminate: true # 開発環境は誤削除ガードなし # group_vars/production.yml env_name: production ec2_instance_type: t3.medium ec2_subnet_id: "subnet-0prod12345678" ec2_allow_terminate: false # 本番環境は削除タスクをガードする

2. 環境を引数で切り替えるPlaybook設計

# playbooks/provision_ec2.yml --- - name: EC2インスタンスを環境別にプロビジョニングする hosts: localhost connection: local gather_facts: false # 実行時に -e "env=development" または -e "env=production" で切り替える vars: env: "{{ lookup('env', 'TARGET_ENV') | default('development') }}" tasks: - name: 環境別のgroup_varsを読み込む ansible.builtin.include_vars: file: "group_vars/{{ env }}.yml" - name: Webサーバー用EC2インスタンスを作成する amazon.aws.ec2_instance: name: "web-{{ env_name }}-01" image_id: "{{ ec2_ami_id }}" instance_type: "{{ ec2_instance_type }}" key_name: "{{ ec2_key_name }}" security_groups: "{{ ec2_security_groups }}" vpc_subnet_id: "{{ ec2_subnet_id }}" state: present region: "{{ aws_region }}" tags: Environment: "{{ env_name }}" Role: webserver ManagedBy: ansible register: ec2_result - name: インスタンス情報を変数に保持する ansible.builtin.set_fact: provisioned_private_ip: "{{ ec2_result.instances[0].private_ip_address }}" provisioned_instance_id: "{{ ec2_result.instances[0].instance_id }}" - name: 起動結果を表示する ansible.builtin.debug: msg: - "環境: {{ env_name }}" - "インスタンスID: {{ provisioned_instance_id }}" - "プライベートIP: {{ provisioned_private_ip }}"

実行コマンド:

# 開発環境のEC2を起動する ansible-playbook playbooks/provision_ec2.yml -e "env=development" # 本番環境のEC2を起動する ansible-playbook playbooks/provision_ec2.yml -e "env=production" # 本番環境のEC2を停止する(stateをstopped に変更) ansible-playbook playbooks/provision_ec2.yml -e "env=production" -e "ec2_state=stopped"

3. --checkオプションで本番適用前にドライランする

本番環境のEC2操作で「意図しないインスタンスを起動してしまった」「削除するつもりがなかったリソースを消してしまった」というミスを防ぐために、オプション(ドライランモード)を使う運用を徹底してください。

を付けて実行すると、実際の変更は行わずに「このPlaybookを実行すると何が変わるか」を事前確認できます。

# STEP1: --checkでドライランを実行して変更内容を確認する $ ansible-playbook playbooks/provision_ec2.yml -e "env=production" --check PLAY [EC2インスタンスを環境別にプロビジョニングする] *** TASK [Webサーバー用EC2インスタンスを作成する] *** changed: [localhost] # 変更が発生する予定(まだ何もしていない) PLAY RECAP *** localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 # STEP2: 内容を確認してから本番適用する $ ansible-playbook playbooks/provision_ec2.yml -e "env=production"

は「このPlaybookを実行すると1件の変更が発生する」ことを示しています。
2回目以降の実行では になり、既存インスタンスに変更がないことを確認できます——これが冪等性の動作確認です。

本番Playbookの実行手順を「--checkで確認→問題なければ本番適用」の2ステップでチームの標準運用にしておくことを強く推奨します。

4. 本番インスタンス誤削除を防ぐ条件ガード

または を指定したPlaybookを本番環境で誤実行すると、EC2インスタンスが削除されてデータが失われます。
group_varsに フラグを持たせ、 条件でガードする設計を推奨します。

# 削除タスクに条件ガードをつける - name: EC2インスタンスを削除する(本番では実行されない) amazon.aws.ec2_instance: name: "web-{{ env_name }}-01" region: "{{ aws_region }}" state: terminated when: ec2_allow_terminate | bool # 本番環境で削除しようとするとスキップされる # TASK [EC2インスタンスを削除する(本番では実行されない)] *** # skipping: [localhost] ← ec2_allow_terminate: false で保護される

この設計では、本番の に を書いておくだけで、削除タスクが自動的にスキップされます。「本番で を誤実行した」というインシデントを構造的に防げます。

5. pre_tasks・post_tasksでEC2 Playbookに前後確認を組み込む

Ansibleのplaybookには (rolesより前・tasksより前に実行)と (roles・tasksの後に必ず実行)があります。EC2プロビジョニングのPlaybookにこの構造を組み込むと、「前提確認→EC2作成→後確認」という3点セットの設計になり、Playbookの意図がひと目で読める構造になります。

pre_tasksで組み込む前提確認の例:

・必須変数(AMI ID・サブネットIDなど)の値が空でないことをassertモジュールで確認する
・実行対象リージョン・環境名をdebugで出力して意図しないリージョンへの誤実行を防ぐ
・boto3/botocoreがインストールされているかをcommandモジュールで確認する

post_tasksで組み込む後確認の例:

・EC2が「running」状態になったことをec2_instance_infoで確認する
・SSHポートの疎通確認(wait_for + port: 22)を実行する
・Slack通知でプロビジョニング完了を通知する

# pre_tasks・post_tasksを組み込んだEC2プロビジョニングPlaybook例 --- - name: EC2インスタンスを前後確認付きで作成する hosts: localhost connection: local gather_facts: false pre_tasks: # 必須変数が空でないことを確認する(変数が未設定のまま本番を実行しないための安全装置) - name: 必須変数の存在を確認する ansible.builtin.assert: that: - ec2_ami_id is defined and ec2_ami_id | length > 0 - ec2_subnet_id is defined and ec2_subnet_id | length > 0 - ec2_key_name is defined and ec2_key_name | length > 0 fail_msg: "必須変数 ec2_ami_id / ec2_subnet_id / ec2_key_name が未設定です" success_msg: "必須変数OK: AMI={{ ec2_ami_id }}, Subnet={{ ec2_subnet_id }}" # 対象リージョンをログに残す(意図しないリージョンへの誤実行防止) - name: 実行対象リージョン・環境を表示する ansible.builtin.debug: msg: "実行リージョン: {{ aws_region }} / 環境: {{ env_name }}" tasks: - name: EC2インスタンスを作成する amazon.aws.ec2_instance: name: "web-{{ env_name }}-01" image_id: "{{ ec2_ami_id }}" instance_type: "{{ ec2_instance_type }}" key_name: "{{ ec2_key_name }}" security_groups: "{{ ec2_security_groups }}" vpc_subnet_id: "{{ ec2_subnet_id }}" state: present region: "{{ aws_region }}" tags: Environment: "{{ env_name }}" ManagedBy: ansible register: ec2_result post_tasks: # EC2が running になるまで待つ - name: EC2が起動完了するまで待つ amazon.aws.ec2_instance_info: instance_ids: "{{ ec2_result.instances[0].instance_id }}" region: "{{ aws_region }}" register: instance_info until: instance_info.instances[0].state.name == 'running' retries: 10 delay: 15 # SSHポートの疎通確認(running になってもSSHデーモン起動まで数秒かかる) - name: SSHポートの疎通を確認する ansible.builtin.wait_for: host: "{{ ec2_result.instances[0].private_ip_address }}" port: 22 timeout: 60 delay: 5 # プロビジョニング完了の情報を表示する - name: プロビジョニング完了情報を表示する ansible.builtin.debug: msg: - "インスタンスID: {{ ec2_result.instances[0].instance_id }}" - "プライベートIP: {{ ec2_result.instances[0].private_ip_address }}" - "状態: {{ instance_info.instances[0].state.name }}"

にassertを入れておくと、「AMI IDの指定を忘れたまま本番を実行してしまった」「サブネットIDが古い値のままだった」という初歩的なミスをPlaybook起動直後に止められます。この確認をtasksの中に入れてしまうと、EC2作成タスクの直前でしか気づけませんが、pre_tasksに置けばPlaybook全体の起動直後に止まります。

のSSHポート確認は、後続のadd_hostパターン(次セクション参照)とも連携しやすく、「EC2は起動した、でもSSHでつながらない」という問題をPlaybook内で自己完結させられます。「pre_tasksで前提確認→tasksでEC2作成→post_tasksで起動後確認」という3点セットをEC2プロビジョニングの標準パターンとして覚えておくと、設計判断がスムーズになります。

6. Roleとして実装してEC2プロビジョニングを複数プロジェクトで再利用する

EC2プロビジョニングのPlaybookが安定したら、roleとして切り出すと複数プロジェクトから再利用できる設計になります。
Ansibleの変数優先順位(group_vars > role defaults)を活かし、 に全環境共通の基底値を置いてgroup_varsで上書く2層構造が実務上の定石です。

# roleを使ったディレクトリ構成 provision/ ├── roles/ │ └── aws_ec2/ │ ├── tasks/ │ │ └── main.yml # EC2プロビジョニングの実処理 │ └── defaults/ │ └── main.yml # 基底値(変数の最低優先順位) ├── group_vars/ │ ├── all.yml # 共通設定(リージョン・キーペア名) │ ├── development.yml # dev環境の上書き値 │ └── production.yml # prod環境の上書き値 └── site.yml # role を呼び出すPlaybook

# roles/aws_ec2/defaults/main.yml — 基底値(全環境共通のデフォルト) --- ec2_instance_type: "t3.micro" ec2_ami_id: "ami-0a0000000000aaaaa" # Amazon Linux 2023 ap-northeast-1 ec2_key_name: "my-ec2-keypair" ec2_security_groups: - "web-sg" ec2_state: present ec2_allow_terminate: false # 基底値は削除禁止(安全側) aws_region: "ap-northeast-1"

# group_vars/production.yml — prod環境だけ差分を上書く --- env_name: production ec2_instance_type: "t3.medium" # prod環境はt3.mediumに上書く ec2_subnet_id: "subnet-0prod12345678" ec2_allow_terminate: false # 明示的に削除禁止 # group_vars/development.yml — dev環境の差分 --- env_name: development ec2_instance_type: "t3.micro" # defaultsと同じでも明示する ec2_subnet_id: "subnet-0dev123456789" ec2_allow_terminate: true # dev環境のみ削除を許可

# site.yml — roleを呼び出すPlaybook --- - name: EC2プロビジョニング hosts: localhost connection: local gather_facts: false roles: - role: aws_ec2 tags: ec2 # 実行例:prod環境のEC2をドライランで確認 $ ansible-playbook -i inventory/ site.yml -l production --tags ec2 --check # 実行例:dev環境のEC2を起動 $ ansible-playbook -i inventory/ site.yml -l development --tags ec2

の値は変数優先順位が最も低いため、group_varsで上書きしなかった環境変数はdefaultsの値がそのまま使われます。「全環境で変えない値(AMI ID・リージョン)はdefaults、環境ごとに変える値(インスタンスタイプ・サブネット)はgroup_vars」という切り分けが、複数プロジェクト間で設定を共有しながら個別にカスタマイズする最も保守しやすい設計です。

AnsibleでAWS構築を体系的に学びたい方へ

Playbookの書き方・Role設計・Vault暗号化・CI/CD連携まで、実機環境で手を動かしながら習得できます。
独学でつまずいている方は、現役エンジニアが指導するハンズオンセミナーで一気に理解を深めましょう。
>> Ansible実践セミナーの詳細・申込はこちら

プロビジョニング後に接続するadd_host設計|EC2作成から設定適用まで1本のPlaybookで完結させる

EC2を作成したら、すぐにそのIPに接続してパッケージインストールや設定ファイル配布を行いたいケースがほとんどです。
モジュールを使うと、Playbookの実行中に動的にホストを追加できます。

# EC2プロビジョニング → 初期設定まで1本で流すPlaybook例 --- - name: フェーズ1:EC2インスタンスを作成する hosts: localhost connection: local gather_facts: false tasks: - name: EC2を起動する amazon.aws.ec2_instance: name: "web-prod-01" image_id: "ami-0a0000000000aaaaa" instance_type: t3.medium key_name: my-ec2-keypair security_groups: - web-sg vpc_subnet_id: subnet-0prod12345678 state: present region: ap-northeast-1 tags: Environment: production register: ec2_result - name: EC2が起動完了するまで待つ amazon.aws.ec2_instance_info: instance_ids: "{{ ec2_result.instances[0].instance_id }}" region: ap-northeast-1 register: instance_info until: instance_info.instances[0].state.name == 'running' retries: 10 delay: 15 - name: 新しいEC2をインベントリに追加する ansible.builtin.add_host: name: "{{ ec2_result.instances[0].private_ip_address }}" groups: newly_provisioned ansible_user: ec2-user ansible_ssh_private_key_file: "~/.ssh/my-ec2-keypair.pem" - name: フェーズ2:新しいEC2に初期設定を流す hosts: newly_provisioned gather_facts: true tasks: - name: SSHが応答できる状態になるまで待つ ansible.builtin.wait_for_connection: delay: 10 timeout: 120 - name: パッケージを最新化する ansible.builtin.dnf: name: "*" state: latest become: true - name: Nginxをインストールする ansible.builtin.dnf: name: nginx state: present become: true

を挟むのが重要です。EC2が「running」状態になってもSSHデーモンが起動するまで数秒かかります。
このタスクを省くと「SSH connection refused」でPlaybookがエラー終了することがあります。

の ループでインスタンスの を確認してから に進むことで、「起動中なのにまだrunningになっていないIPに接続しようとした」というエラーを防げます。

動的インベントリ(aws_ec2プラグイン)で既存EC2を自動管理する

add_hostはプロビジョニング直後の1回限りの接続に向きますが、既存のEC2グループを継続的に管理したい場合は 動的インベントリプラグインが便利です。
タグやリージョンをフィルタとして使い、AWSに存在するEC2を自動でインベントリに取り込めます。

# inventory/aws_ec2.yml — aws_ec2プラグインの設定例 --- plugin: amazon.aws.aws_ec2 regions: - ap-northeast-1 filters: tag:Environment: production # Environmentタグがproductionのインスタンスのみ instance-state-name: running # 起動中のみを対象にする keyed_groups: - key: tags.Role prefix: role # role_webserver, role_database のようなグループを自動生成 - key: placement.availability_zone prefix: az # az_ap-northeast-1a のようなグループを生成 hostnames: - private-ip-address # プライベートIPをホスト名として使う compose: ansible_user: "'ec2-user'" ansible_ssh_private_key_file: "'~/.ssh/my-ec2-keypair.pem'"

# 動的インベントリで取れるホスト一覧を確認する $ ansible-inventory -i inventory/aws_ec2.yml --graph @all: |--@role_webserver: | |--10.0.1.50 | |--10.0.1.51 |--@role_database: | |--10.0.2.20 |--@az_ap-northeast-1a: | |--10.0.1.50 | |--10.0.2.20 # role_webserverグループ全台にPlaybookを流す $ ansible-playbook -i inventory/aws_ec2.yml deploy_app.yml --limit role_webserver

動的インベントリを使うと、EC2を追加・削除しても 配下のファイルを更新する必要がなくなります。タグ(Role・Environment)をサーバー管理の分類軸として使い、Ansibleのグループに自動マッピングする設計がインフラ規模が増えても維持しやすいパターンです。

よくあるエラーとトラブルシュート

エラーメッセージ 原因 対処
An error occurred (AuthFailure) IAM権限が不足、または認証情報が未設定 IAMポリシーにec2:RunInstances等が含まれているか確認。環境変数が正しくエクスポートされているか確認する
botocore.exceptions.NoCredentialsError: Unable to locate credentials boto3が認証情報を見つけられない aws sts get-caller-identity でAWS CLIが正常動作するか確認する。インスタンスプロファイルのアタッチ・環境変数(AWS_ACCESS_KEY_ID等)の設定を確認する
InvalidAMIID.NotFound AMI IDが誤り、またはリージョンが違う aws ec2 describe-images --image-ids ami-XXXX --region ap-northeast-1 で確認する
InvalidSubnetID.NotFound Subnet IDが誤り、または別リージョンのID aws ec2 describe-subnets --region ap-northeast-1 でサブネット一覧を確認する
InvalidKeyPair.NotFound key_nameで指定したキーペアが対象リージョンに存在しない aws ec2 describe-key-pairs --region ap-northeast-1 でキーペア名を確認する。キーペアはリージョンごとに作成が必要なので別リージョンへの流用はできない
ModuleNotFoundError: No module named 'boto3' boto3がインストールされていない Ansibleを実行するPython環境にpip install boto3 botocoreを実行する
unreachable: SSH connection refused EC2起動直後でSSHデーモンが未起動 add_hostの前にwait_for_connectionタスクを追加する
You need to install the amazon.aws collection コレクションが未インストール ansible-galaxy collection install amazon.awsを実行する
Resource sg-xxxxxxxx already exists ec2_security_groupのdescriptionを変更しようとした AWSの制約でdescriptionは後から変更できない。旧SGを削除してから再作成する
VPCIdNotSpecified デフォルトVPCが存在しないリージョンでvpc_subnet_idを省略した vpc_subnet_id を明示的に指定する。VPC・サブネットをAnsibleで管理し、registerしたIDを渡す
AnsibleUndefinedVariable: ec2_ami_id is undefined 必須変数が未定義のままPlaybookを実行した pre_tasksにassertを置いてPlaybook起動直後に未定義変数を検出する。group_varsのファイルが正しく読み込まれているか確認する

セキュリティグループでSSH(22番ポート)が開いていない場合も接続できません。
ポートの開放状況はLinux側から確認する場合はssコマンドやlsofによるポート確認の方法も参考にしてください。

【注意】state: absent は本番環境での誤実行に注意

または を指定したPlaybookを本番環境で誤って実行すると、EC2インスタンスが削除されます。
削除されたインスタンスはストレージも含めて失われ、復元はできません。
本番Playbook実行前には必ず (ドライランモード)で変更内容を確認する習慣をつけてください。
また、本番環境用のPlaybookには の条件ガードを設けて、誤った環境への適用を防ぐ設計が重要です。

まとめ

この記事では、AnsibleのEC2プロビジョニング設計を、VPC・セキュリティグループの準備からEC2インスタンスの起動・設定適用・動的インベントリまで一貫して解説しました。

やりたいこと Ansible設定・操作
VPCを作成する amazon.aws.ec2_vpc_net: state: present
サブネットを作成する amazon.aws.ec2_vpc_subnet: state: present
インターネットゲートウェイを作成する amazon.aws.ec2_vpc_igw: state: present
ルートテーブルを設定する amazon.aws.ec2_vpc_route_table: state: present
セキュリティグループを作成する amazon.aws.ec2_security_group: state: present
前タスクのリソースIDを次タスクに渡す register: vpc_result → vpc_result.vpc.id
EC2インスタンスを作成・起動する amazon.aws.ec2_instance: state: present
EC2インスタンスを停止する amazon.aws.ec2_instance: state: stopped
EC2インスタンスを削除する amazon.aws.ec2_instance: state: absent
インスタンスIPを取得して後続タスクに渡す register: ec2_result → ec2_result.instances[0].private_ip_address
EC2作成前に必須変数・前提条件を確認する pre_tasksにassertを置いてPlaybook起動直後に検証する
EC2作成後に起動状態・SSH疎通を確認する post_tasksにec2_instance_info + wait_forを置く
起動後に動的に接続する ansible.builtin.add_host + wait_for_connection
既存EC2グループをタグで動的管理する amazon.aws.aws_ec2 動的インベントリプラグイン
dev・prod環境を1本のPlaybookで管理する group_vars/development.yml / group_vars/production.yml に分離する
本番適用前に変更内容を確認する ansible-playbook --check でドライラン実行する
本番インスタンスの誤削除を防ぐ ec2_allow_terminate: false + when 条件ガードをgroup_varsで制御する
アクセスキーを安全に管理する IAMロール(インスタンスプロファイル)またはAnsible Vaultで暗号化する
EC2プロビジョニングを複数プロジェクトで再利用する roles/aws_ec2/defaults/main.ymlに基底値 + group_varsで環境差分を上書く

次のステップとして、以下のような応用を検討してみてください。

・VPC/ネットワーク管理: ec2_vpc_igw・ec2_vpc_route_tableを使い、インターネットゲートウェイとルートテーブルもPlaybookに組み込む
・pre_tasks/post_tasks設計: EC2プロビジョニングにpre_tasks(assert前提確認)とpost_tasks(起動後のSSH疎通確認)を組み込むと、Playbookが自己完結した構造になる
・Ansible Vault: AWSのアクセスキーをVaultで暗号化してリポジトリに安全にコミットする
・動的Inventory: aws_ec2プラグインを使えば既存EC2を自動で拾えるため、add_hostなしで管理できる
・Role化: EC2プロビジョニング部分をroleとして分離し、defaults/main.ymlに基底値を置くと複数プロジェクトで再利用しやすくなる
・CI/CD連携: GitHub ActionsやJenkinsからPlaybookを実行することで、コードマージ時にインフラ変更を自動適用できる

関連記事:
・Linux ポート確認の全コマンド(ss・lsof・netstat)
・Linux DNS 設定の基本(resolv.conf・nmcliの設定方法)

AnsibleでAWS構築を体系的に学びたい方へ

Playbookの書き方・Role設計・Vault暗号化・CI/CD連携まで、実機環境で手を動かしながら習得できます。
独学でつまずいている方は、現役エンジニアが指導するハンズオンセミナーで一気に理解を深めましょう。
>> Ansible実践セミナーの詳細・申込はこちら

現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
Linux無料マニュアルを受け取る >>

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録

運営

リナックスマスター.JP(代表 宮崎智広)

株式会社イーネットマーキュリーが運営するLinux教育サイトです。代表の宮崎智広は現役のLinuxサーバー管理者として20年以上の実務経験を持ち、累計3,100名以上のエンジニアを指導してきました。実践型のLinuxセミナーや無料マニュアルの配布を通じてLinux人材の育成に取り組んでいます。