Anleitung zur automatisierten Installation von Servern mit FreeBSD und ZFS

Die Installation fabrikneuer Server in unserem Rechenzentrum (RZ) kann auf manuelle Art und Weise eine gehörige Portion Zeit in Anspruch nehmen. Neben der reinen Zeitersparnis sorgt eine automatisierte Installation auch dafür, dass die Ergebnisse zuverlässig richtig und einheitlich sind. Wir zeigen, wie es geht. 

router bgp 16188

Patrick M. Hausen
Netzwerke und Infrastruktur sind sein Steckenpferd - damit Sie sich auf eine stabile Anwendung verlassen können.
Lesedauer: ca. 8 Minuten

Aufgabe

Um bei der Installation neuer Server Zeit zu sparen und Fehler zu vermeiden, stand die Implementierung einer vollautomatischen Installation neuer Systeme recht hoch auf unserer Prioritäten–Liste. Der Start von Servern per PXE [1] ist dabei natürlich nichts wesentlich Neues. Auch der Betrieb von FreeBSD über das Netzwerk ist recht gut dokumentiert. [2]

Was dagegen neu ist, ist die unbeaufsichtigte vollautomatische Provisionierung von Systemen vom "blanken Metall" bis zur betriebsbereiten FreeBSD/ZFS-basierten Hosting-Plattform.

Modernes Remote-Management wie IPMI ermöglicht uns dabei, eine größere Anzahl von Servern "auf Vorrat" in den Racks zu verbauen und mit allen nötigen Verbindungen zu versehen – diese aber noch ausgeschaltet zu lassen. Aus der Ferne eingeschaltet installieren die Systeme sich quasi selbst und laden dabei die von uns derzeit eingesetzte Management-Plattform Chef.io nach und registrieren sich am zentralen Chef-Server. Die weitere Parametrisierung (installierte und aktive Software, Netzwerk-Konfiguration, Benutzer-Accounts) erfolgt dann mittels Chef.

Überblick

Der Systemstart

Der Start eines Systems mit FreeBSD per Netz-Boot verläuft in vier Phasen:

1. PXE

Das in das BIOS oder das Option–ROM der Netzwerkkarte integrierte Preboot–Execution–Environment fordert per DHCP eine lokal gültige IPv4-Adresse an. Die Parameter next-server und filename der DHCP-Antwort enthalten dabei die Information, wo genau der FreeBSD–Bootloader gefunden werden kann.

2. Bootloader

Der im ersten Schritt ermittelte Bootloader wird per TFTP in den Rechner geladen und die Kontrolle an diesen übergeben.

Der Bootloader lädt anschließend den Kernel sowie weitere evtl. angegebene Module nach. Dies erfolgt in FreeBSD ebenso wie der nächste Schritt per NFS statt per TFTP!

3. Kernel

Der Kernel initialisiert wie gewohnt die Hardware und versucht anschließend, das Root-Filesystem zu mounten. Im Gegensatz zu den meisten Linux-Distributionen, die innerhalb solcher Umgebungen RAM-Disks dafür verwenden, die ebenfalls per TFTP nachgeladen werden, verwendet FreeBSD grundsätzlich NFS. Dies entspricht dem traditionellen "diskless" Betrieb z.B. von Workstations von Sun Microsystems. NFS-Server und -Pfad erhält der Kernel vom Bootloader, der diese Information über die DHCP-Option root-path bekommen hat.

N.B. Es existieren eine größere Anzahl von "HowTo"-Dokumenten im Netz, die recht große Klimmzüge veranstalten, um die Notwendigkeit von NFS zu vermeiden. Dabei wird meist auf externe Tools aus dem Linux-Bereich zurückgegriffen, um eine per TFTP nachzuladende RAM-Disk zu erstellen. Diese Schritte sind meiner Ansicht nach unnötig, da innerhalb einer isolierten Installations-Umgebung nichts gegen den Einsatz von NFS spricht. Zumal diese Architektur im Nachhinein viele Dinge vereinfacht. (s.u.)

4. Systemstart

Kann das Root-Filesystem erfolgreich eingehängt werden, wird wie im lokalen Betrieb /sbin/init aufgerufen und das System startet wie gewohnt.


Installation

Verwendet man als Root-Filesystem ein Abbild eines FreeBSD-Installationsmediums, führt der Systemstart direkt in den FreeBSD-Installer bsdinstall.

