Loading...
06-05-2019

Sinds de komst van DotNet Core, ben ik nagenoeg volledig overgestapt op het hosten van mijn applicaties in Docker. Dit werkt niet alleen heel handig, maar ook de afhankelijkheid onderling is afgenomen. Zo ben ik daarnaast afgestapt van m'n eigen hacky mail omgeving op basis van Postfix, Dovecot en MySql, naar Mailcow, die dezelfde tools gebruikt, maar dan goed op elkaar afgestemd, in Docker draait en met een hele aardige interface. Mailcow is daarmee bijzonder makkelijk te installeren en te configureren voor direct gebruik.

Nou blijkt dat de mail van Mailcow in een Docker volume opgeslagen wordt en deze staat (in mijn geval) op de eerste partitie van de disk. Dit is niet zo wenselijk, aangezien deze daarmee volloopt. Tevens maak ik normaal gesproken een ZFS partitie aan, zodat ik snapshots kan maken voor backup's enzo, maar de eerste partitie is meestal ext4. Voor alle applicaties die ik draai, zorg ik dan ook dat de data samen met de docker bestanden allemaal in de zelfde map staan (een ZFS pool in dit geval).

In het geval van en standaard Mailcow installatie is dit niet het geval. Voor de opslag van de mail, wordt een Docker volume gebruikt, en deze wordt in mijn geval in /var/lib/docker opgeslagen. Deze partitie loopt nu dan ook redelijk vlot vol, vanwege de aantal mailboxen die erop staan. Mailcow heeft zelf een beschrijving hoe je dit kan aanpassen, maar voordat ik dat in productie ga doorvoeren, test ik het eerst in VM.

Om dit na te bootsen, maak ik zoveel mogelijk een zelfde situatie na. In dit geval gebruik ik Ubuntu 18.04, met 2 partities, waarbij de 2de de ZFS partitie is. Voor de test gebruik ik VMWare Fusion op een MacBook. Ubuntu heb ik al gedownload. Vervolgens maak ik een nieuwe machine met een disk van 50Gig. De "easy install" van VMWare zet ik uit, want ik wil handmatig de partities kunnen instellen.

Zo gezegd, zo gedaan. De installatie start, en bij de partionering maak ik 1 partitie aan van 20 Gig, en de rest laat ik open. Out of box kan Ubuntu niet met ZFS omgaan. Dit is na de installatie makkelijk om toe te voegen. Na de rest van de standaard installatie en een reboot kan ik inloggen op het systeem. Voor het gemak heb ik wel de ssh server geïnstalleerd, zodat ik de terminal van MacOs kan gebruiken.

Vervolgens update ik het systeem, installeer Docker en Docker Compose:

sudo -s
apt update && apt upgrade -y && apt install -y docker.io apt-transport-https

De docker compose versie van Ubuntu loopt nogal een stuk achter. Daarom installeer ik deze niet via apt, maar handmatig.

# curl -L "https://github.com/docker/compose/releases/download/1.23.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose

Hierna kan de versie van docker compose getest worden:

# docker-compose --version
docker-compose version 1.23.1, build b02f1306

Voor de 2de partitie wordt ZFS als filesystem gebruikt. Met ZFS kunnen readonly snapshots gemaakt worden, wat erg handig is voor het maken van backups en het testen van updates bijvoorbeeld. Out of the box installeert Ubuntu dit niet, dus dat doen we handmatig:

apt-get install -y zfsutils-linux

Vervolgens moeten we de partitie aanmaken en deze van ZFS voorzien. Het is hierbij belangrijk de juiste disk op te zoeken en vervolgens de lege ruimte van het filesystem te voorzien. Hieronder test ik eerst of er al een zpool is, en vervolgens zoek ik de naam op van onze disk:

# zpool status
no pools available

# lsblk -S
NAME HCTL       TYPE VENDOR   MODEL             REV TRAN
sda  2:0:0:0    disk VMware,  VMware Virtual S 1.0  spi
sr0  4:0:0:0    rom  NECVMWar VMware SATA CD01 1.00 sata

Sr0 is de virtuele cd-rom die geïnstalleerd is in VMWare. Het gaat dus om de eerste regel die begint met sda. Dat is dan ook de virtuele disk waar onze partitie op staat. Via het commando fdisk is het snel te zien welke partities deze disk heeft. Fdisk gebruik ik daarna ook om de nieuwe partitie te maken:

# fdisk /dev/sda

Welcome to fdisk (util-linux 2.31.1).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Command (m for help): p
Disk /dev/sda: 40 GiB, 42949672960 bytes, 83886080 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 573B2250-71D0-48F1-88A7-A1B47BA9FBD5

Device     Start      End  Sectors Size Type
/dev/sda1   2048     4095     2048   1M BIOS boot
/dev/sda2   4096 41947135 41943040  20G Linux filesystem

Command (m for help): n
Partition number (3-128, default 3): 3
First sector (41947136-83886046, default 41947136): 
Last sector, +sectors or +size{K,M,G,T,P} (41947136-83886046, default 83886046): 

Created a new partition 3 of type 'Linux filesystem' and of size 20 GiB.

Command (m for help): p
Disk /dev/sda: 40 GiB, 42949672960 bytes, 83886080 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 573B2250-71D0-48F1-88A7-A1B47BA9FBD5

Device        Start      End  Sectors Size Type
/dev/sda1      2048     4095     2048   1M BIOS boot
/dev/sda2      4096 41947135 41943040  20G Linux filesystem
/dev/sda3  41947136 83886046 41938911  20G Linux filesystem

Command (m for help): w
The partition table has been altered.
Syncing disks.

Er zal eerst een extra partitie toegevoegd moeten worden. Kies daarom in fdisk voor "n" van new. In mijn geval kan ik alle default waardes gebruiken om de rest van de lege ruimte te gebruiken voor de partitie. Hierna kunnen via het commando "w" (write) de wijzigingen worden opgeslagen. Deze partitie heeft nog geen filesystem. Die wordt via zpool toegevoegd. Als naam kies ik "pool" hoewel dat niet de meest handige naam is, maar in dit geval komt dat overeen met het systeem waar ik deze test voor doe:

# zpool create -f pool /dev/sda3
# zpool list
NAME   SIZE  ALLOC   FREE  EXPANDSZ   FRAG    CAP  DEDUP  HEALTH  ALTROOT
pool  19.9G   660K  19.9G         -     0%     0%  1.00x  ONLINE  

# mount | grep pool
pool on /pool type zfs (rw,xattr,noacl)

Kijkende naar de output van mount, is de ZFS pool dus op /pool gemount. Dit wil ik aanpassen naar /mnt/pool, zoals dat ook in mijn productie situatie gedaan is:

# mkdir -p /mnt/pool && zfs set mountpoint=/mnt/pool pool
# mount | grep pool
pool on /mnt/pool type zfs (rw,xattr,noacl)

Installatie Mailcow

Het probleem is dus dat mailcow standaard een docker volume gebruikt en die wordt op de eerste partitie aangemaakt. Die zal ergens onder /var/lib/docker te vinden moeten zijn. Dit is onwenselijk omdat bij de groei van de mailboxen e.d. de 'root' partitie volloopt. Helaas realiseerde ik me dit pas nadat mailcow op meerdere servers al zo geïnstalleerd is. Zelf kies ik ervoor, om voor alle applicaties die ik via docker host, de applicatie data bij de docker-compose file te houden. Hierbij staat in de root van het project de compose file met wat aanvullende bestanden, een build directory waarin per applicatie eventueel nog een dockerfile te vinden is en een data directory waarin de data per applicatie wordt opgeslagen.

Omdat het om een bestaande mailcow installatie gaat, wil ik exact dezelfde versie in de testomgeving hebben. Om mailcow te installeren wordt er een clone van het project uit github gemaakt. Hierbij kunnen er ook gekozen worden om een specifieke commit (versie) te gebruiken. Dit is dan ook wat ik wil om een zo goed mogelijk overeenkomende test productie situatie te hebben. Dus eerst zoek ik op welke commit er momenteel gebruikt wordt in productie. Hier zit overigens een addertje onder het gras, want het update script van Mailcow maakt blijkbaar lokale Git merges vanwege de configuratie file. "git rev-parse HEAD" geeft in mijn geval dan ook een lokale hash terug en niet die in de origin terug te vinden is. Via het log vind ik de juiste terug:

# git log -n3
commit a12e8f0f6948c4ee2bbdb9eef26f26e436a9332a (HEAD -> master)
Merge: 00131499 6a13609b
Author: moo <moo@cow.moo>
Date:   Tue Mar 19 15:30:08 2019 +0100

    After update on 2019-03-19_15_29_12

commit 0013149989f9181c749fe5ee8ead1b6efca9e0b4
Author: moo <moo@cow.moo>
Date:   Tue Mar 19 15:30:06 2019 +0100

    Before update on 2019-03-19_15_29_12

commit 6a13609bf0e1bc75c907bdc46a4099b92de69eb3 (origin/master, origin/HEAD)
Author: andryyy <andre.peters@debinux.de>
Date:   Tue Mar 19 08:45:08 2019 +0100

De 3de is in mijn geval de commit die ik wil gebruiken. Eerst maak ik een pool aan op de ZFS partitie, om daarin Mailcow te klonen.

# zfs create pool/mailcow
# cd /mnt/pool/mailcow/
# git clone https://github.com/mailcow/mailcow-dockerized
# cd mailcow-dockerized
# git checkout 6a13609bf0e1bc75c907bdc46a4099b92de69eb3
Note: checking out '6a13609bf0e1bc75c907bdc46a4099b92de69eb3'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at 6a13609b [Web] Fix slow UI by switching QR provider and only generating qr image on demand

Hierna kan de installatie verder afgerond worden:

# ./generate_config.sh
Press enter to confirm the detected value '[value]' where applicable or enter a custom value.
Hostname (FQDN): lnx-test.lan.nl
Timezone [Etc/UTC]: 
Installed memory is <= 2.5 GiB. It is recommended to disable ClamAV to prevent out-of-memory situations.
ClamAV can be re-enabled by setting SKIP_CLAMD=n in mailcow.conf.
Do you want to disable ClamAV now? [Y/n] y
Disabling Solr on low-memory system.

Vervolgens moet de configuratie file nog aangepast worden. Hierbij zet ik het automatisch bijwerken van de letsencrypt certificaten uit. Dit gaat toch niet, omdat de VM niet direct aan de buitenwereld verbonden is.

# nano mailcow.conf

SKIP_LETS_ENCRYPT=y

Hier kunnen de containers gestart worden:

docker-compose pull && docker-compose up -d

Inloggen kan via het IP van de VM met de gebruiker admin en wachtwoord moohoo. Normaal gesproken is het verstandig dit meteen aan te passen, maar dat laat ik voor de test maar zitten. Tevens kan je standaard ook zonder https inloggen. In productie moet dit wel uitgeschakeld worden. Normaal laat ik al het verkeer via een reverse proxy verlopen

Verplaatsen van de storage

In de beschrijving in de Maiilcow documentatie Move vmail volume wordt eerst de lokatie van de volume van de vmail directory opgezocht. Vervolgens wordt de data gekopieerd naar een nieuwe lokatie, de verwijzing gemaakt naar deze lokatie en Mailcow weer opgestart. Email's die voor het verplaatsen in de mailbox stonden, zouden na het verplaatsen weer terug gevonden moeten kunnen worden. Eerst bepaal ik de lokatie van de vmail volume op met "docker volume inspect". Hierna moet de container(s) gestopt worden, de data gekopieerd (let op de -a!), de override configuratie toegevoegd worden met het nieuwe pad en de containers weer gestart worden:

# docker volume inspect mailcowdockerized_vmail-vol-1
[
    {
        "CreatedAt": "2019-05-04T11:30:24Z",
        "Driver": "local",
        "Labels": {
            "com.docker.compose.project": "mailcowdockerized",
            "com.docker.compose.version": "1.23.1",
            "com.docker.compose.volume": "vmail-vol-1"
        },
        "Mountpoint": "/var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data",
        "Name": "mailcowdockerized_vmail-vol-1",
        "Options": null,
        "Scope": "local"
    }
]

# docker-compose down
# mkdir -p /mnt/pool/mailcow/data/vmail
# rsync -a -u -v /var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data/* /mnt/pool/mailcow/data/vmail/
# nano docker-compose.override.yml

version: '2.1'
volumes:
  vmail-vol-1:
    driver_opts:
      type: none
      device: /mnt/pool/mailcow/data/vmail
      o: bind

# docker volume rm mailcowdockerized_vmail-vol-1
# docker-compose up -d

In mijn geval starte ik docker-compose up voordat ik het oorspronkelijke volume had weggehaald. Docker kwam hierbij met een melding dat het volume eerst verwijderd moet worden. Ondanks dat ik dat liever niet deed (iets met angst zweet etc) heb ik dit toch maar door gezet. Docker meldt hierbij zelf wat het commando is:

# docker-compose up -d
WARNING: The WATCHDOG_NOTIFY_EMAIL variable is not set. Defaulting to a blank string.
Creating network "mailcowdockerized_mailcow-network" with driver "bridge"
ERROR: Configuration for volume vmail-vol-1 specifies "type" driver_opt none, but a volume with the same name uses a different "type" driver_opt (None). If you wish to use the new configuration, please remove the existing volume "mailcowdockerized_vmail-vol-1" first:
$ docker volume rm mailcowdockerized_vmail-vol-1

Na opstarten heb ik de mailbox gecontroleerd en alles werkte. Voortaan zullen de mailtjes dus opgeslagen staan in de map van de applicatie. Houdt er wel rekening mee, dat dit niet de enige bron van data is. Mailcow gebruikt ook nog een database (MySql / Postgresql) database en wat andere tools. Deze zouden voor de volledigheid ook verplaatst moeten worden. Mijn voorkeur gaat dan ook uit, dat dit standaard zo gebeurd. Misschien dat ik dit bij het project nog een keer ga aankaarten

  • Docker
  • Linux
  • Mailcow