Il y a 7 années -

Temps de lecture 11 minutes

Retour de l’atelier Continuous Delivery – Partie 4 – L’infrastructure as a code

Comme promis dans le premier article de cette série sur le “making off” du workshop Continuous Delivery, nous vous présentons plus de détails sur la préparation de l’infrastructure de l’atelier. Nous prévoyons de reprogrammer cet atelier au mois de janvier (pour ceux qui sont intéressés, soyez attentifs au calendrier des Tech Events Xebia et Eventbrite!)

1. Infrastructure à créer et outils employés

Avant de présenter les outils que nous avons utilisés, un petit rappel sur l’infrastructure du workshop. Pour chaque équipe de deux personnes, nous disposions de l’architecture suivante :

  • Un repository de l’appli Petclinic sur Github
  • Une instance EC2 avec un serveur Jenkins, un serveur Rundeck et un serveur Deployit.
  • Trois instances Tomcat. Un serveur Tomcat de développement et deux autres appelés “valids” qui jouaient le rôle de serveurs de production.

Les instances Amazon EC2 ont été créées et installées en infra-as-code avec l’API Amazon et le package d’Ubuntu CloudInit. D’autres services Amazon ont été utilisés afin d’héberger les packages dont on avait besoin (Amazon S3) et pour associer les IP dynamiques des nœuds à des noms de domaine (Amazon Route 53). La création des repositories Github a été automatisée en Java avec l’API v2 GitHub.

2. Création de l’infrastructure

Scripting avec CloudInit

Nous avons utilisé le package d’Ubuntu CloudInit pour installer chaque instance au premier démarrage. Ci-dessous le script cloud-config cloud-config-amzn-linux-jenkins-rundeck.txt pour l’installation de Jenkins sur un nœud découpé en trois blocs :

repo_additions : le repo Jenkins est ajouté à la liste de repos yum du serveur

#cloud-config

repo_additions:
 - source: jenkins
   filename: jenkins.repo
   name: Jenkins
   baseurl: http://pkg.jenkins-ci.org/redhat/
   key: http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key
   enabled:

packages : liste de packages à installer (via rpm ou yum)

packages:
 - yum-utils
 - java-1.6.0-openjdk
 - java-1.6.0-openjdk-devel
 - jenkins
 - git

runcmd : permet d’exécuter de commandes linux. Ici, on installe maven (puisqu’il n’y a pas de linux package manager disponible pour l’installer automatiquement) ainsi que quelques plugins Jenkins dont on aura besoin. A suivre vous trouverez aussi quelques détails de configuration, le rajout de l’utilisateur ec2-user au group ‘jenkins’ et le démarrage du service. Attention, les commandes runcmd doivent respecter la syntaxe yaml !

runcmd:
 - [ sh, -xc, "echo $(date) ': cloudinit runcmd begin'" ]
# installs Maven
 - [cd, /opt]
 - [wget, "http://mirror.ibcp.fr/pub/apache//maven/binaries/apache-maven-2.2.1-bin.tar.gz"]
 - [tar, xvzf, apache-maven-2.2.1-bin.tar.gz]
 - [ln, -s, /opt/apache-maven-2.2.1, /opt/maven]
# Jenkins might not be completely installed, so let's create its folders
 - [mkdir, -p, /var/lib/jenkins/plugins/]
 - [chown, -R, "jenkins:jenkins", /var/lib/jenkins]
# downloads additional Jenkins plugins
 - [wget, --no-check-certificate, "http://updates.jenkins-ci.org/latest/batch-task.hpi", -O, "/var/lib/jenkins/plugins/batch-task.hpi"]
 - [wget, --no-check-certificate, "http://updates.jenkins-ci.org/latest/git.hpi", -O, "/var/lib/jenkins/plugins/git.hpi"]
 - [wget, --no-check-certificate, "http://updates.jenkins-ci.org/latest/github.hpi", -O, "/var/lib/jenkins/plugins/github.hpi"]
 - [chown, -R, "jenkins:jenkins", /var/lib/jenkins/plugins]
# tells Jenkins where to find Maven
 - [chown, "jenkins:jenkins", /var/lib/jenkins/hudson.tasks.Maven.xml]
# activates Jenkins as a service
 - [chkconfig, jenkins, on]
 - [service, jenkins, start ]
 - [usermod, -a, -G, jenkins, ec2-user]
 - [ sh, -xc, "echo $(date) ': cloudinit runcmd end'" ]

Afin de tester le script, le plus simple c’est de créer une instance Amazon Linux Centos (où CloudInit est installé par défaut) et rajouter le script cloud-config à sa création. Comme toujours, nous pouvons faire ces opérations via l’interface de commandes AWS Tools, la console d’Amazon ou directement par du code avec la SDK d’Amazon.

Troubleshooting CloudInit

