(complex) partitioning in kickstart

UPDATE: This article was written back in 2009. According to a commenter below, Busybox has been replaced by Bash in RHEL 6; perhaps Fedora as well?

Bonjour my geeky friends ! 🙂  As you are likely aware, it is now summer-time here in the northern hemisphere, and thus, i’ve been spending as much time away from the computer as possible.  That said, it’s been a long time, i shouldn’t have left you, without a strong beat to step to.

Now, if you’re not familiar with kickstarting, it’s basically just a way to automate the installation of an operating environment on a machine – think hands-free installation.  Anaconda is the OS installation tool used in Fedora, RedHat, and some other Linux OS’s, and it can be used in a kickstart capacity.  For those of you looking for an intro, i heavily suggest reading over the excellent documentation at the Fedora project website.  The kickstart configuration process could very easily be a couple of blog entries on its own (which i’ll no doubt get around to in the future), but for now i want to touch on one particular aspect of it : complex partition schemes.

how it is

The current method for declaring partitions is relatively powerful, in that all manner of basic partitions, LVM components, and even RAID devices can be specified – but where it fails is in the creating of the actual partitions on the disk itself.  The options that can be supplied to the partition keywords can make this clunky at best (and impossible at worst).

A basic example of a partitioning scheme that requires nothing outside of the available functions :

DEVICE                 MOUNTPOINT               SIZE
/dev/sda               (total)                  500,000 MB
/dev/sda1              /boot/                       128 MB
/dev/sda2              /                         20,000 MB
/dev/sda3              /var/log/                 20,000 MB
/dev/sda5              /home/                   400,000 MB
/dev/sda6              /opt/                     51,680 MB
/dev/sda7              swap                       8,192 MB

Great, no problem – we can easily define that in the kickstart :

part  /boot     --asprimary  --size=128
part  /         --asprimary  --size=20000
part  /var/log  --asprimary  --size=20000
part  /home                  --size=400000
part  /opt                   --size=51680
part  swap                   --size=8192

But what happens if we want to use this same kickstart on another machine (or, indeed, many other machines) that don’t have the same disk size ?  One of the options that can be used with the « part » keyword is « –grow », which tells Anaconda to create as large a partition as possible.  This can be used along with « –maxsize= », which does exactly what you think it does.

Continuing with the example, we can modify the « /home » partition to be of a variable size, which should do us nicely on disks which may be smaller or larger than our original 500GB unit.

part  /home  --size=1024  --grow

Here we’ve stated that we’d like the partition to be at least a gig, but that it should otherwise be as large as possible given the constraints of both the other partitions, as well as the total space available on the device.  But what if you also want « /opt » to be variable in size ?  One way would be to grow both of them :

part  /home  --size=1024  --grow
part  /opt   --size=1024  --grow

Now, what do you think that will do ? If you guessed « grow both of them to half the total available size each », you’d be correct.  Maybe this is what you wanted – but then again, maybe it wasn’t.  Of course, we could always specify a maximum ceiling on how far /opt will grow :

part  /opt  --size=1024  --maxsize=200000  --grow

That works, but only at the potential expense of /home.  Consider what would happen if this was run against a 250GB disk ; the other (static) partitions would eat up some 48GB, /opt would grow to the maximum specified size of 200GB, and /home would be left with the remaining 2GB of available space.

If we were to add more partitions into the mix, the whole thing would become an imprecise mess rather quickly.  Furthermore, we haven’t even begun to look at scenarios where there may (or may not) more than one disk, nor any fun tricks like automatically setting the swap size to be same as the actual amount of RAM (for example).  For these sorts of things we need a different approach.

the magic of pre, the power of parted

The kickstart configuration contains a section called « %pre », which should be familiar to anybody who’s dealt with RPM packaging.  Basically, the pre section contains text which will be parsed by the shell during the installation process – in other words, you can write a shell script here.  Fairly be thee warned, however, as the shell spawned by Anaconda is « BusyBox », not « bash », and it lacks some of the functionality that you might expect.  We can use the %pre section to our advantage in many ways – including partitioning.  Instead of using the built-in functions to set up the partitions, we can do it ourselves (in a manner of speaking) using « parted ».

Parted is, as you might expect, a tool for editing partition data.  Generally speaking it’s an interactive tool, but one of the nifty features is the « scripted mode », wherein partitioning commands can be passed to Parted on the command-line and executed immediately without further intervention.  This is very handy in any sort of automated scenario, including during a kickstart.

