⚠️ Traduction non officielle - Cette documentation est une traduction communautaire non officielle de Docker.

Construire votre image Go

Aperçu

Dans cette section, vous allez construire une image de conteneur. L'image comprend tout ce dont vous avez besoin pour exécuter votre application – le fichier binaire de l'application compilée , l'environnement d'exécution, les bibliothèques et toutes les autres ressources requises par votre application.

Logiciels requis

Pour suivre ce tutoriel, vous avez besoin des éléments suivants :

  • Docker s'exécutant localement. Suivez les instructions pour télécharger et installer Docker.
  • Un IDE ou un éditeur de texte pour modifier les fichiers. Visual Studio Code est un choix gratuit et populaire mais vous pouvez utiliser tout ce avec quoi vous vous sentez à l'aise.
  • Un client Git. Ce guide utilise un client git en ligne de commande, mais vous êtes libre d'utiliser ce qui vous convient.
  • Une application de terminal en ligne de commande. Les exemples présentés dans ce module proviennent du shell Linux, mais ils devraient fonctionner dans PowerShell, l'invite de commande Windows ou le terminal OS X avec des modifications minimes, voire inexistantes.

Rencontrez l'application d'exemple

L'application d'exemple est une caricature de microservice. Elle est volontairement triviale pour rester concentré sur l'apprentissage des bases de la conteneurisation pour les applications Go.

L'application offre deux points de terminaison HTTP :

  • Elle répond avec une chaîne contenant un symbole de cœur (<3) aux requêtes vers /.
  • Elle répond avec {"Status" : "OK"} JSON à une requête vers /health.

Elle répond avec l'erreur HTTP 404 à toute autre requête.

L'application écoute sur un port TCP défini par la valeur de la variable d'environnement PORT. La valeur par défaut est 8080.

L'application est sans état.

Le code source complet de l'application se trouve sur GitHub : github.com/docker/docker-gs-ping. Vous êtes encouragé à le forker et à l'expérimenter autant que vous le souhaitez.

Pour continuer, clonez le dépôt de l'application sur votre machine locale :

$ git clone https://github.com/docker/docker-gs-ping

Le fichier main.go de l'application est simple, si vous êtes familier avec Go :

package main

import (
	"net/http"
	"os"

	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
)

func main() {

	e := echo.New()

	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	e.GET("/", func(c echo.Context) error {
		return c.HTML(http.StatusOK, "Bonjour, Docker ! <3")
	})

	e.GET("/health", func(c echo.Context) error {
		return c.JSON(http.StatusOK, struct{ Status string }{Status: "OK"})
	})

	httpPort := os.Getenv("PORT")
	if httpPort == "" {
		httpPort = "8080"
	}

	e.Logger.Fatal(e.Start(":" + httpPort))
}

// Implémentation simple d'un minimum entier
// Adapté de : https://gobyexample.com/testing-and-benchmarking
func IntMin(a, b int) int {
	if a < b {
		return a
	}
	return b
}

Créer un Dockerfile pour l'application

Pour construire une image de conteneur avec Docker, un Dockerfile avec des instructions de construction est nécessaire.

Commencez votre Dockerfile avec la ligne de directive de l'analyseur (facultative) qui indique à BuildKit d'interpréter votre fichier selon les règles de grammaire pour la version spécifiée de la syntaxe.

Vous indiquez ensuite à Docker quelle image de base vous souhaitez utiliser pour votre application :

# syntax=docker/dockerfile:1

FROM golang:1.19

Les images Docker peuvent être héritées d'autres images. Par conséquent, au lieu de créer votre propre image de base à partir de zéro, vous pouvez utiliser l'image Go officielle qui dispose déjà de tous les outils et bibliothèques nécessaires pour compiler et exécuter une application Go.

Note

Si vous êtes curieux de créer vos propres images de base, vous pouvez consulter la section suivante de ce guide : création d'images de base. Notez, cependant, que cela n'est pas nécessaire pour continuer avec votre tâche actuelle.

Maintenant que vous avez défini l'image de base pour votre prochaine image de conteneur, vous pouvez commencer à construire par-dessus.

Pour faciliter les choses lors de l'exécution du reste de vos commandes, créez un répertoire à l'intérieur de l'image que vous construisez. Cela indique également à Docker d'utiliser ce répertoire comme destination par défaut pour toutes les commandes suivantes. De cette façon, vous n'avez pas à taper les chemins de fichiers complets dans le Dockerfile, les chemins relatifs seront basés sur ce répertoire.

WORKDIR /app

Habituellement, la toute première chose que vous faites une fois que vous avez téléchargé un projet écrit en Go est d'installer les modules nécessaires pour le compiler. Notez que l'image de base a déjà la chaîne d'outils, mais votre code source n'y est pas encore.