Alors vous démarrez l’instance avec le script cloud-config, vous vous connectez avec le navigateur sur l’IP et le port que vous aviez configuré mais Jenkins n’apparaît pas… Comment peut-on savoir qu’est-ce qui s’est passé ? Voici quelques astuces pour troubleshooting CloudInit :

  • Logs : En premier, il faudrait savoir où se trouvent les logs. Vous pouvez les trouver dans /var/log/messages et /var/log/cloud-init.log
  • Relancer le script CloudInit : Comme dit précédemment, cloudInit installe l’instance pendant son premier démarrage. Pour éviter d’être obligés de recréer une instance à chaque fois qu’on modifie le script, nous pouvons tout simplement supprimer les sémaphores qui existent dans le répertoire /var/lib/cloud/sem et relancer cloud-init avec la nouvelle config.
[ec2-user@ip-10-234-133-197 sem]$ sudo rm /var/lib/cloud/sem/*
[ec2-user@ip-10-234-133-197 log]$ sudo /etc/init.d/cloud-init start

Gestion des binaires et de la clé privée sur S3

Suite à la première édition du workshop où nous avions eu un problème à l’installation des instances (plus précisément le mirroir Apache sur lequel nous allions chercher Maven et Tomcat de Rundeck n’était plus disponible) nous avons pris conscience que le téléchargement HTTP de packages à chaud avant l’atelier était trop risqué. Dans la recherche d’une solution plus fiable nous avons décidé d’utiliser Amazon S3 pour héberger les binaires tels que les rpm de Rundeck ou Tomcat ainsi que le certificat pour accéder en remote aux instances EC2.

Qu’est-ce qu’Amazon S3 ?

Amazon S3 est un service d’hébergement payant sur Internet fournit par Amazon. Un compte Amazon peut avoir un ou plusieurs buckets ou compartiments sur lesquels nous pouvons stocker, télécharger et supprimer des objets (fichiers, packages…) dont la taille peut aller d’un byte à plusieurs Terabytes. Comme pour les instances EC2, l’utilisateur peut sélectionner les buckets de la région la plus proche afin d’économiser coût et temps d’accès.

Clone des repo Github et changement du groupe

Pour la réalisation de l’atelier, nous avions besoin d’un repository github par équipe. La création automatique des repositories a été faite en Java avec l’API v2 de GitHub.

Dans l’impossibilité de faire un fork avec cet API, nous avons vu qu’un simple git clone pouvait nous aider à achever la réplication des repositories. Par contre, ensuite, il a fallu automatiser quelques modifications au niveau du pom.xml de chaque projet. Les paramètres groupId et la connexion SCM du projet de référence ont dû être remplacés par celles du groupe en question. Voici un exemple du pom.xml avec le groupId et les paramètres SCM spécifiques pour l’équipe 1.

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>fr.xebia.demo.petclinic-1</groupId>
  <artifactId>xebia-petclinic-lite</artifactId>
  <version>1.0.3-SNAPSHOT</version>
  <packaging>war</packaging>
  <name>xebia-petclinic-lite</name>
  <scm>
    <connection>scm:git:git://github.com/xebia-guest/xebia-petclinic-lite-1</connection>
    <developerConnection>scm:git:https://xebia-guest@github.com/xebia-guest/xebia-petclinic-lite-1.git</developerConnection>
  </scm>

Ci-dessous un extrait du code GithubRepositoriesCreator.java utilisé pour cloner le repository de référence.

private Git initGitLocalRepository(File tmpRepoDir) {
    Git git;
    if (sourceGitHubRepositoryUrl != null) {
        logger.info("Repository {} is cloning into {}", new Object[] {sourceGitHubRepositoryUrl, tmpRepoDir.getAbsolutePath()});
        git = Git.cloneRepository()
                .setURI(sourceGitHubRepositoryUrl)
                .setDirectory(tmpRepoDir)
                .call();
    } else {
        logger.info("Repository is initiating into {}", tmpRepoDir);
        git = Git.init()
                .setDirectory(tmpRepoDir)
                .call();
        }
    return git;
}

Concept Elastic IP pour Nexus

Qu’est-ce une IP Elastique ?

Une IP élastique est une adresse IP liée à un compte Amazon qui peut être associée à une instance EC2 précise. Elle peut aussi être dissociée de celle-ci et être assignée à une autre en cas de besoin. Cette possibilité permet de switcher de nœud si la première instance tombe, par exemple, et facilite le fait que d’autres nœuds qui pointaient sur cette instance puissent le faire vers le nouveau nœud rapidement.

Dans le workshop, un seul serveur Nexus gère les artifacts de toutes les équipes. Le fait d’utiliser une IP Elastique pour le serveur Nexus a permis que tous les pom.xml de tous les projets pointent vers le même serveur Nexus indépendamment de l’instance virtuelle sur laquelle il a été finalement installé. En plus, en cas de problème on peut réassigner l’IP Elastique à un autre nœud EC2 rapidement sans avoir à retoucher la config de tous les projets.

Comment créer une IP Elastique sur Amazon EC2 ?

Une IP Elastique peut être créée facilement via la console d’Amazon :

et aussi sur l’interface de commandes avec ec2-allocate-address :

Ci-dessous la partie du code Java CreateNexusInstance.java associant l’IP Elastique au serveur Nexus :

// ASSOCIATE NEXUS INSTANCE WITH PUBLIC IP
Address address = Iterables.getOnlyElement(ec2.describeAddresses(new DescribeAddressesRequest().withPublicIps(publicIp))
      .getAddresses());
String currentlyAssociatedId = address.getInstanceId();
if (currentlyAssociatedId == null) {
      logger.debug("Public IP {} is not currently associated with an instance", publicIp);
} else {
      logger.info("Public IP {} is currently associated instance '{}'. Disassociate it first.", publicIp, currentlyAssociatedId);
      ec2.disassociateAddress(new DisassociateAddressRequest(publicIp));
}

ec2.associateAddress(new AssociateAddressRequest(nexusInstance.getInstanceId(), publicIp));

Génération de documentation avec Freemarker au format wiki

Afin de faciliter le déroulement des exercices du workshop, nous avons fourni une page wiki à chaque équipe. Dans chaque page wiki il y avait les liens des serveurs de l’équipe en question (Jenkins, Rundeck, Deployit et les trois serveurs Tomcat et le Nexus). Pour générer ces pages, nous avons utilisé Freemarker.

Freemarker est un package Java qui permet de générer du HTML à partir de templates et d’objets Java. Voici une partie du template apache-tomcat-maven-plugin.fmt pour l’exercice avec l’Apache Tomcat Plugin :

#summary Continuous Delivery with Jenkins and rundeck Lab for team '${infrastructure.identifier}'

*<font size="5">Continuous Delivery Apache Tomcat Maven Plugin for Team '${infrastructure.identifier}'</font>*

= Your architecture =

<img width="400" src="http://xebia-france.googlecode.com/svn/wiki/cont-delivery-img/per-team-infrastructure.png" />

<table>
<tr><td> *SSH Private key* </td><td> [https://s3-eu-west-1.amazonaws.com/continuous-delivery/continuous-delivery-workshop.pem continuous-delivery-workshop.pem]</td></tr>
<tr><td> *!GitHub project repository url* </td><td> [${infrastructure.githubRepositoryHomePageUrl}] </td></tr>
<tr><td> *Jenkins URL* </td><td> [${infrastructure.jenkinsUrl}] </td></tr>
<tr><td> *Rundeck URL* </td><td> [${infrastructure.rundeckUrl}] </td></tr>
<tr><td> *Deployit URL* </td><td> [${infrastructure.deployitUrl}] </td></tr>

et un extrait de l’objet java TeamInfrastructure.java qui apparaît dans le template :

/**
 * <p>
 * Team infrastructure for the lab.
 * </p>
 */
