Glassfish 4.1, JAX-RS, Jersey, ja Jackson JSON-serializer

Jep, päivitin vähän servereitä Glassfish 4.1 versioon, ja vastaan tuli mielenkiintoinen bugi: JAX-RS palvelut lakkasivat toimimasta ja antoivat sensijaan kaikenlaisia herjoja, niistä ehkä mielenkiintoisin:

Severe: Error occurred when processing a response created from an already mapped exception.
Warning: StandardWrapperValve[com.qpr.entice.common.ApplicationConfig]: Servlet.service() for servlet com.mycompany.jaadajaada.ApplicationConfig threw exception
java.lang.ClassNotFoundException: com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector not found by com.fasterxml.jackson.jaxrs.jackson-jaxrs-json-provider [129]

Glassfish käyttää oletuksena Moxya JSON serialisointiin, mutta itse käytän Jacksonia suorituskyvyn ja ominaisuuksien johdosta. Mielenkiintoista kyllä Glassfish toimitetaan osittaisin Jackson kirjastoin joten sen käyttöönotto on niinkin helppoa kuin aktivoida se config tiedostossa, tähän tapaan:

import javax.ws.rs.ApplicationPath;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.server.ResourceConfig;

@ApplicationPath("rest")
public class ApplicationConfig extends ResourceConfig {

  public ApplicationConfig() {
    packages(true,
       "com.mycompany.jaadajaada",
       "com.wordnik.swagger.jersey.listing");
    register(JacksonFeature.class);
  }
}

Eli rekisteröidään JacksonFeature – ja samantien käytössä on Jackson kirjastot. Aiemmassa Glassfish 4.0:ssa tämä pelasi hienosti – mutta 4.1 antaa ylläolevaa virheilmoitusta. Kokeilin myös jättää JacksonFeaturen pois ja testata pelkkää Moxya mutta sieltä tuli ihan omat herjansa. (Tuossa muuten näkyy myös Swagger joka automaattisesti dokumentoi REST APIt – ja käyttää Jackson kirjastoja)

Mielenkiintoista virheilmoituksessa on, että se viittaa puuttuvaan tiedostoon joka itselläkin oli kirjastopoluissa – mutta käy ilmi kaksi asiaa: se pitää olla Glassfishin alla, ja sen pitää olla oikea versio. Tiedosto joka uupuu on jackson-module-jaxb-annotations-2.3.2.jar, sen pitäisi olla glassfishin alla kansiossa modules, ja tosiaan versionumero 2.3.2, ei uusin. Tämän päälle kun putsaa osgi-cachen ja buuttaa serverin, saa taas REST JSON palvelut takaisin. 😉

Eli aika spesifi pulma. Aika noloa että Glassfishistä on hiljalleen turvonnut kohtuullisen monoliittinen mötikkä jossa on paljon kirjastoriippuvuuksia joita oma sovellus ei pysty helposti ylikirjoittamaan. JBOSS muistuu mieleen äärimmäisen modularisuutensa ansiosta, mutta muistelen kyllä sielläkin olleen aina uuden julkaisun kohdalla kirjastohaasteita. Mutta siellä on esim. web services implementaatio mukavan helposti vaihdettavissa.

Mutta, minä taidan digressoida 😉 Tässä joka tapauksessa virheilmoitus, ja korjaus, ja tuossa linkkiä keskusteluun samasta pulmasta:

https://java.net/jira/browse/GLASSFISH-21141

Teoriassa on mahdollista tehdä korjaus siistimminkin, autodeploy/bundles kansioon asennettavana paikkana joka ei muuta serverin toimintaa, mutta itse en saanut sitä vaihtoehtoa toimimaan.

Mainokset

Glassfish remote deployment

Olen viritellyt Glassfish serveriklusterin deploymentin parissa. Ideana on saada etäkoneelta buildi, esim. Jenkins tai kehittäjäkone, tipauttamaan nätisti buildin tuotokset serverille jossa niitä voidaan testata ja demoilla.

Tähän on moniakin vaihtoehtoja:

– Glassfish serverissä on autodeploy-kansio, eli jos sinne siirtää tiedoston esim. scp:llä tai ftp:llä, se nousee itsestään pystyyn

– Deployn voi tietysti tehdä käsin, glassfish hallintanäyttöjen kautta

– Cargo plugin on maineikas sen suhteen, että se osaa asennella monellekin eri serverille kun vain kerrotaan miten

– Asadmin työkalu toimii komentoriviltä, se kykenee myös asentamaan tuotoksia

– Joku muu jonka olen unohtanut :p

Tuosta listasta kokeilen ensiksi Cargo pluginia. Se vaikutti muuten lupaavalta paitsi ei toiminut ollenkaan. Ilmeisesti pulmia teeman deploy/redeploy välillä, ja monessa lähteessä mainittu force=true ei tuntunut auttavan. Ongelma siis että jos serverillä ei ole sovellusta, pitäisi tehdä deploy, myöhemmillä kerroilla taas redeploy. En saanut tätä kuitenkaan toimimaan. Sain kivoja nullpointer exceptioneitä Glassfish nelosen kanssa.

Noista tiedostopohjaisista kujeista en ole suunnattoman innostunut, eli käytän niitä vasta jos on pakko. Tiedostojen kanssa on ongelmia mm. lukituksen/säikeiden ja transaktionaalisen eheyden kanssa siirrossa, sekä jälkien putsauksen kanssa.

Asadmin työkalua tuli käyteltyä ja virittelin sen päälle maven exec tehtävän joka osaa sitä ajaa. En ole vielä aivan tyytyväinen vakauteen, mutta toistaiseksi riittävä ratkaisu.

