Générer une image automatisée avec Jenkins, Packer et Kubernetes

La création d'images personnalisées pour démarrer vos instances Compute Engine ou vos conteneurs Docker permet de réduire le temps de démarrage et d'augmenter la fiabilité. En préinstallant le logiciel dans une image personnalisée, vous réduisez également votre dépendance à la disponibilité de dépôts tiers hors de votre contrôle.

Vous choisissez le nombre de composants logiciels et la configuration à inclure dans vos images personnalisées. À une extrémité du spectre, l'image d'une configuration minimale, appelée image de base dans ce document, contient l'image du système d'exploitation de base (Ubuntu 14.10 par exemple) et peut également inclure un logiciel et une configuration de base. Vous pouvez par exemple préinstaller des environnements d’exécution de langage, tels que Java ou Ruby, configurer la journalisation à distance ou appliquer des correctifs de sécurité. Une image de base fournit une image de référence stable qui peut être personnalisée pour servir une application.

À l'autre extrémité, une image entièrement configurée (appelée image immuable dans ce document) contient non seulement le système d'exploitation de base ou une image de base, mais également tout ce qui est nécessaire pour exécuter une application. La configuration de l'environnement d'exécution, telle que les informations de connexion à une base de données ou les données sensibles, peut être incluse dans l'image ou fournie via l'environnement, des métadonnées ou le service de gestion de clés au moment du lancement.

Le processus de création d’images a de nombreuses similitudes avec le développement logiciel : vous avez du code (Chef, Puppet, bash, etc.) et des personnes qui l’écrivent ; la compilation s'effectue lorsque vous appliquez le code à une image de base ; un processus de compilation réussi génère un artefact ; et vous voulez souvent soumettre l'artefact à des tests. La plupart des bonnes pratiques applicables à la création de logiciels s'appliquent également à la création d'images : vous pouvez contrôler les versions pour gérer les scripts de configuration d'image ; déclencher des compilations lorsque des modifications sont apportées à ces scripts ; effectuer automatiquement des compilations d'image ; gérer les versions et même tester les artefacts d’image obtenus après compilation.

Objectifs d'apprentissage

Dans cette solution, vous découvrirez deux approches générales permettant de créer des images personnalisées. Vous apprendrez également à vous servir de plusieurs outils Open Source courants (tels que Jenkins, Packer, Docker et Kubernetes) pour concevoir un pipeline automatisé permettant de générer des images en continu. Ce pipeline s'intègre à Cloud Source Repositories dans Google Cloud Platform (GCP) et génère à la fois des images Compute Engine et des images Docker.

Vous apprendrez à créer des images de base et des images immuables, et découvrirez les bonnes pratiques pour gérer l'accès à ces images dans plusieurs projets GCP. Enfin, un tutoriel complet à la fin du document vous permettra de déployer et d'utiliser une implémentation de référence Open Source de la solution.

Types d'image

Dans la solution Applications Web évolutives et résilientes, une application Web Ruby on Rails est utilisée comme référence pour l'exécution d'applications Web sur GCP. Le code source de cette solution n'utilise pas d'images personnalisées. Lorsqu'une instance Compute Engine démarre, un script de démarrage installe Chef Solo, lequel installe ensuite tous les éléments nécessaires pour exécuter l'application. Cela inclut nginx, Ruby 2, cURL et d’autres outils système, Unicorn, l’application Rails et tous ses précieux composants, imagemagick et la configuration de l’application.

Le schéma ci-dessous décrit le processus de démarrage.

Schéma illustrant le processus de démarrage sans image personnalisée.

Le processus n'est pas rapide : il faut 10 à 15 minutes à chaque instance pour démarrer, en fonction de la vitesse de téléchargement des différents dépôts requis pour les paquets. Et ce, si chaque dépôt hébergeant ces paquets est en ligne et disponible. Dans les sections suivantes, vous allez examiner comment une image de base et une image immuable peuvent améliorer les performances et la fiabilité du processus de démarrage d'instance.

Images de base

