Weboldalak biztonsági mentése - hogyan csináld jól szabadúszóként/ügynökségként? előnézeti képe

Weboldalak biztonsági mentése - hogyan csináld jól szabadúszóként/ügynökségként?

| Olvasási idő: 20 perc

Ha már költözőben vagy a tárhely szolgáltatódtól egy villámgyors VPS-re, ne felejts el egy nagyon fontos dolgot: a biztonsági mentések készítéséről és tárolásáról is neked kell gondoskodnod. 

De hát végülis mi a bonyolult ezen - rosszabb esetben feldobunk egy plugint (khm... WordPress, khm...), ami vagy működik, vagy nem, jobb esetben esetleg beállítunk egy cronjob-ot, ami minden nap összecsomagolja a kis weboldalunkat, és elteszi ugyanott (vagy egy másik VPS-ünkön)  mondjuk egy backups mappába, ahol csak gyűlik és gyűlik a sok zip fájl. Na de mi van akkor, ha elszáll a szolgáltató adott szerverterme vagy csak simán le kell lőnünk a backupos VPS-t? Vagy ha elfogy a szabad hely a szerveren (ráadásul ha a VPS tárhelyét kell bővíteni, akkor az drágább is), és megáll a teljes alkalmazás/weboldal, mert nem maradt semmilyen hely, ahova dolgozhatna? Hátőőő... ugye. 

Egy jó ötlet lehet a szervertől független helyre pakolászni ezeket a mentéseket, na de hova? Elsőre eszünkbe juthatna a Google Drive, esetleg a Dropbox - viszont itt bekattanhat az OCD-nk, hogy egyrészt mit keres a privát tárhelyünkön munkahelyi cucc, vagy mi lesz, ha mondjuk véletlenül letöröljük (de ha nem mi, akkor a telefonunkhoz hozzáférő) a mentéseket, esetleg ott is elfogy a tárhelyünk, meg persze hogyan osztjuk meg az adatokat az ügyféllel - szóval igen, szívás. De ne aggódj, mert van "normális" megoldás, amit most meg is mutatok neked. 

A Terv

A kirakós fő összetevője (a VPS-ek mellett) a DigitalOcean Spaces nevű szolgáltatása, ami nem más, mint egy S3 kompatibilis object storage - hogy mi?! Menjünk szépen sorjában, hogy mi mit jelent: 

  • S3 kompatibilis: vagyis az Amazon S3 nevű szolgáltatásának API-jával kompatibilis. Ez azt jelenti, hogy - legalábbis elméletileg - az összes dokumentáció, amit az S3-hoz használni lehet, és az összes program, ami az Amazon S3-at használja, szinte fájdalommentesen átállítható Amazon S3-ról DigitalOcean Spaces-re, "csak" a kapcsolódási adatokat kell "átírni". 
  • object storage: struktúrálatlan adat blob-ok (binary large object) formájában menti el és nyeri vissza az adatokat ahelyett, hogy fájlokként tenné ugyanezt, egy HTTP API-n keresztül. Ettől sem lettünk sokkal tájékozottabbak, ezért javaslom, hogy nézz rá erre a bejegyzésre, ami részletezi, hogy mi a különbség a block és az object storage között. Nekünk legyen elég egyelőre annyi, hogy webes biztonsági mentésre az object storage a nyerő

