Snapshot-serveri 503 virheelle ja Varnish

You are currently viewing Snapshot-serveri 503 virheelle ja Varnish

Varnish tarjoaa sivustoiltani kävijöille snapshot-version, jos Apachen kaatuessa tulee 503 virhe tai 500 kun WordPressin plugin ei toimi. Samaa perhettä olisi 504 backendin ja tietokannan hidasteluille sekä timeouteille, mutta tuota en ole vielä ottanut käyttöön. En ole moista virhettä eläessäni nähnyt

Tuon lisäksi saan ilmoituksen foorumille https://foorumi.katiska.eu 50x-virheitä vahtivan scriptin toimesta. Koska minulla on push-ilmoitusten myötä foorumi aktiivisemmassa seurannassa, niin saan heti tiedon ongelmista.

Snapshot-serveri on staattinen kopio WordPress-sisällöstäni. Se tehdään yksinkertaisuudessaan siten, että olen wgetin avulla koostanut kopiot muutoin dynaamisista WordPress-sivuista. Ei paras vaihtoehto, mutta ehdottomasti edullisin. Sen rinnalla on Varnishin cachessa mahdolliset välimuistitetut sivuosumat. Kun Apache ei tee töitään, niin Varnish esittää pyydetyn sivun joko välimuististaan tai jos sitä ei löydy, niin kääntää pyynnön Nginxille. Se sitten esittää staattisen version.

Virhetilanteessa — kunhan se koskee Apachea — näytetään siis sisältö virheen sijaan. Parhaimmassa tapauksessa niin, että käyttäjällä ei ole mitään tietoa siitä, että puolet servereistä on rikki. Huonoimmassa tapauksessa, eli kun käytetään cachen sijaan snapshottia, sisältö voi olla hieman ontuvaa ja jotain kuvia sekä muotoiluja saattaa puuttua, mutta se on kuitenkin sisältöä.

Ketju on siis Nginx – Varnish -Apache2 – WordPress. Rinnalla päivystää error_reporter.py, joka tekee foorumialoituksen (sekä vahtii Varnishin hyvinvointia, mutta se on oman aiheensa väärtti).

Raportoinnin ongelma

Se, että tarjoan kävijälle joko aidon sisällön Varnishin cachesta tai eräänlaisen lite-sisällön snapshot-backendin kautta, on UX-valinta. On totaalisen turhaa tarjota kävijälle minkäänlaista virhekoodia, jos asiat saadaan hoidettua niin, että näytetäänkin pyydetty sisältö. Tai edes jotain, joka olisi vähintään sinnepäin.

Siksi nykyinen setup esittää backendin kaaduttua ensisijaisesti cachetetun sisällön ja toissijaisesti snapshotin. Vasta sitten kun mitään ei ole tarjolla, niin näytetään virhe.

Joten käyttäjän näkökulmasta, ja aika paljon myös järjestelmän mielestä, mitään virhettä ei ole. Käyttäjä pyysi jotain, ja se esitettiin. Se, että takana on Apachen kaatumisen takia error 503 on Varnishin sisäinen asia, eikä se näy muille — paitsi jos mitään sisältöä ei voida palauttaa, mutta nyt voidaan.

Minulle adminina tilanne on kuitenkin se, että Apache2 on naamallaan, ja jotta voisin korjata sen, niin siitä pitäisi saada tieto. Ja ollaan takaisin siinä alkupisteessä, josta koko projekti ja prosessi alkoi: parannetaan käyttäjäkokemusta ja kerrotaan ylläpitäjälle virheestä.

Koska olen köyhä, ja teen itse itselleni, niin kaupalliset monitoroinnit eivät ole vaihtoehto.

Aikoinaan esitin custom-virhesivun ja lähetytin ilmoituksen API:lla foorumille. Tuon ratkaisuni heikkous — siis sen lisäksi, että käyttäjälle kerrottiin vain virhe — oli määrätty viive. Työn tehnyt scripti error_reporter.py ja sitä ohjaava service reagoivat vasta siinä vaiheessa, kun kävijä sai aidosti errorin ja se näkyy systeemissä. Tuolloinkin Varnishin cachesta tuleva sisältö hidasti reagointia.

Kun kävijä pyysi sivua, jota ei ollut cachessa, niin vasta silloin virhereitti aktivoitui. Scripti muutti kävijän näkemää virhetekstiä ja aloitti Discourse-foorumilla aiheen. Ei siinä vaiheessa kun Apache2 aidosti kaatui. Sisältöä ei cachen ulkopuolelta tarjoiltu, vaan vakiota hieman mukavampi error-sivu.

Pystyin elämään tuon viiveen kanssa. Toki minulla on sivustoja, joissa ei oikeastaan kukaan käy koskaan, joten ne eivät liipaisisi virhetilannetta käyttöön. Mutta minulla on katiska.eu, jossa on koko ajan liikennettä, ihmisiä ja/tai botteja. Joten Apachen romahtamisesta tuli aina nopeasti tieto. Mutta minulle oli asennaongelma sisällön häviämisen kanssa.

Nyt tilanne on täysin toinen Apachen suhteen. Sisältö esitetään eikä virhe tule näkyviin; joko käytetään cachea tai snapshotia. Tosin seurauksena oli, että minäkään en saanut virheilmoitusta — ja ilmoitus oli ylläpitäjän roolille oleellinen asia.

Joten tein sen mitä kykenin tuon kiertämiseen. Ei ehkä paras tai tyylikkäin ratkaisu, mutta palvelee minun tarpeitani. Kierrätän seurannan syslogin kautta.

Virhestatusten suo

Kun päätin, että virheen sijaan voisin jopa esittää sisältöäkin, niin olin hassussa tilanteessa. 50x-sarjan virheitä monitoroiva error-reporter.py ei enää esittänyt virhettä käyttäjille, koska heille tarjottiin vaihtoehtoinen sisältö. Samalla virhettä ei enää myöskään esitetty minulle, ylläpitäjälle. Aivan kuin mitään virhettä ei olisi ollutkaan. Virhe oli siellä tietenkin, mutta ratkaisussani se oli piilotettu.

