Blog d'Eric MICHEL

Aller au contenu | Aller au menu | Aller à la recherche

mardi 14 février 2012

Installation JMX VISUALVM JSTATD et VISUALGC pour un tomcat (environnement protégé par firewall)

Il est assez aisé de configurer les applications pour accéder aux données JMX avec des outils comme JCONSOLE ou JVISUALVM ou tout autre outil de monitoring applicatif. En revanche, lorsque des firewall sont en place, la configuration JMX est un peu plus complexe à réaliser. Ce billet traite de la manière dont on peut configurrer tomcat pour faire passer les requêtes JMX à travers un firewall.

JMX POUR PASSER LES FIREWALLS

OPTIONS JVM

OPTIONS JVM =" -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=${PORT1} -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=${SERVER_NAME} -Dcom.sun.management.jmxremote.authenticate=true -Dcom.sun.management.jmxremote.password.file=${JMX_CONF}/jmx.password -Dcom.sun.management.jmxremote.access.file=${JMX_CONF}/jmx.access"

avec :
SERVEUR NAME = DNS ou IP
JMX_CONF = path vers les fichiers credential (pour protéger l'accès JMX par role/passwd)

FICHIERS CREDENTIALS

  • créer un fichier ${JMX_CONF}/jmx.password avec les droits -rw (rw pour user seulement ! )

monitorRole password
controlRole password

  • créer un fichier ${JMX_CONF}/jmx.access avec les droits -rw (rw pour user seulement ! )

monitorRole readonly
controlRole readwrite

AJOUT DU LISTENER JMX

dans le fichier serveur.xml de tomcat, ajouter la ligne suivante :
<listener classname="org.apache.catalina.mbeans.JMXAdaptorLifecycleListener" namingport="${PORT2}" port="${PORT1}" host="${SERVEUR_NAME}">

MISE A JOUR DES LIBRAIRIES

Ajouter dans les libs de tomcat les jar suivants :

  • jmx-adaptor-listener.jar
  • tomcat-catalina-jmx-remote-${VERSION}.jar
  • commons-logging-${VERSION}.jar
  • carol-${VERSION}.jar


MISE A JOUR DU FIREWALL

Ouvrir les ports ${PORT1} et ${PORT2} sur le firewall pour permettre l'accès aux machines susceptibles de se connecter à la console JMX

VISUALGC

ACTIONS SUR LE SERVEUR


  • Créer un fichier (nommé ici : jstatd.all.policy) pour permettre à l'environnement java de s'exécuter avec les bons droits


grant codebase "file:/usr/local/jdk1.6.0_30/lib/tools.jar" { permission java.security.AllPermission; };

  • sur le serveur lancer jstatd afin de permettre le monitoring GC


$JAVA_HOME/bin/jstatd -J-Djava.security.policy=jstatd.all.policy -J-Djava.rmi.server.hostname=${SERVEUR_NAME} &amp;
ou
rmiregistry 2020&amp;
$JAVA_HOME/bin/jstatd -J-Djava.security.policy=jstatd.all.policy -J-Djava.rmi.server.hostname=${SERVEUR_NAME} -p 2020 &amp;
si vous souhaitez utiliser le port rmi 2020 à la place du port par défaut (1099)
Ouvrir le port (1099 ou autre) pour permettre aux clients de se connecter par RMI

ACTIONS SUR LES CLIENTS

  • sur la machine sur laquelle s'exécutera l'outil visualgc, lancer la commande suivante :


jps -l -m -v rmi://${IP_SERVEUR}:1099 (1099 = port rmi par defaut, 2020 ou autre si vous utilisez la commande rmiregistry) cette commande va lister tous les processus java lancé sur le serveur avec en première colonne le numéro de processus associé à la commande java.

  • pour lancer visualgc tapez ensuite


visualgc ${NUMERO_DE_PROCESSUS}@${SERVEUR_NAME}
avec le numéro de processus correspondant au numéro de processus renvoyé par la commande jps précédente</listener>

mardi 12 avril 2011

TOMCAT MULTI INSTANCES

Contexte


Avec une installation standard de tomcat, nous déployons les applications dans un même et unique répertoire webapp. Bien qu'assez pratique, cette solution n'est pas sans inconvénients :

  • On ne peut pas gérer l'arrêt d'une application sans engendrer l'arrêt des autres services.
  • La mémoire allouée au tomcat est fonction des applications déployées. Pour le dire autrement, cela oblige à allouer d'un bloc plus de mémoire au tomcat. Si la mémoire arrive à saturation, nous constatons un arrêt de service sur toutes les applications déployées. D'autre part il est conseillé d'éviter d'allouer un montant trop important au niveau de la mémoire, car cela peut engendrer des "freeze" des applications lorsque le garbage collector s'active.

Un début de solution serait de se dire qu'il faut installer autant de tomcat que nous avons d'applications à déployées. Ainsi nous serions capables de tailler la JVM par applicatif sans que cela engendre des risques sur les autres applicatifs. L'inconvénient principal de cette architecture est de ne pas pouvoir faire évoluer facilement la version des binaires tomcat. Si on a dix applis installées il faudrait alors réinstaller 10 fois la nouvelle version de tomcat. La solution élégante est décrite dans la suite de ce billet avec une installation tomcat multi-instances :

Avantages du tomcat multi instances


La solution proposée par ce post est d'installer une seul binaire de tomcat puis de créer pour chaque applicatif une configuration distincte. Ce type de solution nous offre les avantages suivants :

  • 1 seul bianire tomcat (facilitant le travail de maintenance et de mise à jour de version)
  • Chaque application aurait sa propre JVM et sa propre configuration (taille JVM, ports etc ...)
  • Possibilité d'arrêter indépendamment chaque application.
  • Un crash mémoire sur une des applications n'entrainera pas de dysfonctionnement sur les autres applications.


Recette de cuisine pour configurer un tomcat multi-instances


  1. Téléchargez votre version de tomcat et installez là sur le disque (ex : /srv/java/tomcat/mon_binaire_tomcat )
  2. Mettre à jour une variable système pour y configurer l'endroit ou se trouve le binaire tomcat (ex : CATALINA_HOME= /srv/java/tomcat/mon_binaire_tomcat )
  3. Mettre à jour une deuxième variable système qui servira à définir l'endroit ou sont configurées les instances tomcat (ex : PRGDIR=~/tomcat)
  4. Recharger votre configuration d'environnement (source /.bashrc sous linux). En gros la commande "env" doit afficher les nouvelles variables. Téléchargez le template en pièce jointe et le décompresser dans $PRGDIR

Nous venons de désarchiver le fichier joint qui contient les instances. Ce fichier contient :

  • 1 fichier run.sh, à éditer/modifier pour les plus curieux. C'est ce script qui lancera et arrêtera les instances de tomcat.
  • 1 répertoire shared qui contient la configuration partagé et dans conf le fichier server.xml (modifié pour injecter les numéros de ports en fonction des instances)
  • 3 répertoires d'instances dans lesquels on placera dans chacun une applications (on peut ajouter ou supprimer autant d'instances que l'on souhaite ou encore les renommer). Dans ces répertoires, le fichier ./instance_monAppli{XX}/conf/catalina.properties permet de configurer les ports tomcat alloués à cette instance et seront injectés dans le fichier ./shared/conf/server.xml. Il y a de plus pour les options JVM, un second fichier ./instance_monAppli{XX}/bin/setenv.sh modifiable en fonction des besoins (Xms, Xmx et autres options de jvm)


Déploiement d'une application


  1. Editez le fichier ./instance_{nom_de_mon_instance}/conf/catalina.properties et configurez les ports attribués à tomcat
  2. Editez le fichier ./instance_{nom_de_mon_instance}/bin/setenv.sh et modifiez vos paramètres JVM
  3. Placez votre application dans le répertoire : ./instance_{nom_de_mon_instance}/webapps
  4. lancez l'instance tomcat via la commande : ./run.sh start nom_de_mon_instance
  5. Vérifier le démarrage de l'instance via le catalina.out (dans ./instance_{nom_de_mon_instance}/logs/catalina.out) + logs applicatifs
  6. Pour la forme vérifier le lancement du tomcat et les paramètres associés avec un "ps -ef | grep nom_de_mon_instance". On notera que nos settings sont bien pris en compte via le retour de la commande ps => -Dcatalina.base=/srv/java/tomcatMultiInstances/instance_monAppli1 -Dcatalina.home=/srv/java/apache-tomcat-6.0.20
  7. Pour arrêter l'instance, il suffit de taper la commande ./run.sh stop {nom_de_mon_instance}

mercredi 3 décembre 2008

Sous Rapport avec Jasper et Ireport

Présentation

Cet article montre comment créer un rapport à trois niveaux de maitre détail en java. On utilisera exclusivement jasper et java sans utiliser la moindre ligne SQL pour générer les rapports. Il sera intéressant de voir ici deux techniques pour passer des listes de détails de manière différentes aux sous rapports. Soit en implémentant directement l'interface JRDataSource, soit en créant de simple collection et en configurant dans Ireport le passage des listes en paramètre.
Le rapport que nous allons créer va afficher dans un premier temps la liste des pays européens et un troisième niveau avec les principales villes de chacun des pays. En maitre, nous aurons les informations concernant l'Europe, nom et couleur associée à ce continent à savoir le bleu. En détail, nous trouverons la liste des pays européens. Dans ce détail, nous allons créer un nouveau détail qui affichera la liste des principales villes du pays. NB : Toute ressemblance entre la réalité et les données affichées dans ces rapports n'est que pure coincidence !!!

Création d'un maître détail avec jasper en implémentant la datasource

Partie Java

La première étape de ce rapport consiste à afficher les informations générales du continent européens, ici le nom du continent, la couleur associé qui est le bleu, puis de faire un sous rapport dans le détail afin d'afficher les pays européens. Voici la classe définissant un pays :

public class Pays {
        private String nom;
        private Integer population;
        public String getNom() {
                return nom;
        }
        public void setNom(String nom) {
                this.nom = nom;
        }
        public Integer getPopulation() {
                return population;
        }
        public void setPopulation(Integer population) {
                this.population = population;
        }
}

Cette classe ne présente rien de spécial, il s'agit d'un simple POJO avec des attributs et des accesseurs.
Voyons maintenant la classe Continent contenant les informations générales liées à un continent ainsi qu'une liste des pays.

public class Continent extends AbstractJRDataSource<Pays> {
        private String nom;
        private String couleur;
        
        public String getNom() {
                return nom;
        }
        public void setNom(String nom) {
                this.nom = nom;
        }
        public String getCouleur() {
                return couleur;
        }
        public void setCouleur(String couleur) {
                this.couleur = couleur;
        }
}

La liste des pays n'apparait pas ici en Collection, en revanche, on étend une classe maison AbstractJRDataSource<Pays> dont voici le code :

public abstract class AbstractJRDataSource<T> implements JRDataSource {
    protected int index=0;
    protected List<T>listeObj = null;
    public boolean next() throws JRException {
        index++;
        return (index <= listeObj.size());
    }

    public Object getFieldValue(JRField field) throws JRException {
        T obj = listeObj.get(index-1);
        Object value;
        try {
            String methodName = "get"+field.getName().substring(0,1).toUpperCase()+field.getName().substring(1);
            value = MethodUtils.invokeExactMethod( obj, methodName, null);
        } catch (Exception e) {
            throw new JRException(e.getMessage());
        }
        return value;
    }
    public List<T> getListeObj() {
        return listeObj;
    }
    public void setListeObj(List<T> listeObj) {
        this.listeObj = listeObj;
    }
}

Il s'agit d'une classe générique dont un peut définir librement le type de la liste implémentée via le template, celle-ci implémente les méthodes getFieldValue() et next() de l'interface JRDataSource de Jasper. L'implémentation de l'interface JRDataSource va nous permettre de facilement alimenter les sous raports en créant de simple liste de pays. Voici maintenant le code de création des rapports :

public class SubReportDemo {
        public static void main(String[] args) {
        Continent continent = init();
        JasperReport jasperReport = null;
        JasperPrint jasperPrint = null;
        Map<Object,Object> parameterMap = new HashMap<Object,Object>();
        parameterMap.put( "reportName", "Rapport DEMO" );       
        parameterMap.put( "nom", continent.getNom() );
        parameterMap.put( "couleur", continent.getCouleur() );
        InputStream reportStream = SubReportDemo.class.getClassLoader().getResourceAsStream("rapport_demo_parent.jrxml");
        try {
                        jasperReport = JasperCompileManager.compileReport(reportStream);
                        jasperPrint = JasperFillManager.fillReport(jasperReport, parameterMap, continent);
                        JasperExportManager.exportReportToPdfFile(jasperPrint, "/tmp/reportEurope_"+new Date()+".pdf");
                } catch (JRException e) {
                        e.printStackTrace();
                }
        }
        private static Continent init(){                
                Continent europe = new Continent();             
                List<Pays> paysEurope = new ArrayList<Pays>();
                Pays france = new Pays();
                france.setNom("FRANCE"); france.setPopulation(60000000);
                paysEurope.add(france);
                
                Pays italie = new Pays();
                italie.setNom("Italie"); italie.setPopulation(45000000);      
                paysEurope.add(italie);
                
                Pays angleterre = new Pays();
                angleterre.setNom("Angleterre"); angleterre.setPopulation(55000000);
                paysEurope.add(angleterre);
                
                europe.setListeObj(paysEurope);
                europe.setNom("Europe");
                europe.setCouleur("bleu");    
                return europe;
        }
}

la Liste des pays est renseigné dans la classe continent via la méthode setListeObj() définit comme template Pays => public class Continent extends AbstractJRDataSource<Pays>

Partie iReport

Création du rapport principal

Pour commencer, nous allons définir les paramètres, Le titre du rapport sera passé en paramètre au rapport puis affiché sur celui-ci. Reste les données à afficher dans la partie entête en l'occurence les données de l'objet continent : le nom et la couleur que nous passons en tant que paramètres, En tant que champs, nous passerons les données affichées dans la partie détail (partie automatiquement alimentée avec les méthodes de l'interface que nous avons implémentée : JRDataSource) provenant de la classe Pays. Jasper Paramètres Maitre Ensuite, nous disposons les données sur le rapports : asper report maitre Voici alors le résultat obtenu avec la partie Maître Continent et les Détails Pays Jasper Resultat Maitre Detail

Etape 2 : Sous Rapport en java avec jasper

Nous avons vu en première partie comment faire un simple maître détail en utilisant Jasper et en implémentant l'interface JRDataSource mise à disposition par l'API jasper. Voyons maintenant comment créer un sous rapport dans la partie détail du rapport. Nous allons ajouter en dessous de chaque pays, une liste des principales villes. Nous allons créer un sous rapport dans la partie détail. Voyons tout d'abord les modification au niveau structure objet.

Partie JAVA

On commence par ajouter la classe ville dont voici les attributs :

public class Ville {
        private String nom;
        private Integer population;
        private Double superficie;
        public String getNom() {
                return nom;
        }
        public void setNom(String nom) {
                this.nom = nom;
        }
        public Integer getPopulation() {
                return population;
        }
        public void setPopulation(Integer population) {
                this.population = population;
        }
        public Double getSuperficie() {
                return superficie;
        }
        public void setSuperficie(Double superficie) {
                this.superficie = superficie;
        }
}

Ensuite, on modifie la classe Pays pour y ajouter la collection de ville. Ici, on n'étend pas l'interface DataSource, on verra comment sera créer la dataSource dans iReport un peu plus tard...

public class Pays {
        private String nom;
        private Integer population;
        private Collection<Ville> villes;
        public String getNom() {
                return nom;
        }
        public void setNom(String nom) {
                this.nom = nom;
        }
        public Integer getPopulation() {
                return population;
        }
        public void setPopulation(Integer population) {
                this.population = population;
        }
        public Collection<Ville> getVilles() {
                return villes;
        }
        public void setVilles(Collection<Ville> villes) {
                this.villes = villes;
        }       
}

Pour la partie java... on a finit !

Partie iReport

En dessous des informations concernant le pays, nous allons créer un rapport secondaire. Une boîte de dialogue s'ouvre alors, nous sélectionnerons just create subreport element
Dans les propriétés du rapport secondaire, nous nommons le rapport qui sera appélé dans l'onglet Rapport Secondaire (autre) par exemple Rapport_Villes.jasper.
Ensuite, nous passons à l'onglet Rapport Secondaire et nous définissons les paramètres du rapport secondaire en réutilisant les paramètres du rapport principal dans Expression de mappe de paramètre avec la valeur : new HashMap($P{REPORT_PARAMETERS_MAP}), En dessous, on sélectionne : Utiliser l'expression de source de donnée et en valeur, on y met notre collection qui alimente la datasource : new JRBeanCollectionDataSource($F{villes}). Une fois fait, l'onglet problem mentionne que nous n'avons pas tout a fait terminé, il faut rajouter dans le rapport principal la propriété que nous avons ajouté dans je code java de la classe Pays à savoir villes de type : java.util.Collection. On rajoute le champs et nous pouvons passer au sous rapport.

A venir !!!