tiistai 31. tammikuuta 2012

Käytä UTC-aikaa sovelluksissa


Universal time, Coordinated eli lyhemmin UTC tai koordinoitu yleisaika on nollameridiaanilla käytetty aikavyöhyke. UTC-aikaan ei vaikuta vuodenaika eli mahdollinen kesä- ja talviajan vaihtelu. Yleensä kaikissa järjestelmissä on helppo toimia UTC-ajan puitteissa, eikä sen käyttäminen vaadi erityisiä muunnoksia.

Mitä hyötyä on UTC-ajan käyttämisestä sovelluksen sisäisenä aikana? UTC-aika helpottaa ajan käsittelyä sovelluksen sisällä ainakin Suomessa, koska talvi- ja kesäaikaa ei tarvitse huomioida muuten kuin korkeintaan käyttöliittymässä. Sovelluksen ulkoisissa rajapinnoissa on hyvä käyttää UTC-aikaa, koska mikäli sovelluksen rajapintoihin ollaan yhteydessä muualta kuin Suomen aikavyökkeeltä, ei asiakassovellusten tarvitse välttämättä tehdä lainkaan muunnoksia aikavyöhykkeiden välillä (jos myös asiakas käyttää UTC-aikaa sisäisesti) eikä asiakassovelluksen tarvitse huomioida Suomen kesä- ja talviajan vaihtelua.

Käytännössä ainoastaan ihmiskäyttäjää kiinnostaa tietää mitä sovelluksen käyttämä aika on käyttäjän paikallisessa aikavyöhykkeessä. Käyttöliittymässä on hyvin yksinkertaista tehdä muunnokset UTC-ajasta paikalliseen aikaan ja takaisin. Usein ainoastaan käyttöliittymä tietää mikä on käyttäjän paikallinen aika, ellei käyttäjän aikavyöhykettä sitten välitetä jatkuvasti taustajärjestelmään ja toteuteta taustajärjestelmässä tarvittavat muunnokset. Aikavyöhykkeen esitysongelmat tulevat esiin erityisesti paikkariippumattomien asiakassovellusten, kuten puhelinten ja muiden kannettavien laitteiden kanssa sekä selainpohjaisissa sovelluksissa, joita käytetään monelta aikavyöhykkeeltä.

UTC-aikaa käyttävä palvelinsovellus on helppo asentaa tai siirtää toiselle aikavyöhykkeelle. Palvelinsovellus toimii oikein eikä käyttöympäristön aikavyöhyke sotke aikaleimoja suuntaan tai toiseen. Mikäli sovellusta käytetään monikansallisessa ympäristössä, käyttöympäristö voi hyvinkin vaihtua toiseen maahan sovelluksen elinkaaren aikana.

UTC-ajan käyttö helpottaa sovelluksen testaamista. Kesä- ja talviajan vaihtumiset aiheuttavat usein yllättäviä tilanteita, mikäli testaamista ei ole suoritettu huolella. UTC-ajan käyttäminen sovelluksen sisällä ei poista kesä- ja talviajan vaihtumisen testaamisen tarvetta käyttöliittymästä, mutta muualta järjestelmästä ongelman pitäisi hävitä.

Olen nähnyt useita projekteja, joissa aikavyöhykeongelmien tai kesä- ja talviaikaongelmien huomioiminen tai huomiotta jättäminen projektin alussa on tuottanut ongelmia projektin edetessä tai vasta tuotantokäytössä.

Eräässä järjestelmässä samaa palvelinsovellusta ajettiin yhden maan sisällä kahdessa eri paikassa. Koska kyseessä oli suuri maa ja nämä kaksi eri paikkaa sijaitsivat eri aikavyöhykkeillä, eivät sovelluksen eri instanssit toimineet keskenään suunnitellusti. Ongelma johtui siitä, että molemmat instanssit toimivat eri aikavyöhykkeillä, eikä tätä oltu mitenkään huomoioitu sovellusten välisessä tiedonsiirrossa. Vastaavia ongelmia ei olisi ilmennyt, mikäli koko järjestelmä olisi toiminut UTC-ajassa.

Toisessa järjestelmässä palvelinsovellus toimi yhdellä aikavyöhykkeellä ja palvelimen asiakkaat ympäri maailmaa. Järjestelmässä ei oltu huomioitu toimimista usealla aikavyöhykkeellä, vaikka se oli keskeistä järjestelmän toiminnan kannalta. Järjestelmän sisällä aikaleimat olivat aluksi käytännössä satunnaisia, koska aikavyöhyketietoja ei ollut käytettävissä. Ongelmia ei toki olisi ollut, mikäli sovelluksen rajapinnoissa olisi välitetty aikavyöhyketieto ja rajapintojen takana olisi tehty sopivia muunnoksia. Näin kansainvälisessä sovelluksessa olisi kuitenkin kannattanut käyttää palvelimella pelkästään UTC-aikaa, koska aikaleimojen muuttaminen paikalliseen aikaan ei olisi kuitenkaan hyödyttänyt ylläpitäjiä. Asiakassovelluksien käyttämä aika olisi ollut hyödyllistä pitää myös UTC-aikana, koska käyttäjän oletettiin liikkuvan maasta toiseen, jolloin käyttäjän päätteenkin aikavyöhykkeen voi olettaa vaihtuvan. Lisäksi kaikkien osapuolten käyttäessä UTC-aikaa sisäisesti ja rajapinnoissaan ei minkään osapuolen tarvitsisi tehdä aikavyöhykemuunnoksia kuin korkeintaan käyttöliittymässä.