Virhestatuksen 50x sijaan käyttäjälle, oli sitten botti tai ihminen, kerrotaan vastauksena 302 Found, eli väliaikainen uudelleenohjaus.

302 ei tule luonnollista, orgaanista, tietä web-serverien tekemänä tässä rakennelmassa. Se on vain kikka, jonka tekee Varnish. Tavallinen käyttäjähän ei http statuksia koskaan näe, paitsi tietysti virheilmoituksissa. Jos on kiinnostunut, niin esimerkiksi curl -IL <url> esittää ne. Sen sijaan botit näkevät http statuksen ja päättävät aika ajoin niiden perusteella jatkotoimistaan. Siksi noiden kanssa kikkaillessa pitää hieman miettiä mitä tekee ja miksi. Tässä asiayhteydessä huolestuttaa ensisijaisesti googlebot.

Kävijä näkee sivustoillani virhetilanteen aikana joko cachetettua tai väliaikaista ja staattista snapshot-sisältöä. Kun sisältö tulee cachesta, niin sitä käsitellään kuin aitoa — jota se tietenkin käytännössä on. Mutta kun hypätään staattiseen snapshot-sisältöön, niin sivu ei saa missään nimessä saada silloin tiloja 200 OK, 404 Not Found tai 410 Gone. Jokainen noista voi muuttaa hakukoneindeksointia, tai poistaa osuman. 50x errorin voisi esittää, mutta silloin Google lähettää hetken kuluttua sähköpostia ja vinkuu indeksointivirheistä, enkä vaan jaksa niitä — ne ovat täysin turhaa postia.

Kun Nginx tarjoilee staattisen sisällön Varnishin ohjaamana, niin se on 200 OK. Määritelmällisesti ihan oikein, koska meillä on tarjottavana sitä mitä on pyydetty. Mutta silti ei ole. Se on vajaata ja erilaista sisältöä, joka näytetään virhetilanteen takia lähteestä, jota ei muutoin käytetä. En minä halua sanoa hakukoneille, että tämä on oikea tulos ja indeksoi se. Joten http status on muutettava Varnishissa.

Tällaisessa tilanteessa sitä ei kannata muuttaa osiossa vcl_deliver, kuten normaalisti ehkä tehtäisiin muutettavien headereiden kanssa. Tuossa osiossa käsitellään valmista sisältöä, joka on seuraavaksi lähdössä vastauksena takaisin web-serverille ja sieltä käyttäjälle. Varnish ei anna silloin enää muuttaa http tilaa. Joten muutos tehdään, tässä tapauksessa, blokissa vcl_backend_response. Backend, jopa väliaikainen snapshot, lähetti vastauksena 200, ja tuo vastaus vaihdetaan 302:ksi siellä missä vastauksia käsitellään — backend responsessa.

302 Found ja 307 Temporary Redirect ovat käytännössä täysin samoja, ja tietääkseni botit suhtautuvat niihin samoin. Omalla tavallaan 307 voisi olla loogisempi kuin 302. Ero on enemmänkin semanttinen. 302 vaihtaa tai voi vaihtaa pyynnön metodin, joten jos alkuperäinen oli POST niin uudelleenohjauksen jälkeen se voi olla GET. 307 sen sijaan käyttää uudelleenohjauksessa samaa metodia kuin pyynnössä.

Koska minulla ohjaus tulee backendin kaatuessa dynaamisesta sisällöstä staattiseen sisältöön, ja snapshot pystyy käyttämään vain GET , niin eikö minun pidäkin käyttää statusta 302? Kyllä, semanttisesti ajatellen pitää, koska riippumatta metodista ainoa käytettävä on GET. Jos joku on yrittänyt lähettää vaikka yhteydenottolomakkeen ja käyttänyt POST, niin 307 säilyttäisi sen. Mutta koska snapshot ei staattisena moista pysty käsittelemään, niin lopputuloksena olisi esimerkiksi 500-sarjan virhe.

Nyt käytetään 302 ja pyyntö muuttuukin GET ja sisältö saadaan — mutta lomaketta ei lähetetä. Kumpi sitten on haluttu tilanne, se että käyttäjä saa virheen sisällön sijaan ja siten tiedon, että lomaketta ei käsitelty, vai väärän lopputuloksen, mutta jonkun sivun, on hieman makuasia. Minä mieluummin tarjoan sisältöä, koska kokemus kertoo, että virhekoodi aiheuttaa kävijässä paniikkireaktion ja paon. Siitä ei seuraa loogista pohdiskelua että ”kappas, lomake on rikki, mutta sivusto toimii jotenkin, joten täällä on jokin häiriötilanne, joten koitan myöhemmin uudestaan”.

Botit sen sijaan tarvitsevat muutakin huolenpitoa. Niille täytyy kertoa, että snapshot-sisältöä ei ole syytä indeksoida. Se tehdän vaihtamalla 200 koodiksi 302 (tai 307).

Varnish ja ohjauksen logiikka

Kun kävijä antaa verkkosivun urlin, osoitteen, niin domainin avulla löydetään oikealla serverille. Siellä vastassa Nginx. Se tekee hieman omia töitään, esimerkiksi varmista https- yhteyden varmistavan SSL-sertfikaatin toimivuuden. Mutta se toimii reverse proxyna Varnishin suuntaan ja lähettää pyydetyn osoitteen sille.

Varnish tarkistaa löytyykö sisältö cachesta valmiiksi välimuistitettuna. Jos se on siellä, niin Varnish lähettää sen takaisin Nginxille, joka antaa sen kävijälle. Koko tuon ajan niin Apache2, WordPress, tietokanta kuin PHP ovat vain istuneet paikallaan pyörittämässä peukaloitaan. Niitä ei tarvita sillä hetkellä mihinkään. Ja tähän kiteytyy se vahvin syy miksi mm. Varnishia käytetään.

Mutta Apachelle on tullut virhetilanne, ja se on kaatunut.

Pyyntö tulee tuttua reittiä Varnishille, mutta koska mitään ei löydy cachesta, niin Varnish lähettää pyynnön backendille — eli Apachelle ja siitä eteenpäin WordPressille. Koska ollaan virheessä, eikä Varnish saa vastausta, niin tieto kaatumisesta siirtyy Varnishin virheenkäsittelylle.

