Skip to content

Ansible と Packer に入門する

はじめに

EC2 の AMI を作成する方法として以下の方法があります。

  1. AMI Image Buider
  2. Packer + Ansible
  3. Packer
  4. その他

参画している案件では、 Packer + Ansible で AMI を作成しています。
自分で Ansible の設定した訳ではないため、参画している案件の Ansible の設定はなんとなくしか理解していません。 そのため今回は Packer と Ansible についてインプットしながら、アウトプットとして学習の記録を残そうと思います。

目標

Packer + Ansible を使って AMI を作成する

成果物

https://github.com/kntks/blog-code/tree/main/2023/12/ansible-packer-ami

特徴

  • 権限を絞ったロールに AssumeRole をしてから Packer の実行をする
  • SessionManager を利用するので、Security Group のインバウンドを設定しなくても良く、KeyPair の管理も必要ない
  • Packer の定義は Json ではなく HCL2

環境

バージョン
MacVentura 13.2.1
Packerv1.10.0
Ansible2.10.17
Terraform1.6.5

インストール

バージョン管理に rtx を使用します。

Packer

Terminal window
$ rtx plugin install packer
# 最新のバージョンを確認する
$ rtx ls-remote packer
$ rtx install packer 1.10.0
$ rtx local packer 1.10.0

Ansible

Terminal window
$ rtx plugin install ansible-base
# 最新のバージョンを確認する
$ rtx ls-remote ansible-base
$ rtx install ansible-base 2.10.17
$ rtx local ansible-base 2.10.17

インストールされたものを確認する

Terminal window
$ rtx where ansible-base
/Users/xxxxxx/.local/share/rtx/installs/ansible-base/2.10.17
$ ls /Users/xxxxxx/.local/share/rtx/installs/ansible-base/2.10.17
bin tmp venv
$ ls /Users/xxxxxx/.local/share/rtx/installs/ansible-base/2.10.17/bin
ansible ansible-connection ansible-doc ansible-inventory ansible-pull ansible-vault
ansible-config ansible-console ansible-galaxy ansible-playbook ansible-test

Packer

  • Packer の定義には、Hashicorp Configuration Language (以下: HCL) を使用する
  • コメントには、 #///* */ が使用可能
  • 複数行の文字列には、heredoc シンタックスが使用可能

引用:Introduction to Packer HCL2 - Packer Documentation

AWS plugin を使用する

packer ブロック内の required_plugins に指定すれば、packer init . でプラグインをインストールできます。

packer {
required_plugins {
amazon = {
source = "github.com/hashicorp/amazon"
version = "~> 1"
}
}
}

amazon-ebs builder は、ソースAMIを起動し、プロビジョニング後に新しいAMIに再パッケージ化することで、EBSバックアップAMIを作成します。今回はこの builder を使用します。

source "amazon-ebs" "ubuntu" {}

template の書き方は Packer のドキュメントや GitHub に転がっているコードを参考にすると簡単に設定できそうです。

参考 Amazon - Packer Documentation Amazon EBS - Packer Documentation

Ansible

”Hello Ansible” を実行する

とりあえず Hello Ansible したいと思います。

playbook.yml
---
- hosts: all
tasks:
- name: debug
debug:
msg: "Hello Ansible!"
Terminal window
$ ansible-playbook playbook.yml -i localhost, -e ansible_connection=local
PLAY [all] *******************************************************************************************************************************************************************
TASK [Gathering Facts] *******************************************************************************************************************************************************
[WARNING]: Platform darwin on host localhost is using the discovered Python interpreter at /usr/bin/python3, but future installation of another Python interpreter could
change the meaning of that path. See https://docs.ansible.com/ansible/2.10/reference_appendices/interpreter_discovery.html for more information.
ok: [localhost]
TASK [debug] *****************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Hello Ansible!"
}
PLAY RECAP *******************************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

引用: https://rheb.hatenablog.com/entry/ansible_variables

Warning 調査

Warning が出たので確認してみます。