Damit ist bereits eine interaktive Installation möglich wie beim Start von CD/DVD/USB-Drive. Wenn der Installer im Root-Filesystem eine Datei /etc/installerconfig findet, die die richtigen Anweisungen enthält, kann die Installation aber auch vollautomatisch ablaufen bis zum Reboot des neu installierten Servers in betriebsbereitem Zustand.

Netzwerk

Netzwerk

Die Installation findet in einem isolierten privaten Netz statt. Der PXE-Install-Server übernimmt dabei folgende Aufgaben:

  • DHCP-Server
  • TFTP-Server
  • NFS-Server
  • DNS-Server
  • Default-Gateway und NAT-Device

Letzteres ist nötig, damit die zu installierenden Systeme Internet-Ressourcen und weitere Systeme in unserem Rechenzentrum (Package-Repository, Chef-Server, ...) erreichen können. Die Implementierung von Routing und NAT auf dem Install-Server selbst hilft, alle benötigten Dienste konsistent an einer Stelle zu halten.

In unserer Beispiel-Implementierung für diesen Artikel existiert das gesamte Setup innerhalb von VMware Fusion auf einem Apple Mac Laptop. Für das private VLAN wird innerhalb von VMware ein neues Netz angelegt. Wichtig ist, dass hier der VMware-eigene DHCP-Server nicht aktiviert ist!

Bei mir heißt dieses Netz in VMware vmnet2.

Installations-Server

Unser Installations–Server verwendet dieselbe FreeBSD-Version, die wir auch auf den Hosting-Systemen installieren. Im Moment (Mai 2017) ist dies FreeBSD 11.0p10

Netzwerk

Interfaces, Routing, NAT

Der Installations–Server selbst hat zwei Netzwerk-Interfaces. Eines davon extern mit Internet-Zugang, eines im privaten Installations-VLAN:

# Hostname des Systems
hostname="pxeinstall.intern.punkt.de"
# Innerhalb von VMware externe IP–Adresse per DHCP
ifconfig_em0="DHCP"
# Private IP–Adresse statisch (RFC 1918)
ifconfig_em1="inet 192.168.0.1/24"

/etc/rc.conf

Außerdem ist der Server als Router und NAT-Gateway zu konfigurieren:

# NAT aktivieren und externes Interface festlegen
firewall_enable="YES"
firewall_type="open"
firewall_nat_enable="YES"
firewall_nat_interface="em0"
# Routing aktivieren
gateway_enable="YES"

/etc/rc.conf

Benötigte Pakete

pkg install ca_root_nss isc-dhcp43-server bind910

Alle weiteren Dienste sind im FreeBSD-Basis-System enthalten.

DHCP–Server

Der DHCP-Server wird global aktiviert:

dhcpd_enable="YES"
dhcpd_ifaces="em1"

/etc/rc.conf

und konfiguriert:

# Globale Parameter
default-lease-time 600;
max-lease-time 7200;
 
ddns-update-style none;
authoritative;
log-facility local7;
 
# Unser Installations-Netz
subnet 192.168.0.0 netmask 255.255.255.0
{
    option routers 192.168.0.1;
    option subnet-mask 255.255.255.0;
    option domain-name "intern.punkt.de";
    option domain-name-servers 192.168.0.1;
    use-host-decl-names on;
 
    # PXE-Boot Parameter
    next-server 192.168.0.1;
    filename "pxeboot";
    # Root-Filesystem per NFS
    option root-path "192.168.0.1:/var/pxeinstall";
}
  
# Jeder einzelne Hosting–Server hat ab hier einen statischen Eintrag mit
# seiner MAC-Adresse und einer individuellen IPv4-Adresse
 
# hosting00.intern.punkt.de
host hosting00 { fixed-address 192.168.0.100; hardware ethernet 00:0A:E4:88:AB:04; }

/usr/local/etc/dhcpd.conf

Durch den Verzicht auf ein range statement innerhalb der DHCP-Konfiguration erhalten nur explizit aufgeführte Server eine Antwort auf DHCP-Requests. So verhindern wir, dass Systeme unbeabsichtigt einen Netzwerk-Boot durchführen und überschreiben, was auch immer schon auf ihren Festplatten ist.

Nameserver

Der Nameserver wird aktiviert:

named_enable="YES"

/etc/rc.conf

