Packer is a tool for creating (golden) images from a configurable template.
Packer configuration file is a JSON file e.g. config.json.
config.json contains:
- variables
- builders
- provisioners
Example of configuration file used to create AWS AMI for multiple environments, by using Ansible as a provisioner:
config.json:
{
"variables": {
"env_name": "{{env `env_name`}}",
"instance_profile": "",
"ami_name": "asb-linux-al23-arm-{{timestamp}}",
"kms_key_id": "{{env `kms_key_id`}}",
"sg_default": "{{env `SG_DEFAULT`}}",
"current_user": "{{env `USER`}}"
},
"builders": [
{
"ami_name": "{{user `ami_name`}}",
"instance_type": "t4g.medium",
"region": "us-east-1",
"source_ami_filter": {
"filters": {
"virtualization-type": "hvm",
"name": "al2023-ami-2023.*-kernel-6.1-arm64",
"root-device-type": "ebs"
},
"owners": "amazon",
"most_recent": true
},
"ssh_username": "ec2-user",
"ssh_bastion_host": "bastion.mycorp.com",
"ssh_bastion_username": "{{user `current_user`}}",
"ssh_bastion_agent_auth": true,
"ssh_bastion_port": 22,
"ssh_timeout": "2m",
"ssh_clear_authorized_keys": "true",
"iam_instance_profile": "{{user `instance_profile`}}",
"type": "amazon-ebs",
"tags": {
"Name": "{{user `ami_name`}}",
"Environment": "{{user `env_name`}}"
},
"vpc_filter": {
"filters": {
"tag:Environment": "{{user `env_name`}}",
"isDefault": "false"
}
},
"subnet_filter": {
"filters": {
"tag:Name": "vpc-{{user `env_name`}}-private-us-east-*"
},
"most_free": true
},
"security_group_ids": [
"{{user `sg_default`}}"
],
"launch_block_device_mappings": [
{
"device_name": "/dev/xvda",
"encrypted": true,
"kms_key_id": "{{user `kms_key_id`}}",
"delete_on_termination": true,
"volume_type": "gp3"
}
]
}
],
"provisioners": [
{
"type": "shell",
"inline": [
"sudo dnf update -y",
"sudo dnf install -y python3"
]
},
{
"type": "ansible",
"host_alias": "packer",
"user": "ec2-user",
"inventory_directory": "ansible/env/aws/{{user `env_name`}}",
"playbook_file": "ansible/playbooks/playbook.yml",
"extra_arguments": [
"-D",
"--vault-password-file",
"./aws-{{user `env_name`}}-vault.pw",
"--scp-extra-args", "'-O'",
"--extra-vars",
"'ansible_python_interpreter=/usr/bin/python3'"
]
}
]
}
To run Packer:
$ packer build [-debug] config.json
AWS AMI gets created in the following way:
- a temporary SSH keypair is created
- new temporary EC2 instance is started. This instance is based on source AMI specified in configuration file
- SSH tunnel is established between local/dev host and remote EC2 instance
- provisioners are run e.g. Ansible is installing packages etc...
- once provisioners work is completed ephemeral key is removed from authorized_keys file on temp EC2 instance
- temp EC2 instance is terminated
- a snapshot of the root disk of that EC2 instance is created
- a new AMI is created based on that snapshot
- tags are added to snapshot
- tags are added to AMI
- temp SSH keypair is destroyed
If some of provisioning steps need to access resources that belong to some other AWS account, we can add its profile to ~/.aws/credentials and then refer to that profile in the command e.g.
# Download a file from an S3 bucket
- name: Download file from S3
command: "aws s3 cp s3://{{ s3_bucket_name }}/{{ file_name }} /path/to/dest_directory/{{ file_name }} --profile {{ aws_profile }} --region {{ s3_bucket_region }}"
run_once: True
delegate_to: localhost
become: no
When it comes to services, Packer provisioner should only install and enable them but not start. Temporary EC2 instance that Packer creates during the image creation process does not have necessary IAM roles attached (IAM role attached to EC2 instance needs to have CloudWatchAgentServerPolicy attached to it as that policy allows to push metrics/logs to CloudWatch). Therefore it does not make sense starting the agent on that EC2 instance (as an Ansible provisioning step). But we can make service start on the instance launch/boot by using ansible.builtin.service's enabled property:
Wrong:
- name: Restart CloudWatch agent service
ansible.builtin.service:
name: amazon-cloudwatch-agent
state: restarted
become: yes
Correct:
- name: Start CloudWatch agent service on boot
ansible.builtin.service:
name: amazon-cloudwatch-agent
enabled: true
become: yes
---