Doen-dit-self kaalmetaalvoorsiening, of outomatiese voorbereiding van bedieners van nuuts af

Hallo, ek is Denis en een van my aktiwiteite is die ontwikkeling van infrastruktuuroplossings by X5. Vandag wil ek graag met jou deel hoe jy 'n outomatiese bedienervoorbereidingstelsel kan ontplooi wat gebaseer is op publieke beskikbare gereedskap. Na my mening is dit 'n interessante, eenvoudige en buigsame oplossing.

Doen-dit-self kaalmetaalvoorsiening, of outomatiese voorbereiding van bedieners van nuuts af

Met voorbereiding bedoel ons: verander 'n nuwe bediener uit die boks in 'n volledig gekonfigureerde bediener met OS. Linux of met die ESXi hypervisor (die ontplooiing van Windows-bedieners word nie in hierdie artikel bespreek nie).

Voorwaardes:

  • bedieners – bedieners wat gekonfigureer moet word.
  • installasiebediener is die hoofbediener wat die hele voorbereidingsproses oor die netwerk verskaf.

Hoekom is outomatisering nodig?

Kom ons sê daar is 'n taak: om bedieners van nuuts af massaal voor te berei, op 'n hoogtepunt - 30 per dag. Bedieners van verskillende vervaardigers en modelle, verskillende bedryfstelsels kan daarop geïnstalleer word, en kan 'n hipervisor hê of nie.

Watter bewerkings is ingesluit in die opstelproses (sonder outomatisering):

  • koppel 'n sleutelbord, muis, monitor aan die bediener;
  • konfigureer BIOS, RAID, IPMI;
  • werk komponent firmware op;
  • ontplooi 'n lêerstelselbeeld (of installeer 'n hypervisor en kopieer virtuele masjiene);

Let wel. Alternatiewelik is OS-ontplooiing moontlik deur installasie met 'n outo-antwoordlêer. Maar dit sal nie in die artikel bespreek word nie. Alhoewel u hieronder sal sien dat dit nie moeilik is om hierdie funksionaliteit by te voeg nie.

  • stel OS-parameters op (gasheernaam, IP, ens.).

Met hierdie benadering word dieselfde instellings opeenvolgend op elke bediener uitgevoer. Die doeltreffendheid van sulke werk is baie laag.

Die essensie van outomatisering is om menslike deelname uit die bedienervoorbereidingsproses uit te skakel. So veel as moontlik.

Outomatisering verminder stilstand tussen bedrywighede en maak dit moontlik om verskeie bedieners gelyktydig te voorsien. Die waarskynlikheid van foute as gevolg van menslike faktore word ook aansienlik verminder.

Doen-dit-self kaalmetaalvoorsiening, of outomatiese voorbereiding van bedieners van nuuts af

Hoe word bedieners outomaties gekonfigureer?

Kom ons ontleed al die stadiums in detail.

Jy het 'n Linux-bediener wat jy as 'n PXE-installasiebediener gebruik. Dienste word daarop geïnstalleer en gekonfigureer: DHCP, TFTP.

Dus, ons begin die bediener (wat gekonfigureer moet word) via PXE. Kom ons onthou hoe dit werk:

  • Netwerk selflaai is gekies op die bediener.
  • Die bediener laai die PXE-ROM van die netwerkkaart en kontak die installasiebediener via DHCP om 'n netwerkadres te verkry.
  • Die DHCP-installasiebediener reik 'n adres uit, sowel as instruksies vir verdere aflaai via PXE.
  • Die bediener laai die netwerklaaier vanaf die installasiebediener via PXE, verdere laai vind plaas volgens die PXE-konfigurasielêer.
  • Die selflaai vind plaas op grond van die ontvangde parameters (kern, initramfs, monteerpunte, squashfs-beeld, ens.).

Let wel. Die artikel beskryf selflaai via PXE via BIOS-modus. Tans implementeer vervaardigers UEFI selflaaimodus aktief. Vir PXE sal die verskil wees in die konfigurasie van die DHCP-bediener en die teenwoordigheid van 'n bykomende selflaaiprogram.

Kom ons kyk na 'n voorbeeld van 'n PXE-bedienerkonfigurasie (pxelinux-kieslys).

Lêer pxelinux.cfg/default:

default menu.c32
prompt 0
timeout 100
menu title X5 PXE Boot Menu
LABEL InstallServer Menu
	MENU LABEL InstallServer
	KERNEL menu.c32
	APPEND pxelinux.cfg/installserver
LABEL VMware Menu
	MENU LABEL VMware ESXi Install
	KERNEL menu.c32
	APPEND pxelinux.cfg/vmware
LABEL toolkit // меню по умолчанию
	MENU LABEL Linux Scripting Toolkits
	MENU default
	KERNEL menu.c32
	APPEND pxelinux.cfg/toolkit // переход на следующее меню

Lêer pxelinux.cfg/toolkit:

prompt 0
timeout 100
menu title X5 PXE Boot Menu
label mainmenu
    menu label ^Return to Main Menu
    kernel menu.c32
    append pxelinux.cfg/default
label x5toolkit-auto // по умолчанию — автоматический режим
        menu label x5 toolkit autoinstall
        menu default
        kernel toolkit/tkcustom-kernel
        append initrd=toolkit/tk-initramfs.gz quiet net.ifnames=0 biosdevname=0 nfs_toolkit_ip=192.168.200.1 nfs_toolkit_path=tftpboot/toolkit nfs_toolkit_script=scripts/mount.sh script_cmd=master-install.sh CMDIS2=”…”
label x5toolkit-shell // для отладки - консоль
        menu label x5 toolkit shell
        kernel toolkit/tkcustom-kernel
        append initrd=toolkit/tkcustom-initramfs.gz quiet net.ifnames=0 biosdevname=0 nfs_toolkit_ip=192.168.200.1 nfs_toolkit_path=tftpboot/toolkit nfs_toolkit_script=scripts/mount.sh script_cmd=/bin/bash CMDIS2=”…”

Die kern en initramfs op hierdie stadium is 'n intermediêre Linux-beeld, met behulp waarvan die hoofvoorbereiding en konfigurasie van die bediener sal plaasvind.

Soos u kan sien, stuur die selflaaiprogram baie parameters na die kern deur. Sommige van hierdie parameters word deur die kern self gebruik. En ons kan sommige vir ons eie doeleindes gebruik. Dit sal later bespreek word, maar vir nou kan jy net onthou dat alle geslaagde parameters beskikbaar sal wees in die intermediêre Linux-beeld via /proc/cmdline.

Waar kan ek dit kry, kernel en initramfs?
As basis kan u enige Linux-verspreiding kies. Waaraan ons aandag gee wanneer ons kies:

  • die selflaaibeeld moet universeel wees (beskikbaarheid van drywers, vermoë om bykomende nutsprogramme te installeer);
  • Heel waarskynlik sal jy die initramfs moet aanpas.

Hoe word dit in ons oplossing vir X5 gedoen? CentOS 7 is gekies as die basis.Kom ons probeer die volgende truuk: berei die toekomstige beeldstruktuur voor, pak dit in 'n argief en skep 'n initramfs, waarbinne ons lêerstelsel-argief sal wees. Wanneer die prent gelaai word, sal die argief uitgebrei word na die geskepte tmpfs-partisie. Op hierdie manier sal ons 'n minimale, dog volwaardige lewendige Linux-beeld kry met al die nodige nutsprogramme, wat slegs uit twee lêers bestaan: vmkernel en initramfs.

#создаем директории: 

mkdir -p /tftpboot/toolkit/CustomTK/rootfs /tftpboot/toolkit/CustomTK/initramfs/bin

#подготавливаем структуру:

yum groups -y install "Minimal Install" --installroot=/tftpboot/toolkit/CustomTK/rootfs/
yum -y install nfs-utils mariadb ntpdate mtools syslinux mdadm tbb libgomp efibootmgr dosfstools net-tools pciutils openssl make ipmitool OpenIPMI-modalias rng-tools --installroot=/tftpboot/toolkit/CustomTK/rootfs/
yum -y remove biosdevname --installroot=/tftpboot/toolkit/CustomTK/rootfs/

# подготавливаем initramfs:

wget https://busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-x86_64 -O /tftpboot/toolkit/CustomTK/initramfs/bin/busybox
chmod a+x /tftpboot/toolkit/CustomTK/initramfs/bin/busybox
cp /tftpboot/toolkit/CustomTK/rootfs/boot/vmlinuz-3.10.0-957.el7.x86_64 /tftpboot/toolkit/tkcustom-kernel

# создаем /tftpboot/toolkit/CustomTK/initramfs/init (ниже содержание скрипта):

#!/bin/busybox sh
/bin/busybox --install /bin
mkdir -p /dev /proc /sys /var/run /newroot
mount -t proc proc /proc
mount -o mode=0755 -t devtmpfs devtmpfs /dev
mkdir -p /dev/pts /dev/shm /dev/mapper /dev/vc
mount -t devpts -o gid=5,mode=620 devpts /dev/pts
mount -t sysfs sysfs /sys
mount -t tmpfs -o size=4000m tmpfs /newroot
echo -n "Extracting rootfs... "
xz -d -c -f rootfs.tar.xz | tar -x -f - -C /newroot
echo "done"
mkdir -p /newroot/dev /newroot/proc /newroot/sys
mount --move /sys  /newroot/sys
mount --move /proc /newroot/proc
mount --move /dev  /newroot/dev
exec switch_root /newroot /sbin/init

# упаковываем rootfs и initramfs:

cd /tftpboot/toolkit/CustomTK/rootfs
tar cJf /tftpboot/toolkit/CustomTK/initramfs/rootfs.tar.xz --exclude ./proc --exclude ./sys --exclude ./dev .
cd /tftpboot/toolkit/CustomTK/initramfs
find . -print0 | cpio --null -ov --format=newc | gzip -9 > /tftpboot/toolkit/tkcustom-initramfs-new.gz

Ons het dus die kern en initramfs gespesifiseer wat gelaai moet word. As gevolg hiervan, op hierdie stadium, deur die intermediêre linux-beeld via PXE te laai, sal ons die OS-konsole ontvang.

Groot, maar nou moet ons beheer oordra na ons "outomatisering".

Dit kan so gedoen word.

Kom ons neem aan dat ons na die laai van die prent beplan om beheer na die mount.sh-skrip oor te dra.
Kom ons sluit die mount.sh script in autorun in. Om dit te doen sal jy die initramfs moet wysig:

  • pak initramfs uit (as ons die bogenoemde initramfs-opsie gebruik, is dit nie nodig nie)
  • sluit kode by opstart in wat die parameters wat deur /proc/cmdline gaan, sal ontleed en beheer verder sal oordra;
  • pak initramfs.

Let wel. In die geval van die X5-gereedskapstel word laaibeheer na die skrif oorgedra /opt/x5/toolkit/bin/hook.sh с помощью override.conf в getty tty1 (ExecStart=…)

Dus, die prent word gelaai, waarin die mount.sh-skrip by outorun begin. Vervolgens ontleed die mount.sh-skrip die geslaagde parameters (script_cmd=) tydens uitvoering en begin die nodige program/script.

etiket gereedskapstel-motor
kern...
voeg...nfs_toolkit_script=scripts/mount.sh by script_cmd=master-install.sh

etiket gereedskapstel-dop
kern...
voeg...nfs_toolkit_script=scripts/mount.sh by script_cmd=/bin/bash

Doen-dit-self kaalmetaalvoorsiening, of outomatiese voorbereiding van bedieners van nuuts af

Hier aan die linkerkant is die PXE-kieslys, aan die regterkant is die beheeroordragdiagram.

Ons het die oordrag van beheer uitgepluis. Afhangende van die keuse van die PXE-kieslys, word óf die outokonfigurasieskrip óf die ontfoutingskonsole geloods.

In die geval van outomatiese konfigurasie, word die nodige gidse vanaf die installasiebediener gemonteer, wat bevat:

  • skrifte;
  • gestoorde BIOS/UEFI-sjablone vir verskeie bedieners;
  • firmware;
  • bedienerhulpmiddels;
  • logs

Vervolgens dra die mount.sh script beheer oor na die master-install.sh script vanaf die script gids.

Die skrifboom (die volgorde waarin hulle bekendgestel word) lyk so:

  • meester-installeer
  • deelfunksies (gedeelde funksies)
  • inligting (inligtingsuitset)
  • modelle (instelling van installasieparameters gebaseer op die bedienermodel)
  • prepare_utils (installering van nodige nutsprogramme)
  • fwupdate (firmware-opdatering)
  • diag (elementêre diagnostiek)
  • biosconf (BIOS/UEFI-instellings)
  • clockfix (stel die tyd op die moederbord)
  • srmconf (afgeleë koppelvlak-koppelvlakkonfigurasie)
  • raidconf (konfigurasie van logiese volumes)

een van:

  • voorafinstalleer (dra beheer oor na die bedryfstelsel of hypervisor installeerder, soos ESXi)
  • saamgevoeg-installeer (onmiddellike begin van die uitpak van die prent)

Nou weet ons:

  • hoe om 'n bediener via PXE te begin;
  • hoe om beheer na jou eie skrif oor te dra.


Kom ons gaan aan. Die volgende vrae het relevant geword:

  • Hoe om die bediener wat ons voorberei te identifiseer?
  • Watter nutsprogramme en hoe om die bediener op te stel?
  • Hoe om instellings vir 'n spesifieke bediener te kry?

Hoe om die bediener wat ons voorberei te identifiseer?

Dit is eenvoudig - DMI:

dmidecode –s system-product-name
dmidecode –s system-manufacturer
dmidecode –s system-serial-number

Alles wat jy nodig het is hier: verkoper, model, reeksnommer. As jy nie seker is dat hierdie inligting in alle bedieners teenwoordig is nie, kan jy hulle identifiseer deur hul MAC-adres. Of op beide maniere op dieselfde tyd, as die bedienerverkopers verskillend is en op sommige modelle is daar eenvoudig geen inligting oor die reeksnommer nie.

Op grond van die inligting wat ontvang is, word netwerkvouers vanaf die installasiebediener gemonteer en alles wat nodig is, word gelaai (hulpprogramme, firmware, ens.).

Watter nutsprogramme en hoe om die bediener op te stel?

Ek sal nutsprogramme vir Linux vir sommige vervaardigers verskaf. Alle nutsprogramme is beskikbaar op die amptelike webwerwe van verkopers.

Doen-dit-self kaalmetaalvoorsiening, of outomatiese voorbereiding van bedieners van nuuts af

Met die firmware dink ek alles is duidelik. Hulle kom gewoonlik in die vorm van verpakte uitvoerbare lêers. Die uitvoerbare lêer beheer die firmware-opdateringsproses en rapporteer die terugkeerkode.

BIOS en IPMI word gewoonlik deur sjablone gekonfigureer. Indien nodig, kan die sjabloon geredigeer word voordat dit afgelaai word.

RAID-hulpprogramme van sommige verskaffers kan ook met behulp van 'n sjabloon gekonfigureer word. As dit nie die geval is nie, sal jy 'n konfigurasieskrif moet skryf.

Die prosedure vir die opstel van RAID is meestal soos volg:

  • Ons versoek die huidige opstelling.
  • As daar reeds logiese skikkings is, vee ons dit uit.
  • Kom ons kyk watter fisiese skywe teenwoordig is en hoeveel daar is.
  • Skep 'n nuwe logiese skikking. Ons onderbreek die proses in geval van 'n fout.

Hoe om instellings vir 'n spesifieke bediener te kry?

Kom ons neem aan dat die instellings van alle bedieners op die installasiebediener gestoor sal word. In hierdie geval, om ons vraag te beantwoord, moet ons eers besluit hoe om die instellings na die installasiebediener oor te dra.

Aanvanklik kan jy met tekslêers klaarkom. (In die toekoms sal jy dalk 'n tekslêer wil gebruik as 'n terugvalmetode vir die oordrag van instellings.)

U kan 'n tekslêer op die installasiebediener "deel". En voeg sy mount by die mount.sh script.

Die lyne sal byvoorbeeld so lyk:

<reeksnommer> <gasheernaam> <subnet>

Hierdie lyne sal deur die ingenieur vanaf sy werkmasjien na die lêer oorgedra word. En dan, wanneer 'n bediener opgestel word, sal die parameters vir 'n spesifieke bediener uit die lêer gelees word.

Maar op die lang termyn is dit beter om 'n databasis te gebruik om instellings, toestande en logs van bedienerinstallasies te stoor.

Natuurlik is 'n databasis alleen nie genoeg nie, en jy sal 'n kliëntdeel moet skep met behulp waarvan instellings na die databasis oorgedra sal word. Dit is moeiliker om te implementeer in vergelyking met 'n tekslêer, maar in werklikheid is alles nie so moeilik soos dit lyk nie. Dit is heel moontlik om 'n minimale weergawe van 'n kliënt te skryf wat bloot self data na die databasis sal oordra. En in die toekoms sal dit moontlik wees om die kliëntprogram in vrye modus te verbeter (verslae, druk van etikette, stuur van kennisgewings, ens. wat in gedagte kom).

Deur 'n spesifieke versoek aan die databasis te rig en die bedienerreeksnommer te spesifiseer, sal ons die nodige parameters ontvang om die bediener te konfigureer.

Boonop hoef ons nie met slotte vorendag te kom vir gelyktydige toegang nie, soos die geval is met 'n tekslêer.

Ons kan die konfigurasielog in alle stadiums na die databasis skryf en die installasieproses beheer deur gebeure en vlae van die voorbereidingsfases.

Nou weet ons hoe:

  • selflaai die bediener via PXE;
  • beheer oor na ons skrif oordra;
  • identifiseer die bediener wat voorberei moet word deur sy reeksnommer;
  • konfigureer die bediener met die toepaslike nutsprogramme;
  • dra instellings oor na die installasie bediener databasis met behulp van die kliënt deel.

Ons het uitgevind hoe:

  • die geïnstalleerde bediener ontvang die nodige instellings vanaf die databasis;
  • alle voorbereidingsvordering word in die databasis aangeteken (logboeke, gebeure, verhoogvlae).

Wat van die verskillende soorte sagteware wat jy installeer? Hoe om 'n hypervisor te installeer, 'n VM te kopieer en dit alles op te stel?

In die geval van die implementering van 'n lêerstelselbeeld (linux) na hardeware, is alles redelik eenvoudig:

  • Nadat ons al die bedienerkomponente opgestel het, ontplooi ons die beeld.
  • Installeer die grub selflaaiprogram.
  • Ons chroot en konfigureer alles wat nodig is.

Hoe om beheer na die OS-installeerder oor te dra (met ESXi as voorbeeld).

  • Ons organiseer die oordrag van beheer vanaf ons skrip na die hypervisor-installeerder deur die outomatiese antwoordlêer (kickstart) te gebruik:
  • Ons verwyder die huidige partisies op die skyf.
  • Skep 'n partisie met 'n grootte van 500MB.
  • Ons merk dit as selflaaibaar.
  • Formateer na FAT32.
  • Ons kopieer die ESXi-installasielêers na die wortel.
  • Syslinux installeer.
  • Kopieer syslinux.cfg na /syslinux/

default esxi
prompt 1
timeout 50
label esxi
kernel mboot.c32
append -c boot.cfg

  • Kopieer mboot.c32 na /syslinux.
  • Boot.cfg moet kernelopt=ks=ftp:// hê /ks_esxi.cfg
  • Ons herlaai die bediener.

Nadat die bediener herlaai het, sal die ESXi-installeerder van die bediener se hardeskyf aflaai. Alle nodige installeerderlêers sal in die geheue gelaai word en dan sal die ESXi-installasie begin, volgens die gespesifiseerde outo-antwoordlêer.

Hier is 'n paar reëls van die outorespons-lêer ks_esxi.cfg:

%firstboot --interpreter=busybox
…
# получаем серийный номер

SYSSN=$(esxcli hardware platform get | grep Serial | awk -F " " '{print $3}')

# получаем IP

IPADDRT=$(esxcli network ip interface ipv4 get | grep vmk0 | awk -F " " '{print $2}')
LAST_OCTET=$(echo $IPADDRT | awk -F'.' '{print $4}')

# подключаем NFS инсталл-сервера

esxcli storage nfs add -H is -s /srv/nfs_share -v nfsshare1

# копируем временные настройки ssh, для использования ssh-клиента

mv /etc/ssh /etc/ssh.tmp
cp -R /vmfs/volumes/nfsshare1/ssh /etc/
chmod go-r /etc/ssh/ssh_host_rsa_key

# копируем ovftool, для развертывания ВМ сейчас, плюс возможно пригодится позже

cp -R /vmfs/volumes/nfsshare1/ovftool /vmfs/volumes/datastore1/

# развертываем ВМ

/vmfs/volumes/datastore1/ovftool/tools/ovftool --acceptAllEulas --noSSLVerify --datastore=datastore1 --name=VM1 /vmfs/volumes/nfsshare1/VM_T/VM1.ova vi://root:[email protected]
/vmfs/volumes/datastore1/ovftool/tools/ovftool --acceptAllEulas --noSSLVerify --datastore=datastore1 --name=VM2 /vmfs/volumes/nfsshare1/VM_T/VM2.ova vi://root:[email protected]

