Een collega en een medewerker van onze voormalige leverancier hebben in het verleden wat shell-scripts uitgewerkt die op de NAS stonden. Via de taakplanner op de NAS werden die elke nacht uitgevoerd. Deze haalden van onze externe hostingomgeving de bestanden naar onze NAS, zodat we een eigen kopie hadden van de data. Zou er brand uitbreken of iets anders niet goed gaan, dan hadden we altijd nog de mogelijkheid om met onze eigen data een noodplan uit te werken. En het is ook heel fijn dat als een klant belt "ik heb per ongeluk een map met een dikke 100 bestanden verwijderd...." je die bestanden zelf redelijk snel terug kunt zetten.
Nadat de hosting naar een andere partij gemigreerd is, werkte dit niet meer. Het principe van rsync (linux-tool die standaard voor bestandssynchronisatie gebruikt wordt) is gebaseerd op SSH. In theorie kun je met private en public keys uitwisseling zorgen dat het ene systeem het andere systeem vertrouwt en je "dus" zonder wachtwoorden kunt syncen. Zo werkte het tenslotte ook op de oude omgeving. Maar nu dus niet meer... Ten tijde van de migratie had ik daar geen tijd voor om er verder in te duiken, maar nu dat (gelukkig) succesvol afgerond is, was dit iets wat nog aan mij knaagde. Want eigenlijk wil ik dit weer werkend krijgen.
Ik zit nu in de fase dat zaken (deels) werken, ik waarschijnlijk nog meer moet configureren/fouten afvangen e.d., maar omdat er mogelijk meer mensen problemen mee hebben, wil ik mijn acties hier delen. Misschien kan het jou uit de brand helpen, of ken je iemand die met dit probleem worstelt en kun je hem/haar hiermee weer een stap verder helpen.
Als je zoekt op public/private key, rsync e.d., dan kom je artikelen tegen over het mee geven van het wachtwoord via de commandline of via een environment-variabele. Dat werkt niet. Het schijnt dat dan "de andere kant" de rsync als daemon uitgevoerd moet worden of iets dergelijks. Ook kwam ik "expect" tegen. Dat had meer kans van slagen. Dat is namelijk een linux-tool waarmee je iets kunt uitvoeren en kunt aangeven wat "expected" is, namelijk de vraag om het wachtwoord. Die kun je mee geven in dat script en expect zorgt dan dat het wachtwoord doorgegeven wordt en vervolgens wordt het commando succesvol uitgevoerd. Yes!
Maar goed, dan loop je tegen de problemen aan van de NAS. Want er staan wel scripts op, maar "expect" niet. En die kun je ook niet installeren. Dus wat nu?
Nou, dan bedenk je dat voor de Zabbix monitoring we binnen ons bedrijf een proxy hebben draaien, een Docker container. En daar zouden we best nog een eigen container aan toe kunnen voegen. Eerst lokaal testen, zaken tunen en uiteindelijk heb ik het op mijn eigen pc werkend.
Dat werkend krijgen is een combinatie van informatie die je van anderen krijgt. Zo wilde ik eerst een container opzetten op basis van Arch Linux (daarmee kun je een minimaal systeem opbouwen). Mijn collega Jeroen Smink gaf mij echter de tip om Alpine te gebruiken (een term die ik volgens mij ook wel eens bij mijn PHP containers tegen ben gekomen). Ook daarmee heb je een hele kleine/lichtgewicht Linux-omgeving en hoef je niet zoals bij Arch alles zelf op te bouwen. Bij het inrichten van mijn Dockerfile liep ik tegen problemen aan bij het opstarten. Ik had een start.sh bestand gekopieerd met een COPY statement in de Dockerfile, maar het starten wilde niet lukken. En zo waren er nog een paar issues waar ik tegenaan liep. Met het stellen van de juiste vragen aan ChatGPT kreeg ik antwoorden waar ik wat mee kon en de boel kon fixen. Via StackOverflow had ik ook al wat antwoorden op vragen gekregen, onder andere op iets waar ik tegenaan liep. In je "expect" voer je namelijk het rsync-commando uit. Maar als er 1000-en bestanden over de lijn gaan, dan duurt dat statement heel lang. Niet erg, maar er zit een soort standaard time-out in expect. Waardoor het proces stopt. Dat zie je niet. Maar als je de bestanden checkt zie je dat die op een bepaald moment stoppen: er komt niet meer bij en we gaan niet door naar de volgende map. Door die aanpassing liep vervolgens het binnenhalen van de bestanden als een trein!
Het syncen ging goed, vervolgens was het nog een kwestie van monitoring toevoegen. In de oude situatie hadden we ook al monitoring, die heb ik hier ook weer toegevoegd. Omdat het nu allemaal "wat anders werkt" heb ik de scripts namelijk herschreven. Voor monitoring gebruiken we Zabbix. Want daar kun je handig via triggers zorgen dat als zaken fout gaan, taken niet uitgevoerd worden dat geconstateerd wordt en vervolgens geëscaleerd via verschillende kanalen.
Hierbij de belangrijkste stappen die ik uitgevoerd heb. Mocht je een vergelijkbaar probleem hebben, of een Linux container in Docker nodig hebben die bijvoorbeeld wat cron-taken voor je moet uitvoeren, dan moet dat met deze instructies mogelijk zijn.
1. Maak een bestand aan met de naam Dockerfile
Dit is een gewoon tekstbestand. Deze naamgeving is wat Docker standaard gebruikt. De inhoud van "mijn" bestand is dit, pas het zo nodig aan voor je eigen wensen;
# noodzakelijk: geef aan welke container je gebruik en zet bij opstarten ook meteen de juiste locatie, de home-folder
FROM alpine:latest
WORKDIR /home
# optioneel: dit zijn de tools die ik nodig had.
RUN apk add --no-cache expect rsync openssh bind-tools jq curl openrc# optioneel: in onze container sturen we data naar Zabbix. Daarvoor heb je zabbix_sender nodig. Die kopiëren we hier.
COPY ./zabbix_sender /home/# optioneel: voor onze rsync hebben we wat configuratiebestanden gemaakt. Hiermee worden ze gekopieerd.
COPY ./conf/* /home/conf/# noodzakelijk: we hebben wat shell-scripts die we naar de container kopiëren. start.sh is noodzakelijk voor een goede werking.
COPY ./*.sh /home/
# noodzakelijk: zorg dat dit shell-script uitvoerbaar is
RUN chmod +x /home/start.sh# optioneel: we hebben cron-taken, dus die "losse taken" kopiëren we naar een map cron in de home-folder.
COPY ./cron/* /home/cron/# optioneel: alleen als je cron-taken gaat draaien, dan zet je die taken in 1 bestand en kun je zo het rooster configureren.
RUN crontab /home/cron/cron.txt# optioneel: volgens mij nodig om cron te laten werken.
RUN mkdir /run/openrc
RUN mkdir /run/openrc/exclusive# noodzakelijk: het opstartscript, de laatste stap van het aanmaken van de container.
ENTRYPOINT ["sh", "/home/start.sh"]
2. Maak het bestand start.sh aan en vul het met de instructies die bij opstarten uitgevoerd moeten worden.
Het laatste statement moet zorgen dat het commando "actief" blijft. Anders stopt je container zodra het script "klaar" is met uitvoeren. Dat kan volgens mij met een sleep, maar het door mij gebruikte tail-commando wordt ook vaak geadviseerd;
In ons geval gaan we via rsync data van locatie A (en B) naar ons halen. Een eerste keer verbinden met een onbekende locatie levert je de melding "weet je zeker dat je wilt verbinden met A/B". Dat werkt natuurlijk niet met ons "expect"-commando, die verwacht dat je een wachtwoord in moet voeren. Daarom voer je bij het opstarten de externe servers meteen toe aan je "ssh_known_hosts" zodat je die vraag niet meer krijgt. crond zorgt dat de cronservice als daemon draait (volgens mij). Het werkt in ieder geval :)
#!/bin/bash
clear
echo "==== THIS DOCKER IMAGE SYNCS THE DATA FROM [xxx] TO OUR [system] ===="
echo "@ KEEP THIS CONTAINER ACTIVE @"
ssh-keyscan aaa.bbb.ccc.d >> /etc/ssh/ssh_known_hosts
ssh-keyscan aaa.bbb.ccc.m >> /etc/ssh/ssh_known_hosts
crond -l 2 -f
tail -f /dev/null
3. Wil je cron-taken draaien net als ik? Maak dan het bestand cron.txt wat in Dockerfile gekopieerd wordt.
Let op, de tijd is in UTC! Dus als je wilt dat het script om 00:00 uur draait, dan moet je 23:00 in je cron-script plaatsen.
# min hour day month weekday command
5 23 * * * sh /home/cron/rsync-serverA.sh
10 23 * * * sh /home/cron/rsync-serverB.sh
4. Maak een powershell-script, zodat je een container snel kunt verwijderen en daarna door het powershell-script uit te voeren binnen "no-time" de container weer draaiend hebt.
Een korte toelichting op het script, je ziet dat er volumes gekoppeld worden aan de NAS. De gebruiker die je daarvoor gebruikt moet rechten hebben op de NAS. Op deze manier kun je in je container bij de "network-shares" komen.
Dit is de inhoud van mijn script:
docker volume create schijf1 --driver local `
--opt type=cifs `
--opt device=//aaa.bb.cc.dd/schijf1 `
--opt o=username=[usernamexxx],password=[wachtwoord],vers=3.0,uid=1000,gid=1000docker volume create schijf2 --driver local `
--opt type=cifs `
--opt device=//aaa.bb.cc.dd/schijf2 `
--opt o=username=[usernamexxx],password=[wachtwoord],vers=3.0,uid=1000,gid=1000docker build --no-cache -t mijnimagenaam .
docker run -d --restart=unless-stopped --name mijncontainernaam --mount source=schijf1,target=/mnt/schijf1 --mount source=schijf2,target=/mnt/schijf2 mijnimagenaam
5. Geen time-out voor expect
De fix had ik gevonden via Stack Overflow in deze post. Dit is dus de syntaxt van je expect script:
#!/usr/bin/expect
set timeout -1
rsync ....
expect "Enter password:"
send "password\r"