und konfiguriert – hier sorgen wir dafür, dass er nur aus dem privaten Netz und nicht aus dem Internet angesprochen werden kann:

listen-on       { 127.0.0.1; 192.168.0.1; };

/usr/local/etc/namedb/named.conf

Die eigentliche Boot-Umgebung

TFTP-Server

Der TFTP-Serverdienst ist einfach zu konfigurieren, er wird über inetd eingebunden, der auf die private Adresse beschränkt ist:

inetd_enable="YES"
inetd_flags="-wW -C 60 -a 192.168.0.1" 

/etc/rc.conf

tftp    dgram   udp wait    root    /usr/libexec/tftpd  tftpd -l -s /tftpboot 

/etc/inetd.conf

Das TFTP-Verzeichnis befindet sich innerhalb der PXE-Install-Umgebung, die wir im nächsten Schritt anlegen. Daher setzen wir zunächst einen Symlink:

ln -s var/pxeinstall/boot /tftpboot 

NFS-Server

Die bereits per TFTP bereitgestellte Umgebung muss nun noch zusätzlich per NFS exportiert werden. Dabei achten wir darauf, dass der Server nur im privaten VLAN ansprechbar ist:

rpcbind_enable="YES"
rpcbind_flags="-h 192.168.0.1"
mountd_enable="YES"
mountd_flags="-r -h 192.168.0.1"
nfs_server_enable="YES"
nfs_server_flags="-t -u -h 192.168.0.1"

/etc/rc.conf

/var/pxeinstall -ro -maproot=root -network 192.168.0.0 -mask 255.255.255.0

/etc/exports

Das Root–Filesystem für die Installation

Für die Dateien, die sich der Hosting-Server per TFTP "zieht" ebenso wie für das NFS-Root-Filesystem benötigen wir eine aktuelle FreeBSD-Installations-CD. Diese können wir von den üblichen Servern herunterladen oder selbst erstellen. Einer der Vorteile von FreeBSD: mit einem Befehl lässt sich aus den Sourcen des kompletten Systems ein Installationsmedium generieren, das nicht nur die letzte Release-Version sondern genau den gerade aktuellen Patchlevel hat.

Genügend Zeit und Leistung des Installationsservers vorausgesetzt ist das Vorgehen folgendermaßen:

1. Sourcecode auschecken

# "svn" Kommando bereitstellen, was das spaetere Update der Sourcen vereinfacht.
# "svnlite" ist im Basissystem enthalten.
mkdir -p /usr/local/bin
ln -s ../../bin/svnlite /usr/local/bin/svn
 
# Dem Build-System mitteilen, dass wir Updates zukuenftig per "make update" durchfuehren wollen
echo "SVN_UPDATE= yes" > /etc/make.conf
 
# Source von FreeBSD 11.0 mit aktuellem Patchlevel auschecken
svn co https://svn.freebsd.org/base/releng/11.0 /usr/src
  
# Updates koennen danach jederzeit so durchgefuehrt werden
cd /usr/src && make update

2. System bauen

Dieser Schritt dauert ein wenig :-)

cd /usr/src && make buildworld buildkernel

Einmal initial durchführen und nach jedem Update der Sourcen per make update.


3. Installationsmedium erstellen

cd /usr/src/release && make NOPORTS=1 NOSCRC=1 NODOC=1 NOPKG=1 cdrom

Das Ergebnis ist eine Datei mit dem Namen disc1.iso im Verzeichnis /usr/src/release.

Alternativ kann man die letzte Release–Version von den dokumentierten Servern herunterladen. Mit diesem Disc–Image geht es nun weiter.


4. Root–Filesystem mounten und kopieren

mdconfig -a -t vnode -f /usr/src/release/disc1.iso
mount -t cd9660 /dev/md0 /mnt
cd /mnt
mkdir -p /var/pxeinstall && find -d -x . | cpio -pdum /var/pxeinstall
cd
umount /mnt
mdconfig -d -u 0

Anschließend ist in der fstab dieses Installations-Systems noch eine Zeile auszukommentieren, die nur beim Boot von einer CD sinnvoll ist:

# /dev/iso9660/11_0_RELEASE_P10_AMD64_CD / cd9660 ro 0 0

/var/pxeinstall/etc/fstab

Test einer interaktiven Installation

MAC-Adresse des neuen Hosting-Servers in der dhcpd.conf eintragen (s.o.) und den DHCP-Server durchstarten:

vi /usr/local/etc/dhcpd.conf
service isc-dhcpd restart 

Anschließend den neuen Hosting-Server booten.

Das Ergebnis sollte so aussehen:

Das System ist nun nach einem Reboot einsatzbereit für eine interaktive Installation per Netzwerk. Es empfiehlt sich, dies einmal zu testen.

Screenshot von PXE
Screenshot FreeBSD Installer
Screenshot Bootloader

Herzlichen Glückwunsch! Sie haben einen FreeBSD-Netzwerk-Installations-Server implementiert!

Automatisierung der Installation

Der "neue" FreeBSD–Installer namens bsdinstall sucht beim Start nach einer Datei /etc/installerconfig. Ist diese vorhanden wird sie eingelesen und die darin spezifizierten Anweisungen übernommen. Auf diese Weise ist eine komplett unbeaufsichtigte Installation möglich.

An dieser Stelle zeigt sich der Vorteil des NFS–basierten Ansatzes. Die Datei ist auf dem Install–Server leicht zu ändern, oder eine von mehreren Alternativen auszuwählen, z.B. für unterschiedliche Hardware (Festplatten, Netzwerk–Interfaces, ...). Dies ist in der Install–Umgebung sofort wirksam, d.h. der nächste Start eines zu installierenden Servers erfolgt immer mit der aktuellen Datei.

Grundlegendes

Die Datei ist in Shell-Syntax und besteht aus zwei Teilen. Diese werden durch eine Leerzeile voneinander getrennt. Der erste Teil legt Variablen fest, die bsdinstall steuern. Der zweite Teil ist ein Shellscript, das als letzte Aktion vor Ende der Installation ausgeführt wird. In diesem Script sind wir vollkommen frei zu tun, was immer nötig ist, da der Install-Server ja per NAT und DNS auch Internet-Zugang im Installations-VLAN zur Verfügung stellt.

Erster Teil

Was ist zu installieren?

DISTRIBUTIONS="base.txz doc.txz kernel.txz lib32.txz"

/var/pxeinstall/etc/installerconfig

Welches Netzwerk–Interface wird für die Installation benutzt?

INTERFACES="em0"

/var/pxeinstall/etc/installerconfig

Hier ist das Interface des zu installierenden Servers gemeint, über das dieser auch bootet.

ZFS Festplatten–Layout

export nonInteractive="YES"
export ZFSBOOT_DISKS="da0 da1"
export ZFSBOOT_VDEV_TYPE="mirror"
export ZFSBOOT_FORCE_4K_SECTORS="1"
export ZFSBOOT_SWAP_SIZE="8g"
export ZFSBOOT_SWAP_MIRROR="1"
export ZFSBOOT_POOL_CREATE_OPTIONS="-O compress=lz4 -O checksum=fletcher4"
export ZFSBOOT_BEROOT_NAME="ROOT"
export ZFSBOOT_BOOTFS_NAME="default"
export ZFSBOOT_DATASETS="
    /$ZFSBOOT_BEROOT_NAME mountpoint=none
    /$ZFSBOOT_BEROOT_NAME/$ZFSBOOT_BOOTFS_NAME mountpoint=/
    /tmp mountpoint=/tmp,setuid=off
    /usr mountpoint=/usr,canmount=off
    /usr/local mountpoint=/usr/local
    /var mountpoint=/var
    /var/db mountpoint=/var/db,canmount=off
    /var/db/mysql mountpoint=/var/db/mysql,recordsize=16k,atime=off,primarycache=metadata
    /var/db/mysql/logs mountpoint=/var/db/mysql/logs,recordsize=128k,atime=off,primarycache=metadata
    /var/db/pkg mountpoint=/var/db/pkg
    /var/tmp mountpoint=/var/tmp,setuid=off
    /home mountpoint=/home
"

/var/pxeinstall/etc/installerconfig


Hiermit wird auf den beiden vorhandenen Festplatten ein Mirror–Pool angelegt und wie beschrieben ZFS–Datasets angelegt. Diese sind natürlich auf unsere spezielle Hosting–Umgebung zugeschnitten und ggf. anzupassen. Auch die Devices der Festplatten sind unter Umständen anders benannt. SCSI → da0, AHCI → ada0.


