My Universe Logo

FreeBSD-Installation aus einem Rettungssystem

Einige Anbieter dedizierter Server bieten auch ein BSD-basiertes Rettungssystem an. In dieser meist per PXE gebooteten Umgebung kann man problemlos an Platten und Partitionen herumschrauben; es fehlen jedoch häufig einige elementare Dinge, um tatsächlich eine Installation von FreeBSD durchführen zu können. Diese Anleitung beschäftigt sich damit, aus einem Rettungssystem heraus einen dedizierten Server mit FreeBSD zu installieren. Durchgespielt wurde das ganze Procedere von mir mit einem Server von OVH.

Vorbereitungen

Im Rettungssystem meines Providers stehen leider die FreeBSD-Installationsmedien nicht zur Verfügung. Da das System auf einem via NFS gemounteten (und schreibgeschützten) Root-Dateisystem basiert, bleibt einem nichts anderes übrig, als alle benötigten Daten zeitweise im RAM abzulegen (davon hat der Server glücklicherweise genug). Dazu wird eine Memory Disk benötigt, auf der die ISO-Images abgelegt werden können. Von dort aus lassen sie sich dann problemlos mounten:

mdconfig -a -t malloc -o reserve -s 2g -u 4
newfs /dev/md4
mount /dev/md4 /mnt
cd /mnt && mkdir disk1 disk2
fetch ftp://ftp7.de.freebsd.org/pub/FreeBSD/releases/amd64/ISO-IMAGES/7.2/7.2-RELEASE-amd64-disc1.iso
mdconfig -a -t vnode -f 7.2-RELEASE-amd64-disc1.iso -u 5
mount_cd9660 /dev/md5 /mnt/disk1

Festplatten präparieren

Dies ist der wahrscheinlich mühseligste und fehleranfälligste Schritt im gesamten Installationsprozess. Ziel dabei ist es, auf den beiden vorhandenen Festplatten (identisches Modell) mit gmirror ein Software-RAID1 für die erste Slice einzurichten, den übrigen Plattenplatz aber „roh“ zu belassen – auf diesem soll später ein ZFS RAIDZ Pool residieren.

Die Bezeichnung der Festplatten hängt vom Bustyp und vom Kanal ab, an den sie angeschlossen sind. Der Einfachheit halber werde ich immer die Bezeichnungen ad0 und ad1 verwenden.

RAID auflösen und Platten reinigen

Zunächst einmal mussten bei mir die beiden Festplatten aus einem bereits vom Provider angelegten gmirror-Verbund „befreit“ werden:

gmirror forget gm0
gmirror remove gm0 ad1
gmirror remove gmo ad0
gmirror dump ad0
gmirror dump ad1

Mit dem dump-Befehl wird überprüft, ob die gmirror-Metadaten tatsächlich aus dem letzten Sektor gelöscht wurden. Anschließend werden die Festplatten gründlich gereinigt, um Konflikte mit alten gmirror-Metadaten und Partitionsinformationen vorzubeugen:

dd if=/dev/zero of=/dev/ad0 bs=1m
dd if=/dev/zero of=/dev/ad1 bs=1m

Slices anlegen

Nach Abschluss dieser doch recht langwierigen Prozedur geht es jetzt ans Eingemachte: Die Platten müssen in Slices aufgeteilt werden. Ich verwende dazu die klassischen FreeBSD-Werkzeuge fdisk und bsdlabel. Gerade fdisk ist in seiner Anwendung etwas gewöhnungsbedürftig, und ich kann nur jedem empfehlen, spätestens jetzt einen Taschenrechner (oder noch besser: eine Tabellenkalkulationsanwendung) bereit zu halten.

Ich persönlich bevorzuge es, die „Drecksarbeit“ weitgehend fdisk zu überlassen und hinterher nur die Partitionstabelle meinen Wünschen anzupassen. Die einmal mühsam zusammengebastelte Konfiguration der ersten Platte wird dann einfach gnadenlos geklont und auf die zweite Platte übertragen; das spart Zeit und Mühe – und reduziert das Risiko von Zahlendrehern…