Kolmantena esimerkkinä vielä tilanne, jossa palvelinsovelluksen tiedetään pysyvän yhdellä aikavyöhykkeellä eikä sen sijaintia aiota ikinä vaihtaa toiselle aikavyöhykkeelle. Vaikka mielestäni on rohkeata olettaa, että hyödyllistä sovellusta ei ikinä siirretä muualle eikä siihen tule ulkoisia kansainvälisesti käytettäviä rajapintoja, tämä lienee yleisin argumentti paikallisen ajan käyttämiseen sovelluksen sisällä. Mielestäni kuitenkin UTC-ajan käyttäminen tällaisessakin tapauksessa on hyödyllistä, koska UTC-aikavyöhykkeen käytöllä vältetään ongelmat kesä- ja talviajan kanssa. Esimerkiksi ajastuksia käytettäessä täytyy huomioida, että talviaikaan siirryttäessä kellonaika siirtyy tunnilla taaksepäin. Jos talviaikaan siirtymistä ei huomioi, saattaa käydä niin, että ajastettu operaatio suoritetaan kahdesti ja tästä aiheutuu virhetoimintaa.

Vaikka järjestelmä toimii sisäisesti UTC-ajassa, pitää käyttäjän edelleen käyttää järjestelmää omassa ajassaan ja huomoioida sovelluksen käyttö halutussa aikavyöhykkeessä. Jos esimerkiksi sovelluksen tarjoaman palvelun laskutus riippuu kellonajasta siten, että osa päivästä on halvempaa kuin muuten, pitää käyttäjän luonnollisesti konfiguroida sovelluksen laskutus nimenomaan haluttuun aikavyöhykkeeseen nähden. Jos käytössä on kaksi instanssia sovelluksesta kahdessa eri aikavyöhykkeessä, kuten aiemmassa esimerkissä, pitää olla erityisen tarkkana, että palvelun laskutus toimii molemmissa aikavyöhykkeissä perustuen oikeaan aikaan. Jos kahden instanssin järjestelmässä olisi erillinen laskutuspalvelu ja molemmista instansseista tulisi UTC-aikaleimoja, pitäisi laskutuspalvelun kohdella erillisiä instansseja kuitenkin eri tavalla, vaikka kaikki käyttäisivät UTC-aikaa. Aikavyöhykkeet aiheuttavat siis ongelmia, vaikka sovelluksessa olisi olisi käytössä sisäisesti vain yksi aikavyöhyke.

Mitä haittaa on UTC-ajan käyttämisestä sovelluksen sisällä? Ensinnäkin useimmiten kaikki järjestelmät toimivat oletuksena paikallisessa ajassa. Aluksi on työlästä selvittää ja huomioida eri järjestelmien konfigurointi UTC-aikaan tai järjestää yhteensopivuuskerros, joka paikallisen aikavyöhykkeen huomioi. Sovelluksen ylläpitäjän täytyy mahdollisesti oppia huomioimaan se, että aikaleimat ovat lokeissa ja tietovarastoissa UTC-ajassa. Mikäli järjestelmässä mahdollisesti olevat ajastukset konfiguroidaan UTC-ajassa, ylläpitäjän pitää tämäkin huomioida konfigurointeja tehdessään.

Itse rinnasta aikavyöhykeongelman kaikille tuttuun merkistön koodausongelmaan. Useimmiten merkistöongelmat ratkeavat, kun sovellus käyttää mahdollisimman useassa kohtaa vain ja ainoastaan UTF-8 -koodausta. Tästä on helppo tehdä tarvittavat muunnokset ja toisaalta järjestelmän on helpompaa toimia sisäisesti vain yhdessä merkistökoodauksessa. Analogisesti järjestelmän on helpompaa toimia sisäisesti vain yhdessä aikavyöhykkeessä ja konversiot tehdään vai järjestelmän ulkopuolisia tahoja varten, joista yksi on loppukäyttäjä.

Ajan kanssa tulee olla joka tapauksessa tarkkana, sillä varsinkin viranomaiskäytössä sekä esimerkiksi laskutuksessa väärä aikaleima voi merkittävästi vaikuttaa palvelun oikeellisuuteen.

maanantai 23. tammikuuta 2012

lauantai 21. tammikuuta 2012

Tomcat clustering and session persistency in AWS


Tomcat supports clustering out-of-the-box (see http://tomcat.apache.org/tomcat-7.0-doc/cluster-howto.html). However, since Amazon EC2 environment has some pecualiar features such as not supporting IP multicasting (http://aws.amazon.com/vpc/faqs/#R4), the traditional(?) way of Tomcat clustering cannot be used. Instead of using direct communication between different cluster nodes, you can use HTTP session sharing via database. Although this is not as fast or flexible as direct communication, it's still better than no clustering.

How session sharing works

Tomcat copies session data to a database when it's shut down. It also makes a periodical copying of idle sessions every now and then (the configuration given below doesn't define the exact maximum session idle time before Tomcat does replication to database. The configured maximum time is about maxIdleBackup + processExpiresFrequency * engine.backgroundProcessorDelay. Google for more details...).

Because all the Tomcats are sharing a commong session repository i.e. database, they all have access to each other's sessions that have been persisted at least once. Whenever a new Tomcat instance starts, it reads the existing sessions from common session repository and is then able to serve any request to any Tomcat in the cluster up to start-up time of the instance.