public class TeamInfrastructure implements Comparable{

    private final WorkshopInfrastructure workshopInfrastructure;
    private Instance jenkins;
    private Instance nexus;
    private Instance rundeck;

    @Nonnull
    public String getGithubRepositoryHomePageUrl() {
        return workshopInfrastructure.getGithubGuestAccountUrl() + getGithubRepositoryName();
    }

    public String getJenkinsUrl() throws IllegalStateException {
        return Strings.nullToEmpty(TeamInfrastructure.getJenkinsUrl(jenkins));
    }

    @Nullable
    public String getRundeckUrl() throws IllegalStateException {
        return Strings.nullToEmpty(TeamInfrastructure.getRundeckUrl(rundeck));
    }

    public String getDeployitUrl() throws IllegalStateException {
        return Strings.nullToEmpty(TeamInfrastructure.getDeployitUrl(jenkins));
    }

Une fois que l’infrastructure est créée, le code Java scanne l’infrastructure Amazon EC2 et récupère toutes les URLs dont il a besoin pour générer une page wiki par équipe et exercice. Voici la page wiki générée pour l’équipe 1.

3. Conclusion

La génération de la plate-forme pour le workshop en infra-as-code sur Amazon EC2 a prouvé qu’on pouvait utiliser Amazon EC2 pour créer une infrastructure d’une taille considérable (presque 50 VMs) dans un temps plus qu’acceptable (environ 10 minutes) et de façon flexible et rapide.

Ce workshop nous a permis de créer une infrastructure facile à générer à la demande et portable pour plusieurs types d’atelier. Il nous a aussi permis de découvrir quelques services Amazon tels que Amazon Route 53 ou l’Elastic IP. Grâce à ce workshop, nous avons appris aussi qu’il vaut mieux stocker les binaires dans un serveur interne ou un service d’hébergement tel qu’Amazon S3 plutôt que sur un serveur HTTP dont le téléchargement à chaud est beaucoup plus risqué.

Quant au coût des services Amazon, le plus significatif a été logiquement celui des instances. Nous avons utilisé des instances à la demande dont le prix par heure est autour d’un $0.1/heure pour l’UE (le temps d’utilisation commence dès que l’instance est lancée et finit quand elle est terminée). Si on tient compte du nombre d’instances utilisées (environ 45) et du nombre d’heures du workshop (3 heures) on voit que le prix de l’atelier revient à moins de 10€.

Pour ceux qui sont intéressés, tout le code est disponible sur le project Google Code xebia-cloudcomputing-extras sous license Apache.

Publié par Xavier Bucchiotty

Software Engineer Scala Fanboy FP constant learner Akka trainer

Commentaire

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Xebian, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.