Die obigen Anweisungen dürfen keine Leerzeile enthalten! Diese würde den ersten Teil mit den Parametern beenden und den zweiten Teil mit dem Shellscript einleiten. Letzteres kann dann beliebig formatiert werden, auch mit Leerzeilen.

Zweiter Teil

Globale Systemeinstellungen

Im Shellscript legen wir zunächst die Datei /etc.rc.conf im installierten System an. Netzwerk–Interface entsprechend anpassen!

#!/bin/sh
cat >>/etc/rc.conf <<EOF
keymap="de"
zfs_enable="YES"
ifconfig_em0="DHCP"
sshd_enable="YES"
netwait_enable="YES"
netwait_ip="192.168.0.1"
netwait_timeout="60"
EOF

/var/pxeinstall/etc/installerconfig

Anschließend setzen wir das Root–Passwort, damit wir uns über die Konsole anmelden können:

echo 'ROOT_PW_HASH' | pw user mod root -H 0

/var/pxeinstall/etc/installerconfig

ROOT_PW_HASH ist dabei natürlich durch den echten Hash des Root–Passworts zu ersetzen.


Reboot des Systems

Die ZFS-Logik für die automatisierte Installation ist in FreeBSD noch relativ neu. Daher ruft der Installer zum Abschluss einen Dialog mit Debug-Informationen auf. Im Sinne einer automatischen Installation erzwingen wir einen Reboot:

# force reboot into new system
reboot

/var/pxeinstall/etc/installerconfig

Abschluss

Anschließend kann man sich über die Konsole anmelden und mit der Konfiguration des Systems beginnen. Diesen Teil übernimmt bei uns Chef.io, dessen Client wir ebenfalls über das installerconfig Skript automatisch installieren. Der ebenfalls angelegte Cron-Job für den Chef-Client sorgt nach dem Reboot für die Registrierung am Chef-Server, der das System anschließend durch Zuweisung eines Environments und einer Run List provisioniert.


Serielle Konsole

Die von uns derzeit präferierte Hardware-Plattform bietet eine ausgezeichnet umgesetzte serielle Konsole über IP. Diese wird von FreeBSD natürlich (es gab eine Zeit, da waren alle Bildschirme serielle Terminals) voll unterstützt. Die nötigen Änderungen am obigen Setup folgen.

Änderungen im Installations-System

Wieder zeigt sicher der Vorteil des NFS-Root-Filesystems. Änderungen an Konfigurationsdateien sind trivial im Install-Server durchzuführen und sofort wirksam. Für eine serielle Konsole per IPMI auf COM1, 9600, 8N1 sind im Installations-Root diese Änderungen vorzunehmen:

console="comconsole"
beastie_disable="YES"

/var/pxeinstall/boot/loader.conf

Das Standardskript der FreeBSD-Install-CD besteht leider darauf, den Terminal-Typ interaktiv vom Benutzer abzufragen, wenn es sich nicht um eine lokale VGA-Konsole handelt. Das steht einer automatischen Installation natürlich im Weg. Die Lösung – Code einfach auskommentieren:

[...]
else
    # Serial or other console
    #echo
    #echo "Welcome to FreeBSD!"
    #echo
    #echo "Please choose the appropriate terminal type for your system."
    #echo "Common console types are:"
    #echo "   ansi     Standard ANSI terminal"
    #echo "   vt100    VT100 or compatible terminal"
    #echo "   xterm    xterm terminal emulator (or compatible)"
    #echo "   cons25w  cons25w terminal"
    #echo
    #echo -n "Console type [vt100]: "
    #read TERM
    TERM=${TERM:-vt100}
fi

/var/pxeinstall/etc/rc.local

Konkret geht es hier um das Statement read TERM. Weg damit :-)

Änderungen im installierten System

Diese können über den Script-Teil der /var/pxinstall/etc/installconfig auf das Zielsystem gebracht werden. Wir verwenden im Moment diesen Code an geeigneter Stelle:

echo "console=\"comconsole\"" >> /boot/loader.conf
echo "beastie_disable=\"YES\"" >> /boot/loader.conf 

/var/pxeinstall/etc/installerconfig

Referenzen

[1] Preboot Execution Environment (Deutsch)

[2] FreeBSD Diskless Operation (Englisch)

Teilen:

Weitere Beiträge

:(){ :|:& };:
Wolfgang Medina-Erhardt, DevOps bei punkt.de
Arbeiten bei punkt.de