fdisk -BIv /dev/ad0
cd ~ && fdisk -p /dev/ad0 > fdisk.txt

Mit dieser Vorgehensweise wurde eine saubere Partitionstabelle auf ad0 angelegt; außerdem wurde der Bootcode in den MBR geschrieben. Allerdings wurde die Platte auch mit nur einer Slice initialisiert, die den gesamten Plattenplatz für sich beansprucht (ich mache diesen Zwischenschritt, um mir etwas Tipparbeit zu sparen und eine bearbeitbare Partitionstabelle zu erhalten, die auch schon die korrekten Geometriedaten der Festplatte enthält). Mit dem zweiten Befehl wurde diese in die Datei fdisk.txt gespeichert. Diese sieht nun in etwa folgendermaßen aus:

# /dev/ad0
g c1453521 h16 s63
p 1 0xa5 63 1465149105
a 1

Jetzt kommt der Taschenrechner oder die Tabellenkalkulation zum Einsatz: In meinem Fall ist die Partition 1.465.149.105 Sektoren groß geraten. Diesen Betrag gilt es nun, sinnvoll auf drei Partitionen aufzuteilen. Als grober Daumenwert sollte die erste Slice etwa zwischen 15 und 25 GB groß sein, die zweite so groß wie der Arbeitsspeicher des Systems, und die dritte bekommt den restlichen Speicherplatz zugewiesen.

Zur Erklärung: die erste Slice wird /, /var, /tmp und /usr beherbergen. Während / und /tmp nicht allzu groß sein müssen (je 1 GB sollte mehr als ausreichend sein), empfiehlt es sich, für /var und /usr mindestens je 8 GB zu reservieren (auf /var liegen neben den Syslog-Dateien auch die Accounting- und Audit-Daten, und /usr sollte für den Source Tree und den Ports Tree etwas Luft nach oben haben).

Die zweite Slice ist für den Auslagerungsspeicher (Swap) des Systems vorgesehen. Da der FreeBSD-Kernel ohnehin versucht, mehrere vorhandene Auslagerungsspeicher optimiert zu nutzen, wäre ist nicht sehr sinnvoll, den Swap-Bereich mit in den gmirror-Spiegel einzubeziehen – das würde den Zugriff auf ausgelagerte Speicherbereiche nur unnötig ausbremsen.

Nach Bearbeitung sieht die Datei fdisk.txt bei mir so aus:

# /dev/ad0
g c1453521 h16 s63
p 1 0xa5       63   37748736
p 2 0xa5 37748799   16777216
p 3 0xbf 54526015 1410623153
a 1

Nun kommt der Moment der Wahrheit, in dem sich zeigen wird, ob die angestellten Berechnungen einigermaßen korrekt waren:

fdisk -if ~/fdisk.txt /dev/ad0
fdisk /dev/ad0

Wenn die Ausgabe des letzten Befehls einigermaßen zufriedenstellend aussieht, kann die Aufteilung von ad0 auf ad1 übertragen werden:

fdisk -BIv /dev/ad1
fdisk -p /dev/ad0 | fdisk -if - /dev/ad1

Damit verfügen nun beide Platten über eine absolut identische Slice-Aufteilung.

gmirror-Array erstellen

Nun ist es an der Zeit, aus den ersten beiden Slices ein gmirror-RAID-Array zu erstellen. Bei der Wahl der Balance-Algorithmen scheint mir split am vernünftigsten zu sein (zumal beide Platten bei mir an unterschiedlichen Kanälen hängen, so dass echte parallele reads möglich sind). Um die Namenskonvention ein wenig hochzuhalten, bezeichne ich mein mirror-Device als gm0s1 – damit ist (zumindest für mich) kenntlich gemacht, dass das Array aus zwei Slices besteht (OK, es besteht natürlich ein gewisses Verwechslungsrisiko mit einem Array aus zwei Disks, auf dem dann mit fdisk eine Slice angelegt wurde, aber da so etwas bei mir nicht existiert, ist das Risiko für mich dann doch eher gering ;-))

Und so wird das Array erzeugt:

gmirror label -v -b split -s 4096 gm0s1 ad0s1 ad1s1
gmirror list

