Note:
- This tutorial requires access to Oracle Cloud. To sign up for a free account, see Get started with Oracle Cloud Infrastructure Free Tier.
- It uses example values for Oracle Cloud Infrastructure credentials, tenancy, and compartments. When completing your lab, substitute these values with ones specific to your cloud environment.
Use Packer to create Oracle Cloud Infrastructure custom images
Introduction
This tutorial is about taking an OCI base image, or whatever compute image you already have in OCI and modify it according to the customer needs.
For example, you might need to change the image launch mode from native to paravirtualization or to change the Virtual Network Interface Card (VNIC) attachment. For example, OKE worker nodes need these. Equally you may install additional OS packages or alter the Instance kernel parameters using Packer provisioner (like in Terraform).
Let’s say you need an image on which you want to have PostgreSQL database installed and some ulimit parameters to have other values. All these above can be done by launching a compute from an image and then change whatever you need in it, install the programs, application, packages you need then save the instance as a new Custom Image.
Now imagine you need to create multiple custom image , for multiple architecture and over time you need to add/change the programs or packages added to your custom image. For this, we can use Packer from HashiCorp.
Packer
Packer is HashiCorp’s open-source tool used to create machine images from source configuration. It is lightweight, runs on every major operating system, highly performant. It does not replace configuration management like Chef or Puppet. In fact, when you build images, Packer is able to use tools like Chef or Puppet to install software onto the image. Some of the feature are:
-
Automate the creation of any type of machine image. Customize images to match application and organizational requirements.
-
Create identical machine images for multiple platforms from a single source configuration.
-
Create machine images for multiple platforms in parallel.
Advantages of using Packer
- Automate the entire process.
- Create more than one custom images at once.
- Create images for different architecture.
- Can run in parallel.
- Can be used within OCI DevOps build pipeline.
- It uses HCL language.
- Can use provisioner (i.e. shell or Ansible ) to run custom scripts/commands on the compute.
Packer Authentication
You can authenticate Packer using the following options.
-
By default it uses the DEFAULT profile from
.oci/config
file. -
By using instance principal: add use_instance_principals argument in Packer configuration file (there is an example below).
-
API signing keys.
-
Security token.
Note: Learn more about Packer Authentication, but the preferable way is to use either the
.oci/config
file or instance principal.
How Packer works
In the tasks, Packer takes an image specified in the base_image_ocid parameter and deploys a compute instance with the attributes specified by you in the required configuration parameters (i.e. availability*domain, shape, and more).
After that Packer will try to ssh into the instance to run any provisioner (_provisioner “shell”*) you specified in build block on the base image after launching it. For this task you have to consider where you deploy the instance (the subnet_ocid attributes), so Packer is able to ssh in the instance from the machine that runs Packer. At the end Packer snapshots it creates a reusable custom image.
Packer provisioners
Provisioners are important components of Packer that install and configure software within a running machine prior to that machine being turned into a static image. They perform the major work of making the image contain useful software.
Example provisioners include:
- Shell: The easiest way to get software installed and configured on a machine.
- File: To upload files to machines built by Packer.
- Ansible: Run Ansible playbooks.
Objectives
-
Create a custom image from an existing image and alter the
nic_attachment_type
andimage_launch_mode
attributes. -
Create a custom image from an existent image and run an Ansible playbook on the newly created image.
Prerequisites
-
OCI tenancy and OCI account with sufficient privileges to manage instance-family, use Network and Images.
Allow group PackerGroup to manage instance-family in compartment ${COMPARTMENT_NAME} Allow group PackerGroup to manage instance-images in compartment ${COMPARTMENT_NAME} Allow group PackerGroup to use virtual-network-family in compartment ${COMPARTMENT_NAME} Allow group PackerGroup to use compute-image-capability-schema in tenancy
-
Install Packer Packer.
-
Packer requires some configuration parameters to be present, see required configuration parameters.
-
Packer will use ssh to connect to provisioned compute in order to make the changes described by Packer configuration file. Make the instance reachable on port 22 (usually specify a subnet OCID that can be accessed from the host running Packer - the subnet_ocid atribute).
Task 1: Create the Packer configuration file
Packer supports HCL or JSON format. In the following task, it’s a HCL format.
-
Name the file as you want but at the end include .pkr.hcl .
packer { required_plugins { oracle = { source = "github.com/hashicorp/oracle" version = ">= 1.0.3" } } } source "oracle-oci" "example" { availability_domain = "GqIF:EU-FRANKFURT-1-AD-2" #you may use an OCID or use a base_image_filter #base_image_ocid = "ocid1.image.oc1.eu-frankfurt-1......" base_image_filter { #display_name = "Oracle-Linux-7.9-2023.05.24-0" display_name_search = "^Oracle-Linux-7.9*" } compartment_ocid = "ocid1.compartment.oc1......" image_name = "ExampleImage" shape = "VM.Standard1.1" ssh_username = "opc" #This is public subnet as I run packer from outside OCI VCN subnet_ocid = "ocid1.subnet.oc1.eu-frankfurt-1....." #this attribute will be changed nic_attachment_type = "VFIO" #this attribute will be changed image_launch_mode = "NATIVE" #you may skip the image creation if you are running tests #when you want to test set this as true skip_create_image = false } build { sources = ["source.oracle-oci.example"] }
-
The subnet_ocid is the subnet where the instance is provisioned until the custom image is built. This is a subnet that must be reachable from the host running Packer. Normally you wouldn’t want to create development instances with a public IP, as there is no reason for them to be exposed to the internet, and doing so just creates security concerns. One way to avoid using public IP is to run Packer from within OCI or to use the OCI Bastion service. You can also use FastConnect or other VPN to reach the OCI subnet from on-premises without the need to traverse the Internet.
Task 2: Initialize Packer
-
Run Packer init.
<path to packer bin> init <packer conf file>
Example:
<path to packer bin> init oke.pkr.hcl
Task 3: Build the image
-
At this point you have a custom image that will have
image_launch_mode = "NATIVE"
andnic_attachment_type = "VFIO"
. There are other attributes you can change. All are described in official doc. -
You can run custom commands or script (there are examples attached).
<path to packer bin> build oke.pkr.hcl oracle-oci.example: output will be in this color. ==> oracle-oci.example: Creating temporary ssh key for instance... ==> oracle-oci.example: Creating instance... ==> oracle-oci.example: Created instance (ocid1.instance.........). ==> oracle-oci.example: Waiting for instance to enter 'RUNNING' state... ==> oracle-oci.example: Instance 'RUNNING'. ==> oracle-oci.example: Instance has IP: 130.61.X.X ==> oracle-oci.example: Using SSH communicator to connect: 130.61.X.X ==> oracle-oci.example: Waiting for SSH to become available... ==> oracle-oci.example: Connected to SSH! ==> oracle-oci.example: Creating image from instance... ==> oracle-oci.example: Updating image schema... ==> oracle-oci.example: Created image (ocid1.image........). ==> oracle-oci.example: Terminating instance (ocid1.instance.....)... ==> oracle-oci.example: Terminated instance. Build 'oracle-oci.example' finished after 4 minutes 37 seconds. ==> Wait completed after 4 minutes 37 seconds ==> Builds finished. The artifacts of successful builds are: --> oracle-oci.example: An image was created: 'ExampleImage' (OCID: ocid1.image..........'
Example using provisioner:
-
This example uses provisioner (shell and file).
-
It would create 2 images (specified in source).
variable "VAREXAMPLE" { type = string default = "val Example" } variable "deploy_in_comp" { type = string default = "ocid1.compartment.oc1......." } variable "OCI_PROFILE" { type = string default = "DEFAULT" } locals { pkr_root = "${path.cwd}" #the directory from where Packer was started pkr_scripts = "${path.root}/scripts" #the directory of the input HCL file or the input folder root = path.root } source "oracle-oci" "img1" { access_cfg_file_account = var.OCI_PROFILE availability_domain = "GqIF:US-ASHBURN-AD-3" base_image_ocid = "ocid1.image.oc1.iad....." compartment_ocid = "ocid1.compartment.oc1......" image_name = "img1" shape = "VM.Standard.E4.Flex" #usage of shape config shape_config { ocpus = "1" } ssh_username = "opc" subnet_ocid = "ocid1.subnet.oc1.iad....." } source "oracle-oci" "img2" { access_cfg_file_account = var.OCI_PROFILE availability_domain = "GqIF:US-ASHBURN-AD-3" base_image_ocid = "ocid1.image.oc1.iad....." compartment_ocid = "ocid1.compartment.oc1........." image_name = "img2" shape = "VM.Standard.E4.Flex" shape_config { ocpus = "2" } ssh_username = "opc" subnet_ocid = "ocid1.subnet.oc1.iad........" image_compartment_ocid = var.deploy_in_comp } build { # here we secific both source above, img1 and img2 sources = [ "source.oracle-oci.img1", "source.oracle-oci.img2" ] provisioner "shell" { # this will run on all sources inline = [ "echo to run on all sources ${var.VAREXAMPLE}" ] } provisioner "shell" { # This provisioner only runs for img1 source. only = ["oracle-oci.img1"] inline = [ "echo To run in img1", ] } provisioner "shell" { # This provisioner runs on all but img1 source. # Mnd that there is no "source" string but only the name of the source except = ["oracle-oci.img1"] inline = [ "echo To run in all except img1", ] } provisioner "file" { only = ["oracle-oci.img2"] source = "${local.pkr_scripts}/test.txt" destination = "/tmp/test.txt" } # This will run local on the machine you run packer provisioner "shell-local" { inline = ["echo ${local.pkr_root} and ${local.pkr_scripts}"] } # This will run local on the machine you run packer provisioner "shell-local" { inline = ["echo VAR from file :${var.VAREXAMPLE}"] } }
-
It uses variables (like in Terraform).
image_compartment_ocid = var.deploy_in_comp
-
the Auth method is with .oci/config file (the default) but it use an atttribute to specify wich profile within the file to use.
access_cfg_file_account = var.OCI_PROFILE
-
In this we use provisioner “shell” and provisioner “file” and provisioner “shell-local”.
-
The shell Packer provisioner provisions machines built by Packer using shell scripts. Shell provisioning is the easiest way to get software installed and configured on a machine.
-
You might either run shell command on the machine (use inline) or to execute e script (use script).
-
We used the file provisioner as well. The file Packer provisioner uploads files to machines built by Packer.
-
You might see more example in the hcl file that you can download prov-example.pkr.hcl .
Example using variable files and Ansible provisioners :
-
We use:
- variable files.
- a different Auth method - using variables.
-
Instead of using shell to install packages we are using ansible to runs playbooks stored in a separate folder.
-
This method is for more complex situation that can be handled better by Ansible.
-
There are two ansible provisioner:
- ansible-local : Needs ansible installed on the host that Packer will build (you can use provisioner shell to install Ansible).
- ansible: It will run ansible from host that runs Packer so no need to be present on the instance you build.
-
there are multiple configuration files involved so you have to specify the directory name when you build.
packer build <directory name>
-
Here the example file ansible-example.pkr.hcl. Below is the ansible-example.pkr.hcl.
# mind there is a variables.pkr.hcl file where # other vars are set # so when build use: packer build <directory name> # so all the files within dir will be loaded source "oracle-oci" "img1" { #auth vars region = var.REGION tenancy_ocid = var.TENANCY_OCID user_ocid = var.USER_OCID key_file = var.KEY_FILE fingerprint = var.FINGERPRINT # availability_domain = var.AD base_image_ocid = var.BASE_IMG_OCID compartment_ocid = var.COMP_IMG_OCID image_name = var.IMG_NAME shape = var.SHAPE shape_config { ocpus = var.SHAPE_OCPUS } ssh_username = "opc" subnet_ocid = var.SUBNET_OCID # for testing and debug. remove it after skip_create_image = true } source "oracle-oci" "img2" { #auth vars region = var.REGION tenancy_ocid = var.TENANCY_OCID user_ocid = var.USER_OCID key_file = var.KEY_FILE fingerprint = var.FINGERPRINT # availability_domain = var.AD base_image_ocid = var.BASE_IMG_OCID compartment_ocid = var.COMP_IMG_OCID image_name = var.IMG_NAME1 shape = var.SHAPE shape_config { ocpus = var.SHAPE_OCPUS } ssh_username = "opc" subnet_ocid = var.SUBNET_OCID # for testing and debug. remove it after skip_create_image = true } build { sources = [ "source.oracle-oci.img1", "source.oracle-oci.img2" ] #need ansible installed on VM that packer will build #so you need to install ansible beforehand provisioner "shell" { # this will run on all sources inline = [ "sudo yum install ansible -y" ] } # this provisioner ansible-local needs ansible installed # on the host that packer will builds provisioner "ansible-local" { playbook_file = "${path.root}/ansible/pb1.yaml" } # This provisioner ansible # will run ansible playbook from host that runs packer # so you don't need to inmstall ansible on VM that packer wil create #provisioner "ansible" { # playbook_file = "${path.root}/ansible/pb-remote.yaml" # user = "opc" # # in case you need to debug ansible tasks # extra_arguments = [ "-vvvv" ] #} }
-
Here is the vars.pkr.hcl. In this file we declare and initialize them.
variable "AD" { type = string default = "GqIF:US-ASHBURN-AD-3" } variable "BASE_IMG_OCID" { type = string default = "ocid1.image.oc1.iad......" } variable "COMP_IMG_OCID" { type = string default = "ocid1.compartment.oc1......." } variable "IMG_NAME" { type = string default = "img1" } variable "IMG_NAME1" { type = string default = "img2" } variable "SHAPE" { type = string default = "VM.Standard.E3.Flex" } variable "SHAPE_OCPUS" { type = string default = "1" } variable "SUBNET_OCID" { type = string default = "ocid1.subnet.oc1.iad......" }
-
Here is the auth-vars.pkr.hcl, we use this file to setup the authentication.
variable "REGION" { type = string default = "us-ashburn-1" } variable "TENANCY_OCID" { type = string default = "ocid1.tenancy.oc1......." } variable "USER_OCID" { type = string default = "ocid1.user.oc1......" } variable "KEY_FILE" { type = string default = ".....oci_api_key.pem" } variable "FINGERPRINT" { type = string default = "...." }
-
Related Links
Acknowledgments
Author - Francisc Vass (Principal Cloud Architect)
More Learning Resources
Explore other labs on docs.oracle.com/learn or access more free learning content on the Oracle Learning YouTube channel. Additionally, visit education.oracle.com/learning-explorer to become an Oracle Learning Explorer.
For product documentation, visit Oracle Help Center.
Use Packer to create Oracle Cloud Infrastructure custom images
F87727-01
October 2023
Copyright © 2023, Oracle and/or its affiliates.