Le Labo #14 | Intégration Continue - Jenkins/Puppet/Gitlab
10/Jun 2016
Intégration Continue - Jenkins/Puppet/Gitlab
Petite introduction
Lors d’un de mes labos précédent, j’avais fais connaissance avec Gitlab-CI qui, à l’aide de Gitlab permet de bénéficier d’un environnement de déploiement continue unifié. Ce sur quoi j’ai travaillé ici est adaptable sur Gitlab-CI/Gitlab. C’est tout d’abord un environnement Puppet masterless dont les recettes Puppet sont stockées dans des dépots Gitlab, dont chaque nouveau commit fera l’objet d’un démarrage de tâche sur chaque agent Puppet.
Mon environnement
J’avoue…Je suis litteralement tombé sous le charme de Terraform…Si bien que je l’ai encore utilisé pour déployer mon environnement de Lab.
Je vais avoir besoin des éléments suivants :
- Un serveur Jenkins
- Un Serveur Gitlab
- Quatre agents Puppet
Déploiement à l’aide de Terraform
Les variables
digitalocean-provider.tf :
variable "do_token" {}
variable "pub_key" {}
variable "pvt_key" {}
variable "ssh_fingerprint" {}
variable "user_data_puppet" {}
variable "user_data_jenkins" {}
provider "digitalocean" {
token = "${var.do_token}"
}
variable "puppet_count" {
description = "Number of web servers"
default = "4"
}
variable "do_region" {
default = "ams2"
}
variable "do_image" {
default = "ubuntu-14-04-x64"
}
terraform.tfvars
do_token = "<token>"
pub_key = "<clé_publique>"
pvt_key = "clé_privée"
ssh_fingerprint = "<fingerprint_ssh>"
user_data_puppet = "puppet.sh"
user_data_jenkins = "jenkins.sh"
La clé SSH
Il est possible de se passer de clé SSH pour créer les serveurs, mais dans le cas de DigitalOcean, les mots de passe root sont transmis par mail.
Tout d’abord, on crée la clé à l’aide de la commande suivante :
ssh-keygen -t rsa
Et on intègre la clé au fichier de déploiement :
resource "digitalocean_ssh_key" "default" {
name = "ssh_key"
public_key = "${var.pub_key}"
}
L’output de la commande suivante est à intégrer dans la variable “ssh_fingerprint” :
ssh-keygen -lf ~/.ssh/id_rsa.pub | awk '{print $2}'
Les serveurs
resource "digitalocean_droplet" "Jenkins" {
image = "ubuntu-14-04-x64"
name = "Jenkins"
region = "ams2"
size = "1gb"
private_networking = true
ipv6 = true
user_data = "jenkins.sh"
ssh_keys = [
"${var.ssh_fingerprint}"
]
connection {
user = "root"
type = "ssh"
key_file = "${var.pvt_key}"
timeout = "2m"
}
}
resource "digitalocean_floating_ip" "Jenkins" {
droplet_id = "${digitalocean_droplet.Jenkins.id}"
region = "${digitalocean_droplet.Jenkins.region}"
}
resource "digitalocean_droplet" "gitlab" {
image = "17689953"
name = "gitlab"
region = "ams2"
size = "1gb"
private_networking = true
ipv6 = true
ssh_keys = [
"${var.ssh_fingerprint}"
]
connection {
user = "root"
type = "ssh"
key_file = "${var.pvt_key}"
timeout = "2m"
}
}
resource "digitalocean_floating_ip" "gitlab" {
droplet_id = "${digitalocean_droplet.gitlab.id}"
region = "${digitalocean_droplet.gitlab.region}"
}
resource "digitalocean_droplet" "Node4" {
image = "ubuntu-14-04-x64"
name = "Puppet"
region = "ams2"
size = "1gb"
private_networking = true
ipv6 = true
user_data = "puppet.sh"
ssh_keys = [
"${var.ssh_fingerprint}"
]
connection {
user = "root"
type = "ssh"
key_file = "${var.pvt_key}"
timeout = "2m"
}
}
resource "digitalocean_floating_ip" "Node4" {
droplet_id = "${digitalocean_droplet.Node4.id}"
region = "${digitalocean_droplet.Node4.region}"
}
resource "digitalocean_droplet" "Node1" {
image = "ubuntu-14-04-x64"
name = "Node1"
region = "ams2"
size = "1gb"
private_networking = true
ipv6 = true
user_data = "puppet.sh"
ssh_keys = [
"${var.ssh_fingerprint}"
]
connection {
user = "root"
type = "ssh"
key_file = "${var.pvt_key}"
timeout = "2m"
}
}
resource "digitalocean_floating_ip" "Node1" {
droplet_id = "${digitalocean_droplet.Node1.id}"
region = "${digitalocean_droplet.Node1.region}"
}
resource "digitalocean_droplet" "Node2" {
image = "ubuntu-14-04-x64"
name = "Node2"
region = "ams2"
size = "1gb"
private_networking = true
ipv6 = true
user_data = "puppet.sh"
ssh_keys = [
"${var.ssh_fingerprint}"
]
connection {
user = "root"
type = "ssh"
key_file = "${var.pvt_key}"
timeout = "2m"
}
}
resource "digitalocean_floating_ip" "Node2" {
droplet_id = "${digitalocean_droplet.Node2.id}"
region = "${digitalocean_droplet.Node2.region}"
}
resource "digitalocean_droplet" "Node3" {
image = "ubuntu-14-04-x64"
name = "Node3"
region = "ams2"
size = "1gb"
private_networking = true
ipv6 = true
user_data = "puppet.sh"
ssh_keys = [
"${var.ssh_fingerprint}"
]
connection {
user = "root"
type = "ssh"
key_file = "${var.pvt_key}"
timeout = "2m"
}
}
resource "digitalocean_floating_ip" "Node3" {
droplet_id = "${digitalocean_droplet.Node3.id}"
region = "${digitalocean_droplet.Node3.region}"
}
Les scripts
puppet.sh
#!/bin/bash -e
## Install Git and Puppet
wget -O /tmp/puppetlabs.deb http://apt.puppetlabs.com/puppetlabs-release-`lsb_release -cs`.deb
dpkg -i /tmp/puppetlabs.deb
apt-get update
apt-get -y install git-core puppet
jenkins.sh
#!/bin/bash
wget -q -O - http://pkg.jenkins-ci.org/debian/jenkins-ci.org.key | sudo apt-key add -
sudo apt-get update
sudo sh -c 'echo deb http://pkg.jenkins-ci.org/debian binary/ > /etc/apt/sources.list.d/jenkins.list'
sudo apt-get update -y
sudo apt-get install -y jenkins
service jenkins start
Configuration des outils
Puppet (1ere partie)
Sur l’agent Puppet Node1, je commence par générer une clé ssh à l’aide de la commande suivante : ssh-keygen -t rsa
Une fois généré, je le copie sur chaque agent puppet.
Gitlab
La configuration de Gitlab est moins compliquée que celle de jenkins.
J’ai créé un dépôt nommé puppet et copié la clé ssh (générée sur Node1 et copiée sur chacun des agents puppet)
Puppet (2eme partie)
Git
Ensuite, je génère la configuration Git qui permettra d’uploader la configuration de puppet ainsi que les manifests vers Git pour, par la suite, les récupérer sur chaque agent puppet.
Voici les commandes qui permettront d’effectuer tout cela :
Générer la configuration Git
cd /etc/puppet && git init
git config --global user.name "Administrator"
git config --global user.email "admin@example.com"
Insérer l’adresse IP du serveur gitlab dans le fichier /etc/hosts
echo xxx.xxx.xxx.xxx gitlab >> /etc/hosts
Ajouter le contenur du dossier et valider
git add .
git commit -m "Initial commit of Puppet files"
Ajouter le projet Git à la configuration locale et uploader les modifications
git remote add origin git@gitlab:root/<depot_puppet>.git
git push -u origin master
Configuration de Puppet
puppet.conf
De base, le fichier puppet.conf contient ce qui suit :
[main]
logdir=/var/log/puppet
vardir=/var/lib/puppet
ssldir=/var/lib/puppet/ssl
rundir=/var/run/puppet
factpath=$vardir/lib/facter
templatedir=$confdir/templates
[master]
# These are needed when the puppetmaster is run by passenger
# and can safely be removed if webrick is used.
ssl_client_header = SSL_CLIENT_S_DN
ssl_client_verify_header = SSL_CLIENT_VERIFY
Il faut supprimer la ligne suivante : templatedir=$confdir/templates
Je vais créer les fichiers suivants : init.pp, site.pp et post-merge
cron-puppet/manifests/init.pp
class cron-puppet {
file { 'post-hook':
ensure => file,
path => '/etc/puppet/.git/hooks/post-merge',
source => 'puppet:///modules/cron-puppet/post-merge',
mode => 0755,
owner => root,
group => root,
}
cron { 'puppet-apply':
ensure => present,
command => "cd /etc/puppet ; /usr/bin/git pull",
user => root,
minute => '*/30',
require => File['post-hook'],
}
}
cron-puppet/files/post-merge
#!/bin/bash -e
## Run Puppet locally using puppet apply
/usr/bin/puppet apply /etc/puppet/manifests/site.pp
## Log status of the Puppet run
if [ $? -eq 0 ]
then
/usr/bin/logger -i "Puppet has run successfully" -t "puppet-run"
exit 0
else
/usr/bin/logger -i "Puppet has ran into an error, please run Puppet manually" -t "puppet-run"
exit 1
fi
/etc/puppet/manifests/site.pp
node default {
include cron-puppet
}
Une fois ces fichiers créés, j’upload le tout sur gitlab à l’aide de la commande suivante :
git add . && git commit -m "Added the cron-puppet module"
git push -u origin master
Sur chacun des autres agents Puppet, il faudra lancer les commandes suivantes :
Générer la configuration Git
cd /etc/puppet && git init
git config --global user.name "Administrator"
git config --global user.email "admin@example.com"
Insérer l’adresse IP du serveur gitlab dans le fichier /etc/hosts
echo xxx.xxx.xxx.xxx gitlab >> /etc/hosts
git remote add origin git@gitlab:root/puppet-project.git
Récupérer la configuration Puppet stockée sur le dépot Gitlab
git pull
Jenkins
La première fois que l’on accède à l’UI de Jenkins, il nous est possible de laisser Jenkins installer les plugins recommandés ou de choisir ceux que l’on veut installer.
J’ai préféré installer ceux que je souhaitais…c’est à dire tous ceux qui étaient proposés…cela m’a permit de bénéficier des plugins suivants :
- Gitlab
- SSH
- PAM authentication
Ces plugins seront très utiles par la suite.
Les esclaves
Il y a deux manières de configurer les esclaves :
- Soit en ajoutant des esclaves, via Administrer Jenkins/Gérer les noeuds
- Soit en ajoutant des hôtes SSH, via Administrer Jenkins/Configurer le système
Il ne me semble pas vraiment utile d’ajouter des noeuds, car il est possible de démarrer des builds sur chaque noeud en ajoutant des hôtes SSH.
Par contre, il faut ajouter la configuration gitlab, en modifiant :
- Connection name
- Gitlab host URL
- API token : Secret Text
Dans Identifiants, il faudra entrer le token affiché dans la configuration de Gitlab.
Les jobs
Pour créer un job, on clique sur Nouveau item (vive la traduction), on nomme le projet et on selectionne le type parmi les suivants :
- free-style
- maven
- pipeline
- multi-configuration
- multijob
- External job
- folder
- Multibranch
Dans le cas qui m’intérese, j’ai choisi de créer plusieurs jobs free-style et de les configurer ainsi :
General : Je n’ai rien sélectionné dans cette partie.
Gestion de code source : Malgré le fait que le plugin gitlab ait été installé, il ne semble pas figurer parmi les outils de versionning. Il n’empêche qu’en sélectionnant git il est possible de lier Jenkins à Gitlab.
Pour cela, je renseigne l’url du dépot Gitlab dans le champs Repository URL, les identifiants de connexion et la branche depuis laquel seront lancés les builds.
Ce qui déclenche les builds : J’ai sélectionné l’option suivante : Build when a change is pushed to GitLab.
Et On push to source or targe branch sur Rebuild open Merge Requests.
Environnement de build : J’ai sélectionné l’option suivante : Execute shell script on remote host using ssh.
Je sélectionne l’agent Puppet concerné par le job en question et je défini des actions pré et post build :
Pré build
apt-get update -y
Post build
cd /etc/puppet/ git pull puppet apply /etc/puppet/manifests/site.pp
Build et Actions à la suite du build : Je n’ai rien modifié dans ces deux sections…mais ce sera à vous de voir ce que vous souhaiterez définir en fonction des jobs que vous créerez.
Une fois le job sauvegardé…il est temps de démarrer le job et de vérifier que tout fonctionne bien.
Et du côté des recettes Puppet ?
Elles sont disponible sur de nombreux sites.
Ce sera à vous de les intégrer dans votre projet.