Terminal window
[WARNING]: Platform darwin on host localhost is using the discovered Python interpreter at /usr/bin/python3, but future installation of another Python interpreter could
change the meaning of that path. See https://docs.ansible.com/ansible/2.10/reference_appendices/interpreter_discovery.html for more information.

どうやらローカル (Mac) で実行しているので、そこにインストールされている Python を探して実行しているようです。

Most Ansible modules that execute under a POSIX environment require a Python interpreter on the target host. Unless configured otherwise, Ansible will attempt to discover a suitable Python interpreter on each target host the first time a Python module is executed for that host.

(訳) POSIX 環境で実行されるほとんどの Ansible モジュールは、ターゲットホスト上の Python インタプリタを必要とします。とくに設定しない限り、AnsibleはPythonモジュールがはじめて実行されるときに、各ターゲットホスト上で適切なPythonインタプリタを検出しようとします。

Interpreter Discovery

先ほどの Warning のメッセージでは、 /usr/bin/python3 を使用して task を実行している訳ですね。
/usr/bin/python3 と Homebrew でインストールした python だとバージョンが違うので、環境によっては注意が必要そうです。

Terminal window
$ /usr/bin/python3 --version
Python 3.9.6
$ python3 --version
Python 3.11.4

先ほどの Warning に、詳しくは Interpreter Discovery を読みなさい、と書かれています。
そのページによると、ansible 2.8 はデフォルトで auto_legacy という設定であり、/usr/bin/python が存在する場合は、/usr/bin/python を使用して、警告を発行します と書かれています。 ansible 2.12 だと auto という設定になるようです。

ansible.cfg に関するドキュメントやオプションは Ansible Configuration Settings にあります。
Ansible: ターゲットノードでpython3を使用するansible.cfg設定は、ansible_python_interpreterでは無くinterpreter_python” にわかりやすくまとめられていたので、こちらの方が理解しやすいと思います。

明示的に指定すると、Warning が消えました。

Terminal window
$ ansible-playbook playbook.yml -i localhost, -e ansible_connection=local -e ansible_python_interpreter=/usr/bin/python3
PLAY [all] ********************************************************************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************************************
ok: [localhost]
TASK [debug] ******************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Hello Ansible!"
}
PLAY RECAP ********************************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

ansible-playbook

Ansible.Builtin って何?

Collection は、playbook、role、module、plugin を含むことができる Ansible コンテンツの配布形式のことです。 そんな Ansible.Builtin は Collection と呼ばれるものの1つで Ansible が管理しています。Collection について調べるなら、ansible-collections/overview のリポジトリを読むと良さそうです。

Ansible のドキュメントでは ansible.builtin も書くことを推奨しています。

Use fully qualified collection names (FQCN) to avoid ambiguity in which collection to search for the correct module or plugin for each task. (訳) 完全修飾コレクション名(FQCN)を使用して、各タスクの正しいモジュールやプラグインを検索するコレクションが曖昧にならないようにします。

For builtin modules and plugins, use the ansible.builtin collection name as prefix, for example, ansible.builtin.copy. (訳) 組み込みモジュールとプラグインには、接頭辞として ansible.builtin.copy のように ansible.builtin コレクション名を使用します。

引用:Use fully qualified collection names - Ansible Documentation

参考:Using Ansible collections - Ansible Documentation

Packer と Ansible を統合する

Packer と Ansible についてなんとなく理解してきたところで、実際に EC2 に接続し AMI を作成してみます。ここでは EC2 に接続するための SSH Key Pair は作成せず、AWS SSM を利用します。

Session Manager Connections - Packer Documentation

Terraform で VPC、subnet、Instance Profile を作成する

この main.tf では、2つのロールを作成しています。

  1. ローカルから EC2 インスタンスを立ち上げる + SSM のセッションを開始する用の AssumeRole
  2. 起動したEC2 インスタンスに付与する Insntace Profile

Instance Profile には、AmazonSSMManagedInstanceCore をつけます。

