JDEV2017 - T4 AP02 - Frédéric Woelffel
Marseille / 5 juillet 2017
- Frédéric Woelffel
- Ingénieur R&D à l'IHU de Strasbourg
- Développeur Node.js
(http://static.fwoelffel.me/JDEV2017/ap02)
Binaires :
- docker 17.03
- git
Images Docker :
docker pull gitlab/gitlab-ce:9.3.0-ce.0
docker pull parabuzzle/craneoperator:2.1.3
docker pull registry:2
docker pull rancher/server:v1.6.2
docker pull rancher/agent:v1.2.2
docker pull jenkins:2.60.1-alpine
docker pull fwoelffel/jenkins-slave-dind:latest
Créons un réseau Docker pour cet atelier
docker network create --subnet=172.28.0.0/16 jdev
Mettons à jour la configuration du daemon Docker
sudo nano /etc/docker/daemon.json
{
...
"insecure-registries" : [ "172.28.0.0/16" ]
...
}
Redémarrons le daemon Docker
sudo service docker restart
Démarrons une instance de Jenkins
docker run \
--detach \
--publish 8080:8080 \
--publish 50000:50000 \
--net jdev \
--ip 172.28.0.13 \
--name jenkins \
jenkins:2.60.1-alpine
Débloquons l'instance en récupérant le mot de passe administrateur
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
Choisissons de n'installer que les plugins suivants
- Gitlab Plugin
- Pipeline
Continuons en tant qu'administrateur et installons un plugin supplémentaire (choisissez l'installation après redémarrage)
http://127.0.0.1:8080/pluginManager/available
Self-Organizing Swarm Plug-in Modules
Commençons par créer un nouveau projet Jenkins de type Freestyle project nommé echo
Rendons-nous directement dans la section Build
Ajoutons une étape de build de type Execute shell et copions-y la ligne suivante
echo 'Hello world!'
Après avoir sauvegardé nous sommes redirigés vers la page du projet. Déclenchons l'exécution du projet en cliquant sur le bouton Build now
(http://127.0.0.1:8080/job/echo/build?delay=0sec)
Nous venons d'exécuter notre premier projet Jenkins !
http://127.0.0.1:8080/job/echo/1/console
Démarrons une instance de Gitlab CE
docker run \
--detach \
--publish 80:80 \
--publish 22:22 \
--net jdev \
--ip 172.28.0.10 \
--name gitlab \
--env GITLAB_OMNIBUS_CONFIG="external_url 'http://172.28.0.1/'" \
gitlab/gitlab-ce:9.3.0-ce.0
- Rendons nous sur la page d'accueil de cette instance Gitlab http://127.0.0.1
- Définissons un mot de passe administrateur (password, c'est facile à retenir)
- Connectons nous
- Créons le groupe jdev
Démarrons une instance de registre
docker run \
--detach \
--publish 5000:5000 \
--net jdev \
--ip 172.28.0.11 \
--name registry \
registry:2
Vérifions qu'il est correctement exécuté
http://127.0.0.1:5000/v2/_catalog
La réponse attendue est la suivante
{
"repositories": []
}
Démarrons une instance d'interface graphique pour le registre Docker
docker run \
--detach \
--publish 5001:80 \
--net jdev \
--ip 172.28.0.12 \
--env REGISTRY_HOST=172.28.0.11 \
--env REGISTRY_PORT=5000 \
--env REGISTRY_PROTO=http \
--env REGISTRY_PUBLIC_URL=http://127.0.0.1:5000 \
--name registry-ui \
parabuzzle/craneoperator:2.1.3
Admirons
Démarrons simplement ce slave Docker
docker run \
--detach \
--name jenkins-slave \
--privileged \
--net jdev \
--ip 172.28.0.14 \
--env DOCKER_DAEMON_ARGS="--insecure-registry 172.28.0.0/16" \
--env COMMAND_OPTIONS="-master http://172.28.0.13:8080 -username admin -password < votre password > -labels docker" \
fwoelffel/jenkins-slave-dind
## Initialisons un projet
Commençons par instancier le projet hello-world sur Gitlab
Récupérons les sources d'une application sur Github
git clone https://github.com/FWoelffel/express-hello-world.git
Publions cette application sur notre instance de Gitlab
cd express-hello-world
git remote add jdev http://127.0.0.1/jdev/hello-world
git push --set-upstream jdev master
Créons un utilisateur Gitlab pour Jenkins
http://127.0.0.1/admin/users/new
Champ | Valeur |
---|---|
Name | jenkins |
Username | jenkins |
jenkins@jenkins.jenkins |
Ajoutons lui un accès Developper au projet
http://127.0.0.1/jdev/hello-world/settings/members
Incarnons ensuite cet utilisateur...
http://127.0.0.1/admin/users/jenkins
...et récupérons un token d'accès à l'API de Gitlab
http://127.0.0.1/profile/personal_access_tokens
Dans la configuration globale de Jenkins, renseignons les champs requis pour Gitlab
http://127.0.0.1:8080/configure
Testons ensuite la connexion.
Créons également une clef SSH pour l'accès à Gitlab via SSH.
docker exec -it jenkins ssh-keygen -t rsa -C "jenkins@jenkins.jenkins" -b 1024
docker exec -it jenkins cat /var/jenkins_home/.ssh/id_rsa.pub
Ajoutons cette clef au profil de l'utilisateur Jenkins sur Gitlab
Créons également un nouvel identifiant SSH sur Jenkins
Ca se passe ici
Créons un projet Freestyle hello-world-freestyle
Configurons une restriction d'exécution sur les noeuds possédant le label docker
Dans la section Source Code Management cochons git
Saisissons les valeurs suivantes
Champ | Valeur |
---|---|
Repository URL | git@172.28.0.10:jdev/hello-world.git |
Credentials | Gitlab SSH Key |
Name | origin |
...
...
Champ | Valeur |
---|---|
Refspec | +refs/heads/*:refs/remotes/origin/* +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/* |
Branch Specifier | origin/${gitlabSourceBranch} |
Configurons ensuite les Build Triggers
Nous allons configurer un déclenchement de build par webhook, cochons donc la case correspondante
Saisissons les valeurs suivantes
Champ | Valeur |
---|---|
Push events | Ok |
MR events | Ok |
Rebuild MR | On push to source branch |
Comments | NOk |
Enable CI Skip | Ok |
Ignore WIP | Ok |
Allowed branches | Toutes |
Générons le token et prenons en note.
Configurons ensuite Gitlab pour appeler ce webhook
http://127.0.0.1/jdev/hello-world/settings/integrations
Champ | Valeur |
---|---|
URL | http://172.28.0.13:8080/project/build |
Token | ... |
Push events | Ok |
Tag push events | Ok |
MR events | Ok |
Le reste | NOk |
Maintenant, les scripts !
Execute shell
COMMMIT_SHORT_SHA=$(git rev-parse --short HEAD)
docker build -t 172.28.0.11:5000/hello-world:ci_$COMMMIT_SHORT_SHA .
Execute shell
COMMMIT_SHORT_SHA=$(git rev-parse --short HEAD)
docker push 172.28.0.11:5000/hello-world:ci_$COMMMIT_SHORT_SHA
Enfin, ajoutons un feedback vers Gitlab en ajoutant une étape Post-build de type Publish build status to Gitlab commit
N'oublions pas de nommer ce build hello-world-freestyle
Testons ce projet en déclenchant un appel du webhook via Gitlab
http://127.0.0.1/jdev/hello-world/settings/integrations
Okay... Mais avec un Pipeline ce serait plus sympa
Créons un nouveau job de type Pipeline nommé hello-world
Reprenons la même configuration pour les Build triggers
N'oublions pas de rajouter l'intégration sur Gitlab
http://127.0.0.1/jdev/hello-world/settings/integrations
Nous allons définir notre pipeline directement dans l'interface Jenkins
(Il est possible de le stocker dans un dépôt git)
### Récupérer les sources
pipeline {
agent {
label 'docker'
}
stages {
stage('Checkout') {
steps {
checkout(
scm: [
$class: "GitSCM",
userRemoteConfigs: [
[url: "git@172.28.0.10:jdev/hello-world.git", credentialsId: "< id de votre credential ssh >"]
],
branches: [
[name: env.gitlabSourceBranch]
]
]
)
}
}
}
}
Récupérez la bonne valeur de credentialsId pour l'identifiant "Gitlab SSH Key"
http://127.0.0.1:8080/credentials/
Validons ce premier stage en testant le webhook
http://127.0.0.1/jdev/hello-world/settings/integrations
pipeline {
agent {
label 'docker'
}
stages {
stage('Checkout') {
steps {
checkout(
scm: [
$class: "GitSCM",
userRemoteConfigs: [
[url: "git@172.28.0.10:jdev/hello-world.git", credentialsId: "< id de votre credential ssh >"]
],
branches: [
[name: env.gitlabSourceBranch]
]
]
)
}
}
stage('Build') {
steps {
sh '''
COMMMIT_SHORT_SHA=$(git rev-parse --short HEAD)
docker build -t 172.28.0.11:5000/hello-world:ci_$COMMMIT_SHORT_SHA .
docker push 172.28.0.11:5000/hello-world:ci_$COMMMIT_SHORT_SHA
'''
}
}
}
}
pipeline {
agent {
label 'docker'
}
stages {
stage('Checkout') {
steps {
checkout(
scm: [
$class: "GitSCM",
userRemoteConfigs: [
[url: "git@172.28.0.10:jdev/hello-world.git", credentialsId: "< id de votre credential ssh >"]
],
branches: [
[name: env.gitlabSourceBranch]
]
]
)
}
}
stage('Build') {
steps {
sh '''
COMMMIT_SHORT_SHA=$(git rev-parse --short HEAD)
docker build -t 172.28.0.11:5000/hello-world:ci_$COMMMIT_SHORT_SHA .
docker push 172.28.0.11:5000/hello-world:ci_$COMMMIT_SHORT_SHA
'''
}
}
stage('Tests') {
steps {
sh '''
COMMMIT_SHORT_SHA=$(git rev-parse --short HEAD)
docker run 172.28.0.11:5000/hello-world:ci_$COMMMIT_SHORT_SHA test
'''
}
}
}
}
pipeline {
agent {
label 'docker'
}
stages {
stage('Checkout') {
steps {
checkout(
scm: [
$class: "GitSCM",
userRemoteConfigs: [
[url: "git@172.28.0.10:jdev/hello-world.git", credentialsId: "< id de votre credential ssh >"]
],
branches: [
[name: env.gitlabSourceBranch]
]
]
)
}
}
stage('Build') {
steps {
sh '''
COMMMIT_SHORT_SHA=$(git rev-parse --short HEAD)
docker build -t 172.28.0.11:5000/hello-world:ci_$COMMMIT_SHORT_SHA .
docker push 172.28.0.11:5000/hello-world:ci_$COMMMIT_SHORT_SHA
'''
}
}
stage('Tests') {
steps {
sh '''
COMMMIT_SHORT_SHA=$(git rev-parse --short HEAD)
docker run 172.28.0.11:5000/hello-world:ci_$COMMMIT_SHORT_SHA test
'''
}
}
stage('Release:Latest') {
when {
expression {
return env.gitlabSourceBranch == 'master'
}
}
steps {
sh '''
COMMMIT_SHORT_SHA=$(git rev-parse --short HEAD)
docker tag 172.28.0.11:5000/hello-world:ci_$COMMMIT_SHORT_SHA 172.28.0.11:5000/hello-world:latest
docker push 172.28.0.11:5000/hello-world:latest
'''
}
}
stage('Release:Tagged') {
when {
expression {
TAGGED = sh (
script: "git describe --exact-match --tags",
returnStatus: true
) == 0
return TAGGED
}
}
steps {
sh '''
COMMMIT_SHORT_SHA=$(git rev-parse --short HEAD)
COMMIT_TAG=$(git describe --exact-match --tags)
docker tag 172.28.0.11:5000/hello-world:ci_$COMMMIT_SHORT_SHA 172.28.0.11:5000/hello-world:$COMMIT_TAG
docker push 172.28.0.11:5000/hello-world:$COMMIT_TAG
'''
}
}
}
}
Et avec un peu de feedback...
pipeline {
agent {
label 'docker'
}
stages {
stage('Checkout') {
steps {
updateGitlabCommitStatus name: 'hello-world', state: 'running'
checkout(
scm: [
$class: "GitSCM",
userRemoteConfigs: [
[url: "git@172.28.0.10:jdev/hello-world.git", credentialsId: "< id de votre credential ssh >"]
],
branches: [
[name: env.gitlabSourceBranch]
]
]
)
}
}
stage('Build') {
steps {
sh '''
COMMMIT_SHORT_SHA=$(git rev-parse --short HEAD)
docker build -t 172.28.0.11:5000/hello-world:ci_$COMMMIT_SHORT_SHA .
docker push 172.28.0.11:5000/hello-world:ci_$COMMMIT_SHORT_SHA
'''
}
}
stage('Tests') {
steps {
sh '''
COMMMIT_SHORT_SHA=$(git rev-parse --short HEAD)
docker run 172.28.0.11:5000/hello-world:ci_$COMMMIT_SHORT_SHA test
'''
}
}
stage('Release:Latest') {
when {
expression {
return env.gitlabSourceBranch == 'master'
}
}
steps {
sh '''
COMMMIT_SHORT_SHA=$(git rev-parse --short HEAD)
docker tag 172.28.0.11:5000/hello-world:ci_$COMMMIT_SHORT_SHA 172.28.0.11:5000/hello-world:latest
docker push 172.28.0.11:5000/hello-world:latest
'''
}
}
stage('Release:Tagged') {
when {
expression {
TAGGED = sh (
script: "git describe --exact-match --tags",
returnStatus: true
) == 0
return TAGGED
}
}
steps {
sh '''
COMMMIT_SHORT_SHA=$(git rev-parse --short HEAD)
COMMIT_TAG=$(git describe --exact-match --tags)
docker tag 172.28.0.11:5000/hello-world:ci_$COMMMIT_SHORT_SHA 172.28.0.11:5000/hello-world:$COMMIT_TAG
docker push 172.28.0.11:5000/hello-world:$COMMIT_TAG
'''
}
}
}
post {
success {
updateGitlabCommitStatus name: 'hello-world', state: 'success'
}
failure {
updateGitlabCommitStatus name: 'hello-world', state: 'failed'
}
}
}
Et si je souhaite conserver une trace de mon build ?
stage('Documentation') {
steps {
sh '''
echo "On s'eclate !" >> ma_documentation.txt
'''
archiveArtifacts artifacts: "ma_documentation.txt", fingerprint: true
}
}
Pratique !
http://127.0.0.1:8080/job/hello-world/lastSuccessfulBuild/artifact/ma_documentation.txt
Ajoutons un fichier docker-compose.yml à notre projet
version: '2'
services:
hello-world:
image: 172.28.0.1:5000/hello-world:latest
ports:
- 3000:3000
git add docker-compose.yml
git commit -m "chore(docker): Add a Docker orchestration file [skip ci]"
git push
Démarrons une instance de Rancher Server
docker run \
--detach \
--net jdev \
--ip 172.28.0.15 \
--publish 8081:8080 \
--name rancher-server \
rancher/server:v1.6.2
Vérifions que ce conteneur est bien démarré
Commençons par mettre à jour l'URL d'inscription des agents avec la valeur http://172.28.0.1:8081
Démarrons une instance de Rancher Agent sur le réseau par défaut de Docker
docker run \
--detach\
--rm \
--env CATTLE_AGENT_IP="172.17.0.1"
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume /var/lib/rancher:/var/lib/rancher \
rancher/agent:v1.2.2 http://172.28.0.1:8080/v1/scripts/FEADA433891177754729:1483142400000:F3TVBX3X1LGIL9wHVoUnMMiQkYk
Nous disposons maintenant d'un esclave Rancher.
Rendons-nous à l'adresse suivante et ajoutons une nouvelle stack.
http://127.0.0.1:8081/env/1a7/apps/stacks?which=all
Appelons cette stack staging et collons le contenu de notre fichier docker-compose.yml dans le bloc approprié.
Commençons par récupérer un accès à l'API de Rancher
http://127.0.0.1:8081/env/1a5/api/keys
Mettons notre pipeline à jour
pipeline {
agent {
label 'docker'
}
stages {
stage('Checkout') {
steps {
updateGitlabCommitStatus name: 'hello-world', state: 'running'
checkout(
scm: [
$class: "GitSCM",
userRemoteConfigs: [
[url: "git@172.28.0.10:jdev/hello-world.git", credentialsId: "< id de votre credential ssh >"]
],
branches: [
[name: env.gitlabSourceBranch]
]
]
)
}
}
stage('Build') {
steps {
sh '''
COMMMIT_SHORT_SHA=$(git rev-parse --short HEAD)
docker build -t 172.28.0.11:5000/hello-world:ci_$COMMMIT_SHORT_SHA .
docker push 172.28.0.11:5000/hello-world:ci_$COMMMIT_SHORT_SHA
'''
}
}
stage('Tests') {
steps {
sh '''
COMMMIT_SHORT_SHA=$(git rev-parse --short HEAD)
docker run 172.28.0.11:5000/hello-world:ci_$COMMMIT_SHORT_SHA test
'''
}
}
stage('Release:Latest') {
when {
expression {
return env.gitlabSourceBranch == 'master'
}
}
steps {
sh '''
COMMMIT_SHORT_SHA=$(git rev-parse --short HEAD)
docker tag 172.28.0.11:5000/hello-world:ci_$COMMMIT_SHORT_SHA 172.28.0.11:5000/hello-world:latest
docker push 172.28.0.11:5000/hello-world:latest
'''
}
}
stage('Release:Tagged') {
when {
expression {
TAGGED = sh (
script: "git describe --exact-match --tags",
returnStatus: true
) == 0
return TAGGED
}
}
steps {
sh '''
COMMMIT_SHORT_SHA=$(git rev-parse --short HEAD)
COMMIT_TAG=$(git describe --exact-match --tags)
docker tag 172.28.0.11:5000/hello-world:ci_$COMMMIT_SHORT_SHA 172.28.0.11:5000/hello-world:$COMMIT_TAG
docker push 172.28.0.11:5000/hello-world:$COMMIT_TAG
'''
}
}
stage('Deploy:Staging') {
when {
expression {
return env.gitlabSourceBranch == 'master'
}
}
steps {
sh '''
docker run --rm --volume $(pwd):/mnt rancher/cli --env Default --url http://172.28.0.1:8081/v1 --access-key <RANCHER_ACCESS_KEY> --secret-key <RANCHER_SECRET_KEY> up -d --upgrade --force-upgrade --stack staging --pull hello-world
docker run --rm --volume $(pwd):/mnt rancher/cli --env Default --url http://172.28.0.1:8081/v1 --access-key <RANCHER_ACCESS_KEY> --secret-key <RANCHER_SECRET_KEY> up -d --confirm-upgrade --stack staging
'''
}
}
}
post {
success {
updateGitlabCommitStatus name: 'hello-world', state: 'success'
}
failure {
updateGitlabCommitStatus name: 'hello-world', state: 'failed'
}
}
}
Il ne nous reste qu'à l'essayer
http://127.0.0.1/jdev/hello-world/settings/integrations
Des sceptiques ?
Modifions notre Hello world! (api.js)
http://127.0.0.1/jdev/hello-world/edit/master/api.js
## En résumé
- Installation de Gitlab CE
- Installation d'un registre Docker et de son interface graphique
- Installation de Jenkins
- Installation d'un slave Docker-in-Docker pour Jenkins
- Configuration d'un pipeline de CI
- Mise en place d'un environnement d'intégration
- Configuration du déploiement automatique des services
JDEV2017 - Frédéric Woelffel