あけましておめでとうございます、わっしょい村です。前回はAnsibleの導入と簡単な操作をやりました。
今回はAnsibleを使ってEC2の立ち上げからEC2内への操作までやっていきます。EC2内にはワードプレスをセットアップしアクセスできるところをゴールとします。
全体像
まずは全体像を確認します。流れとしては最初にAWS関係の構築を行います。VPCやサブネット、インターネットゲートウェイ、EC2などです。その後EC2内にWebサーバー系のミドルウェアを導入し、ワードプレスを構築します。
EC2内にワードプレスを構築する流れは以下の公式チュートリアルのコマンドをAnsibleに置き換えてやることとします。
完成するAnsibleのディレクトリ構造は以下になります。
├── ansible.cfg
├── create.yml
├── delete_aws
│ ├── delete.yml
│ └── roles
│ └── delete_aws
│ ├── tasks
│ │ └── main.yml
│ └── vars
│ └── main.yml
├── group_vars
│ └── all.yml
├── hosts
├── roles
│ ├── aws_vpc
│ │ ├── tasks
│ │ │ └── main.yml
│ │ └── vars
│ │ └── main.yml
│ ├── web_server
│ │ ├── tasks
│ │ │ └── main.yml
│ │ └── vars
│ │ └── main.yml
│ └── wordpress
│ ├── tasks
│ │ └── main.yml
│ └── vars
│ └── main.yml
└── ssh_config
写真の方がわかりやすいかもしれません。