terraform/main.tf
data "aws_caller_identity" "current" {}
data "aws_iam_policy_document" "assume_role" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
principals {
type = "AWS"
identifiers = [data.aws_caller_identity.current.arn]
# identifiers = [ "arn:aws:iam::${data.aws_caller_identity.current.account_id}:user/*" ]
}
}
}
resource "aws_iam_role" "packer" {
name = "AssumePackerAMIBuilderRole"
assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
# Amazon EC2 のアクション、リソース、および条件キー
# https://docs.aws.amazon.com/ja_jp/service-authorization/latest/reference/list_amazonec2.html
data "aws_iam_policy_document" "packer" {
statement {
# IAM Task or Instance Role
# https://developer.hashicorp.com/packer/integrations/hashicorp/amazon#iam-task-or-instance-role
actions = [
"ec2:AttachVolume",
"ec2:AuthorizeSecurityGroupIngress",
"ec2:CopyImage",
"ec2:CreateImage",
"ec2:CreateKeyPair",
"ec2:CreateSecurityGroup",
"ec2:CreateSnapshot",
"ec2:CreateTags",
"ec2:CreateVolume",
"ec2:DeleteKeyPair",
"ec2:DeleteSecurityGroup",
"ec2:DeleteSnapshot",
"ec2:DeleteVolume",
"ec2:DeregisterImage",
"ec2:DescribeImageAttribute",
"ec2:DescribeImages",
"ec2:DescribeInstances",
"ec2:DescribeInstanceStatus",
"ec2:DescribeRegions",
"ec2:DescribeSecurityGroups",
"ec2:DescribeSnapshots",
"ec2:DescribeSubnets",
"ec2:DescribeTags",
"ec2:DescribeVolumes",
"ec2:DetachVolume",
"ec2:GetPasswordData",
"ec2:ModifyImageAttribute",
"ec2:ModifyInstanceAttribute",
"ec2:ModifySnapshotAttribute",
"ec2:RegisterImage",
"ec2:RunInstances",
"ec2:StopInstances",
"ec2:TerminateInstances"
]
resources = ["*"]
}
statement {
actions = [
"iam:GetInstanceProfile",
"iam:PassRole"
]
resources = ["*"]
}
statement {
actions = [
"ssm:StartSession",
"ssm:TerminateSession"
]
resources = ["*"]
}
}
resource "aws_iam_role_policy" "packer" {
name = "PackerEC2InstancePolicy"
role = aws_iam_role.packer.id
policy = data.aws_iam_policy_document.packer.json
}
data "aws_iam_policy" "ssm_managed_instance_core" {
arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
resource "aws_iam_role" "ssm_instance_core" {
name = "PackerAMIBuiderRole"
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 = "PackerAMIBuilderRole"
role = aws_iam_role.ssm_instance_core.name
}
terraform/vpc.tf
resource "aws_vpc" "ssm_vpc" {
cidr_block = "172.16.0.0/16"
tags = {
Name = "aws-ssm-example"
}
}
resource "aws_subnet" "ssm_public_subnet" {
vpc_id = aws_vpc.ssm_vpc.id
cidr_block = "172.16.10.0/24"
availability_zone = "ap-northeast-1a"
tags = {
Name = "ssm-public-subnet"
}
}
resource "aws_internet_gateway" "example_igw" {
vpc_id = aws_vpc.ssm_vpc.id
tags = {
Name = "ssm-example-igw"
}
}
resource "aws_route_table" "example_rt" {
vpc_id = aws_vpc.ssm_vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.example_igw.id
}
tags = {
Name = "ssm-example-rt"
}
}
resource "aws_route_table_association" "ssm_association" {
subnet_id = aws_subnet.ssm_public_subnet.id
route_table_id = aws_route_table.example_rt.id
}
data "aws_iam_policy_document" "instance_assume_role_policy" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}

apply します。

Terminal window
$ terraform init
$ terraform fmt
$ terraform plan
$ terraform apply

Packer の設定ファイルを作成する

公式 Tutorials の設定を参考に設定を追加します。

arn は aws cli から取得します。

