How to build a custom CentOS DigitalOcean image

Unified instance images are critical to a sound solid cloud deployment. There's nothing more annoying than having to manage the dozens of different types of images just because a different provider enforces their default image on you.

While Digital Ocean doesn't seem to support custom images, kickstarts or kernel parmeters here's how I managed to do a full kickstart install!

The snippet below can be run as a single bash script. This works by first downloading the vmlinuz and initrd.img which are the minimum required to start the CentOS installer.

Next we define our kickstart file, this is optional but it allows us to automate the steps. Espically the %post section which defines the most important section regarding partition labeling.

After we've defined our kickstart, we then boot into the CentOS installer using kexec. The use of the $IP, $NETMASK and $GATEWAY variable is pulled from the metadata service, but can be substituted with hand coded values.

Once it's done, sit back and watch the install go over the remote console. You will not require any further input.

yum -y install wget curl

mkdir /boot/centos  
cd /boot/centos  

cat <<'EOL' > /boot/centos/kickstart.ks

url --url=  
firewall --enabled --service=ssh  
repo --name="Base" --baseurl=

rootpw  --iscrypted $6$lApTqNOAYmyCrIfy$dXt9vKgMGihzZZniafkcHyMf/QzM7iSDmcLwEVcO.IewBP0EX9HVCJJrMXsv1u2Er568sma/jdPi4dcOFDvXA0  
authconfig --enableshadow --passalgo=sha512

# System keyboard
keyboard us  
# System language
lang en_US.UTF-8  
# SELinux configuration
selinux --enforcing

# System services
services --enabled="network,sshd,rsyslog,tuned,acpid"  
# System timezone
timezone Australia/Melbourne  
# Network information
network  --bootproto=dhcp --device=eth0 --onboot=on  
#network  --bootproto=dhcp --device=eth1 --onboot=on
bootloader --location=mbr --driveorder=vda --append="crashkernel=auto rhgb quiet"  
clearpart --all --drives=vda  
part / --fstype=ext4 --size=1000 --grow  
#part swap --size=512

%packages --nobase


# DigitalOcean customization
touch /etc/digitalocean

# DO sets the kernel param root=LABEL=DOROOT
e2label /dev/vda1 DOROOT  
sed -i -e 's?^UUID=.* / .*?LABEL=DOROOT     /           ext4    defaults,relatime  1   1?' /etc/fstab



# Enable swap partition if less than 800mb RAM
totalmem=$(cat /proc/meminfo | grep MemTotal | awk '{ print $2 }')  
if [ $totalmem -lt 800000 ]; then  
  sed -i 's/#part swap --size=512/part swap --size=512/g' /boot/centos/kickstart.ks 

# Booting into vmlinuz
yum install -y kexec-tools


sed -i "s/network  --bootproto=dhcp --device=eth0 --onboot=on/network  --bootproto=static --device=eth0 --onboot=on --ip=$IP --netmask=$NETMASK --gateway=$GATEWAY --nameserver,"1 /boot/centos/kickstart.ks

kexec -l /boot/centos/vmlinuz --initrd=/boot/centos/initrd.img  --append="ip=$IP netmask=$NETMASK gateway=$GATEWAY dns= ksdevice=eth0 ks=hd:vda1:/boot/centos/kickstart.ks method= lang=en_US keymap=us"  
sleep 2  
kexec -e  

After it's installed, you will face an issue with the eth0 interface not coming up. This is related to the different kernels provided, you will need to power off the instance and select the latest CentOS kernel, making sure it matches the one installed on your system. You'll notice this when you see:
Device eth0 does not seem to be present, delaying initialization

Now you should be good to go with a fresh CentOS install, most importantly with Selinux enabled! Package it up as a snapshot using the DigitalOcean control panel and let lose with your prebuilt custom CentOS images.

If you use foreman like I do, there's a plugin available to add DigitalOcean as a compute resource. It still appears to have some issues but two lines will get you going:

yum install ruby193-rubygem-foreman_digitalocean  
service foreman restart  

Your API keys will be available to add to the computeresources section.

If you intend to use this as a snapshot, a few extra things you may consider adding to your %post section

yum -y install  
yum -y install

yum -y install cloud-init nano screen ntp ntpdate curl wget dracut-modules-growroot acpid tuned puppet openssh-clients

yum -y update

# make sure firstboot doesn't start
echo "RUN_FIRSTBOOT=NO" > /etc/sysconfig/firstboot

# set virtual-guest as default profile for tuned
echo "virtual-guest" > /etc/tune-profiles/active-profile

# Fix hostname on boot
sed -i -e 's/\(preserve_hostname:\).*/\1 False/' /etc/cloud/cloud.cfg  
sed -i '/HOSTNAME/d' /etc/sysconfig/network  
rm /etc/hostname

# Remove all mac address references
sed -i '/HWADDR/d' /etc/sysconfig/network-scripts/ifcfg-eth0  
sed -i '/HOSTNAME/d' /etc/sysconfig/network-scripts/ifcfg-eth0  
sed -i '/UUID/d' /etc/sysconfig/network-scripts/ifcfg-eth0

sed -i '/HWADDR/d' /etc/sysconfig/network-scripts/ifcfg-eth1  
sed -i '/HOSTNAME/d' /etc/sysconfig/network-scripts/ifcfg-eth1  
sed -i '/UUID/d' /etc/sysconfig/network-scripts/ifcfg-eth1

yum clean all  
rm -rf /etc/ssh/*key*

rm -f /root/.ssh/*  
rm -f /home/cloud-user/.ssh/*

rm -f /etc/udev/rules.d/*-persistent-*  
touch /etc/udev/rules.d/75-persistent-net-generator.rules  

This took me some time to hack together, but I'm happy to get this working as we can now add DO to our list of available hosting providers.

Finally, the EL6 version of cloud-init does not seem to support the DigitalOcean meta datastore, while the same version EL7 release seems to.
I haven't dug into it further but if you use cloud-init you should set the datastore to DigitalOcean, even though el6 doesn't support it. It will fall back to none and prevent the 120 seconds timeout set by cloud-init to query the incorrect datastore (EC2 by default).

echo 'datasource_list: [ DigitalOcean, None ]  
   retries: 5
   timeout: 10

' >> /etc/cloud/cloud.cfg

For el6, to obtain the SSH key, something simple like this may work:

curl > /home/cloud-user/.ssh/authorized_keys  

If this helped and you do end up giving DigitalOcean a try, sign up with my referal link I get $25 credits and you get free $10 just for using the link!

Happy hacking!

comments powered by Disqus