ZFS: Automatische Replikation via ssh

Ich habe das ZYNK-Script von Ben Rockwood dahingehend angepasst, dass es einen ZFS-Dataset vollständig (d. h. rekursiv, inklusive aller abhängigen Dateisysteme und Snapshots) auf einen Backup-Server repliziert. Das Script wird einmal am Tag via crontab aufgerufen, so dass die Daten der letzten Nacht auf dem Backup-Server zur Verfügung stehen. Es kann durchaus häufiger aufgerufen werden (jede Stunde oder auch mehrmals pro Stunde). Hier erst einmal das Skript:

#!/bin/bash
## ZYNK: The Zuper Zimple ZFS Sync (Replication) Tool
## Form: zynk local/dataset root@remote.host destination/dataset

# Please note: The reason this is so simple is because there is no error checking, reporting, or cleanup.
#               In the event that something goes wonkey, you'll manually need to fix the snapshots and
#               modify or remote the /var/run/zynk datafile which contains the most recent snapshot name.
# Furthermore, this absolutely relies on the GNU version of 'date' in order to get epoch time
# Before using, make sure you've distributed your SSH key to the remote host and can ssh without password.

if [ ! $3 ] 
then
        echo "Usage: zynk local/dataset root@remote.host destination/dataset"
        echo "WARNING: The destination is the full path for the remote dataset, not the prefix dataset stub."
        exit
fi

DATE=`date +%s`
if [ $DATE == "%s" ]
then
        echo "Must use GNU Date, please install and modify script."
        exit
fi

if [ -e /var/run/zynk ] 
then
        # Datafile is found, creating incr.
        echo "Incremental started at `date`"
        zfs snapshot -r ${1}@zynk-${DATE}
        zfs send -RI  ${1}@zynk-`cat /var/run/zynk` ${1}@zynk-${DATE} | ssh ${2} zfs recv -Fd ${3}
        zfs destroy -r ${1}@zynk-`cat /var/run/zynk`
        ssh ${2} zfs destroy -r ${3}@zynk-`cat /var/run/zynk`
        echo ${DATE} > /var/run/zynk
        echo "Incremental complete at `date`"
else 
        # Datafile not found, creating full.
        echo "Full started at `date`"
        zfs snapshot -r ${1}@zynk-${DATE}
        zfs send -R     ${1}@zynk-${DATE} | ssh ${2} zfs recv -Fd ${3}
        echo ${DATE} > /var/run/zynk
        echo "Full completed at `date`"
fi

In diesem Szenario soll tank/export vom Server (rubin) nach rpool/export auf dem Backup-Server (onyx) repliziert werden. Beide Systeme laufen mit OpenIndiana build 151a7. Das ZYNK-Skript liegt auf rubin unter /usr/bin/zynk. Der initiale Aufruf sieht wie folgt aus und dauerte hier mehrere Stunden (für 406 GB):

root@rubin:~# rm /var/run/zynk
root@rubin:~# zync tank/export onyx rpool
Full started at 20. November 2012 15:21:09 CET
Full completed at 21. November 2012 04:35:42 CET

Nach diesem ‚Full Backup‘ sind die inkrementellen Sicherungen schnell erledigt, da nur noch die Dateisystemänderungen (lies: Snapshots) gesendet werden, die seit dem letzten ZYNK-Aufruf angefallen sind:

root@rubin:~# zynk tank/export onyx rpool
Incremental started at 21. November 2012 13:39:24 CET
Incremental complete at 21. November 2012 13:48:11 CET

Um ZYNK jeden Tag um 2:00 Uhr automatisch aufzurufen, muss schließlich folgende Zeile in die root-crontab (crontab -e) eingetragen werden:

0  2 * * * /usr/bin/zynk tank/export onyx rpool

Das Skript setzt voraus, dass ein passwortloses (ssh-key-basiertes) root-Login möglich ist. Es empfiehlt sich allerdings einen dediziert zynk-Account mit entsprechenden Attributen zu benutzen. Wie schon dem Originalskript von Ben Rockwood, fehlt auch dieser Version jegliche Fehlerprüfung oder -behandlung.

OpenIndiana: EFI-Label entfernen

