Tutoriel Symfony et docker-compose v2

Symfony et Docker-compose en version 2

Cet article fait suite à l’article Comment utiliser Symfony et Docker grâce à docker-compose.

Pourquoi un deuxième article ?

Docker s’est imposé comme une solution parfaite pour facilement déployer des applications, qu’elles soient en local pour développer mais également pour réaliser des déploiements en production. Avec toute l’émulsion créée, de nombreux « problèmes » ont été levés et des solutions ont dû être mise en place pour répondre à tous les détails manquants.

Dans le lot d’évolution, on retrouve la gestion des volumes et des réseaux qui ont été ajoutés au Docker Engine. Ces améliorations ne s’intégraient pas dans le premier standard défini par docker-compose.

1
2
3
4
5
6
7
8
9
10
# Format d'un docker-compose v1
front:
image: nginx
ports:
- 80
engine:
image: php:7-fpm
links:
- front:front

Nous allons donc voir le format 2 et comment intégrer une application Symfony.

Quelles problématiques et quelles solutions apportées ?

Gestion des networks

Si vous avez utilisé la version 1 de compose, vous avez à coup sûr rencontré le fameux problème de dépendances circulaires. En effet au début de Docker, nous n’avions pas la possibilité de faire connaître deux containers entre eux.

Exemple, le container A doit connaître le container B qui doit lui même connaître le container A.

Dépendances circulaires

Avant la version 1.9 de Docker, il n’existait pas la libnetwork et pour qu’un container connaisse un autre container, Docker imposait que le container lié soit créé. Cela n’empêchait pas à un container de parler à un autre container si on connaissait son IP. Tous les containers se trouvaient alors dans le même réseau (bridge).

Avec l’arrivée de la libnetwork au sein de Docker, nous avons également pu jouer avec les réseaux. Nous pouvons créer autant de réseaux que nous le souhaitons, nous pouvons attacher plusieurs réseaux à un même container.

libnetwork dans Docker, comment ça marche (en bref…) ?

libnetwork a donné lieu à un projet à part entière de part sa complexité. Cette librairie a introduit la possibilité de créer des réseaux et d’attacher des réseaux à des containers.

1
2
3
4
# Créer un réseau
$ docker network create my_network
# Attacher un _container_ à un réseau
$ docker network connect my_network my_container

Tous les containers d’un même réseau peuvent se parler, et par opposition, si deux containers ne font pas partie du même réseau, ils ne pourront pas communiquer.

Lorsqu’un container fait partie d’un réseau, le container se voit attribuer une IP pour ce réseau. Il s’agit d’une première différence avec la configuration précédente. Cela a d’ailleurs donné lieu à de profonds changements au sein des applications utilisant l’API de Docker (par exemple pour les projets jwilder/docker-gen et jwilder/nginx-proxy auxquels j’ai contribué).

libnetwork contient un démon qui tourne en permanence. Dès qu’il voit un nouveau container sur un réseau, il s’assure que ce container puisse parler aux autres containers du même réseau. La libnetwork utilise un serveur DNS pour remplacer les anciens alias et résoudre les vhost.

Démonstration

Comportement avant la libnetwork
1
2
3
4
5
6
7
8
# docker-compose.yml
front:
image: nginx
links:
- engine:engine
engine:
image: php:7-fpm
1
2
3
4
5
6
$ docker-compose up -d
Creating test_engine_1
Creating test_front_1
$ docker-compose exec front cat /etc/hosts
x.x.x.x front
x.x.x.x test_front_1
Comportement avec la libnetwork
1
2
3
4
5
6
7
8
9
# docker-compose.yml
version: '2'
services:
front:
image: nginx
engine:
image: php:7-fpm
1
2
3
4
5
6
7
8
9
$ docker-compose up -d
Creating network "test_default" with the default driver
Creating test_engine_1
Creating test_front_1
$ docker-compose exec front cat /etc/hosts
127.0.0.1 localhost
y.y.y.y current_container_id
# Aucun virtual host pointant vers notre container engine

Avec ce nouveau fonctionnement, un container peut contacter des containers qui sont sur l’un des réseaux du container appelant, sans avoir à les connaître dans son /etc/hosts.

libnetwork fait office de serveur DNS pour faire communiquer les containers d'un même réseau

Quelles différences à l’usage ?

Puisque nos containers d’un même réseau se connaissent et qu’il n’est désormais plus nécessaire de leurs donner des alias, cela entraîne forcément une contrepartie.

Dans la première version, on aurait pu contacter l’engine depuis le front en utilisant le vhost engine (alias contenu dans le link). D’une autre part, si vous aviez scalé votre service engine en n versions, le vHost aurait pointé sur un unique container.

Avec la nouvelle méthodologie, front peut contacter engine et inversement. Mais les alias n’existant plus (ils étaient définis individuellement sur chaque link), le vHost de chaque container est initialement définit avec le nom du container. Cela peut facilement être surchargé avec l’option –dns-search.

1
$ docker run --dns-search=engine …
1
2
3
4
5
# docker-compose.yml
engine:
image: php:7-fpm
dns_search:
- engine

Le petit plus

Si plusieurs containers définissent une même entrée dns_search, Docker via son binding DNS, effectue un Round-Robin DNS.

Gestion des volumes

La question des volumes est souvent un sujet compliqué à aborder. Comment peut-on définir un volume au sens large ? Il s’agit d’un montage au sein d’un container dans le but de pouvoir conserver des données après la suppression d’un container, mais également de partager des données entre plusieurs containers (également cross-machine via Swarm par exemple).

Au tout début nous montions à chaque fois les dossiers.

1
2
3
4
5
# docker-compose.yml version 1
database:
image: mysql
volumes:
- "./volumes/database:/var/lib/mysql:rw"

Ensuite, Docker a introduit une notion au sein de volumes permettant aux utilisateurs de stocker des données au sein de Docker (comprennez, vous donnez un nom et Docker gère toute une arborescence pour vous). Docker-compose créait pour vous un volume avec un nom et effectuait un montage.

1
2
3
4
# La liste des volumes sur votre machine gérée par Docker
$ docker volume ls
DRIVER VOLUME NAME

Cette possibilité a été utilisée comme un patch par docker-compose dans un premier temps. En effet, pour tous les volumes déclarés au niveau de l’image qui n’étaient pas montés au niveau du container, compose créait un volume. Cela permettait de ne pas perdre ses données. Malgré tout, cela ne permettait pas réellement de les gérer proprement.

La version 2 du format de docker-compose a introduit la possibilité de gérer les volumes.

1
2
3
4
5
6
7
8
9
10
11
# docker-compose.yml version 2
version: '2'
services:
database:
image: mysql
volumes:
- "database:/var/lib/mysql:rw"
volumes:
database: {}

Symfony et Docker-compose en version 2 !

Sans ré-expliquer le fonctionnement global de mon docker-compose (vous pourrez vous reporter à mon premier article), le voici dans sa nouvelle version.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
version: '2'
services:
front:
image: nginx
ports:
- 80
volumes:
- .:/home/docker:ro
- ./docker/front/default.conf:/etc/nginx/conf.d/default.conf:ro
networks:
- default
engine:
build: ./docker/engine/
volumes:
- .:/home/docker:rw
- ./docker/engine/php.ini:/usr/local/etc/php/conf.d/custom.ini:ro
working_dir: /home/docker
networks:
- default
dns_search:
- engine
db:
image: mysql
ports:
- 3306
environment:
- MYSQL_ROOT_PASSWORD=your_root_password
- MYSQL_USER=your_user
- MYSQL_PASSWORD=your_user_password
- MYSQL_DATABASE=your_database_name
volumes:
- db:/var/lib/mysql:rw
networks:
- default
dns_search:
- db
volumes:
db: {}
networks:
default: {}

Les fichiers de configuration et le Dockerfile sont disponibles ici ainsi que dans le premier article.

Quelques détails

  • Il n’est pas nécessaire de spécifier le réseau default car il est par défaut créé et assigné à tous les containers de votre application.
  • volumes et networks ne sont pas requis, mais sont rapidement très utilisés.
  • Utiliser des volumes évite de garder ses données dans un emplacement personnel (à gérer soit-même).
  • L’utilisation de volumes est quasi obligatoire lorsqu’on utilise Docker dans un Cluster (Swarm) car il permet de décentraliser le stockage des données sur une machine remote (voir la liste des plugins).
  • L’utilisation de réseaux permet d’isoler nos applications entre elles, ainsi que les parties métiers d’une même application.
  • dns_search supporte une ou plusieurs valeurs. Les valeurs permettront de résoudre les IPs de vos containers. Cela vous permet de définir un host à utiliser dans votre configuration (par exemple dans le default.conf) et vous facilite la tâche (pas besoin d’utiliser de Service Discovery… Du moins dans un premier temps !).

Conclusion

Migrer nos « vieux » docker-compose.yml en version 2 prend peu de temps et peut rapidement vous en faire gagner. Il sera obligatoire pour résoudre les problèmes de dépendances circulaires ainsi que la gestion des volumes d’une manière plus fine.