TLDR
nsinjector est un controller Kubernetes qui permet d’injecter des ressources dans un namespace lors de sa création.

Intro

J’ai eu récemment l’occasion de travailler sur l’élaboration d’un pipeline CI/CD basé sur la méthodologie GitOps. Dans ce pipeline, le code poussé par les développeurs déclenche la création de namespaces pour chacune des branches de développement. Plusieurs équipes partageant le même cluster Kubernetes, elles ont besoin d’appliquer des ressources RBAC spécifiques selon le namespace créé. Si le namespace est créé par l’équipe front, l’équipe back ne doit pas y avoir accès. J’ai alors profité de ce besoin pour en apprendre plus sur le pattern contrôleur de Kubernetes, afin d’automatiser le déploiement de ces ressources RBAC en fonction du nom du namespace créé. nsinjector est le résultat de mon apprentissage.

Dans cet article, j’explique comment utiliser nsinjector pour déployer automatiquement des ressources Kubernetes (en l’occurrence un ClusterRole et un ClusterRoleBinding) lors de la création d’un namespace.

Prérequis

Kubernetes

Nous allons d’abord avoir besoin d’un cluster Kubernetes pour nos tests. J’imagine que si vous êtes là c’est que vous avez accès à un cluster d’une manière ou d’une autre, mais si vous le souhaitez, vous pouvez en créer un localement très rapidement avec minikube. Et si vous êtes sur MacOS c’est encore plus simple puisque Docker Desktop embarque un cluster Kubernetes local.

kubectl

Afin d’interagir avec le cluster, nous allons utiliser l’outil en ligne de commande kubectl. Suivez la documentation officielle pour l’installer.

Helm (optionnel)

Dans cet article, je présente l’installation de nsinjector par kubectl, mais si vous préférez Helm, je propose également un Chart ici.

Installer nsinjector

Custom Resource Definition (CRD)

Je n’entrerai pas dans les détails ici, mais un controller se base généralement sur la définition de ressources personnalisées (CRD) qui permettent d’étendre les fonctionnalités de Kubernetes au-delà des objets natifs (Deployment, Service, Job, etc.). nsinjector utilise donc le CRD NamespaceResourcesInjector afin de définir les conditions de déploiement de ressources lors de la création d’un namespace.

Pour installer ce CRD, exécutez la commande suivante :

> kubectl apply -f https://raw.githubusercontent.com/blakelead/nsinjector/master/deploy/k8s/crd/namespaceresourcesinjector-crd-1.16.yaml

Controller

La seconde étape consiste à déployer le controller. Le controller ayant besoin d’interagir avec un grand nombre de ressources, nous appliquons d’abord les objets RBAC suivants :

# rbac.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: nsinjector-controller

---

apiVersion: v1
kind: ServiceAccount
metadata:
  name: nsinjector-controller
  namespace: nsinjector-controller

---

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: nsinjector-controller
rules:
- apiGroups: [""]
  resources: ["namespaces"]
  verbs: ["list", "get", "watch"]
- apiGroups: ["blakelead.com"]
  resources: ["namespaceresourcesinjectors"]
  verbs: ["list", "get", "watch", "update"]
- apiGroups: ["rbac"]
  resources: ["*"]
  verbs: ["list", "get", "watch", "update"]

---

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: nsinjector-controller
subjects:
- kind: ServiceAccount
  name: nsinjector-controller
  namespace: nsinjector-controller
roleRef:
  kind: ClusterRole
  name: nsinjector-controller
  apiGroup: rbac.authorization.k8s.io

Appliquez les objets :

> kubectl apply -f rbac.yaml

Nous pouvons ensuite déployer le controller en lui-même :

# nsinjector-controller.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nsinjector-controller
  namespace: nsinjector-controller
  labels:
    app: nsinjector-controller
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nsinjector-controller
  template:
    metadata:
      labels:
        app: nsinjector-controller
    spec:
      serviceAccountName: nsinjector-controller
      containers:
      - name: nsinjector-controller
        image: blakelead/nsinjector-controller:v0.1.0

Appliquez l’objet :

> kubectl apply -f nsinjector-controller.yaml

NamespaceResourcesInjector

Le controller est responsable de surveiller la création des namespaces, et d’appliquer les objets NamespaceResourcesInjector s’ils existent. Voici un exemple de NamespaceResourcesInjector que l’on peut appliquer :

# rbac-dev-inject.yaml

kind: NamespaceResourcesInjector
apiVersion: blakelead.com/v1alpha1
metadata:
  name: nri-test
spec:
  namespaces:
  - dev-.*
  resources:
  - |
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: dev-role
    rules:
      - apiGroups: [""]
        resources: ["pods","pods/portforward", "services", "deployments", "ingresses"]
        verbs: ["list", "get"]
      - apiGroups: [""]
        resources: ["pods/portforward"]
        verbs: ["create"]
      - apiGroups: [""]
        resources: ["namespaces"]
        verbs: ["list", "get"]
  - |
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: dev-rolebinding
    subjects:
    - kind: User
      name: dev
    roleRef:
      kind: Role
      name: dev-role
      apiGroup: rbac.authorization.k8s.io

Appliquez l’objet :

> kubectl apply -f rbac-dev-inject.yaml

Cela va instruire le controller de déployer les objets dev-role et dev-rolebinding lorsqu’un namespace dont le nom correspond au regex dev-.* est créé.

Créer un namespace

Si vous créez un namespace qui respecte la règle regex du NamespaceResourcesInjector ci-dessus, vous pourrez constater que les ressources RBAC ont bien été déployées :

> kubectl create namespace dev-namespace-1
namespace "dev-namespace-1" created

> kubectl get clusterroles
NAME                        CREATED AT
dev-role                    2020-10-12T16:50:53Z
...

> kubectl get clusterrolebindings
NAME                ROLE                             AGE
dev-rolebinding     ClusterRole/dev-role             1m
...

Conclusion

Premier article de ce blog, je l’ai souhaité court et clair et j’espère qu’il l’a été pour vous. Mais si ce n’est pas le cas, n’hésitez pas à me le dire en commentaire. J’aimerais également écrire un article dans la suite celui-ci, expliquant comment j’ai écrit ce controller. Il pourrait faire office de tuto ‘Développer un controller Kubernetes’. Si c’est quelque chose qui vous intéresse, dites-le-moi et je l’écrirai avec plaisir.