Accueil
Le blog tech de Nicolas Steinmetz (Time Series, IoT, Web, Ops, Data)
Depuis que j'ai migré des sites sous kubernetes, j'avais perdu l'automatisation du déploiement de mes conteneurs. Pour ce site, je modifiais donc le site et une fois le git push
realisé, j'attendais que Gitlab-CI crée mon conteneur. Je récupérai alors le tag du conteneur que je mettais dans le dépôt git où je stocke mes fichiers de configuration pour kubernetes. Une fois le tag mis à jour, je pouvais procéder au déploiement de mon conteneur. Il était temps d'améliorer ce workflow.
Gitlab propose depuis un moment une intégration avec kubernetes mais je lui trouve quelques inconvénients au regard de mes besoins :
Mon besoin pourrait se résumer à pouvoir interagir avec mon cluster au travers de kubectl et de pouvoir y déployer la nouvelle version du conteneur que je viens de créer. Cela suppose alors d'avoir 3 choses :
kubeconfig
pour m'authentifier auprès du cluster et interagir avec,Utilisant le service managé d'OVH, je n'ai pas accès à tous les certificats du cluster permettant de créer de nouveaux comptes utilisateurs. Par ailleurs, pour les intégrations comme Gitlab, il est recommandé d'utiliser des Service Accounts. C'est ce que nous allons faire.
En plus du Service Account, il nous faut donner un rôle à notre compte pour qu'il puisse réaliser des actions sur le cluster. Par simplicité pour ce billet, je vais lui donner les droits d'admin au sein d'un namespace. Le compte de service pourra alors faire ce qu'il veut mais uniquement au sein du namespace en question. En cas de fuite du compte, les dégats potentiels sont donc moindres qu'avec un compter qui est admin global du cluster. Le rôle admin existe déjà sous kubernetes, il s'agit du ClusteRole admin mais qui est restreint à un namespace via le RoleBinding.
Créons le fichier gitlab-integtration.yml
avec ces éléments :
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: gitlab-example
namespace: example
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name: gitlab-admin
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: admin
subjects:
- kind: ServiceAccount
name: gitlab-example
namespace: example
Déployons notre configuration sur le cluster :
# Apply yml file on the cluster
kubectl apply -f gitlab-integration.yml
serviceaccount/gitlab-example created
rolebinding.rbac.authorization.k8s.io/gitlab-admin created
Pour alimenter notre fichier kubeconfig, il nous faut récupérer le token :
# Get secret's name from service account
SECRETNAME=`kubectl -n example get sa/gitlab-example -o jsonpath='{.secrets[0].name}'`
# Get token from secret, encoded in base64
TOKEN=`kubectl -n example get secret $SECRETNAME -o jsonpath='{.data.token}'`
# Decode token
CLEAR_TOKEN=`echo $TOKEN |base64 --decode`
En prenant votre fichier kubeconfig de référence, vous pouvez alors créer une copie sous le nom kubeconfig-gitlab-example.yml
et l'éditer de la façon suivante :
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: <Existing certificate in a base64 format from your original kubeconfig file>
server: <url of your k8s http endpoint like https://localhost:6443/ >
name: kubernetes # adjust your cluster name
contexts:
- context:
cluster: kubernetes # adjust your cluster name
namespace: example # adjust your namespace
user: gitlab-example # adujust your user
name: kubernetes-ovh # adujust your context
current-context: kubernetes-ovh # adujust your context
kind: Config
preferences: {}
users:
- name: gitlab-example # adujust your user
user:
token: <Content of the CLEAR_TOKEN variable>
Vous pouvez tester son bon fonctionnement via :
# Fetch example resources if any:
kubectl --kubeconfig=./kubeconfig-gitlab-example.yml get all
...
# Check you can't access other namespaces information, like kube-system:
kubectl --kubeconfig=./kubeconfig-gitlab-example.yml get all -n kube-system
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:example:gitlab-example" cannot list resource "pods" in API group "" in the namespace "kube-system"
Error from server (Forbidden): replicationcontrollers is forbidden: User "system:serviceaccount:example:gitlab-example" cannot list resource "replicationcontrollers" in API group "" in the namespace "kube-system"
Error from server (Forbidden): services is forbidden: User "system:serviceaccount:example:gitlab-example" cannot list resource "services" in API group "" in the namespace "kube-system"
Error from server (Forbidden): daemonsets.apps is forbidden: User "system:serviceaccount:example:gitlab-example" cannot list resource "daemonsets" in API group "apps" in the namespace "kube-system"
Error from server (Forbidden): deployments.apps is forbidden: User "system:serviceaccount:example:gitlab-example" cannot list resource "deployments" in API group "apps" in the namespace "kube-system"
Error from server (Forbidden): replicasets.apps is forbidden: User "system:serviceaccount:example:gitlab-example" cannot list resource "replicasets" in API group "apps" in the namespace "kube-system"
Error from server (Forbidden): statefulsets.apps is forbidden: User "system:serviceaccount:example:gitlab-example" cannot list resource "statefulsets" in API group "apps" in the namespace "kube-system"
Error from server (Forbidden): horizontalpodautoscalers.autoscaling is forbidden: User "system:serviceaccount:example:gitlab-example" cannot list resource "horizontalpodautoscalers" in API group "autoscaling" in the namespace "kube-system"
Error from server (Forbidden): jobs.batch is forbidden: User "system:serviceaccount:example:gitlab-example" cannot list resource "jobs" in API group "batch" in the namespace "kube-system"
Error from server (Forbidden): cronjobs.batch is forbidden: User "system:serviceaccount:example:gitlab-example" cannot list resource "cronjobs" in API group "batch" in the namespace "kube-system"
Gitlab permet de stocker des variables. Dans le cas d'un fichier kubeconfig, on va vouloir ne jamais afficher son contenu dans les logs ou autre. Pour cela il est possible de masquer vos variables en respectant quelques contraintes et notamment que la valeur de la variable tienne sur une seule ligne.
Nous allons donc encoder le fichier en base64 et rajouter un argument pour que tout soit sur une seule ligne (et non pas sur plusieurs lignes par défaut):
# create a one line base64 version of kubeconfig file
cat kubeconfig-gitlab-example.yml | base64 -w 0
Copier le contenu obtenu dans une variable que nous appelerons KUBECONFIG
et dont on cochera bien la case "Mask variable". Une fois la variable sauvée, vous avez ceci :
Soit le fichier .gitlab-ci.yml
suivant:
---
stages:
- publish
- image
- deploy
publish:
image: $CI_REGISTRY/nsteinmetz/hugo:latest
artifacts:
paths:
- public
expire_in: 1 day
only:
- master
- web
script:
- hugo
stage: publish
tags:
- go
docker:
stage: image
image: docker:stable
services:
- docker:dind
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_DRIVER: overlay2
RELEASE_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA-$CI_PIPELINE_ID-$CI_JOB_ID
before_script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
script:
- echo "IMAGE=${RELEASE_IMAGE}" >> docker.env
- docker build --pull -t $RELEASE_IMAGE .
- docker push $RELEASE_IMAGE
when: on_success
tags:
- go
artifacts:
reports:
dotenv: docker.env
kube:
stage: deploy
script:
- echo $KUBECONFIG | base64 --decode > kubeconfig
- export KUBECONFIG=`pwd`/kubeconfig
- sed -i -e "s|IMAGE|${IMAGE}|g" deployment.yml
- kubectl apply -f deployment.yml
needs:
- job: docker
artifacts: true
when: on_success
tags:
- shell
Petite explication rapide :
publish
va générer la version html du site et la stocker sous la forme d'un artefact qui sera passé aux jobs suivants,docker
va créer l'image en mettant l'artefact du job précédent dans un conteneur nginx et le publier dans la registry gitlab avec le nom suivant gitlab.registry/group/project:<short commit>-<pipeline id>-<job id>
kube
va récupérer le contenu de la variable KUBECONFIG
, le décoder et créer un fichier kubeconfig
. On initialise la variable d'environnement KUBECONFIG
pour que kubectl puisse l'utiliser. On met à jour la référence de l'image docker obtenue précédemment dans le fichier deployment.yml
qui sert de modèle de déploiement. On applique le fichier obtenu sur le cluster kubernetes pour mettre à jour le déploiement.Le point d'attention ici est que le passage de la variable RELEASE_IMAGE
se fait via un dotenv
qui est créé sous la forme d'un artefact à l'étape docker
et est donc disponible à l'étape kube
. Cela devrait être automatique mais j'ai ajouté une dépendance explicite via la directive needs
. Lors de l'étape kube
, le contenu du fichier docker.env
est disponible sous la forme de variables d'environnement. On peut alors faire la substitution de notre placeholder par la valeur voulue dans deployment.yml
.
Tout ce mécanisme est expliqué dnas la doc des variables gitlab et sur les variables d'environnement héritées. Attention, il vous faut Gitlab 13.0+ pour avoir cette fonctionnalité et en plus, il faut préalablement activer ce feature flag.
sudo gitlab-rails console
Feature.enable(:ci_dependency_variables)
En conclusion, nous avons vu comment :
kubeconfig
utilisant notre service account dans Gitlab sous la forme d'une variable masquée,Ainsi, toute mise à jour de master conduira à une mise à jour du déploiement associé au sein du cluster kubernetes et ne nécessitera plus d'interventions manuelles comme précédemment. Avec un service account lié à un namespace, on évite aussi de s'exposer inutilement en cas de fuite des identifiants.
Le mois prochain, dans le cadre d'InfluxDays London, j'aurai le plaisir de présenter un talk sur le passage d'un monitoring Bare Metal vers un monitoring dans un monde Kubernetes avec Telegraf et InfluxDB.
depends_on
, count
et for_each
. Cela devrait éviter des dépendances parfois cryptiques.docker-compose.yml
) pour la rendre plus "cloud native" et plus générique avec une extension au provider cloud d'une part et d'autre part à des solutions comme kubernetes ou Amazone ECS par ex.DEV*
soient surprovisionnés et qu'il faille envisager des profils GP*
pour avoir des performances correctes. L'offre est du coup moins compétitive en termes de prix pour des petits clusters.jQuery.htmlFilter
pour toutes les versions inférieures à 3.5.0 ; il est vivement encouragé de mettre à jour vos sites. Pour le reste, je vous renvoie à la lecture de l'article.Dans le cadre du déploiement d'applications stateful sur un cluster kubernetes, je me suis posé la question des solutions me permettant de déployer une instance PostgreSQL. Ce comparatif est succint et comporte surement un certain nombre d'approximations. C'est le résultat de quelques heures de veille et de tests sur le sujet (jusqu'à plusieurs semaines pour KubeDB).
URL | https://github.com/helm/charts/tree/master/stable/postgresql |
Mainteneur | Bitnami |
Version actuelle | 8.2.1 |
Version testée | 7.6 & 8.2.1 |
Version PG disponible | 9.6, 10.11, 11.6, 12.1 |
Version PG testée | 11.6 |
Replication | O |
Failover | N |
Backup | N |
Gestion Upgrade PG | O |
Metrics | Prometheus |
Le chart est basé sur des images custom Bitnami plutôt que sur les images officielles Postgresql. Il reste toutefois possible d'utiliser les images officielles. Ce choix d'image custom se justifie par la fonctionnalité de réplication et d'avoir des images non root. Il faudra partir sur une version Debian (10.0 pour la version 8+ du chart), CentOS 7.0 ou Oracle Linux 7.
Le chart offre d'autres fonctionnalités (authentification ldap, personnalisation de pg_hba.conf, etc) et s'avère assez riche. Il peut donc a priori gérer des cas basiques à plus avancés.
Il existe un chart pour avoir une version Postgresql High Availability (non testé).
URL | https://github.com/sorintlab/stolon |
Mainteneur | Sorint OSS |
Version actuelle | 0.15.0 |
Version testée | - |
Version PG disponible | 9.4+, 10, 11, 12 |
Version PG testée | - |
Replication | O |
Failover | O |
Backup | N |
Gestion Upgrade PG | ? |
Metrics | ? |
La solution s'appuie par défaut sur les images officielles Postgresql mais il est possible d'utiliser ses propres images. Si la solution semble intéressante, je l'ai trouvé complexe, même si cela se justifie. Le fait d'avoir de multiples composants (keeper pour les instances Postgresql, des proxy pour la gestion de la connexion à la base de données et enfin des sentinels qui surveillent le tout) m'a un peu rebuté, tout comme le fait d'avoir un binaire de plus à utiliser. La documentation est assez rudimentaire également pour bien apprécier le produit.
URL | https://kubedb.com/ |
Mainteneur | AppsCode |
Version actuelle | 0.13.0-rc0 |
Version testée | 0.13.0-rc0 |
Version PG disponible | 9.5/9.6, 10.2/10.6, 11.1 |
Version PG testée | 11.1 |
Replication | O |
Failover | ? |
Backup | O |
Gestion Upgrade PG | ? |
Metrics | Prometheus |
Le produit est prometteur mais manque encore de stabilité : il se base sur un operateur, il est édité par une société assez implémentée dans l'écosystème kubernetes et il permet de gérer plusieurs bases de données, dont Postgresql.
L'initialisation est assez simple et le produit semble bien pensé et offre l'ensemble des fonctionnalités que l'on peut attendre d'un operator pour gérer une base Postgresql (initialisation, réplication, sauvegarde, monitoring, etc)
Pour les backups, le produit s'appuie sur stash pour faire des backups dans des espace de stockages distants (S3, Swift, etc). Pour Restic et Swift, il faut un conteneur de type object storage 'normal'. En voulant utiliser le stockage Cloud Archive d'OVH, l'intégration ne fonctionnait pas bien.
Je ne l'ai pas retenu notant des restart des pods à répétition en lisaison avec le mécanisme d'élection de leaders qui n'aboutissait pas. J'espère que les prochaines versions vont me permettre de tester à nouveau le produit.
URL | https://access.crunchydata.com/documentation/postgres-operator/4.1.0/ |
Mainteneur | CruncyData |
Version actuelle | 4.1 |
Version testée | - |
Version PG disponible | 9.5/9.6, 10.10, 11.5 |
Version PG testée | - |
Replication | O |
Failover | O |
Backup | O |
Gestion Upgrade PG | ? |
Metrics | Prometheus |
Déjà, voir que l'installation se fait via Ansible ou via des commandes bash et qu'il faut un binaire spécifique pour interagir avec la plateforme, je coince un peu. La solution semble aussi très riche mais complexe à prendre en main. Venant de KubeDB, j'avoue avoir passé rapidement mon chemin.
URL | https://github.com/zalando/postgres-operator |
Mainteneur | Zalando |
Version actuelle | 1.3.1 |
Version testée | - |
Version PG disponible | 9.6, 10, 11 |
Version PG testée | - |
Replication | O |
Failover | ? |
Backup | O |
Gestion Upgrade PG | ? |
Metrics | Prometheus |
Zalando a rendu public son operator kubernetes. Il s'appuie sur leur solution patroni pour créer un cluster haute disponibilité. Sortant de mon test KubeDB, j'ai trouvé leur modèle trop complexe et avec des fonctionnalités dont on a a priori pas besoin (les Teams ?). J'ai du coup moins l'impression de manipuler une base de données Postgres classique.
EntrepriseDB, un acteur majeur de l'écosystème Postgres, a publié en septembre dernier son operator : EDB Postgres on Kubernetes. Il ne semble pas open source et l'accès aux conteneurs demande une authentification. Je ne suis donc pas allé plus loin.
Alors que mes besoins sont très simples (hébergement d'instances NextCloud pour quelques utilisateurs à chaque fois) et que j'utilisais pour le moment des instances Postgresql sur un seul serveur dans des conteneurs Docker (avec la gestion des backups via un container dédié), j'avoue être resté un peu sur ma fin. J'avais fondé beaucoup d'espoirs sur KubeDB mais qui tardent à se réaliser. En attendant, je suis repassé sur le chart helm qui fonctionne bien. Il faut juste prévoir un job annexe pour les backups.
Certains pourront me dire qu'il est encore trop tôt pour faire du statefull sur kubernetes ou bien qu'il faut utiliser des base de données "cloud native". Pour le premier point, c'est aussi avec ces petits instances non critiques que l'on peut se faire la main sur le sujet et après tout, je fais ça depuis des années avec des containers Docker sans soucis. Pour le second point, faut-il encore que ces bases existent et que les outils associés les utilisent...
Rendez-vous le 21 janvier prochain à la troisième édition du Paris Time Series Meetup consacré à TSL (billet introductif à TSL : TSL: a developer-friendly Time Series query language for all our metrics) et le module RedisTimeSeries qui apporte des fonctionnalités et des structures Time Seriies à Redis. Le meetup était prévu initialement le mardi 17 décembre mais a été reporté du fait des grèves.
Je n'ai plus qu'à vous souhaiter des bonnes fêtes de fin d'année ; nous nous retrouvons l'année prochaine !
Traefik, depuis sa version V1, permet d'envoyer des métriques vers différents backends (StatsD, Prometheus, InfluxDB et Datadog). J'ai enfin pris le temps d'activer cette fonctionnalité et de creuser un peu le sujet étant donné que le dashboard de Traefik V2 n'affiche plus certaines de ses statistiques.
La documentation de Traefik sur le sujet :
Commençons par créer une base traefik
dans InfluxDB (version 1.7.8)
influx
Connected to http://localhost:8086 version 1.7.8
InfluxDB shell version: 1.7.9
> auth
username: XXX
password: XXX
> CREATE DATABASE traefik
> CREATE USER traefik WITH PASSWORD '<password>'
> GRANT ALL ON traefik to traefik
> SHOW GRANTS FOR traefik
database privilege
-------- ---------
traefik ALL PRIVILEGES
> quit
Dans mon cas, l'accès à InfluxDB se fait en https au travers d'une (autre) instance Traefik. J'utilise donc la connexion en http
plutôt qu'en udp
.
Cela donne les instructions suivantes en mode CLI :
--metrics=true
--metrics.influxdb=true
--metrics.influxdb.address=https://influxdb.domain.tld:443
--metrics.influxdb.protocol=http
--metrics.influxdb.database=traefik
--metrics.influxdb.username=traefik
--metrics.influxdb.password=<password>
J'ai gardé les valeurs par défaut pour addEntryPointsLabels
(true), addServicesLabels
(true) et pushInterval
(10s).
Cela donne le Deployment
suivant :
apiVersion: apps/v1
kind: Deployment
metadata:
name: traefik2-ingress-controller
labels:
k8s-app: traefik2-ingress-lb
spec:
replicas: 2
selector:
matchLabels:
k8s-app: traefik2-ingress-lb
template:
metadata:
labels:
k8s-app: traefik2-ingress-lb
name: traefik2-ingress-lb
spec:
serviceAccountName: traefik2-ingress-controller
terminationGracePeriodSeconds: 60
containers:
- image: traefik:2.0.6
name: traefik2-ingress-lb
ports:
- name: web
containerPort: 80
- name: admin
containerPort: 8080
- name: secure
containerPort: 443
readinessProbe:
httpGet:
path: /ping
port: admin
failureThreshold: 1
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 2
livenessProbe:
httpGet:
path: /ping
port: admin
failureThreshold: 3
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 2
args:
- --entryPoints.web.address=:80
- --entryPoints.secure.address=:443
- --entryPoints.traefik.address=:8080
- --api.dashboard=true
- --api.insecure=true
- --ping=true
- --providers.kubernetescrd
- --providers.kubernetesingress
- --log.level=ERROR
- --metrics=true
- --metrics.influxdb=true
- --metrics.influxdb.address=https://influxdb.domain.tld:443
- --metrics.influxdb.protocol=http
- --metrics.influxdb.database=traefik
- --metrics.influxdb.username=traefik
- --metrics.influxdb.password=<password>
Appliquer le contenu du fichier dans votre cluster Kubernetes
kubectl apply -f deployment.yml -n <namespace>
Sur le dashboard Traefik, dans la section "Features", la boite "Metrics" doit afficher "InfluxDB", comme ci-dessous :
Vous pouvez alors vous connecter à votre instance InfluxDB pour valider que des données sont bien insérées :
influx
Connected to http://localhost:8086 version 1.7.8
InfluxDB shell version: 1.7.9
> auth
username: traefik
password:
> use traefik
Using database traefik
> show measurements
name: measurements
name
----
traefik.config.reload.lastSuccessTimestamp
traefik.config.reload.total
traefik.entrypoint.connections.open
traefik.entrypoint.request.duration
traefik.entrypoint.requests.total
traefik.service.connections.open
traefik.service.request.duration
traefik.service.requests.total
Il ne vous reste plus qu'à utiliser Chronograf ou Grafana pour visualiser vos données et définir des alertes.
Un exemple rapide avec la répartition des codes HTTP dans Grafana :
Rendez-vous le 17 décembre prochain à la troisième édition du Paris Time Series Meetup consacré à TSL (billet introductif à TSL : TSL: a developer-friendly Time Series query language for all our metrics) et le module RedisTimeSeries qui apporte des fonctionnalités et des structures Time Seriies à Redis.
matrix
qui va permettre de faire la même action avec des configurations différentes plutôt que d'avoir un jenkinsfile pour chaque option/déclinaison du job. Le parallelisme semble supporté par défaut et un système d'inclusion/exclusion permet de mieux définir la combinaison des possibles. Dans l'exemple donné qui croise des systèmes d'exploitation et des navigateurs, cela permet par ex de ne pas lancer le job utilisant Micrsoft Edge sous Linux (même si...).Avec la sortie de Traefik 2, il était temps de mettre à jour le billet Kubernetes @ OVH - Traefik et Cert Manager pour le stockage des certificats en secrets pour tenir compte des modifications.
L'objectif est toujours de s'appuyer sur Cert-Manager pour la génération et le stockage des certificats Let's Encrypt qui seront utilisés par Traefik. L'idée est de stocker ces certificats sous la forme d'un objet Certificate
et de ne plus avoir à provisionner un volume pour les stocker. On peut dès lors avoir plusieurs instances de Traefik et non plus une seule à laquelle le volume serait attaché.
Installation de cert-manager :
# Install the CustomResourceDefinition resources separately
kubectl apply --validate=false -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.11/deploy/manifests/00-crds.yaml
# Create the namespace for cert-manager
kubectl create namespace cert-manager
# Add the Jetstack Helm repository
helm repo add jetstack https://charts.jetstack.io
# Update your local Helm chart repository cache
helm repo update
# Install the cert-manager Helm chart
helm install \
--name cert-manager \
--namespace cert-manager \
--version v0.11.0 \
jetstack/cert-manager
Nous allons ensuite devoir créer un Issuer dans chaque namespace pour avoir un générateur de certificats propre à chaque namespace. Cela est notamment du au fait que Traefik s'attend à ce que le secret et l'ingress utilisant ce secret soient dans le même namespace. Nous spécifions également que nous utiliserons traefik comme ingress pour la génération des certificats.
cert-manager/issuer.yml
:
apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
name: letsencrypt-prod
spec:
acme:
# The ACME server URL
server: https://acme-v02.api.letsencrypt.org/directory
# Email address used for ACME registration
email: user@example.com
# Name of a secret used to store the ACME account private key
privateKeySecretRef:
name: letsencrypt-prod
# Enable HTTP01 validations
solvers:
- selector: {}
http01:
ingress:
class: traefik
Puis créons le "issuer" dans la/les namespace(s) voulu(s) :
# Create issuer in a given namespace
kubectl create -n <namespace> -f cert-manager/issuer.yml
Installons ensuite traefik V2
Créons le namespace traefik2 :
# Create namespace
kubectl create ns traefik2
# Change context to this namespace so that all commands are by default run for this namespace
# see https://github.com/ahmetb/kubectx
kubens traefik2
En premier lieu, Traefik V2 permet d'avoir un provider Kubernetes qui se base sur des Custom Ressources Definition
(aka CRD
).
Créeons le fichier traefik2/crd.yml
:
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: ingressroutes.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: IngressRoute
plural: ingressroutes
singular: ingressroute
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: middlewares.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: Middleware
plural: middlewares
singular: middleware
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: ingressroutetcps.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: IngressRouteTCP
plural: ingressroutetcps
singular: ingressroutetcp
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: ingressrouteudps.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: IngressRouteUDP
plural: ingressrouteudps
singular: ingressrouteudp
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: tlsoptions.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: TLSOption
plural: tlsoptions
singular: tlsoption
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: tlsstores.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: TLSStore
plural: tlsstores
singular: tlsstore
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: traefikservices.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: TraefikService
plural: traefikservices
singular: traefikservice
scope: Namespaced
Vous pouvez retrouver les sources de ces CRD.
Continuons avec traefik2/rbac.yml
- le fichier défini le compte de service (Service Account
), le rôle au niveau du cluster (Cluster Role
) et la liaison entre le rôle et le compte de service (Cluster Role Binding
). Si vous venez d'une installation avec Traefik 1, ce n'est pas tout à fait la même définition des permissions.
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: traefik2-ingress-controller
namespace: traefik2
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: traefik2-ingress-controller
namespace: traefik2
rules:
- apiGroups:
- ""
resources:
- services
- endpoints
- secrets
verbs:
- get
- list
- watch
- apiGroups:
- extensions
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- extensions
resources:
- ingresses/status
verbs:
- update
- apiGroups:
- traefik.containo.us
resources:
- middlewares
- ingressroutes
- traefikservices
- ingressroutetcps
- ingressrouteudps
- tlsoptions
- tlsstores
verbs:
- get
- list
- watch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: traefik2-ingress-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: traefik2-ingress-controller
subjects:
- kind: ServiceAccount
name: traefik2-ingress-controller
namespace: traefik2
Nous pouvons alors songer à déployer Traefik V2 sous la forme d'un Deployment
. Mais avant de produire le fichier, ce qu'il faut savoir ici :
Ingress
. Dès lors, il faut activer les deux providers kubernetes disponibles avec Traefik V2 : KubernetesCRD
et KubernetesIngress
. Le premier provider permettra de profiter des nouveaux objets fournis par la CRD et le second permet que Traefik gère les Ingress traditionnelles de Kubernetes et notamment celles de cert-manager.KubernetesIngress
ne supporte pas les annotationsKubernetesIngress
, on se simplifie aussi la migration d'un socle Traefik V1 vers V2, au support des annotations près.traefik2/deployment.yml
:
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: traefik2-ingress-controller
labels:
k8s-app: traefik2-ingress-lb
spec:
replicas: 2
selector:
matchLabels:
k8s-app: traefik2-ingress-lb
template:
metadata:
labels:
k8s-app: traefik2-ingress-lb
name: traefik2-ingress-lb
spec:
serviceAccountName: traefik2-ingress-controller
terminationGracePeriodSeconds: 60
containers:
- image: traefik:2.1.1
name: traefik2-ingress-lb
ports:
- name: web
containerPort: 80
- name: admin
containerPort: 8080
- name: secure
containerPort: 443
readinessProbe:
httpGet:
path: /ping
port: admin
failureThreshold: 1
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 2
livenessProbe:
httpGet:
path: /ping
port: admin
failureThreshold: 3
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 2
args:
- --entryPoints.web.address=:80
- --entryPoints.secure.address=:443
- --entryPoints.traefik.address=:8080
- --api.dashboard=true
- --api.insecure=true
- --ping=true
- --providers.kubernetescrd
- --providers.kubernetesingress
- --log.level=DEBUG
Pour permettre au cluster d'accéder aux différents ports, il faut définir un service via le fichier traefik2/service.yml
:
---
kind: Service
apiVersion: v1
metadata:
name: traefik2-ingress-service-clusterip
spec:
selector:
k8s-app: traefik2-ingress-lb
ports:
- protocol: TCP
port: 80
name: web
- protocol: TCP
port: 8080
name: admin
- protocol: TCP
port: 443
name: secure
type: ClusterIP
Et pour avoir un accès de l'extérieur, il faut instancier un load-balancer via le fichier traefik/traefik-service-loadbalancer.yml
---
kind: Service
apiVersion: v1
metadata:
name: traefik-ingress-service-lb
spec:
selector:
k8s-app: traefik2-ingress-lb
ports:
- protocol: TCP
port: 80
name: web
- protocol: TCP
port: 443
name: secure
type: LoadBalancer
Pour donner l'accès au dashboard via une url sécurisée par un certificat Let's Encrypt, il faut déclarer un Ingress, dans le fichier traefik2/api-ingress.yml
:
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: traefik2-web-ui
spec:
entryPoints:
- secure
routes:
- match: Host(`traefik2.k8s.cerenit.fr`)
kind: Rule
services:
- name: traefik2-ingress-service-clusterip
port: 8080
tls:
secretName: traefik2-cert
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: traefik2-web-ui-http
spec:
entryPoints:
- web
routes:
- match: Host(`traefik2.k8s.cerenit.fr`)
kind: Rule
services:
- name: traefik2-ingress-service-clusterip
port: 8080
L'idée est donc de rentre le dashboard accessible via l'url traefik2.k8s.cerenit.fr.
La section tls
de l'ingress indique le nom d'hôte pour lequel le certificat va être disponible et le nom du secret contenant le certificat du site que nous n'avons pas encore créé.
Il nous faut donc créer ce certificat :
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
name: traefik2-cert
namespace: traefik2
spec:
secretName: traefik2-cert
issuerRef:
name: letsencrypt-prod
commonName: traefik2.k8s.cerenit.fr
dnsNames:
- traefik2.k8s.cerenit.fr
Il ne reste plus qu'à faire pour instancier le tout :
kubectl apply -f traefik2/
Pour la génération du certificat, il conviendra de vérifier la sortie de
kubectl describe certificate traefik2-cert
A ce stade, il nous manque :
C'est là que les Middlewares rentrent en jeu.
Pour la redirection https: traefik2/middleware-redirect-https.yml
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: https-only
spec:
redirectScheme:
scheme: https
Pour l'authentification : traefik2/middleware-auth.yml
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: auth-traefik-webui
spec:
basicAuth:
secret: traefik-auth
Il faut alors créer un secret kubernetes qui contient une variable users
contenant la/les ligne(s) d'authentification :
apiVersion: v1
kind: Secret
metadata:
name: traefik-auth
namespace: traefik2
data:
users: |2
dGVzdDokYXByMSRINnVza2trVyRJZ1hMUDZld1RyU3VCa1RycUU4d2ovCnRlc3QyOiRhcHIxJGQ5aHI5SEJCJDRIeHdnVWlyM0hQNEVzZ2dQL1FObzAK
Cela correspond à 2 comptes test/test
et test2/test2
, encodés en base64 et avec un mot de passe chiffré via htpasswd.
test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/
test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0
On peut alors mettre à jour notre fichier traefik2/api-ingress.yml
et rajouter les deux middlewares que nous venons de définir :
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: traefik2-web-ui
spec:
entryPoints:
- secure
routes:
- match: Host(`traefik2.k8s.cerenit.fr`)
middlewares:
- name: auth-traefik-webui
kind: Rule
services:
- name: traefik2-ingress-service-clusterip
port: 8080
tls:
secretName: traefik2-cert
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: traefik2-web-ui-http
spec:
entryPoints:
- web
routes:
- match: Host(`traefik2.k8s.cerenit.fr`)
middlewares:
- name: https-only
kind: Rule
services:
- name: traefik2-ingress-service-clusterip
port: 8080
Et pour le prendre en compte:
kubectl apply -f traefik2/
Vous devez alors avoir une redirection automatique vers le endpoint en https et une mire d'authentification.
Pour ceux qui font une migration dans le même cluster de Traefik V1 vers Traefik V2 :
ingressClass
de Traefik et créer un issuer cert-manager qui utilise cette même ingressClass
.Certificate
Sources utiles:
Rendez-vous le 5 Novembre prochain à la seconde édition du Paris Time Series Meetup consacré à QuasarDB pour des cas d'usages autour de la finance et des transports.
--output <table|json|yaml>
à certaines commandes. Pratique quand on manipule les sorties de Helm dans des scripts...