Generate Kickstarts With Ansible

Table of Contents

I wanted a way to generate kickstart files so I can easily spin up new VMs. I will also be making use of cloning, but I want to have a PXE boot server at some point as well.

I’ve yet to test a deployment–and I’m sure there’s still tweaking to do–but the workflow is complete.

You can find the relevant git commit here.

Overview

tasks/
└── make_kickstarts.yml
roles/kickstart/
├── tasks
│   └── main.yml
├── templates
│   └── kickstart.j2
└── vars
    └── main.yml

tasks/make_kickstarts.yml

---
- name: Make kickstarts
  hosts: repo.lan.nathancurry.com
  gather_facts: false
  user: root

  roles:
    - kickstart

vars/main.yml

Here, I define all the variables used for templating. A lot of info is pulled from hostvars.

---
ks_dir: /data/www/html/ks
repo_url: 'http://repo.lan.nathancurry.com/repo/centos7/base'
ntp_servers:
  - gold.lan.nathancurry.com
  - silver.lan.nathancurry.com
  - bronze.lan.nathancurry.com
timezone: "America/Denver"
hostlist: "{{ groups[all] }}"
bootargs: 'console=ttyS0,115200'


defaults:
  netmask: 255.255.255.128
  ns:
    - 10.3.3.2
    - 10.3.3.3
  gw: 10.3.3.1
  domain: lan.nathancurry.com
  netdev: eth0

tasks/main.yml

Short and sweet. Just iterate over the list of requested hosts, and install templates to the server.

---
  - name: Include vars
    include_vars: ../vars/main.yml


  - name: Include secrets
    include_vars: ~/0/vault/secrets.yml

  - name: Generate unattended install
    template:
      src: ../templates/kickstart.j2
      dest: "{{ ks_dir }}/{{ hostvars[item].inventory_hostname_short }}.ks"
    with_items: "{{ groups['all'] | difference(groups['proxmox']) }}"

templates/kickstart.j2

This isn’t the most sophisticated template in the world, but I’m faced with doing a lot on the front end with kickstarts or on the back end with Ansible.

# front matter
install
url --url={{ repo_url }}
skipx
text
reboot
lang en_US.UTF-8
keyboard us
eula --agreed
firstboot --disable

# services
services --enabled=network,sshd,snmpd,chronyd

# passwords
rootpw --iscrypted {{ root_password_hash }}
authconfig --enableshadow --passalgo=sha512
user --groups=wheel --name=nc --password={{ nc_password_hash }} --iscrypted --gecos="nc"


# timezone setup
timezone --utc --ntpservers {{ ntp_servers|join(',') }} {{ timezone }}

network --device={{ hostvars[item]['netdev'] | default(defaults.netdev) }} --onboot=yes --hostname={{ item }}.{{ hostvars[item]['domain'] | default(defaults.domain) }} {{ hostvars[item]['netargs'] | default('') }} --bootproto=static --ip={{ hostvars[item]['ansible_host'] }} --netmask={{ hostvars[item]['netmask'] | default(defaults['netmask']) }} --gateway={{ hostvars[item]['gateway'] | default(defaults.gw)}} --nameserver=
{%- for ns in hostvars[item]['nameservers'] | default(defaults.ns) %}
{{ ns }}{% if not loop.last %},{% endif %}
{%- endfor %}



# partition setup
clearpart --all --drives={{ hostvars[item]['drive'] | default(defaults.drive) }}
bootloader --location=mbr --timeout=5 --append="{{ bootargs }}"
zerombr
clearpart --drives={{ hostvars[item]['drive'] | default(defaults.drive) }} --all
part /boot --fstype=xfs --size=250
part pv.156 --fstype="lvmpv" --ondisk={{ hostvars[item]['drive'] | default(defaults.drive) }} --size=1 --grow
volgroup vg_{{ hostvars[item].inventory_hostname_short }} --pesize=4096 pv.156
{% if hostvars[item].swap is defined %}
logvol swap  --fstype="swap" --size={{ hostvars[item].swap }} --name=swap --vgname=vg_{{ hostvars[item].inventory_hostname_short }}
{% endif %}
logvol /  --fstype="xfs" --size=1 --name=root --vgname=vg_{{ hostvars[item].inventory_hostname_short }} --grow


# Packages
%packages --nobase --ignoremissing
@core
chrony
vim-enhanced
{% if hostvars[item]['packages'] is defined %}
{% for pkg in hostvars[item]['packages'] %}
{{ pkg }}
{% endfor %}
{% endif %}

%end

%post --log=/root/ks-post.log
sed -i -e 's|^# %wheel\tALL=(ALL)\tNOPASSWD: ALL|%wheel\tALL=(ALL)\tNOPASSWD: ALL|; s|^Defaults    requiretty|#&|' /etc/sudoers


##########################
# repository configuration
find  /etc/yum.repos.d/ -type f -exec mv '{}' '{}'.save ';'
cat <<'EOF' >/etc/yum.repos.d/local.repo
[base]
name=Local CentOS-$releasever - Base
baseurl=http://repo.lan.nathancurry.com/repo/centos7/base/
gpgcheck=1
gpgkey=http://mirror.centos.org/centos/RPM-GPG-KEY-centos4
enabled=1
exclude=centos-release*

#released updates
[updates]
name=Local CentOS-$releasever - Updates
baseurl=http://repo.lan.nathancurry.com/repo/centos7/updates/
gpgcheck=1
gpgkey=http://mirror.centos.org/centos/RPM-GPG-KEY-centos4
enabled=1

#additional packages that may be useful
[extras]
name=Local CentOS-$releasever - Extras
baseurl=http://repo.lan.nathancurry.com/repo/centos7/extras/
gpgcheck=1
gpgkey=http://mirror.centos.org/centos/RPM-GPG-KEY-centos4
enabled=1

#epel
[epel]
name=Local CentOS-$releasever - Extras
baseurl=http://repo.lan.nathancurry.com/repo/centos7/epel/
gpgcheck=1
gpgkey=http://mirror.grid.uchicago.edu/pub/linux/epel/RPM-GPG-KEY-EPEL-7
enabled=1
EOF
#####################
# END REPOS

#---- Install our SSH key ----
mkdir -m0700 /root/.ssh/

cat <<EOF >/root/.ssh/authorized_keys
{{ id_ed25519_pub }}
EOF

### set permissions
chmod 0600 /root/.ssh/authorized_keys

### fix up selinux context
restorecon -R /root/.ssh/

####################
# END SSH KEYS

%end

Back matter

I’m fairly happy with this implementation. It could be a little more elegant, and I need to import my public key during install, but I’ll get to that shortly.

In the meantime, check out the relevant github commit here.