Normaalisti tehtäisiin error 503 ilmoitus ja tuupattaisiin se Nginxin kautta käyttäjälle. Mutta nyt käytössä onkin varasuunnitelma. Varnish vaihtaakin backendiksi Ningxillä asustavan snapshot-version ja pyöräyttää pyynnön takaisin alkuun. Sivua pyydetään uudestaan, ja koska se ei vieläkään ole cachessa, niin se lähetetään uudestaan backendille — joka ei tällä kertaa olekaan Apache, vaan Nginx.

Sisältö saadaan, tuodaan Varnishille, joka laittaa sen ensin cacheen, ja sitten palauttaa toiseen osaan Nginxiä, ja siitä käyttäjälle.

Miksi ei anneta aitoa sisältöä?

Snapshotin ongelma on, että se on valokuvamainen kopio aidosta sivusta. Siitä puuttuu toiminnallisuuksia, joita aidossa WordPress-sisällössä on. Kun WordPress tekee sivun, niin iso liuta PHP-tiedostoja rakentaa sen kuin palapelin, hakemalla jokaisen palan sisällön tietokannasta. Tuo tehdään joka kerta uudestaan, vaikka peräkkäiset kysyjät pyytäisivät samaa sisältöä. Siksi WordPress on raskas ja hidas.

Mutta WordPress on oma ohjelmistonsa, web-appinsa. Apache2 tai mitä webserveriä käytetäänkään, on vain ohjaamassa liikennettä oikeassa järjestyksessä WordPressille, ja samalla ottamassa vastaan lopputuotteen, sen valmiin sivun.

Koska on ihan sama mitä web-serveriä käytetään, niin miksi palautan snapshot-sisältöä, enkä vain anna käyttää WordPressiä normaalisti, mutta Nginxin kautta? Sisältö pysyisi koko ajan samana, eikä käyttäjä huomaisi mitään muuta kuin ehkä pienen hitauden.

Voisinkin tehdä noin, ja ehkä jossain vaiheessa teenkin. Mutta tämä oli kompromissi, joka kattoi useamman vikatilanteen.

Jos Nginx hyppäisi Apachen tilalle, niin silloin varauduttaisiin vain Apachen kaatumiseen. Mutta entä jos syy ei olekaan Apachessa, vaan hiljaisuus johtuu siitä, että WordPress, PHP-FPM tai MariaDB ovat kaatuneet? Silloin sivut eivät toimisi Nginxilläkään.

Toki voisin huomioida noistakin tulevat virheet, ja ehkä niin joskus teenkin. Mutta se vaatii lisää logiikkaa, ja se kasvattaa riskiä muihin ongelmiin.

Varnishin lisäykset

Varnish on vähään tyytyväinen.

Lisää default.vcl alkuun uuden backendin määritykset.

Minulla on näin:

backend snapshot {
        .host = "127.0.0.1";
        .port = "8383";
}
  • portti on vapaavalintainen, mutta sitä ei saa käyttää jokin muu palvelu

Sitten tarvitaan ehdot, jonka perusteella Varnish palauttaa pyynnön uuden backendin käyttöön. Se tehdään osiossa vcl_backend_error

Tuo osio ei ole mukana mallina olevassa default.vcl tiedostossa, eikä kovinkaan monessa esimerkissäkään. Siellä on säännöt, joita tarvitaan virheiden käsittelyyn. Yleensä Varnishin sisäiset säännöt riittävät, mutta eivät tällä kertaa. Tuo sijoitetaan usein vcl_backend_response jälkeen.

Minulla se on tällainen:

 sub vcl_backend_error {

        if (bereq.retries == 0) {
             std.syslog(180, "ALERT: Apache is not responding on first attempt: " + bereq.url);
        }

   # Jos ei vielä ole retry tehty, tee se
    if (bereq.retries == 0) {
        std.syslog(180, "ALERT: Apache is not responding on first attempt: " + bereq.url);
        std.log(">> Backend error: first retry");
        return (retry);
    }

    # Jos ensimmäinen retry tehty, kokeillaan snapshot-backendia
    if (bereq.retries == 1 &&
        (beresp.status == 500 || beresp.status == 503 || beresp.status == 504)) {
        std.log(">> Backend error: switching to snapshot backend");
        set bereq.backend = snapshot;
        return (retry);
    }

    # Mikään ei toiminut, anna virhe asiakkaalle
    std.log(">> Backend error: all retries failed");
    return (fail);

# We are ready here
}

Kun tullaan virheen takia, tässä koska Apachelta ei saatu vastausta, niin ensimmäisellä käynnillä kirjoitetaan kaksi logimerkintää

  • std.syslog kirjoittaa syslogiin. Käyttämäni monitorointi käynnistyy tuosta merkinnästä
  • std.log kirjoittaa varnishlogiin ja varnishncsa:han. Joten noista merkinnöistä, kuten ylipäätään Varnishin logeista, on harvoin jos koskaan mitään hyötyä yritettäessä selvittää mennyttä asiaa. Mennyt on mahdollista saada, jos aikaa on kulunut hyvin vähän. Varnislog asuu RAM:ssa ja on kiertävä logi, eli sitä kirjoitetaan koko ajan uudestaan. Siksi sitä käytetään yleensä shellin toisessa ikkunassa ja toisessa annetaan esim. curl. Sitten katsotaan mitä sillä hetkellä logiin ilmestyi. Joten yleensä ryhmittelen samankaltaiset asiat jollain samanlaisella aloituksella, kuten Backend error: ja etsin sillä.

Pyyntö lähetetään varmuuden vuoksi uudelle kierrokselle. Ehkä ongelma olikin vain joku lyhyt kömmähdys. Jos se tulee toisen kerran ja virhe on 500, 503 tai 504, niin otetaan snapshot-backend käyttöön, ja laitetaan pyyntö menemään vielä uudelle kierrokselle noutamaan sisältöä toisesta paikasta.

Jos virhe oli alunperin joku muu, kuten 502 (joka ei ole käytännössä mahdollinen) tai pyyntö palautuu edelleenkin virheenä, niin luovutetaan ja näytetään perinteinen virheilmo.

