1 changed files with 229 additions and 0 deletions
@ -0,0 +1,229 @@ |
|||||
|
--- |
||||
|
title: "Implémenter le motif de conception 'Strangler Fig' dans OpenShift" |
||||
|
date: '2021-01-13T00:00:00+02:00' |
||||
|
#description: "" |
||||
|
opensource: |
||||
|
- OpenShift |
||||
|
topics: |
||||
|
- Containers |
||||
|
resources: |
||||
|
#- src: '*.yaml' |
||||
|
#- src: '*.png' |
||||
|
--- |
||||
|
|
||||
|
Le motif de conception [Strangler Fig](https://docs.microsoft.com/en-us/azure/architecture/patterns/strangler-fig) a été documenté par [Martin Fowler](https://martinfowler.com/bliki/StranglerFigApplication.html) en 2004. |
||||
|
Il fait référence à un arbre nommé le "[figuier étrangleur](https://fr.wikipedia.org/wiki/Figuier_%C3%A9trangleur)" qui s'appuie sur son hôte pour ses premières années de vie, jusqu'à ce que ses racines touchent le sol. |
||||
|
Il peut ainsi se nourrir et grandir de manière autonome. |
||||
|
Son hôte sert alors de support et finit par mourir "étranglé". |
||||
|
|
||||
|
C'est une analogie avec la ré-ingénierie d'un système en production: les composants d'un monolithe sont réécrits un à un, sous forme de micro-services, à coté du système existant. |
||||
|
Les composants du monolithe sont alors remplacés au fil de l'eau par leur équivalent micro-service. |
||||
|
Une fois tous les composants du monolithe remplacés, il peut alors être décommissionné. |
||||
|
|
||||
|
La question qui m'a été posée est: est-il possible d'implémenter ce motif à l'aide des outils et fonctions d'OpenShift ? |
||||
|
|
||||
|
<!--more--> |
||||
|
|
||||
|
La réponse est **oui** et les fonctions de *Path Routing* d'OpenShift 4 sont faites pour cela ! |
||||
|
|
||||
|
## Path Routing dans OpenShift 4 |
||||
|
|
||||
|
Les fonctions de *Path Routing* permettent l'implémentation du motif de conception **Strangler Fig** et sont présentes depuis OpenShift 3. |
||||
|
La documentation de la version 4 ne mentionne plus cette possibilité mais [Red Hat a bien confirmé que ces fonctions sont toujours supportées](https://access.redhat.com/solutions/4675571). |
||||
|
|
||||
|
La [documentation de la version 3 sur le *Path Routing*](https://docs.openshift.com/container-platform/3.11/architecture/networking/routes.html#path-based-routes) reste valable pour la version 4. |
||||
|
|
||||
|
## Cas pratique |
||||
|
|
||||
|
Imaginons un monolithe portant deux fonctions métiers *APP1* et *APP2*. |
||||
|
Pour l'exemple, ce monolithe sera simulé par un serveur *Nginx* servant les fonctions *APP1* sur /APP1/index.html et *APP2* sur /APP2/index.html. |
||||
|
|
||||
|
Commençons par créer un projet dédié à cet exemple. |
||||
|
|
||||
|
```sh |
||||
|
oc new-project strangler-fig |
||||
|
``` |
||||
|
|
||||
|
Puis, déployons notre monolithe (simulé ici par un Nginx). |
||||
|
|
||||
|
```sh |
||||
|
oc new-build --name monolith --strategy=docker --docker-image quay.io/centos7/nginx-116-centos7:1.16 -D - <<EOF |
||||
|
FROM quay.io/centos7/nginx-116-centos7:1.16 |
||||
|
RUN mkdir -p /tmp/src/APP1/ /tmp/src/APP2/ \ |
||||
|
&& echo OLDAPP1 > /tmp/src/APP1/index.html \ |
||||
|
&& echo OLDAPP2 > /tmp/src/APP2/index.html \ |
||||
|
&& chown 1001:0 -R /tmp/src |
||||
|
|
||||
|
RUN /usr/libexec/s2i/assemble |
||||
|
CMD /usr/libexec/s2i/run |
||||
|
EOF |
||||
|
oc logs -f bc/monolith |
||||
|
oc new-app --name monolith -i monolith |
||||
|
``` |
||||
|
|
||||
|
Le monolithe est alors exposé sous la forme d'une route OpenShift. |
||||
|
|
||||
|
```sh |
||||
|
oc create route edge mon-appli --service=monolith --hostname=mon-appli.apps.ocp4.itix.fr |
||||
|
``` |
||||
|
|
||||
|
**Note:** Les fonctions de *Path Routing* nécessitent une route de type *Edge* ou *Reencrypt*. |
||||
|
Dans le cas d'une route *Passthrough*, le flux TLS n'est pas déchiffré par le routeur OpenShift et les fonctions de *Path Routing* ne peuvent pas être appliquées. |
||||
|
|
||||
|
Une rapide vérification nous confirme de que les deux fonctions métiers du monolithe sont bien exposées. |
||||
|
|
||||
|
``` |
||||
|
$ curl https://mon-appli.apps.ocp4.itix.fr/APP1/index.html |
||||
|
OLDAPP1 |
||||
|
|
||||
|
$ curl https://mon-appli.apps.ocp4.itix.fr/APP2/index.html |
||||
|
OLDAPP2 |
||||
|
``` |
||||
|
|
||||
|
Imaginons maintenant que la fonction métier *APP1* a été réécrite sous la forme d'un micro-service. |
||||
|
Déployons ce micro-service à coté du monolithe. |
||||
|
|
||||
|
```sh |
||||
|
oc new-build --name new-svc-1 --strategy=docker --docker-image quay.io/centos7/nginx-116-centos7:1.16 -D - <<EOF |
||||
|
FROM quay.io/centos7/nginx-116-centos7:1.16 |
||||
|
RUN mkdir -p /tmp/src/APP1/ \ |
||||
|
&& echo NEWAPP1 > /tmp/src/APP1/index.html \ |
||||
|
&& chown 1001:0 -R /tmp/src |
||||
|
|
||||
|
RUN /usr/libexec/s2i/assemble |
||||
|
CMD /usr/libexec/s2i/run |
||||
|
EOF |
||||
|
oc logs -f bc/new-svc-1 |
||||
|
oc new-app --name new-svc-1 -i new-svc-1 |
||||
|
``` |
||||
|
|
||||
|
Nous pouvons ensuite créer une route pour ce micro-service sur le même nom d'hôte que le monolithe mais en spécifiant le préfixe du chemin d'accès à la fonction *APP1* (paramètre `--path`). |
||||
|
|
||||
|
```sh |
||||
|
oc create route edge new-svc-1 --service=new-svc-1 --hostname=mon-appli.apps.ocp4.itix.fr --path=/APP1 |
||||
|
``` |
||||
|
|
||||
|
Sans surprise, la fonction métier *APP1* a bien été routée vers le micro-service. |
||||
|
La fonction métier *APP2* est toujours servie par le monolithe. |
||||
|
|
||||
|
```sh |
||||
|
$ curl https://mon-appli.apps.ocp4.itix.fr/APP1/index.html |
||||
|
NEWAPP1 |
||||
|
|
||||
|
$ curl https://mon-appli.apps.ocp4.itix.fr/APP2/index.html |
||||
|
OLDAPP2 |
||||
|
``` |
||||
|
|
||||
|
Il est important de noter que le micro-service remplaçant *APP1* a été obligé de conserver l'ensemble des URLs de *APP1* inchangées. |
||||
|
C'est une contrainte à prendre en compte au moment des spécifications. |
||||
|
|
||||
|
Pour rendre le cas pratique un peu plus concret, imaginons que le micro-service remplaçant *APP2* ne respecte pas cette contrainte et qu'il a été décidé d'exposer l'ensemble des services d'APP2 à la racine. |
||||
|
|
||||
|
Déployons ce nouveau micro-service à coté du monolithe. |
||||
|
|
||||
|
```sh |
||||
|
oc new-build --name new-svc-2 --strategy=docker --docker-image quay.io/centos7/nginx-116-centos7:1.16 -D - <<EOF |
||||
|
FROM quay.io/centos7/nginx-116-centos7:1.16 |
||||
|
RUN mkdir -p /tmp/src \ |
||||
|
&& echo NEWAPP2 > /tmp/src/index.html \ |
||||
|
&& chown 1001:0 -R /tmp/src |
||||
|
|
||||
|
RUN /usr/libexec/s2i/assemble |
||||
|
CMD /usr/libexec/s2i/run |
||||
|
EOF |
||||
|
oc logs -f bc/new-svc-2 |
||||
|
oc new-app --name new-svc-2 -i new-svc-2 |
||||
|
``` |
||||
|
|
||||
|
Nous pouvons ensuite créer une route pour ce micro-service sur le même nom d'hôte que le monolithe mais en spécifiant le préfixe du chemin d'accès à la fonction *APP2* (paramètre `--path`). |
||||
|
Afin d'accommoder le changement d'URL entre le monolithe et le micro-service le remplaçant, nous utilisons [une annotation de route](https://docs.openshift.com/container-platform/4.6/networking/routes/route-configuration.html#nw-route-specific-annotations_route-configuration) (commande `oc annotate`). |
||||
|
|
||||
|
```sh |
||||
|
oc create route edge new-svc-2 --service=new-svc-2 --hostname=mon-appli.apps.ocp4.itix.fr --path=/APP2 |
||||
|
oc annotate route/new-svc-2 haproxy.router.openshift.io/rewrite-target=/ |
||||
|
``` |
||||
|
|
||||
|
Toujours sans surprise, la fonction métier *APP2* a bien été routée vers le micro-service. |
||||
|
|
||||
|
``` |
||||
|
$ curl https://mon-appli.apps.ocp4.itix.fr/APP1/index.html |
||||
|
NEWAPP1 |
||||
|
|
||||
|
$ curl https://mon-appli.apps.ocp4.itix.fr/APP2/index.html |
||||
|
NEWAPP2 |
||||
|
``` |
||||
|
|
||||
|
Maintenant que toutes les fonctions métiers du monolithe ont été remplacées par leur équivalent en micro-service, le monolithe peut être décommissionné. |
||||
|
|
||||
|
```sh |
||||
|
oc delete bc,is,svc,deploy,route monolith |
||||
|
``` |
||||
|
|
||||
|
## Et si mon monolithe n'est pas dans OpenShift ? |
||||
|
|
||||
|
Au début de cet article, j'ai pris l'hypothèse que le monolithe était déployé dans OpenShift mais cela n'est pas toujours le cas. |
||||
|
Parfois, seuls les micro-services remplaçant le monolithe sont éligibles à un déploiement sur OpenShift. |
||||
|
Dans ce cas de figure, il reste néanmoins possible de mettre en œuvre le motif de conception *Strangler Fig* dans OpenShift 4 ! |
||||
|
|
||||
|
Pour ce faire, nous aurons besoin de déclarer l'adresse du monolithe dans OpenShift, sous la forme d'un *Service* et d'un *Endpoints*. |
||||
|
|
||||
|
L'objet *Service* est de type *Headless*, c'est à dire qu'il n'y a pas d'adresse IP interne affectée à ce service. |
||||
|
Il précise également les ports sur lequel le monolithe est joignable. |
||||
|
|
||||
|
```yaml |
||||
|
kind: "Service" |
||||
|
apiVersion: "v1" |
||||
|
metadata: |
||||
|
name: "monolith" |
||||
|
spec: |
||||
|
clusterIP: None |
||||
|
ports: |
||||
|
- name: "http" |
||||
|
protocol: "TCP" |
||||
|
port: 80 |
||||
|
targetPort: 80 |
||||
|
nodePort: 0 |
||||
|
selector: {} |
||||
|
``` |
||||
|
|
||||
|
L'objet *Endpoints* reprend les ports du monolithe et précise son adresse IP. |
||||
|
En guise de démonstration, j'ai mis ici l'adresse IP du service *ftp.lip6.fr*. |
||||
|
|
||||
|
```yaml |
||||
|
kind: "Endpoints" |
||||
|
apiVersion: "v1" |
||||
|
metadata: |
||||
|
name: "monolith" |
||||
|
subsets: |
||||
|
- addresses: |
||||
|
- ip: "195.83.118.1" |
||||
|
ports: |
||||
|
- port: 80 |
||||
|
name: "http" |
||||
|
protocol: TCP |
||||
|
``` |
||||
|
|
||||
|
Nous pouvons créer la route associée. |
||||
|
|
||||
|
```sh |
||||
|
oc create route edge mon-appli --service=monolith --hostname=mon-appli.apps.ocp4.itix.fr |
||||
|
``` |
||||
|
|
||||
|
Si vous ouvrez la route dans votre navigateur, vous verrez apparaitre la page d'accueil du miroir *ftp.lip6.fr* proxyfié par OpenShift. |
||||
|
|
||||
|
Le reste de la procédure (création des deux micro-services avec le *Path Routing* associé) est identique. |
||||
|
|
||||
|
## Pour aller plus loin |
||||
|
|
||||
|
Il peut exister d'autre cas où le micro-service nécessiterait plus qu'un simple routage sur l'URL. |
||||
|
Notamment lorsqu'il est nécessaire d'enrichir le contenu de la réponse avec la réponse d'un autre micro-service. |
||||
|
|
||||
|
On peut imaginer que l'affichage du détail d'une commande affiche les informations client et les informations de facture. |
||||
|
Si ces deux fonctions sont découpées en deux micro-services distincts, il peut être nécessaire de faire l'intégration du tout dans un troisième micro-service. |
||||
|
|
||||
|
Ce cas de figure dépasse ce qui est disponible en standard dans OpenShift. |
||||
|
Il faudra alors se tourner vers [Red Hat Fuse](https://www.redhat.com/fr/technologies/jboss-middleware/fuse) / [Apache Camel](https://camel.apache.org/). |
||||
|
|
||||
|
## Conclusion |
||||
|
|
||||
|
Cet article a présenté le motif de conception *Strangler Fig* et son implémentation dans OpenShift au moyen de la fonction *Path Routing*. |
||||
Loading…
Reference in new issue