CI / CD utilisant Jenkins sur Kubernetes

Bon après-midi


Il existe déjà plusieurs articles sur Habré sur jenkins, ci / cd et kubernetes, mais je ne veux pas me concentrer sur l'analyse des capacités de ces technologies, mais sur leur configuration la plus simple pour construire le pipeline ci / cd.


Je veux dire que le lecteur a une compréhension de base de docker, et je ne m'attarderai pas sur les sujets d'installation et de configuration de kubernetes. Tous les exemples seront affichés sur minikube, mais peuvent également être appliqués sur EKS, GKE ou similaires sans modifications importantes.



L'environnement


Je suggère d'utiliser les environnements suivants:


  • test - pour le déploiement manuel et les tests de branche
  • staging - un environnement dans lequel toutes les modifications qui relèvent du maître sont automatiquement déployées
  • production - l'environnement utilisé par de vrais utilisateurs, où les changements ne se produiront qu'après confirmation de leur opérabilité lors de la mise en scène

Les environnements seront organisés à l'aide d'espaces de noms kubernetes au sein d'un seul cluster. Cette approche est aussi simple et rapide que possible au départ, mais a aussi ses inconvénients: les espaces de noms ne sont pas complètement isolés les uns des autres dans kubernetes.


Dans cet exemple, chaque espace de noms aura le même ensemble de ConfigMaps avec les configurations de cet environnement:


apiVersion: v1 kind: Namespace metadata: name: production --- apiVersion: v1 kind: ConfigMap metadata: name: environment.properties namespace: production data: environment.properties: | env=production 

Heaume


Helm est une application qui aide à gérer les ressources installées sur kubernetes.
Les instructions d'installation peuvent être trouvées ici .
Pour commencer, vous devez initialiser le module de barre pour utiliser Helm avec le cluster:


 helm init 

Jenkins


J'utiliserai Jenkins car c'est une plateforme assez simple, flexible et populaire pour construire des projets. Il sera installé dans un espace de noms distinct pour s'isoler des autres environnements. Étant donné que je prévois d'utiliser Helm à l'avenir, je peux simplifier l'installation de Jenkins en utilisant les graphiques open source existants:


 helm install --name jenkins --namespace jenkins -f jenkins/demo-values.yaml stable/jenkins 

demo-values.yaml contient la version Jenkins, un ensemble de plugins préinstallés, un nom de domaine et d'autres configurations


demo-values.yaml
 Master: Name: jenkins-master Image: "jenkins/jenkins" ImageTag: "2.163-slim" OverwriteConfig: true AdminUser: admin AdminPassword: admin InstallPlugins: - kubernetes:1.14.3 - workflow-aggregator:2.6 - workflow-job:2.31 - credentials-binding:1.17 - git:3.9.3 - greenballs:1.15 - google-login:1.4 - role-strategy:2.9.0 - locale:1.4 ServicePort: 8080 ServiceType: NodePort HostName: jenkins.192.168.99.100.nip.io Ingress: Path: / Agent: Enabled: true Image: "jenkins/jnlp-slave" ImageTag: "3.27-1" #autoadjust agent resources limits resources: requests: cpu: null memory: null limits: cpu: null memory: null #to allow jenkins create slave pods rbac: install: true 

Cette configuration utilise admin / admin comme nom d'utilisateur et mot de passe pour la connexion et peut être reconfigurée ultérieurement. Une des options possibles est Google SSO (le plug-in de connexion Google est requis pour cela, ses paramètres sont situés dans Jenkins> Gérer Jenkins> Configurer la sécurité globale> Contrôle d'accès> Domaine de sécurité> Connexion avec Google).


Jenkins sera immédiatement configuré pour créer automatiquement un esclave unique pour chaque build. Grâce à cela, l'équipe ne s'attendra plus à un agent libre pour l'assemblage, et l'entreprise pourra économiser sur le nombre de serveurs requis.



Également prêt à l'emploi, PersistenceVolume est configuré pour enregistrer les pipelines lors du redémarrage ou de la mise à jour.


Pour que les scripts de déploiement automatique fonctionnent correctement, vous devez accorder à Jenkins l'autorisation d'administrateur de cluster pour obtenir une liste de ressources dans kubernetes et les manipuler.


 kubectl create clusterrolebinding jenkins --clusterrole cluster-admin --serviceaccount=jenkins:default 

