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.jpgthumb.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.

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
latesttag-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
Mariano D'Agostino