Lors de la création d'une image de base, vous choisissez les logiciels et les paquets à inclure dans l'image. Voici quelques critères à prendre en compte pour prendre cette décision :

  • Vitesse d'installation : les gros paquets peuvent être lents à télécharger ; les logiciels qui doivent être créés à partir de la source peuvent prendre beaucoup de temps ; et les paquets comprenant de nombreuses dépendances aggravent le problème. Réfléchissez avant d'inclure ces types de logiciels et de packages dans votre image de base.
  • Fiabilité du dépôt distant : si vous n'incluez pas le logiciel dans votre image de base, mais le téléchargez au démarrage, faites-vous confiance à la disponibilité du dépôt distant ? Si ce dépôt n'est pas disponible au démarrage, cela empêchera-t-il votre application de fonctionner ? Pour réduire votre dépendance aux dépôts distants que vous ne contrôlez pas nécessairement, pensez à inclure les dépendances critiques dans l'image de base.
  • Taux de changement : le logiciel ou le paquet change-t-il très souvent ? Si la réponse est oui, envisagez de ne pas l'inclure dans l'image de base et de le stocker dans un emplacement fiable et accessible, tel qu'un bucket Cloud Storage.
  • Obligatoire ou obligatoire en matière de sécurité : si certains paquets (tels que la journalisation, OSSEC, etc.) doivent être exécutés avec une configuration spécifique sur chaque instance au sein de votre organisation, ils doivent être installés dans une image de base qui sera étendue par toutes les autres images. L'équipe de sécurité peut utiliser un outil plus avancé tel que Chef ou Puppet pour créer une image de base Docker, tandis que les développeurs en aval peuvent utiliser un fichier Docker pour étendre facilement la base.

Selon ces critères, une image de base de l'application Ruby on Rails issue de la solution d'applications Web évolutives et résilientes peut inclure Chef Solo, nginx, Ruby, cURL et d'autres outils système, ainsi que Unicorn ; les autres dépendances étant installées au démarrage.

Le schéma ci-dessous décrit le processus de démarrage à partir d'une image de base :

Schéma illustrant le processus de démarrage avec une image de base.

Dans cet exemple, l'instance fonctionnelle récupère sa configuration (chaîne de connexion à la base de données, clés d'API, etc.) à partir du service de métadonnées Compute Engine. Vous pouvez choisir d'utiliser différents services, tels qu'etcd ou un simple bucket Cloud Storage pour gérer la configuration.

Les prochaines sections se concentrent sur les outils utilisés pour automatiser la création de l’image de base Ruby illustrée ici.

Images immuables

Contrairement à une image de base, une image immuable contient tous ses logiciels associés. Lorsqu'une instance ou un conteneur est lancé à partir de l'image, il n'y a aucun paquet à télécharger ni logiciel à installer. Une image immuable de l'application Ruby on Rails issue de la solution Applications Web évolutives et résilientes inclurait tous les logiciels, et l'instance serait prête à diffuser le trafic au démarrage.

Schéma illustrant le processus de démarrage avec une image immuable.

Configuration et images immuables

Vous pouvez choisir de laisser votre application accéder aux données de configuration dont elle a besoin à partir d'un service de configuration ou vous pouvez inclure toutes les configurations dans l'image immuable. Si vous choisissez cette dernière approche, prenez bien en compte les implications pour la sécurité de l'inclusion de secrets dans vos images. Si vous transférez des images immuables vers des dépôts publics dans Docker Hub, elles sont accessibles à tous et ne doivent contenir aucune information sensible ou secrète.

Images immuables comme unité de déploiement

L'utilisation d'images immuables comme unité de déploiement élimine le risque d'écarts de configuration, dans lesquels une ou plusieurs instances sont dans un état différent de celui attendu. Cela peut arriver par exemple lorsque vous appliquez un correctif de sécurité à 100 conteneurs en cours d'exécution et que certains d'entre eux ne parviennent pas à se mettre à jour. L'image devient l'élément que vous déployez quelle que soit la modification apportée. Si le système d'exploitation nécessite un correctif logiciel ou si la configuration de journalisation doit être mise à jour, vous créez une nouvelle image pour inclure ces modifications et vous la déployez en lançant de nouvelles instances ou conteneurs, et en remplaçant toutes les anciennes. Si vous choisissez de regrouper la configuration de l'application dans une image immuable, une simple modification telle que la mise à jour d'une chaîne de connexion à la base de données signifie la création et la publication d'une nouvelle image.

Architecture et implémentation d'un pipeline automatisé de génération d'images