Tuokaan ei ihan riitä. 503 on Varnishille järjestelmätason virhe. Silloin se hyppää samantien osioon vcl_response_error ja tässä tapauksessa snapshot-reitti käynnistyy. Mutta jos WordPressin lisäosa on rikki tai admin sekoilee wp-config.php kanssa, niin saadaan liian monelle WP-ylläpitäjälle liiankin tuttu 500 Internal Server Error.

Se on sovellustason virhe. Varnishin näkemyksen mukaan backend on pystyssä, kuten se onkin, ja error 500 on omalla perverssillä tavallaan vastaus pyyntöön kuin mikä tahansa muukin. Se on myös cachettavissa — joka on jo ajatuksenakin ongelmallinen. Mutta se tuo mukanaan kaksi asiaa.

Koska 50x-sarjan virheet voivat tulla myös backendin vastauksena, niin

  • ne on käsiteltävä vcl_backend_responseosiossa, koska ei tiedetä etukäteen mitä reittiä ne tulevat
  • 500 ja 504 tarvitsevat ohjauksen snapshot-reitille

Taas kerran semanttisesti miettien 500 ja 504 ovat hyvin eri asioita kuin 503. 503 tarkoittaa, että serveri on rikki, mutta kaikki sen takana on ehjiä. 504 vaikuttaa kylläkin myös kaikkiin, mutta serveri on useimmiten terve. 500 saattaa koskea vain yhtä web-appia, vaikka WordPress-sivustoa, mutta kaikki muu toimii, mukaanlukien backendin Apache2.

Minä olen hyvin pragmaattinen luonne. Ja pragmaattisesti ajatellen minulle nuo kaikki ovat samoja — se takia niputan ne niin usein yhteen ilmaisulla 50x-sarjan virheet. Kävijälle lopputulos on aina sama: näkyy tylsällä sivulla 5-alkuinen numerosarja, eikä sisältöä näe. Syy mikä on korjattava koskee vain minua. Käyttäjälle sillä ei ole eroa.

Joten kaikki lähtevät samalle snapshot-reitille.

Tuo tehdään tällä vcl_backend_response osioon kuuluvalla osalla.

        ## WordPress is down, let's start snapshot route
        if (beresp.status == 500) {
                std.syslog(180, "Backend: error  HTTP 500 – move to vcl_backend_error");
                return (fail);
        }

        ## One really stranger thing where backend can tell 503, let's start snapshot route
        if (beresp.status == 503) {
                std.syslog(180, "Backend: error  HTTP 503 – move to vcl_backend_error");
                return (fail);
        }

        ## Backend has gateway issue, let's start snapshot route
        if (beresp.status == 504) {
                std.syslog(180, "Backend: error  HTTP 504 – move to vcl_backend_error");
                return (fail);
        }

        ## if backend is down we move to snapshot backend that serves static content, when not in cache
        ## Few things must  be changed then
        # Varnish uses 200 OK, because it got content, but we don't want to tell to bits that temporary content is 200
        if (bereq.backend == snapshot && beresp.status == 200) {
                std.log(">> Snapshot backend responded 200 — rewriting to 302");
                set beresp.status = 302;
                set beresp.reason = "Service Unavailable (snapshot)";
        }

        ## If there was an backend error (500/502/503/504) where backend can give a response
        if (beresp.status == 500 || beresp.status == 502 || beresp.status == 503 || beresp.status == 504) {

                # If this was a background fetch (i.e. after grace delivering), abandom
                if (bereq.is_bgfetch) {
                        std.syslog(180, "Backend failure, abandoning bgfetch for " + bereq.url);
                        return(abandon);
                }

                # Stop cache only if not in snapshot backend
                if (bereq.backend != snapshot) {
                        std.syslog(180, "Backend failure, marking response uncacheable: " + bereq.url);
                        set beresp.uncacheable = true;
                }
        }

Kolme ensimmäistä sääntöä — 500, 503 ja 504 — kääntävät nimenomaan snapshot backendille. Se tehdään komennolla return(fail). Se käskee Varnishin lopettaa tekemisen siihen paikkaan ja hypätä osioon vcl_backend_error — tismalleen samoin kuin jos Apache2 olisi hiljentynyt. Siellä meillä on if- lause, joka suostuu käsittelemään vain noita virheitä.

Neljäs muuttaa http statuksen 200 OK muotoon 302 Found. Saadaan botit, varsinkin googlebot, tyytyväisiksi.

Viides on hyvin hyödytön tällä hetkellä. Olkoot, koska saatan tarvita sitä joskus myöhemmin, eikä tarvitse silloin miettiä miten moinen tehdään.

Se tekee kaksi asiaa.

Jos sellaiselle pyynnölle tulee 50x virhe, jonka TTL on nolla tai se on cachen suhteen jatkoajalla, niin normaalisti cache yritettäisiin virkistää noutamalla se hiljaisesti taka-alalla. Nyt se keskeytetään, koska moiselle ei ole mitään mieltä, sillä nythän jo tiedetään, että backend ei toimi. Loppuosa vaan kertoo, että mitään noiden virheiden vastauksia ei saa laittaa cacheen.

Tuo on hyödytön siksi, että 502 on ainoa virhe, joka voi päästä tuohon asti. Enkä ole eläessäni törmännyt tilanteeseen, jossa serveri olisi kykenevä vastaamaan 502.

502 Bad Gateway on aina servereiden ja proxyjen välinen katkos. Koska Nginx ei edes tiedä Apachen olemassaolosta, niin Nginx voi saada 502 vain kun Varnish ei vastaa mitään järkevää. Apache2 taasen ei huutele 502 virhettä Varnishin kaatuessa, koska Apache2 ei juttele mitään Varnishin suuntaan. Se vain kuuntelee koska Varnish puhuu sille. Ja kun Apache vastaa, niin se ei tiedä mikä kuuntelee. Kun Varnish on välistä kaatunut, niin ainoa keneltä käyttäjä voi saada vastauksen, on Nginx.

503 Service Unavailable tulee, kun palvelin ei pysty vastaamaan mitään. Jos se tulee palvelimelta itseltään, niin sillä on häiriö, joka estää toiminnan. Eikä se aina johdu siitä, että jokin on rikki tai ylikuormitettu. Palvelimella voi olla myös menossa päivitys.

Varnish ilmoittaa backendin kaatumisesta virheellä 503, koska se ei ole itse varsinaisesti serveri. Varnish vain tulkkaa tilanteen, jossa Apache2 ei puhu sille. Muutoinhan 502 olisi oikeampi koodi — mutta koska Varnish ei ole serveri tuolla ajatuksella, niin se ei anna 502 virhettä.

Nginx saattaa antaa itsestään 502 virheen. Ylläpito erottaa sen Varnishin kaatumisesta oikeastaan vain siitä, että jos Varnish on hengissä ja kaikki valot ovat vihreinä, niin silloin syypää on Nginx.

On toinenkin tapa. Tekee Nginxiin 502 virheen tapahtuessa vastaavan snapshot emergency failsafen kuin mitä Varnishilla on Apachen varalle, tai tuottaa edes virheilmoituksen error_reporter.py avulla. Jos se toimii, niin Varnish on kaatunut. Jos tulee tylsä Nginxin ilmoitus, niin se itse on niin sekaisin päästään, että ei pysty toimimaan.

Vielä on yksi asia lisättävänä.

Kun lähdetään noutamaan sisältöä snapshotista, niin lisätään yksi kontrolli-header. Sitä käytetään myöhemmin Nginxin kanssa. Se lisätään osioon vcl_backend_fetch. Tämäkään osio ei ole tavanmukaisissa asennuksissa default.vcl mukana, mutta nyt se tarvitaan. Osio sijoitetaan yleensä ennen osiota vcl_backend_response.

Lisää tämä:

sub vcl_backend_fetch {

        if (bereq.backend == snapshot) {
             set bereq.http.X-Emergency-Redirect = "true";
        }
}

Snapshot toimii Varnishin osalta näillä. Tosin itse snapshot-serveriä ei vielä ole.

Snapshotin TTL+grace

Moiselle hätäapubackendille voi rakentaa omat cachesäännöt. Olin jo tekemässä niitä ja olin saanut oletuksena käytettävät TTL, grace ja keep ajat paikalleen.

Sitten tulin järkiini ja pyyhin ne pois. Niitä ei tarvita. Moisten rakentaminen tekee VCL:stä vain sottaisen ja hankalasti luettavan.

Minulla on vahva cache. Se kattaa noin 80 % ihmisten tekemistä pyynnöistä. Bottien osalta puhutaan noin 30 prosentista. Boteilla prosentti on noin alhainen, koska isossa osassa tapauksista pyydetään jotain objektia vain kerran, ja siitä saadaan return(miss). Toista kertaa ei koskaan tule. Tai sitten pyyntö kohdistuu sellaiseen asiaan, jota ei koskaan cacheteta. Mutta pointti on siinä, että ihmiset saavat useimmiten välimuistista pyytämänsä, ja ketään ei kiinnosta botit (jotain ne esilämmittävät cacheen ihmisiä varten, mutta ei mennä siihen nyt).

Sitten Apache2 kaatuu. Välimuistin takia suurin osa kävijöistä ei koskaan tiedä noin käyneen. Pieni osa joutuu jonkun harvinaisemman asian takia piipahtamaan snapshotissa. Mutta jos ja kun se on kertalaatuinen asia, niin pyyntö on toki hashattu, mutta sitä ei koskaan haeta cachesta. Ehkä sillä perusteella voisi harkita lyhyempää TTL:ää, mutta onko sille aitoa tarvetta? Oletus kuitenkin on, että moiset häiriöt ovat poikkeuksellisia, harvinaisia ja lyhytikäisiä.

Osa alkuperäisestä välimuistitetusta sisällöstä saattaa vanhentua sinä aikana kun Apache2 on poissa pelistä. Silloin pyynnöt menevät ensiksi suoraan snapshottiin, se populoi cachen ja seuraavat saavat sisällön välimuistista. Minulle tuo sopii. Jos joku kyselee jo kerran pyydettyä, niin cachesta sen nopeammin saa.

Kaiken kaikkiaan tuo tarkoittaa sitä, että vakio-TTL toimii. Jos jokin on katkoksen aikana päivittynyt cacheen, niin se on viety sinne uutena polulla /emergency/. Vanha, alkuperäinen, cachetieto on mennyt yli-ikäiseksi. Kun backend palaa takaisin linjoille, noita snapshotin polun urleja ei haeta, vaan ne häviävät digi-ikuisuuteen omien aikojensa perusteella unohdettuina ja yksinäisinä.

Siksi backendin toipuessa kannattaakin antaa snapshotin /emergency/ polulle heti ban(). Saa hiukan RAM:ia vapaaksi johonkin hyödyllisempään tarpeeseen.

Nginx: snapshot-serveri

Varnish, joka on eräällä tavalla liikennepoliisina ohjaamassa liikennettä, kun asia, jota ei haluta, tapahtuu — backend kaatuu millä tahansa tavalla.

Nyt tarvitaan snapshot-palvelin, joka paikkaa Varnishin cachen vajeita. Siihen käytetään Nginxiä, joka tavallisesti hoitaa tärkeimpinä asioina https-liikenteen SSL-sertifikaatin tarkistuksen ja välittää liikenteen Varnishille.

Siellä on asetustiedostot, conffit, jokaiselle hostille. Niistä löytyy jo serveriblokit 443 ja 80. Lisätään sinne kolmas, 127.0.0.1:8383

Portti saa olla joku muukin. Kunhan sitä ei kuuntele mikään muu palvelu. Minulla se on 8383 vain siksi, että Apache2 keskustelee portin 8282 kautta. Koska Varnishissa minulla on ensimmäisenä varsinainen sites backend porttiin 8282, niin toisena oleva snapshot porttiin 8383 näytti ammattimaiselta. Tai jotain.

Makuasioita, mutta kannattaa sitoa tuo localhostiin. Snapshot-serverille ei ole tarvetta päästä ulkomaailmasta. Silloin ainoa tapa, tietysti adminin käyttämän curlin lisäksi, on vain Varnishin kautta. Ja Varnish päästää sinne vain silloin kun Apache2 ei vastaa eikä sisältöä löydy cachesta.

Muista vaihtaa serveritiedot. Tämä on live-esimerkki.

