Skip to content

ローカル環境 から Ansible を実行し EC2 インスタンスを設定する

はじめに

前回から Ansible と Packer に入門するPacker と Ansible で Docker イメージを作成する から Ansible について勉強してきました。

しかし、 Docker イメージを使用して Ansible の playbook を作成しても、CPU のアーキテクチャの違いなどから EC2 インスタンスで必ず実行できるとは限りません。

今回はローカル環境 ( M1 Mac ) で Ansible を実行し EC2 インスタンスでも実行できる playbook を作成したいと思います。

成果物

https://github.com/kntks/blog-code/tree/main/2024/01/ansible-packer-ec2

環境

バージョン
MacVentura 13.2.1
Packerv1.10.0
Ansible2.10.17
Docker cli24.0.7
session-manager-plugin1.2.463.0

前提

  • ansible, terraform, packer がローカル環境にインストールされていること
  • session-manager-plugin がローカル環境にインストールされていること
  • 環境変数を定義して aws cli が使用できること
    AWS_ACCESS_KEY_ID=
    AWS_SECRET_ACCESS_KEY=
    AWS_DEFAULT_REGION=ap-northeast-1
    # https://github.com/ansible/ansible/issues/32499#issuecomment-341578864
    OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES

Example1: Session Managerを通してSSH接続する

まずは Terraform で EC2 インスタンスを作成したあと、ローカルから SSH を使って Ansible を実行できるか確認します。

SSHキーを作成する

ローカル環境で、private key と public key を作成します。

Terminal window
$ ssh-keygen -t ed25519 -f ~/.ssh/id_ansible -N ""
$ ls ~/.ssh | grep ansible
id_ansible
id_ansible.pub

Terraformで環境構築

まずは EC2 のスポットインスタンスを作成し、Session Manager を使用できるようにします。

public key をクリップボードにコピーします。

Terminal window
$ cat ~/.ssh/id_ansible.pub | pbcopy

コピーした public key を変数として terraform.tfvars に設定します。

terraform/terraform.tfvars
public_key="public key をペーストする"
terraform/main.tf
terraform/main.tf
data "aws_caller_identity" "current" {}
data "aws_iam_policy" "ssm_managed_instance_core" {
arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
data "aws_iam_policy_document" "instance_assume_role_policy" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}
resource "aws_iam_role" "ssm_instance_core" {
name = "AnsibleTestRole"
assume_role_policy = data.aws_iam_policy_document.instance_assume_role_policy.json
managed_policy_arns = [data.aws_iam_policy.ssm_managed_instance_core.arn]
}
resource "aws_iam_instance_profile" "ssm_instance_core" {
name = "AnsibleTestRole"
role = aws_iam_role.ssm_instance_core.name
}
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-**"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
resource "aws_key_pair" "ssh" {
key_name = "ansible-tmp"
public_key = var.public_key
}
resource "aws_instance" "this" {
ami = data.aws_ami.ubuntu.id
instance_market_options {
market_type = "spot"
spot_options {
max_price = 0.0024
}
}
instance_type = "t3.micro"
iam_instance_profile = aws_iam_instance_profile.ssm_instance_core.name
associate_public_ip_address = true
subnet_id = aws_subnet.ssm_public_subnet.id
key_name = aws_key_pair.ssh.key_name
tags = {
Name = "ansible-spot"
}
}

あとは terraform コマンドを実行するだけです。

Terminal window
$ cd terraform
$ pwd
/path/to/blog-code/2024/01/ansible-packer-ec2/terraform
$ terraform init
$ terraform plan
$ terraform apply -auto-approve

インスタンスIDを取得

次に AWS CLI からインスタンス ID を取得します。

Terminal window
$ aws ec2 describe-instances --filter "Name=tag:Name,Values=ansible-spot" "Name=instance-state-name,Values=running" --query "Reservations[].Instances[].InstanceId" --output text

nginxがインストールされていないことを確認する

立ち上がったばかりの EC2 インスタンスに nginx がインストールされていないことを確認して、セッションを終了します。

Terminal window
$ aws ssm start-session --target i-08cf98ce41af6c9b7
$ which nginx
$ echo $?
1
$ systemctl status nginx
Unit nginx.service could not be found.
$ exit

Ansibleの設定

