With packer ready to go, the next step was to specify the installation options for the template. In CentOS, that’s achieved with a text file called a kickstart file. The easiest way to create a kickstart file is to manually install CentOS in a throwaway VM using the desired options. Once the installation was completed, I could download the /root/anaconda-ks.cfg file from the VM and customize it using the CentOS documentation. My manual install included automatic partitioning, disabling kdump, choosing a minimum installation set, enabling the network adapter and creating a root password and an ansible user for future use. Once I downloaded the file, I added a few options and made some changes, all commented below.
#version=RHEL8
# Autopartition as lvm without home partition (CHANGED)
ignoredisk --only-use=sda
autopart --type=lvm --nohome
# Partition clearing information
clearpart --none --initlabel
# Use graphical install
graphical
# Use CDROM installation media
cdrom
# Keyboard layouts
keyboard --vckeymap=us --xlayouts='us'
# System language
lang en_US.UTF-8
# Network information - disable ipv6 (CHANGED)
network --bootproto=dhcp --device=eth0 --noipv6 --activate
network --hostname=localhost.localdomain
repo --name="AppStream" --baseurl=file:///run/install/repo/AppStream
# Root password
rootpw --iscrypted $6$poUOPKJL.FwIJlYW$6qcoGwXwoxcusg92P7vQTDg6naxmYovIXfrU6Ck6pRV.LoDWxYu0GzmFWnrQHF5kBaF6rIx2t0C6IlUO0qXXO.
# Don't run the Setup Agent on first boot (CHANGED)
firstboot --disable
# Enable firewall with ssh allowed (ADDED)
firewall --enabled --ssh
# Do not configure the X Window System
skipx
# System services (ADDED VMTOOLS)
services --disabled="chronyd"
services --enabled="vmtoolsd"
# System timezone (not UTC in VM environment) (CHANGED)
timezone America/Chicago
# Add ansible service account and add to 'wheel' group
user --groups=wheel --name=ansible --password=$6$D5GqhqMWaRMWrrGP$WVktoxs2QAhyEtozouyUEwjMFShVGEXUvKJ0hG89LW38jU1Uax5tCkFvp4O3z0piP3NiSV34XBn6qQk3RwfQ5. --iscrypted --gecos="ansible"
# Reboot after installation (ADDED)
reboot
%packages
@^minimal-environment
wget
open-vm-tools
%end
%addon com_redhat_kdump --disable --reserve-mb='auto'
%end
%anaconda
pwpolicy root --minlen=6 --minquality=1 --notstrict --nochanges --notempty
pwpolicy user --minlen=6 --minquality=1 --notstrict --nochanges --emptyok
pwpolicy luks --minlen=6 --minquality=1 --notstrict --nochanges --notempty
%end
I added a few packages, including open-vm-tools, to the install list. Any additional packages I could install later with ansible, but getting VM Tools installed up front is important.
Once I created the file, I had to figure out how to get it into a place where the CentOS installer could reach it. Packer has the ability to drop the file onto a virtual “floppy disk” and mount it to the VM, but CentOS 8 no longer supports floppy disks during boot. There are many other options, including FTP, HTTP and NFS, so I put Nginx on my automation machine and dropped the Kickstart file into the html folder.
Packer uses JSON as its legacy language. It was in the process of moving to HCL2, but as it was in beta at the time of this blog post, I stayed with JSON. Here was my initial configuration file for CentOS on vSphere.
{
"variables": {
"vcenter-server": "vcsa.lab.clev.work",
"vcenter-username": "automation@vsphere.local",
"vcenter-password": "VMware1!",
"vcenter-datacenter": "datacenter",
"vcenter-cluster": "cluster1",
"vcenter-datastore": "storage1",
"vcenter-folder": "Templates",
"vm-name": "CentOS_8_Minimal",
"vm-cpu": "2",
"vm-ram": "2048",
"vm-disk": "81920",
"vm-network": "VM Network",
"ks-path": "http://automation.lab.clev.work/ks_centos8_minimal.cfg",
"iso-path": "[storage2] iso/CentOS-8.1.1911-x86_64-dvd1.iso"
},
"builders": [
{
"type": "vsphere-iso",
"vcenter_server": "{{user `vcenter-server`}}",
"username": "{{user `vcenter-username`}}",
"password": "{{user `vcenter-password`}}",
"insecure_connection": "true",
"datacenter": "{{user `vcenter-datacenter`}}",
"cluster": "{{user `vcenter-cluster`}}",
"datastore": "{{user `vcenter-datastore`}}",
"folder": "{{user `vcenter-folder`}}",
"ssh_username": "root",
"ssh_password": "RootPW1#",
"convert_to_template": "false",
"vm_name": "{{user `vm-name`}}",
"notes": "Built by Packer at {{timestamp}}",
"guest_os_type": "centos8_64Guest",
"CPUs": "{{user `vm-cpu`}}",
"RAM": "{{user `vm-ram`}}",
"RAM_reserve_all": "false",
"disk_controller_type": "pvscsi",
"disk_size": "{{user `vm-disk`}}",
"disk_thin_provisioned": "true",
"network_card": "vmxnet3",
"network": "{{user `vm-network`}}",
"iso_paths": ["{{user `iso-path`}}"],
"boot_command": [
"<esc><wait>",
"linux ks={{user `ks-path`}}<enter>"
]
}
]
}
It looks a little intimidating at first, but the layout is pretty straightforward. The first section is called “variables” and it allows me to define variables that will be used later in the code. Most of these are self-explanatory–they define the settings for the VM, such as disk size and network, as well as how it will be deployed in vCenter, such as which datacenter, cluster and datastore it will be written to. Also note the ks-path, which is where the CentOS installer will find its Kickstart file, and the iso-path, which is where I uploaded the CentOS ISO.
The second section is called “builders” and passes those variables on to the builder, which actually does the work. Any insertion is encased in double sets of braces, and the variable name is further encased in backticks. Again, everything is fairly straightforward until we get to the boot_command field. Here, the builder will actually tell vCenter to type the characters into the VM, interrupting the boot and instructing grub to boot the installer and grab the Kickstart file from the web server.
So, with the template saved in the packer directory as centos8_minimal.json, I was ready to build.
./packer build centos8_minimal.json
Because I was interested, I ran this command and immediately jumped into vCenter. The “CentOS_8_Minimal” VM appeared almost instantly and I launched a remote console into it. There I could see the boot options literally being typed into the window. The installer then booted and proceeded with a completely hands-off graphical install. It then rebooted the VM and finished.
The process was so seamless that I ran it again with the “time” command to show how long the entire process took:

I fired up the VM and verified that everything was in place and that both the root and ansible users were created correctly. After deleting the VM and changing “convert_to_template” to “true”, I reran Packer and it dutifully rebuilt the VM and converted it to a template of the same name.
Packer has the ability to install additional software, run scripts and perform other tasks on a freshly prepared VM before shutting it down, using “provisioners.” My goal is to manage my infrastructure using Ansible, so I won’t be using provisioners to perform that task here; however, I don’t want my VMs to be deployed from a hopelessly out-of-date template, so I added in a quick provisioner:
"provisioners": [
{
"type": "shell",
"inline": [
"sudo yum -y upgrade"
]
}
]
The output of the yum upgrade was copied into the output from Packer, so I could verify that the build and upgrade were successful. With that, I had a basic clean freshly-upgraded of CentOS 8 that was templated and ready to deploy in vCenter. The final task was to schedule regular redeployments. I wanted to schedule my deployments for Saturdays, and since Windows patches come out on the second Tuesday, I decided to rebuild all of my templates on the second Saturday of each month. Since my automation VM was running on Linux, I would use cron to schedule it. However, cron doesn’t have the concept of “every second Saturday of the month.” However, since the second Saturday of the month always happens between the eighth and fourteenth of the month, inclusive, I set cron to run every day during that second week and then only proceed if it was a Saturday:
0 2 8-14 * * [ `date +\%u` = 6 ] && /opt/automation/packer build -force /opt/automation/centos8_minimal.json
This crontab entry builds at 2:00am every second Saturday. The “-force” option overwrites the previous template with the newly built one. In the next post, I will build Windows Server templates in Packer.