AnsibleではRolesという仕様を使って構成を組むことが多いです。今回はロールを3つに分けています。1つはAWSを立ち上げるロール。2つめはEC2内にWebサーバーミドルウェアやDBを構築するロール。最後がワードプレスを構築し設定するロールです。
そして今回はこの3つのロールとは別でAWS環境を削除するロールも用意しています。今回はCFnを使わないので環境を抹消したい時にコンソールのいろんなとこをぽちぽちしなくちゃいけなかったんですがそれもAnsibleで行ってしまおうというわけです。
設定ファイル
今回roles関連のファイル以外の設定ファイルがいくつかあります。
1つがhostsです。中身は一行だけです。
my_host
これは今回使うEC2が一つだけなのでそれのホスト名としてmy_hostと名付けています。これがもし2台構成など複数台の場合はそれに応じてホスト名を追加してください。今回は1行だけでOKです。
他にもansible.cfgと言う設定ファイルがあります。中身は以下です。
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -F ./ssh_config -q
これはEC2へssh接続するための設定です。ここでssh接続にはssh_configファイルを使いますよと宣言しています。そのためssh_configファイルも作成しておきましょう。ただし最初はssh_configファイルは空ファイルのままで大丈夫です。EC2起動後に自動で中身を書き加えるようにします。
最後がgroup_vars/all.ymlです。名前の通りグローバル変数的な役割です。ホスト名をどこでも使えるように変数化しています。
ssh_host: my_host
ロール
それではロールファイルに入っていきます。基本的にロールはタスクが書かれたタスクファイルと変数が定義されている変数ファイルに分かれています。
AWSロール
それではroles/aws_vpc/tasks/main.ymlから見ていきます。ここでは以下を行っています。
- VPC作成
- サブネットの作成
- IGW作成
- ルートテーブルの作成
- 秘密鍵生成
- セキュリティグループ作成
またそれぞれ作成後にIDをスタックと呼んでいるymlファイルに書き込んでいます。こちらは後ほど作成します。
- name: VPC作成
amazon.aws.ec2_vpc_net:
name: "{{ vpc_name }}"
cidr_block: "{{ vpc_cidr }}"
region: "{{ region }}"
register: vpc_info
#スタックにVPCidを入力。スタックに別の値が書き込まれている場合は書き換える
- name: スタックにVPC idを追加
ansible.builtin.lineinfile:
path: ./delete_aws/roles/delete_aws/vars/main.yml
regexp: '^vpc_id'
line: "{{ 'vpc_id: '+vpc_info.vpc.id }}"
- name: サブネット作成
amazon.aws.ec2_vpc_subnet:
vpc_id: "{{ vpc_info.vpc.id }}"
cidr: "{{ item.subnet_cidr }}"
az: "{{ item.subnet_az }}"
region: "{{ region }}"
resource_tags: {"Name": "{{ item.subnet_name }}"}
register: sub_info
with_items:
- "{{ subnet }}"
#スタックに1個目のsubnetidを入力。スタックに別の値が書き込まれている場合は書き換える
- name: スタックに1個目のsubnetidを追加
ansible.builtin.lineinfile:
path: ./delete_aws/roles/delete_aws/vars/main.yml
regexp: '^subnet1_id'
line: "{{ 'subnet1_id: '+sub_info.results[0].subnet.id }}"
#スタックに2個目のsubnetidを入力。スタックに別の値が書き込まれている場合は書き換える
- name: スタックに2個目のsubnetidを追加
ansible.builtin.lineinfile:
path: ./delete_aws/roles/delete_aws/vars/main.yml
regexp: '^subnet2_id'
line: "{{ 'subnet2_id: '+sub_info.results[1].subnet.id }}"
- name: IGW作成
amazon.aws.ec2_vpc_igw:
vpc_id: "{{ vpc_info.vpc.id }}"
region: "{{ region }}"
tags: { "Name": "{{ igw_name }}" }
register: igw_info
#スタックにIGWidを入力。スタックに別の値が書き込まれている場合は書き換える
- name: スタックにIGWidを追加
ansible.builtin.lineinfile:
path: ./delete_aws/roles/delete_aws/vars/main.yml
regexp: '^IGW_id'
line: "{{ 'IGW_id: '+igw_info.gateway_id }}"
- name: ルートテーブルの作成
amazon.aws.ec2_vpc_route_table:
vpc_id: "{{ vpc_info.vpc.id }}"
subnets: "{{ attache_igw_subnet }}"
routes:
- dest: 0.0.0.0/0
gateway_id: "{{ igw_info.gateway_id }}"
region: "{{ region }}"
resource_tags: { "Name": "{{ routetable_name }}" }
register: RT_info
#スタックにルートテーブルidを入力。スタックに別の値が書き込まれている場合は書き換える
- name: スタックにルートテーブルidを追加
ansible.builtin.lineinfile:
path: ./delete_aws/roles/delete_aws/vars/main.yml
regexp: '^route_table_id'
line: "{{ 'route_table_id: '+RT_info.route_table.id }}"
- name: キーペア作成
amazon.aws.ec2_key:
name: "{{ keypair_name }}"
region: "{{ region }}"
register: keypair_info
#スタックにキーペア名を入力。スタックに別の値が書き込まれている場合は書き換える
- name: スタックにキーペア名を追加
ansible.builtin.lineinfile:
path: ./delete_aws/roles/delete_aws/vars/main.yml
regexp: '^ec2_key_pair_name'
line: "{{ 'ec2_key_pair_name: '+keypair_info.key.name }}"
- name: 鍵用の空ファイル作成
ansible.builtin.file:
path: ~/.ssh/{{ keypair_info.key.name }}.pem
state: touch
mode: 0600
when: keypair_info.key.private_key is defined
- name: 空ファイルに秘密鍵を書き込む
ansible.builtin.shell: echo "{{ keypair_info.key.private_key }}" > ~/.ssh/"{{ keypair_info.key.name }}".pem
when: keypair_info.key.private_key is defined
- name: セキュリティグループ作成
amazon.aws.ec2_group:
name: "{{ secgrp_name }}"
description: "{{ description }}"
vpc_id: "{{ vpc_info.vpc.id }}"
#vpc_id: vpc-083c83aba2a42c56e 既存のVPCに入れる場合は上記コメントアウトしてここに指定
region: "{{ region }}"
rules:
- proto: "tcp"
ports: "{{ sec_tcp_ports }}"
cidr_ip: "{{ sec_ip }}"
register: secgrp_info
#スタックにSGidを入力。スタックに別の値が書き込まれている場合は書き換える
- name: スタックにセキュリティグループidを入力。
ansible.builtin.lineinfile:
path: ./delete_aws/roles/delete_aws/vars/main.yml
regexp: '^SG_id'
line: "{{ 'SG_id: '+secgrp_info.group_id }}"
- name: EC2インスタンスの生成
amazon.aws.ec2_instance:
name: "{{ ec2_name }}" # インスタンスの名前を指定
key_name: "{{ keypair_name }}" # インスタンスにログインするための認証鍵を指定
instance_type: "{{ instance_type }}" # インスタンスタイプを指定
image_id: "{{ image }}"
region: "{{ region }}" # 東京リージョンを指定
vpc_subnet_id: "{{ sub_info.results[0].subnet.id }}" # サブネットを指定(ここは適宜変える)
security_group: "{{ secgrp_name }}" # セキュリティグループ名を指定(ここは適宜変える)
network:
assign_public_ip: yes
state: started
wait: yes
register: ec2_info
# ec2ステータスチェック(上記ec2作成でもステータスチェックオプションを入れているが念のためこちらでもステータスチェック)
- name: ec2がstartedになるまで待機
ansible.builtin.shell: aws ec2 describe-instances --instance-ids "{{ ec2_info.instance_ids[0] }}" --query "Reservations[].Instances[].State.Name | [0]" | tr -d "\""
register: state
until: state.stdout == "running"
retries: 12
delay: 10
#スタックにec2のidを入力。スタックに別の値が書き込まれている場合は書き換える
- name: スタックにec2idを追加
ansible.builtin.lineinfile:
path: ./delete_aws/roles/delete_aws/vars/main.yml
regexp: '^ec2_id'
line: "{{ 'ec2_id: '+ec2_info.instance_ids[0] }}"
#ssh_configファイルにec2情報を書き込み
- name: ssh_configに書き込み
ansible.builtin.lineinfile:
dest: ./ssh_config
line: "{{ item }}"
with_items:
- "{{ 'Host '+ssh_host }}"
- "{{ ' HostName '+ec2_info.instances[0].network_interfaces[0].association.public_ip }}"
- "{{ ' User '+ec2_user_name }}"
- "{{ ' IdentityFile ~/.ssh/'+keypair_name+'.pem' }}"
変数ファイルは以下です。気をつけるポイントはsec_ipです。こちらには自分の使用しているIPアドレスを書き込んでください。ワードプレスへのアクセスやssh接続などは全てここに書かれたIPアドレスのみに制限します。自分のIPアドレスがわからない方はこちらのサイトで確認してください。インスタンスは無料枠を指定しているのでそのままで大丈夫です。
# vars file for aws
#リージョン
region: "ap-northeast-1"
#VPC名
vpc_name: "wordpress-vpc"
#VPC CIDR
vpc_cidr: "10.0.0.0/16"
#サブネット
subnet:
- { subnet_cidr: "10.0.1.0/24" ,subnet_az: "ap-northeast-1a" ,subnet_name: "wordpress-sabnet1" }
- { subnet_cidr: "10.0.2.0/24" ,subnet_az: "ap-northeast-1c" ,subnet_name: "wordpress-sabnet2" }
#IGW名
igw_name: "wordpress-igw"
#IGWアタッチ先サブネットCIDR
attache_igw_subnet:
- "10.0.1.0/24"
- "10.0.2.0/24"
#ルートテーブル名
routetable_name: "wordpress-rt"
#ec2キーペア名
keypair_name: "wordpress-ec2-key"
#セキュリティグループ名
secgrp_name: "wordpress-secgrp"
#セキュリティグループ定義
description: "wordpress Security Group"
#インバウンドルール ポート番号(TCP)
sec_tcp_ports:
- "443"
- "53"
- "80"
- "22"
#インバウンドルール 許可IPアドレス
sec_ip:
- "x.x.x.x/32"
#ec2名
ec2_name: wordpress-ec2
#ec2インスタンスタイプ
instance_type: t2.micro
#ec2 イメージid AmazonLinux2(ami-0bba69335379e17f8)推奨
image: ami-0bba69335379e17f8
#ec2ユーザー名
ec2_user_name: ec2-user
Webサーバロール
ここからはEC2内への操作がメインとなります。変数ファイルは使いません。変数化したいものがあれば適宜追加してください。今回は分かりやすく変数ファイルは使っていません。
さてここで一つ問題になってくるのがansible.builtin.shellモジュールです。Ansibleではshellモジュールはなるべく使わないという風潮があります。なぜなら冪等性が担保されていないからです。AWSロールの時のように状態の確認などで使う分には問題ないのですが実際に操作を加える時には好まれないのが実際のところです。それでも今回インストールにはamazon-linux-extrasが必要です。しかしこのコマンドのモジュールがAnsibleにはありません。どうやって冪等性を担保するかを他のモジュールを駆使して実装するわけですが、、、今回はshellモジュールのままでOKです。なぜならamazon-linux-extrasコマンド自体に冪等性があるからです。そのため何度amazon-linux-extrasコマンドを実行してもインストールが2回行われることはありません。のでshellモジュールを使っちゃいまーーーす!
# # webserver構築
- name: LAMPとPHP7.2のインストール
ansible.builtin.shell: amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2
- name: ApacheとMariaDBインストール
ansible.builtin.yum:
name:
- httpd
- mariadb-server
- MySQL-python
state: latest
- name: PyMySQLインストール
ansible.builtin.pip:
name: PyMySQL
- name: Apache起動
ansible.builtin.systemd:
name: httpd
state: started
enabled: yes
- name: ec2-userをapacheグループに追加
ansible.builtin.user:
name: ec2-user
groups: apache
append: yes
- name: /var/www配下のユーザーとグループを変更する。
ansible.builtin.file:
path: /var/www
owner: ec2-user
group: apache
recurse: yes
- name: /var/wwwとその配下のディレクトリの権限変更
ansible.builtin.file:
path: /var/www/
state: directory
recurse: yes
mode: '2755'
ワードプレスロール
次はワードプレスの構築です。パスワード等はAWSチュートリアルのままにしちゃってます。適宜変えてください。これこそ変数ファイルに入れてもよさそうです。
# # wordpress構築
- name: ワードプレスダウンロード
ansible.builtin.get_url:
url: https://wordpress.org/latest.tar.gz
dest: /home/ec2-user/wordpress.tar.gz
- name: ワードプレス解凍
ansible.builtin.unarchive:
src: /home/ec2-user/wordpress.tar.gz
dest: /home/ec2-user
remote_src: yes
- name: MariaDB起動
ansible.builtin.systemd:
name: mariadb
state: started
enabled: yes
- name: DB作成
community.mysql.mysql_db:
name: wordpress-db
- name: ユーザー作成
community.mysql.mysql_user:
name: wordpress-user
host: localhost
password: your_strong_password
priv: 'wordpress-db.*:ALL,GRANT'
- name: ワードプレス設定ファイルコピー
ansible.builtin.copy:
src: /home/ec2-user/wordpress/wp-config-sample.php
dest: /home/ec2-user/wordpress/wp-config.php
remote_src: yes
- name: ワードプレス設定ファイル編集
ansible.builtin.replace:
path: /home/ec2-user/wordpress/wp-config.php
regexp: "{{ item.before }}"
replace: "{{ item.after }}"
with_items:
- { before: 'database_name_here', after: 'wordpress-db'}
- { before: 'username_here', after: 'wordpress-user'}
- { before: 'password_here', after: 'your_strong_password'}
- name: ワードプレスファイル移動
ansible.builtin.copy:
src: /home/ec2-user/wordpress
dest: /var/www/html/
remote_src: yes
AWS削除ロール
最後はAWS環境を削除するロールです。間違って実行しないようにディレクトリを別に分けています。そこだけ注意してください。やってることは簡単でAWS構築時に変数ファイルに書かれたIDをCLIを使って削除しているだけです。削除するだけなので冪等性とかは考えていません。
- name: EC2インスタンス削除
shell: aws ec2 terminate-instances --instance-ids "{{ ec2_id }}"
register: ec2_delete_info
- name: ec2削除確認
debug:
msg: "{{ ec2_delete_info.stdout }}"
- name: ec2が終了済みになるまで待機
shell: aws ec2 describe-instances --instance-ids "{{ ec2_id }}" --query "Reservations[].Instances[].State.Name | [0]" | tr -d "\""
register: state
until: state.stdout == "terminated"
retries: 18
delay: 10
- name: キーペア削除
shell: aws ec2 delete-key-pair --key-name "{{ ec2_key_pair_name }}"
register: key_pair_delete_info
- name: キーペア削除確認
debug:
msg: "{{ key_pair_delete_info.stdout }}"
- name: ローカルのキーペア削除
shell: rm {{ ec2_key_pair_path+ec2_key_pair_name+'.pem' }}
- name: IGWデタッチ
shell: aws ec2 detach-internet-gateway --internet-gateway-id "{{ IGW_id }}" --vpc-id "{{ vpc_id }}"
register: IGW_detach_info
- name: IGWデタッチ確認
debug:
msg: "{{ IGW_detach_info.stdout }}"
- name: IGW削除
shell: aws ec2 delete-internet-gateway --internet-gateway-id "{{ IGW_id }}"
register: IGW_delete_info
- name: IGW削除確認
debug:
msg: "{{ IGW_delete_info.stdout }}"
- name: サブネット1削除
shell: aws ec2 delete-subnet --subnet-id "{{ subnet1_id }}"
register: subnet1_delete_info
- name: サブネット1削除確認
debug:
msg: "{{ subnet1_delete_info.stdout }}"
- name: サブネット2削除
shell: aws ec2 delete-subnet --subnet-id "{{ subnet2_id }}"
register: subnet2_delete_info
- name: サブネット2削除確認
debug:
msg: "{{ subnet2_delete_info.stdout }}"
- name: ルートテーブル削除
shell: aws ec2 delete-route-table --route-table-id "{{ route_table_id }}"
register: RT_delete_info
- name: ルートテーブル削除確認
debug:
msg: "{{ RT_delete_info.stdout }}"
- name: セキュリティグループ削除
shell: aws ec2 delete-security-group --group-id "{{ SG_id }}"
register: SG_delete_info
- name: セキュリティグループ削除確認
debug:
msg: "{{ SG_delete_info.stdout }}"
- name: VPC削除
shell: aws ec2 delete-vpc --vpc-id "{{ vpc_id }}"
register: vpc_delete_info
- name: VPC削除確認
debug:
msg: "{{ vpc_delete_info.stdout }}"
- name: ssh_config削除
file:
path: ../ssh_config
state: absent
- name: ssh_config空ファイル生成
file:
path: ../ssh_config
state: touch
僕が擬似スタックと呼んでる変数ファイルはこんな感じです。AWS構築時に随時ここにIDが追加されていく仕様にしています。
vpc_id:
IGW_id:
route_table_id:
subnet1_id:
subnet2_id:
SG_id:
ec2_key_pair_name:
ec2_key_pair_path: ~/.ssh/
ec2_id:
構築後は以下みたいな感じになります。この値を取り出してCLIで一括削除すると言うわけですね。
vpc_id: vpc-08f19dc7eafd47b2e
IGW_id: igw-09223a23fce6a0892
route_table_id: rtb-0d548ddf8d868fc5a
subnet1_id: subnet-0ebdea41fde8585cb
subnet2_id: subnet-0390a316b377e37d1
SG_id: sg-00fa631c83d56555c
ec2_key_pair_name: wordpress-ec2-key
ec2_key_pair_path: ~/.ssh/
ec2_id: i-07f640f45bd5c9a1e
Ansible実行
さて準備は整いましたので実行していきたいと思います。の前にAWS CLIだけローカルで使えるようにしておいてください。こちらがわかりやすいかなと思います。
それでは最後に実行していきます。以下のPlaybookファイルを作成します。ここでは先ほど作成したロールを順番に実行していくものです。
#AWS環境を立ち上げるロール
- name: create vpc subnet igw routetable
hosts: localhost
connection: local
roles:
- aws_vpc
#ec2内にwebサーバーを立ち上げるロール
- name: create web server
vars_files: group_vars/all.yml
hosts: "{{ ssh_host }}"
become: yes
become_user: root
roles:
- web_server
#ec2内にワードプレスを立ち上げるロール
- name: create editor
vars_files: group_vars/all.yml
hosts: "{{ ssh_host }}"
become: yes
become_user: root
roles:
- wordpress
そして実行は以下で行ってください。
ansible-playbook -i hosts create.yml
問題なければ最後までエラーなく実行が終わるかと思います。ワードプレスへのアクセスはコンソールからパブリックDNSを確認し、ブラウザで「パブリックDNS/wordpress」で検索すると制限したIPアドレスでのみアクセスできます。
最後に消す場合です。delete_aws配下にdelete.ymlを書きます。
- name: AWS環境削除
hosts: localhost
connection: local
roles:
- delete_aws
そして実行します。
ansible-playbook -i hosts delete_aws/delete.yml
これでAWS環境が一括削除されます。
その他
Ansible実行時に以下の警告が出る場合があります。これはコントロール先のEC2のどのpythonを使うか指定されてないからわかんないよと言う警告です。
[WARNING]: Platform linux on host my_host is using the discovered Python interpreter at /usr/bin/python3.7, but future installation of another
Python interpreter could change the meaning of that path. See https://docs.ansible.com/ansible-
core/2.12/reference_appendices/interpreter_discovery.html for more information.
別に無視でも問題ないのですが気になる方はansible.cfgに以下の設定を足してください。
[defaults]
interpreter_python=/usr/bin/python3
最後に
今回はEC2にワードプレス環境を構築しました。しかし今回のやり方に課題もあります。それはAnsibleがMacに依存していると言うことです。同じことを別のPCやOSで実行すると使えていたモジュールが使えなかったり、ansibleのバージョン差分でエラーが起きたりします。
次回はこれを解決するべくAnsibleをコンテナ内で実行することにより環境差分を無くすansible-runnerを使ってワードプレス構築をやっていきたいと思います。