Fontos: a cikk feltételezi egy működő VPS létezését, amit nagyrészt az én útmutatóim alapján állítottál be: 

    Persze nem kötelező ezek szerint menni, de feltételezem, hogy az ezekben a bejegyzésekben foglalt tennivalókat megtetted és úgy, ahogyan ott írom őket. Ha nem, akkor értelemszerűen változtatnod kell az alábbiakon is, az eltéréseknek megfelelően!

    Árazás

    Egy fontos tényező az árazás, ami a Spaces esetében szerintem kifejezetten kedvező: van egy alapdíj, ami havi 5 dollár, ebben benne van: 

    • 250GB tárhely, 
    • 1TB kimenő adatforgalom, 
    • korlátlan feltöltés, 
    • korlátlan "Space",

    ha pedig túllépjük az "alapcsomagban" szereplő mennyiséget, akkor azon felül 0.02 dollár/GB (vagyis 250 GB/5 dollár, de gigánként számolva). Ezzel nagyon kedvezően tudunk nagymennyiségű adatot tárolni, biztonságosan, miközben nem kell az infrastruktúráról gondoskodnunk. 

    DO Spaces Main page

    Hozzuk létre az első Space-ünket

    Mielőtt továbbmennénk, készítsük el a Space-t, amivel dolgozni fogunk: jobb felső sarokban Create, aztán pedig Spaces. Válasszunk helyszínt, tiltsuk le a fájlok listázását, válasszunk nevet (alatta megjelenik majd a Space neve, ezt használni fogjuk később), majd pedig nyomjunk a zöld Create a Space gombra. A létrehozás után navigáljunk el a Kulcsainkhoz (Manage Keys a Spaces áttekintő nézetén, vagy pedig bal oldalt API menüpont) és hozzunk létre egy új Spaces access key-t a Generate New Key gombra kattintva. A név megadása után (érdemes lehet VPS-enként eltérő kulcsot használni) a Key oszlopban megjelenik kettő sor: az első lesz a key azonosítója, a második sorban levő pedig a "titok". Ezeket másoljuk ki, szükség lesz rájuk! A secretet soha többet nem fogjuk tudni megnézni a DO felületén, tehát ha elveszítjük, újat kell generálnunk!

    Do Api Screen

    Mivel nem egy "sima" tárhelyről van szó, ezért szükségünk lesz egy programra, amivel könnyen tudunk feltölteni a tárhelyünkre, ez pedig az s3cmd. Telepíteni Ubuntu-n a tárolóból lehet, a sudo apt-get install s3cmd parancs kiadásával. Amikor települt, futtassuk az s3cmd paranccsal, hogy az alap konfigurációs fájl létrejöjjön, amiben később a beállításokat tárolni fogjuk. Futtatás után nyissuk meg a ~/.s3cfg fájlt (pl.: nano ~/.s3cfg), és töltsük ki az alábbiakat a Spaces létrehozásakor kapott adatokkal: 

    • access_key: ide kerül a Key azonosítója (a fenti képen az első sorban levő szöveg)
    • host_base: ide kerül a Space-ünk settings oldalán található Endpoint címe
    • host_bucket: attól függően, hogy milyen helyszínt választottunk a Space-nek, kerül ide cím - Amszterdam esetén %(bucket)s.ams3.digitaloceanspaces.com - az ams3.digitaloceanspaces.com a változó rész, a többi mindegyik esetben fix. 
    • secret_key: ide kerül a Secret értéke, a fenti képen a második sorban levő szöveg

    A többi opcióval nem kell foglalkozzunk, maradhatnak az alapértelmezetten beállított értékek. 

    Próbáljuk ki, hogy minden megfelelően működik-e a s3cmd info s3://{{ BUCKETNAME }} paranccsal, ahol a {{ BUCKETNAME }} részt cseréld le a Space nevére (nálam például clientbackups a Space neve, ezért én s3cmd info s3://clientbackups paranccsal tudom lekérdezni az infókat). Ha minden jó, akkor egy, az alábbihoz hasonló választ fogsz kapni: 

    DO Bucket test

    Nagyon jó, sikerült kapcsolódni. Feltölteni fájlokat a s3cmd put MENTENDO/FILE.zip s3://{{ BUCKETNAME }}/UTVONAL/A/SPACEBEN formátumú paranccsal tudunk, ahol MENTENDO/FILE a feltöltendő fájl elérési útvonala (ez a későbbiekben jobb lesz, ha abszolútan adjuk meg), az UTVONAL/A/SPACEBEN pedig egy sima könyvtárszerkezet, azzal az eltéréssel, hogy itt nem kell létrehozni könyvtárakat - ami azt illeti, nincsenek is könyvtárak -, simán pakolhatunk bármilyen útvonalra. Na de ha nincsenek könyvtárak, akkor... mi van?? Szóval amit mi könyvtárként szoktunk használni, azt az S3 prefixnek hívja: minden fájlnak van egy prefixe, amin "belül" helyezkedik el, és ezt a legkönnyebb "könyvtárként" megjeleníteni. Ezt csak azért írtam le, hogy amikor majd prefixről lesz szó, tudjuk, miről is beszélünk. 

    Ha kipróbáltuk ezt egy tetszőleges fájlunkkal, akkor azt megtaláljuk a Spaces felületén, a Space-re kattintva, könyvtár szerkezetben, ahogy az alábbi képen is látható: 

    Spaces Overview

    Haladunk - de hogyan lesz a weboldalunkból és az adatbázisból fájl, amit fel tudunk tölteni? 

    Fájlok csomagolása

    Az elképzelésem az, hogy a weboldalunk fájljait összecsomagoljuk, amit majd egyben töltünk fel a Space-be. Lesznek viszont olyan könyvtárak is, amikre nem lesz szükségünk, azokat ki kellene szűrni a tömörítésből. Ezt megtehetjük a zip program -x kapcsolójával, ahol csak fel kell sorolnunk az exclude-olni kívánt mappákat, valahogy így: zip -r0q a.zip akonyvtar -x ".git" "node_modules". A többi kapcsoló jelentése: 

    • -r: rekurzív, vagyis a mappákat is tömöríti
    • -0: a tömörítés mértéke, értéke 0-9 közötti lehet
    • -q: quiet, vagyis csendes: nem sorolja fel az összes tömörített fájlt. 

    Érdemes lehet kísérletezni a tömörítés mértékével, melyet a -[SZÁM] kapcsolóval tehetünk meg (tehát pl. -0 csak tárolás, -9 a legdurvább tömörítés). Méretben komoly eltéréseket okozhat, de a mentés idejét meghosszabbíthatja, tehát teszteljük, hogy melyik a nekünk megfelelő mérték. Ezekben a kódrészletekben -0 kapcsolót használok azért, hogy minél kevesebbet kelljen várni az eredményre, de a tesztjeim alapján a -0 és az -1 között nagy, a -1 és a -9 között elhanyagolható méretbeli különbséget találtam, így elég lehet a -1 kapcsolót használni.

    Az adatbázis mentése

    Ha Docker-t használunk a VPS-ünkön, akkor könnyű dolgunk lesz, nem kell semmilyen érzékeny adatot (pl. DB jelszó) a scriptünkbe beledrótozni, mert ezek a változók elérhetőek a futó Docker containeren belül, így a következő snippettel kész is vagyunk: 

    bash
    docker exec DB_CONTAINER_NEVE sh -c 'exec mysqldump "$MYSQL_DATABASE" -u "$MYSQL_USER" -p"$MYSQL_PASSWORD"' | gzip -9 > db.sql.gz

    A fentiben természetesen a DB_CONTAINER_NEVE értéket cseréljük le arra a névre, ami az adatbázis containerünk neve. Futtatást követően egy db.sql.gz fájl jön létre, ami már be van tömörítve gzip-pel - mehetne is fel a Space-be. Viszont ha nem dinamikusan nevezzük el a fájlt, akkor felül fogja írni a korábbi mentést, és amúgy is, bonyolult ezt mindig lefuttatni... Itt az ideje, hogy automatizáljuk a folyamatot!

    Backup script

    Készítettem egy scriptet, amivel könnyedén automatizálhatod a teljes folyamatot, és "pöccre" fog menni, ha lemented (azért futási jogokat adj neki és állítsd be előtte az s3cmd-t megfelelően). 

    A HOST_BUCKET változót állítsd be a neked megfelelőre!

    Fontos:
     a 6. sorban szerepel egy $DATABASE értékadás, erre figyelj! Ha nem PROJEKTNEV_database sémát követ az elnevezése, akkor nem fog pöccre menni. Hogy nálam miért van így, elolvashatod a Docker alapú VPS tárhely éles környezetben bejegyzésemben, de röviden összefoglalva: minden projektben ugyanazt a docker-compose.yml fájlt használom, viszont nehézkes lenne mindig átírni a neveket, ezért a .env fájlban meghatározott prefixet kapnak a létrejövő containerek nevei, amivel PROJEKTNEV_web, PROJEKTNEV_database, stb. formátumú container nevek jönnek létre. 

    bash
    #!/bin/bash
    # Variables definitions
    PROJECT_NAME=$1
    REPODIR=$2
    PROJECT_PREFIX=$3
    DATABASE=$3_database
    NOW=$(date +"%Y-%m-%d_%H-%M-%S")
    BACKUP_PATH=/tmp
    FILENAME=$PROJECT_NAME-daily-$NOW
    HOST_BUCKET=
    
    # Run the program
    cd $REPODIR
    
    # Back up the files
    echo "Running backup on files..."
    zip -q -r0 $BACKUP_PATH/$FILENAME.zip $REPODIR -x "*.git*" "*vendor*" "*node_modules*" "*storage/runtime*" "*storage/logs*" "*.docker/dump/*.sql"
    echo "Backup file created at $BACKUP_PATH/$FILENAME"
    
    # Upload it to bucket
    echo "Uploading to Spaces in progress..."
    s3cmd put $BACKUP_PATH/$FILENAME.zip s3://$HOST_BUCKET/$PROJECT_NAME/files/
    echo "Delete local copy..."
    rm $BACKUP_PATH/$FILENAME.zip
    
    # Back up the database
    echo "Exporting database from Docker image..."
    docker exec $DATABASE sh -c 'exec mysqldump "$MYSQL_DATABASE" -u "$MYSQL_USER" -p"$MYSQL_PASSWORD"' | gzip -9 > $BACKUP_PATH/$FILENAME.sql.gz
    
    # Upload it to bucket
    echo "Uploading to Spaces in progress..."
    s3cmd put $BACKUP_PATH/$FILENAME.sql.gz s3://$HOST_BUCKET/$PROJECT_NAME/database/
    echo "Local copy removed..."
    rm $BACKUP_PATH/$FILENAME.sql.gz

    A futtatásához 3 paramétert vár, ezek a következők: 

    1. A projekt neve: én ezt a domainre szoktam beállítani, ebbe a nevű mappába fogja pakolni a Space-ben a feltöltött cuccainkat. 
    2. A "munkakönyvtár", vagyis ahol a weboldalhoz tartozó fájljaink vannak. Érdemes abszolút útvonallal megadni. 
    3. A projekt prefixe. A fájlnevek generálásához és az adatbázis container nevének megállapoításához használja. 

    Próbaképpen futtassuk, például így: 

    bash
    sh /home/saboteur/scripts/backup.sh unicornsdatabase.com /home/saboteur/www/unstableunicorns_craft/ unstableunicorns_craft

    Ha minden jól megy, akkor valami hasonlót fogsz kapni: 

    Do Spaces Script

    Ezt követően már látni fogod a Spaces felületén a fájljaidat - ebben az esetben a unicornsdatabase.com könyvtárban a files és a database mappákban. Az utolsó lépés a folyamatban a teljes folyamat időzítése lesz, ehhez pedig a crontab-ot fogjuk használni: a fenti parancsot fogjuk minden nap 6 és 18 órakor futtatni. 

    A crontab tartalmazza a cron által időzítetten futtatandó feladatok listáját.

    Szerkesszük a crontab-ot a crontab -e paranccsal. Ugorjunk a fájl végére (lefele nyíl), majd a fájl végére új sorba illesszük be a következőt: 

    0 6,18 * * * sh /home/saboteur/scripts/backup.sh unicornsdatabase.com /home/saboteur/www/unstableunicorns_craft/ unstableunicorns_craft

    Magyarázat a fentihez: 

    • az első oszlop a perc, 
    • a második az óra, 
    • a harmadik a hónap napja, 
    • a negyedik a hónap, 
    • az ötödik a hét napja,

    vagyis a mi parancsunk 0 perckor, 6 és 18 órakor, a hónap minden napján, minden hónapban, a hét minden napján fog lefutni. 

    Hála annak, hogy paraméterekkel ellátható a script, ha több ügyfél oldaláról, vagy egy ügyfél több oldaláról szeretnénk mentést készíteni, akkor elég csak több sort felvennünk, eltérő paraméterekkel. Látható az is, hogy nagyon jól testre lehet szabni, hogy az adott ügyfél oldaláról milyen gyakorisággal készüljön biztonsági mentés: elég csak a crontab adott sorában az időzítést átalakítanunk úgy, hogy az nekünk - és az ügyféllel kötött megállapodásunknak - megfeleljen. 

    Joggal merül fel viszont benned a kérdés: "Rendben Ottó, ez így mind szuper. Viszont folyamatosan csak töltjük fel a fájlokat, de arról nem szólt a mese, hogy korlátlanul töltjük fel őket? Vagy ha nem, akkor ki törli őket, vagy mi lesz velük?"

    S3 Lifecycle

    Az S3-ba (tehát így a Spaces-be) feltöltött tartalom úgynevezett életciklussal rendelkezik, ami a fájl feltöltésével kezdődik, és annak a törlésével ér véget. Viszont meghatározhatunk szabályokat, hogy mennyi idő elteltével törölje a rendszer a fájlokat automatikusan. Igen, ez még annál is királyabb, mint amilyennek tűnik: nem csak azt mondhatjuk meg, hogy a fájlok X nap után automatikusan törlődjenek, hanem azt is, hogy ez a szabály csak bizonyos prefixek esetén legyen érvényes. Ha pedig figyelmesen olvastál, akkor rögtön össze is áll a kép: a prefixekről ezért beszéltem korábban, és ezért határoztuk meg a prefixeket weboldalanként: így oldalanként be tudod állítani, hogy melyik oldalról milyen hosszan tárolódjanak a biztonsági mentések. Tehát elképzelhető az, hogy az a.hu-ról 15 napig, a b.hu-ról pedig 180 napig maradjanak meg a mentések, és a törlésről sem neked kell gondoskodnod. Egy példa Lifecycle szabályt látsz alább (bővebben lásd a DigitalOcean Spaces vagy az Amazon S3 dokumentációját): 

    <LifecycleConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
      <Rule>
        <ID>Expire old logs</ID>
        <Prefix>logs/</Prefix>
        <Status>Enabled</Status>
        <Expiration>
          <Days>90</Days>
        </Expiration>
      </Rule>
    
      <Rule>
        <ID>Remove uncompleted uploads</ID>
        <Status>Enabled</Status>
        <Prefix/>
        <AbortIncompleteMultipartUpload>
          <DaysAfterInitiation>1</DaysAfterInitiation>
        </AbortIncompleteMultipartUpload>
      </Rule>
    </LifecycleConfiguration>

    A fenti szabály intuitívan is jól érthető: az első szabály esetében az ID a szabály neve, a Status az állapota, az Expiration pedig a lejáratot adja meg, napokban (Days), a Prefix pedig azt határozza meg, hogy milyen prefixű fájlokra használja a rendszer azt a szabályt - vagyis hogy melyik "mappában" szereplő fájlokra vonatkozik (a példa szabály esetében ez a logs/ mappa). 

    A második szabályban vannak már ismert elemek, viszont van egy új is: AbortIncompleteMultipartUpload, vagyis a befejezetlen feltöltések törlése, 1 nappal azt követően, hogy azokat elindították (DaysAfterInitiation). Erre azért van szükség, mert a megszakított feltöltések felszaporodnának, és helyet foglalnának.

    Nézzünk egy példát arra, hogy mi milyen szabályt használhatnánk két oldal (a.hu és b.hu) biztonsági mentéseinek eltérő (15 és 180 nap) ideig történő megőrzéséhez: 

    <LifecycleConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
      <Rule>
        <ID>Backup expiration - a.hu</ID>
        <Prefix>a.hu/</Prefix>
        <Status>Enabled</Status>
        <Expiration>
          <Days>15</Days>
        </Expiration>
      </Rule>
    
      <Rule>
        <ID>Backup expiration - b.hu</ID>
        <Prefix>b.hu/</Prefix>
        <Status>Enabled</Status>
        <Expiration>
          <Days>180</Days>
        </Expiration>
      </Rule>
    
      <Rule>
        <ID>Remove uncompleted uploads</ID>
        <Status>Enabled</Status>
        <Prefix/>
        <AbortIncompleteMultipartUpload>
          <DaysAfterInitiation>1</DaysAfterInitiation>
        </AbortIncompleteMultipartUpload>
      </Rule>
    </LifecycleConfiguration>

    Nagyon fontos, hogy a következő két útvonal esetében a prefixek eltérnek: 

    s3://{{ BUCKETNAME }}/valami

    s3://{{ BUCKETNAME }}//valami

    Az első esetben a Prefix <Prefix></Prefix> lenne, míg a második esetben <Prefix>/</Prefix> - a szabályok megalkotásánál erre tehát nagyon figyelj!

    Most már csak egy kérdés maradt: hogyan juttatjuk fel a Space-be a szabályunkat? Használjuk ehhez is az s3cmd-t! 

    Hozzunk létre egy fájlt, mondjuk expiration.xml néven, a fenti tartalommal. Ezt követően pedig adjuk ki a s3cmd setlifecycle expiration.xml s3://{{ BUCKETNAME }} parancsot (ahol értelemszerűen {{ BUCKETNAME }}-et lecseréljük a Space-ünk nevére), és ha minden jól ment, a s3://{{ BUCKETNAME }}/: Lifecycle Policy updated üzenetet kapjuk. Ellenőrizzük az aktív Lifecycle szabályokat a s3cmd getlifecycle s3://{{ BUCKETNAME }} paranccsal! Ha mindent jól csináltunk, az expiration.xml fájl tartalmát fogjuk visszakapni a válaszban. 

    Fontos: majdnem 3 hónap levelezést követően (2018. november 21-én nyitottam a ticket-et) a DigitalOcean szerint a szabályok nem szabad, hogy átfedjék egymást (tehát nem lehet egy globális default + ügyfélspecifikus szabály együtt például). Ennek az oka állításuk szerint az, hogy az általuk használt Ceph szigorúbban ellenőrzi a szabályokat és az ilyet átfedő szabályokat nem fogadja el. Tehát ha esetleg 400-as hibát kapnál vissza, az emiatt is lehet. 

    Visszaállítás

    Mit teszel akkor, ha úgy alakul, hogy használnod kell a biztonsági mentéseidet? Nagyon egyszerű a válasz: a Spaces webes felületén csak simán letöltöd a vonatkozó fájlokat az ügyfeled könyvtárából, és... ennyi, hiszen innentől ugyanúgy folytatod, mint bármilyen más esetben: a fájlokat feljuttatod az új tárhelyre a kedvenc deployment eljárásoddal (legyen az FTP (ne) vagy valamilyen CI/CD megoldás (inkább)), az adatbázist pedig szintén importálod az új tárhelyre/VPS-re. 

    Quick Share

    Egy nagyon hasznos funkciója még a Spaces-nek a Quick Share: ekkor egy fájlt előre meghatározott ideig elérhetővé teszel. Ilyenkor a link birtokában a fájlt bárki letöltheti, de csak a megadott ideig: az idő elteltével a fájlhoz való hozzáférés lezárul. Hasznos lehet abban az esetben, ha egy fejlesztőnek szeretnéd elérhetővé tenni az oldal egy bizonyos időpontbani állapotát, hogy mondjuk hibát keressen rajta, vagy valamilyen funkciót fejlesszen az oldalhoz. 

    Do Quick Share

    Összefoglalás

    A fentiek során tehát elkészítettünk egy megbízható és jól testreszabható biztonsági mentési megoldást: 

    • a mentések nem a VPS-en tárolódnak, hanem egy attól független helyen
    • a mentések könnyedén visszaállíthatók, az azokhoz való (akár időzített) hozzáférés egyszerűen, webes felületen keresztül megoldható, 
    • a mentések gyakorisága és megőrzési ideje ügyfelenként eltérően szabályozható
    • teljesen automatizált: a beállítást követően teljesen autopilótán mozog a rendszer

    Remélem, hogy hasznos volt ez a bejegyzésem - ha hibát találtál, kérdésed merült fel, esetleg szeretnél magadnak is egy ilyet, de nem szeretnél vacakolni vele és inkább csináljam meg én, ne habozz felvenni velem a kapcsolatot! :)