À l'avenir, vous pouvez mettre à jour Jenkins à l'aide de Helm en cas de nouvelles versions de plugins ou de changements de configuration.


 helm upgrade jenkins stable/jenkins -f jenkins/demo-values.yaml 

Cela peut être fait via l'interface de Jenkins lui-même, mais avec Helm vous aurez la possibilité de revenir aux révisions précédentes en utilisant:


 helm history jenkins helm rollback jenkins ${revision} 

Construction d'application


À titre d'exemple, je vais créer et déployer l'application Spring Boot la plus simple. De même avec Jenkins, j'utiliserai Helm.


L'assemblage se déroulera dans l'ordre suivant:


  • caisse
  • compilation
  • test unitaire
  • test d'intégration
  • assemblage d'artefact
  • déploiement d'artefacts dans le registre docker
  • déploiement d'artefact au transfert (uniquement pour la branche principale)

Pour cela, j'utilise le fichier Jenkins . À mon avis, c'est une façon très flexible (mais, malheureusement, pas la plus simple) de configurer l'assemblage du projet. L'un de ses avantages est la possibilité de conserver la configuration de l'ensemble de projet dans le référentiel avec le projet lui-même.


caisse



Dans le cas d'une organisation bitbucket ou github, vous pouvez configurer Jenkins pour analyser périodiquement l'intégralité du compte pour la présence de référentiels avec Jenkinsfile et créer automatiquement des assemblys pour eux. Jenkins collectera à la fois le maître et les branches. Les demandes d'extraction seront affichées dans un onglet séparé. Il existe une option plus simple - ajoutez un référentiel git séparé, quel que soit l'endroit où il est hébergé. Dans cet exemple, je ferai juste cela. Tout ce qui est nécessaire est dans le menu Jenkins> Nouvel élément> Multibranch Pipeline, sélectionnez le nom de l'assembly et liez le référentiel git.


Compilation


Puisque Jenkins crée un nouveau pod pour chaque assembly, dans le cas de l'utilisation de maven ou de collecteurs similaires, les dépendances seront à nouveau téléchargées à chaque fois. Pour éviter cela, vous pouvez allouer PersistenceVolume pour .m2 ou des caches similaires et le monter dans le pod qui construit le projet.


 apiVersion: "v1" kind: "PersistentVolumeClaim" metadata: name: "repository" namespace: "jenkins" spec: accessModes: - ReadWriteMany resources: requests: storage: 10Gi 

Dans mon cas, cela a permis d'accélérer le pipeline d'environ 4 à la 1ère minute.


Versioning


Pour que CI / CD fonctionne correctement, chaque build a besoin d'une version unique.


Une très bonne option serait d'utiliser le versionnage sémantique . Cela vous permettra de suivre les modifications rétrocompatibles et incompatibles, mais cette version est plus difficile à automatiser.


Dans cet exemple, je vais générer une version à partir de id et de la date de la validation, ainsi que du nom de la branche, si elle n'est pas maître. Par exemple 56e0fbdc-201802231623 ou b3d3c143-201802231548-PR-18 .


