はじめに
前回から 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
環境
バージョン Mac Ventura 13.2.1 Packer v1.10.0 Ansible 2.10.17 Docker cli 24.0.7 session-manager-plugin 1.2.463.0
前提
ansible, terraform, packer がローカル環境にインストールされていること
session-manager-plugin がローカル環境にインストールされていること
環境変数を定義して aws cli が使用できること
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 を作成します。
$ ssh-keygen -t ed25519 -f ~/.ssh/id_ansible -N ""
$ ls ~/.ssh | grep ansible
まずは EC2 のスポットインスタンスを作成し、Session Manager を使用できるようにします。
public key をクリップボードにコピーします。
$ cat ~/.ssh/id_ansible.pub | pbcopy
コピーした public key を変数として terraform.tfvars
に設定します。
public_key = "public key をペーストする"
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" {
actions = [ "sts:AssumeRole" ]
identifiers = [ "ec2.amazonaws.com" ]
resource "aws_iam_role" "ssm_instance_core" {
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" {
role = aws_iam_role . ssm_instance_core . name
data "aws_ami" "ubuntu" {
values = [ "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-**" ]
name = "virtualization-type"
resource "aws_key_pair" "ssh" {
public_key = var . public_key
resource "aws_instance" "this" {
ami = data . aws_ami . ubuntu . id
instance_market_options {
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
あとは terraform コマンドを実行するだけです。
/path/to/blog-code/2024/01/ansible-packer-ec2/terraform
$ terraform apply -auto-approve
インスタンスIDを取得
次に AWS CLI からインスタンス ID を取得します。
$ 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 がインストールされていないことを確認して、セッションを終了します。
$ aws ssm start-session --target i-08cf98ce41af6c9b7
Unit nginx.service could not be found.
Ansibleの設定
- name : Install a Nginx Package
Ubuntu イメージを使用するので、ansible_user
を ubuntu
にします。
1つ前の作業で、取得したインスタンス ID を ansible_host
に設定します。
instance1 ansible_host=i-08cf98ce41af6c9b7
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_ssh_private_key_file='~/.ssh/id_ansible'
Ansibleの実行
/path/to/blog-code/2024/01/ansible-packer-ec2/example1
$ ansible-playbook ansible/playbook.yml -i ansible/hosts
PLAY [all] *****************************************************************************************************************************************************************
TASK [Gathering Facts] *****************************************************************************************************************************************************
TASK [Install a Nginx Package] *********************************************************************************************************************************************
PLAY RECAP *****************************************************************************************************************************************************************
instance1 : ok= 2 changed= 1 unreachable= 0 failed= 0 skipped= 0 rescued= 0 ignored= 0
Ansible実行後の確認
再度、セッションを開始して、nginx の有無を確認します。
$ aws ssm start-session --target i-08cf98ce41af6c9b7
Starting session with SessionId: xxxxxxxx-0f650e7ea52d0768f
● 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
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 )
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" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
片付け
$ cd path/to/blog-code/2024/01/ansible-packer-ec2/terraform
$ terraform destroy -auto-approve
Example1 では、key pair を作成することでローカルから EC2 インスタンスに向けて Ansible を実行しました。しかし、community.aws.aws_ssm を使用すると key pair が不要らしいので、試してみます。
Example1 の Terraform から以下の設定を追記し、ロールに対して、 s3 のアクセス権限をインラインポリシーとして定義します。
resource "aws_s3_bucket" "ansible" {
bucket = "ansible-bucket-fkejsj"
data "aws_iam_policy_document" "ansible" {
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を取得
$ aws ec2 describe-instances --filter "Name=tag:Name,Values=ansible-spot" "Name=instance-state-name,Values=running" --query "Reservations[].Instances[].InstanceId" --output text
nginxがインストールされていないことを確認する
$ aws ssm start-session --target i-0a12687643b2bf362
Unit nginx.service could not be found.
Ansibleの設定
変数を 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
- name : Install a Nginx Package
collections_paths=./.collections
collectionインストール
ansible.cfg に collections_paths
を設定したことにより、aws collection をインストールすると、example2/ansible
ディレクトリ配下に .collections
ファイルが作成されます。
$ cd path/to/blog-code/2024/01/ansible-packer-ec2/example2/ansible
$ ansible-galaxy collection install community.aws
$ ansible-galaxy collection list
Ansibleの実行
設定が完了したので、実行してみます。
$ 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
PLAY RECAP *****************************************************************************************************************************************************************
localhost : ok= 1 changed= 1 unreachable= 0 failed= 0 skipped= 0 rescued= 0 ignored= 0
CLIのオプションにインスタンスIDを指定する
インスタンス変数を playbook.yml に定義せずに CLI のオプションから渡すとファイルの編集をなくすことができます。
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
- name : Install a Nginx Package
$ ansible-playbook playbook.yml -e "ansible_aws_ssm_instance_id=i-0a12687643b2bf362"
Ansible実行後の確認
Nginx がインストールされているか確認してみると、問題なさそうです。
$ aws ssm start-session --target i-0a12687643b2bf362
● 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
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 )
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
片付け
コレクション削除
$ cd path/to/blog-code/2024/01/ansible-packer-ec2/example2/ansible
AWS リソース削除
$ cd path/to/blog-code/2024/01/ansible-packer-ec2/terraform
$ terraform destroy -auto-approve
トラブルシューティングメモ
Failed to import the required Python library (boto3)
$ 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"}
解決策
$ /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
参考