Aller au contenu principal

Diagrams as Code

· 5 minutes de lecture
Stéphane ROBERT
Consultant DevOps

Diagrams sont deux librairies, une en Python l’autre en Go, permettant de créer rapidement des schémas d’architecture. Le gros avantage est qu’il n’a pas besoin de recourir à un éditeur pour les créer, si ce n’est la présence de Graphviz sur votre poste de travail. On peut aussi imaginer d’ajouter un petit bout de code dans votre CI pour les générer automatiquement à chaque détection de sa modification.

Diagrams permet de créer des schémas pour la plupart des systèmes de Cloud : AWS, Azure, GCP, AlibabaCloud,… mais aussi pour le On-Premise et même pour les clusters Kubernetes.

Installation de Graphviz

Dans un premier procédons à l’installation de Graphviz :

Debian et dérivées :

sudo apt install graphviz

Rhel et dérivées :

sudo yum install graphviz

Arch et dérivées :

sudo pacman -S graphviz

Sur macOS :

brew install graphviz

Sur windows :

choco install graphviz
#ou
winget install graphviz

Ecrire des schémas en Go Python

Installation de la librairie

Personnellement j’utilise pour gérer mes dépendances python sur la grande majorité de mes projets écrits en python. Pour ne pas ajouter de librairies non indispensables à mes projets, ce genre de librairies je l’installe au niveau des dépendances dev :

poetry add --dev diagrams
poetry install --only dev

Plus classiquement avec pip :

pip install diagrams

Ecrire un schéma

En plus de la librairie principale, il faut importer celles prenant en charge votre système de cloud et qui permettra d’instancier des Nodes.

Prenons par exemple le schéma suivant :

from diagrams import Diagram
from diagrams.aws.compute import EC2
from diagrams.aws.database import RDS
from diagrams.aws.network import ELB

Ensuite, on instancie un Diagram, dans lequel on ajoute des instances des nodes.

with Diagram("Client to Application Flow", show=False):
    ELB("lb") >> [EC2("worker1"),
                  EC2("worker2"),
                  EC2("worker3"),
                  EC2("worker4"),
                  EC2("worker5")] >> RDS("events")

Pour connecter les nodes entre eux il faut utiliser les opérateurs <<, >> en fonction de la direction ou - pour une connexion sans direction. Ici notre ELB est connecté aux cinq instances EC2.

On peut utiliser les options suivantes au moment de l’instanciation de la classe diagrams :

  • format="jpg" : pour choisir un format d’image parmi : png, jpg, svg et pdf.
  • filename="nom-de-limage" l’extension est fonction de celle choisie.
  • show="False" permet de dire qu’il ne faut pas ouvrir l’image une fois générée.
  • graph_attr, node_attr and edge_attr permet de définir des propriétés de rendu comme la couleur du fond, la taille des polices de caractère…
from diagrams import Diagram
from diagrams.aws.compute import EC2

graph_attr = {
    "fontsize": "45",
    "bgcolor": "transparent"
}
with Diagram("Simple Diagram", show=False, graph_attr=graph_attr):
...

Pour regrouper des nodes en cluster on utilisera la librairie Cluster :

from diagrams import Cluster, Diagram
from diagrams.aws.compute import ECS
from diagrams.aws.database import RDS
from diagrams.aws.network import Route53

with Diagram("Simple Web Service with DB Cluster", show=False):
    dns = Route53("dns")
    web = ECS("service")

    with Cluster("DB Cluster"):
        db_primary = RDS("primary")
        db_primary - [RDS("replica1"),
                     RDS("replica2")]

    dns >> web >> db_primary

Pour regrouper les nodes en fonction par exemple de leur réseau, ou de leur utilité on utilisera les edges :

from diagrams import Cluster, Diagram, Node, Edge
from diagrams.k8s.compute import Pod
from diagrams.k8s.network import Ing
from diagrams.gcp.network import LoadBalancing
from diagrams.onprem.network import Nginx

with Diagram("Cluster nesting", show=False):

    gcp_lb = LoadBalancing("GCP LB")

    with Cluster("Kubernetes"):

        with Cluster("Nginx"):
            nginx= Nginx("")

        with Cluster("MyApp"):
            myapp_ing = Ing("")
            with Cluster("Pods"):
                myapp_pods = Pod("myapp")

        with Cluster("MySQL"):
            myapp_db = Pod("myapp-db")

    gcp_lb >> Edge(headport="c", tailport="c", minlen="1", lhead='cluster_Kubernetes') >> nginx
    nginx >> Edge(headport="c", tailport="c", minlen="1", lhead='cluster_MyApp') >> myapp_ing >> Edge(headport="c", tailport="c", minlen="1", lhead='cluster_MyApp pods') >> myapp_pods >> myapp_db

En plus des librairies de base il est possible de créer des classes custom utilisant des icones externes (en local, ou en ligne) :

from diagrams import Diagram, Cluster
from diagrams.custom import Custom
from urllib.request import urlretrieve

with Diagram("Custom with remote icons", show=False, filename="custom_remote", direction="LR"):

  # download the icon image file
  diagrams_url = "https://github.com/mingrammer/diagrams/raw/master/assets/img/diagrams.webp"
  diagrams_icon = "diagrams.png"
  urlretrieve(diagrams_url, diagrams_icon)

  diagrams = Custom("Diagrams", diagrams_icon)

  with Cluster("Some Providers"):

    openstack_url = "https://github.com/mingrammer/diagrams/raw/master/resources/openstack/openstack.png"
    openstack_icon = "openstack.png"
    urlretrieve(openstack_url, openstack_icon)

    openstack = Custom("OpenStack", openstack_icon)

    elastic_url = "https://github.com/mingrammer/diagrams/raw/master/resources/elastic/saas/elastic.png"
    elastic_icon = "elastic.png"
    urlretrieve(elastic_url, elastic_icon)

    elastic = Custom("Elastic", elastic_icon)

  diagrams >> openstack
  diagrams >> elastic

Un autre exemple :

Toutes les classes sont décrites dans la documentation en ligne. Cette documentation est très complète et regorge d’exemples, d’ailleurs je les ai utilisé pour écrire ce billet.

Ecrire des schémas en Go

Pour installer la version go de Diagrams :

go get github.com/blushft/go-diagrams

Créez votre premier diagramme d’architecture en Go

Cette librairie reprend le même principe que celle en python :

d, err := diagram.New(diagram.Label("my-diagram"), diagram.Filename("diagram"))
if err != nil {
    log.Fatal(err)
}

fw := generic.Network.Firewall().Label("fw")
sw := generic.Network.Switch().Label("sw")

d.Connect(fw, sw)

Par contre, pas de trace de documentation ! Il faudra s’inspirer des exemples présent sur le projet.

Conclusion

Vraiment sympathique ces Diagrams as Code non ? Personnellement, je vais l’utiliser dans mes futurs projets. Reste juste à créer une image de container pour l’utiliser dans vos CI. Je l’ajouterai rapidement d’ailleurs.