Avantages de cette approche:


  • facilité d'automatisation
  • il est facile d'obtenir le code source et son heure de création à partir de la version
  • visuellement, vous pouvez distinguer la version finale du candidat (de l'assistant) ou expérimentale (de la branche)
    mais:
  • cette version est plus difficile à utiliser dans la communication orale
  • il n'est pas clair s'il y a eu des changements incompatibles.

Étant donné que l'image docker peut avoir plusieurs balises en même temps, les approches peuvent être combinées: toutes les versions utilisent les versions générées, et celles qui tombent sur la production sont en outre (manuellement) balisées avec le versioning sémantique. Ceci, à son tour, est associé à une complexité d'implémentation encore plus grande et à l'ambiguïté de la version que l'application doit afficher.


Artefacts


Le résultat de l'assemblée sera:


  • image Docker avec une application qui sera stockée et chargée à partir du registre Docker. L'exemple utilisera le registre intégré de minikube, qui peut être remplacé par un hub docker ou un registre privé d'Amazon (ecr) ou de google (n'oubliez pas de leur fournir des informations d'identification à l'aide de la construction withCredentials).
  • des graphiques de barre avec une description du déploiement de l'application (déploiement, service, etc.) dans le répertoire de barre. Idéalement, ils devraient être stockés sur un référentiel séparé d'artefacts, mais, pour simplifier, ils peuvent être utilisés en vérifiant la validation nécessaire depuis git.

Jenkinsfile


Par conséquent, l'application sera créée à l'aide du fichier Jenkins suivant:


Jenkinsfile
 def branch def revision def registryIp pipeline { agent { kubernetes { label 'build-service-pod' defaultContainer 'jnlp' yaml """ apiVersion: v1 kind: Pod metadata: labels: job: build-service spec: containers: - name: maven image: maven:3.6.0-jdk-11-slim command: ["cat"] tty: true volumeMounts: - name: repository mountPath: /root/.m2/repository - name: docker image: docker:18.09.2 command: ["cat"] tty: true volumeMounts: - name: docker-sock mountPath: /var/run/docker.sock volumes: - name: repository persistentVolumeClaim: claimName: repository - name: docker-sock hostPath: path: /var/run/docker.sock """ } } options { skipDefaultCheckout true } stages { stage ('checkout') { steps { script { def repo = checkout scm revision = sh(script: 'git log -1 --format=\'%h.%ad\' --date=format:%Y%m%d-%H%M | cat', returnStdout: true).trim() branch = repo.GIT_BRANCH.take(20).replaceAll('/', '_') if (branch != 'master') { revision += "-${branch}" } sh "echo 'Building revision: ${revision}'" } } } stage ('compile') { steps { container('maven') { sh 'mvn clean compile test-compile' } } } stage ('unit test') { steps { container('maven') { sh 'mvn test' } } } stage ('integration test') { steps { container ('maven') { sh 'mvn verify' } } } stage ('build artifact') { steps { container('maven') { sh "mvn package -Dmaven.test.skip -Drevision=${revision}" } container('docker') { script { registryIp = sh(script: 'getent hosts registry.kube-system | awk \'{ print $1 ; exit }\'', returnStdout: true).trim() sh "docker build . -t ${registryIp}/demo/app:${revision} --build-arg REVISION=${revision}" } } } } stage ('publish artifact') { when { expression { branch == 'master' } } steps { container('docker') { sh "docker push ${registryIp}/demo/app:${revision}" } } } } } 

Pipelines Jenkins supplémentaires pour la gestion du cycle de vie des applications


Supposons que les référentiels sont organisés de telle sorte que:


  • contenir une application distincte sous forme d'image docker
  • peut être déployé à l'aide de fichiers helm situés dans le répertoire helm
  • sont versionnés en utilisant la même approche et ont un fichier helm / setVersion.sh pour définir les révisions dans les graphiques de barre

Ensuite, nous pouvons construire plusieurs pipelines Jenkinsfile pour gérer le cycle de vie de l'application, à savoir:



Dans le fichier Jenkins de chaque projet, vous pouvez ajouter un appel de pipeline de déploiement qui sera exécuté chaque fois que la branche principale est correctement compilée ou lorsque la branche de déploiement demande explicitement l'environnement de test.


Appel de pipeline de déploiement de fichier Jenkins
 ... stage ('deploy to env') { when { expression { branch == 'master' || params.DEPLOY_BRANCH_TO_TST } } steps { build job: './../Deploy', parameters: [ [$class: 'StringParameterValue', name: 'GIT_REPO', value: 'habr-demo-app'], [$class: 'StringParameterValue', name: 'VERSION', value: revision], [$class: 'StringParameterValue', name: 'ENV', value: branch == 'master' ? 'staging' : 'test'] ], wait: false } } ... 

Ici vous pouvez trouver Jenkinsfile avec toutes les étapes.


Ainsi, il est possible de construire un déploiement continu sur l'environnement de test ou de combat sélectionné, en utilisant également jenkins ou ses notifications par e-mail / slack / etc, avoir un audit de quelle application, quelle version, par qui, quand et où elle a été installée.


Conclusion


En utilisant Jenkinsfile et helm, vous pouvez simplement construire ci / cd pour votre application. Cette méthode peut être plus pertinente pour les petites équipes qui ont récemment commencé à utiliser kubernetes et ne sont pas en mesure (quelle qu'en soit la raison) d'utiliser des services qui peuvent fournir de telles fonctionnalités dès le départ.


Vous pouvez trouver des exemples de configuration pour les environnements, Jenkins et le pipeline pour gérer le cycle de vie de l'application ici et un exemple d'application avec Jenkinsfile ici .

Source: https://habr.com/ru/post/fr442614/


All Articles