こういった悩みを持つ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プロビジョニングを複数プロジェクトで再利用できる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
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
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
# 認証情報の疎通確認(AnsibleもbotocoreのSDKを使うため同じ結果になる) $ aws sts get-caller-identity { "UserId": "AIDAXXXXXXXXXXXXXXXX", "Account": "123456789012", "Arn": "arn:aws:iam::123456789012:role/ansible-ec2-role" }
・(インスタンス起動)
・(一覧取得・状態確認)
・ / /
・(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
がパブリックサブネットとプライベートサブネットの境界を決める設定です。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
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
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
実践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"
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 }}"
の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
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
このタスクを省くと「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
よくあるエラーとトラブルシュート
| エラーメッセージ | 原因 | 対処 |
|---|---|---|
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無料マニュアルを受け取る >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます