Drupal az Azure-on - Docker image készítés és egyebek

Tanulságok négy weboldal Azure-ra történő migrálása után.

Amikor PaaS-ról IaaS-ra váltasz, hirtelen egy sor új felelősség hárul rád. Sok mindent kell megtanulni, és sokat utánozni a meglévő PaaS szolgáltatóktól. Szeretnénk megosztani tapasztalatainkat négy nagyobb Drupal oldal Pantheonról Azure felhőbe történő sikeres migrálása után. Az áttekintés kronológiai sorrendben követi a megvalósítás lépéseit.

Infrastruktúra alapok

Egy kiváló infrastruktúra csapattal dolgoztunk együtt, akik a következő architektúrát biztosították számunkra:

  • App Services: A Drupal/PHP alkalmazások futtatásához.
  • Managed MySQL szerverek, minden környezethez egy-egy.
  • Azure Blob Storage: A publikus és privát Drupal fájlok tárolásához, NFS-en keresztül csatolva az App Service konténerhez.
  • Azure FrontDoor: CDN-ként és Web Application Firewall-ként (WAF) a jobb teljesítmény és biztonság érdekében (az összes projekt és környezet között megosztva a költséghatékonyság miatt).
  • Azure Container Registry (ACR): A Docker image-ek kezeléséhez. Egy tag ACR-be történő push-olása deployment-et indít az App Service-ben.

A megfelelő Docker image elkészítése

Miután kiválasztottál egy alap image-et (például Alpine vagy Ubuntu alapú, vagy egy már konfigurált web stack-et igényeid szerint), valószínűleg módosításokat kell végrehajtanod rajta. A legfontosabb tanulság, amit levontunk, hogy próbáld az image-eket a lehető legkisebbnek tartani, de ez önmagában nem elég.

  • Ne szinkronizálj felesleges részeket az image-be (Git repo metaadatok, téma fordítási fájlok, README-k, CHANGELOG-ok stb.). Fontold meg a forráskód image-en kívül tartását, ha ez megvalósítható az infrastruktúra beállításaidban.
  • Szabadulj meg az összes gyorsítótárazott telepítési fájltól (apt, npm, apk cache-ek). Például: apk add --no-cache [csomagnév].
  • Úgy építsd fel a rétegeket, hogy hatékonyan gyorsítótárazhatók legyenek.
  • Bármilyen szoftvercsomag telepítésekor mindig rögzítsd a verzió számokat pontos értékre. Nem szeretnél meglepetéseket közvetlenül a deployment előtt.

Biztonság alulról felfelé

Ha egyedi Docker image-ed van, aggódnod kell az SSL gyengeségek, a PHP nulladik napi sérülékenységei, az Nginx buffer túlcsordulások és az ImageMagick szegmentációs hibái miatt.

Naiv megközelítés lenne a Docker image karbantartójára hagyatkozni a frissítéseket illetően, és hagyni, hogy az alap image foglalkozzon a frissítésekkel. Ez általában nem így működik. Tipikusan az alap OS kiadások tükröződnek a származtatott image-ben. Az összes többi bejövő frissítésnél a csomagkezelőre hagyatkozol:

# Biztonsági és egyéb frissítések alkalmazása.
RUN apk -U upgrade

De honnan tudod, mikor itt az ideje újraépíteni a Docker image-et? Dönthetsz úgy, hogy naponta, hetente, vagy igény szerint push-olsz új image-eket, amikor sérülékenység van. Az érintett csomagok száma miatt ez automatizálást igényel. A Trivy segítségével sérülékenységekre és egyéb hibás konfigurációkra vizsgálhatod a Docker image-et, ami olyan jelzést adhat, ami illik a projektmenedzsment módszeredhez. Így biztosak lehettünk benne, hogy nem maradunk le kritikus biztonsági frissítésekről.

Ez csak egy része a stack biztonságának. Magasabb szinten a Drupal és modul frissítéseknek meg kell történniük; még magasabb szinten ott a WAF. És végül a host rendszernél az Azure-ra hagyatkozhatunk a biztonság fenntartásában.

Eszközök

Távoli Drush és SSH

Egyszeri bejelentkezési link lekérése, Drupal watchdog logok megjelenítése, státusz jelentések ellenőrzése - mindez kényelmesen elvégezhető parancssorból, ha rendelkezel a megfelelő eszközökkel. A Pantheonon élvezzük a terminus használatát, ami távoli Drush végrehajtást és még sok mást biztosít. Bizonyos mértékig törekedtünk a funkcionalitás replikálására. Az Azure egy általános eszközt biztosít alapként, az Azure CLI-t. Erre építve a távoli Drush végrehajtás nem olyan nehéz replikálni.

az webapp create-remote-connection

Ez egy szabványos SSH tunnelt biztosít, amit parancs végrehajtásra használhatsz. Egyedi DDEV paranccsá alakítottuk.

#!/bin/bash

# Azure App Service, SSH kapcsolat részletei,
# RESOURCE_GROUP és WEBAPP_NAME DDEV környezeti változókból jön.

SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$SCRIPT_DIR/az-env-select"

# Függvény debug üzenetek kiírásához
debug() {
    if [[ $DEBUG == 1 ]]; then
        echo "DEBUG: $1"
    fi
}

# Függvény SSH tunnel létrehozásához
establish_tunnel() {
    debug "SSH tunnel létrehozása..."
    az webapp create-remote-connection --resource-group $RESOURCE_GROUP -n $WEBAPP_NAME --port $SSH_PORT &
    TUNNEL_PID=$!
    wait_for_port
    ssh-keygen -f "/home/.ssh-agent/known_hosts" -R "[localhost]:34222"
}

wait_for_port() {
    debug "Várakozás a $SSH_PORT port megnyílására..."
    while ! nc -z localhost $SSH_PORT; do
        sleep 1
    done
    debug "A $SSH_PORT port nyitva."
}

close_tunnel() {
    debug "SSH tunnel bezárása..."
    kill $TUNNEL_PID
}

establish_tunnel

debug "Drush parancs végrehajtása..."
echo "cd /app && ./vendor/bin/drush --uri="'$PUBLIC_URL'" $@" | sshpass -p "$SSH_PASSWORD" ssh $SSH_USER@localhost -p $SSH_PORT

close_tunnel

Logok

Alapértelmezés szerint, ha nem használsz speciális logger szolgáltatást, minden log (access logok, PHP error logok stb.) a konténer standard output-jára és standard error-jára kerül. Ízlés kérdése, hogy a logok kiértékelése gyorsabb-e az Azure portálról vagy a parancssori felületről, de mi létrehoztunk erre is egy DDEV parancsot, ahol a lényeg szintén az Azure CLI-ből jön:

az webapp log download --resource-group="$RESOURCE_GROUP" --name="$WEBAPP_NAME" --log-file=/tmp/webapp.zip

Ha így van, bármilyen log elemzőbe betáplálhatod, vagy csak grep-pel gyorsan megkereshetsz valamit.

Fájl szinkronizáció

Bármely webalkalmazásnak legalább három példánnyal kell rendelkeznie egyszerre: development, testing és live, hogy minden érintett fél túl sok súrlódás nélkül dolgozhasson.

Konténerizált környezetben a Drupal publikus (és privát) fájlrendszerének egy külső menedzselt helyen kell lennie, mint az Azure Blob Storage. A három környezetnél három független kötettel, bucket-tel és megosztással végzed.

Amikor a tartalomszerkesztők végzik a munkájukat, új fájlok gyűlnek fel gyorsan, és képesnek kell lenned a környezetek szinkronizálására, adatbázis és fájlok együtt. Ez hosszú folyamat lehet, ha gigabájtnyi adattal végzed, de itt az Azure biztosítja az azcopy-t, ami igazán hatékony, mivel többszálú és mindenféle mikrooptimalizálást támogat különböző forgatókönyvekhez.

Ahogy sejtheted, egyedi DDEV parancsaink vannak erre is. A kulcs az, hogy az eszközt elérhetővé tegyük a DDEV-en belül:

#!/bin/bash

if [[ ! -f ~/.local/bin/azcopy ]]; then
  wget https://aka.ms/downloadazcopy-v10-linux
  tar -xvf downloadazcopy-v10-linux
  rm downloadazcopy-v10-linux
  if [[ ! -d ~/.local/bin/ ]]; then
    mkdir -p ~/.local/bin/
  fi
  mv ./azcopy_linux_amd64_*/azcopy ~/.local/bin/
  chmod +x ~/.local/bin/azcopy
  rm -rf ./azcopy_linux_amd64_*
fi

A többi a pontos infrastruktúrától függ.

Egyik a fájlok szinkronizálására a helyi DDEV példányhoz, egy másik ami adatokat másol környezetek között. Ebben az esetben maga az adat nem megy át a helyi számítógépeden, csak a metaadat, még ha a megosztások különböző előfizetési csoportokban vannak is.