server {
    listen 127.0.0.1:8383;
    server_name www.katiska.eu;
    root /var/www/emergency/katiska/www.katiska.eu;

    access_log   /var/log/nginx/access-snapshot.log main;
    error_log    /var/log/nginx/error-snapshot.log;

    location / {
        # Merkitään tuleeko snapshot-redirectistä
        set $serve_snapshot "0";

        if ($http_x_emergency_redirect = "true") {
            set $serve_snapshot "1";
        }

        # Tullaan vasta snapshotiin
        if ($serve_snapshot = "0") {
            add_header X-Emergency-Redirect "true" always;
            return 302 https://$host/_emergency_$request_uri;
        }

        # Ollaan jo snapshotissa, kuten vaihdetaan sivulta toiselle
        if ($serve_snapshot = "1") {
            return 302 https://$host/_emergency_$request_uri;
        }

    }
    
    location ^~ /_emergency_/ {
        add_header X-Snapshot-Served "true" always;
        add_header X-Robots-Tag "noindex, nofollow" always;
        add_header Cache-Control "no-store" always;
        try_files $uri $uri/ =404;
    }
}

Kyllä, teen kaksi uudelleenohjausta samaan paikkaan. Pääsisin lähes samaan tekemällä suoran uudelleenohjauksen kaikille. Ne ovat erillisten if-lauseiden takana logien takia. Jos on joskus tarvetta, niin nyt pystyn erottamaan milloin joku on haarautunut snapshottiin ensimmäisen kerran vai onko joku pyörimässä siellä useampaa kierrosta.

Lisään urliin /emergency/. Osaksi siksi, että joku saattaa ihmettellä miksi ulkonäkö on ihmeellinen. Silloin on mahdollista, että hän vilkaisee osoitetta ja oivaltaa olevansa syystä tai toisesta jossain muualla. Mutta oikeammin teen sen bottien takia. Ja cachen.

Kun snapshottiin haaraudutaan, niin mukaan laitettiin http status 302 Found. Siinä kerrotaan, että osoite on siirretty väliaikaisesti Location headerin mukaiseen paikkaan. Jos en muuta urlia, niin silloin väliaikaisen paikan url on tismalleen sama kuin varsinaisen paikan. Olen jotain oppinut vuosien saatossa webmasterina jumalan armosta ja se on, että älä sekoita botteja. Silloin ne tekevät ihan mitä sattuu. Ihan sama kuin nykyään tekälyjen kanssa — sano jotain, joka poikkeaa odotetusta käsikirjoituksesta, ja niiden pasmat sekoavat heti.

Toki varmistelen asiaa vielä sillä, että kerron headereissa, että tätä ei kuulu cachettaa eikä indeksoida. Sen lisäksi minulla robots.txt tiedostossa /emergency kielletty — mutta botit, mukaan lukien eri versiot googlebotista, noudattavat robots.txt ohjauksia hyvin vaihtelevasti. Annan vaadittavat cachen estävät http headerit jopa Nginxin conffissa — joka on totaalisen turhaa, koska Varnish jyrää ne kuitenkin heti.

Nginxin konffissa käyttämä 302 ei toimi statuksen muuttamisessa. Se kohdistuu sisäiseen uudenohjaukseen urlin muuttumisen takia. Varnish sen sijaan ilmoittaa 200 OK, koska sehän on saanut sisältöä snapshotista, mutta 200 ei haluta ilmoittaa boteille.

Joten tehdään varmistuksen varmistusta ja pidetään huoli siitä, että vierailijan mukana tosiaan kulkee error 302. Varnish osaa, paitsi cachettaa, myös muuttaa suunnilleen kaiken meta-tiedon, jota pyyntöjen mukana kulkee.

Snapshotin rakentaminen

Snapshot-backend ilman sisältöä on aika turha. Joten se täytyy rakentaa. Minä käytin siihen wgetiä, joka hakeaa sitemapin kautta tiedot.

Ongelma on siinä, että suurin osa sivustoista käyttää hakemistotiedostoa sitemap_index.xml, jota wget ei hallitse. Joten tein pienen scriptin, joka parsii eri sitemapit yhdeksi ja tarjoilee sen wgetille.

nano /usr/local/bin/generate-snapshot.sh

Ja lisää tiedot gististä:

https://gist.github.com/eksiscloud/58a0378cc5323aa470f532b5aa7ce7f0

Sivuston katiska.eu rakentaminen kesti 14 tuntia, joka on… turhan kauan. Joten jokin ei ole tuossa kohdallaan. Korjaan kun tiedän mikä. Tosin wget on aina ollut hidas. Mitä tuohon voisi sanoa… tmux tai screen on ystäväsi.

Sen pitäisi myös lämmittää cache, mutta ehkä se kannattaa estää (Varnishissa esimerkiksi return(pass); tuolle user-agentille) ja tehdä erikseen kohdistetumpi cachen lämmitys. Kuten laittaa sinne tosiaan ne urlit, joita ihmiset aidosti pyytävät. Tuo on vahvasti tulkintakysymys.

Cachen lämmittäminen

Cachen lämmittäminen tarkoittaa sitä, että joku spideri kahlaa sivuston läpi kuin käyttäjä ja lataa sisällön nimenomaan backendiltä. Silloin se on valmiina odottamassa käyttäjiä.

Minä olen tehnyt kumpaakin, lämmittänyt ja antanut kävijöiden viedä cacheen sen missä piipahtavat. Nykyään lämmitän, koska minulla on muistia riittävästi.

Varnishin cache sijaitsee RAM:ssa, ja siihen käytetty muistitila on pois sitten muusta. Toiselta puolen, silloin muistia ei tarvita sivustojen käyttöön, koska suurin osa välimuistitettu. Joten se, mitä ylipäätään cachettaa — videoklipit olisivat täyttä murhaa — ja kuinka kauan asioita pidetään cachessa, ovat vahvasti taloudellisia kysymyksiä.

Jos muistia on 4 gigaa, niin ei siinä kovinkaan isoa cachea varastoida. Mutta lyhytaikaisille piikeille se riittää yleensä joltisenkin hyvin — varsinkin jos edes yrittää tappaa joutavat botit juoksentelemasta nurkissa.