example1/ansible/playbook.yml
---
- hosts: all
become: true
tasks:
- name: Install a Nginx Package
ansible.builtin.apt:
name: nginx
state: present

Ubuntu イメージを使用するので、ansible_userubuntu にします。
1つ前の作業で、取得したインスタンス ID を ansible_host に設定します。

[instance]
instance1 ansible_host=i-08cf98ce41af6c9b7
[all:vars]
ansible_ssh_common_args=-o StrictHostKeyChecking=no -o ProxyCommand="sh -c \"aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'\""
ansible_user='ubuntu'
ansible_become=true
ansible_ssh_private_key_file='~/.ssh/id_ansible'

Ansibleの実行

Terminal window
$ cd ../example1
$ pwd
/path/to/blog-code/2024/01/ansible-packer-ec2/example1
$ ansible-playbook ansible/playbook.yml -i ansible/hosts
PLAY [all] *****************************************************************************************************************************************************************
TASK [Gathering Facts] *****************************************************************************************************************************************************
ok: [instance1]
TASK [Install a Nginx Package] *********************************************************************************************************************************************
changed: [instance1]
PLAY RECAP *****************************************************************************************************************************************************************
instance1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Ansible実行後の確認

再度、セッションを開始して、nginx の有無を確認します。

Terminal window
$ aws ssm start-session --target i-08cf98ce41af6c9b7
Starting session with SessionId: xxxxxxxx-0f650e7ea52d0768f
$ which nginx
/usr/sbin/nginx
$ systemctl status nginx
nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
Active: active (running) since Sun 2023-12-31 06:55:57 UTC; 3min 24s ago
Docs: man:nginx(8)
Process: 1913 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
Process: 1914 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
Main PID: 2009 (nginx)
Tasks: 3 (limit: 1091)
Memory: 6.9M
CPU: 31ms
CGroup: /system.slice/nginx.service
├─2009 "nginx: master process /usr/sbin/nginx -g daemon on; master_process on;"
├─2011 "nginx: worker process" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
└─2012 "nginx: worker process" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""

片付け

Terminal window
$ cd path/to/blog-code/2024/01/ansible-packer-ec2/terraform
$ terraform destroy -auto-approve

Example2: community.awsを使用する

Example1 では、key pair を作成することでローカルから EC2 インスタンスに向けて Ansible を実行しました。しかし、community.aws.aws_ssm を使用すると key pair が不要らしいので、試してみます。

Terraformで環境構築

Example1 の Terraform から以下の設定を追記し、ロールに対して、 s3 のアクセス権限をインラインポリシーとして定義します。

terraform/main.tf
resource "aws_s3_bucket" "ansible" {
bucket = "ansible-bucket-fkejsj"
force_destroy = true
}
data "aws_iam_policy_document" "ansible" {
statement {
actions = [
"s3:List*",
"s3:Get*",
]
resources = ["*"]
}
}
resource "aws_iam_role_policy" "ansible" {
name = "AnsibleEC2InstancePolicy"
role = aws_iam_role.ssm_instance_core.id
policy = data.aws_iam_policy_document.ansible.json
}

インスタンスIDを取得

Terminal window
$ aws ec2 describe-instances --filter "Name=tag:Name,Values=ansible-spot" "Name=instance-state-name,Values=running" --query "Reservations[].Instances[].InstanceId" --output text

nginxがインストールされていないことを確認する

Terminal window
$ aws ssm start-session --target i-0a12687643b2bf362
$ which nginx
$ echo $?
1
$ systemctl status nginx
Unit nginx.service could not be found.
$ exit

Ansibleの設定

変数を vars に定義します。

example2/ansible/playbook.yml
---
- hosts: localhost
gather_facts: false
become: true
vars:
ansible_python_interpreter: /usr/bin/python3 # EC2側のpython path
ansible_connection: aws_ssm
ansible_aws_ssm_instance_id: i-0a12687643b2bf362
ansible_aws_ssm_bucket_name: ansible-bucket-fkejsj
tasks:
- name: Install a Nginx Package
ansible.builtin.apt:
name: nginx
state: present
[defaults]
collections_paths=./.collections

collectionインストール

ansible.cfg に collections_paths を設定したことにより、aws collection をインストールすると、example2/ansible ディレクトリ配下に .collections ファイルが作成されます。

Terminal window
$ cd path/to/blog-code/2024/01/ansible-packer-ec2/example2/ansible
$ ansible-galaxy collection install community.aws
$ ansible-galaxy collection list
Collection Version
------------- -------
amazon.aws 7.1.0
community.aws 7.0.0

Ansibleの実行

設定が完了したので、実行してみます。

Terminal window
$ ansible-playbook playbook.yml
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [localhost] ***********************************************************************************************************************************************************
TASK [Install a Nginx Package] *********************************************************************************************************************************************
[WARNING]: Collection community.aws does not support Ansible version 2.10.17
[WARNING]: Collection amazon.aws does not support Ansible version 2.10.17
changed: [localhost]
PLAY RECAP *****************************************************************************************************************************************************************
localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

CLIのオプションにインスタンスIDを指定する

インスタンス変数を playbook.yml に定義せずに CLI のオプションから渡すとファイルの編集をなくすことができます。

example2/ansible/playbook.yml
---
- hosts: localhost
gather_facts: false
become: true
vars:
ansible_python_interpreter: /usr/bin/python3 # EC2側のpython path
ansible_connection: aws_ssm
ansible_aws_ssm_instance_id: i-0a12687643b2bf362
ansible_aws_ssm_bucket_name: ansible-bucket-fkejsj
tasks:
- name: Install a Nginx Package
ansible.builtin.apt:
name: nginx
state: present
Terminal window
$ ansible-playbook playbook.yml -e "ansible_aws_ssm_instance_id=i-0a12687643b2bf362"

Ansible実行後の確認

Nginx がインストールされているか確認してみると、問題なさそうです。

Terminal window
$ aws ssm start-session --target i-0a12687643b2bf362
$ which nginx
/usr/sbin/nginx
$ systemctl status nginx
nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2024-01-01 07:27:37 UTC; 40s ago
Docs: man:nginx(8)
Process: 1995 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
Process: 1996 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
Main PID: 2091 (nginx)
Tasks: 3 (limit: 1091)
Memory: 6.8M
CPU: 28ms
CGroup: /system.slice/nginx.service
├─2091 "nginx: master process /usr/sbin/nginx -g daemon on; master_process on;"
├─2093 "nginx: worker process" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
└─2094 "nginx: worker process" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""

community.aws.aws_ssm connection – connect to EC2 instances via AWS Systems Manager

片付け

コレクション削除

Terminal window
$ cd path/to/blog-code/2024/01/ansible-packer-ec2/example2/ansible
$ rm -r .collections

AWS リソース削除

Terminal window
$ cd path/to/blog-code/2024/01/ansible-packer-ec2/terraform
$ terraform destroy -auto-approve

トラブルシューティングメモ

Failed to import the required Python library (boto3)

Terminal window
$ ansible-playbook playbook.yml
fatal: [localhost]: FAILED! => {"msg": "Failed to import the required Python library (boto3) on <PC 名>'s Python /Users/xxxx/.local/share/rtx/installs/ansible-base/2.10.17/venv/bin/python3. Please read the module documentation and install it in the appropriate location. If the required library is installed, but Ansible is using the wrong Python interpreter, please consult the documentation on ansible_python_interpreter"}

解決策

Terminal window
$ /Users/xxx/.local/share/rtx/installs/ansible-base/2.10.17/venv/bin/python3 -m pip install boto3

The error was: botocore.exceptions.ClientError: An error occurred (AccessDenied) when calling the GetBucketLocation operation: Access Denied

GetBucketLocation の権限が足りないらしいので、EC2 のインスタンスプロファイルに権限を追加したのですが、エラーが直りませんでした。

解決策 S3 バケットの作成し忘れだったので Terraform で aws_s3_bucket を追記しました。

As per boto3 documentation, “Access Denied” is returned when there is a permission’s issue. However real life showed me this problem might also appear when the referenced bucket name doesn’t exist.

boto3のドキュメントによると、パーミッションに問題がある場合、“Access Denied “が返されます。しかし、実際にやってみると、参照したバケット名が存在しない場合にもこの問題が発生することがわかりました。 引用:Boto3 get_bucket_location returns: Access Denied when using variable - stackoverflow

参考