Terminal window
$ aws iam get-role --role-name PackerBuildAMI --query 'Role.Arn' --output text
arn:aws:iam::111111222333:role/PackerBuildAMI

Packer の定義は以下のとおりです。

aws-ubuntu.pkr.hcl
packer {
required_plugins {
amazon = {
version = ">= 1.2.8"
source = "github.com/hashicorp/amazon"
}
ansible = {
version = ">= 1.1.1"
source = "github.com/hashicorp/ansible"
}
}
}
source "amazon-ebs" "ubuntu" {
ami_name = "learn-packer-linux-aws"
vpc_id = "vpc-08bc27368c830b6da"
subnet_id = "subnet-018bedd2828774def"
instance_type = "t3.micro"
region = "ap-northeast-1"
source_ami_filter {
filters = {
name = "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-**"
root-device-type = "ebs"
virtualization-type = "hvm"
}
most_recent = true
owners = ["099720109477"]
}
ssh_username = "ubuntu"
ssh_interface = "session_manager"
ssh_timeout = "1h" # default: 5m
communicator = "ssh"
iam_instance_profile = "PackerAMIBuilderRole" # Terraform で作成する
temporary_key_pair_type = "ed25519"
pause_before_ssm = "20s"
associate_public_ip_address = true # EC2をpublic subnetに配置してpublic ipを設定する
aws_polling {
delay_seconds = 60
max_attempts = 60
}
assume_role {
role_arn = "arn:aws:iam::<aws-account-id>:role/AssumePackerAMIBuilderRole"
}
max_retries = 2
}
build {
name = "learn-packer"
sources = [
"source.amazon-ebs.ubuntu"
]
provisioner "ansible" {
# https://developer.hashicorp.com/packer/integrations/hashicorp/ansible/latest/components/provisioner/ansible#default-extra-variables
extra_arguments = [
"-vvvv",
"--scp-extra-args",
"'-O'"
]
user = "ubuntu"
playbook_file = "./playbook.yml"
}
}

Build an image - Packer Documentation

nginx をインスールした AMI イメージを作成してみる

palybook.yml
---
- hosts: all
tasks:
- name: Install the version '1.18.0' of package "nginx" and allow potential downgrades
ansible.builtin.apt:
name: nginx

packer build を実行する

packer の設定も完了したので、 build します。

Terminal window
$ packer init .
$ packer fmt .
$ packer build aws-ubuntu.pkr.hcl

無事に作成することができました。

Terminal window
$ aws ec2 describe-images --owners self --query 'Images[*].[ImageId,Name]'
[
[
"ami-0179e44502917320e",
"learn-packer-linux-aws"
]
]

さいごに

参画している案件では Packer、Ansible を管理しているリポジトリがすでにあるため、そこからコピペすれば簡単に設定できます。しかし 0 から設定してみることで理解を深めたく勉強してみました。
Ansible は variable や role などを1つも使っていないので、また次の機会に設定してみようと思います。

関連資料

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

Retryable error: TargetNotConnected: i-xxxxx is not connected.

Retryable error: TargetNotConnected: i-xxxxx is not connected. というデバッグログが出る場合、そもそもEC2インスタンスに対してセッションを開始できない設定になっている可能性があります。 SSH time out due to AWS Session Manager tunnel failure (connection refused) #9897 という issue に対する回答では pause_before_ssm で SSM を実行するタイミングを遅らせることができる と書いてあります。しかし、Pakcer を使用する以前に SSM を実行できる環境になっているかどうかを調べたほうがいいと思います。

自分の環境では、はじめにデフォルト VPC や subnet を使用していましが、TargetNotConnected というログにより実行できなかったので以前 “publicサブネットに配置したAWS EC2インスタンスにssmでログインする” に書いた設定をそのまま使用したらエラーを解消できました。

SSH handshake err: ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey], no supported methods remain

aws-ubuntu.pkr.hcl ファイルで ssh_interface を session_manager や ssh_interface の行をコメントアウトしても handshake に失敗しました。
どうやら Ubuntu 22.04 でも発生するらしいです。
SSH error while building image with CentOS stream 9 cloud images #11656