Because of delays in sharing the sessions, you should enable sticky sessions in you load balancer. This means that each request from a user will be directed to the same Tomcat as long as user HTTP session exists.

Use application generated cookie in ELB

In order to use database session sharing, you must use sticky sessions in your load balancer. Amazon ELB has the options of using either ELB generated session cookie or application generated session cookie. I think that in this case one should use application generated session cookie (JSESSIONID), because ELB generated session cookie may have different expiration time than your application's session. If ELB cookie expires before you application's session, user may get served by different Tomcat, although application's session has not changed. This may lead into nasty problems.

Setup RDS based database for Tomcat session storing

The easiest way of using a common session replication database in Amazon is RDS service. Of course you may also set up your own database on one EC2 instance, but this would be a single point of failure. So, I'm using RDS and MySQL in this example. Create a session database and a session table in the database.
CREATE DATABASE tomcat_data DEFAULT CHARACTER SET UTF8 COLLATE utf8_swedish_ci;
GRANT ALL ON tomcat_data.* TO "tomcat"@"%" IDENTIFIED BY "tomcat";


create table tomcat_data.tomcat_sessions (
  session_id varchar(100) not null primary key,
  valid_session char(1) not null,
  max_inactive int not null,
  last_access bigint not null.
  app_name varchar(255),
  session_data mediumblob,
  KEY kapp_name(app_name)
);

MySQL connection from Tomcat