Mit dem list-Kommando kontrollieren wir wie üblich den Erfolg unserer Mühen. Hier sollte eine Ausgabe ähnlich der folgenden ausgegeben werden:

Geom name: gm0s1
State: COMPLETE
Components: 2
Balance: split
Slice: 4096
Flags: NONE
GenID: 0
SyncID: 1
ID: 919049269
Providers:
1. Name: mirror/gm0s1
   Mediasize: 19327246336 (18G)
   Sectorsize: 512
   Mode: r0w0e0
Consumers:
1. Name: ad0s1
   Mediasize: 19327246848 (18G)
   Sectorsize: 512
   Mode: r1w1e1
   State: ACTIVE
   Priority: 0
   Flags: NONE
   GenID: 0
   SyncID: 1
   ID: 4258031912
2. Name: ad1s1
   Mediasize: 19327246848 (18G)
   Sectorsize: 512
   Mode: r1w1e1
   State: ACTIVE
   Priority: 1
   Flags: NONE
   GenID: 0
   SyncID: 1
   ID: 238864099

Label erstellen

Spätestens jetzt sollte man sich überlegt haben, wie denn die Plattenaufteilung für das Basissystem aussehen soll. Hier mein Partitionierungsvorschlag:

Device Größe Mount Point
/dev/mirror/gm0s1a 1 GB /
/dev/mirror/gm0s1d 8 GB /usr
/dev/mirror/gm0s1e 8 GB /var
/dev/mirror/gm0s1f 1 GB /tmp

Die Umsetzung erfolgt mit dem Werkzeug bsdlabel:

setenv EDITOR vi
bsdlabel -Bw /dev/mirror/gm0s1
bsdlabel -e /dev/mirror/gm0s1

Der letzte Befehl startet einen Editor, um die Disklabel-Tabelle zu bearbeiten (ich habe mich auf vi festgelegt, wer etwas anderes haben möchte, muss halt die Umgebungsvariable $EDITOR entsprechend setzen…). Nach Aufruf des Editors sollte die Label-Tabelle in etwa wie folgt aussehen:

# /dev/mirror/gm0s1:
8 partitions:
#        size   offset    fstype   [fsize bsize bps/cpg]
  a: 37748512       16    unused        0     0
  c: 37748528        0    unused        0     0         # "raw" part, don't edit

Nun gilt es, diese Tabelle zu bearbeiten, bis sie beispielsweise so aussieht:

# /dev/mirror/gm0s1:
8 partitions:
#        size   offset    fstype   [fsize bsize bps/cpg]
  a:       1G       16    4.2BSD
  c: 37748528        0    unused        0     0         # "raw" part, don't edit
  d:       8G        *    4.2BSD
  e:       8G        *    4.2BSD
  f:        *        *    4.2BSD

Nun die Datei speichern und den Editor verlassen (bei vi: [ESC] :wq) und kontrollieren, ob alles nach Plan gelaufen ist:

bsdlabel /dev/mirror/gm0s1

sollte etwa folgende Ausgabe zu Tage fördern:

8 partitions:
#        size   offset    fstype   [fsize bsize bps/cpg]
  a:  2097152       16    4.2BSD        0     0     0
  c: 37748528        0    unused        0     0         # "raw" part, don't edit
  d: 16777216  2097168    4.2BSD        0     0     0
  e: 16777216 18874384    4.2BSD        0     0     0
  f:  2096928 35651600    4.2BSD        0     0     0

Dateisysteme erzeugen

Jetzt können die Dateisysteme erzeugt werden:

newfs -O2 -L root /dev/mirror/gm0s1a
newfs -O2 -U -L usr /dev/mirror/gm0s1d
newfs -O2 -U -L var /dev/mirror/gm0s1e
newfs -O2 -U -L tmp /dev/mirror/gm0s1f

Damit ist die Vorbereitung der Festplatten nun abgeschlossen, und die eigentliche Installation kann beginnen.

FreeBSD installieren

Dateisysteme mounten

Zunächst müssen die eben erzeugten Dateisysteme gemounted werden, damit die Installation erfolgen kann:

mkdir /mnt/inst
mount /dev/ufs/root /mnt/inst
mkdir /mnt/inst/usr /mnt/inst/var /mnt/inst/tmp
mount /dev/ufs/usr /mnt/inst/usr
mount /dev/ufs/var /mnt/inst/var
mount /dev/ufs/tmp /mnt/inst/tmp

Minimalsystem installieren

Jetzt kann endlich das FreeBSD-System installiert werden. Ich installiere an dieser Stelle nur ein absolutes Minimalsystem; den Rest installiere ich später nach:

setenv DESTDIR /mnt/inst
cd /mnt/disk1/7.2-RELEASE/base
./install.sh

# Für den Bau eines eigenen Kernels werden noch ein paar Kleinigkeiten benötigt:
cd /mnt/disk1/7.2-RELEASE/src
./install.sh sys base

Kernel erstellen

Der GENERIC-Kernel schleppt mir zu viel Ballast mit sich herum; außerdem wäre es sinnvoll, GEOM_MIRROR direkt in den Kernel einzukompilieren – das vermeidet später Ärger beim booten…

Dazu muss man in das eben installierte FreeBSD-System „einsteigen“:

mount -t devfs devfs /mnt/inst/dev
chroot /mnt/inst

Nun kann man sich seine eigene Kernel-Konfiguration erstellen:

cd /usr/src/sys/amd64/conf/
mkdir /root/kernel
cp GENERIC /root/kernel/CUSTOM
ln -s /root/kernel/CUSTOM

cat > /etc/make.conf << EOF
# Global settings
CPUTYPE?=             nocona
CFLAGS=               -O2 -pipe

# Kernel
KERNCONF=             CUSTOM
MODULES_OVERRIDE=     opensolaris zfs
EOF

vi CUSTOM

alert Den Inhalt der Datei /etc/make.conf bitte den eigenen Bedürfnissen anpassen!

Eine für OVH-Server recht brauchbare Kernel-Konfiguration könnte etwa wie folgt aussehen (funktioniert nur für den „SuperPlan Max“, die kleineren Server haben ein RealTek NIC verbaut und benötigen daher mii und re):

cpu           HAMMER
ident         CUSTOM
options       SCHED_ULE               # ULE scheduler
options       PREEMPTION              # Enable kernel thread preemption
options       INET                    # InterNETworking
options       INET6                   # IPv6 communications protocols
options       FFS                     # Berkeley Fast Filesystem
options       SOFTUPDATES             # Enable FFS soft updates support
options       UFS_ACL                 # Support for access control lists
options       UFS_DIRHASH             # Improve performance on big directories
options       UFS_GJOURNAL            # Enable gjournal-based UFS journaling
options       MD_ROOT                 # MD is a potential root device
options       PROCFS                  # Process filesystem (requires PSEUDOFS)
options       PSEUDOFS                # Pseudo-filesystem framework
options       GEOM_PART_GPT           # GUID Partition Tables.
options       GEOM_LABEL              # Provides labelization
options       GEOM_MIRROR
options       GEOM_ELI
device        crypto
options       COMPAT_43TTY            # BSD 4.3 TTY compat [KEEP THIS!]
options       COMPAT_IA32             # Compatible with i386 binaries
options       SCSI_DELAY=5000         # Delay (in ms) before probing SCSI
options       KTRACE                  # ktrace(1) support
options       STACK                   # stack(9) support
options       SYSVSHM                 # SYSV-style shared memory
options       SYSVMSG                 # SYSV-style message queues
options       SYSVSEM                 # SYSV-style semaphores
options       _KPOSIX_PRIORITY_SCHEDULING # POSIX P1003_1B real-time extensions
options       KBD_INSTALL_CDEV        # install a CDEV entry in /dev
options       ADAPTIVE_GIANT          # Giant mutex is adaptive.
options       STOP_NMI                # Stop CPUS using NMI instead of IPI
options       AUDIT                   # Security event auditing
options       SMP                     # Symmetric MultiProcessor Kernel
device        cpufreq
device        acpi
device        pci
device        fdc
device        ata
device        atadisk                 # ATA disk drives
options       ATA_STATIC_ID           # Static device numbering
device        scbus                   # SCSI bus (required for SCSI)
device        ch                      # SCSI media changers
device        da                      # Direct Access (disks)
device        pass                    # Passthrough device (direct SCSI access)
device        ses                     # SCSI Environmental Services (and SAF-TE)
device        atkbdc                  # AT keyboard controller
device        atkbd                   # AT keyboard
device        psm                     # PS/2 mouse
device        kbdmux                  # keyboard multiplexer
device        vga                     # VGA video card driver
device        splash                  # Splash screen and screen saver support
device        sc
device        agp                     # support several AGP chipsets
device        sio                     # 8250, 16[45]50 based serial ports
device        uart                    # Generic UART driver
device        ppc
device        ppbus                   # Parallel port bus (required)
device        ppi                     # Parallel port interface device
device        em                      # Intel PRO/1000 Gigabit Ethernet Family
device        loop                    # Network loopback
device        random                  # Entropy device
device        ether                   # Ethernet support
device        pty                     # Pseudo-ttys (telnet etc)
device        md                      # Memory "disks"
device        gif                     # IPv6 and IPv4 tunneling
device        faith                   # IPv6-to-IPv4 relaying (translation)
device        bpf                     # Berkeley packet filter
device        pf
device        pflog
options       ALTQ
options       ALTQ_CBQ
options       ALTQ_RED
options       ALTQ_RIO
options       ALTQ_HFSC
options       ALTQ_PRIQ
options       ALTQ_NOPCC

Nun muss der Kernel nur noch übersetzt und installiert werden:

setenv DESTDIR /
cd /usr/src
make buildkernel
make installkernel

Abschließende Konfigurationsarbeiten

/etc/fstab erstellen

Noch im chroot:

cat > /etc/fstab << EOF
# Device              Mountpoint      FSType  Options                         Dump    Pass
/dev/ufs/root         /               ufs     rw                              1       1
/dev/ad0s2.eli        none            swap    sw                              0       0
/dev/ad1s2.eli        none            swap    sw                              0       0
/dev/ufs/usr          /usr            ufs     rw,noatime                      2       2
/dev/ufs/var          /var            ufs     rw,noatime                      2       2
/dev/ufs/tmp          /tmp            ufs     rw,noatime,noexec,nosuid        2       2
EOF

idea Auf die Verschlüsselung der Swap-Bereiche mit GELI kann man auch verzichten – dazu den Suffix .eli einfach weglassen (in dem Fall kann man auch im Kernel auf GEOM_ELI und das Crypto-Device verzichten).

/etc/rc.conf erstellen

cat > /etc/rc.conf << EOF
# rc.conf
# version 2009-07-01

# ----- Ensure availability at boot time -----
fsck_y_enable="YES"

# ----- Security settings -----
syslogd_flags="-ss"
clear_tmp_enable="YES"
accounting_enable="YES"
auditd_enable="YES"

# ----- Network setup -----
defaultrouter="12.34.56.1"
hostname="myhost.mydomain.tld"
ifconfig_em0="inet 12.34.56.78 netmask 255.255.255.0"

# ----- Miscellaneous settings -----
keymap="german.iso"

# ----- Service Startups -----
sshd_enable="YES"
pf_enable="YES"
pflog_enable="YES"
gateway_enable="YES"
EOF

alert Netzwerk-Einstellungen und Hostname an die eigenen Gegebenheiten anpassen!

Netzwerk konfigurieren

cat > /etc/resolv.conf << EOF
nameserver 22.33.44.55
EOF

echo -e "12.34.56.78\tmyhost.mydomain.tld\myhost" >> /etc/hosts

Installation beenden

Soweit müsste es das jetzt gewesen sein. Die chroot-Umgebung können Sie jetzt mit exit verlassen. Anschließend müssen noch alle Datenträger wieder ausgehängt werden, bevor das System neu gestartet werden kann:

cd ~
umount /mnt/inst/dev
umount /mnt/inst/tmp
umount /mnt/inst/var
umount /mnt/inst/usr
umount /mnt/inst

shutdown -r now