Terminal window
$ PACKER_LOG=1 packer build aws-ubuntu.pkr.hcl
...
==> learn-packer.amazon-ebs.ubuntu: Waiting for SSH to become available...
2023/12/25 11:40:03 packer-plugin-amazon_v1.2.8_x5.0_darwin_arm64 plugin: 2023/12/25 11:40:03 Using host value: 57.180.37.30
2023/12/25 11:40:03 packer-plugin-amazon_v1.2.8_x5.0_darwin_arm64 plugin: 2023/12/25 11:40:03 [INFO] Attempting SSH connection to 57.180.37.30:22...
2023/12/25 11:40:03 packer-plugin-amazon_v1.2.8_x5.0_darwin_arm64 plugin: 2023/12/25 11:40:03 [DEBUG] reconnecting to TCP connection for SSH
2023/12/25 11:40:03 packer-plugin-amazon_v1.2.8_x5.0_darwin_arm64 plugin: 2023/12/25 11:40:03 [DEBUG] handshaking with SSH
2023/12/25 11:40:04 packer-plugin-amazon_v1.2.8_x5.0_darwin_arm64 plugin: 2023/12/25 11:40:04 [DEBUG] SSH handshake err: ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey], no supported methods remain

自分の環境では、 ubuntu イメージを使用しているにもかかわらず、ssh_username が “ec2-user” になっていたのが原因で、 “ubuntu” にしたら動くようになりました。

aws-ubuntu.pkr.hcl
source "amazon-ebs" "ubuntu" {
ami_name = "learn-packer-linux-aws"
...
ssh_username = "ubuntu"
ssh_username = "ec2-user"
ssh_interface = "session_manager"
}

FAILED! No such file or directory scp: failed to upload file

以下のような設定で task の定義をしました。

playbook.yml
---
- hosts: all
tasks:
- name: Install the version '1.18.0' of package "nginx" and allow potential downgrades
ansible.builtin.apt:
name: nginx

実行してみると、failed to transfer file to から始まるメッセージが出力されました。

Terminal window
$ PACKER_LOG=1 packer build aws-ubuntu.pkr.hcl
...
learn-packer.amazon-ebs.ubuntu: fatal: [default]: FAILED! => {"msg": "failed to transfer file to /Users/xxxxxx/.ansible/tmp/ansible-local-839757jg27drf/tmpg3wfb31t ~xxxxxx/.ansible/tmp/ansible-tmp-1703480323.9974961-83978-270428962576934/AnsiballZ_setup.py:\n\nscp: dest open \"'~xxxxxx/.ansible/tmp/ansible-tmp-1703480323.9974961-83978-270428962576934/AnsiballZ_setup.py'\": No such file or directory\r\nscp: failed to upload file /Users/xxxxxx/.ansible/tmp/ansible-local-839757jg27drf/tmpg3wfb31t to '~xxxxxx/.ansible/tmp/ansible-tmp-1703480323.9974961-83978-270428962576934/AnsiballZ_setup.py'\r\n"}
learn-packer.amazon-ebs.ubuntu:

extra_arguments-vvvv を追加して、もう一度 packer コマンドを実行してみます。

aws-ubuntu.pkr.hcl
build {
name = "learn-packer"
sources = [
"source.amazon-ebs.ubuntu"
]
provisioner "ansible" {
extra_arguments = [
"-vvvv"
]
playbook_file = "./playbook.yml"
}
}

"msg": "failed to transfer file tostat remote: No such file or directory と出力されています。 ※ ログ1行があまりにも長かったため、不要な箇所か削り、改行しています。

