おはようございます、わっしょい村です。前回は実際にAnsibleを使ってEC2にワードプレスを構築しました。
しかし前回の普通のAnsibleではAnsibleをインストールした環境によって使えるモジュールなどの環境差分が出てくる可能性があります。今回は環境差分を無くすべく、Ansibleの実行をコンテナ内で行うことができるAnsible Runnerを解説していきます。
Ansible Runnerとは
Ansible Runnerは簡単に言うとAnsibleをいい感じに実行してくれるツールです(雑)。このいい感じというのは何個かメリットがあります。できることは以下です。
- 実行履歴を記録してくれる。
- コンテナから実行することができるため環境差分問題がない。
- 決まったディレクトリ構造があり余計なファイル指定がいらない。
などなど他にもありそうですが僕が受けてる恩恵はこれくらいです。ディレクトリ構造については言葉だけではわかりづらいかと思いますが、例えばインベントリファイルなどです。前回はansible実行時に-iオプションでhostsファイルを指定していました。Ansible Runnerではインベントリファイルの保存場所が決まっていたりとディレクトリ構造にルールが設けられているのでチーム内で設定ファイルの理解がしやすかったり余計なオプションの指定などが不要になったりします。
全体像
前回同様やることはEC2へのワードプレス構築です。ただしディレクトリ構造が少し変わっています。
├── env
│ └── settings
├── inventory
│ └── hosts
└── project
├── ansible.cfg
├── create.yml
├── delete_aws
│ ├── delete.yml
│ └── roles
│ └── delete_aws
│ ├── tasks
│ │ └── main.yml
│ └── vars
│ └── main.yml
├── group_vars
│ └── all.yml
├── roles
│ ├── aws_vpc
│ │ ├── files
│ │ │ ├── config
│ │ │ └── credentials
│ │ ├── tasks
│ │ │ └── main.yml
│ │ └── vars
│ │ └── main.yml
│ ├── web_server
│ │ ├── tasks
│ │ │ └── main.yml
│ │ └── vars
│ │ └── main.yml
│ └── wordpress
│ ├── tasks
│ │ └── main.yml
│ └── vars
│ └── main.yml
└── ssh_config
基本的なファイル群はprojectディレクトリ配下に移動しています。そしてhostsファイルはinventory配下に移動しています。これがansible-runnerのディレクトリ構造です。そして新しくsettingsファイルが追加されています。こちらは後で解説します。
実行準備(ansible-builder)
今回はDockerコンテナを使用します。Dockerのインストールは以下記事をご確認ください。
Ansible Runnerで実行するためには実行環境をビルドする必要があります。そこで登場するのがAnsible Builderです。大まかな流れとしてAnsible Builderでビルドし、その後にAnsible Runnerで実行するイメージです。まずはAnsible BuilderとRunnerをインストールします。
$pip3 install ansible-builder ansible-runner
それではビルドに移ります。まずはディレクトリを作成します。
$ mkdir ansible-docker
$ cd ansible-docker
execution-environment.ymlを作成。
---
version: 1
dependencies:
galaxy: requirements.yml
python: requirements.txt
additional_build_steps:
prepend: |
RUN pip3 install --upgrade pip
requirements.txtを作成。これは必要なpythonライブラリを書くことで実行環境にインストールすることができます。AWS周りのライブラリを入れておきましょう。
boto
boto3
botocore
awscli
requirements.ymlにはAnsibleで使用するモジュールを書きます。基本的にansible.builtin以外は全部入れておくといいでしょう。今回は以下です。
collections:
- amazon.aws
- community.aws
- community.mysql
これでOKです。
最後にビルドを実行します。tagにはわかりやすい名前を設定しておきましょう。実行完了までに数分かかります。
$ ansible-builder build --tag aws_wordpress_image
実行(ansible-runner)
ビルドが完了したらansible-runnerの実行に移ります。env/settingsファイルに先ほどビルドしたイメージを指定します。
container_image: aws_wordpress_image
process_isolation_executable: docker
process_isolation: true
これだけで準備完了です。roles配下やその他のファイル内容は基本的には前回と一緒です。ただしAWS周りのmain.ymlのみ少々変更があるのでそれだけ以下に示します。実行コンテナ内には.sshディレクトリや.awsディレクトリがないのでそれを作成しています。またアクセスキーなどの情報もないのでファイルとして用意する必要があります。
- name: ".awsディレクトリ作成"
ansible.builtin.file:
dest: ~/.aws/
state: directory
- name: "AWS用のconfigファイル作成"
ansible.builtin.copy:
src: ../files/config
dest: ~/.aws/config
- name: "AWS用のcredentialsファイル作成"
ansible.builtin.copy:
src: ../files/credentials
dest: ~/.aws/credentials
# VPC作成
- 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 }}"
# IGWの作成
- 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: "sshディレクトリ作成"
ansible.builtin.file:
dest: ~/.ssh/
state: directory
#鍵用の空ファイル作成
- name: 鍵用の空ファイル作成
ansible.builtin.file:
path: ~/.ssh/{{ keypair_info.key.name }}.pem
state: touch
mode: 0600
#作成された空ファイルに秘密鍵を書き込む
- name: 空ファイルに秘密鍵を書き込む
ansible.builtin.shell: echo "{{ keypair_info.key.private_key }}" > ~/.ssh/"{{ keypair_info.key.name }}".pem
#セキュリティグループ作成
- 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 }}"
#ec2作成
- 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' }}"
アクセスキーやシークレットキーは既に設定済みの環境でcat ~/.aws/credentialsなどで確認するのが早いと思います。シークレットキーは暗号化されているので注意が必要です。
[default]
region = ap-northeast-1
output = json
[default]
aws_access_key_id = アクセスキー
aws_secret_access_key = シークレットキー
実際に実行してみましょう。コマンドは以下です。
$ ansible-runner run . -p create.yml
project/create.ymlの指定が必要では?となるかもしれませんがその指定は不要です。ansible-runnerはインベントリファイルはinventoryディレクトリ配下、他のファイルはprojectディレクトリ配下を勝手に参照するようになっています。
また実行履歴はartifactsディレクトリが勝手に作られその中に保存されます。
AWS環境の削除は以下コマンドでできます。
$ ansible-runner run . -p delete_aws/delete.yml
delete時のmain.ymlも前回と異なる部分があるので以下を使ってください。ローカルの秘密鍵の削除が不要になったり、.aws/configファイルの追加などがあります。
- name: ".awsディレクトリ作成"
ansible.builtin.file:
dest: ~/.aws/
state: directory
- name: "AWS用のconfigファイル作成"
ansible.builtin.copy:
src: ../../../../roles/aws_vpc/files/config
dest: ~/.aws/config
- name: "AWS用のcredentialsファイル作成"
ansible.builtin.copy:
src: ../../../../roles/aws_vpc/files/credentials
dest: ~/.aws/credentials
- name: EC2インスタンス削除
ansible.builtin.shell: aws ec2 terminate-instances --instance-ids "{{ ec2_id }}"
register: ec2_delete_info
- name: ec2削除確認
debug:
msg: "{{ ec2_delete_info.stdout }}"
- name: ec2が終了済みになるまで待機
ansible.builtin.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: キーペア削除
ansible.builtin.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: IGWデタッチ
ansible.builtin.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削除
ansible.builtin.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削除
ansible.builtin.shell: aws ec2 delete-subnet --subnet-id "{{ subnet1_id }}"
register: subnet1_delete_info
- name: サブネット1削除確認
debug:
msg: "{{ subnet1_delete_info.stdout }}"
- name: サブネット2削除
ansible.builtin.shell: aws ec2 delete-subnet --subnet-id "{{ subnet2_id }}"
register: subnet2_delete_info
- name: サブネット2削除確認
debug:
msg: "{{ subnet2_delete_info.stdout }}"
- name: ルートテーブル削除
ansible.builtin.shell: aws ec2 delete-route-table --route-table-id "{{ route_table_id }}"
register: RT_delete_info
- name: ルートテーブル削除確認
debug:
msg: "{{ RT_delete_info.stdout }}"
- name: セキュリティグループ削除
ansible.builtin.shell: aws ec2 delete-security-group --group-id "{{ SG_id }}"
register: SG_delete_info
- name: セキュリティグループ削除確認
debug:
msg: "{{ SG_delete_info.stdout }}"
- name: VPC削除
ansible.builtin.shell: aws ec2 delete-vpc --vpc-id "{{ vpc_id }}"
register: vpc_delete_info
- name: VPC削除確認
debug:
msg: "{{ vpc_delete_info.stdout }}"
- name: ssh_config削除
ansible.builtin.file:
path: ../ssh_config
state: absent
- name: ssh_config空ファイル生成
ansible.builtin.file:
path: ../ssh_config
state: touch
次回 Docker Compose編
今回はEC2への操作でしたがもっと気軽に検証環境を作成するにはローカルにコンテナを立てるのが手っ取り早いです。次回はローカルPCにDocker Composeでコンテナをたて、そこにAnsibleでワードプレス環境を構築していくやり方を解説します。