Asadminilla onnistuu .war paketin asennus verkon yli serverille esim. näin:

c:\glassfish4\bin\asadmin deploy --host devserver.mycompany.com --force=true --availabilityenabled=true --target cluster1 --virtualservers server c:\projects\MyProject\target\MyDeliverable.war

Tämä komento hoitaa myös redeployn. Tämä kuitenkin edellyttää että komennon ajavalla käyttäjällä on salasanatiedot jo talletettuna profiiliinsa. Saamme tästä hieman geneerisemmän näin:

c:\glassfish4\bin\asadmin --user admin --passwordfile c:\glassfish4\password --host devserver.mycompany.com deploy  --force=true --availabilityenabled=true --target cluster1 --virtualservers server  c:\projects\MyProject\target\MyDeliverable.war

Tässä koukataan salasanat salasanatiedostosta, joka on aluksi Glassfish serverin kansiossa (paikallinen asennus, jotta saamme asadmin työkalun käyttöön). Sen voi suojata haluamallaan tavalla.

Tältä näyttäisi mavenin exec plugin:

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>exec-maven-plugin</artifactId>
  <version>1.3.1</version>
  <configuration>
    <executable>${glassfish.asadmin.executable}</executable>
    <arguments>
      <argument>--user</argument>
      <argument>${glassfish.user}</argument>
      <argument>--passwordfile</argument>
      <argument>${glassfish.password.file}</argument>
      <argument>--host</argument>
      <argument>${glassfish.remote.host}</argument> 
      <argument>deploy</argument> 
      <argument>--force=true</argument>
      <argument>--availabilityenabled=true</argument>
      <argument>--target</argument>
      <argument>cluster1</argument>
      <argument>--virtualservers</argument>
      <argument>server</argument>
      <argument>${project.build.directory}/${project.build.finalName}.war</argument>
    </arguments>
  </configuration>
  <executions>
    <execution>
      <goals>
        <goal>exec</goal>
      </goals>
    </execution>
  </executions>
</plugin>

 

Ja tältä näyttävät tuossa käytetyt propertyt:

<glassfish.asadmin.executable>c:/glassfish4/bin/asadmin.bat</glassfish.asadmin.executable>
 <glassfish.user>admin</glassfish.user>
 <glassfish.password.file>${basedir}/password</glassfish.password.file>
 <glassfish.remote.host>devserver.mycompany.com</glassfish.remote.host>

Ja propertythän voi taas pistää profiilien alle, samoin kuin muutenkin exec konfiguraatiot, eli näillä voi tarvittaessa joka devaaja tai joka ympäristö asennella eri serverille eri tiedoilla.

JAX-RS ja Swagger: Helposti dokumentoity REST API

Hetken aikaa jo on tykyttänyt mietintämyssyn alla miten REST rajapintoja voisi helposti dokumentoida – WSDL kun sieltä uupuu (Ja WADL:ia ei kukaan halua). Kuitenkin pitäisi pystyä kertomaan ja rekisteröimään missä osoitteissa on palvelua, mitä ne tekevät, miten niitä kutsutaan, jne, muutenkin kuin testeillä ja esimerkeillä.

Törmäsin mukavaan jersey-yhteensopivaan laajennukseen nimeltä jersey-swagger. Sovitin tätä Glassfishin versioon, jossa pyörii Jersey 2, ja sainkin jotain aikaan. Tässä muistiinpanoja:

Käyttöönotto vaatii muutaman askelen: Ensiksi täytyy Mavenissä tai Gradlessa ottaa käyttöön swagger jersey versiolle kaksi. Samalla on tuikitärkeää määrittää pari exclusionia, tai serverille päätyy duplikaatteja hieman eri versioista ja tulee mukavia poikkeuksia logista. Tässä toimivaksi testattu dependency Glassfish neloselle:

 <dependency>
   <groupId>com.wordnik</groupId>
   <artifactId>swagger-jersey2-jaxrs_2.10</artifactId>
   <version>1.3.7</version>
   <exclusions>
     <exclusion>
       <groupId>org.glassfish.jersey.media</groupId>
       <artifactId>jersey-media-multipart</artifactId>
     </exclusion>
     <exclusion>
       <groupId>org.glassfish.jersey.containers</groupId>
       <artifactId>jersey-container-servlet-core</artifactId> 
     </exclusion>
   </exclusions>
 </dependency>

Hyvä tarkistaa tässä välissä että vanhat palvelut edelleen toimivat, jos niitä on. Seuraavaksi rekisteröidään Swagger servicet. Tämän voi tehdä xml:ssä – mutta itse käytän ResourceConfig-luokkaa ja annotaatioita:

@ApplicationPath("rest")
public class ApplicationConfig extends ResourceConfig {
public ApplicationConfig() {
packages("my.own.resources","com.wordnik.swagger.jersey.listing");
 }
}

Noin, sieltä aktivoituu tosiaan pari resurssiluokkaa, automaattisesti /rest/api-docs osoitteella. Seuraavaksi rekisteröidään Swagger servlet. Tämänkin voi tehdä annotaatioilla mutta kun sattui olemaan vielä web.xml (se on ainoa paikka jossa voi tehdä distributable-setin ha klusteriin) – tein sen siellä näin:

 <servlet>
 <servlet-name>JerseyJaxrsConfig</servlet-name>
 <servlet-class>com.wordnik.swagger.jersey.config.JerseyJaxrsConfig</servlet-class>
 <init-param>
 <param-name>api.version</param-name>
 <param-value>1.0.0</param-value>
 </init-param>
 <init-param>
 <param-name>swagger.api.basepath</param-name>
 <param-value>http://localhost:8080/rest/api-docs</param-value>
 </init-param>
 <load-on-startup>2</load-on-startup>
 </servlet>

Nyt alkaa jo melkein tapahtumaan. Seuraavaksi pitää varustaa ainakin yksi palvelu @Api-annotaatiolla, metodeille voi pistää @ApiOperation, ja parametreille ja paluuarvoille @ApiModel annotaatioita. Jotta saat jotain näkyviin tarvitset ainakin yhden @Api-annotaatiolla varustetun palvelun, jolla on ainakin yksi @ApiOperation annotaatiolla varustettu operaatio.

Tähän tapaan:

@Path("/pet")
@Api(value = "/pet", description = "Operations about pets")
@Produces({"application/json", "application/xml"})
public class PetResource {
  ...
@GET
@Path("/{petId}")
@ApiOperation(value = "Find pet by ID", notes = "More notes about this method", response = Pet.class)
@ApiResponses(value = {
  @ApiResponse(code = 400, message = "Invalid ID supplied"),
  @ApiResponse(code = 404, message = "Pet not found") 
})
public Response getPetById(
    @ApiParam(value = "ID of pet to fetch", required = true) @PathParam("petId") String petId)
    throws WebApplicationException {
@ApiModel(value = "A pet is a person's best friend")
@XmlRootElement(name = "Pet")
public class Pet {
  @XmlElement(name = "status")
  @ApiModelProperty(value = "Order Status", required=true, allowableValues = "placed,approved,delivered")
  public void setStatus(String status) {
    this.status = status;
  }
  public String getStatus() {
    return status;
  }

Sitten voitkin käynnistellä serverin, ja kokeilla mitä löytyy kontekstin alta osoitteella /api-docs, /api-docs/myservicename, jne – tässä tietysti myservicename on joku palvelu johon olet noita Swagger-annotaatioita ripotellut.

Tuolta löytyy myös esimerkkiä live-sisällöstä, niin kauan kuin se tietysti on pystyssä:

http://petstore.swagger.wordnik.com/api/api-docs

Näyttäisi tämmöiseltä:

{"apiVersion":"1.0.0","swaggerVersion":"1.2","apis":[{"path":"/pet","description":"Operations about pets"},{"path":"/user","description":"Operations about user"},{"path":"/store","description":"Operations about store"}],"authorizations":{"oauth2":{"type":"oauth2","scopes":[{"scope":"write:pets","description":"Modify pets in your account"},{"scope":"read:pets","description":"Read your pets"}],"grantTypes":{"implicit":{"loginEndpoint":{"url":"http://petstore.swagger.wordnik.com/oauth/dialog"},"tokenName":"access_token"},"authorization_code":{"tokenRequestEndpoint":{"url":"http://petstore.swagger.wordnik.com/oauth/requestToken","clientIdName":"client_id","clientSecretName":"client_secret"},"tokenEndpoint":{"url":"http://petstore.swagger.wordnik.com/oauth/token","tokenName":"auth_code"}}}}},"info":{"title":"Swagger Sample App","description":"This is a sample server Petstore server.  You can find out more about Swagger \n    at <a href=\"http://swagger.wordnik.com\">http://swagger.wordnik.com</a> or on irc.freenode.net, #swagger.  For this sample,\n    you can use the api key \"special-key\" to test the authorization filters","termsOfServiceUrl":"http://helloreverb.com/terms/","contact":"apiteam@wordnik.com","license":"Apache 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.html"}}

Törmäsin myös toiseen aika metkaan standardiin – ALPS. Sitä näyttäisi osaavan Spring Framework hyvin, mutta ainakan nyky-jerseystä ei löytynyt helppoa ratkaisua siihen. Pidän tätäkin kuitenkin silmällä. Looking good!

http://www.dzone.com/links/r/spring_data_rest_now_comes_with_alps_metadata.html

http://alps.io/

Näistä linkeistä löytyy vähän lisää ideoita. Varovaisuutta vanhemman Swagger-version kanssa!

https://github.com/wordnik/swagger-core/wiki/Java-JAXRS-Quickstart

 

 

Linux + Glassfish 4.0 High availability Load Balancing Cluster – osa 3

Viime kerroille rakensimme Glassfish servereistä klusterin, joka voisi elää vaikka virtuaalikoneissa tai Amazonin palveluissa. Pistimme sen eteen Apache Http-palvelimesta rakennetun Load Balancer ratkaisun – josta kannattaa myös tehdä High Availability ratkaisu, eli pistää niitäkin useampi kopio käyttöön. Vielä pitäisi saada aikaan se, että jos yksi Load Balancer kaatuu, muut ottavat sen paikan. Sitä pohdimme tällä kertaa.

Ideana on viritellä keepalived-niminen daemon ohjelma siten, että sille annetaan yksi yhteinen jaettu virtuaalinen ip-osoite. Keepalived reitittää sen ensisijaisesti ykköskoneelle, mutta jos se kaatuu, kakkoskone ottaa virtuaalisen ip:n haltuunsa. Näin ollen elleivät kaikki load balancerit kaadu yhtäaikaa, palvelun pitäisi pyöriä lähes katkotta.

Tässä esimerkissä virtuaalinen ip-osoite on 192.168.1.100, ja sen takana olevien koneiden todelliset ip-osoitteet voivat olla vaikkapa 192.168.1.101, 102, 103, jne – niitä ei tässä esimerkissä käytetä, vaan koneiden domain nimiä, jotka ovat edelleen debian1, debian2, jne.

Ensin asenna keepalived joka nodelle:

sudo apt-get install keepalived

Seuraavaksi editoi keepalived konfiguraatiota – sitä ei alussa ole ollenkaan, joten aloitetaan tyhjästä:

sudo nano /etc/keepalived/keepalived.conf

Ensimmäiselle koneelle, joka on oletuksena master, kopioi tiedostoon jotain tämäntapaista:

vrrp_script chk_http_port {   # Requires keepalived-1.1.13

script "wget -q -T 1.0 -t 2 --delete-after -O /tmp/test.wget http://localhost:80/index.html"

interval 5            # check every 5 seconds

weight 2              # add 2 points of prio if OK

}

vrrp_instance VI_1 {

interface eth0

state MASTER          # MASTER debian1, BACKUP debian2

virtual_router_id 51  # same id in both hosts

priority 101          # 101 on master, 100 on backup

virtual_ipaddress {

192.168.1.100         # virtual IP

}

track_script {

chk_http_port

}

}

 

Ja kakkoskoneelle modifioidaan tuota vähän, tähän tapaan:

 

vrrp_script chk_http_port {   # Requires keepalived-1.1.13

script "wget -q -T 1.0 -t 2 --delete-after -O /tmp/test.wget http://localhost:80/index.html"

interval 5            # check every 5 seconds

weight 2              # add 2 points of prio if OK

}

vrrp_instance VI_1 {

interface eth0

state BACKUP          # MASTER debian1, BACKUP debian2

virtual_router_id 51  # same id in both hosts

priority 100          # 101 on master, 100 on backup

virtual_ipaddress {

192.168.1.100         # virtual IP

}

track_script {

chk_http_port

}

}

 

Eli muuten sama, mutta prioriteetti vähän pienempi, ja tilana tosiaan BACKUP. Toki sovelletaan sen mukaan onko verkkokortti eth0, ja löytyykö serverin juuresta index.html tiedosto jolla voi testata vastaako serveri, miten tiheään haluat pollailla, jne.

Mutta viiden sekunnin välein siis testataan löytyykö index.html tiedostoa – jos ei, serverin prioriteetti nousee kahdella. Eli jos molemmat serverit ovat pystyssä, debian1 prioriteetti on 101+2 eli 103, ja debian2 on 100+2 eli 102, eli debian1 saa virtuaalisen ip-osoitteen. Jos ykkösserveri kaatuu, sen prioriteetti laskee 101+0, eli 101, ja näin debian2 saa virtuaalisen ip-osoitteen.

Lopuksi potkaistaan keepalived käyntiin ja testataan:

sudo service keepalived start

Ota yhteyttä selaimella virtuaaliseen ip-osoitteeseen – voit testata suoraan aiemmin käytettyä clusterjsp-sovellusta:

http://192.168.1.100/clusterjsp

Toimiiko? Hyvä. Oletuksena kytkeydyit varmaankin debian1 koneeseen, sekä apache että glassfish instanssiin. Seuraavaksi voitaisiin nitistää debian1 koneen glassfish:

sudo service glassfish-inst stop

Toimiiko? Hyvä, en olisi uskonut. Debian1 koneen apache ohjaa kuorman debian2 koneen glassfish serverille. Nitistetään seuraavaksi debian1 koneen apache:

sudo service apache2 stop

Kokeile vielä kerran virtuaali-ip:tä. Nyt sen pitäisi ohjata debian2 koneen apachelle, joka ohjaa debian2-koneen glassfishille.

Jos ei toimi, mokasit. Savun hälvettyä tarkista kytkennät ja yritä.uudestaan. Paljon on säätöjä näissä. Mutta metkasti toimii 😉 Kannattaa vielä buutata palvelimet ja katsoa että kaikki pelaa edelleen.

 

No niin, ja tässä pari kuvaruutukaappausta high availabilitystä toiminnassa. Olen hieman retusoinut serverinimiä ja ip osoitteita suojellakseni alkuperäisten tietokonehenkien identiteettiä ja persoonaa julkisuudelta, mutta näistä käy idea selväksi:

HAClient_1

Eli sessioon on pumpattu vähän dataa, tässä välissä kävin Glassfish konsolissa nitistämässä debian1 instanssin.

HAClient_2

Ja heittämällä mentiin kakkosnodelle, sessio data replikoituneena verkon yli ja edelleen käytettävissä ilman käyttäjälle näkyvää katkosta. Samoin voisi nitistää kumman hyvänsä load balancerin, tai vaikka vetää töpselin seinästä koko koneesta.

 

Nyt kun saisi vielä tietokantakerroksen vikasietoiseksi ja kuormaa tasaavaksi…..

Linux + Glassfish 4.0 High availability Load Balancing Cluster – osa 2

Viime kerralla viritettiin kaksi Glassfish serveriä juttelemaan keskenään – ja tarvittaessa replikoimaan istunnotkin. Tällä kertaa laitetaan niiden eteen Apache Http serveri LoadBalanceriksi. LoadBalancerin tehtävänä on ohjata liikenne toimiville servereille ja ohittaa vikatilassa tai sammuneina olevat – sekä tasata kuormaa serverien välillä. Näitäkin kannattaa tehdä ainakin kaksi kappaletta – yksi joka instanssikoneeseen – koska muuten loadbalancer on uusi kriittinen pisteesi – heikoin lenkki.

Aloitin ensin MOD_JK:lla, koska se on load balancer vaihtoehdoista ’javamaisempi’, joka tarkoittaa että se osaa tehdä monenmoisia temppuja mitä muut eivät osaa, mm. ssl-salauksen, monitoroinnin ja dynaamisten säätöjen suhteen. Valitettavasti testatessa paljastui, että vaikka se toimi hienosti clusterjsp esimerkille, se antoi suurikokoisilla tiedostoilla mehukkaita Grizzly-poikkeuksia (mm. HeapBuffer has already been disposed). Tämä on ilmeisesti Glassfish 4.0 bugi – en ollut ainoa joka tästä kärsi. Kenties 4.1 korjaa tämän – 4.0:han on tarkoituskin olla high availability early access versio. Mutta kärsimätön mieli ei jaksa eikä pysty odottamaan. Joten tein ratkaisun mod_proxyllä. Listaan kuitenkin testatut mod_jk käyttöönotto-ohjeet tähän samaan artikkeliin – loppuun. Mutta ensin mod_proxy ja toimiva versio:

MOD_PROXY Load balancer asennusohjeet

Ohjeissa oletetaan että koneet joihin asennat nämä ovat nimeltään debian1 ja debian2, ja asennat kaiken joka koneelle.

Aloita asentamalla ja aktivoimalla tarvittavat moduulit:

sudo apt-get install apache2 libapache2-mod-proxy-html libxml2-dev

sudo a2enmod proxy proxy_http proxy_balancer rewrite

Testaa käynnistyykö apache2:

sudo service apache2 start

Jos kaikki hyvin, seuraavaksi editoi tiedostoa /etc/apache2/sites-available/default lempieditorillasi. Sen voisi muokata esim. tämän näköiseksi (muista muokata se klusterin kaikille koneille, ja vastaavasti muutaa host name osia, jotta load balanceristä on kopioita):

<VirtualHost *:80>
 ServerName debian1.mycompany.com
 ServerAdmin webmaster@localhost

 ErrorLog ${APACHE_LOG_DIR}/error.log

 # Possible values include: debug, info, notice, warn, error, crit,
 # alert, emerg.
 LogLevel warn

 CustomLog ${APACHE_LOG_DIR}/access.log combined

 RewriteEngine On
 ProxyRequests Off
 ProxyPreserveHost On

 <Proxy *>
 Order deny,allow
 Allow from all
 </Proxy>

 <Location /balancer-manager>
 SetHandler balancer-manager
 Order Allow,Deny
 Allow from all
 # Only allow from internal network
 #Allow from 10.0.0.0/8
 </Location>

 <Proxy balancer://mycluster>
 BalancerMember http://debian1:8080 route=debian1
 BalancerMember http://debian2:8080 route=debian2
 ProxySet lbmethod=byrequests
 </Proxy>

 ProxyPass /balancer-manager !
 ProxyPass / balancer://mycluster/ stickysession=JSESSIONID
 ProxyPassReverse / http://debian1:8080/
 ProxyPassReverse / http://debian2:8080/

</VirtualHost>

Käynnistä apache uudelleen, ja kokeile ohjata selaimesi load balancer-koneen osoitteeseen. Sen pitäisi ohjata vuoronperään eri myllyille – voit testata tätä vaikkapa avaamalla pari eri selainta, tai sammuttamalla ykkösmyllyn. Proxypasseja voi tietysti lisätä ja muokata sen mukaan mitä kaikkia kansioita haluat uudelleenohjata ja miten. Load balancer algoritmejakin löytyy useampia.

No niin, seuraavaksi vaihtoehtoiset ohjeet mod_jk versiolle. Sitä en itse päätynyt käyttämään, mutta se voi olla testaamisen arvoinen Glassfish 4.1 kanssa aikanaan.

MOD_JK load balancer asennusohjeet

Aloita asentamalla softat kaikille haluamillesi koneille:

sudo apt-get install apache2 libapache2-mod-jk

Voisit tässä kohtaa editoida tiedostoa /etc/apache2/mods-available/jk.conf  – mutta siellä ei ole mitään pakollista muutettavaa, joten mennään sensijaan suoraan editoimaan toista tiedostoa:

sudo nano /etc/libapache2-mod-jk/workers.properties

Editoi tätä tiedostoa, voit kommentoida pois esim. oletus-jk13 workerin säätöjä, mutta muuta yksi riveistä tällaiseksi:

worker.list=loadbalancer,jk-status

Ja lisää tämätapaista (jos laitteita on enemmän kuin kaksi, lisää rivejä vain – tietysti sovella verkkonimet jne):

worker.debian1.port=8009
worker.debian1.host=debian1.mycompany.com
worker.debian1.type=ajp13
worker.debian1.lbfactor=1
worker.debian2.port=8009
worker.debian2.host= debian2.mycompany.com
worker.debian2.type=ajp13
worker.debian2.lbfactor=1
worker.loadbalancer.type=lb
worker.loadbalancer.balance_workers=debian1,debian2
worker.loadbalancer.sticky_session=1
worker.jk-status.type=status

Huomaa miten voit load balance factoreilla esim. painottaa miten kutsuja jaetaan. Tässä on käytössä sticky_session joka ei ole välttämätöntä. Olemme jättäneet myös status-tiedot päälle. Huomaa että jk moduuli myös kirjoittelee logia oletuksena paikkaan /var/log/apache2/mod_jk.log – logit on hyvä tarkastaa aika ajoin näitä testatessa.

Hommaa alkaa olla melkein valmis. Seuraavaksi editoidaan apache serverin omaa oletuskonfiguraatiota, ja lisätään kaikki halutut hakemistot joita halutaan uudelleenohjata:

sudo nano /etc/apache2/sites-available/default

Ja konfiguraatio voi mennä esim. näin:

<Location /jk-status>
 # Inside Location we can omit the URL in JkMount
 JkMount jk-status
 #Order deny,allow
 #Deny from all
 #Allow from 127.0.0.1
 </Location>

# mount clusterjsp
 JkMount /clusterjsp loadbalancer
 JkMount /clusterjsp/* loadbalancer

Nämä tulevat tietysti kaikki juurielementin VirtualHost sisään, loppuun, ja jos haluat lisää hakemistoja loadbalanceriin clusterjsp:n lisäksi, määrittele ne kaikki tähän.

Seuraavaksi enabloidaan mod_jk (luultavasti jo päällä), buutataan apache, ja aletaan kokeilemaan:

sudo a2enmod jk
sudo /etc/init.d/apache2 restart

Näppärä testi on ensin avata selain osoitteeseen http://debian1/clusterjsp – ja katsoa aukeaako sovellus. Apache pyörii portissa 80 – glassfishit porteissa 8080 omissa koneissaan. Jos sovellus toimii, näet miltä serveriltä se on noudettu. Nyt voit mennä glassfish hallintaan (DAS) ja sammuttaa instanssin joka on käytössä. Jos load balancer toimii oikein, sen pitäisi seuraavan kerran sivua päivitettäessä saumattomasti pompauttaa sinut toiselle instanssille.

Muista tehdä tämä load balancer molempiin/kaikkiin instansseihin niin voimme seuraavalla kertaa rakentaa siitäkin high availability version. Koska mitä jos Load Balancer kaatuu?

 

 

Maven, Glassfish 4 ja Cargo plugin

Kun hioo jatkuvaa integraatiota kuntoon tulee ennen pitkää ajankohtaiseksi pohdiskella miten automatisoidaan Maven buildien asennus etäservereille – kehittäjätestiservereille, hyväksyntätestiservereille, ja tuotantoonkin. Tapojahan tähän on monia – serverien omat työkalut, ihan tiedostotasolla toiminta ja hot deploy. Itse kuitenkin halusin kytkeä tämänkin Maveniin, se helpottaa kovasti mm. Jenkinsin kanssa toimintaa ja pitää munat samassa korissa.

Kokeilin muutamaa plug-inia, ja suosikiksi niistä erääntyi Cargo plug-in. Muissa tuli vastaan bugeja, ja Cargon konfiguraatio oli simppeleimmästä päästä joten se päätyi mukaan. Mukavaa on myös se, että Cargo ei ole yhden serverin ihme vaan tukee valtaisaa määrää Java Appservereitä.

Eli lisätään pom.xml tiedostoon tällainen plug-in:

 <!-- Maven Cargo plug-in to deploy .war to test servers -->
  <plugin>
    <groupId>org.codehaus.cargo</groupId>
    <artifactId>cargo-maven2-plugin</artifactId>
    <version>1.4.8</version>
    <configuration>
      <container>
        <containerId>glassfish4x</containerId>
        <type>remote</type>
      </container>
    <configuration>
      <type>runtime</type>
      <properties>
        <cargo.hostname>munserveri</cargo.hostname>
        <cargo.remote.username>muntunnus</cargo.remote.username>
        <cargo.remote.password>munsalasana</cargo.remote.password>
      </properties>
   </configuration> 
   </configuration>
    <dependencies>
      <dependency>
        <groupId>org.glassfish.deployment</groupId>
        <artifactId>deployment-client</artifactId>
        <version>3.2-b06</version>
      </dependency>
    </dependencies>
  </plugin>

Ja siinä se. Tuossa yllä munserveri on tietysti oman Glassfish 4 serverin osoite, muntunnus ja munsalasana sen admin-oikeudet omaavat tunnukset.

Nyt voit ajaa seuraavia komentoja:

mvn clean package cargo:deploy
mvn clean package cargo:redeploy
mvn cargo:undeploy

Yritin viritellä tätä toimimaan force-parametrilla siten että deploy hoitaisi myös redeploy-toiminnon – mutta jostain syystä ei pelittänyt ainakaan tässä ympäristössä. Ratkaisuna on ajaa aina cargo:redeploy, se ei näytä pahastuvan ellei serveriltä softaa ennestään löydykään, mutta deploy pahastuu jos softa on jo serverillä samalla nimellä, ja undeploy pahastuu jos softaa ei ole.

Seuraava askel on sitten tuunata profiileja käyttöön. Niillä voit säätää helposti serveriosoitteet, salasanat ja tunnukset vaikka miten monelle eri serverille ja käytellä niitä esim. näin:

mvn -Pintegration-test package cargo:undeploy cargo:deploy

Varmaan on fiksumpiakin tapoja tehdä näitä, esim. kenties force parametrin saa toimimaan, kenties redeploy hoitaa saman kuin undeploy+deploy. Tällä pääsee kuitenkin alkuun, ja kommentoida sopii jos keksii liukkaampia tapoja.

Plug-in on CodeHousin käsialaa ja löytyy täältä: http://cargo.codehaus.org/

 

 

Ja näin opimme taas jotain kivaa ja hyödyllistä 😉

 

 

 

Linux + Glassfish 4.0 High availability Load Balancing Cluster – osa 1

Sain taannoin loistavan syyn perehtyä toimintavarman ja skaalautuvan järjestelmän rakentamiseen Glassfish serverin vinkkelistä – olen aiemmin rakentanut vastaavia mm. JBOSS alustalla. Tässä mitä teen ei ole mitään mullistavaa, nykyisellään löytyy paljon teknisesti jännempiäkin juttuja mm. Riak, MongoDB, Hadoop, ja pilvipalveluiden ajatusmaailmasta. Mutta tämä on yksi kätevä työkalu pakissa: miten saada Java sovelluspalvelin toimimaan katkotta yli käyttöhäiriöiden ja päivitysten, ja miten saada se skaalautumaan yli satojen yhtäaikaisten käyttäjien (satoja saa yhdelläkin serverillä vaikka mitenpäin aikaiseksi).

Perusidea on vähintäänkin tuplata kaikki: Jotta saadaan serveri vikasietoiseksi, niitä tulisi olla ainakin se kaksi (mieluiten enemmän), ja jos jotain tilaa on käytetty, se pitäisi jotenkin replikoitua. Mutta vikasietoisuus on yhtä vahva kuin heikoin lenkki: glassfish serverien replikointi ei vielä riitä. Siihen tarvitaan eteen mylly joka reitittää pyynnöt toimiville serverille, load balancer toisinsanoen. Mutta mitä jos load balancer kaatuu? No niitäkin tarvitaan kaksi, tai enemmän, siten että yhden ollessa maissa toinen ottaa sen paikan. Entäpä.. taustatietokannat? Verkko? On paljon kahdennettavia kohtia.

Tämän voi tehdä monella tapaa, mutta itseä viehätti tällainen malli: Joka serveri on virtuaalikone, jossa on Glassfish serveri, Apache Http serveri Load balancerina, ja MySQL tietokanta. Periaatteessa siis joka mylly on lähes identtinen, ja sisältää kaiken tarvittavan. Skaalautuvuutta ja vikasietoisuutta saadaan siis lisäämällä myllyjä – ainakin tiettyyn rajaan asti (Session replikointi, sen tarve ja ratkaisuperiaate vaikuttaa skaalautuvuuden skaalautuvuuteen – ja kaikki serveriresurssit eivät muutenkaan skaalaudu niin hyvin 😉

Tuossa on kuva siitä miltä tämmöinen voisi näyttää – ei ole oma mutta kuvaa niin hyvin mistä on kyse että menköön (lopussa on lähdeviittaus sivustoon josta kuva ja alkuperäiset ideat ovat peräisin):

diagram

No niin, olen jo aiemmin käsitellyt aihetta, miten asennetaan Debian alustalle yksi Glassfish instanssi – joten se tarvitaan tietysti pohjaksi. Samalla kaavalla tehdään vähintään kaksi lähes identtistä serveriä – jotka näkevät toisensa (ping toimii ristiin).

Ensimmäinen askel on poistaa paikallinen serveri ja http kuuntelijat – koska niiden tilalle tulee klusterin instanssikuuntelijat. Tämä tehdään molemmille myllyille, ja Glassfish täytyy olla käynnissä aluksi:

#run these on both debian1 and debian2 machines

$ asadmin delete-http-listener http-listener-1

$ asadmin delete-http-listener http-listener-2

$ asadmin delete-virtual-server server

Selvä pyy. Vaihdetaan seuraavaksi Glassfish tunnukseen, koska sillä serverit ajavat käskyjään, ja luodaan ssh avainparit joilla serverit voivat viestiä. Tämän voit ajaa vain node1 koneesta – ellet sitten halua Domain Admin nodea molempiin koneisiin. Huomaa että glassfish tunnuksella tulisi tässä kohtaa olla myös salasana – sama molemmissa nodeissa. Ellet ole sellaista luonut, aja passwd glassfish nyt.

Tässä esimerkissä koneiden verkkonimet ovat: debian1.mycompany.com ja debian2.mycompany.com:

#run this only on debian1 machine

sudo su --shell /bin/bash glassfish

asadmin setup-ssh --generatekey=true debian1.mycompany.com

# seuraava komento antaa virheen koska glassfish on jo asennettu kakkosnodeen, mutta 
# se myös mukavasti tarkistaa että kommunikaatio pelaa

asadmin install-node debian2.mycompany.com

Seuraava askel, luodaan kaksi hallinta-nodea Glassfishiin:

# run this on debian1 machine

asadmin create-node-ssh --nodehost localhost debian1-ssh 

asadmin create-node-ssh --nodehost debian2.mycompany.com debian2-ssh

Nämä ovat ssh nodeja, eli näiden avulla Glassfish voi yhdestä DA-serveristä käsin lähettää ssh-protokollalla komentoja kaikille klusterin nodeille.

Seuraavaksi luodaan klusteri ja kaksi serveri-instanssia siihen:

# run this on debian1 machine

asadmin create-cluster --systemproperties HTTP_SSL_LISTENER_PORT=8181:HTTP_LISTENER_PORT=8080 cluster1 

asadmin create-instance --cluster cluster1 --node debian1-ssh debian1-gf

asadmin create-instance --cluster cluster1 --node debian2-ssh debian2-gf

Jos tähän asti kaikki sujui, seuraavaksi tehdään Load Balanceriä varten AJP-palvelut. Tässä kikkaillaan vähän – ja asetetaan järjestelmämuuttujiin (system properties) instanssin nimi jota voi serverissä sitten käyttää esim. virtuaalikoneparametreissa kätevästi:

# run this on debian1 machine

asadmin create-http-listener --listenerport 8009 --listeneraddress 0.0.0.0 --defaultvs server --target cluster1 jk-connector

asadmin set configs.config.cluster1-config.network-config.network-listeners.network-listener.jk-connector.jk-enabled=true

asadmin create-jvm-options --target cluster1 "-DjvmRoute=\${AJP_INSTANCE_NAME}"

asadmin create-system-properties --target debian1-gf AJP_INSTANCE_NAME=debian1

asadmin create-system-properties --target debian2-gf AJP_INSTANCE_NAME=debian2

Seuraavaksi, piipahdetaan molemmilla koneilla vaihtamassa masterpassword – mutta siten että se tallettuu tiedostoon jolloin sitä ei koko aikaa kysellä:

# run this on debian1 machine

asadmin change-master-password --savemasterpassword true debian1-ssh

#run this on debian2 machine:

asadmin change-master-password --savemasterpassword true debian2-ssh

Ja lopulta, talletetaan myös tavallinen admin salasana tiedostoon, jotta asadmin työkalun ajo ei juutu dialogeihin:

# on debian1:

asadmin --host debian2 --port 4848 login 

#on debian2:

asadmin --host debian1 --port 4848 login

Nyt ollaan jo aika loppusuoralla. Meillä on jo ennestään init scripti joka ajaa glassfish domain serverin (ja tässä tapauksessa DAS) käyntiin, mutta kannattaa ehkä lisätä scripti joka käynnistää myös molemmissa koneissa instanssin – tai clusterin. Editoi tiedostoa etc/init.d/glassfish-inst ja pistä sinne jotain tämäntapaista:

#!/bin/sh
#
# glassfish init script for Linux 
# It only starts one instance by name. The instance needs to have
# .asadminpass and .asadmintruststore password files created
# for the user.
#
# .asadminpass is created via "asadmin login" command
# .asadmintruststore is created via 
# "asadmin change-master-password --savemasterpassword true"
### BEGIN INIT INFO
# Provides: glassfish-inst
# Required-Start: $local_fs $remote_fs $network $syslog $named
# Required-Stop: $local_fs $remote_fs $network $syslog $named
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# X-Interactive: true
# Short-Description: Start/stop a glassfish instance
### END INIT INFO

GLASSFISH_HOME=/opt/glassfish4
GLASSFISH_USER=glassfish
GLASSFISH_INSTANCE=debian1-gf

case $1 in

start)
 echo "Starting Glassfish local instance ${GLASSFISH_INSTANCE}..."
 su - "${GLASSFISH_USER}" -c \
 "${GLASSFISH_HOME}/bin/asadmin start-local-instance "${GLASSFISH_INSTANCE}""
 ;;

stop)
 echo "Stopping Glassfish local instance ${GLASSFISH_INSTANCE}..."
 su - "${GLASSFISH_USER}" -c \
 "$GLASSFISH_HOME/bin/asadmin stop-local-instance "${GLASSFISH_INSTANCE}""
 ;;

restart)
 echo "Restarting Glassfish local instance ${GLASSFISH_INSTANCE}..."
 su - "${GLASSFISH_USER}" -c \
 "$GLASSFISH_HOME/bin/asadmin restart-local-instance "${GLASSFISH_INSTANCE}""
 ;;

*)
 echo "usage: $0 (start|stop|restart|help)"

esac

Huom! Vaihda ylläolevassa tuo GLASSFISH_INSTANCE arvoon 1, 2, tai mitä serveriä käsitteletkään. Joka serverillä se tulee tietysti olla omansa. Jos käynnistät clusterin, se tietysti potkaisee joka instanssin käyntiin kerralla. Tätä tapaa en kuitenkaan vielä ehtinyt kokeilemaan.

Seuraavaksi pistetään tämäkin automaattikäynnistymään:

sudo chmod 755 /etc/init.d/glassfish-inst

sudo update-rc.d glassfish-inst defaults

No niin, sitten vain boottia kehiin. Serverin 1 pitäisi toimia DAS-serverinä, joten sen kautta voit käydä ihan hallintakonsolista katsomassa miltä kaikki näyttää. Kannattaa myös kokeilla asentaa joku pieni ja varmasti toimiva klusterisovellus ja testata vähän miten se asentuu kaikille instansseille, ja miten se käyttäytyy kun instanssit sammuvat ja taas käynnistyvät.

Sellainen löytyy esim. täältä: http://blogs.nologin.es/rickyepoderi/uploads/SimplebutFullGlassfishHAUsingDebian/clusterjsp.war

Pari huomiota sessioreplikoinnin vaatimuksista ja nykytilasta:

Glassfish 4.0 ei virallisesti tue HA piirteitä, mutta siellä on olemassa early access joka toimii ihan hyvin. 4.1 versiossa olisi paremmin testattuna tulossa samat piirteet. Glassfish versioissahan on aina joissain klusterituki, joissain ei, eli kannattaa olla tarkkana version kanssa.

Glassfish 4.0 tukee eri replikointitavoista vain in-memory tapaa, joka tarkoittaa että nodet heittelevät verkon yli toisilleen notifikaatioita, ja replikoivat sessiodataa. Tämä ei ole parhaiten skaalautuva malli, mutta pienille ja keskikokoisille klustereille joissa sessiotilaa ei ole valtavasti ihan ok malli. Tämä edellyttää että klusterissa on GMS palvelu päällä, ja nodet näkevät toisensa verkossa. Hyvä myös tarkistaa että konfiguraation availability service alla on rastit oikeissa laatikoissa, mutta oletuksena pitäisi olla.

gms_service_enabled

availability_service_enabled

Mutta vielä on tärkeä huomioitava asia: Asennettava sovellus tulee olla availability-yhteensopiva ja availability-moodissa. Ensimmäinen edellyttää web.xml tiedostossa distributable=true arvoa (tai ei ollenkaan web.xml tiedostoa). Toinen vaatii että availability laitetaan päälle joko asennettaessa sovellus tai sen jälkeen. Esim. kun asensin em. clusterjsp sovelluksen oletusasetuksilla, siitä jäi rasti pois ruudusta, jolloin sessiot eivät replikoituneet.

availability_enabled

Voit myös kokeilla näkevätkö serverit toisensa ajamalla molemmissa nodeissa about yhtäaikaa tämän:

asadmin validate-multicast

 

Tässä tämä tällä kertaa. Tästä lähteestä ammensin paljon ideoita ja käytännön säätöjä:

http://blogs.nologin.es/rickyepoderi/index.php?/archives/53-Simple-but-Full-Glassfish-HA-Using-Debian.html

Jos tykkäät mielummin tehdä näitä UI:n puolella, tässä hyvä tutoriaali siihen (Glassfish 3 oppaat pätevät suurimmalta osin Glassfish 4:seen):

http://javadude.wordpress.com/2011/04/25/glassfish-3-1-clustering-tutorial/