Retour d’expérience technique de plus d’un an de mission chez TF1 sur #Go, #GraphQL, des micro-services contactés en #gRPC suivant un contrat d’interface avec #Protobuf.
J’ai aussi abordé les sujets de #WebPerf avec du cache applicatif et HTTP et enfin un 3ème volet sur le déploiement #Kubernetes, #Kustomize, le monitoring avec #Grafana et #Prometheus et la façon dont les développeurs travaillent en local avec tous ces micro-services.
7. Architecture micro-services.
Début
~Juin 2018
Juin 2019
MEPMon
arrivée
Catalog
Search API
SEO API
Mediatheque
/
Image proxy
Novembre 2018
CMS
User
History
Favory
Reco
API
Benchmarks
Derniers
tests
/
correctifs
Go
GraphQL
Kubernetes
7
8. Architecture micro-services.
Début
Février 2020
~Juin 2018
Juin 2019
MEPMon
arrivée
Catalog
Search API
SEO API
Mediatheque
/
Image proxy
Novembre 2018
CMS
User
History
Favory
Reco
API
Benchmarks
Derniers
tests
/
correctifs
Correctifs
Post-MEP
Ajout de logs,
métriques
ISP Exporter
User Downloader
Refactoring de
code
Go
GraphQL
Kubernetes
8
13. ● Go
○ Utilisation du langage et avantages
○ Activation de Docker buildkit
○ gRPC / Protobuf avec Go
● Performances en production
○ GraphQL : Gestion de cache (cache applicatif)
○ GraphQL : Persisted queries (cache HTTP)
○ Proxy images : retaille d’images et invalidation
● DevOps
○ Monitoring avec Prometheus et Grafana
○ Kubernetes, Kustomize et Linkerd
○ Environnement de développement local
Sommaire.
15. Utilisation du langage et avantages.
● Général
○ Facile à apprendre, les développeurs montent vite en compétence
○ Éprouvé chez Google (~10 ans)
○ Très utilisé par les outils d’infrastructure (Docker, Kubernetes, Consul, …)
○ Communauté grandissante, utilisée par des acteurs français
■ Molotov TV, Teads.tv, Algolia, Heetch, Orange, Veepee, Mention, TF1 …
○ Langage bas niveau, léger et offrant d’excellente possibilités de performances
(Goroutines)
■ var job = job.New()
■ go job.Run()
○ Possibilité d’échanger des données entre plusieurs goroutines via les channels
15
16. Utilisation du langage et avantages.
● Goroutine
“A goroutine is a lightweight thread managed
by the Go runtime”
https://tour.golang.org/concurrency/1
16
17. Utilisation du langage et avantages.
● Channel
○ Partager des valeurs entre les Goroutines sans problème d’accès concurrent
○ Un channel peut recevoir n’importe quel type (int, string, struct, func, …)
UNBUFFERED.
BUFFERED.
GR2GR1
CHANNEL.
GR2GR1
CHANNEL.
GR2GR1
CHANNEL.
GR2GR1
CHANNEL.
GR2GR1
CHANNEL.
GR2GR1
CHANNEL.
c := make(chan string) c := make(chan string, 100)
17
18. Utilisation du langage et avantages.
● Exemple Goroutine / channel
○ Ecriture d’un spooler (file d’attente) :
■ Empilement de données dans un channel et envoi toutes les 30 secondes
■ Si le channel atteint sa capacité maximale (100), envoi des données
30s. 30s. 30s. 30s. 30s.
Flush.
Flush.
Flush.
Flush.
Flush.
Flush.
LIMITE
18
19. Utilisation du langage et avantages.
● Exemple Goroutine / channel
main.go
type Spooler struct {
ItemChan chan *SpoolerItem
}
type SpoolerItem struct {
ID int
}
func main() {
var itemChan = make(chan *SpoolerItem, 100)
var spooler = &Spooler{
ItemChan: itemChan,
}
go spooler.Run()
for i := 0; i <= 100000; i++ {
item := &SpoolerItem{ID: i}
spooler.Add(item)
time.Sleep(time.Duration(rand.Intn(2000 - 100)) * time.Millisecond)
}
}
main.go
func (s *Spooler) Flush() { // Do something }
// Run executes the ticker to tick and flush every 30 seconds
func (s *Spooler) Run() {
ticker := time.NewTicker(30 * time.Second)
for range ticker.C {
s.logger.Info("Ticker: flushing queue")
s.Flush()
}
}
// Add adds a new item into the spooler queue
func (s *Spooler) Add(item *SpoolerItem) {
if len(s.ItemChan) == cap(s.ItemChan) {
s.logger.Info("Max video queue reached: flushing queue")
s.Flush()
}
s.ItemChan <- item
}
19
20. Utilisation du langage et avantages.
● Consommation d’une Goroutine
○ Dépend uniquement de sa consommation mémoire
○ Une Goroutine ne bloquera jamais votre programme, mais peut ralentir le garbage
collector
○ Go utilise au minimum 2kb / Goroutine (100 000 Goroutines = ~200mb)
https://github.com/golang/go/blob/master/src/runtime/stack.go#L72
20
21. Utilisation du langage et avantages.
● Gestion des dépendances
○ Normalisée avec les Go modules (depuis Go 1.13)
○ On travaille directement dans le répertoire du projet (plus de $GOPATH)
○ Gestion de dépendance ≠ Vendoring : GOFLAGS=-mod=vendor
go.mod
module go.tf1.fr/mytf1/catalog-graphql
go 1.13
require (
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/heetch/confita v0.8.0
github.com/prometheus/client_golang v0.9.3
go.tf1.fr/platform/cms-api v1.9.1
go.tf1.fr/platform/zap-graylog v1.4.5
go.uber.org/zap v1.10.0
)
main.go
package main
import (
"go.tf1.fr/mytf1/catalog-graphql/config"
"go.uber.org/zap"
)
func main() {
var conf = config.Load()
var logger = zap.New()
// ...
}
21
22. Utilisation du langage et avantages.
● Gestion des dépendances
○ Deux meta “go-import” et “go-source” retournent les informations pour accéder
au repository Git
Terminal
$ curl go.uber.org/zap
<!DOCTYPE html>
<html>
<head>
<meta name="go-import" content="go.uber.org/zap git https://github.com/uber-go/zap">
<meta name="go-source" content="go.uber.org/zap https://github.com/uber-go/zap
https://github.com/uber-go/zap/tree/master{/dir} https://github.com/uber-go/zap/tree/master{/dir}/{file}#L{line}">
22
23. Utilisation du langage et avantages.
● Langage compilé
○ Possibilité de compiler sur toutes les architectures:
■ GOOS=linux GOARCH=amd64 go build -o app .
○ Une fois buildé, le binaire peut être exécuté directement, sans aucune dépendance
○ Images Docker légères = limite les failles de sécurité
Terminal
$ cat Dockerfile
FROM golang:alpine as builder
ENV GOFLAGS -mod=vendor
RUN go build -o /catalog-graphql ./cmd/server
FROM scratch
COPY --from=builder /catalog-graphql /
CMD ["/catalog-graphql"]
23
25. Activation de Docker buildkit.
● Opt-out pour le moment
○ Activation des fonctions “experimentales” Docker buildkit sur les builds
■ Réduit significativement le temps de build
○ Une variable d’environnement à ajouter lors de “docker build” et une ligne dans le
Dockerfile :
Terminal
$ cat Dockerfile
# syntax = docker/dockerfile:experimental
FROM golang:alpine as builder
$ DOCKER_BUILDKIT=1 docker build -t mon-image .
x2
25
26. Activation de Docker buildkit.
● Lecture améliorée du Dockerfile
○ Lit le fichier en partant de la fin pour simplifier l’arbre de décision
○ Exécute les ordres en utilisant de la concurrence si possible
FROM
RUN
RUN
FROM
RUN
RUN
RUN
FROM
RUN
FROM
RUN
FROM
RUN
RUN
RUN
FROM
RUN
RUN
LEGACY.
BUILDKIT.
26
27. Activation de Docker buildkit.
● Build amélioré grâce à gRPC
○ Echanges du contexte de build grandement améliorés :
■ Legacy : le client fait un .tar et envoie tout le répertoire courant
■ Buildkit : le client envoie les fichiers Dockerfile, .dockerignore et uniquement
ce qui est nécessaire
Terminal
$ DOCKER_BUILDKIT=1 docker build -t test .
[+] Building 0.1s (5/5) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 37B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
[...]
=> writing image
sha256:ba5bca3a525ac97573b2e1d3cb936ad50cf8129eedfa9 0.0s
Terminal
$ docker build -t test .
Sending build context to Docker daemon 4.315GB
[...]
Successfully built c9ec5d33e12e
27
28. Activation de Docker buildkit.
● Point de montage host <> build
○ Possibilité de partager :
■ un cache de build (go, npm, composer, …)
■ un cache de packages unix (apt-get install, apk add …)
■ ...
Terminal
# syntax = docker/dockerfile:experimental
FROM golang:alpine as builder
RUN --mount=type=cache,target=/var/cache/apk
apk add ca-certificates
RUN --mount=type=cache,target=/root/.cache/go-build
go build -o /catalog-graphql ./cmd/server
28
29. Activation de Docker buildkit.
● Point de montage host <> build
○ D’autres types disponibles :
■ --mount=type=[ bind | secret | ssh ]
○ Pour plus d’informations :
■ https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md
Terminal
# syntax = docker/dockerfile:experimental
FROM golang:alpine as builder
RUN --mount=type=secret,id=aws,target=/root/.aws/credentials
aws s3 cp s3://... …
RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts
RUN --mount=type=ssh ssh -q -T git@github.com
29
31. gRPC / Protobuf avec Go.
● Qu’est-ce-que Protobuf ?
○ Implémentation de Protocol Buffers, un format de sérialisation multi-langage (Go,
Java, Javascript, PHP, …) via une description d’interface
syntax = "proto3";
package catalog;
service Catalog {
rpc GetVideos (GetVideosRequest)
returns (GetVideosResponse) {}
}
message GetVideosRequest {
int32 offset = 1;
int32 limit = 2;
}
message GetVideosResponse {
int32 total = 1;
bool has_next = 2;
int32 offset = 3;
repeated Video items = 4;
}
message Video {
string id = 1;
string stream_id = 2;
VideoType type = 3;
string title = 4;
}
enum VideoType {
REPLAY = 0;
BONUS = 1;
EXTRACT = 2;
ADVERTISEMENT = 3;
}
31
32. gRPC / Protobuf avec Go.
● Qu’est-ce-que gRPC ?
○ Protocole d’échange RPC (Remote Procedure Call) qui utilise Protocol Buffers par
défaut comme langage de définition
○ Basé sur HTTP/2, offre une librairie haut-niveau pour interagir entre plusieurs
langages via un même contrat d’interface
○ Gestion des erreurs, load balancing, failover, …
○ Transit du contexte de la requête
ServerClient
Appel GetVideos()
Réponse GetVideos()
Stream de données
GoJS
32
33. gRPC / Protobuf avec Go.
○ Il faut tout d’abord compiler un fichier qui sera le même pour le/les client(s) et le
serveur (contrat d’interface entre les deux).
○ Compilation du fichier de définition (catalog.proto) via l’outli CLI “protoc” fourni
avec Protobuf :
Terminal
$ protoc -I ./api catalog.proto --go_out=plugins=grpc:./pkg/grpc
// Génère du code Go dans le fichier : ./pkg/grpc/catalog.pb.go
33
34. gRPC / Protobuf avec Go.
catalog.pb.go
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: catalog.proto
package catalog
import (
context "context"
fmt "fmt"
proto "github.com/golang/protobuf/proto"
wrappers "github.com/golang/protobuf/ptypes/wrappers"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
...
34
35. gRPC / Protobuf avec Go.
Mise en place d’un controller (struct) permettant d’accueillir les méthodes gRPC
controller.go
import (
“context”
“error”
"go.tf1.fr/mytf1/catalog-api/pkg/catalog"
)
type Controller struct { ... }
func (c *Controller) GetVideos(ctx context.Context, req *catalog.GetVideosRequest) (*catalog.GetVideosResponse, error) {
videos, err := c.elasticsearchClient.GetVideos(ctx)
if err != nil {
return nil, err
}
return &catalog.GetVideosResponse(items: videos), nil
}
35
36. gRPC / Protobuf avec Go.
Mise en route du serveur gRPC sur le port “8080”
server.go
import (
"net"
"go.tf1.fr/mytf1/catalog-api/pkg/catalog"
"google.golang.org/grpc"
)
func main() {
var server = grpc.NewServer()
var controller = &Controller{}
catalog.RegisterCatalogServer(server, controller)
listener, _ := net.Listen("tcp", ":8080")
if err := server.Serve(listener); err != nil {
panic(err)
}
}
36
37. gRPC / Protobuf avec Go.
Côté client, on contacte le serveur gRPC
server.go
import (
"context"
"go.tf1.fr/mytf1/catalog-api/pkg/catalog"
"google.golang.org/grpc"
)
func main() {
conn, _ := grpc.Dial("localhost:8080", grpc.WithInsecure())
defer conn.Close()
client := catalog.NewCatalogClient(conn)
videos, err := client.GetVideos(context.Background(), &catalog.GetVideosRequest{Offset: 0, Limit: 10})
if err != nil {
panic(err)
}
// Les vidéos sont dans la variable videos
}
37
38. gRPC / Protobuf avec Go.
● Simple à mettre en place
○ Une nouvelle méthode RPC s’ajoute très facilement/rapidement
● Contrat d’interface clair et versioning
○ Le fichier généré “.pb.go” ainsi que le code du client Go (controller) sont
versionnés avec le code serveur
○ Si le contrat du serveur change, on tag une nouvelle release et on récupère les
clients sur les projets via la gestion de dépendance
● Excellentes performances
○ En production, en pic de charge (soirée), 99% des méthodes RPC répondent en
moins de 100ms .
38
40. GraphQL : Gestion de cache (cache applicatif).
● Au début : cache mémoire GraphQL
○ Afin de cacher les réponses (contextualisées) des micro-services tiers
■ un cache mémoire dans chaque pod GraphQL
■ Inconvénient : lors d’un déploiement en production, les caches mémoires
sont vides sur tous les pods en même temps
Pod 1
Cache mémoire
Pod 2
Cache mémoire
Pod 3
gRPC Server
HIGH
40
41. GraphQL : Gestion de cache (cache applicatif).
● Maintenant : cache mémoire / Redis partagé
○ Afin d’éviter de trop solliciter nos briques back-end via gRPC, nous avons ajoutés
des layers de cache dans GraphQL :
■ un cache mémoire pour les éléments les plus chauds
■ un cache Redis partagé en fallback
Redis
Pod 1
Cache mémoire
Pod 2
Cache mémoire
Cache Redis Cache Redis
Pod 3
gRPC Server
LOW
41
42. GraphQL : Gestion de cache (cache applicatif).
● Echange de données
○ Retourner au maximum uniquement des identifiants (vidéo, programme, …)
○ Chargement des données via une couche de “data loader” : bénéfice du cache
■ Évite de faire un nouvel appel gRPC GetVideo(id) ou GetProgram(id) si la
donnée est en cache (quelques secondes)
■ Une donnée chargée pour un utilisateur sert au suivant
Data loader
Cache HITid-1
id-2
id-3
Pod 3
gRPC ServerCache HIT
Cache MISS
id-4 Cache MISS
42
44. GraphQL : Persisted queries (cache HTTP).
● Que sont les persisted queries ?
○ Par défaut, les requêtes GraphQL étant en POST, pas de cache HTTP possible
GraphQL
Handler GraphQL
AWS Cloudfront
Cache MISS
POST /graphql
{
query: {
getVideos(limit: 10) {
total
items {
title
}
}
}
}
x10
44
45. GraphQL : Persisted queries (cache HTTP).
● Que sont les persisted queries ?
○ Par défaut, les requêtes GraphQL étant en POST, pas de cache HTTP possible
○ Solution : passer sur du GET avec un identifiant de requête (checksum)
○ Gains en bande passante (cache, payloads, trafic interne)
GraphQL
Handler GraphQL
AWS Cloudfront
Cache MISS
GET /graphql?id=37hdk087d32j4S42k55d2
Elasticsearch
Cache HIT
x10
x9
x1
45
46. GraphQL : Persisted queries (cache HTTP).
● Comment fait-on ?
○ Les fronts build l’ensemble des requêtes GraphQL qu’ils utilisent sur leur
application et contactent une API
○ L’API stocke l’identifiant et la requête dans un Elasticsearch
Persisted query API
37hdk087d32j4S42k55d2 Elasticsearch
{
query: {
getVideos(limit: $limit) {
total
items {
title
}
}
}
}
GraphQL
HTTP Handler
37hdk087d32j4S42k55d2
{query: {getVideos(limit: $limit){total items { title }}}}
46
48. AWS Lambda
Proxy images : Retaille d’images et invalidation.
● Contexte : image proxy
○ Documents (images, videos, ...) stockés dans Amazon S3 : ~600 000 objets
○ Accès en HTTP via Cloudfront en déclenchant une fonction AWS Lambda
○ Sécurité pour éviter de demander n’importe quelle dimension d’image
Proxy image
HTTP Handler AWS
S3
//photos.tf1.fr/<width>/<height>/<slug>-<token>@<scale>x.<ext>
<slug>
Security
Resizer
//photos.tf1.fr/600/200/jean-pierre-foucault-2h6d3@1x.png
OK
OK
AWS
Cloudfront
Cache
48
49. Proxy images : Retaille d’images et invalidation.
https://docs.aws.amazon.com/lambda/latest/dg//limits.html
49
50. Proxy images : Retaille d’images et invalidation.
● Solution : Cloudfront “multi-origins”
○ La 1ère origine, bucket AWS S3, vérifie si le contenu existe sur Amazon S3
■ Si oui : renvoie les informations de l’origine S3 à Cloudfront
■ Si non : appelle la Lambda de resize puis renvoie des informations de
l’origine S3 à Cloudfront
AWS Lambda
AWS
S3
//photos.tf1.fr/<width>/<height>/<slug>-<token>@<scale>x.<ext>
//photos.tf1.fr/600/200/jean-pierre-foucault-2h6d3@1x.png
Proxy image
Resizer
1
3
AWS
Cloudfront
Cache
4
2
50
51. Kubernetes
Proxy images : Retaille d’images et invalidation.
● Solution retenue : pod Kubernetes
○ Plus simple à mettre en oeuvre, délais serrés avant la MEP
○ 10 pods en production pour servir tous les contenus
○ Cache de 24h dans Cloudfront et 1h dans nginx
Proxy image
HTTP Handler AWS
S3
//photos.tf1.fr/<width>/<height>/<slug>-<token>@<scale>x.<ext>
<slug>
Security
Resizer
//photos.tf1.fr/600/200/jean-pierre-foucault-2h6d3@1x.png
OK
OK
AWS
Cloudfront
Nginx
Proxy
Cache
Cache
51
52. Proxy images : Retaille d’images et invalidation.
● Invalidation du cache des images
○ Lors de l’upload d’une nouvelle image, invalidation des deux caches :
■ Nginx : via le plugin “proxy_cache_purge”
Kubernetes
Proxy
image
Resizer
AWS
Cloudfront
Nginx
Proxy
Cache
Cache
go.mod
proxy_cache_revalidate on;
proxy_cache_lock on;
proxy_cache_path /mnt/images_cache levels=2:2 keys_zone=cache:100m
max_size=10g inactive=1d use_temp_path=off;
location ~ /purge(/.*) {
allow 10.1.1.0/24;
deny all;
proxy_cache_purge cache $1$is_args$args;
}
$ curl https://photos.tf1.fr/purge/600/200/jean-pierre-foucault-23h4s@1x.png
52
53. Proxy images : Retaille d’images et invalidation.
● Invalidation du cache des images
○ Lors de l’upload d’une nouvelle image, invalidation des deux caches :
■ Cloudfront : via le SDK AWS
Kubernetes
Proxy
image
Resizer
AWS
Cloudfront
Nginx
Proxy
Cache
Cache
invalidation.go
import "github.com/aws/aws-sdk-go/service/cloudfront"
var myImagePath = “/600/200/jean-pierre-foucault-23h4s@1x.png”
var service = cloudfront.New(session)
service.CreateInvalidation(
&cloudfront.CreateInvalidationInput{
DistributionId: <cloudfront-distribution-id>,
InvalidationBatch: &cloudfront.InvalidationBatch{
CallerReference: <unique-id>
Paths: &cloudfront.Paths{Items: []*string{myImagePath}}
},
},
)
53
55. DevOps : Monitoring avec Prometheus et Grafana.
● Prometheus
○ Récupération et stockage de métriques dans sa propre base de données
○ Fourni un langage “PromQL” pour requêter les données
● Grafana
○ Fournit une interface web permettant de requêter des sources de données (dont
Prometheus)
○ Permet de composer des dashboards à partir de graphiques
55
57. DevOps : Monitoring avec Prometheus et Grafana.
● et surtout … l’alerting !
○ Être informé lorsqu’une valeur passe sur ou sous un seuil
■ Slack, email, webhook, Telegram, ...
57
58. DevOps : Monitoring avec Prometheus et Grafana.
Requêtes par écran.
Temps de réponse jusqu’au 99ctl.
58
60. DevOps : Monitoring avec Prometheus et Grafana.
Resolvers GraphQL en erreur.
Services gRPC en erreur.
60
61. DevOps : Monitoring avec Prometheus et Grafana.
● Métriques : Go
○ Le client Prometheus Go expose par défaut des métriques techniques Go :
■ Temps d’exécution médian du garbage collector
■ Nombre de Go routines en cours d’exécution
■ Mémoire utilisée par le processus Go, etc ...
61
62. DevOps : Monitoring avec Prometheus et Grafana.
● Métriques : Cache
○ Taux d’utilisation des différents caches mémoire (videos, programmes, …)
62
63. DevOps : Monitoring avec Prometheus et Grafana.
● Prometheus : récupération de métriques
○ Prometheus récupère (scrape) des métriques à un interval défini
■ Exposées en HTTP
■ Possibilité de pousser des métriques via Push Gateway
○ Tous nos micro-services exposent des métriques
Prometheus
Scraping
Kubernetes
Search API
User Favorites
...
Database
Server
Grafana
Web UI
/metrics
/metrics
/metrics
63
64. DevOps : Monitoring avec Prometheus et Grafana.
● Exposer une métrique avec le client Go Prometheus
○ Plusieurs types possibles : Counter, Gauge, Histogram
metrics.go
package metrics
import "github.com/prometheus/client_golang/prometheus"
var cacheCollector = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "collector",
Namespace: "cache",
Help: "This represent the number of items in cache",
},
[]string{"service", "cache_key", "metric"},
)
cacheCollector.WithLabelValues("catalog", "allVideosCaches", "hit_count").Set(3)
64
65. DevOps : Monitoring avec Prometheus et Grafana.
● Format de lecture des métriques par Prometheus
65
66. DevOps : Monitoring avec Prometheus et Grafana.
● Grafana : Requêter Prometheus
○ Langage PromQL :
■ Utilisation de fonctions (sum, rate, round, sort, …)
https://prometheus.io/docs/prometheus/latest/querying/functions/
■ Filtres par labels, utilisation d’expressions régulières et variables
66
68. DevOps : Kubernetes, Kustomize et Linkerd.
● Qu’est-ce-que Kubernetes ?
○ Orchestrateur de conteneurs Docker, définition de ressources YAML :
■ Ingress : Réception d’une requête à destination du cluster
■ Service : Abstraction qui définit un ensemble logique de pods et une politique
permettant d’y accéder
■ Pod : Groupement d’un ou plusieurs conteneurs
■ Deployment : Décrit la façon dont les pods sont déployés (nombre de replicas,
liveness, readiness, …)
■ Config Maps : Configuration utilisée par les déploiements/pods
Ingress
Catalog GraphQL
Service
Catalog GraphQL
Mediatheque
Pod
Mediathèque
Deployment
Catalog GraphQL
Mediatheque
Catalog GraphQL
Mediatheque
Config Maps
graphql
mediath...
68
70. DevOps : Kubernetes, Kustomize et Linkerd.
● Appliquer un fichier de ressource Kubernetes
○ via le CLI Kubernetes : kubectl
metrics.go
# Application de la ressource “config map” GraphQL
$ kubectl --context integration -n mytf1 -f ./mytf1/integration/catalog-graphql-configmap.yaml
configmap/catalog-graphql configured
# Application de la ressource “deployment” GraphQL
$ kubectl --context integration -n mytf1 -f ./mytf1/integration/catalog-graphql-deploy.yaml
deployment.extensions/catalog-graphql configured
# Obtention des pods actuellement créés sur le cluster
$ kubectl --context integration -n mytf1 get pods
NAME READY STATUS RESTARTS AGE
catalog-graphql-7dc4bfd449-ktwk7 1/1 Running 0 1m
70
71. DevOps : Kubernetes, Kustomize et Linkerd.
● Organisation des fichiers de ressources
○ Dupliqués pour chaque environnement ...
mytf1
catalog-graphql-configmap.yaml
integration
catalog-graphql-deploy.yaml
catalog-graphql-service.yaml
catalog-graphql-configmap.yaml
validation
catalog-graphql-deploy.yaml
catalog-graphql-service.yaml
...
...
preprod
catalog-graphql-configmap.yaml
Equivalents à
.90%.
71
72. DevOps : Kubernetes, Kustomize et Linkerd.
● Pour la moindre modification ...
○ … X fichiers à modifier, ou X = le nombre d’environnements
mytf1
catalog-graphql-configmap.yaml
integration
catalog-graphql-deploy.yaml
catalog-graphql-service.yaml
catalog-graphql-configmap.yaml
validation
catalog-graphql-deploy.yaml
catalog-graphql-service.yaml
...
...
preprod
catalog-graphql-configmap.yaml
Equivalents à
.90%.
72
73. DevOps : Kubernetes, Kustomize et Linkerd.
● Kustomize à la rescousse !
○ Templating et patch de la configuration de base
mytf1
catalog-graphql
base
catalog-graphql-deploy.yaml
catalog-graphql-service.yaml
catalog-graphql-ingress.yaml
kustomization.yaml
resources
overlays
integration
validation
kustomization.yaml
patches
kustomization.yaml
73
76. DevOps : Kubernetes, Kustomize et Linkerd.
● Kustomize : CLI
○ Génération des fichiers GraphQL pour l’overlay “prod”
■ Déploiement via kubectl
Terminal
# Génération des fichiers
$ kustomize build mytf1/catalog-graphql/overlays/prod
apiVersion: v1
data:
MID_CAT_GRAPH_PERSISTED_QUERIES_ONLY: true
...
# Pour appliquer les changements, utiliser kubectl
$ kustomize build mytf1/catalog-graphql/overlays/prod | kubectl --context prod -n mytf1 apply -f -
configmap/catalog-graphql unchanged
deployment.extensions/catalog-graphql configured
76
77. DevOps : Kubernetes, Kustomize et Linkerd.
● Helm : pour aller plus loin
○ Gestion des dépendances pendant les déploiements
■ Une “chart” Helm englobe plusieurs services
○ Gestion de versions d’une collection de plusieurs micro-services
■ Une “chart” Helm est versionnée
○ Gestion de versions des changements sur la chart
○ Simplification des rollbacks
https://helm.sh/
77
78. DevOps : Kubernetes, Kustomize et Linkerd.
● Linkerd : Service mesh
○ Sécurité, routage et traçabilité des flux entre micro-services
■ Gestion du load balancing / autoscaling
■ Déploiement blue/green (canary releases)
■ Vision sur le routage des requêtes entre les micro-services
■ Exposition de métriques vers Prometheus
Kubernetes
Linkerd
Web UI
GraphQL
HTTP
CONTROLPLANE.
DATAPLANE.
Pools gRPC.
Catalog API
User Favorites
...
78
79. DevOps : Kubernetes, Kustomize et Linkerd.
● Linkerd : Installation
○ Simple, rapide, efficace
■ Installe ses outils dans un namespace dédié (linkerd) par défaut
Terminal
# Installation du CLI Linkerd
$ curl -sL https://run.linkerd.io/install | sh
# Installation de Linkerd sur le cluster Kubernetes
$ linkerd install | kubectl apply -f -
# Vérification de l’installation
$ linkerd check
$ kubectl -n linkerd get pods
NAME READY STATUS RESTARTS AGE
linkerd-controller-5f865d7998-4v8l4 3/3 Running 0 61d
linkerd-destination-797df4fc87-kxjgn 2/2 Running 0 61d
linkerd-grafana-59875c9ff-x7cm6 2/2 Running 0 61d
linkerd-identity-ff594df8-fnx79 2/2 Running 0 61d
linkerd-prometheus-64d7df6bd7-mwpfd 2/2 Running 0 20d
...
79
83. Environnement de développement local.
● Besoins des développeurs
○ Après un débat avec l’équipe, voici les besoins principaux :
■ Lancer un pool d’applications en local simplement
■ Avoir du 🔥hot reloading sur ses applications locales, plus besoin d’aller
relancer le “go run ...”
■ Faire du port-forward sur Kubernetes ou simplement via SSH
■ Avoir une reconnexion automatique lorsque qu’un forward perd la
connexion
■ Utiliser des hostnames personnalisés, mappés sur des IPs privées pour avoir
plusieurs applications mappées sur un même port
■ Tout en gardant un partage simplifié de la configuration entre les
développeurs
83
84. Environnement de développement local.
● Solutions existantes
Port-forward Kubernetes en local
Lancer des applications (images
Docker) dans un cluster
Kubernetes
Build d’image Docker + deploy sur
Kubernetes
https://skaffold.dev/
https://www.telepresence.io/
https://kubefwd.com/
84
85. Environnement de développement local.
● Skaffold : Testé mais non approuvé
○ Très bon outil mais :
■ ne permet pas de forwarder des services distants en local
■ oblige d’avoir une instance Kubernetes en local
85
86. Environnement de développement local.
● Finalement
○ Pas de solution tranchée
■ À titre personnel, j’ai développé Monday, un outil pour gérer tous ces cas
■ Certains l’utilisent mais pas tout le monde
■ On ne veut pas imposer un outil, chacun doit rester libre de ses outils
https://github.com/eko/monday
86
89. Environnement de développement local.
Local applications
Microservice 1
Microservice 2
Microservice 3
YAML Configuration
Database 1
RUNNER
Monday components
Terminal
$ monday
RUNSETUP
89
90. Environnement de développement local.
Local applications
Microservice 1
Microservice 2
Microservice 3
YAML Configuration
Database 1
RUNNER
WATCHER
Monday components
Hot reload
Terminal
$ monday
RUNSETUP
90
91. Environnement de développement local.
SSH Connection
Local applications
Microservice 1
Microservice 2
Microservice 3
YAML Configuration
Kubernetes cluster
Microservice 5
Microservice 6
Database 1
FORWARDER
RUNNER
WATCHER
Monday components
Hot reload
Terminal
$ monday
PROXY
Microservice 4
Monday Proxy
RUNSETUP
REMOTE
LOCAL
PROXY
TCP Connection
Another service
TCP
Auto
reconnect
91
92. Environnement de développement local.
● Monday : Configuration
○ Déclaration de plusieurs “projets”
■ 1 projet = plusieurs micro-services en local et/ou forward
Terminal
$ cat ~/monday.project.yaml
projects:
- name: “MyTF1: Catalog GraphQL + Catalog API”
local:
- *catalog-graphql-local
- *catalog-api-local
forward:
- *search-api-kubernetes
- *reco-api-kubernetes
- *reco-persona-kubernetes
- *user-favorites-kubernetes
- *user-history-kubernetes
92
93. Environnement de développement local.
● Monday : Configuration
○ Exemple de déclaration d’une application exécutée en local
Terminal
$ cat ~/monday.local.yaml
<: &catalog-graphql-local
name: catalog-graphql
path: $GOPATH/go.tf1.fr/mytf1/catalog-graphql
watch: true
hostname: catalog-graphql.svc.local
executable: go
args:
- run
- -mod=vendor
- cmd/server/main.go
env:
- HTTP_PORT: 8000
93
94. Environnement de développement local.
● Monday : Configuration
○ Exemple de déclaration d’une application “forwardée” sur Kubernetes
Terminal
$ cat ~/monday.forward.yaml
<: &search-api-kubernetes
name: search-api
type: kubernetes
values:
context: *kubernetes-context
namespace: mytf1
labels:
app: search-api
hostname: search-api.svc.local
ports:
- 8080:8080
94
95. Environnement de développement local.
● Monday : Exécution
○ Sélection du projet à exécuter
Terminal
$ monday
Use the arrow keys to navigate: ↓ ↑ → ←
? Which project do you want to work on?:
CMS API
CMS Front
CMS GraphQL
MyTF1: Catalog GraphQL
▸ MyTF1: Catalog GraphQL + Services API
MyTF1: Catalog Indexer Job (Scope : Full)
MyTF1: Catalog Indexer Job (Scope : Programmes & Videos)
Platform: Reco API
Platform: Search API
95
96. Environnement de développement local.
● Monday : Exécution
○ Lancement d’un projet
Terminal
$ monday
✔ MyTF1: Catalog GraphQL + Services API
📡 Forwarding 'search-api' over kubernetes...
📡 Forwarding 'reco-api' over kubernetes...
📡 Forwarding 'reco-persona' over kubernetes...
📡 Forwarding 'user-favorites' over kubernetes...
📡 Forwarding 'user-history' over kubernetes...
⚙ Running local app ‘catalog-graphql-local’ (go.tf1.fr/mytf1/catalog-graphql)...
⚙ Running local app ‘catalog-api-local’ (go.tf1.fr/mytf1/catalog-api)...
...
96