ローカル環境 から 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
環境
バージョン | |
---|---|
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_ACCESS_KEY_ID=AWS_SECRET_ACCESS_KEY=AWS_DEFAULT_REGION=ap-northeast-1# https://github.com/ansible/ansible/issues/32499#issuecomment-341578864OBJC_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 ansibleid_ansibleid_ansible.pub
Terraformで環境構築
まずは 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" { 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 コマンドを実行するだけです。
$ 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 を取得します。
$ 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
$ which nginx$ echo $?1$ systemctl status nginxUnit nginx.service could not be found.
$ exit
Ansibleの設定
---- hosts: all become: true
tasks: - name: Install a Nginx Package ansible.builtin.apt: name: nginx state: present
Ubuntu イメージを使用するので、ansible_user
を ubuntu
にします。
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=trueansible_ssh_private_key_file='~/.ssh/id_ansible'
Ansibleの実行
$ 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 の有無を確認します。
$ 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" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
片付け
$ 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 のアクセス権限をインラインポリシーとして定義します。
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を取得
$ 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
$ which nginx$ echo $?1$ systemctl status nginxUnit nginx.service could not be found.
$ exit
Ansibleの設定
変数を vars
に定義します。
---- 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
ファイルが作成されます。
$ 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.0community.aws 7.0.0
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.17changed: [localhost]
PLAY RECAP *****************************************************************************************************************************************************************localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
CLIのオプションにインスタンスIDを指定する
インスタンス変数を playbook.yml に定義せずに CLI のオプションから渡すとファイルの編集をなくすことができます。
--- - 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
$ ansible-playbook playbook.yml -e "ansible_aws_ssm_instance_id=i-0a12687643b2bf362"
Ansible実行後の確認
Nginx がインストールされているか確認してみると、問題なさそうです。
$ 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
片付け
コレクション削除
$ cd path/to/blog-code/2024/01/ansible-packer-ec2/example2/ansible$ rm -r .collections
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