Comment créer mon site Web personnel à l’aide de conteneurs avec un Makefile


  • Français


  • le make L’utilitaire et son Makefile associé sont utilisés depuis longtemps pour créer des logiciels. Le Makefile définit un ensemble de commandes à exécuter, et le make l’utilitaire les exécute. Il est similaire à un Dockerfile ou à un Containerfile, un ensemble de commandes utilisées pour créer des images de conteneur.

    Ensemble, un Makefile et un Containerfile sont un excellent moyen de gérer un projet basé sur des conteneurs. Le Containerfile décrit le contenu de l’image du conteneur et le Makefile décrit comment gérer le projet lui-même : lancer la création, le test et le déploiement de l’image, entre autres commandes utiles.

    Faire des cibles

    Le Makefile est constitué de “cibles”: une ou plusieurs commandes regroupées sous une seule commande. Vous pouvez exécuter chaque cible en exécutant le make commande suivie de la cible que vous souhaitez exécuter :

    # Runs the "build_image" make target from the Makefile
    $ make build_image

    C’est la beauté du Makefile. Vous pouvez créer une collection de cibles pour chaque tâche qui doit être effectuée manuellement. Dans le contexte d’un projet basé sur un conteneur, cela inclut la création de l’image, sa transmission à un registre, le test de l’image et même le déploiement de l’image et la mise à jour du service qui l’exécute. J’utilise un Makefile pour mon site Web personnel pour effectuer toutes ces tâches de manière simple et automatisée.

    Construire, tester, déployer

    Je crée mon site web en utilisant Hugo, un générateur de site Web statique qui crée du code HTML statique à partir de fichiers YAML. J’utilise Hugo pour créer les fichiers HTML pour moi, puis créer une image de conteneur avec ces fichiers et Caddie, un serveur Web simple et rapide, et exécutez cette image en tant que conteneur. (Hugo et Caddy sont tous deux des projets open source sous licence Apache.) J’utilise un Makefile pour faciliter la création et le déploiement de cette image en production.

    La première cible dans le Makefile est de manière appropriée le image_build commander:

    image_build:
      podman build --format docker -f Containerfile -t $(IMAGE_REF):$(HASH) .

    Cette cible appelle Podman pour construire une image à partir du Containerfile inclus dans le projet. Il y a quelques variables dans la commande ci-dessus, quelles sont-elles ? Les variables peuvent être spécifiées dans le Makefile, de la même manière que Bash ou un langage de programmation. Je les utilise pour une variété de choses dans le Makefile, mais le plus utile est de créer la référence d’image à transmettre aux registres d’images de conteneurs distants :

    # Image values
    REGISTRY := "us.gcr.io"
    PROJECT := "my-project-name"
    IMAGE := "some-image-name"
    IMAGE_REF := $(REGISTRY)/$(PROJECT)/$(IMAGE)

    # Git commit hash
    HASH := $(shell git rev-parse --short HEAD)

    En utilisant ces variables, le image_build target construit une référence d’image comme us.gcr.io/my-project-name/my-image-name:abc1234 en utilisant le hachage de révision Git court comme balise d’image afin qu’il puisse être lié au code qui l’a construit facilement.

    Le Makefile marque ensuite cette image comme :latest. je n’utilise généralement pas :latest pour tout ce qui est en production, mais plus bas dans ce Makefile, il sera utile pour le nettoyage :

    image_tag:
      podman tag $(IMAGE_REF):$(HASH) $(IMAGE_REF):latest

    Donc, maintenant, l’image a été construite et doit être validée pour s’assurer qu’elle répond à certaines exigences minimales. Pour mon site Web personnel, il s’agit simplement de « le serveur Web démarre-t-il et renvoie-t-il quelque chose ? » Cela pourrait être accompli avec des commandes shell dans le Makefile, mais il m’était plus facile d’écrire un script Python qui démarre un conteneur avec Podman, envoie une requête HTTP au conteneur, vérifie qu’il reçoit une réponse, puis nettoie le conteneur. La gestion des exceptions « essayer, sauf, finalement » de Python est parfaite pour cela et considérablement plus facile que de répliquer la même logique à partir de commandes shell dans un Makefile :

    #!/usr/bin/env python3

    import time
    import argparse
    from subprocess import check_call, CalledProcessError
    from urllib.request import urlopen, Request

    parser = argparse.ArgumentParser()
    parser.add_argument('-i', '--image', action='store', required=True, help='image name')
    args = parser.parse_args()

    print(args.image)

    try:
        check_call("podman rm smk".split())
    except CalledProcessError as err:
        pass

    check_call(
        "podman run --rm --name=smk -p 8080:8080 -d {}".format(args.image).split()
    )

    time.sleep(5)

    r = Request("http://localhost:8080", headers={'Host': 'chris.collins.is'})
    try:
        print(str(urlopen(r).read()))
    finally:
        check_call("podman kill smk".split())

    Cela pourrait être un test plus approfondi. Par exemple, pendant le processus de génération, le hachage de révision Git peut être intégré à la réponse et le test peut vérifier que la réponse inclut le hachage attendu. Cela aurait l’avantage de vérifier qu’au moins une partie du contenu attendu est là.

    Si tout se passe bien avec les tests, alors l’image est prête à être déployée. J’utilise le service Cloud Run de Google pour héberger mon site Web et, comme tous les principaux services cloud, il existe un excellent outil d’interface de ligne de commande (CLI) que je peux utiliser pour interagir avec le service. Étant donné que Cloud Run est un service de conteneur, le déploiement consiste à transférer les images créées localement vers un registre de conteneurs distant, puis à lancer un déploiement du service à l’aide de la gcloud Outil CLI.

    Vous pouvez faire le push en utilisant Podman ou Skopeo (ou Docker, si vous l’utilisez). Ma cible push pousse le $(IMAGE_REF):$(HASH) l’image et aussi la :latest étiqueter:

    push:
      podman push --remove-signatures $(IMAGE_REF):$(HASH)
      podman push --remove-signatures $(IMAGE_REF):latest

    Une fois l’image poussée, utilisez le gcloud run deploy pour déployer l’image la plus récente dans le projet et faire vivre la nouvelle image. Encore une fois, le Makefile est utile ici. je peux préciser le --platform et --region arguments en tant que variables dans le Makefile afin que je n’aie pas à m’en souvenir à chaque fois. Soyons honnêtes : j’écris si rarement pour mon blog personnel, il n’y a aucune chance que je me souvienne de ces variables si je devais les taper de mémoire à chaque fois que je déployais une nouvelle image :

    rollout:
      gcloud run deploy $(PROJECT) --image $(IMAGE_REF):$(HASH) --platform $(PLATFORM) --region $(REGION)

    Plus de cibles

    Il y a d’autres utiles make cibles. Lors de l’écriture de nouveaux éléments ou du test de CSS ou de modifications de code, j’aime voir sur quoi je travaille localement sans le déployer sur un serveur distant. Pour cela, mon Makefile a un run_local commande, qui lance un conteneur avec le contenu de mon commit actuel et ouvre mon navigateur à l’URL de la page hébergée par le serveur Web exécuté localement :

    .PHONY: run_local
    run_local:
      podman stop mansmk ; podman rm mansmk ; podman run --name=mansmk --rm -p $(HOST_ADDR):$(HOST_PORT):$(TARGET_PORT) -d $(IMAGE_REF):$(HASH) && $(BROWSER) $(HOST_URL):$(HOST_PORT)

    J’utilise aussi une variable pour le nom du navigateur, donc je peux tester avec plusieurs si je veux. Par défaut, il s’ouvrira dans Firefox lorsque je lance make run_local. Si je veux tester la même chose dans Google, je lance make run_local BROWSER="google-chrome".

    Lorsque vous travaillez avec des conteneurs et des images de conteneurs, le nettoyage des anciens conteneurs et images est une corvée ennuyeuse, en particulier lorsque vous effectuez des itérations fréquentes. J’inclus également des cibles dans mon Makefile pour gérer ces tâches. Lors du nettoyage d’un conteneur, si le conteneur n’existe pas, Podman ou Docker reviendra avec un code de sortie de 125. Malheureusement, make s’attend à ce que chaque commande renvoie 0 ou qu’elle arrête le traitement, j’utilise donc un script wrapper pour gérer ce cas :

    #!/usr/bin/env bash

    ID="${@}"

    podman stop ${ID} 2>/dev/null

    if [[ $?  == 125 ]]
    then
      # No such container
      exit 0
    elif [[ $? == 0 ]]
    then
      podman rm ${ID} 2>/dev/null
    else
      exit $?
    fi

    Le nettoyage des images nécessite un peu plus de logique, mais tout peut être fait dans le Makefile. Pour ce faire facilement, j’ajoute une étiquette (via le Containerfile) à l’image lors de sa construction. Cela facilite la recherche de toutes les images avec ces étiquettes. La plus récente de ces images peut être identifiée en recherchant le :latest étiqueter. Enfin, toutes les images, à l’exception de celles pointant vers l’image taguée avec :latest, peut être supprimé :

    clean_images:
      $(eval LATEST_IMAGES := $(shell podman images --filter "label=my-project.purpose=app-image" --no-trunc | awk '/latest/ {print $$3}'))
      podman images --filter "label=my-project.purpose=app-image" --no-trunc --quiet | grep -v $(LATEST_IMAGES) | xargs --no-run-if-empty --max-lines=1 podman image rm

    C’est à ce stade que l’utilisation d’un Makefile pour gérer des projets de conteneurs aboutit vraiment à quelque chose de cool. À ce stade, le Makefile inclut des commandes pour créer et marquer des images, tester, pousser des images, déployer une nouvelle version, nettoyer un conteneur, nettoyer des images et exécuter une version locale. Exécuter chacun d’eux avec make image_build && make image_tag && make test… etc. est considérablement plus facile que d’exécuter chacune des commandes d’origine, mais cela peut être encore simplifié.

    Un Makefile peut regrouper des commandes dans une cible, permettant à plusieurs cibles de s’exécuter avec une seule commande. Par exemple, mon Makefile regroupe les image_build et image_tag cibles dans le cadre de la build cible, donc je peux exécuter les deux en utilisant simplement make build. Mieux encore, ces cibles peuvent être regroupées dans la valeur par défaut make cible, all, me permettant de les exécuter tous dans l’ordre en exécutant make all ou plus simplement, make.

    Pour mon projet, je veux la valeur par défaut make action pour tout inclure, de la création de l’image au test, au déploiement et au nettoyage, j’inclus donc les cibles suivantes :

    .PHONY: all
    all: build test deploy clean

    .PHONY: build image_build image_tag
    build: image_build image_tag

    .PHONY: deploy push rollout
    deploy: push rollout

    .PHONY: clean clean_containers clean_images
    clean: clean_containers clean_images

    Cela fait tout ce dont j’ai parlé dans cet article, sauf le make run_local cible, en une seule commande : make.

    Conclusion

    Un Makefile est un excellent moyen de gérer un projet basé sur des conteneurs. En combinant toutes les commandes nécessaires pour construire, tester et déployer un projet dans make cibles dans le Makefile, tout le travail « méta »—tout à part l’écriture du code—peut être simplifié et automatisé. Le Makefile peut même être utilisé pour des tâches liées au code : exécuter des tests unitaires, maintenir des modules, compiler des binaires et des sommes de contrôle. Bien qu’il ne puisse pas encore écrire de code pour vous, l’utilisation d’un Makefile combiné aux avantages d’un service conteneurisé basé sur le cloud peut make (clin d’œil, clin d’œil) gérer de nombreux aspects d’un projet beaucoup plus facilement.

    Source

    Houssen Moshinaly

    Pour contacter personnellement le taulier :

    Laisser un commentaire

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

    Copy code