Minulla on 16 gigaa muistissa, ja olen varannut Varnishille 12 gigaa. Yleinen muistisääntö on, että Varnishille annettaisiin 80 %, mutta tuota toki kannattaa miettiä sen suhteen mitä muuta käyttöä. Jos järjestelmä työntää koko ajan tavaraa swappiin, niin ehkä kannattaa jättää Varnish asentamatta.

Cachen lämmittämiseen käytin aikoinaan tätä:

wget --spider -o wget.log -e robots=off -r -l 5 -p -S -T3 --header="X-Bypass-Cache: 1" --header="User-Agent:CacheWarmer" -H --domains=example.com --show-progress www.example.com

Kyllä se toimi. Latasi aika paljonkin kaikkea. Se tosin latasi kaiken levylle, eikä kohdetta ollut määritelty, joten sitä kannatta käyttää vain /tmp hakemistosta. Tai sitten opetteli rm -rf ... komennon käytön.

Nykyään käytän tätä scriptiä.

https://gist.github.com/eksiscloud/2ba66cacb1e0387848381de8105b1f58

Se hakee Matomolta API:a käyttäen 30 päivän ajalta (saa muutettua) top-200 eniten käytettyä urlia, joissa määrätyn maan kävijät ovat käyneet, ja lämmittää cache niillä. Tarkoitus on käyttää niitä sivuja, joita eniten käytetään. On hieman tilan tuhlaamista lämmittää cache osumilla, joita kukaan ei ole aikoihin lukenut. Tai joita botit ovat hakeneet.

Ylläpitäjän ilmoitus kaatumisesta

Tämä kappale on oleellinen vain Discoursen kanssa

Jos sinulla ei ole omaa foorumia, tai olet käyttäjänä jossain, jossa ylläpitäjä ei ole myötämielinen ajatukselle, että käyttäisit foorumia push-serverinä error-viesteille (siitähän kyse on), niin ohita tämä. Kaikki allaoleva tehdään vain siksi, että saadaan lähetettyä foorumialoituksen tekevä API-pyyntö Discourseen.

Snapshot/cache toimii ilmankin. Joten Discoursea et tarvitse, mutta Varnishin kylläkin.

Takaisin asiaan. Nyt ihmetellään miten admin saa tiedon siitä, että Apache2 on alhaalla ja vierailija on sen takia snapshot-reitillä.

Se tehdään logien kautta. Varnish tekee merkin, että nyt tuli virhe ja sitten serverin palvelut lukevat sen ja tekevät omia taikojaan. Siihen lukemiseen tarvitaan rsyslog.

Jota rsyslog pystyisi lukemaan jotain, niin tehdään virhetilanteesta erillinen merkintä Varnishin kautta. Sehän on tällä hetkellä järjestelmässä ainoa, joka näkee virheen.

Aiemmin lisättiin tämä lohkon vcl_backend_error alkuun. Sinne virhetilanteet aina päätyvät.

if (bereq.retries == 0) {
    std.syslog(180, "ALERT: Apache is not responding on first attempt: " + bereq.url);
}

Tuo kirjoittaa syslogiin pyynnön tullessa ensimmäisellä kierroksella. std.syslog ei näy varnishlogissa, mutta kylläkin journalctlissa ja syslogissa. Tuo virheilmoitus on triggeri, joka käynnistää ketjun, joka lopussa Discoursessa julkaistaan ilmoitus virhetilanteesta.

Tarvitaan middle-man siirtämään tuo logimerkintä helpommin käytettävään muotoon.

nano /etc/rsyslog.d/apache_alert.conf

Lisää tämä:

# Ladataan oletusmoduulit
module(load="builtin:omfile")

# Kirjoita kaikki varnishd-viestit debugiin (tilapäinen, voit poistaa myöhemmin)
#if $programname == 'varnishd' then {
#    action(type="omfile" file="/var/log/apache_rsyslog_raw.log")
#}

# Kirjoita alertit queue-logiin
if ($programname == 'varnishd' and $msg contains 'Apache is not responding') then {
    action(type="omfile"
        file="/var/log/apache_alert_queue.log")
    stop
}
  • Tässä välissä lienee sopiva hetki luoda käytettävä logi touch komennolla
  • login /var/log/apache_alert_queue.log ,ja ylipäätään kaikkien järjestelmälogien omistajuus täytyy olla
chown syslog:adm /var/log/apache_alert_queue.log
chmod 664 /var/log/apache_alert_queue.log
systemctl restart rsyslog
  • Tuolle kannattaa myös tehdä logrotate:
/var/log/apache_alert_queue.log {
    size 1M
    rotate 5
    compress
    missingok
    notifempty
    create 664 syslog adm
}

Käynnistä rsyslog uudestaan:

systemctl restart rsyslog
  • normaali status näyttää toki, että onko se pystyssä. Ja on, vaikka ei toimisi ollenkaan. Toimivuuden saat kokeiltua komennolla rsyslogd -N1

Rakennetaan tarvittava apache_alert.sh

nano /usr/local/bin/apache_alert.sh

Lisää tämä:

#!/bin/bash

# Debug
#echo "Script käynnistyi $(date)" >> /var/log/apache_alert_debug.log

tail -Fn0 /var/log/apache_alert_queue.log | while read line; do
    echo "RSYSLOG: $line" >> /var/log/apache_alert_debug.log
    json="{\"code\":503, \"source\":\"Varnish\", \"message\":\"$line\"}"
    curl -X POST http://127.0.0.1:5060/report/503 \
     -H "X-Error-Token: <TOKEN>" \  # asetetaan error_reporter.py
     -H "Content-Type: application/json" \
     -d '{"message":"test"}'
done
  • molemmat logit on oltava olemassa

Tehdään tuosta suoritettava scripti:

chmod +x /usr/local/bin/apache_alert.sh

Käynnistetään rsyslog uudestaan, varmuuden vuoksi:

systemctl restart rsyslog

Vielä tarvitaan yksinkertainen daemon, tai service — miksi sitä sitten haluatkaan kutsua.

nano /etc/systemd/system/apache-alert.service

Lisätään tämä:

[Unit]
Description=Apache crash alert daemon
After=network.target rsyslog.service