Egy Azure-specifikus anomália, amit tapasztaltunk, hogy a megosztott mappák/kötetek fájljai kis-nagybetű érzéketlenek. Ez adatvesztést okozhat. Képzeld el, hogy ilyen fájlneveid vannak:

  • thumb.jpg
  • thumb.JPG

Akkor az egyik képed elveszne az ilyen fájlrendszerre másolás után. Gondolkodj előre és kerüld el a fáradságos manuális helyreállítást. Vagy ha tudod, válassz kis-nagybetű érzékeny tárolót a konténer mellé, ha ez nem lehetséges, használhatsz egy szkriptet az ütközések észlelésére és átnevezésére a migráció előtt mind a fájlrendszerben, mind az adatbázis managed files táblájában.

CDN és WAF

A négy oldalból az egyik gyakori támadásokat kapott, ami lelassította az oldalt és leállásokat is okozott. Kifejezetten kértünk webalkalmazás tűzfalat ebben az esetben, és az infrastruktúra csapat a FrontDoor-t biztosította CDN-ként WAF-fal kombinálva.

Anonim látogatók számára a FrontDoor teljesen gyorsítótárazott oldalakat szolgálhat ki. Mindössze annyit kell tennünk, hogy szabályokat konfigurálunk a gyorsítótárazás elkerülésére, amikor az nem kívánatos (pl. bejelentkezett látogatóknál).

A megfelelő szabálykészlettel megkérhetjük a FrontDoor-t, hogy kapcsolja ki a gyorsítótárazást, amikor a Cookie fejléc megfelel a (NO_CACHE|S+ESS[a-z0-9]+|PHPSESSID|SimpleSAML[A-Za-z]+)= regexnek. Könnyű volt követni a Pantheon dokumentációt a megfelelő megoldáshoz. Az alkalmazásodnak más gyorsítótár szabályokra is szüksége lehet. Tipikusan csak annyit kell tenned, hogy kiadod a megfelelő HTTP fejlécet, és a FrontDoor tiszteletben tartja azt.

Gyorsítótárazási szabály a FrontDoor-hoz
Gyorsítótárazási szabály a FrontDoor-hoz

Biztonsági okokból nem tudjuk nyilvánosságra hozni a tűzfal szabálykészletet. Hetek finomhangolása után a tartalomszerzőknek egyáltalán nem volt problémájuk tartalmat felvinni az oldalra. Ami nyilvánosan elmondható: Ne felejtsd el korlátozni a forgalom sebességét. Ha valaki túl sok kérést küld túl gyorsan, az nem valami, amit szeretnél. Megfontoltan kalibrálhatsz egy olyan sebességet, ami nem blokkolja a tartalomszerkesztést, de blokkolja a rosszindulatú szereplőket.

Az Azure nemrég bevezette a Javascript kihívásokat a WAF szabályokhoz. Hatékonyan használható sebességkorlátozással együtt. Meghatározhatsz alacsonyabb sebességkorlátot, mivel az Azure kegyesen megpróbálhatja megállapítani, hogy a látogató rosszindulatú-e vagy sem.

Hasznos Frontdoor log lekérdezések

A WAF nagyon hatékony lehet a webalkalmazásod védelmében, de még ha gondosan finomhangolsz is, jó eséllyel a tartalomszerzők találnak módot arra, hogy legitim tartalommal blokkolva legyenek.

Van lehetőség a FrontDoor logok lekérdezésére egy SQL-szerű lekérdezési nyelvvel, amit Kusto-nak hívnak.

Ha az oldaladat támadják, használhatod:

AzureDiagnostics
| where Category == "FrontDoorWebApplicationFirewallLog"
| where action_s == "Block"
| project TimeGenerated, requestUri_s, clientIp_s, clientIP_s
| summarize count() by clientIP_s

Hogy lásd azokat az IP-ket, amelyek már a leggyakrabban megsértették a szabályokat (miután ellenőrizted, hogy ezek nem legitim keresőmotorok például), ezeket az IP-ket általában teljesen blokkolni lehet egyedi szabállyal. Néhány esetben ez nem elég erős, és lehet, hogy ideiglenesen be kell állítanod a FrontDoor-t, hogy minden kérést naplózzon, ne csak a blokkoltakat, valamint el kell hagynod az action_s feltételt a lekérdezésből.

Ha egy támadó elég okos ahhoz, hogy elkerülje a szabálysértést, nem tudod elkapni őket a fenti lekérdezéssel, ahogy van. Nagyon nagy oldalakhoz gyakori támadásokkal ez akár automatizálható is egy Logic App-pal.

Ha a tartalomszerzők panaszkodnak, hogy valami blokkolva van, de nem kellene:

AzureDiagnostics
| where Category == "FrontDoorWebApplicationFirewallLog"
| where action_s == "Block"
| where not (details_msg_s contains "bot")
| where not(ruleName_s contains "BotManager")
| where (requestUri_s contains "/edit" or requestUri_s  contains "/node" or requestUri_s  contains "/admin/")
| where not(requestUri_s contains "node_modules")
| project TimeGenerated, host_s, clientIP_s, requestUri_s, details_msg_s, ruleName_s, action_s, policy_s, details_matches_s

Ez némi finomítással hasznos lehet a problémás lekérdezés megtalálásához. Természetesen, ha nem állítasz be egyedi hibaüzenetet a WAF blokk forgatókönyvhöz, használhatod a speciális ID-t, a tranzakció ID-t a problémás kérések megtalálásához.

Ezzel a lekérdezési nyelv rugalmasságával könnyen adaptálhatod a szabályokat a tényleges forgalmadhoz.

Megtanultuk, hogy a JS kihívás használata a forgalom blokkolása helyett hasznos. Ha valami csak gyanús, de nem nyilvánvalóan rosszindulatú, alkalmazhatod ezt. Egy konkrét példa az, hogy feltételesen megállítsd a forgalmat ilyen módon azokból az országokból, amelyek nem a weboldal célközönsége.

Kiadások és deploy stratégia

Mivel más projektekben Travis-t használunk a deployment-ek vezénylésére, itt is ezt az utat követtük. Jól működött. Biztonsági okokból az egyetlen dolog, amit a Travis csinál, az a push az ACR-be.

Miután az új konténer betöltődik az App Service-be, egy sor Drush parancsot hajt végre az adatbázis frissítéséhez, konfigurációs változtatások alkalmazásához és így tovább. Ezt már kipróbáltuk, és itt is működött. Azonban az első néhány deployment során megtanultuk, hogy a folyamat törekenyebb, amikor Docker image-et push-olsz. Néhány tipp:

  • Fagyaszd be az alap Docker image verziót hash használatával.
  • Mindig ugyanazt a Docker image-et deploy-old production-be, mint amit nem-production-on teszteltél először. Csak másold magát az image-et, ne építsd újra. Az újraépítés törékeny folyamat, mivel különböző hálózati helyekről kell különböző asset-eket másolni tipikusan. A build meghiúsulhat, vagy ami rosszabb, véletlenül hibás Docker image generálódhat.
  • Biztosíts lehetőséget a gyors visszaállításra. Tegyük fel, hogy a latest tag-et kell push-olnod a deployment kiváltásához. Push-olj egy másik tag-et is, mondjuk Unix időbélyeggel, így amikor a következő deployment-et végzed és valami rosszul sül el bármilyen okból, gyorsan vissza tudsz menni az időben. A legjobb, ha automatizálod ezt a folyamatot.

Mentések

Bármely infrastruktúra csapat a mentéseket valami olyasminek fogja tekinteni, amin dolgozni kell, de egy Drupal fejlesztő szemszögéből az igények eltérhetnek. Az Azure, AWS és más felhőszolgáltatók tökéletes pillanatkép-alapú mentéseket kínálnak a menedzselt MySQL példányhoz, amelyek könnyen visszaállíthatók a felhőn belül, de nehéz vagy bonyolult a helyi példányodba másolni (mindig beállíthatsz egy ideiglenes MySQL példányt, és végső megoldásként mysqldump-ot csinálhatsz azzal).

Azonban a pillanatkép mellett szükséged van szöveges SQL mentésekre, amelyeket a ddev import-db feldolgozhat. Az Azure-on belül vagy használhatsz egy VM-et oldalról, ami vezényli a mentést (mysqldump meghívása és blob storage-ban tárolása), vagy a Logic App is megteheti ismétlődő módon.

Van erre egy modul, a Backup and Migrate, de nem skálázódik, így csak kis oldalakhoz hasznos. Egyébként a PHP különböző korlátai megakadályoznak a mentés befejezésében.

Tanulságok

Mivel ezek összetett projektek voltak, helyes választás volt egyedi Docker image-eket használni. De ahogy korábban írtuk, egy magasabb szintű menedzselt stack alkalmasabb lehet a Drupal alkalmazásodhoz. Kevesebb felelősség költséget takaríthat meg, de ez természetesen kevesebb szabadsággal jár. A Drupal alatti szinte összes réteg birtoklása kihívás, ezért győződj meg róla, hogy van rá kapacitásod.

Áron Novák

Áron Novák

Mariano D'Agostino

Mariano D'Agostino

Drupal-planet Devops Azure App Service Docker