.
Packer
Create identical machine images for multiple platforms from a single configuration
🚀 Introduction
In this HashiQube DevOps lab, you will get hands-on experience with HashiCorp Packer.
Packer is an open source tool for creating identical machine images for multiple platforms from a single source configuration. It's lightweight, runs on every major operating system, and is highly performant, creating machine images for multiple platforms in parallel.
In this lab, Packer will build a Docker container, use the Shell and Ansible provisioners, and Ansible will connect to Vault to retrieve secrets using a Token.
💡 HCP Packer Cloud Features
Keeping track of base images can be challenging. HashiCorp Co-Founder and CTO Armon Dadgar explains how HCP Packer forms the core of a multi-cloud golden image pipeline.
HCP Packer, part of the HashiCorp Cloud Platform, provides a registry that tracks your image metadata and presents it to downstream processes through an API. Together with the Packer data source in the HCP provider for Terraform, this forms the foundation of a multi-cloud golden image pipeline to automate the lifecycle of images from build through deployment.
🛠️ Getting Started
Packer Templates can be found in these directories:
packer/packer/linux
packer/packer/windows
You can build local Windows and Ubuntu boxes with Packer using these steps:
Navigate to the Packer directory:
cd packer
Run the build script:
./run.sh
📄 Packer Templates
Packer uses the HashiCorp Configuration Language (HCL), designed to allow concise descriptions of the required steps to get to a build file.
Ubuntu 22.04 Packer Template
packer/linux/ubuntu/ubuntu-2204.pkr.hcl
[filename](packer/linux/ubuntu/ubuntu-2204.pkr.hcl ':include :type=code')
Windows 2019 Packer Template
packer/windows/windowsserver/windows-2019.pkr.hcl
[filename](packer/windows/windowsserver/windows-2019.pkr.hcl ':include :type=code')
⚙️ Packer Vagrant Provisioner
The packer.sh
script handles the installation and configuration of Packer:
[filename](packer.sh ':include :type=code')
🔗 Integration Points
Packer integrates with several other HashiCorp and third-party tools:
Vault: For secrets management
Ansible: For configuration management
📚 Resources
#!/bin/bash
function packer-install() {
arch=$(lscpu | grep "Architecture" | awk '{print $NF}')
if [[ $arch == x86_64* ]]; then
ARCH="amd64"
elif [[ $arch == aarch64 ]]; then
ARCH="arm64"
fi
echo -e '\e[38;5;198m'"CPU is $ARCH"
if pgrep -x "vault" >/dev/null
then
echo -e '\e[38;5;198m'"++++ "
echo -e '\e[38;5;198m'"++++ Vault is running.."
echo -e '\e[38;5;198m'"++++ "
else
echo -e '\e[38;5;198m'"++++ "
echo -e '\e[38;5;198m'"++++ Ensure Vault is running.."
echo -e '\e[38;5;198m'"++++ "
sudo bash /vagrant/vault/vault.sh
fi
grep -q "PACKER_LOG=1" /etc/environment
if [ $? -eq 1 ]; then
echo "PACKER_LOG=1" >> /etc/environment
else
sudo sed 's/PACKER_LOG=.*/PACKER_LOG=1/g' /etc/environment
fi
grep -q "PACKER_LOG_PATH=/var/log/packer.log" /etc/environment
if [ $? -eq 1 ]; then
echo "PACKER_LOG_PATH=/var/log/packer.log" >> /etc/environment
else
sudo sed 's/PACKER_LOG_PATH=.*/PACKER_LOG_PATH=\/var\/log\/packer.log/g' /etc/environment
fi
sudo touch /var/log/packer.log
sudo chmod 777 /var/log/packer.log
sudo DEBIAN_FRONTEND=noninteractive apt-get --assume-yes install -qq curl unzip jq python3-hvac < /dev/null > /dev/null
if [ -f /usr/local/bin/packer ]; then
echo -e '\e[38;5;198m'"++++ "
echo -e '\e[38;5;198m'"++++ `/usr/local/bin/packer version` already installed at /usr/local/bin/packer"
echo -e '\e[38;5;198m'"++++ "
else
LATEST_URL=$(curl --silent https://releases.hashicorp.com/index.json | jq '{packer}' | egrep "linux.*$ARCH" | sort -rh | head -1 | awk -F[\"] '{print $4}')
wget -q $LATEST_URL -O /tmp/packer.zip
sudo mkdir -p /usr/local/bin
(cd /usr/local/bin && unzip /tmp/packer.zip)
echo -e '\e[38;5;198m'"++++ "
echo -e '\e[38;5;198m'"++++ Installed: `/usr/local/bin/packer version`"
echo -e '\e[38;5;198m'"++++ "
fi
echo -e '\e[38;5;198m'"++++ "
echo -e '\e[38;5;198m'"++++ Ensure Environment Variables from /etc/environment"
echo -e '\e[38;5;198m'"++++ "
set -a; source /etc/environment; set +a;
# Packer will build a Docker container, use the Shell and Ansible provisioners, Ansible will also connect to Vault to retrieve secrets using a Token.
# https://learn.hashicorp.com/vault/getting-started/secrets-engines
# https://docs.ansible.com/ansible/latest/plugins/lookup/hashi_vault.html
# https://learn.hashicorp.com/vault/identity-access-management/iam-authentication
echo -e '\e[38;5;198m'"++++ "
echo -e '\e[38;5;198m'"++++ https://www.vaultproject.io/docs/auth/approle/"
echo -e '\e[38;5;198m'"++++ Using the root Vault token, enable the AppRole auth method"
echo -e '\e[38;5;198m'"++++ vault auth enable approle"
echo -e '\e[38;5;198m'"++++ "
vault auth enable approle
echo -e '\e[38;5;198m'"++++ "
echo -e '\e[38;5;198m'"++++ Using the root Vault token, Create an Ansible role"
echo -e '\e[38;5;198m'"++++ Create an policy named ansible allowing Ansible to read secrets"
echo -e '\e[38;5;198m'"++++ "
tee ansible-vault-policy.hcl <<"EOF"
# Read-only permission on 'kv/ansible*' path
path "kv/ansible*" {
capabilities = [ "read" ]
}
EOF
vault policy write ansible ansible-vault-policy.hcl
echo -e '\e[38;5;198m'"++++ "
echo -e '\e[38;5;198m'"++++ vault write auth/approle/role/ansible \
secret_id_ttl=10h \\n
token_policies=ansible \\n
token_num_uses=100 \\n
token_ttl=10h \\n
token_max_ttl=10h \\n
secret_id_num_uses=100"
vault write auth/approle/role/ansible \
secret_id_ttl=10h \
token_policies=ansible \
token_num_uses=100 \
token_ttl=10h \
token_max_ttl=10h \
secret_id_num_uses=100
echo -e '\e[38;5;198m'"++++ "
echo -e '\e[38;5;198m'"++++ Fetch the RoleID of the Ansible's Role"
echo -e '\e[38;5;198m'"++++ vault read auth/approle/role/ansible/role-id"
echo -e '\e[38;5;198m'"++++ "
vault read auth/approle/role/ansible/role-id
echo -e '\e[38;5;198m'"++++ "
echo -e '\e[38;5;198m'"++++ Using the root Vault token,Get a SecretID issued against the AppRole"
echo -e '\e[38;5;198m'"++++ vault write -f auth/approle/role/ansible/secret-id"
echo -e '\e[38;5;198m'"++++ "
vault write -f auth/approle/role/ansible/secret-id
echo -e '\e[38;5;198m'"++++ "
echo -e '\e[38;5;198m'"++++ Fetch the Token that Ansible will use to lookup secrets"
echo -e '\e[38;5;198m'"++++ "
ANSIBLE_ROLE_ID=$(vault read auth/approle/role/ansible/role-id | grep role_id | tr -s ' ' | cut -d ' ' -f2)
echo -e '\e[38;5;198m'"++++ "
echo -e '\e[38;5;198m'"++++ ANSIBLE_ROLE_ID: ${ANSIBLE_ROLE_ID}"
echo -e '\e[38;5;198m'"++++ "
ANSIBLE_ROLE_SECRET_ID=$(vault write -f auth/approle/role/ansible/secret-id | grep secret_id | head -n 1 | tr -s ' ' | cut -d ' ' -f2)
echo -e '\e[38;5;198m'"++++ "
echo -e '\e[38;5;198m'"++++ ANSIBLE_ROLE_SECRET_ID: ${ANSIBLE_ROLE_SECRET_ID}"
echo -e '\e[38;5;198m'"++++ vault write auth/approle/login role_id=\"${ANSIBLE_ROLE_ID}\" secret_id=\"${ANSIBLE_ROLE_ID}\""
echo -e '\e[38;5;198m'"++++ "
vault write auth/approle/login role_id="${ANSIBLE_ROLE_ID}" secret_id="${ANSIBLE_ROLE_SECRET_ID}"
echo -e '\e[38;5;198m'"++++ "
echo -e '\e[38;5;198m'"++++ Using the root Vault token, add a Secret in Vault which Ansible will retrieve"
echo -e '\e[38;5;198m'"++++ vault secrets enable -path=kv kv"
echo -e '\e[38;5;198m'"++++ "
vault secrets enable -path=kv kv
echo -e '\e[38;5;198m'"++++ "
echo -e '\e[38;5;198m'"++++ Create a Secret that Ansible will have access too"
echo -e '\e[38;5;198m'"++++ vault kv put kv/ansible devops=\"all the things\""
echo -e '\e[38;5;198m'"++++ "
vault kv put kv/ansible devops="all the things"
ANSIBLE_TOKEN=$(vault write auth/approle/login role_id="${ANSIBLE_ROLE_ID}" secret_id="${ANSIBLE_ROLE_SECRET_ID}" | grep token | head -n1 | tr -s ' ' | cut -d ' ' -f2)
echo -e '\e[38;5;198m'"++++ "
echo -e '\e[38;5;198m'"++++ ANSIBLE_TOKEN: ${ANSIBLE_TOKEN}"
# sed -i "s:token=[^ ]*:token=${ANSIBLE_TOKEN}:" /vagrant/packer/packer/linux/ubuntu/playbook.yml
echo -e '\e[38;5;198m'"++++ Install Ansible to configure Containers/VMs/AMIs/Whatever"
echo -e '\e[38;5;198m'"++++ "
sudo pip3 install ansible --break-system-packages --quiet
if [ -f /usr/local/bin/ansible ]; then
echo -e '\e[38;5;198m'"++++ `/usr/local/bin/ansible --version | head -n 1` already installed at /usr/local/bin/ansible"
else
sudo pip3 install ansible --break-system-packages --quiet
fi
echo -e '\e[38;5;198m'"++++ "
echo -e '\e[38;5;198m'"++++ Install Docker so we can build Docker Images"
# https://docs.docker.com/install/linux/docker-ce/ubuntu/
if [ -f /usr/bin/docker ]; then
echo -e '\e[38;5;198m'"++++ `/usr/bin/docker -v` already installed at /usr/bin/docker"
else
sudo bash /vagrant/docker/docker.sh
fi
echo -e '\e[38;5;198m'"++++ Packer build Linux Docker container configured with Ansible"
echo -e '\e[38;5;198m'"++++ "
# packer build /vagrant/packer/packer/linux/ubuntu/ubuntu-2204.hcl
cd /vagrant/packer/packer/
./run.sh
}
packer-install