Chez Kinsta, nous avons des projets de toutes tailles pour l’hébergement d’applications, l’hébergement de bases de données et l’hébergement WordPress infogéré.
Avec les solutions d’hébergement cloud de Kinsta, vous pouvez déployer des applications dans un certain nombre de langages et de frameworks, tels que NodeJS, PHP, Ruby, Go, Scala et Python. Avec un fichier Docker, vous pouvez déployer n’importe quelle application. Vous pouvez connecter votre dépôt Git (hébergé sur GitHub, GitLab ou Bitbucket) pour déployer votre code directement sur Kinsta.
Vous pouvez héberger des bases de données MariaDB, Redis, MySQL et PostgreSQL prêtes à l’emploi, ce qui vous permet de vous concentrer sur le développement de vos applications plutôt que de vous préoccuper des configurations d’hébergement.
Et si vous choisissez notre hébergement WordPress infogéré, vous bénéficiez de la puissance des machines Google Cloud C2 sur leur réseau Premium et de la sécurité intégrée Cloudflare, ce qui fait de vos sites WordPress les plus rapides et les plus sûrs du marché.
Surmonter le défi du développement d’applications Cloud-Natives au sein d’une équipe distribuée
L’un des plus grands défis du développement et de la maintenance d’applications cloud-natives au niveau de l’entreprise est d’avoir une expérience cohérente tout au long du cycle de développement. C’est encore plus difficile pour les entreprises distantes avec des équipes distribuées travaillant sur différentes plateformes, avec des configurations différentes et une communication asynchrone. Nous devons fournir une solution cohérente, fiable et évolutive qui fonctionne pour :
- Les développeurs et les équipes d’assurance qualité, quel que soit leur système d’exploitation, créent une configuration simple et minimale pour développer et tester des fonctionnalités.
- Les équipes DevOps, SysOps et Infra, pour configurer et maintenir les environnements de staging et de production.
Chez Kinsta, nous nous appuyons fortement sur Docker pour cette expérience cohérente à chaque étape, du développement à la production. Dans cet article, nous vous guidons à travers :
- Comment tirer parti de Docker Desktop pour accroître la productivité des développeurs.
- Comment nous construisons des images Docker et les poussons vers Google Container Registry via des pipelines CI avec CircleCI et GitHub Actions.
- Comment nous utilisons les pipelines de CI pour promouvoir des changements incrémentaux vers la production en utilisant des images Docker, Google Kubernetes Engine et Cloud Deploy.
- Comment l’équipe QA utilise de manière transparente des images Docker pré-construites dans différents environnements.
Utilisation de Docker Desktop pour améliorer l’expérience des développeurs
L’exécution d’une application en local exige que les développeurs préparent méticuleusement l’environnement, installent toutes les dépendances, configurent les serveurs et les services et s’assurent qu’ils sont correctement configurés. Lorsque vous exécutez plusieurs applications, cela peut s’avérer fastidieux, en particulier lorsqu’il s’agit de projets complexes comportant de nombreuses dépendances. Lorsque vous ajoutez à cette variable plusieurs contributeurs disposant de plusieurs systèmes d’exploitation, le chaos s’installe. Pour l’éviter, nous utilisons Docker.
Avec Docker, vous pouvez déclarer les configurations de l’environnement, installer les dépendances et construire des images avec tout ce qu’il faut là où il faut. N’importe qui, n’importe où, avec n’importe quel système d’exploitation peut utiliser les mêmes images et avoir exactement la même expérience que les autres.
Déclarer votre configuration avec Docker Compose
Pour commencer, créez un fichier Docker Compose, docker-compose.yml
. Il s’agit d’un fichier de configuration déclaratif écrit au format YAML qui indique à Docker l’état souhaité de votre application. Docker utilise ces informations pour configurer l’environnement de votre application.
Les fichiers Docker Compose sont très utiles lorsque plusieurs conteneurs sont en cours d’exécution et qu’il existe des dépendances entre les conteneurs.
Pour créer votre fichier docker-compose.yml
:
- Commencez par choisir un fichier
image
comme base de notre application. Recherchez sur Docker Hub et essayez de trouver une image Docker qui contient déjà les dépendances de votre application. Veillez à utiliser une balise d’image spécifique pour éviter les erreurs. L’utilisation de la baliselatest
peut provoquer des erreurs imprévues dans votre application. Vous pouvez utiliser plusieurs images de base pour plusieurs dépendances. Par exemple, une pour PostgreSQL et une pour Redis. - Utilisez
volumes
pour persister les données sur votre hôte si vous en avez besoin. La persistance des données sur la machine hôte vous permet d’éviter de perdre des données si les conteneurs Docker sont supprimés ou si vous devez les recréer. - Utilisez
networks
pour isoler votre installation afin d’éviter les conflits de réseau avec l’hôte et les autres conteneurs. Cela permet également à vos conteneurs de se retrouver facilement et de communiquer entre eux.
En réunissant tous ces éléments, nous obtenons un site docker-compose.yml
qui ressemble à ceci :
version: '3.8'services:
db:
image: postgres:14.7-alpine3.17
hostname: mk_db
restart: on-failure
ports:
- ${DB_PORT:-5432}:5432
volumes:
- db_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: ${DB_USER:-user}
POSTGRES_PASSWORD: ${DB_PASSWORD:-password}
POSTGRES_DB: ${DB_NAME:-main}
networks:
- mk_network
redis:
image: redis:6.2.11-alpine3.17
hostname: mk_redis
restart: on-failure
ports:
- ${REDIS_PORT:-6379}:6379
networks:
- mk_network
volumes:
db_data:
networks:
mk_network:
name: mk_network
Conteneuriser l’application
Construire une image Docker pour votre application
Tout d’abord, nous devons créer une image Docker à l’aide de Dockerfile
, puis l’appeler à partir de docker-compose.yml
.
Pour créer votre fichier Dockerfile
:
- Commencez par choisir une image de base. Utilisez l’image de base la plus petite possible pour votre application. En général, les images alpines sont très minimales et ne contiennent pratiquement aucun paquet supplémentaire. Vous pouvez commencer par une image alpine et construire à partir de celle-ci :
FROM node:18.15.0-alpine3.17
- Parfois, vous devez utiliser une architecture de processeur spécifique pour éviter les conflits. Par exemple, supposons que vous utilisiez un processeur
arm64-based
mais que vous deviez construire une imageamd64
. Vous pouvez le faire en spécifiant-- platform
dansDockerfile
:FROM --platform=amd64 node:18.15.0-alpine3.17
- Définissez le répertoire d’application, installez les dépendances et copiez le résultat dans votre répertoire racine :
WORKDIR /opt/app COPY package.json yarn.lock ./ RUN yarn install COPY . .
- Appelez l’application
Dockerfile
à partir dedocker-compose.yml
:services: ...redis ...db app: build: context: . dockerfile: Dockerfile platforms: - "linux/amd64" command: yarn dev restart: on-failure ports: - ${PORT:-4000}:${PORT:-4000} networks: - mk_network depends_on: - redis - db
- Implémentez le chargement automatique de sorte que lorsque vous modifiez quelque chose dans le code source, vous puissiez prévisualiser vos changements immédiatement sans avoir à reconstruire l’application manuellement. Pour cela, construisez d’abord l’image, puis exécutez-la dans un service séparé :
services: ... redis ... db build-docker: image: myapp build: context: . dockerfile: Dockerfile app: image: myapp platforms: - "linux/amd64" command: yarn dev restart: on-failure ports: - ${PORT:-4000}:${PORT:-4000} volumes: - .:/opt/app - node_modules:/opt/app/node_modules networks: - mk_network depends_on: - redis - db - build-docker volumes: node_modules:
Conseil de pro : Notez que node_modules
est également monté explicitement pour éviter les problèmes de paquets spécifiques à la plate-forme. Cela signifie qu’au lieu d’utiliser le site node_modules
sur l’hôte, le conteneur docker utilise le sien mais le mappe sur l’hôte dans un volume séparé.
Construire progressivement les images de production avec l’intégration continue
La majorité de nos applications et services utilisent CI/CD pour le déploiement. Docker joue un rôle important dans ce processus. Chaque changement dans la branche principale déclenche immédiatement un pipeline de construction via GitHub Actions ou CircleCI. Le flux de travail général est très simple : il installe les dépendances, exécute les tests, construit l’image Docker, et la pousse vers Google Container Registry (ou Artifact Registry). La partie dont nous parlons dans cet article est l’étape de construction.
Construction des images Docker
Nous utilisons des constructions en plusieurs étapes pour des raisons de sécurité et de performance.
Étape 1 : Construction
Dans cette étape, nous copions la base de code entière avec toutes les sources et la configuration, nous installons toutes les dépendances, y compris les dépendances de développement, et nous construisons l’application. Elle crée un dossier dist/
et y copie la version construite du code. Mais cette image est beaucoup trop volumineuse et comporte un grand nombre d’empreintes pour être utilisée en production. De plus, comme nous utilisons des registres NPM privés, nous utilisons également notre site privé NPM_TOKEN
à ce stade. Nous ne voulons donc pas que cette étape soit exposée au monde extérieur. La seule chose dont nous avons besoin à ce stade est le dossier dist/
.
Étape 2 : Production
La plupart des gens utilisent cette étape pour l’exécution car elle est très proche de ce dont nous avons besoin pour faire fonctionner l’application. Cependant, nous devons encore installer les dépendances de production, ce qui signifie que nous laissons des traces et que nous avons besoin du dossier NPM_TOKEN
. Cette étape n’est donc pas encore prête à être exposée. Faites également attention à yarn cache clean
sur la ligne 19. Cette petite commande réduit la taille de notre image de 60 %.
Étape 3 : Exécution
La dernière étape doit être aussi fine que possible avec un minimum d’empreinte. Nous nous contentons donc de copier l’application toute prête de la production et de passer à autre chose. Nous laissons tous ces trucs yarn et NPM_TOKEN
derrière nous et n’exécutons que l’application.
C’est la version finale de Dockerfile.production
:
# Stage 1: build the source code
FROM node:18.15.0-alpine3.17 as builder
WORKDIR /opt/app
COPY package.json yarn.lock ./
RUN yarn install
COPY . .
RUN yarn build
# Stage 2: copy the built version and build the production dependencies FROM node:18.15.0-alpine3.17 as production
WORKDIR /opt/app
COPY package.json yarn.lock ./
RUN yarn install --production && yarn cache clean
COPY --from=builder /opt/app/dist/ ./dist/
# Stage 3: copy the production ready app to runtime
FROM node:18.15.0-alpine3.17 as runtime
WORKDIR /opt/app
COPY --from=production /opt/app/ .
CMD ["yarn", "start"]
Notez que, pour toutes les étapes, nous commençons par copier les fichiers package.json
et yarn.lock
, nous installons les dépendances, puis nous copions le reste de la base de code. La raison en est que Docker construit chaque commande comme une couche au-dessus de la précédente. Et chaque compilation peut utiliser les couches précédentes si elles sont disponibles et ne construire les nouvelles couches qu’à des fins de performance.
Supposons que vous ayez modifié quelque chose dans src/services/service1.ts
sans toucher aux paquets. Cela signifie que les quatre premières couches de l’étape de construction sont intactes et peuvent être réutilisées. Cela rend le processus de construction incroyablement plus rapide.
Pousser l’application vers le registre de conteneurs de Google via les pipelines CircleCI
Il y a plusieurs façons de construire une image Docker dans les pipelines CircleCI. Dans notre cas, nous avons choisi d’utiliser circleci/gcp-gcr orbs
:
executors:
docker-executor:
docker:
- image: cimg/base:2023.03
orbs:
gcp-gcr: circleci/[email protected]
jobs:
...
deploy:
description: Build & push image to Google Artifact Registry
executor: docker-executor
steps:
...
- gcp-gcr/build-image:
image: my-app
dockerfile: Dockerfile.production
tag: ${CIRCLE_SHA1:0:7},latest
- gcp-gcr/push-image:
image: my-app
tag: ${CIRCLE_SHA1:0:7},latest
Une configuration minimale est nécessaire pour construire et pousser notre application, grâce à Docker.
Pousser l’application vers le Google Container Registry via les actions GitHub
Comme alternative à CircleCI, nous pouvons utiliser GitHub Actions pour déployer l’application en continu. Nous configurons gcloud
et construisons et poussons l’image Docker vers gcr.io
:
jobs:
setup-build:
name: Setup, Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Get Image Tag
run: |
echo "TAG=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- uses: google-github-actions/setup-gcloud@master
with:
service_account_key: ${{ secrets.GCP_SA_KEY }}
project_id: ${{ secrets.GCP_PROJECT_ID }}
- run: |-
gcloud --quiet auth configure-docker
- name: Build
run: |-
docker build
--tag "gcr.io/${{ secrets.GCP_PROJECT_ID }}/my-app:$TAG"
--tag "gcr.io/${{ secrets.GCP_PROJECT_ID }}/my-app:latest"
.
- name: Push
run: |-
docker push "gcr.io/${{ secrets.GCP_PROJECT_ID }}/my-app:$TAG"
docker push "gcr.io/${{ secrets.GCP_PROJECT_ID }}/my-app:latest"
À chaque petite modification apportée à la branche principale, nous construisons et poussons une nouvelle image Docker vers le registre.
Déploiement de modifications sur le moteur Google Kubernetes à l’aide de Google Delivery Pipelines
Le fait de disposer d’images Docker prêtes à l’emploi pour chaque modification facilite également le déploiement en production ou le retour en arrière en cas de problème. Nous utilisons Google Kubernetes Engine pour gérer et servir nos applications et nous utilisons Google Cloud Deploy et Delivery Pipelines pour notre processus de déploiement continu.
Lorsque l’image Docker est construite après chaque petite modification (avec le pipeline CI illustré ci-dessus), nous allons plus loin et déployons la modification sur notre cluster de développement à l’aide de gcloud
. Jetons un coup d’œil à cette étape dans le pipeline CircleCI :
- run:
name: Create new release
command: gcloud deploy releases create release-${CIRCLE_SHA1:0:7} --delivery-pipeline my-del-pipeline --region $REGION --annotations commitId=$CIRCLE_SHA1 --images my-app=gcr.io/${PROJECT_ID}/my-app:${CIRCLE_SHA1:0:7}
Cela déclenche un processus de libération pour déployer les changements dans notre cluster Kubernetes de développement. Après avoir testé et obtenu les approbations, nous promouvons le changement vers staging et ensuite vers production. Tout cela est possible parce que nous disposons d’une image Docker isolée pour chaque changement, qui contient presque tout ce dont elle a besoin. Il nous suffit d’indiquer au déploiement quelle balise utiliser.
Comment l’équipe d’assurance qualité bénéficie de ce processus
L’équipe d’assurance qualité a surtout besoin d’une version cloud de pré-production des applications à tester. Cependant, elle a parfois besoin d’exécuter localement une application préconstruite (avec toutes les dépendances) pour tester une certaine fonctionnalité. Dans ces cas, ils ne veulent pas ou n’ont pas besoin de passer par la douleur de cloner le projet entier, d’installer les paquets npm, de construire l’application, de faire face aux erreurs des développeurs, et de revoir tout le processus de développement pour que l’application soit opérationnelle. Maintenant que tout est déjà disponible en tant qu’image Docker sur Google Container Registry, tout ce dont ils ont besoin, c’est d’un service dans le fichier Docker compose :
services:
...redis
...db
app:
image: gcr.io/${PROJECT_ID}/my-app:latest
restart: on-failure
ports:
- ${PORT:-4000}:${PORT:-4000}
environment:
- NODE_ENV=production
- REDIS_URL=redis://redis:6379
- DATABASE_URL=postgresql://${DB_USER:-user}:${DB_PASSWORD:-password}@db:5432/main
networks:
- mk_network
depends_on:
- redis
- db
Grâce à ce service, ils peuvent lancer l’application sur leurs machines locales à l’aide de conteneurs Docker :
docker compose up
Il s’agit là d’un grand pas vers la simplification des processus de test. Même si l’assurance qualité décide de tester un tag spécifique de l’application, elle peut facilement changer le tag de l’image à la ligne 6 et réexécuter la commande Docker Composer. Même s’ils décident de comparer simultanément différentes versions de l’application, ils peuvent facilement y parvenir avec quelques ajustements. Le plus grand avantage est de tenir notre équipe d’assurance qualité à l’écart des défis des développeurs.
Avantages de l’utilisation de Docker
- Il n’y a pratiquement pas d’empreintes de dépendances : Si vous décidez de mettre à jour la version de Redis ou de Postgres, il vous suffit de modifier une ligne et de ré-exécuter l’application. Il n’est pas nécessaire de modifier quoi que ce soit sur votre système. De plus, si vous avez deux applications qui ont toutes deux besoin de Redis (peut-être même avec des versions différentes), vous pouvez les faire tourner toutes les deux dans leur propre environnement isolé sans qu’il y ait de conflit entre elles.
- Plusieurs instances de l’application : Il y a beaucoup de cas où nous avons besoin d’exécuter la même application avec une commande différente. Par exemple, l’initialisation de la base de données, l’exécution de tests, l’observation des changements dans la base de données, ou l’écoute de messages. Dans chacun de ces cas, puisque nous avons déjà l’image construite prête, nous ajoutons simplement un autre service au fichier Docker compose avec une commande différente, et nous avons terminé.
- Un environnement de test plus facile : Le plus souvent, vous avez juste besoin d’exécuter l’application. Vous n’avez pas besoin du code, des paquets ou des connexions aux bases de données locales. Vous voulez seulement vous assurer que l’application fonctionne correctement ou vous avez besoin d’une instance en cours d’exécution comme service de backend pendant que vous travaillez sur votre propre projet. Cela peut également être le cas pour l’assurance qualité, les réviseurs de demandes de retrait, ou même les personnes chargées de l’interface utilisateur qui veulent s’assurer que leur conception a été correctement mise en œuvre. Notre configuration Docker leur permet de prendre les choses en main très facilement, sans avoir à se préoccuper de trop de problèmes techniques.
Cet article a été publié à l’origine sur Docker.
Laisser un commentaire