You need to copy the mysql-connector-java-x.x.xx.jar under $CATALINA/lib (on Ubuntu, it's /usr/share/tomcat6/lib/ depending on your Ubuntu/Tomcat version). This JAR is needed to create connection from Tomcat to MySQL.

Here's context.xml you need. Remember to change localhost to something more meaningful in the XML file.

<Context>

...

    <!-- The idle time in seconds before persisting session is
         maxIdleBackup + processExpiresFrequency * engine.backgroundProcessorDelay.
         The backgroundProcessorDelay is around 10 seconds. This configuration should make Tomcat
         to persist sessions after 20 seconds of idling and hold them in memory as long as each
         instance wants -->
    <Manager className="org.apache.catalina.session.PersistentManager"
      saveOnRestart="true"
      minIdleSwap="-1"
      maxIdleSwap="-1"
      maxIdleBackup="20"
      processExpiresFrequency="1">
      <Store className="org.apache.catalina.session.JDBCStore"
        connectionURL="jdbc:mysql://localhost/tomcat_data"
        driverName="com.mysql.jdbc.Driver"
        connectionName="tomcat"
        connectionPassword="tomcat"
        sessionIdCol="session_id"
        sessionValidCol="valid_session"
        sessionMaxInactiveCol="max_inactive"
        sessionLastAccessedCol="last_access"
        sessionTable="tomcat_sessions"
        sessionAppCol="app_name"
        sessionDataCol="session_data"
      />
    </Manager>
...

</Context>


Problems in this solution

If Tomcat dies unexpectedly, the new sessions served by dead instance are lost. Most likely with the configuration above, sessions newer than 30 seconds will disappear. Also new changes that have been done to session data will be lost.

Session related database traffic could grow quite high in a busy web site. I have no cure for this, but of course you can at least use a different database for sessions than what's being used for other data.

maanantai 16. tammikuuta 2012

Versioi tietokantasi!


Mitä on tietokannan versiointi? Tietokannan versiointi tarkoittaa sitä, että tietokannasta pystyy selvittämään helposti tietokannan skeeman version sekä mahdollisesti tietokantaan skripteillä ajetun datan version.

Mitä hyötyä on tietokannan versioinnista? Tietokannan versiointi mahdollistaa tietokannan sisältämän tiedon systemaattisen ylläpidon. Saman tietokannan eri versioiden päivittäminen uudempaan versioon on helppoa, mikäli tietokannan sisällöstä voi koska tahansa päätellä mitä päivityksiä tietokannasta puuttuu. Versioiminen mahdollistaa päivityspolkujen rakentamisen. Jos et tiedä missä tilassa tietokanta on, et voi mitenkään toteuttaa tietokantapäivityksien automaatiota, eivätkä käsin tehtävät päivityksetkään ole helppoja.

Jotta versiointia voi tehdä, pitää tietokantaan lisätä tietokannan versiota kuvaava taulu. Kenties yksinkertaisin tapa tietokannan versioimiseen on sisällyttää tietokantaa päivittäviin komentosarjoihin alkuun tietokannan versiotaulua päivittävä komento. Jokainen komentosarja voi lisätä versiotauluun rivin, jossa kerroataan komentosarjan sisältävän tiedoston nimi ja sen yksikäsitteisesti tunnistava versionumero. Huonoa ratkaisussa on se, että versiotaulun päivitys unohtuu helposti ja komentoja ajetaan ilman versiotiedon päivittämistä.

Hieman edistyneempi ratkaisu on luoda tietokannan päivityksiä varten oma sovelluksensa. Sovellus voi olla pelkkä komentotulkin varassa toimiva käskysarja. Kun sovellusta käsketään päivittämään tietokanta, sille annetaan versionhallinnassa oleva tiedosto parametrina. Sovellus hakee versionhallinnasta tiedoston, tarkistaa että tiedosto sopii päivitykseksi kohdetietokannalle, päivittää tietokannan sisällön tiedoston mukaan ja lopuksi muuttaa tietokannan versiotaulun ajan tasalle. Sovelluksen käyttäminen jättää tietokantaa päivittävälle ihmiselle vähemmän erehtymisen mahdollisuuksia.

Kehitystyössä on hankalaa toimia usean erillisen tietokantaa eri versioihin päivittävän tiedoston kanssa. Yleensä on helpompaa, jos kehittäjällä on käytössä yksi (tai muutama) tiedosto, jolla voi alustaa tyhjän tietokannan kehityskäyttöön sopivaksi ilman pitkän päivitysketjun seuraamista. Kehitystyön aikana onkin järkevää ylläpitää yksinkertaista tapaa luoda ajantasainen tietokanta. Kun projekti etenee uuden version julkaisuvaiheeseen, tutkitaan minkälainen ero on edellisen julkaisun ja uuden julkaisun tietokantojen kesken. Tämän pitäisi olla helppoa, mikäli edellisen version tuottamasta tietokannasta voi tarkistaa helposti version (versionhallinnasta sitten näkee edellisen version tietokannan luovat komennot). Sovelluksen päivittäminen tuoreeseen versioon tuotannossa on turvallista, koska sovelluksen päivityksessä voidaan helposti tarkistaa, että sovelluksen mukana päivitettävä tietokanta on tietyn versioinen. Erityisen tärkeitä tarkistukset ovat tilanteessa, jossa tuotantokäytössä voi olla samaan aikaan useaa eri versiota samasta sovelluksesta. Jos tuotannon versiota ei tarkisteta ennen päivitystä ja uusin versio vaatii useamman päivitystiedoston käyttämistä, saattaa joku päivityspolun vaatimista päivityksistä jäädä tekemättä ja esimerkiksi vain viimeisin päivitys tulee ajettua tietokantaan.

Usein tietokannan skeeman voi tuottaa suoraan lähdekoodista. Itse en suosittelisi tietokannan automaattista luomista kehityskäytössä lähdekoodin perusteella. On parempi, että kehitysaikanakin tietokannan skeeman kehitys hallitaan versionhallintaan talletettavien tiedostojen kautta. Lähdekoodin muutokset voivat joskus varsin yllättäen vaikuttaa siitä generoitavaan skeemaan ja toisaalta versionhallinasta voidaan koko ajan seurata skeeman kehitystä koodiin verrattuna. Jos testaus suoritetaan aina versionhallinassa olevaa skeemaa vastaan, jäävät koodin toimintaan vaikuttavat muutokset kiinni jo kehitysaikaisessa testauksessa, koska versionhallinasta tuleva skeema ei muutu automaattisesti lähdekoodin mukaiseksi.

Mitä haittaa on tietokannan versioinnista? Tietokannan versioinnista ei ole mitään suoranaista haittaa, mutta jonkin verran lisätyötä versiointi vaatii. Työmäärä on luultavasti kuitenkin vähäisempi kuin myöhemmin epämääräisistä tietokantaversioista huolehtimiseen käytettävä aika. Ainakin riskit epäonnistuneisiin tietokantapäivityksiin pienentyvät. Lisäksi tietokantaa käyttävän sovelluksen päivitykset helpottuvat, kun aina voidaan sanoa mitä tietokantapäivityksiä kunkin version asentaminen vaatii.

Jos projektisi tietokantaa ei ole vielä versioitu, juuri nyt on hyvä aika aloittaa versiointi!

sunnuntai 1. tammikuuta 2012

Amazonin Virtual Private Cloud ja NAT


Tavoitteena luoda kuvan mukainen suljettu verkko (Virtual Private Cloud, VPC), johon on kytketty kuormantasaaja (ELB) ulkoa tulevan liikenteen ohjaukseen ja jos pääsee internetiin vain NAT-koneen kautta. 

Kuvasta näkee, että aliverkko 10.0.1.0/24 on yksityinen verkko, josta ei pääse ulos kuin NAT-koneen kautta. Verkko 10.0.2.0/24 puolestaan on julkinen verkko, jonne sijoitetaan ELB, internet gateway ja NAT-kone. On syytä huomata (jotain mitä en itse heti tajunnut), että ELB:llä on kaksoisrooli internetissä sekä VPC:ssä: ELB näkyy internetiin julkisella IP:llä ja VPC:n sisällä puolestaan VPC:n IP:llä. ELB:n pitää olla VPC:ssä, josta pääsee IGW:n kautta ulos tai ELB ei toimi. Koska NAT-koneelle on annettu julkinen Elastic IP, sekin näkyy ulos suoraan internetiin.

Tekstikuvaus ei johda ihan suoraviivaisesti kuvan tilanteeseen, joten kannattaa lukea huolella, jos jotain kohtaa ajattelee hyödyntää.

Huomaathan, että tämän tekstin ohjeiden kokeilu maksaa! Itselleni kokeilu kustansi $0,93, koska käytin kahta small-instanssia 4 tunnin ajan.

VPC-verkon luominen yhdellä aliverkolla 
Luodaan VPC 10.0.0.0/16 CIDR-lohkolla (Classless Inter-Domain Routing).
$ ec2-create-vpc 10.0.0.0/16
VPC vpc-1cb6db75 pending 10.0.0.0/16 dopt-10b6db79 default

Luodaan aliverkko 10.0.1.0/24. Aliverkossa on 256 IP-osoitetta, joista Amazon varaa 4 ensimmäistä ja viimeisen eli käytettäväksi jää 251 IP-osoitetta.
$ ec2-create-subnet --vpc vpc-1cb6db75 -i 10.0.1.0/24
SUBNET subnet-f3b1dc9a pending vpc-1cb6db75 10.0.1.0/24 251 eu-west-1c

Aliverkko luotiin Amazonin valitsemaan saatavuusalueeseen (Availability Zone, AZ) eu-west-1c.


Internet-yhdyskäytävän luominen VPC:lle 
VPC:ssä olevilla koneilla ei ole pääsyä internetiin ilman, että niille on luotu internet-yhdyskäytävä (Internet gateway, IGW). Luodaan VPC:lle yhdyskäytävä ja liitetään se VPC:hen.
$ ec2-create-internet-gateway
INTERNETGATEWAY igw-ef86eb86
$ ec2-attach-internet-gateway igw-ef86eb86 --vpc vpc-1cb6db75
ATTACHMENT vpc-1cb6db75 attaching

AWS on luonut automaattisesti reititystaulun. Katsotaan taulun tiedot.
$ ec2-describe-route-tables 
ROUTETABLE rtb-1eb6db77 vpc-1cb6db75
ROUTE local  active 10.0.0.0/16
ASSOCIATION rtbassoc-11b6db78 main

Lisätään IGW reititystauluun.
$ ec2-create-route rtb-1eb6db77 -r 0.0.0.0/0 --gateway igw-ef86eb86 
ROUTE igw-ef86eb86   0.0.0.0/0

EC2-instanssin luominen aliverkkoon
Luodaan aluksi pääsyoikeusryhmä, jolla sallitaan pääsy VPC:ssä oleviin koneisiin. Huomaa, että pääsyoikeusryhmä on VPC-kohtainen. Määritetään samalla SSH-portti (22) ja HTTP-portti (80) aukinaisiksi tässä ryhmässä.
$ ec2-add-group vpc_public_access -d "VPC access from public Internet" -vpc vpc-1cb6db75
$ ec2-authorize sg-06acb16a -P tcp -p 22 -s 0.0.0.0/0
GROUP sg-06acb16a    
PERMISSION   ALLOWS tcp 22 22 FROM CIDR 0.0.0.0/0 ingress
$ ec2-authorize sg-06acb16a -P tcp -p 80 -s 0.0.0.0/0
GROUP sg-06acb16a    
PERMISSION   ALLOWS tcp 80 80 FROM CIDR 0.0.0.0/0 ingress

Luodaan aliverkkoon 32-bittinen Ubuntu 10.04-palvelin (AMI löytyy täältä: http://uec-images.ubuntu.com/releases/lucid/release/), jolla pyörii Apache. Käytetään palvelimen luonnissa aiemmin tehtyä (http://pedanttinen.blogspot.com/2011/12/amazon-web-services-ilmainen-kokeilu_29.html) testkeypair-avainta. Annetaan palvelimelle aliverkon ensimmäinen vapaa osoite.
$ ec2-run-instances ami-c00e3cb4 --instance-count 1 --instance-type m1.small --key testkeypair --group sg-06acb16a --subnet subnet-f3b1dc9a --private-ip-address 10.0.1.4
RESERVATION r-3eb35377 093338577702 
INSTANCE i-8cb527c5 ami-c00e3cb4   pending testkeypair 0  m1.small 2011-12-30T08:54:45+0000 eu-west-1c aki-4deec439   monitoring-disabled  10.0.1.4vpc-1cb6db75 subnet-f3b1dc9a ebs     paravirtual xen  sg-06acb16a default

Kuten yltä nähdään, luotu instanssi on tyyppiä small. Small-tyyppinen palvelin maksaa Euroopassa $0,095 tunnilta. Small-tyyppinen palvelin on 32-bittinen ja siksi myös käyttöjärjestelmäksi valittiin 32-bittinen versio.

Tässä vaiheessa luodulle koneelle ei pääse mistään käsiksi. Sille pitää siis antaa hetkeksi julkinen IP-osoite eli VPC Elastic IP -osoite. Huomaa, että myös VPC Elastic IP on maksullinen, mikäli sitä ei ole sidottu mihinkään ajossa olevaan instanssiin ($0.01 tunnilta, jos IP ei ole käytössä kokonaisen tunnin aikana)!
$ ec2-allocate-address -d vpc
ADDRESS 176.34.130.141  vpc eipalloc-d186ebb8
$ ec2-associate-address --allocation-id eipalloc-d186ebb8 --instance i-8cb527c5
ADDRESS  i-8cb527c5 eipalloc-d186ebb8 eipassoc-b486ebdd

Nyt voidaan tehdä yhteyskokeilu palvelimelle SSH:lla.
$ ssh -i ~/aws/certs/testkeypair_private.key ubuntu@176.34.130.141
Jos kaikki meni putkeen, luotu instanssi vastaa huuteluihin. 

Apachen virittäminen instanssille
Kirjaudu sisään instanssille em. komennolla. Jostain syystä nimipalvelu antaa 10-verkon osoitteita Ubuntun päivityspalvelimen osoitteeksi:
$ nslookup eu-west-1.ec2.archive.ubuntu.com
Server:  217.30.180.230
Address: 217.30.180.230#53

Non-authoritative answer:
Name: eu-west-1.ec2.archive.ubuntu.com
Address: 10.228.171.176
Name: eu-west-1.ec2.archive.ubuntu.com
Address: 10.226.230.15

$ nslookup archive.ubuntu.com
Server:  217.30.180.230
Address: 217.30.180.230#53

Non-authoritative answer:
Name: archive.ubuntu.com
Address: 91.189.92.183
...

Tämän takia tehdään "pieni" säätö instanssin hosts-tiedostoon, että päästään sujuvasti eteenpäin. Lisää alla oleva rivi.
91.189.92.183 eu-west-1.ec2.archive.ubuntu.com

Aja seuraavat komennot.
$ sudo apt-get update
$ sudo apt-get install apache2

Nyt Apachen pitäisi vastata osoitteesta http://176.34.130.141/. 

Kuormantasaajan luominen
Luodaan kuormantasaaja (ELB), jonka kautta pitäisi Apachen tarjoilemat sivut näkyviin.
elb-create-lb vpc-lb --subnets subnet-f3b1dc9a --groups sg-06acb16a --listener "protocol=HTTP, lb-port=80, instance-port=80"
elb-create-lb:  Service error: InvalidSubnetID.NotFound 
 AWSRequestId:690642d8-32db-11e1-a58c-ab42d741c3e0

Jostain syystä yllä oleva komento epäonnistuu, mutta täsmälleen sama onnistuu AWS Management Consolen kautta ilman ongelmia. Luodaan kuormantasaaja em. parametrein AWS Management Consolesta (ohjeita täällä: http://docs.amazonwebservices.com/ElasticLoadBalancing/2011-11-15/DeveloperGuide/USVPC_creating_basic_lb.html). Hassua kyllä, netistä ei löytynyt mitään tietoa miksi komentoriviltä homma ei luonnistu.

Kuormantasaajan käynnistymisessä saattaa mennä jonkin aikaa. Joskus instanssit eivät näy heti ELB:llä ja ELB vain sanoo "instance registration is still in progress". Kun kuormantasaaja toimii, näkyy Apache kuormantasaajan läpi: http://vpc-lb-ui-1608557811.eu-west-1.elb.amazonaws.com/.

Palvelu näkyy kuormantasaajan kautta, vaikka Ubuntu-instanssilta poistaisi julkisen IP:n. Kuormantasaajan kautta voi siis päästä käsiksi VPC:n sisälle, vaikka sinne ei muutoin olisi julkista pääsyä.

Ulospäin menevän liikenteen ohjaus NAT:n kautta
Luodaan ensiksi Amazonin tarjoaman VPC NAT AMI:n pohjalta uusi instanssi.
$ ec2-run-instances ami-095b6c7d --instance-count 1 --instance-type m1.small --key testkeypair --group sg-06acb16a --subnet subnet-f3b1dc9a --private-ip-address 10.0.1.5
RESERVATION r-a69272ef 093338577702 
INSTANCE i-3c5dcf75 ami-095b6c7d   pending testkeypair 0  m1.small 2011-12-30T12:15:36+0000 eu-west-1c aki-4deec439   monitoring-disabled  10.0.1.5 vpc-1cb6db75 subnet-f3b1dc9a ebs     paravirtual xen  sg-06acb16a default

Poistetaan SrcDestCheck-attribuutti NAT-instanssilta.
$ ec2-modify-instance-attribute --source-dest-check false i-3c5dcf75
sourceDestCheck i-3c5dcf75 false

Siirretään aiemmin luotu julkinen IP-osoite NAT-instanssille.
$ ec2-describe-addresses
ADDRESS 176.34.130.141 i-8cb527c5 vpc eipalloc-d186ebb8 eipassoc-979cf1fe
$ ec2-disassociate-address --association-id eipassoc-979cf1fe
ADDRESS    eipassoc-979cf1fe
$ ec2-associate-address --allocation-id eipalloc-d186ebb8 --instance i-3c5dcf75
ADDRESS  i-3c5dcf75 eipalloc-d186ebb8 eipassoc-2899f441

Kirjaudutaan sisään NAT-instanssille. SSH valittaa todennäköisesti tässä kohtaa vastapään tunnisteen vaihtuneen. Ongelma ratkeaa .ssh/known_hosts-tiedostoa editoimalla.
ssh -i ~/aws/certs/testkeypair_private.key ec2-user@176.34.130.141

Tämän jälkeen voi kopioida testkeypair_private.key-tiedoston NAT-koneelle ja tehdä seuraavan tempun.
[ec2-user@ip-10-0-1-5 ~]$ ssh -i testkeypair_private.key ubuntu@10.0.1.4

Aliverkon sisällä liikenne toimii siis ok.

NAT-aliverkon luominen
Luodaan uusi aliverkko NAT:lle.
$ ec2-create-subnet --vpc vpc-1cb6db75 -i 10.0.2.0/24
SUBNET subnet-249af74d pending vpc-1cb6db75 10.0.2.0/24 251 eu-west-1c

Poistetaan vanha NAT-kone ja tehdään uusi tilalle uuteen aliverkkoon.
$ ec2-terminate-instances i-3c5dcf75
INSTANCE i-3c5dcf75 running shutting-down
$ ec2-run-instances ami-095b6c7d --instance-count 1 --instance-type m1.small --key testkeypair --group sg-06acb16a --subnet subnet-249af74d --private-ip-address 10.0.2.4
RESERVATION r-189c7c51 093338577702 
INSTANCE i-3648da7f ami-095b6c7d   pending testkeypair 0  m1.small 2011-12-30T13:10:09+0000 eu-west-1c aki-4deec439   monitoring-disabled  10.0.2.4 vpc-1cb6db75 subnet-249af74d ebs     paravirtual xen  sg-06acb16a default

Poistetaan IGW 1-verkon reititystaulusta.
$ ec2-describe-route-tables 
ROUTETABLE rtb-1eb6db77 vpc-1cb6db75
ROUTE local  active 10.0.0.0/16
ROUTE igw-ef86eb86  active 0.0.0.0/0
ASSOCIATION rtbassoc-11b6db78 main
$ ec2-delete-route rtb-1eb6db77 --cidr 0.0.0.0/0
RETURN true

Lisätään NAT-kone 1-verkon reititystauluun.
$ ec2-create-route rtb-1eb6db77 -r 0.0.0.0/0 --instance i-3648da7f
ROUTE  i-3648da7f  0.0.0.0/0

Luodaan uusi reititystaulu uudelle aliverkolle.
$ ec2-create-route-table vpc-1cb6db75
ROUTETABLE rtb-cd95f8a4 vpc-1cb6db75
ROUTE local  active 10.0.0.0/16

Lisätään uuteen reititystauluun aiemmin luotu IGW.
$ ec2-create-route rtb-cd95f8a4 -r 0.0.0.0/0 --gateway igw-ef86eb86 
ROUTE igw-ef86eb86   0.0.0.0/0

Liitetään uusi reititystaulu uuteen aliverkkoon.
$ ec2-associate-route-table rtb-cd95f8a4 --subnet subnet-249af74d
ASSOCIATION rtbassoc-5f94f936 rtb-cd95f8a4 subnet-249af74d

Asetetaan NAT-koneelle julkinen IP.
$ ec2-associate-address --allocation-id eipalloc-d186ebb8 --instance i-3648da7f
ADDRESS  i-3648da7f eipalloc-d186ebb8 eipassoc-6894f901

Poistetaan SrcDestCheck-attribuutti NAT-instanssilta.
$ ec2-modify-instance-attribute --source-dest-check false i-3648da7f
sourceDestCheck i-3648da7f false

Nyt pitäisi olla mahdollista mennä NAT-koneen kautta julkisella IP:llä NAT-koneelle ja siitä suoraan Ubuntu-koneelle. Tämän jälkeen pitää vielä liikuttaa ELB julkiseen (uuteen) aliverkkoon eli samaan, missä NAT-kone asustaa. Kun tämä on tehty, pitäisi liikenteen kulkea myös kuormantasaajan kautta. Kuten aiemmin, uusi kuormantasaaja luodaan AWS Management Consolen kautta.

Lopuksi poistetaan kaikki käytetyt resurssit
$ ec2-terminate-instances i-8cb527c5 i-3648da7f
INSTANCE i-8cb527c5 running shutting-down
INSTANCE i-3648da7f running shutting-down
$ elb-delete-lb vpc-lb-ui
$ ec2-release-address --allocation-id eipalloc-d186ebb8 176.34.130.141
ADDRESS    eipalloc-d186ebb8
$ ec2-disassociate-route-table rtbassoc-5f94f936
RETURN true
$ ec2-delete-subnet subnet-249af74d
SUBNET subnet-249af74d
$ ec2-delete-subnet subnet-f3b1dc9a
SUBNET subnet-f3b1dc9a
$ ec2-delete-route-table rtb-cd95f8a4
RETURN true
$ ec2-delete-route rtb-1eb6db77 -cidr 0.0.0.0/0
RETURN true
$ ec2-detach-internet-gateway --vpc vpc-1cb6db75  igw-ef86eb86
RETURN true
$ ec2-delete-internet-gateway igw-ef86eb86
RETURN true
$ ec2-delete-group sg-06acb16a
RETURN true
$ ec2-delete-group sg-9facb1f3
RETURN true
$ ec2-delete-vpc vpc-1cb6db75
VPC vpc-1cb6db75
$ ec2-delete-volume vol-27d0c94e
VOLUME vol-27d0c94e
$ ec2-delete-volume vol-2bd6cf42
VOLUME vol-2bd6cf42

Kevyt klusterointi Amazon Web Servicessä



Kokeillaan rakentaa kahden weppipalvelimen klusteri yllä olevan kuvan mukaisesti ja käyttää Amazonin tarjoamaa kuormantasaajaa (ELB, Elastic Load Balancing) komentorivikomennoilla. Weppipalvelimena toimivat kaksi Apachea, jotka tarjoilevat molemmat yhtä sivua.

Klusterin palvelimien pystytys
Klusterin palvelimet voisi pystyttää yksitellen edellisen blogauksen ohjeiden perusteella. On kuitenkin mielenkiintoisempaa rakentaa image, jolta molemmat koneet käynnistetään.

Luodaan ensin yksi EC2-instanssi Ubuntu 10.04:n AMI:n pohjalta.

$ ec2-run-instances ami-cc0e3cb8 --instance-count 1 --instance-type t1.micro --key testkeypair --group public_access
RESERVATION r-adfdf5db 093338577702 public_access
INSTANCE i-14cc735d ami-cc0e3cb8 pending testkeypair 0 t1.micro 2011-12-04T16:59:46+0000 eu-west-1a aki-4feec43b monitoring-disabled ebs paravirtual xen sg-12ac4965 default

Asennetaan koneelle Apache-palvelin. Apachen asentaminen vaatii koneelle kirjautumista ja koneelle kirjautuminen edellyttää koneen nimen tietämistä, joten selvitetään nimi ensin.

$ ec2-describe-instances
RESERVATION r-adfdf5db 093338577702 public_access
INSTANCE i-14cc735d ami-cc0e3cb8 ec2-46-137-22-203.eu-west-1.compute.amazonaws.com ip-10-48-9-210.eu-west-1.compute.internal running testkeypair 0 t1.micro 2011-12-04T16:59:46+0000 eu-west-1a aki-4feec43b monitoring-disabled 46.137.22.203 10.48.9.210 ebs paravirtual xen sg-12ac4965 default
BLOCKDEVICE /dev/sda1 vol-7551591c 2011-12-04T17:00:04.000Z

Kirjaudutaan koneelle ja asennetaan Apache.

$ ssh -i certs/testkeypair_private.key ubuntu@ec2-46-137-22-203.eu-west-1.compute.amazonaws.com
$ sudo apt-get update
$ sudo apt-get install apache2

Todetaan selaimella, että Apache toimii eli mennään osoitteeseen http://ec2-46-137-22-203.eu-west-1.compute.amazonaws.com..

Tehdään luodusta Ubuntu-asennuksesta asennusmedia, josta on helppo luoda klusteriin uusia instansseja.

$ ec2-stop-instances i-14cc735d
INSTANCE i-14cc735d running stopping
$ ec2-create-image i-14cc735d --name "weppipalvelin" --description "Klusterin weppipalvelimien luomiseen image"
IMAGE ami-73073b07

Imagen olemassaolon voi vielä tarkistaa näin.

$ ec2-describe-images
IMAGE ami-73073b07 093338577702/weppipalvelin 093338577702 available private x86_64 machine aki-4feec43b ebs paravirtual xen
BLOCKDEVICEMAPPING /dev/sda1 snap-aaef70c2 8

Sitten pitäisi käynnistää klusterin koneet. Poistetaan ensin kuitenkin aiemmin luotu instanssi sotkemasta muuta säätöä.

$ ec2-terminate-instances i-14cc735d

Uusien koneiden käynnistäminen.

$ ec2-run-instances ami-73073b07 --instance-count 2 --instance-type t1.micro --key testkeypair --group public_access
RESERVATION r-2b89815d 093338577702 public_access
INSTANCE i-10bf0059 ami-73073b07 pending testkeypair 0 t1.micro 2011-12-04T18:09:57+0000 eu-west-1c aki-4feec43b monitoring-disabled ebs paravirtual xen sg-12ac4965 default
INSTANCE i-12bf005b ami-73073b07 pending testkeypair 1 t1.micro 2011-12-04T18:09:57+0000 eu-west-1c aki-4feec43b monitoring-disabled ebs paravirtual xen sg-12ac4965 default

Koneiden status käynnistyksen jälkeen.

$ ec2-describe-instance-status
INSTANCE i-10bf0059 eu-west-1c running 16
INSTANCE i-12bf005b eu-west-1c running 16

AWS antoi koneille DNS-nimet ec2-79-125-46-213.eu-west-1.compute.amazonaws.com ja ec2-46-137-144-163.eu-west-1.compute.amazonaws.com. Muutetaan jälkimmäisen koneen tiedostoa /var/www/index.html ja lisätään sinne tunnisteen "Kone 2", jotta koneet voi myöhemmin erottaa kuormantasaajan takaa.

Kuormantasaajan luominen
Aivan ensiksi pitää noutaa kuormantasaajan komentorivityökalut.

$ wget http://ec2-downloads.s3.amazonaws.com/ElasticLoadBalancing.zip

Paketti pitää purkaa ja sen jälkeen vielä editoida aiemmin luotua env.sh-tiedostoa, jotta ELB:n työkalut saadaan polkuun ja jotta ELB:n työkalut tietävät mistä löytyvät. Lisää siis seuraavat rivit.

export AWS_ELB_HOME=~/aws/ElasticLoadBalancing-1.0.15.1
export PATH=$PATH:$EC2_HOME/bin:$AWS_ELB_HOME/bin
export EC2_REGION=eu-west-1

ELB:n luonti.

$ elb-create-lb klusteri-lb --listener "protocol=HTTP, lb-port=80, instance-port=80" --availability-zones eu-west-1c
DNS_NAME klusteri-lb-828561786.eu-west-1.elb.amazonaws.com

Lisätään instanssit ELB:lle.

$ elb-register-instances-with-lb klusteri-lb --instances i-10bf0059,i-12bf005b
INSTANCE_ID i-12bf005b
INSTANCE_ID i-10bf0059

Selaimella voi käydä tarkistamassa, että kuormantasaaja heittelee pyyntöjä molemmille koneille: http://klusteri-lb-828561786.eu-west-1.elb.amazonaws.com.

Jälkien siivoaminen 
Lopuksi poistetaan kaikki varatut resurssit, jotta AWS ei lähettele laskuja perään.

$ elb-delete-lb klusteri-lb
$ ec2-terminate-instances i-12bf005b i-10bf0059

Luodun AMI:n poistaminen on karvan verran hankalampaa.

$ ec2-describe-snapshots
SNAPSHOT snap-aaef70c2 vol-7551591c completed 2011-12-04T17:22:54+0000 100% 093338577702 8 Created by CreateImage(i-14cc735d) for ami-73073b07 from vol-7551591c
$ ec2-deregister ami-73073b07
IMAGE ami-73073b07
$ ec2-delete-snapshot snap-aaef70c2
SNAPSHOT snap-aaef70c2

Voiko kahden koneen klusterin tekeminen mennä vielä paljon helpommaksi?