“Kelaa, jotkut konffaa AWS:ää joka päivä”
Tämä kirjoitus on sangen tekninen ja tarkoitettu erityisesti ATK-ohjelmoitsijoille.
Vuosien koodaustauon jälkeen olen syksyn kuluessa kötöstellyt palvelua, joka koostuu Vue.js:llä toteutetusta frontista ja Djangolla koodatusta REST-backendistä. Lisäksi käytössä on PostgreSQL-tietokanta, Redis-palvelin sekä pieni Node.js-mokkula joka juttelee selaimelle Socket.iolla ja on yhteydessä Redisiin. Kutsun sitä jatkossa nimellä Redis Bridge.
Ajoympäristöksi palvelulle valitsin monista syistä Amazon AWS:n. Se oli minulle aiemmin sangen vieras, ja halusin samalla tutustua siihen tarkemmin. Nyt osa lukijoista haluaa kommentoida että olisit käyttänyt Herokua, Azurea, Digital Oceania tai Google Cloud Platformia. Kyynikkona veikkaan että lopputuloksena olisi yhtä pitkä kirjoitus kun törmäisin erilaisiin ongelmiin. Lisäksi olen käyttänyt nyt niin paljon aikaa AWS:n kanssa tappeluun että yritän pärjätä sen kanssa (sunk cost fallacy).
Tämä kirjoitus on yhteenveto tekemistäni muistiinpanoista ja kohtaamistani ongelmista. Toivottavasti siitä on hyötyä muille AWS:n kanssa painiville.
Hirveä läjä käsittämättömän nimisiä palveluita
Yksi suurimpia tuskia AWS:ssä on palveluiden järjetön määrä. Alla olevassa kuvassa näkyy vain osa tarjolla olevista. Laittaako siis koodi pyörimään EC2:een, Lambdaan, Fargateen vai Elastic Beanstalkiin? Lisäksi osa palveluista on nimetty todella typerästi. Mikä ihmeen elastinen pavunvarsi?
Päädyin laittamaan frontin staattisen koodipaketin tarjolle S3-palvelun bucketiin (kukahan tuon termin on keksinyt?). S3 eli Simple Storage Service tarjoaa yksinkertaisesti tallennustilaa, ja sitä käytetään usein myös ulkomaailmalle suljettuna tietovarastona.
Jotta ämpäriin pääsee ulkomaailmasta käsiksi, pitää sinne määritellä “Bucket Policy” eli tässä tapauksessa 11 riviä pitkä JSON-tiedosto. Tämän lisäksi on vielä nelivaiheinen hiirellä kliksuteltava “block public access” ‑lista, jonka valintoja saa monta kertaa tavata:
Django- ja Redis Bridge ‑palvelimet taas hoituvat Elastic Beanstalkin kautta. Elastic Beanstalkille kerrotaan esimerkiksi, että halutaan Python-palvelin jonne Django laitetaan pyörimään, ja Amazonin tarjoaman eb cli ‑työkalun avulla hoidetaan asennus (tämän automatisointi Gitlabilla on helppoa). Elastic Beanstalk hoitaa loput, eli tapauksessani mm. provisioi EC2-virtuaalikoneen (Elastic Computing Cloud) ja asentaa sinne koodin. Heti asennuksen jälkeen palomuuri esti yhteydet ulkomaailmasta, joten nämä piti käydä muuttamassa.
Versionhallintaan piti lisäksi luoda .ebextensions-hakemisto molemmille Elastic Beanstalkiin ajoon laitettaville palvelimille. Esimerkiksi Djangoa varten pitää kyseiseen hakemistoon luoda vähintään kolme loitsutiedostoa käsin.
Versionhallintana ja CI-työkaluna käytössä on Gitlab, joka hoitaa myös palvelinten päivittämisen. Jotta Gitlab voi koodeja AWS:n päivittää, pitää sille antaa tarvittavat oikeudet. Tätä varten tarvitaan Amazon IAM (Identity and Access Management) ‑palvelua. Sen avulla luodaan käyttäjä, jolle annetaan oikeudet päivittää koodia S3:een sekä Elastic Beanstalkiin. Erilaisia oikeuksia on 473 kappaletta! IAM:n generoimat avaimet tallennetaan Gitlabiin muuttujiksi ja niiden avulla frontin, backendin ja Redis Bridgen asentaminen AWS:n onnistui lopulta suht kivuttomasti.
Myös Redis-palvelimen asennus ja käyttöönotto onnistui valmiiksi paketoidusta setistä varsin helposti AWS Marketplace ‑kauppapaikasta veloituksetta.
PostgreSQL-tietokannan Amazon tarjoaa RDS-palvelulla. Kun vielä tietokannan luonti RDS:n onnistui heittämällä, ajattelin että koko homma on pian valmis. Enpä olisi voinut olla enempää väärässä.
Tuskaa sertifikaattien kanssa
Jotta palvelimille voidaan ottaa suojattu HTTPS-yhteys, tarvitsee niihin asentaa sertifikaatti. Palveluni yhteydessä tarvitaan suojattu yhteys kolmelle palvelimelle: frontend, backend sekä Redis Bridge.
Amazon tarjoaa suhteellisen helppokäyttöisen (ja kuvaavasti nimetyn!) Certificate Manager ‑palvelun, jolla sertifikaatteja voi varata. Se hoitaa myös sertifikaatin uusimisen automaattisesti. Härvelille kerrotaan osoite, jolle sertifikaatti halutaan luoda, ja asennetaan Certificate Managerin pulauttamat tietueet DNS-nimipalvelimelle. Omassa tapauksessani sertifikaatti oli valmis pari tuntia DNS-muutoksen tekemisen jälkeen.
S3 ämpäriin ei voi suoraan asentaa sertifikaattia, vaan jälleen päästään tekemisiin uuden palvelun kanssa. Nyt tarvitaan Amazon CloudFrontia, jonka kautta liikennöinti ämpäriin onnistuu. Ja toki muutetaan jälleen myös DNS:ää, jotta liikenne ohjautuukin S3:n sijasta CloudFrontiin.
Palvelussa on siis kaksi Elastic Beanstalkin kautta asennettua EC2-palvelinta, joille tarvitaan myös sertifikaatit. Amazonin Certificate Managerin tekemiä sertifikaatteja ei voi hyödyntää näiden palvelinten kanssa suoraan, koska sertifikaatteja ei saa siirrettyä ulos.
Yksi vaihtoehto on luoda sertifikaatti suoraan palvelimen konsolilla. Myöhemmin kuulen, että tämä on huono idea — Elastic Beanstalkin provisioimien pönttöjen asetuksia ei pitäisi tökkiä SSH-yhteyden yli, koska näin tehdyt muutokset menetetään jos palvelin on tarve uudelleenprovisioida.
Niinpä kuitenkin otin SSH-yhteyden palvelimiin. Ensimmäinen tehtävä oli selvittää käytössä olevan Linux-distribuution versio, koska ympäristöstä riippuen käytössä on koko Amazon Linux AMI tai tai Amazon Linux 2, ja ne ovat hyvin erilaisia.
Nykyaikainen tapa asentaa sertifikaatit konsolilta on asentaa Let’s Encrypt ‑sovellus palvelimelle ja antaa sen hoitaa ilmaisen sertifikaatin luonti ja uusinta. Tämä on hyvin yleinen toimenpide, mutta Amazon ei tarjoa siihen mitään automatisointia (esimerkiksi Netlifyllä ja Gitlabilla homma hoituu parilla klikkauksella).
Amazon kyllä tarjoaa ohjeet Let’s Encryptin asentamiseen, mutta varoittaa että sitä ei virallisesti tueta ja olet sen jälkeen omillasi. Seuraan ohjeita ja yritän asentaa Django-palvelimelle sertifikaatit. Asennus räjähtää ja samalla vetää palvelimen asetukset sellaiseen jojoon, että lopulta joudun pyytämään AWS:ää uudelleenasentamaan koko roskan.
Samalla turvaudun vaihtoehtoiseen lähestymistapaan. Palvelimen eteen otetaan käyttöön Classic Load Balancer, ja sen kanssa on mahdollista käyttää Certificate Managerin luomia sertifikaatteja. Tässä välissä joudun toki bonuksena odottamaan pari tuntia DNS-muutosta, jotta sertifikaatti valmistuu. Ratkaisu toimii aika kivuttomasti, mutta tuntuu absurdilta että Classic Load Balancerista veloitetaan käytetyn ajan suhteen ja se maksaa noin kuusi kertaa enemmän kuin käytössä oleva palvelin.
Seuraavaksi Redis Bridge ‑palvelimelle tarvitaan sertifikaatti. Optimistina seuraan jälleen Amazonin ohjeita (onhan kyseessä Node-palvelin eikä Python-palvelin). Nyt Let’s Encrypt ‑asennus epäonnistuu toiseen käsittämättömään virheilmoitukseen. Ota suosiolla uuden Classic Load Balancerin käyttöön ja odotan jälleen pari tuntia että Cerificate Manager tekee työnsä. Ja samalla moninkertaistan tämänkin palvelimen kustannukset.
Tämän jälkeen ihmettelen jälleen hyvän tovin, miksi selain ei edelleenkään onnistu juttelemaan Redis bridge ‑palvelimen kanssa, vaikka HTTPS yhteys terminoidaan Amazonin toimesta. Lopulta oivallan, että syypää on käytössä oleva Socket.io. Pyydän Classic Load Balanceria terminoimaan HTTPS:n sijasta SSL-yhteyden, ja lopulta kuso kulkee.
Tämän jälkeen kuulen, että Classic Load Balancerin sijasta kannattaisikin käyttää uudempaa Application Load Balanceria. Typeryksenä unohdan vanhan nyrkkisäännön ettei toimivaa systeemiä kannata rikkoa. AWS:stä löytyy suoraan migraatiotyökalu joka hienosti leipoo CLB:n pohjalta ALB:n. Työkalu kuitenkin unohtaa kertoa että Elastic Beanstalkin kanssa käytettävän Load Balancerin tyyppiä ei voi vaihtaa! Niinpä hallintapaneelista löytyy nyt tuplana Load Balancerit. Joudun jälleen uudelleenluomaan Elastic Beanstalkiin instanssit alusta jotta saan ALB.t käyttöön.
Frontti hajosi
CloudFrontin ja HTTPS-yhteyden käyttöönoton jälkeen huomaan uuden ongelman: frontin päivitys ei enää onnistu. Gitlab kyllä päivittää uuden version ämpäriin, mutta käyttäjä saa aina näkyviin vanhan version.
Syyllinenkin selviää: CloudFront hyödyntää aina välimuistia, josta palvelee sivut. Tämä saadaan korjattua lisäämällä .gitlab-ci.yml:n uusi loitsu deploy-vaiheeseen:
- aws cloudfront create-invalidation --distribution-id $STAGING_CLOUDFRONT_DISTRIBUTION_ID --paths '/*'
Tosin ensimmäisellä yrityksellä komennon ajaminen epäonnistuu riittämättömiin oikeuksiin. Niinpä pitää palata IAM-hallintaan ja antaa siellä luodulle käyttäjälle oikeus kötöstellä myös CloudFrontia.
Frontti ei toimi vieläkään
Nyt frontti sentään päivittyy, mutta muuten on ongelmia. Osa HTTP-headereista jää matkalle, koska CloudFront syö ne. Sinne pitää luoda uusi “behavior” ja määritellä välitettävät headerit.
Tässä välissä voi myös todeta, että AWS:n hallintaa on aika ankea käyttää ihan senkin vuoksi että useat käyttöliittymät ovat todella vanhahtavia ja rumia. Tosikäyttäjät tosin tekevät hallinnan esimerkiksi Terraformilla tai Cloudformationilla koskematta AWS:n käyttöliittymiin. Tässä esimerkiksi näyte CloudFrontista:
Samalla tuli selviteltyä miten tarvittaessa CORS-headereita hallitaan. Niitä varten S3 bucketista löytyy jopa “CORS configuration” kohta, mutta kliksuttelun sijaan sinne täytyy tuupata läjä XML:ää, esimerkiksi:
<?xml version="1.0" encoding="UTF-8"?> <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> <CORSRule> <AllowedOrigin>*</AllowedOrigin> <AllowedMethod>GET</AllowedMethod> <AllowedMethod>HEAD</AllowedMethod> <MaxAgeSeconds>3000</MaxAgeSeconds> <AllowedHeader>Authorization</AllowedHeader> </CORSRule> </CORSConfiguration>
Tämän lisäksi ne toki pitää erikseen päästää läpi CloudFrontista.
Redis heittää voltin
Yhtenä aamuna huomasin, että palvelu ei toimi. Yhteys Redis-palvelimelle aukesi, mutta palvelin ei vastannut pingiin. Amazonin hallintapaneeleista löytyi hälyttävä graafi:
Ilmeisesti Redis-palvelin oli kyllästynyt odottelemaan ja alkanut louhia Bitcoineja. Palvelimen uudelleenkäynnistys kesti todella pitkään, mutta lopulta se oli jälleen pystyssä. Palvelu ei kuitenkaan toiminut edelleenkään.
Lopulta huomasin, että palvelimen alasajon jälkeen AWS vapautti sen IP-osoitteen, ja varasi uuden kun palvelin käynnistyi. Tämän sai korjattua varaamalla palvelimelle “Elastic IP address” ‑osoitteen. Nämä osoitteet ovat jopa ilmaisia, tosin varatuista mutta käyttämättä jätetyistä peritään nimellinen korvaus. Myöhemmin minua valistettiin, että tässäkin yhteydessä Load Balancer olisi ollut Oikea Ratkaisu.
Kun Redis jökkäsi toisen kerran, käytin yhden työpäivän muutokseen jolla pääsin siitä eroon. Jos Redisille on jatkossa pakottava tarve, tutustun AWS ElastiCache-palveluun joka pohjautuu Redisiin.
Ympäristömuuttujat solmussa
Vihdoinkin softa oli muutaman päivän pyörinyt ongelmitta. Oli tullut aika lisätä backendin tietoturvaa.
Elastic Beanstalkissa pystyy sovelluksille antamaan asetuksia ympäristömuuttujien kautta. Minulla oli aiemmin käytössä yksitoista muuttujaa. Otin käyttöön kaksi uutta Djangon suojauksen parantamiseksi.
Pian tämän jälkeen huomasin jälleen, että palvelu ei toimi lainkaan. Virheilmoituksista selvisi, että Django yrittää kytkeytyä osoitteessa 127.0.0.1 pyörivään tietokantaan Amazonin RDS-kannan sijaan. Django siis jostain syystä kuvitteli, että sovellusta pyöritetään paikallisesti kehittäjän koneella.
RDS:n osoite välitetään sovellukselle ympäristömuuttujan kautta. Pitkän selvitystyön jälkeen havaitsin, että nyt käytössä olevasta 13 ympäristömuuttujasta sovellukselle välittyy vain 11. Toinen uusista lisäyksistä ei välity (toinen toimii), eikä myöskään aiemmin toiminut, RDS:n osoitteen sisältävä muuttuja.
Useiden kokeilujen jälkeen poistin aiemmin ympäristömuuttujista sen uuden lisäyksen, joka ei välittynyt sovellukselle. Ja kas, nyt RDS-muuttujakin alkoi jälleen toimia. En edelleenkään tiedä, miksi DJANGO_SECRET_KEY-niminen ympäristömuuttuja aiheuttaa tällaisen ongelman.
Vaihtoehtoisesti tiedot voisi tallentaa AWS Secrets Manageriin, mutta jälleen yhden palikan integroiminen ei houkuttele. SM:ssä on lisäksi sangen absurdi hinnoittelu, jokainen “salaisuus” maksaa $0,4 dollaria kuussa.
Logitus ei toimi
Edes logit eivät toimi heittämällä, ja tarvitaan jälleen hämmästyttävän paljon konfigurointia.
Django-sovelluksen generoimat info-tason logiviestit eivät näy Elastic Beanstalkin loginäytössä. Djangoon pitää määritellä uusi logitusten käsittelijä, joka tallentaa viestit /opt/python/log/django.log-tiedostoon kun koodia ajetaan Amazonissa.
Tämän lisäksi .ebextensions-hakemistooon pitää luoda uusi tiedosto, joka sallii lokitietojen kirjoittamisen tiedostoon:
commands: 01_create_file: command: touch /opt/python/log/django.log 02_change_permissions: command: chmod g+s /opt/python/log/django.log 03_change_owner: command: chown wsgi:wsgi /opt/python/log/django.log
Tiedosto syntyy, mutta lokiviestit eivät sinne tallennu edelleenkään. Tämänkin aiheuttaja on edelleen hämärän peitossa.
Tässä välissä voi myös ihmetellä, että osa AWS:n konfiguraatioista kirjoitetaan XML-muodossa, osa JSON:na ja osa YAML:na.
Frontti hajoaa jälleen
Kun kaikki palikat vihdoinkin toimivat, lisäsin palveluun käyttäjien autentikoinnin. Omalla koneella se toimi hienosti, mutta yllätys yllätys, ei AWS:llä. Frontin lähettämät autentikointiheaderit eivät saapuneet koskaan backendille.
Ratkaisu? Lisätään .ebextensions-kansioon neljäs tiedosto, joka löytyi Stackoverflowsta!
Lambda nukkuu
Amazon Lambda mahdollistaa ns. serverless-palveluiden rakentamisen. Sinne siis tökätään koodi joka suoritetaan pyydettäessä. Palvelinta ei tarvitse varata tai provisioida.
Lambdan avulla palveluun tullaan integroimaan vielä yksi palikka. Kokeiluissa Lambda toimi sangen hienosti, mutta pian havaitsimme että jos Lambdan koodia ei toviin kutsuttu, kesti sekunteja ennen kuin suoritus alkoi. Jos palvelua ei pidä “lämpöisenä”, kyllästyy AWS odottelemaan ja heittää sen pois provisioidusta tilasta.
Aiheesta löytyy lisätietoa ja lämmitysohjeita esimerkiksi Sam Carcosin kirjoituksesta. Jälleen siis pääsemme tutustumaan uuteen AWS-palveluun kun CloudWatchiin pitää rakentaa pingaus.
GitLab tökkii
Jokaisen commitin jälkeen kestää kuutisen minuuttia, että deploy on tapahtunut AWS:n. Gitlabille on mahdollista myös kertoa, että suorita deploy ainoastaan kun tiettyyn versionhallinnan haaraan tulee muutoksia. Harmi vaan että se ei toimi niin kuin pitäisi.
Palvelin näyttää punaista
Elastic Benstalkissa pelkästään Socket.io:ta käyttävä palvelin on punaisena ‘SEVERE’-tilassa, koska Application Load Balancer haluaa tarkistaa palvelimen sykkeen HTTP-viesteillä. Niinpä sovellukseen pitää lisätä myös HTTP-palvelin, jonka ainoa tehtävä on vastata “hengissä ollaan” AWS:n uteluille.
Lopuksi
Enpä aavistanut etukäteen miten kova homma hyvin tavallisen sovelluksen asentamisessa Amazoniin on — onneksi ei ole toistaiseksi Kubernetes-tarvetta. Englanniksi koko AWS:n kanssa tappelua kuvaa mainiosti termi yak shaving — lieneekö sillä kuvaavaa käännöstä suomeksi?
DevOps-konsultit voivat toistaiseksi olla huolettomia töiden riittämisen suhteen. Jos vain hermot riittävät.