We can use Parted to lay the groundwork for the basic example above, wherein /home is dynamically sized.  Initially this will appear inefficient, since we won’t be doing anything that can’t be accomplished by using the existing Kickstart functionality, but it provides an excellent base from which to do more interesting things.  What follows (until otherwise noted) are text blocks that can be inserted directly into the %pre section of the kickstart config :

# clear the MBR and partition table
dd if=/dev/zero of=/dev/sda bs=512 count=1
parted -s /dev/sda mklabel msdos

This ensures that the disk is clean, so that we don’t run into any existing partition data that might cause trouble.  The « dd » command overwrites the first bit of the disk, so that any basic partition information is destroyed, then Parted is used to create a new disk label.

TOTAL=`parted -s /dev/sda unit mb print free | grep Free | awk '{print $3}' | cut -d "M" -f1`

That little line gives us the total size of the disk, and assigns to a variable named « TOTAL ».  There are other ways to obtain this value, but in keeping with the spirit of using Parted to solve our problems, this works.  In this instance, « awk » and « cut » are used to extract the string we’re interested in.  Continuing on…

# calculate start points
let SWAP_START=$TOTAL-8192
let OPT_START=$SWAP_START-51680

Here we determine the starting position for the swap and /opt partitions.  Since we know the total size, we can subtract 8GB from it, and that gives us where the swap partition starts.  Likewise, we can calculate the starting position of /opt based on the start point of swap (and so forth, were there other partitions to calculate).

# partitions IN ORDER
parted -s /dev/sda mkpart primary ext3 0 128
parted -s /dev/sda mkpart primary ext3 128 20128
parted -s /dev/sda mkpart primary ext3 20128 40256
parted -s /dev/sda mkpart extended 40256 $TOTAL
parted -s /dev/sda mkpart logical ext3 40256 $OPT_START
parted -s /dev/sda mkpart logical ext3 $OPT_START $SWAP_START
parted -s /dev/sda mkpart logical $SWAP_START $TOTAL

The variables we populated above are used here in order to create the partitions on the disk.  The syntax is very simple :

  • « parted -s »  : run Parted in scripted (non-interactive) mode.
  • « /dev/sda » : the device (later, we’ll see how to determine this dynamically).
  • « mkpart » : the action to take (make partition).
  • « primary | extended | logical » : the type of partition.
  • « ext3 » : the type of filesystem (there are a number of possible options, but ext3 is pretty standard).
    • Notice that the « extended » and « swap » definitions do not contain a filesystem type – it is not necessary.
  • « start# end# » : the start and end points, expressed in MB.

Finally, we must still declare the partitions in the usual way.  Take note that this does not occur in the %pre section – this goes in the normal portion of the configuration for defining partitions :

part  /boot     --onpart=/dev/sda1
part  /         --onpart=/dev/sda2
part  /var/log  --onpart=/dev/sda3
part  /home     --onpart=/dev/sda5
part  /opt      --onpart=/dev/sda6
part  swap      --onpart=/dev/sda7

As i mentioned when we began this section, yes, this is (so far) a remarkably inefficient way to set this particular basic configuration up.  But, again to re-iterate, this exercise is about putting the groundwork in place for much more interesting applications of the technique.

mo’ drives, mo’ better

Perhaps some of your machines have more than one drive, and some don’t.  These sorts of things can be determined, and then reacted upon dynamically using the described technique.  Back to the %pre section :

# Determine number of drives (one or two in this case)
set $(list-harddrives)
let numd=$#/2
d1=$1
d2=$3

In this case, we’re using a built-in function called « list-harddrives » to help us determine which drive or drives are present, and then assign their device identifiers to variables.  In other words, if you have an « sda » and an « sdb », those identifiers will be assigned to « $d1 » and « $d2 », and if you just have an sda, then $d2 will be empty.

This gives us some interesting new options ; for example, if we wanted to put /home on to the second drive, we could write up some simple logic to make that happen :

# if $d2 has a value, it's that of the second device.
if [ ! -z $d2 ]
then
  HOMEDEVICE=$d2
else
  HOMEDEVICE=$d1
fi

# snip...
part  /home  --size=1024  --ondisk=/dev/$HOMEDEVICE  --grow

That, of course, assumes that the other partitions are defined, and that /home is the only entity which should be grown dynamically – but you get the idea.  There’s nothing stopping us from writing a normal shell script that could determine the number of drives, their total size, and where the partition start points should be based on that information.  In fact, let’s examine this idea a little further.