# получаем строку с настройками нашего сервера

ssh root@is "mysql -h'192.168.0.1' -D'servers' -u'user' -p'secretpassword' -e "SELECT ... WHERE servers.serial='$SYSSN'"" | grep -v ^$ | sed 's/NULL//g' > /tmp/servers
...
# генерируем скрипт настройки сети

echo '#!/bin/sh' > /vmfs/volumes/datastore1/netconf.sh
echo "esxcli network ip interface ipv4 set -i=vmk0 -t=static --ipv4=$IPADDR --netmask=$S_SUB || exit 1" >> /vmfs/volumes/datastore1/netconf.sh
echo "esxcli network ip route ipv4 add -g=$S_GW -n=default || exit 1" >> /vmfs/volumes/datastore1/netconf.sh
chmod a+x /vmfs/volumes/datastore1/netconf.sh

# задаем параметр guestinfo.esxihost.id, указываем в нем серийный номер

echo "guestinfo.esxihost.id = "$SYSSN"" >> /vmfs/volumes/datastore1/VM1/VM1.vmx
echo "guestinfo.esxihost.id = "$SYSSN"" >> /vmfs/volumes/datastore1/VM2/VM2.vmx
...
# обновляем информацию в базе

SYSNAME=$(esxcli hardware platform get | grep Product | sed 's/Product Name://' | sed 's/^ *//')
UUID=$(vim-cmd hostsvc/hostsummary | grep uuid | sed 's/ //g;s/,$//' | sed 's/^uuid="//;s/"$//')
ssh root@is "mysql -D'servers' -u'user' -p'secretpassword' -e "UPDATE servers ... SET ... WHERE servers.serial='$SYSSN'""
ssh root@is "mysql -D'servers' -u'user' -p'secretpassword' -e "INSERT INTO events ...""

# возвращаем настройки SSH

rm -rf /etc/ssh
mv /etc/ssh.tmp /etc/ssh

# настраиваем сеть и перезагружаемся

esxcli system hostname set --fqdn=esx-${G_NICK}.x5.ru
/vmfs/volumes/datastore1/netconf.sh
reboot

Op hierdie stadium word die hypervisor geïnstalleer en gekonfigureer, en virtuele masjiene word gekopieer.

Hoe om virtuele masjiene nou op te stel?

Ons het 'n bietjie gekul: tydens installasie het ons die parameter guestinfo.esxihost.id = "$SYSSN" in die VM1.vmx-lêer gestel en die reeksnommer van die fisiese bediener daarin aangedui.

Nou, nadat jy begin het, kan die virtuele masjien (met die vmware-tools-pakket geïnstalleer) toegang tot hierdie parameter kry:

ESXI_SN=$(vmtoolsd --cmd "info-get guestinfo.esxihost.id")

Dit wil sê, die VM sal homself kan identifiseer (dit ken die reeksnommer van die fisiese gasheer), 'n versoek aan die installasiebedienerdatabasis kan rig en die parameters ontvang wat gekonfigureer moet word. Dit is alles saamgestel in 'n skrip, wat outomaties bekendgestel moet word wanneer guestos vm begin (maar een keer: RunOnce).

Nou weet ons hoe:

  • selflaai die bediener via PXE;
  • beheer oor na ons skrif oordra;
  • identifiseer die bediener wat voorberei moet word deur sy reeksnommer;
  • konfigureer die bediener met die toepaslike nutsprogramme;
  • oordra instellings na die installasie bediener databasis met behulp van die kliënt deel;
  • konfigureer verskeie tipes sagteware, insluitend die implementering van die esxi hypervisor en die opstel van virtuele masjiene (alles outomaties).

Ons het uitgevind hoe:

  • die geïnstalleerde bediener ontvang die nodige instellings vanaf die databasis;
  • alle voorbereidingsvordering word in die databasis aangeteken (logboeke, gebeure, verhoogvlae).


Die bottom line:

Ek glo dat die uniekheid van hierdie oplossing lê in sy buigsaamheid, eenvoud, vermoëns en veelsydigheid.

Skryf asseblief in die kommentaar wat jy dink.

Bron: will.com

Voeg 'n opmerking