Ich wollte heute dem rpool auf einem OpenIndiana-System (151a3) eine weitere Festplatte hinzufügen um einen mirror draus zu machen. Die Festplatte war bereits vorher in einem Raid-Z-Verbund im Einsatz gewesen und hierdurch bereits mit einem EFI-Label versehen:

# fdisk /dev/rdsk/c4d0p0
Total disk size is 60800 cylinders
 Cylinder size is 16065 (512 byte) blocks
Cylinders
 Partition Status Type         Start End Length %
 ========= ====== ============ ===== === ====== ===
     1             EFI            0 60800 60801 100

SELECT ONE OF THE FOLLOWING:
 1. Create a partition
 2. Specify the active partition
 3. Delete a partition
 4. Change between Solaris and Solaris2 Partition IDs
 5. Edit/View extended partitions
 6. Exit (update disk configuration and exit)
 7. Cancel (exit without updating disk configuration)
Enter Selection:

Der erste Fehler den ich gemacht habe, war das ganze Device (z. B. c4d0) hinzufügen zu wollen: Ein root pool muss auf einer Disk mit slices (also z. B. c4d0s0) installiert werden anstatt auf einer ganzen Festplatte (siehe hier):

# zpool attach rpool c5d0 c4d0
cannot label 'c4d0': EFI labeled devices are not supported on root pools.

Der slice c4d0s0 existiert nicht und muss erzeugt werden. Ausserdem kann man Solaris nicht von EFI-Partionen booten, so dass zuvor eine Solaris2-Partition auf der Festplatte erzeugt werden muss:

# fdisk -B /dev/rdsk/c4d0p0

Im nächsten Schritt wird der benötigte slice erzeugt. Zur besseren Übersicht habe ich nach jeder Eingabe einen neuen Code-Block angefangen:

# format
Searching for disks...done

AVAILABLE DISK SELECTIONS:
       0. c4d0
          /pci@0,0/pci-ide@a/ide@0/cmdk@0,0
       1. c5d0
          /pci@0,0/pci-ide@a/ide@1/cmdk@0,0
Specify disk (enter its number): 0
selecting c4d0
Controller working list found
[disk formatted, defect list found]

FORMAT MENU:
        disk       - select a disk
        type       - select (define) a disk type
        partition  - select (define) a partition table
        current    - describe the current disk
        format     - format and analyze the disk
        fdisk      - run the fdisk program
        repair     - repair a defective sector
        show       - translate a disk address
        label      - write label to the disk
        analyze    - surface analysis
        defect     - defect list management
        backup     - search for backup labels
        verify     - read and display labels
        save       - save new disk/partition definitions
        volname    - set 8-character volume name
        !     - execute , then return
        quit
format> p
PARTITION MENU:
        0      - change `0' partition
        1      - change `1' partition
        2      - change `2' partition
        3      - change `3' partition
        4      - change `4' partition
        5      - change `5' partition
        6      - change `6' partition
        7      - change `7' partition
        select - select a predefined table
        modify - modify a predefined partition table
        name   - name the current table
        print  - display the current table
        label  - write partition map and label to the disk
        ! - execute , then return
        quit
partition> modify
Select partitioning base:
        0. Current partition table (original)
        1. All Free Hog
Choose base (enter number) [0]? 1
Part      Tag    Flag     Cylinders         Size            Blocks
  0       root    wm       0                0         (0/0/0)             0
  1       swap    wu       0                0         (0/0/0)             0
  2     backup    wu       0 - 60796      465.73GB    (60797/0/0) 976703805
  3 unassigned    wm       0                0         (0/0/0)             0
  4 unassigned    wm       0                0         (0/0/0)             0
  5 unassigned    wm       0                0         (0/0/0)             0
  6        usr    wm       0                0         (0/0/0)             0
  7 unassigned    wm       0                0         (0/0/0)             0
  8       boot    wu       0 -     0        7.84MB    (1/0/0)         16065
  9 alternates    wm       1 -     2       15.69MB    (2/0/0)         32130

Do you wish to continue creating a new partition
table based on above table[yes]?
Free Hog partition[6]? 0

Die nächsten sechs Eingaben einfach mit (sechs mal) Enter bestätigen.