the size, she is dynamic !

Instead of trying to wrangle the partition sizes together with the default options, we can get as complex (or as simple) as we like with a few if statements, and some basic maths.  Thinking about our layout then, we can express something like the following quite easily :

  • If there is one drive that is at least 500 GB in size, then /opt should be 200 GB, and /home should consume the rest.
  • If there is one drive is less than 500 GB, but more than 250 GB, then /opt and /home should each take half.
  • If there is one drive that is less than 250 GB, then /home should take two-thirds, and /opt gets the rest.
# $TOTAL from above...
if [ $TOTAL -ge 512000 ]
then
  let OPT_START=$SWAP_START-204800
elif [ $TOTAL -lt 512000 ] && [ $TOTAL -ge 256000 ]
then
  # get the dynamic space total, which is between where /var/log ends, and swap begins
  let DYN_TOTAL=$SWAP_START-40256
  let OPT_START=$DYN_TOTAL/2
elif [ $TOTAL -lt 256000 ]
then
  let DYN_TOTAL=$SWAP_START-40256
  let OPT_START=$DYN_TOTAL/3
  let OPT_START=$OPT_START+$OPT_START
fi

Now, instead of having to create three different kickstart files, each describing a different scenario, we’ve covered it with one – nice !

other possibilities

At the end of the day, the possilibities are nearly endless, with the only restriction being that whatever you’d like to do has to be do-able in BusyBox – which, at this level, provides a lot great functionality.

Stay tuned for more entries related to kickstarting, PXE-based installations, and so forth, all to come here on dan’s linux blog.  Cheers !

Author: phrawzty

I have a computer.