Donc, avant de pouvoir exécuter go mod download à l'intérieur de votre image, vous devez y copier vos fichiers go.mod et go.sum. Utilisez la commande COPY pour ce faire.

Dans sa forme la plus simple, la commande COPY prend deux paramètres. Le premier paramètre indique à Docker quels fichiers vous souhaitez copier dans l'image. Le dernier paramètre indique à Docker où vous souhaitez que ce fichier soit copié.

Copiez les fichiers go.mod et go.sum dans votre répertoire de projet /app qui, grâce à votre utilisation de WORKDIR, est le répertoire courant (./) à l'intérieur de l'image. Contrairement à certains shells modernes qui semblent indifférents à l'utilisation de la barre oblique de fin (/), et peuvent deviner ce que l'utilisateur voulait dire (la plupart du temps), la commande COPY de Docker est assez sensible dans son interprétation de la barre oblique de fin.

COPY go.mod go.sum ./
Note

Si vous souhaitez vous familiariser avec le traitement de la barre oblique de fin par la commande COPY, consultez la référence Dockerfile. Cette barre oblique de fin peut causer des problèmes de plus de façons que vous ne pouvez l'imaginer.

Maintenant que vous avez les fichiers de module à l'intérieur de l'image Docker que vous construisez, vous pouvez utiliser la commande RUN pour y exécuter également la commande go mod download. Cela fonctionne exactement de la même manière que si vous exécutiez go localement sur votre machine, mais cette fois, ces modules Go seront installés dans un répertoire à l'intérieur de l'image.

RUN go mod download

À ce stade, vous disposez d'une chaîne d'outils Go version 1.19.x et de toutes vos dépendances Go installées à l'intérieur de l'image.

La prochaine chose que vous devez faire est de copier votre code source dans l'image. Vous utiliserez la commande COPY comme vous l'avez fait avec vos fichiers de module auparavant.

COPY *.go ./

Cette commande COPY utilise un caractère générique pour copier tous les fichiers avec l'extension .go situés dans le répertoire courant sur l'hôte (le répertoire où se trouve le Dockerfile) dans le répertoire courant à l'intérieur de l'image.

Maintenant, pour compiler votre application, utilisez la commande RUN familière :

RUN CGO_ENABLED=0 GOOS=linux go build -o /docker-gs-ping

Cela devrait vous être familier. Le résultat de cette commande sera un binaire d'application statique nommé docker-gs-ping et situé à la racine du système de fichiers de l'image que vous construisez. Vous auriez pu placer le binaire à n'importe quel autre endroit de votre choix à l'intérieur de cette image, le répertoire racine n'a aucune signification particulière à cet égard. Il est simplement pratique de l'utiliser pour garder les chemins de fichiers courts pour une meilleure lisibilité.

Maintenant, il ne reste plus qu'à indiquer à Docker quelle commande exécuter lorsque votre image est utilisée pour démarrer un conteneur.

Vous faites cela avec la commande CMD :

CMD ["/docker-gs-ping"]

Voici le Dockerfile complet :

# syntax=docker/dockerfile:1

FROM golang:1.19

# Définir la destination pour COPY
WORKDIR /app

# Télécharger les modules Go
COPY go.mod go.sum ./
RUN go mod download

# Copier le code source. Notez la barre oblique à la fin, comme expliqué dans
# https://docs.docker.com/reference/dockerfile/#copy
COPY *.go ./

# Construire
RUN CGO_ENABLED=0 GOOS=linux go build -o /docker-gs-ping

# Facultatif :
# Pour se lier à un port TCP, des paramètres d'exécution doivent être fournis à la commande docker.
# Mais nous pouvons documenter dans le Dockerfile les ports sur lesquels
# l'application va écouter par défaut.
# https://docs.docker.com/reference/dockerfile/#expose
EXPOSE 8080

# Exécuter
CMD ["/docker-gs-ping"]

Le Dockerfile peut également contenir des commentaires. Ils commencent toujours par un symbole #, et doivent se trouver au début d'une ligne. Les commentaires sont là pour votre commodité pour vous permettre de documenter votre Dockerfile.

Il existe également un concept de directives Dockerfile, comme la directive syntax que vous avez ajoutée. Les directives doivent toujours être tout en haut du Dockerfile, donc lorsque vous ajoutez des commentaires, assurez-vous que les commentaires suivent après toutes les directives que vous avez pu utiliser :

# syntax=docker/dockerfile:1
# A sample microservice in Go packaged into a container image.

FROM golang:1.19

# ...

Construire l'image

Maintenant que vous avez créé votre Dockerfile, construisez une image à partir de cela. La commande docker build crée des images Docker à partir du Dockerfile et d'un contexte. Un contexte de construction est l'ensemble des fichiers situés dans le chemin spécifié ou l'URL. Le processus de construction Docker peut accéder à tous les fichiers situés dans le contexte.

La commande de construction optionnellement prend une --tag flag. Cette flag est utilisée pour étiqueter l'image avec une valeur de chaîne, ce qui est facile à lire et à reconnaître pour les humains. Si vous ne passez pas une --tag, Docker utilisera latest comme valeur par défaut.

Construisez votre première image Docker.

$ docker build --tag docker-gs-ping .

Le processus de construction imprimera quelques messages de diagnostic à mesure qu'il traverse les étapes de construction. Le suivant est juste un exemple de ce à quoi ces messages pourraient ressembler.

[+] Building 2.2s (15/15) FINISHED
 => [internal] load build definition from Dockerfile                                                                                       0.0s
 => => transferring dockerfile: 701B                                                                                                       0.0s
 => [internal] load .dockerignore                                                                                                          0.0s
 => => transferring context: 2B                                                                                                            0.0s
 => resolve image config for docker.io/docker/dockerfile:1                                                                                 1.1s
 => CACHED docker-image://docker.io/docker/dockerfile:1@sha256:39b85bbfa7536a5feceb7372a0817649ecb2724562a38360f4d6a7782a409b14            0.0s
 => [internal] load build definition from Dockerfile                                                                                       0.0s
 => [internal] load .dockerignore                                                                                                          0.0s
 => [internal] load metadata for docker.io/library/golang:1.19                                                                             0.7s
 => [1/6] FROM docker.io/library/golang:1.19@sha256:5d947843dde82ba1df5ac1b2ebb70b203d106f0423bf5183df3dc96f6bc5a705                       0.0s
 => [internal] load build context                                                                                                          0.0s
 => => transferring context: 6.08kB                                                                                                        0.0s
 => CACHED [2/6] WORKDIR /app                                                                                                              0.0s
 => CACHED [3/6] COPY go.mod go.sum ./                                                                                                     0.0s
 => CACHED [4/6] RUN go mod download                                                                                                       0.0s
 => CACHED [5/6] COPY *.go ./                                                                                                              0.0s
 => CACHED [6/6] RUN CGO_ENABLED=0 GOOS=linux go build -o /docker-gs-ping                                                                  0.0s
 => exporting to image                                                                                                                     0.0s
 => => exporting layers                                                                                                                    0.0s
 => => writing image sha256:ede8ff889a0d9bc33f7a8da0673763c887a258eb53837dd52445cdca7b7df7e3                                               0.0s
 => => naming to docker.io/library/docker-gs-ping                                                                                          0.0s

Votre sortie exacte variera, mais fourni il n'y a pas d'erreurs, vous devriez voir le mot FINISHED dans la première ligne de sortie. Cela signifie que Docker a réussi à construire votre image nommée docker-gs-ping.

Voir les images locales

Pour voir la liste des images que vous avez sur votre machine locale, vous avez deux options. L'un est d'utiliser la CLI et l'autre est d'utiliser Docker Desktop. Puisque vous travaillez actuellement dans le terminal, jetez un coup d'œil à la liste des images avec la CLI.

Pour lister les images, exécutez la commande docker image ls (ou le raccourci docker images):

$ docker image ls

REPOSITORY                       TAG       IMAGE ID       CREATED         SIZE
docker-gs-ping                   latest    7f153fbcc0a8   2 minutes ago   1.11GB
...

Votre sortie exacte peut varier, mais vous devriez voir l'image docker-gs-ping avec la balise latest. Parce que vous n'avez pas spécifié une balise personnalisée lorsque vous avez construit votre image, Docker a supposé que la balise serait latest, ce qui est une valeur spéciale.

Étiqueter des images

Un nom d'image est constitué de composants de nom séparés par des barres obliques. Les composants de nom peuvent contenir des lettres minuscules, des chiffres et des séparateurs. Un séparateur est défini comme un point, un ou deux underscores ou un ou plusieurs tirets. Un composant de nom ne peut commencer ou se terminer par un séparateur.

Une image est constituée d'un manifeste et d'une liste de couches. En termes simples, une balise pointe vers une combinaison de ces artefacts. Vous pouvez avoir plusieurs balises pour l'image et, en fait, la plupart des images ont plusieurs balises. Créez une deuxième balise pour l'image que vous avez construite et jetez un coup d'œil à ses couches.

Utilisez la commande docker image tag (ou le raccourci docker tag):

$ docker image tag docker-gs-ping:latest docker-gs-ping:v1.0

La commande Docker tag crée une nouvelle balise pour l'image. Il ne crée pas une nouvelle image. La balise pointe vers la même image et est juste une autre façon de faire référence à l'image.

Maintenant, exécutez la commande docker image ls pour voir la liste mise à jour des images locales :

$ docker image ls

REPOSITORY                       TAG       IMAGE ID       CREATED         SIZE
docker-gs-ping                   latest    7f153fbcc0a8   6 minutes ago   1.11GB
docker-gs-ping                   v1.0      7f153fbcc0a8   6 minutes ago   1.11GB
...

Vous pouvez voir que vous avez deux images qui commencent par docker-gs-ping. Vous savez ils sont la même image parce que si vous regardez la colonne IMAGE ID, vous pouvez voir que les valeurs sont les mêmes pour les deux images. Cette valeur est un identifiant unique Docker utilisé à l'intérieur de Docker pour identifier l'image.

Supprimez la balise que vous venez de créer. Pour ce faire, vous utiliserez la commande docker image rm, ou le raccourci docker rmi (qui signifie "supprimer l'image"):

$ docker image rm docker-gs-ping:v1.0
Untagged: docker-gs-ping:v1.0

Notez que la réponse de Docker vous dit que l'image n'a pas été supprimée mais uniquement détachée.

Vérifiez cela en exécutant la commande suivante :

$ docker image ls

Vous verrez que la balise v1.0 n'est plus dans la liste des images gardées par votre instance Docker.

REPOSITORY                       TAG       IMAGE ID       CREATED         SIZE
docker-gs-ping                   latest    7f153fbcc0a8   7 minutes ago   1.11GB
...

La balise v1.0 a été supprimée mais vous avez toujours la balise docker-gs-ping:latest disponible sur votre machine, donc l'image est là.

Constructions multi-étapes

Vous auriez pu remarquer que votre image docker-gs-ping pèse plus d'un gigabyte, ce qui est beaucoup pour une application Go compilée simple. Vous auriez également vous demander ce qui est arrivé à l'ensemble complet des outils Go, y compris le compilateur, après que vous ayez construit votre image.

La réponse est que l'ensemble complet des outils est toujours là, dans l'image de conteneur. Pas seulement cela est incommode à cause de la taille du fichier, mais cela peut également présenter un risque de sécurité lorsque le conteneur est déployé.

Ces deux problèmes peuvent être résolus en utilisant constructions multi-étapes.

En bref, une construction multi-étape peut transporter les artefacts d'une étape de construction à une autre, et chaque étape de construction peut être instanciée à partir d'une image de base différente.

Ainsi, dans l'exemple suivant, vous allez utiliser une image Go officielle pleine échelle pour construire votre application. Ensuite, vous copierez le binaire de l'application dans une autre image dont la base est très maigre et ne comprend pas l'outil de chaîne ou autres composants optionnels.

Le Dockerfile.multistage dans le répertoire de l'échantillon d'application a le suivant contenu :

# syntax=docker/dockerfile:1

# Build the application from source
FROM golang:1.19 AS build-stage

WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download

COPY *.go ./

RUN CGO_ENABLED=0 GOOS=linux go build -o /docker-gs-ping

# Run the tests in the container
FROM build-stage AS run-test-stage
RUN go test -v ./...

# Deploy the application binary into a lean image
FROM gcr.io/distroless/base-debian11 AS build-release-stage

WORKDIR /

COPY --from=build-stage /docker-gs-ping /docker-gs-ping

EXPOSE 8080

USER nonroot:nonroot

ENTRYPOINT ["/docker-gs-ping"]

Étant donné que vous avez maintenant deux Dockerfiles, vous devez dire à Docker quel Dockerfile vous aimeriez utiliser pour construire l'image. Étiquetez la nouvelle image avec multistage. Cette balise (comme toute autre, à part latest) n'a aucun sens spécial pour Docker, c'est juste quelque chose que vous avez choisi.

$ docker build -t docker-gs-ping:multistage -f Dockerfile.multistage .

Comparer les tailles de docker-gs-ping:multistage et docker-gs-ping:latest vous voyez quelques ordres de grandeur de différence.

$ docker image ls
REPOSITORY       TAG          IMAGE ID       CREATED              SIZE
docker-gs-ping   multistage   e3fdde09f172   About a minute ago   28.1MB
docker-gs-ping   latest       336a3f164d0f   About an hour ago    1.11GB

C'est parce que l'image de base "distroless" que vous avez utilisée dans la seconde étape de la construction est très maigre et est conçue pour des déploiements maigres de binaires statiques.

Il y a beaucoup plus à construire multi-étape, y compris la possibilité de constructions multi-architecture, alors n'hésitez pas à consulter constructions multi-étape. C'est, cependant, pas essentiel pour votre progression ici.

Étapes suivantes

Dans ce module, vous avez rencontré votre application d'exemple et construit une image de conteneur pour elle.

Dans le module suivant, vous allez regarder comment exécuter votre image comme un conteneur.