Enter size of partition '1' [0b, 0c, 0.00mb, 0.00gb]:
Enter size of partition '3' [0b, 0c, 0.00mb, 0.00gb]:
Enter size of partition '4' [0b, 0c, 0.00mb, 0.00gb]:
Enter size of partition '5' [0b, 0c, 0.00mb, 0.00gb]:
Enter size of partition '6' [0b, 0c, 0.00mb, 0.00gb]:
Enter size of partition '7' [0b, 0c, 0.00mb, 0.00gb]:
Part      Tag    Flag     Cylinders         Size            Blocks
  0       root    wm       3 - 60796      465.71GB    (60794/0/0) 976655610
  1       swap    wu       0                0         (0/0/0)             0
  2     backup    wu       0 - 60796      465.73GB    (60797/0/0) 976703805
  3 unassigned    wm       0                0         (0/0/0)             0
  4 unassigned    wm       0                0         (0/0/0)             0
  5 unassigned    wm       0                0         (0/0/0)             0
  6        usr    wm       0                0         (0/0/0)             0
  7 unassigned    wm       0                0         (0/0/0)             0
  8       boot    wu       0 -     0        7.84MB    (1/0/0)         16065
  9 alternates    wm       1 -     2       15.69MB    (2/0/0)         32130

Okay to make this the current partition table[yes]?
Enter table name (remember quotes): "disk0"
Ready to label disk, continue? yes
partition> q
FORMAT MENU:
        disk       - select a disk
        type       - select (define) a disk type
        partition  - select (define) a partition table
        current    - describe the current disk
        format     - format and analyze the disk
        fdisk      - run the fdisk program
        repair     - repair a defective sector
        show       - translate a disk address
        label      - write label to the disk
        analyze    - surface analysis
        defect     - defect list management
        backup     - search for backup labels
        verify     - read and display labels
        save       - save new disk/partition definitions
        volname    - set 8-character volume name
        !     - execute , then return
        quit
format> q

Jetzt kann man das zweite Device zum rpool hinzufügen:

# zpool attach -f rpool c5d0s0 c4d0s0
Make sure to wait until resilver is done before rebooting.

Mit zpool status kann man sich den Fortschritt des Resilver-Prozesses anschauen:

# zpool status
  pool: rpool
 state: ONLINE
status: One or more devices is currently being resilvered.  The pool will
        continue to function, possibly in a degraded state.
action: Wait for the resilver to complete.
  scan: resilver in progress since Thu Apr 26 11:00:36 2012
    6.14G scanned out of 146G at 42.7M/s, 0h55m to go
    6.13G resilvered, 4.20% done
config:

        NAME        STATE     READ WRITE CKSUM
        rpool       ONLINE       0     0     0
          mirror-0  ONLINE       0     0     0
            c5d0s0  ONLINE       0     0     0
            c4d0s0  ONLINE       0     0     0  (resilvering)

errors: No known data errors

Nun noch Grub installieren, damit die zweite Platte im Ernstfall auch bootfähig ist:

# installgrub /boot/grub/stage1 /boot/grub/stage2 /dev/rdsk/c4d0s0
stage2 written to partition 0, 275 sectors starting at 50 (abs 16115)
stage1 written to partition 0 sector 0 (abs 16065)

OpenIndiana: NIS-Slave konfigurieren

Ich zeige hier, wie man auf einem Rechner namens onyx (OpenIndiana 151a3) den NIS-Slave-Dienst für die NIS-Domain babar eines NIS-Servers names rubin installiert. Es wird vorausgesetzt, dass der NIS-Service auf rubin bereits konfiguriert ist und das beide Rechner in /var/yp/ypservers vermerkt sind und beide Rechner müssen sich gegenseitig in /etc/hosts zu enthalten. So könnte das aussehen:

#/etc/hosts
[...]
192.168.0.11 onyx.local onyx
192.168.0.12 rubin.local rubin
[...]

Alle folgenden Befehle werden auf dem Slave-Server onyx ausgeführt.

NIS-Service installieren:

# pkg install service/network/nis

Domainname setzen

# domainname babar
# domainname > /etc/defaultdomain

NIS-Client konfigurieren. WICHTIG: Zuerst den Slave- und dann den Master-Server eintragen, also im Beispiel, erst onyx dann rubin.