[Service]
ExecStart=/usr/local/bin/apache_alert.sh
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Ladataan ja käynnistetään se.

systemctl daemon-reexec
systemctl enable --now apache-alert.service

Aina on hyvä testata.

  • käynnistä toiseen ikkunaan error_reporter.py käyttämä logi, jos sitä käytät
tail -f /var/log/error_reporter.log
  • tee tämä toisessa ikkunassa
logger -p user.notice -t varnishd 'ALERT: Apache is not responding on first attempt: /test'
  • tarkista, että pyyntö saapuu error_report.py
  • cat /var/log/apache_alert_queue.log
  • cat /var/log/apache_alert_debug.log — siis jos debug on käytössä

Jos jouduit testaamaan, ja muutit jossain vaiheessa apache_alert.sh tiedostoa, niin turhat prosessit kannattaa tappaa kuljeksimasta. Muutoin olet äkkiä tilanteessa, että omat curl-testit menevät läpi, mutta live-kävijät saavat jotain ihan muuta.

Tällä näet prosessit:

ps aux | grep apache_alert.sh

On helpointa tappaa kaikki ja käynnistää service uudestaan:

pkill -f apache_alert.sh
systemctl restart apache-alert

error_reporter.py

Minun on turha laittaa koko koodia näkyviin, koska se on gistinä.

https://gist.github.com/eksiscloud/9f476538366b02ed8d1c19f9d1fc299f

Huomaa, että Flask täytyy olla asennettuna. Kun tulee raportoiva virhe, niin se lähtee Flaskin kuunteleman portin kautta error_reporter.py koodin käsittelyyn ja riippuen sitten reitistä, eli 50x koodista, niin lähtee otsikoltaan erilainen virhe Discourseen, ja toki myös kävijälle.

Tuo koodi käsittelee myös 502 virheen, mutta se on oman aiheensa väärtti. Silloin asioita tehdään Nginxin kautta.

Tässä setupissa error_reporter.py ei kuuluisi koskaan näyttää 503-virhettä, eli backend on nurin, kävijälle — siksi on käytössä cache ja snapshot-backend. Sen sijaan ilmoitus adminille Discoursea käyttävälle serverille on haluttua.

Toiminta-ajatus

Kun Apache2 kaatuu ja ensimmäinen kävijä pyytää jotain, niin ensin Varnish käsittelee pyyntöä normaalisti. Koska pyyntöä ei saada Apachen backendiltä sen ollessa vikatilassa, niin Varnish siirtyy virheen käsittelyyn. Ennen kuin edes valitaan käytetäänkö cachea vai snapshotia, niin tehdään logimerkintä.

rsyslog huomaa tuon virheen ja siirtää sen yksinkertaisemmalle queue-logille. Samaan aikaan taustalla on service, jonka ainoa tehtävä on pitää apache_alert.sh hengissä ja käynnissä. Se seuraa koko ajan queue-login viimeistä riviä. Kun sinne ilmestyy uusi, niin skripti lähettää tiedon suoraan toiselle scriptille error_reporter.py. Sen tehtävä on esittää käyttäjälle virhe, jos sisältöä ei löydy ja kertoa siitä Discourselle.

Tuossa on yksi vaje. Tai ominaisuus. Riippuu ihan miten sen haluaa nähdä.

Logimerkintä tehdään aivan jokainen kerta, kun osutaan virheeseen. Jolloin myös jokainen kerta liipaistaan error_reporter.py käyntiin. Sen oma rate limiter kylläkin estää floodauksen foorumille, joten uusi ilmoitus tulee neljän tunnin välein. Tuon kanssa pystyn elämään.

Minulla tosin oli rate limit ongelma foorumilla, kun otin tuon käyttöön. Mutta se johtui niin yksinkertaisesta asiasta kuin että API-käyttäjä oli tavallisilla TL1 oikeuksilla (päästän kaikki rekisteröityneet suoraan tuohon), mutta API-käyttäjääkin koski uuden käyttäjän 24 tunnin rajoitukset — ja se törmäsi niihin saaden error 429 eli rate limitin. Kiersin ongelman tekemällä siitä adminin, mutta tuo kylläkin rikkoo kaikkia turvallisuusajatuksia. Toiselta puolen, sen API-käyttö on tarkkaan rajattua.

Lopputulos

Minulla on nyt järjestelmä, joka backendin kaatuessa (error 503) tai jos WordPressin joku plugin hajoaa (error 500)

  • alkaa esittämään sisällön cachesta Varnishin tai staattisesta sisällöstä Nginxin avulla
  • jos sisältöä ei löydy, niin esittää lohdutteluviestin
  • julkaisee virheilmoituksen Discourse-foorumilla, joka tekee minulle push-ilmoituksen (joka 4. tunti, jos virhettä ei ole korjattu)

Tuo rakennelma on tilkkutäkki. Kudoin sitä koko ajan edetessäni ja koska minulla ei ollut hajuakaan mitä tein, niin opettelin matkan varrella. Olen aivan varma, että tuon saisi tehtyä helpomminkin — mutta minä en siihen pysty.

Minulla ei ole myöskään hajua siitä kuinka raskas tuo on. Ei serverini ainakaan vielä ole hajonnut.

En tiedä sitäkään olenko jättänyt niin sanotusti takaoven selälleen, mutta en usko. Tekemiset onnistumisen suhteen on kuitenkin tokeneilla suojattu, ja kaikki tapahtuu saman serverin sisällä. Mutta jos olen tehnyt jotain aivan aivokuollutta, niin olisin toki kiitollinen jos siitä kerrottaisiin.

Jakke Lehtonen

Teen B2B-markkinoille sisällöntuottoa sekä UX-testauksia. Samaan liittyy myös koulutukset yrityksille ja webmaailman kanssa muutoin painiville. Serverien sielunelämää on joutunut ohessa opettelmaan. Toinen puoli toiminnasta on koirien ravitsemuksen ja ruokinnan suunnittelua sekä varsinkin omistajien kouluttamista hoitamaan koiriaan oikein ja vielä paremmin. Profiili: Jakke Lehtonen

Keskustele foorumilla Katiskan foorumi

WordPressin kommentit: