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.
NoteSi 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 ./
NoteSi 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.