# ypinit -c
In order for NIS to operate sucessfully, we have to construct a list of the
NIS servers. Please continue to add the names for YP servers in order of
preference, one per line. When you are done with the list, type a
 or a return on a line by itself.
 next host to add: onyx
 next host to add: rubin
 next host to add:
The current list of yp servers looks like this:
onyx

 rubin

Is this correct? [y/n: y] y

NIS-Client starten:

# svcadm enable -r nis/client

NIS-Slave konfigurieren:

# ypinit -s rubin
 Installing the YP database will require that you answer a few questions.
 Questions will all be asked at the beginning of the procedure.
Do you want this procedure to quit on non-fatal errors? [y/n: n]
 OK, please remember to go back and redo manually whatever fails. If you
 don't, some part of the system (perhaps the yp itself) won't work.
 The yp domain directory is /var/yp/babar
 Can we destroy the existing /var/yp/babar and its contents? [y/n: n] y
 There will be no further questions. The remainder of the procedure should take
 a few minutes, to copy the data bases from rubin.
 Transferring group.bygid...
 Transferring netgroup.byhost...
 Transferring auto.master...
 Transferring hosts.byname...
 Transferring hosts.byaddr...
 Transferring passwd.byuid...
 Transferring auto.direct...
 Transferring auto.nfs...
 Transferring passwd.byname...
 Transferring netgroup...
 Transferring ypservers...
 Transferring netgroup.byuser...
 Transferring auto.home...
 Transferring group.byname...

onyx's nis data base has been set up

without any errors.

Das war’s.

OpenIndiana: Statische Netzwerkkonfiguration

Nach der Installation bezieht OpenIndiana 151a (Illumos) seine Netzwerkkonfiguration per default von einem DHCP-Server. Ich zeige hier kurz, wie man dieses Verhalten abschalten kann und eine statische IP-Konfiguration erreicht. Zuerst NWAM deaktivieren und die statische Konfiguration aktivieren:

# svcadm disable network/physical:nwam
# svcadm enable -r network/physical:default

Folgende statische Konfiguration soll erreicht werden:

  • IPv4: 192.168.0.11
  • Netmask: 255.255.255.0
  • Hostname: cartman.mydom.local
  • Gateway: 192.168.0.1
  • Nameserver: 192.168.0.1, 8.8.8.8

Name des Interfaces ermitteln (hier: e1000g0)

# dladm show-phys
LINK    MEDIA    STATE   SPEED DUPLEX  DEVICE
rge0    Ethernet unknown 0     unknown rge0
e1000g0 Ethernet up      1000  full    e1000g0

IP-Adresse setzen:

# echo "192.168.0.11/24" > /etc/hostname.e1000g0

Hostname setzen:

# echo "192.168.0.11 cartman.mydom.local cartman" >> /etc/hosts

Gateway setzen:

# echo "192.168.0.1" > /etc/defaultrouter

Nameserver setzen:

# echo "nameserver 192.168.0.1" > /etc/resolv.conf
# echo "nameserver 8.8.8.8" >> /etc/resolv.conf
# echo "domain mydom.local" >> /etc/resolv.conf
# echo "search mydom.local" >> /etc/resolv.conf

Netzwerk „neustarten“:

# svcadm restart network/physical:default

OpenIndiana: localhost-only sendmail-Konfiguration

Standardmäßig arbeitet sendmail auf OpenIndinana 151a (Illumos) nur auf dem localhost-Interface. Wenn versucht wird eine Mail zu senden findet man im Clientlog eine Meldung, dass die Verbindung abgewiesen wurde:

Apr 25 10:17:17 client postfix/qmgr[3076]: D87EB62F38: from=, size=688, nrcpt=1 (queue active)
Apr 25 10:17:17 client postfix/smtp[22293]: connect to server[xxx.xxx.xxx.xxx]:25: Connection refused
Apr 25 10:17:17 client postfix/smtp[22293]: D87EB62F38: to=, relay=none, delay=490, delays=490/0.04/0/0, dsn=4.4.1, status=deferred (connect to server[xxx.xxx.xxx.xxx]:25: Connection refused)

 

Die Ursache hierfür ist, dass in der Konfiguration des Servers config/local_only=true gesetzt. Mit folgenden Zeilen bindet man sendmail auch an alle weiteren Netzwerkgeräte:

# svccfg -v -s sendmail
svc:/network/smtp:sendmail>setprop config/local_only=false
svc:/network/smtp:sendmail> exit
# svcadm refresh sendmail
# svcadm restart sendmail

OpenIndiana: Panic beim Import eines ZFS-Pools

Eine OpenIndiana 151a2 Installation stellte plötzlich den Dienst ein. Beim Import des Root-Pools (rpool) gibt es eine System-Panic und das System steckt in einem Reboot-Cycle. Dummerweise befinden sich auch Daten im rpool, die es zu retten gilt. Hierzu muss ein USB-Bootmedium erzeugt werden (Wer will kann auch eine DVD dieses ISO-Files brennen.) um wieder Zugriff auf den Rechner zu bekommen: Zuerst das Live-USB-Image herunterladen. Unter Solaris/OpenIndiana kann das Image einfach mit usbcopy auf den Stick übertragen werden. Unter Linux und Mac OS X muss zusätzlich dieser Header heruntergeladen werden. Mit cat und dd werden dann beide Files auf den USB-Key übertragen:

cat 1G.header oi-dev-151a-x86.usb | dd bs=1024k of=/dev/USBDEVICE

/dev/USBDEVICE ist das raw-Device des USB-Sticks. Unter Linux so etwas wie /dev/sdg bzw. unter Mac OS X /dev/rdisk2. Das korrekte Device findet man leicht heraus, indem man die Ausgabe von dmesg betrachtet, nachdem man den USB-Stick mit dem Linux/Mac-Rechner verbunden hat. Weiterhin setzt die Codezeile voraus, dass sich Header- und Image-File im aktuellen Verzeichnis befinden.

Jetzt das Live-System booten. Vor dem Import des rpools folgende Zeilen ausführen

# sudo mdb -kw
> aok/W 0x1
> zfs_recover/E 0x1

Jetzt kann der rpool mittels

# sudo zpool import -f -o readonly=on -R /a rpool

im Nur-Lese-Zugriff unter der alternativen Root /a gemountet werden. Da das Filesystem read-only ist, kann zfs send nicht benutzen werden, um die Daten auf einen anderen Pool zu übertragen. Stattdessen habe ich sie stumpf mit rsync über das Netzwerk weggeschafft.

Shadow-Passwords erzeugen

Verschlüsselte Passwörter, wie sie in /etc/shadow gespeichert werden, kann man mit der folgenden Zeile für das Passwort password erzeugen:

# echo "password" | openssl passwd -1 -stdin $1$YFi.APLv$VZiopcJ9udPYifg/4E7vo/ 

Die Option -1 steht für den zu benutzenden MD5-Algorithmus. Die Ausgabe kann via Copy-Paste für den Benutzer USERNAME in die /etc/shadow eingefügt werden.

USERNAME:$1$YFi.APLv$VZiopcJ9udPYifg/4E7vo/:15132::::::

Diese Vorgehensweise ist dann nützlich, wenn man nicht auf Tools wie adduser oder useradd zurückgreifen kann oder will, weil beispielsweise separate passwd/shadow files für einen NIS-Server gepflegt werden. Zum Anlegen eines neuen Benutzers USERNAME muss noch eine Zeile wie die folgende zu /etc/passwd hinzugefügt werden:

USERNAME:x:541:100:Vorname Nachname (Kommentar):/home/USERNAME:/bin/bash

wobei 514 die userID und 100 die groupID des Benutzer sind.

Automatischer Neustart eines Jobs bei Absturz

Manchmal hat man Jobs die einfach nur laufen müssen (z. b. NIS Server, Name Server). Und wenn sie einmal abstürzen, interessiert mich erst einmal gar nicht das Warum. Vielmehr möchte ich das der Jobs einfach wieder los läuft – Am besten von allein. Die näheren Umstände des Absturzes kann ich ja später noch anhand des Logfiles untersuchen. Unter Solaris gibt es hierzu die Service Management Facility (SMF), die sicher stellt, dass Systemdienste laufen und nötigenfalls auch einen (oder mehrere) Neustart(s) des Dienstes versucht, falls dieser abstürzen sollte. Unter Linux habe ich noch kein Äquivalent dazu gefunden. Eine brutale Methode einen Job bei gelegentlichen Abstürzen automatisch erneut zu starten, ist, ihn in einer Endlosschleife aufzurufen:

while true; do ./jobs.sh; done

Diese Methode geht allerdings nach hinten los, falls jobs.sh ein ernsthaftes Problem hat und sofort nach dem Start wieder terminiert. Zur Sicherheit sollte man also die Neustartorgie durch einen Zähler begrenzen, z. B. auf 1000 Versuche:

for i in {1..1000}; do ./jobs.sh; done

Das ist nicht elegant, hilft aber gelegentlich abstürzende Programme am Laufen zu halten.  Ist der kritische Zählerstand erreicht, wird nicht weiter versucht den Job neu zu starten. Man kann sich hierüber z. B. per Mail an root an  informieren lassen:

for i in {1..1000}; do ./jobs.sh; done ; echo "Message" | mail -s "Job died." root

Zimbra LDAP error

Beim Start von Zimbra 7.1.1 unter CentOS 5.7 kommt es bisweilen zu Fehlern mit dem LDAP, die verhindern, dass Zimbra startet:

[root@zimbra ~]# /etc/init.d/zimbra restart
Host zimbra.frdom.local
Stopping stats...Done.
Stopping mta...Done.
Stopping spell...Done.
Stopping snmp...Done.
Stopping cbpolicyd...Done.
Stopping archiving...Done.
Stopping antivirus...Done.
Stopping antispam...Done.
Stopping imapproxy...Done.
Stopping memcached...Done.
Stopping mailbox...Done.
Stopping logger...Done.
Stopping zmconfigd...Done.
Stopping ldap...Done.
Host zimbra.frdom.local
Starting ldap...Done.
Failed.
Failed to start slapd. Attempting debug start to determine error.
bdb(): PANIC: fatal region error detected; run recovery
bdb_db_close: database "": txn_checkpoint failed: Invalid argument (22).
backend_startup_one (type=hdb, suffix=""): bi_db_open failed! (-30974)
bdb_db_close: database "": alock_close failed

Ich vermute, dass das mit Inkonsistenzen im LDAP zusammenhängt. Die Zimbra-Instanz läuft in einer VirtualBox auf OpenIndiana 151a und ich denke, dass der Fehler durch die Virtualisierung verursacht wird (möglicherweise Dateisystemprobleme auf dem Host oder Gastsystem oder Problem beim „sauberen“ hoch- und runterfahren der VM). Ich hatte bisher nicht die Energie, die Zeit und den nötigen Durchblick, dass Problem zu analysieren. Deshalb hier erst einmal der Fix, der den LDAP repariert. Hat bis jetzt immer gut funktioniert (heilt evtl. auch andere LDAP-Datenbankfehler):

[root@zimbra ~]# su - zimbra
[zimbra@zimbra ~]$ cd /opt/zimbra/data/ldap/hdb/db
[zimbra@zimbra db]$ db_recover -v
Finding last valid log LSN: file: 4 offset 6038659
Recovery starting from [4][6038514]
Recovery complete at Tue Mar 6 16:43:08 2012
Maximum transaction ID 80000015 Recovery checkpoint [4][6038659]
[zimbra@zimbra db]$ logout

Wenn das geklappt hat kann Zimbra gestartet werden (habe hier restart benutzt):

root@zimbra ~]# /etc/init.d/zimbra restart
Host zimbra.frdom.local
Stopping stats...Done.
Stopping mta...Done.
Stopping spell...Done.
Stopping snmp...Done.
Stopping cbpolicyd...Done.
Stopping archiving...Done.
Stopping antivirus...Done.
Stopping antispam...Done.
Stopping imapproxy...Done.
Stopping memcached...Done.
Stopping mailbox...Done.
Stopping logger...Done.
Stopping zmconfigd...Done.
Stopping ldap...Done.
Host zimbra.frdom.local
Starting ldap...Done.
WARNING: Disk space below threshold for /opt/zimbra.
Starting zmconfigd...Done.
Starting logger...Done.
Starting mailbox...Done.
Starting memcached...Done.
Starting antispam...Done.
Starting antivirus...Done.
Starting snmp...Done.
Starting spell...Done.
Starting mta...Done.
Starting stats...Done.
[root@zimbra ~]# 

Et voila.