Cette section détaille la mise en œuvre d'un pipeline automatisé de création d'images. Vous utiliserez les outils Jenkins, Packer, Docker et Google Kubernetes Engine (GKE) pour créer automatiquement des images personnalisées. Chaque sous-section comprend une introduction, un schéma de l'architecture utilisée et une analyse détaillée des composants du schéma.

Logiciels et services utilisés

Les logiciels et services suivants sont utilisés pour créer le générateur automatique d'images :

Logiciel Utilisation
Jenkins Jenkins est un serveur Open Source d’intégration continue (CI). Vous utiliserez Jenkins pour interroger les dépôts Git dans d'autres projets contenant des scripts de configuration d'image, puis pour créer des images à partir de ces dépôts.
Packer Packer est un outil Open Source qui permet de créer des images système identiques pour plusieurs plates-formes à partir d'une seule configuration source. Il accepte de nombreuses sources de configuration, telles que Shell, Chef, Puppet, Ansible et Salt, et peut générer des images pour Compute Engine, Docker et bien d'autres. Les agents Jenkins utilisent Packer pour créer des images à partir de configurations dans les dépôts Git.
Docker Docker est un outil Open Source permettant d'empaqueter et de déployer des applications en tant que conteneurs. Le déploiement Jenkins (y compris le nœud maître et les agents de création) dans cette architecture et ce tutoriel s'effectue au moyen de conteneurs Docker. Les agents de création produisent également des images Docker dans l’une de leurs architectures.
GKE GKE, optimisé par la technologie Open Source Kubernetes, vous permet d'exécuter et de gérer les conteneurs Docker sur les machines virtuelles de GCP.
Container Registry Container Registry fournit un espace de stockage sécurisé et privé d'images Docker sur GCP. Il fonctionne sur GCP et est accessible via un point de terminaison HTTPS.
Instance GKE utilise les VM Compute Engine pour exécuter Kubernetes et pour héberger le maître et les conteneurs d'agent de création Jenkins. Le processus de création Jenkins génère également des images de VM Compute Engine en plus des images Docker.
Cloud Storage Vous utiliserez Cloud Storage pour stocker des sauvegardes de votre configuration Jenkins.
Nginx Nginx fournit une fonctionnalité de proxy inverse ; il transmet les requêtes entrantes à l'interface Web du nœud maître Jenkins. Il peut être configuré pour mettre fin aux connexions SSL et fournir une authentification de base.

Vue d'ensemble du générateur d'images

Le schéma ci-dessous montre comment divers composants interagissent pour créer un système qui génère automatiquement des VM et des images Docker.

Schéma illustrant les différentes composantes du projet de génération d’images.

Vous définissez une tâche sur le maître Jenkins pour chaque image que vous souhaitez créer. La tâche interroge un dépôt de code source, Git dans cette illustration, qui contient des scripts de configuration et un modèle Packer décrivant comment créer une image. Lorsque le processus d'interrogation détecte une modification, le maître Jenkins attribue la tâche à un agent de build. L'agent utilise Packer pour exécuter la compilation, laquelle génère une image Docker dans Container Registry et une image de VM dans Compute Engine.

Packer et scripts de configuration

Un modèle Packer associé à des scripts de configuration définit la manière de créer une image. Ces éléments sont traités comme des logiciels et stockés dans un dépôt Git qui leur est propre. Chaque image que vous créez aura son propre dépôt contenant un modèle Packer et des scripts de configuration.

Cette section présente une configuration possible de Packer qui utilise Chef pour personnaliser Ubuntu 14.04 en ajoutant Ruby et rbenv. Pour des informations complètes sur Packer, consultez l'excellente documentation qui lui est consacrée sur https://www.packer.io/docs.

Noms des images et variables Packer

Le générateur d'images crée une image chaque fois qu'une modification est apportée au dépôt Git contenant le modèle Packer et les scripts de configuration associés à l'image. Il est recommandé de nommer les images ou de leur ajouter des tags à l'aide de la branche Git et de l'ID du commit à partir desquels elles ont été créées. Les modèles Packer vous permettent de définir des variables et de leur attribuer des valeurs lors de l'exécution :

{
...
  "variables": {
      "Git_commit": "",
      "Git_branch": "",
      "ruby_version_name": "212",
      "project_id": "null"
  }
...
}

L'agent de création Jenkins est capable de détecter la branche Git et l'ID de commit et de les fournir sous forme de variables à l'outil de ligne de commande de Packer. Vous verrez cela plus concrètement dans la section Tutoriel de ce document.

Configuration programmatique avec approvisionneurs

Un modèle Packer définit un ou plusieurs approvisionneurs qui décrivent comment utiliser des outils tels que Chef, Puppet ou les scripts Shell pour configurer une instance. Packer prend en charge de nombreux approvisionneurs ; pour en obtenir la liste complète, consultez la table des matières de la documentation Packer. Cet extrait définit un approvisionneur chef-solo avec des chemins vers des livres de recettes et des recettes à exécuter pour configurer une image :

{
  ...
  "provisioners": [
    {
      "type": "chef-solo",
      "install_command": "apt-get install -y curl && curl -L https://www.opscode.com/chef/install.sh | {{if .Sudo}}sudo{{end}} bash",
      "cookbook_paths": ["chef/site-cookbooks"],
      "run_list": [{{
        "recipe[ruby]",
        "recipe[ruby::user]",
        "recipe[ruby::ruby212]"
      ]
    }
  ],
  ...
}

Le livre de recettes et les recettes Chef sont stockés dans le même dépôt Git que le modèle Packer.

Définir l'emplacement des images générées avec des générateurs

La section builders du modèle définit l'emplacement où les approvisionneurs vont s'exécuter pour créer de nouvelles images. Pour créer une image Compute Engine et une image Docker, définissez deux paramètres builders :

{
  "variables": {...},
  "provisioners": [...],
  "builders": [
    {
      "type": "googlecompute",
      "project_id": "{{user `project_id`}}",
      "source_image": "ubuntu-1410-utopic-v20150202",
      "zone": "us-central1-a",
      "image_name": "{{user `ruby_version_name`}}-{{user `Git_branch`}}-{{user `Git_commit`}}"
    },
    {
      "type": "docker",
      "image": "ubuntu:14.10",
      "commit": "true"
    }
  ],
 ...
}

Le générateur googlecompute inclut un attribut project_id qui indique où sera stockée l'image résultante. L'attribut image_name, qui attribue un nom à l'image résultante, concatène les variables pour créer un nom comportant les informations sur l'image : la version de Ruby, la branche Git et l'ID de commit Git utilisés pour créer l'image. Un exemple d'URI pour une image créée par le générateur googlecompute peut se présenter comme suit :

https://www.googleapis.com/compute/v1/projects/image-builder-project-name/global/images/ruby212-master-9909043

Le générateur docker doit inclure l'attribut post-processors pour ajouter à l'image les tags du registre et du dépôt Docker dans lesquels elle sera déployée :

{
  "variables": {...},
  "provisioners": [...],
  "builders": [...],
  "post-processors": [
    [
      {
        "type": "docker-tag",
        "repository": "gcr.io/{{user `project_id`}}/ruby212",
        "tag": "{{user `Git_branch`}}-{{user `Git_commit`}}",
        "only": ["docker"]
      }
    ]
  ]
}

Ce post-processeur ajoutera des tags à l'image en vue de son stockage dans Container Registry avec l'ID de projet project_id fourni lors de l'exécution de la compilation. Une fois l'image Docker déployée, vous pouvez la récupérer :

docker pull gcr.io/image-builder-project-name/ruby212:master-9909043

Chaque image à compiler sera associée à un modèle Packer et des scripts de configuration stockés dans son propre dépôt source, et une tâche sera définie pour l'image sur le maître Jenkins, comme indiqué dans le schéma ci-dessous.

Schéma illustrant le projet de génération d’images avec des images personnalisées.

L’avantage d'utiliser conjointement Jenkins et Packer est que Jenkins peut détecter toutes les mises à jour apportées aux modèles ou aux scripts de configuration de Packer et y répondre. Par exemple, si vous mettez à jour la version de Ruby installée dans votre image de base Ruby, le maître Jenkins répond en affectant un agent pour cloner le dépôt, exécuter Packer selon le modèle et créer les images.

Le tutoriel à la fin de cette solution explique en détail le processus de configuration d’une tâche Jenkins pour exécuter la compilation Packer.

Isoler le projet

Le maître Jenkins et les agents de build s'exécutent ensemble dans le même projet Cloud Platform et les images qu'ils créent sont stockées dans ce projet. Les projets vous permettent d’isoler les applications par fonction. Ils sont gratuits ; vous ne payez que pour les ressources que vous utilisez. Dans cette solution, l'infrastructure Jenkins s'exécutera dans son propre projet, séparé des dépôts de contrôle de code source qu'elle utilise. Les sauvegardes Jenkins, décrites dans une section à venir, sont stockées dans un bucket Google Cloud Storage dans le projet. Jenkins peut ainsi servir de "concentrateur d'images" et partager des images avec d'autres projets, tout en permettant à d'autres projets de gérer leurs propres dépôts de code avec des contrôles d'accès distincts.

Créer et partager des images au sein d'une organisation

Pour faciliter le partage d'images, cette solution place chaque image de build stockée dans Git dans un projet de configuration d'image distinct. Cette séparation permet d'isoler le projet entre le projet de génération d'images et les images de build. Avec cette architecture en étoile, dont le projet de génération d’images est le centre et les projets de configuration d’images sont les rayons, des équipes distinctes peuvent plus facilement être propriétaires des configurations d’images et les gérer.

Cette architecture en étoile est illustrée dans le schéma qui suit.

Schéma illustrant le projet de génération d’images en tant que système en étoile.

Le contrôle d'accès (qui accorde au cluster Jenkins l'accès à chaque projet d'image et à d'autres projets l'accès aux images créées par Jenkins) sera présenté ci-dessous.

Un projet par image

Chaque projet que vous créez est associé à un dépôt Cloud Repository dédié sur Git. Vous pouvez créer autant de projets que vous le souhaitez, et vous ne payez que pour les ressources, telles que les instances Compute Engine, que vous utilisez dans un projet. Par exemple, si vous avez des images PHP, Ruby et Wordpress, chacune aura son propre projet visible dans la console Google Cloud Platform, comme illustré dans le schéma qui suit.

Schéma illustrant le projet de génération d’images avec des projets distincts pour chaque image personnalisée.

Le dépôt Cloud Repository d'un projet est accessible à partir de l'élément de menu Source Code (Code source). Pour les nouveaux projets, vous choisissez comment initialiser le dépôt : vous pouvez mettre en miroir un dépôt GitHub ou Bitbucket existant, déployer un dépôt Git local existant ou créer un dépôt Git local à partir de Cloud Source Repositories, comme illustré dans l'image ci-dessous.

Capture d'écran expliquant comment parcourir le code source avec la console GCP.

L'image qui suit montre le projet d'image de base Ruby initialisé avec un modèle Packer et les recettes Chef définissant le build.

Image de base Ruby avec le modèle Packer et les recettes Chef.

Pour afficher l'URL du dépôt, cliquez sur Paramètres. Vous aurez besoin de cette URL lors de la création d'une tâche de build pour ce dépôt sur le maître Jenkins, comme illustré dans l'image ci-dessous.

Paramètres du dépôt source pour le nœud maître Jenkins.

Contrôler l'accès au dépôt Cloud Repository

Le générateur d'images Jenkins doit disposer des autorisations Consultation autorisée sur le dépôt Cloud Repository de chaque projet de configuration d'image. Le schéma qui suit illustre une vue simplifiée de l’architecture en étoile évoquée précédemment.

Projet de génération d'image avec les autorisations nécessaires.

Chaque projet doit autoriser l'accès au projet de génération d'images Jenkins à l'aide de l'adresse électronique du compte de service Compute du projet. Ce format d'adresse est \{PROJECT_ID\}-compute@developer.gserviceaccount.com et peut être copié dans la section Permissions (Autorisations) du projet dans la console GCP, comme illustré ci-dessous.

Adresse à copier à partir de la section Autorisations d'un projet.

Lorsque vous avez l'adresse électronique du compte de service Compute du projet exécutant le générateur d'images Jenkins, accédez à la section Permissions (Autorisations) de chaque projet disposant d'un dépôt Cloud Repository à partir duquel vous souhaitez créer des images, sélectionnez Add Member (Ajouter un membre) et sélectionnez l'autorisation Can view (Consultation autorisée), comme indiqué dans l'image ci-dessous.

Configurer les autorisations Consultation autorisée dans un projet.

Le maître Jenkins qui s'exécute dans le projet de création d'images sera désormais en mesure d'effectuer des interrogations et des extractions depuis ce dépôt Cloud Repository dans ces projets, et de mettre à jour les images au fur et à mesure des modifications.

Partager des images Compute Engine et Docker

Les images Compute Engine et Docker créées par le générateur d'images sont stockées dans le même projet que le générateur d'images. Les images seront utilisées par les applications d'autres projets pour lancer des instances Compute Engine et des conteneurs Docker. Chaque projet d'application voulant accéder à ces images doit disposer de l'autorisation Consultation autorisée pour le projet de génération d'images. Procédez comme indiqué dans la section précédente, en localisant cette fois le compte de service Compute de chaque projet d'application et en l'ajoutant en tant que membre avec l'autorisation Consultation autorisée pour le projet de génération d'images, comme illustré dans le schéma ci-dessous

Ajout d'un autre projet avec l'autorisation Consultation autorisée sur le projet de génération d'images.

Effectuer des sauvegardes et des restaurations dans Jenkins

Le maître Jenkins inclut une tâche prédéfinie pour la sauvegarde périodique de la configuration et de l'historique des tâches Jenkins dans Google Cloud Storage. Par défaut, la tâche est exécutée périodiquement (une fois toutes les deux heures, tous les jours de la semaine), comme indiqué dans l'image ci-dessous.

Paramètres de compilation automatique sur le maître Jenkins.

L'étape build de la tâche exécute un script shell qui archive les secrets, les utilisateurs, les tâches et l'historique dans un package tarball. Deux copies de l'archive sont créées : l'une est nommée à l'aide d'un horodatage, l'autre est nommée LATEST, ce qui vous permet de restaurer facilement et automatiquement la sauvegarde la plus récente. Vous pouvez personnaliser cette étape pour ajouter ou supprimer des éléments à sauvegarder, comme indiqué dans l'image ci-dessous.

Comment personnaliser le script de compilation.

Une action post-build utilise le plug-in Cloud Storage et les identifiants de métadonnées Google que vous avez créés pour interagir avec les API Google et télécharger l'archive de sauvegarde dans Cloud Storage. Elle télécharge à la fois les archives datestamp et LATEST. L'image qui suit montre comment définir cette étape.

Interface de définition des actions post-build.

L'image qui suit montre un bucket avec les sauvegardes accumulées :

Liste des sauvegardes accumulées pour un projet.

Restaurer une sauvegarde

De la même manière que vous utilisez des variables d'environnement pour activer SSL ou une authentification de base sur le proxy inverse Nginx dans une section précédente, vous pouvez utiliser une variable d'environnement pour configurer la définition du contrôleur de réplication sur le maître Jenkins de sorte qu'il restaure une sauvegarde au démarrage du service. Le code suivant est un extrait de la définition du contrôleur de réplication :

{
  "kind": "ReplicationController",
  ...
  "spec": {
    ...
    "template": {
      "spec": {
        "containers": [
            {
              "name": "jenkins",
              "env": [
                {
                  "name": "GCS_RESTORE_URL",
                  "value": "gs://your-backup-bucket/jenkins-backup/LATEST.tar.gz"
                }
              ],
             ...
           }
        ]
      }
    }
  }
}

L'image Docker sur le maître Jenkins vérifie l'existence de la variable d'environnement GCS_RESTORE_URL au démarrage. Si elle est présente, la valeur est supposée être l'URL de la sauvegarde (y compris le schéma gs://), et le script utilise l'outil de ligne de commande gsutil installé sur l'image du maître Jenkins pour télécharger et restaurer la sauvegarde en toute sécurité.

Le processus de restauration ne se produit que lorsqu'un conteneur est lancé. Pour restaurer une sauvegarde après avoir lancé un maître Jenkins, redimensionnez son contrôleur de réplication sur 0, mettez à jour la définition du contrôleur pour qu'il pointe vers l'URL de la sauvegarde, puis redéfinissez la taille sur 1. Cette opération est abordée dans le tutoriel.

Tutoriel

Le contenu complet du tutoriel, y compris les instructions et le code source, est disponible sur GitHub, à l'adresse https://github.com/GoogleCloudPlatform/kube-jenkins-imager.

Cette page vous a-t-elle été utile ? Évaluez-la :

Envoyer des commentaires concernant…