HashiQube - DevOps Lab
Youtube Channel Medium Posts Riaan Nolan Linkedin Riaan Nolan Hashicorp Ambassador

.

Packer

Create identical machine images for multiple platforms from a single configuration

Packer Logo

🚀 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.

Introduction to HashiCorp Packer

💡 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:

  1. Navigate to the Packer directory:

    cd packer
  2. 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:

📚 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