41 thoughts on “(complex) partitioning in kickstart”

  1. Excellent explanation and examples!
    This is so useful in a datacenter full of machines. In our case, the customer has specific filesystem size requirements. However, our datacenter is full of RAID’ed volumes, 36GB, 73GB, 147 GB, 300 GB simple volumes and SAN volumes. As each new version of RHEL GA’s we have to test our product on each platform. These examples provide a great base to build an adaptive kickstart.
    thanks man!

    Like

  2. Thanks for this excellent job!
    this is really useful, and will permit me to make a full automated DVD
    thx 😉

    Like

  3. Hi,
    Very good ! I start to test this in my generic ks.cfg file.
    Thanks you very much for this article.
    Bye

    Like

  4. Amazing (and easy to read/understand) article. I’m just in a phase of creating kickstart which uses multiple configurations and you simply open my eyes.
    Thank you!

    Like

  5. Would it be possible to create a kickstart script that can install Debian in a raid 0 configuration over four disks on a remote server?

    I know another server is required, just wondered if its possible, it could save me the 100euro reinstalls fee I need to pay my datacenter,

    Like

    1. In principle, yes, but you’d need to use a different platform than that described above. Debian has a project called “FAI” which may be what you’re looking for.

      Like

  6. Thank you for this article, really great and exactly what I was looking for! I think you’ve made my day now 🙂

    Like

  7. Hi Dan,
    Thank you very for help, In my enviorment I have three 3TB disk and I wanted to label it in kickstart
    how to give the specific disk through part command ?, when I use –disk=sda and –label= in part command, after the installation in /etc/fstab it comes with UUID instead of label.
    Please give some light on this

    Like

    1. You may note that the thesis of this post is that part isn’t that great at dealing with “complicated” configurations – including multiple disks. That is exactly why the combination of parted and basic shell scripting is useful.

      As an aside, those UUIDs are labels which are assigned to the actual disks. In most (all?) modern Linuxes, you can see the relationship via the /dev/ file system construct :
      ls -l /dev/disk/by-uuid/

      Like

  8. Hi Dan,
    Thanks for your input, I did even useing a script in %pre setion of kisk start, it could only successfull by using LVM (where it will not come LABEL= in fstab), I wanted do it native partitioning, if you can change below script by native partioning would be very much helpfull

    %pre --interpreter /bin/sh
    export OUT=/tmp/part-include
    act_mem=$(awk '/MemTotal/{print $2/1024}' /proc/meminfo)

    echo "# Kickstart `date`" >${OUT}
    echo "part /boot --fstype ext4 --onpart=sda1" >>${OUT}
    echo "part pv.2 --onpart=sda2" >>${OUT}
    echo "volgroup rootvg --pesize=32768 pv.2" >>${OUT}
    echo "# Disk partitioning information" >>${OUT}
    echo "logvol / --fstype ext4 --name=root --vgname=rootvg --size=30000" >>${OUT}
    echo "logvol /var/local --fstype ext4 --name=local --vgname=rootvg --size=100000" >>${OUT}
    echo "logvol swap --fstype swap --name=LogVol01 --vgname=rootvg --size=${act_mem%.*}" >>${OUT}
    echo "logvol /NGDB0 --fstype ext4 --name=NGDB0 --vgname=rootvg --size=1 --grow --fsoptions="noatime,nodiratime" " >>${OUT}

    echo "part pv.1 --onpart=sdb1" >>${OUT}
    echo "volgroup ngdb1vg --pesize=32768 pv.1" >>${OUT}
    echo "logvol /NGDB1 --fstype ext4 --name=NGDB1 --vgname=ngdb1vg --size=1 --grow --fsoptions="noatime,nodiratime" " >>${OUT}

    echo "part pv.3 --onpart=sdc1" >>${OUT}
    echo "volgroup ngdb2vg --pesize=32768 pv.3" >>${OUT}
    echo "logvol /NGDB2 --fstype ext4 --name=NGDB2 --vgname=ngdb2vg --size=1 --grow --fsoptions="noatime,nodiratime" " >>${OUT}

    Like

  9. Hi Dan: I am trying this and when it starts partitioning I sometimes get “Specified nonexistent partition sda5 in partition command”. After several attempts it partitions the workstation. Do you know what might be the cause of this?

    Like

    1. Unfortunately, with so little information to go on, I can’t even speculate as to what that might relate to. If you want to put up your scripts and a full output of the runtime (as a gist, for example), I’d be happy to try to help you debug the issue.

      Like

  10. Hi Dan. Thanks for the fast reply. Below is the kickstart. Also, when I set the file system in the %pre section the partitions would be ext4 instead of ext3. I moved the fstype to where the partitions are declared.

    install
    cdrom
    key
    lang en_US.UTF-8
    keyboard us
    network --device eth0 --bootproto static --ip --netmask --gateway --hostname myhost
    rootpw --iscrypted
    firewall --disabled
    selinux --disabled
    authconfig --enableshadow --passalgo=
    timezone America/New_York
    bootloader --location=mbr --append="quiet"
    #clearpart --linux
    #clearpart --initlabel

    part /boot --fstype ext3 --onpart=/dev/sda1
    part / --fstype ext3 --onpart=/dev/sda2
    part /var/log --fstype ext3 --onpart=/dev/sda3
    part /home --fstype ext3 --usepart=/dev/sda5
    part /opt --fstype ext3 --usepart=/dev/sda6
    part swap --fstype ext3 --usepart=/dev/sda7

    %packages --excludedocs --nobase

    # Pre-install scripts
    %pre

    # clear the MBR and partition table
    dd if=/dev/zero of=/dev/sda bs=512 count=1
    parted -s /dev/sda mklabel msdos

    TOTAL=`parted -s /dev/sda unit s print free | grep Free | awk '{print $3}'`

    parted -s /dev/sda mkpart primary 2048s 1230847s
    parted -s /dev/sda mkpart primary 1230848s 74958847s
    parted -s /dev/sda mkpart primary 74958848s 105678847s
    parted -s /dev/sda mkpart extended 105678848s $TOTAL
    parted -s /dev/sda mkpart logical 105680896s 126357503s
    parted -s /dev/sda mkpart logical 126359552s 142940159s
    parted -s /dev/sda mkpart logical 142942208s $TOTAL

    %post
    #!/bin/sh

    eject

    %end

    Like

  11. By the way this works on a VM but when it is done on a PC it sometimes does not work. I would have to try to install it multiple times in order to partitions to be declared. When it fails, from recue mode, I see that the partitions were declared but not defined.

    Like

    1. My suggestion would be to add some sleep calls in between each parted call. This will allow the disk(s) to settle down before you move on to the next step.

      Like

  12. Thanks Dan! That did the trick. Do you know why the script moved to the next step without the disk(s) being settled?

    Like

    1. The script is only doing what you tell it, which is to say that it’s running a series of commands; the real question, therefore, is why parted is exiting 0 when the disks haven’t yet completed the operation. I don’t have an answer to that question. 😛

      Like

  13. Hi, nice article! Can you add a complete example of the kickstart file? It will much easier to get the total overview if we could see the finish result of all the examples above. Thank you! 🙂

    Like

  14. kenneth :
    Hi, nice article! Can you add a complete example of the kickstart file? It will much easier to get the total overview if we could see the finish result of all the examples above. Thank you! 🙂

    Hi Kenneth,

    The examples above are just that: examples. They don’t really make any sense if they are smooshed together – in fact, they’d be completely contradictory in some cases.

    You can see an example of a complete kickstart file in comment #21 above.

    Like

  15. trying to eliminate 3 ks scripts-and merge into one.i saw where u mentioned to set the variables in %pre-helped a ton.. i am getting and error like it cant reference the variable in %pre that i set for the disk “device”…
    using `fdisk -l |grep -i disk|head -1 |cut -c 11-13`

    Like

  16. Hi Dan,

    this is a great article, thanks a lot!

    Maybe you can find the time to help me with this:

    I want to examine if there is a device md127 in my system. If so, the partition for /var/audit should be created on it. If not the partition should be created on sda.

    The part of my kickstart file is:

    ——————-
    %pre
    list-harddrives | grep md127
    if [ $? -eq 0 ]
    then
    AUDITDRIVE=md127
    else
    AUDITDRIVE=sda
    fi
    echo “AUDITDRIVE is set to AUDITDRIVE”
    %end

    part /var/audit –fstype ext3 –size=1720 –ondisk=/dev/$AUDITDRIVE

    ——————

    Install failes complaining: “Specified nonexistent disk /dev/$AUDITDRIVE in partition command”.
    It seems that anaconda doesnt replace variable name with variable value. Do you see the mistake? (and by the way, where can I see the output of the echo command?)

    Thank you!

    Like

    1. Steve & Clemens, it seems like you’re both hitting the same problem, though in Clemens’ case the likely cause is more clear – it appears to be a scoping issue.

      I suspect that variables declared in a section (ex. %pre) can’t be accessed from outside of that section. I say suspect because I haven’t actually touched kickstart in years, so this is a guess on my part. 🙂

      Try moving your part declaration up into the %pre section and see if that helps.

      Like

  17. Hi Dan,

    thank you for the quick answer!
    I discovered a few things by myself in the meantime:

    A possible solution is to write the required line to a temporary file and include that file later on:

    echo “part /var/audit –fstype ext3 –size=1720 –ondisk=/dev/$AUDITDRIVE” >/tmp/line_for_part_command
    %end


    %include /tmp/line_for_part_command

    Putting the part declaration into the pre section will not work as the part declaration would be interpreted as a shell command.

    Kind regards
    Clemens

    Like

  18. Hello,

    I try to kick-start my Centos 7 minimal installation with a 50GB Hard disk. I follow your guide however when the installation is kickstarted, it’s failed with following error:
    “sda may not have enough space for GRUB2 embed core .img…”

    Below is my %pre script
    %pre
    # clear the MBR and partition table
    dd if=/dev/zero of=/dev/sda bs=512 count=1
    parted -s /dev/sda mklabel msdos
    TOTAL=`parted -s /dev/sda unit mb print free | grep Free | awk ‘{print $3}’ | cut -d “M” -f1`
    let SWAP_START=$TOTAL-4096
    parted -s /dev/sda mkpart primary ext4 0 25
    parted -s /dev/sda mkpart primary ext4 250 $SWAP_START
    parted -s /dev/sda mkpart extended $SWAP_START $TOTAL
    parted -s /dev/sda mkpart logical $SWAP_START $TOTAL
    %end

    And the part directives:
    part /boot –onpart=/dev/sda1
    part / –onpart=/dev/sda2
    part swap –onpart=/dev/sda5

    Like

  19. thanks for the great article Dan. Appreciate it. This helped me a lot in creating the boot and LVMPV. I recommend higlighting what can be added and what cant as that can be very confusing.
    I was having trouble with creating /boot on sda1 and the rest as LVM on sda2. finally managed to get that done.
    thanks again!.

    Like

  20. This page has good advice about parted but it’s really outdated with regard to kickstart. Be advised that kickstart syntax has changed a lot since this page was originally published. I suggest installing ksvalidator which is available through yum. Type yum install pykickstart then make sure you tell it to use the RHEL7 syntax:
    ksvalidator –version RHEL7 moo-ks.cfg

    Like

    1. Yes, this blog entry is literally 10 years old at this point, so it’s no surprise that thing have evolved since then. Thanks for your comment! Hopefully it helps people who might stumble across this info over the next 10 years. 😄

      Like

Leave a comment