Terminal window
$ PACKER_LOG=1 packer build aws-ubuntu.pkr.hcl
...
learn-packer.amazon-ebs.ubuntu: <127.0.0.1> (0, b'', b"OpenSSH_9.0p1, LibreSSL 3.3.6
\r\ndebug1: Reading configuration data /Users/xxxxxx/.ssh/config
\r\ndebug1: Reading configuration data /etc/ssh/ssh_config
\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe
\r\ndebug2: Received exit status from master 0\r\n")
learn-packer.amazon-ebs.ubuntu: fatal: [default]: FAILED! => {
learn-packer.amazon-ebs.ubuntu: "msg": "failed to transfer file to /Users/xxxxxx/.ansible/tmp/ansible-local-850965rvgwsmm/tmpmmasf_4y ~xxxxxx/.ansible/tmp/ansible-tmp-1703481872.151838-85099-212045282765235/AnsiballZ_setup.py:
\n\nExecuting: program /usr/bin/ssh host 127.0.0.1, user (unspecified), command sftp\nOpenSSH_9.0p1, LibreSSL 3.3.6
\r\ndebug1: Reading configuration data /Users/xxxxxx/.ssh/config
\r\ndebug1: Reading configuration data /etc/ssh/ssh_config
\r\nscp: debug1: stat remote: No such file or directory
\r\nscp: debug3: source_sftp: copying local /Users/xxxxxx/.ansible/tmp/ansible-local-850965rvgwsmm/tmpmmasf_4y to remote '~xxxxxx/.ansible/tmp/ansible-tmp-1703481872.151838-85099-212045282765235/AnsiballZ_setup.py'
\r\nscp: debug2: do_upload: upload local \"/Users/xxxxxx/.ansible/tmp/ansible-local-850965rvgwsmm/tmpmmasf_4y\" to remote \"'~xxxxxx/.ansible/tmp/ansible-tmp-1703481872.151838-85099-212045282765235/AnsiballZ_setup.py'\"
\r\nscp: debug2: Sending SSH2_FXP_OPEN \"'~xxxxxx/.ansible/tmp/ansible-tmp-1703481872.151838-85099-212045282765235/AnsiballZ_setup.py'\"
\r\nscp: debug3: Sent dest message SSH2_FXP_OPEN I:3 P:'~xxxxxx/.ansible/tmp/ansible-tmp-1703481872.151838-85099-212045282765235/AnsiballZ_setup.py' M:0x001a
\r\nscp: dest open \"'~xxxxxx/.ansible/tmp/ansible-tmp-1703481872.151838-85099-212045282765235/AnsiballZ_setup.py'\": No such file or directory
\r\nscp: failed to upload file /Users/xxxxxx/.ansible/tmp/ansible-local-850965rvgwsmm/tmpmmasf_4y to '~xxxxxx/.ansible/tmp/ansible-tmp-1703481872.151838-85099-212045282765235/AnsiballZ_setup.py'

failed to transfer file to ~/.ansible/tmp/ansible-tmp-xxx/setup.py: [Errno 2] No such file or directory #21562 の issue をみていると HOME=/root ansible-playbook ... といった感じで実行すれば良い、と書かれています。

ログの一部に '~xxxxxx/.ansible/tmp/ansible-tmp-1703485282.178923-86184-126909671221149/' となっている箇所があります。(xxx はユーザ名)-o 'User="xxxxxx"' となっている箇所が原因っぽいので設定できる箇所を探してみました。

Terminal window
$ PACKER_LOG=1 packer build aws-ubuntu.pkr.hcl
...
learn-packer.amazon-ebs.ubuntu: <127.0.0.1> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o Port=63858 -o 'IdentityFile="/var/folders/l7/54yrvv4n6xz3j0xx5txz_xcr0000gn/T/ansible-key2088040117"' -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="xxxxxx"' -o ConnectTimeout=10 '-o IdentitiesOnly=yes' -o ControlPath=/Users/xxxxxx/.ansible/cp/4805e905b5 127.0.0.1 '/bin/sh -c '"'"'rm -f -r '"'"'"'"'"'"'"'"'~xxxxxx/.ansible/tmp/ansible-tmp-1703485282.178923-86184-126909671221149/'"'"'"'"'"'"'"'"' > /dev/null 2>&1 && sleep 0'"'"''
...

その結果、Packer の設定で user という項目を見つけたので、設定します。

user (string) - The ansible_user to use. Defaults to the user running packer, NOT the user set for your communicator. If you want to use the same user as the communicator, you will need to manually set it again in this field.

(訳) 使用する ansible_user です。デフォルトは packer を実行しているユーザーで、コミュニケーターに設定されているユーザーではありません。コミュニケーターと同じユーザーを使いたい場合は、手動でこのフィールドに設定し直す必要があります。 Configuration Reference - Packer Documentation

aws-ubuntu.pkr.hcl
build {
name = "learn-packer"
sources = [
"source.amazon-ebs.ubuntu"
]
provisioner "ansible" {
extra_arguments = [
"-vvvv",
]
user = "ubuntu"
playbook_file = "./playbook.yml"
}
}

実行してみると、依然として "msg": "failed to transfer file tostat remote: No such file or directory が出力されますが、先ほどと違いコピー先が ~xxxxxx から /home/ubuntu に変わりました。あまり関係なさそうでした。

Terminal window
$ PACKER_LOG=1 packer build aws-ubuntu.pkr.hcl
learn-packer.amazon-ebs.ubuntu: <127.0.0.1> ESTABLISH SSH CONNECTION FOR USER: ubuntu
learn-packer.amazon-ebs.ubuntu: <127.0.0.1> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o Port=65370 -o 'IdentityFile="/var/folders/l7/54yrvv4n6xz3j0xx5txz_xcr0000gn/T/ansible-key2260870593"' -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="ubuntu"' -o ConnectTimeout=10 '-o IdentitiesOnly=yes' -o ControlPath=/Users/xxxxxx/.ansible/cp/cccb080090 127.0.0.1 '/bin/sh -c '"'"'rm -f -r /home/ubuntu/.ansible/tmp/ansible-tmp-1703491958.7139351-89293-259791063661757/ > /dev/null 2>&1 && sleep 0'"'"''
...
learn-packer.amazon-ebs.ubuntu: <127.0.0.1> (0, b'', b"OpenSSH_9.0p1, LibreSSL 3.3.6
\r\ndebug1: Reading configuration data /Users/xxxxxx/.ssh/config
\r\ndebug1: Reading configuration data /etc/ssh/ssh_config
\r\ndebug2: Received exit status from master 0\r\n")
learn-packer.amazon-ebs.ubuntu: fatal: [default]: FAILED! => {
learn-packer.amazon-ebs.ubuntu: "msg": "failed to transfer file to /Users/xxxxxx/.ansible/tmp/ansible-local-87906e1jgtgzj/tmpxt_dllyh /home/ubuntu/.ansible/tmp/ansible-tmp-1703489650.179384-87909-71632209335177/AnsiballZ_setup.py:\n\nExecuting: program /usr/bin/ssh host 127.0.0.1, user (unspecified), command sftp\nOpenSSH_9.0p1, LibreSSL 3.3.6
\r\ndebug1: Reading configuration data /Users/xxxxxx/.ssh/config
\r\ndebug1: Reading configuration data /etc/ssh/ssh_config
\r\ndebug1: /etc/ssh/ssh_config line 21: include /etc/ssh/ssh_config.d/* matched no files
\r\ndebug1: /etc/ssh/ssh_config line 54: Applying options for *
\r\nscp: debug2: Sending SSH2_FXP_STAT \"/home/ubuntu/.ansible/tmp/ansible-tmp-1703489650.179384-87909-71632209335177/AnsiballZ_setup.py\"
\r\nscp: debug1: stat remote: No such file or directory
\r\nscp: debug3: source_sftp: copying local /Users/xxxxxx/.ansible/tmp/ansible-local-87906e1jgtgzj/tmpxt_dllyh to remote /home/ubuntu/.ansible/tmp/ansible-tmp-1703489650.179384-87909-71632209335177/AnsiballZ_setup.py
\r\nscp: debug2: do_upload: upload local \"/Users/xxxxxx/.ansible/tmp/ansible-local-87906e1jgtgzj/tmpxt_dllyh\" to remote \"/home/ubuntu/.ansible/tmp/ansible-tmp-1703489650.179384-87909-71632209335177/AnsiballZ_setup.py\"
\r\nscp: debug2: Sending SSH2_FXP_OPEN \"/home/ubuntu/.ansible/tmp/ansible-tmp-1703489650.179384-87909-71632209335177/AnsiballZ_setup.py\"
\r\nscp: debug3: Sent dest message SSH2_FXP_OPEN I:3 P:/home/ubuntu/.ansible/tmp/ansible-tmp-1703489650.179384-87909-71632209335177/AnsiballZ_setup.py M:0x001a
\r\nscp: debug3: SSH2_FXP_STATUS 0
\r\nscp: debug3: In write loop, ack for 6 32768 bytes at 32768
\r\nscp: debug3: SSH2_FXP_STATUS 0
\r\nscp: debug3: In write loop, ack for 7 32768 bytes at 65536
\r\nscp: debug3: SSH2_FXP_STATUS 0
\r\nscp: debug3: In write loop, ack for 8 32768 bytes at 98304
\r\nscp: debug3: SSH2_FXP_STATUS 0
\r\nscp: debug3: In write loop, ack for 9 32768 bytes at 131072
\r\nscp: debug3: SSH2_FXP_STATUS 0
\r\nscp: debug3: In write loop, ack for 10 32768 bytes at 163840
\r\nscp: debug3: SSH2_FXP_STATUS 0
\r\nscp: debug3: In write loop, ack for 11 32768 bytes at 196608
\r\nscp: debug3: SSH2_FXP_STATUS 0
\r\nscp: debug3: In write loop, ack for 12 32768 bytes at 229376
\r\nscp: debug3: SSH2_FXP_STATUS 0
\r\nscp: debug3: In write loop, ack for 13 6104 bytes at 262144
\r\nscp: debug3: Sent message SSH2_FXP_CLOSE I:4
\r\nscp: debug3: SSH2_FXP_STATUS 0
\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe
\r\ndebug2: Control master terminated unexpectedly
\r\n"

さらに調べてみると、どうやら OpenSSH 9.0+ で発生する問題っぽいことがわかりました。
以下の issue から extra_arguments = [ "--scp-extra-args", "'-O'" ] を設定すると解決するらしいので、設定を変更してみます。

aws-ubuntu.pkr.hcl
build {
name = "learn-packer"
sources = [
"source.amazon-ebs.ubuntu"
]
provisioner "ansible" {
extra_arguments = [
"-vvvv",
"--scp-extra-args",
"'-O'"
]
user = "ubuntu"
playbook_file = "./playbook.yml"
}
}

再度 PACKER_LOG=1 packer build aws-ubuntu.pkr.hcl を実行すると、無事 failed to transfer file tostat remote: No such file or directory のエラーは消えました。

E: Could not open lock file /var/lib/dpkg/lock-frontend - open (13: Permission denied)

実行のユーザは ubuntu なので権限がないそうです。

Terminal window
$ PACKER_LOG=1 packer build aws-ubuntu.pkr.hcl
...
"stderr_lines": [
learn-packer.amazon-ebs.ubuntu: "E: Could not open lock file /var/lib/dpkg/lock-frontend - open (13: Permission denied)",
learn-packer.amazon-ebs.ubuntu: "E: Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend), are you root?"
learn-packer.amazon-ebs.ubuntu:
],

become: true を設定します。

playbook.yml
---
- hosts: all
tasks:
- name: Install the version '1.18.0' of package "nginx" and allow potential downgrades
ansible.builtin.apt:
name: nginx
become: true

Apt module fails with “Permission denied” #34922

無事作成することができました。

Terminal window
$ PACKER_LOG=1 packer build aws-ubuntu.pkr.hcl
...
uild 'learn-packer.amazon-ebs.ubuntu' finished after 5 minutes 47 seconds.
==> Wait completed after 5 minutes 47 seconds
==> Builds finished. The artifacts of successful builds are: