ローカル環境 から 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接続する
Section titled “Example1: Session Managerを通してSSH接続する”まずは Terraform で EC2 インスタンスを作成したあと、ローカルから SSH を使って Ansible を実行できるか確認します。
SSHキーを作成する
Section titled “SSHキーを作成する”ローカル環境で、private key と public key を作成します。
$ ssh-keygen -t ed25519 -f ~/.ssh/id_ansible -N ""
$ ls ~/.ssh | grep ansibleid_ansibleid_ansible.pubTerraformで環境構築
Section titled “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を取得
Section titled “インスタンス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 textnginxがインストールされていないことを確認する
Section titled “nginxがインストールされていないことを確認する”立ち上がったばかりの EC2 インスタンスに nginx がインストールされていないことを確認して、セッションを終了します。
$ aws ssm start-session --target i-08cf98ce41af6c9b7
$ which nginx$ echo $?1$ systemctl status nginxUnit nginx.service could not be found.
$ exitAnsibleの設定
Section titled “Ansibleの設定”---- hosts: all  become: true
  tasks:    - name: Install a Nginx Package      ansible.builtin.apt:        name: nginx        state: presentUbuntu イメージを使用するので、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の実行
Section titled “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=0Ansible実行後の確認
Section titled “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-approveExample2: community.awsを使用する
Section titled “Example2: community.awsを使用する”Example1 では、key pair を作成することでローカルから EC2 インスタンスに向けて Ansible を実行しました。しかし、community.aws.aws_ssm を使用すると key pair が不要らしいので、試してみます。
Terraformで環境構築
Section titled “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を取得
Section titled “インスタンスIDを取得”$ aws ec2 describe-instances --filter "Name=tag:Name,Values=ansible-spot" "Name=instance-state-name,Values=running" --query "Reservations[].Instances[].InstanceId" --output textnginxがインストールされていないことを確認する
Section titled “nginxがインストールされていないことを確認する”$ aws ssm start-session --target i-0a12687643b2bf362
$ which nginx$ echo $?1$ systemctl status nginxUnit nginx.service could not be found.
$ exitAnsibleの設定
Section titled “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=./.collectionscollectionインストール
Section titled “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.0Ansibleの実行
Section titled “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=0CLIのオプションにインスタンスIDを指定する
Section titled “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実行後の確認
Section titled “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 .collectionsAWS リソース削除
$ cd path/to/blog-code/2024/01/ansible-packer-ec2/terraform$ terraform destroy -auto-approveトラブルシューティングメモ
Section titled “トラブルシューティングメモ”Failed to import the required Python library (boto3)
Section titled “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 boto3The error was: botocore.exceptions.ClientError: An error occurred (AccessDenied) when calling the GetBucketLocation operation: Access Denied
Section titled “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