0. Introduction
- Did it on a macbook air as I am away from my Linux workstation at home
- I will use some of my selfhosted services in examples, e.g.
registry.bouvais.lufor docker registry andgit.bouvais.lufor gitea.
1. Create env
First let's install everything that I will use using brew.
% brew install docker
% brew install kind kubectl helm argocd k9s
2. Images
To created the needed dependency, I did a simple 2 images base + jupyter. The first image is a minimal python slim. I then add some
Base is a simple python:3.12-slim-bookworm + tini using a non-root user.
Jupyter simply install python dependencies and start the jupyterlab server.
I then build and push them to my registry
% docker build -t registry.bouvais.lu/tenant-base:1.0.0 images/base
% docker push registry.bouvais.lu/tenant-base:1.0.0
% docker build -t registry.bouvais.lu/tenant-jupyter:1.0.0 images/jupyter
% docker push registry.bouvais.lu/tenant-jupyter:1.0.0
3. Cluster setup
In this section, will create the cluster, create namespaces and shared minio + argo cd namespace.
3.1 Cluster
Now for the actual deployment, I will make a simple local kubernetes cluser with kind.
% kind create cluster --name ctie-exercice
% kubectl cluster-info
% kubectl apply --server-side -k "https://github.com/argoproj/argo-cd/manifests/crds?ref=stable"
% kubectl apply -f https://github.com/emberstack/kubernetes-reflector/releases/latest/download/reflector.yaml
3.2 Minio
I deploy a unique shared minio instance in a namespace named minio. Obviously it can have its own scaling stategy later if needed but that's out of scope here.
Create minio/manifests.yaml and deploying it:
% kubectl create ns minio
% kubectl apply -f minio/manifests.yaml
% kubectl -n minio get pods
NAME READY STATUS RESTARTS AGE
minio-688ffcdbbd-qm47b 1/1 Running 0 3m6s
Can check the connection by forwarding port and goinf to localhost:9001
% kubectl -n minio port-forward svc/minio 9000:9000 9001:9001
3.3 Argo CD
Similar to minio, a single instance in a unique namespace. First lets fetch and run an Argo CD insance.
% kubectl create ns argocd
% kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
Similarly, can get admin password + forward to go to Argo CD webui. Obviously getting and using admin credentials is not good, but onece again that's out of scope.
% kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d; echo
% kubectl -n argocd port-forward svc/argocd-server 8080:443
4. GitOps
Now let's create Argo CD application set automated with a path in the repo.
First let's add files in gitops/ and run:
% kubectl apply -f gitops/bootstrap/root-app.yaml
At localhost:8080, we can see the root application.
Now when we add any directory in format tenant-* with a config.yaml file,
it will automatically create a namespace and a SA, deploy a jupyterlab server, add a new bucket to main monio.
[root-app.yaml] (Applied manually)
│
├──► Creates AppProject ("tenants")
└──► Creates ApplicationSet ("tenants")
│
├──► Scans Git for tenants/*/config.yaml
│
└──► Generates Application: tenant-a
│
├──► Pulls Helm Chart from: charts/tenant
├──► Applies Values from: tenants/tenant-a/config.yaml
└──► Create and deploy to Namespace: tenant-a
3.4 Secrets
Now that we have working automated tenants, they need secrets.
I will also use the addon reflector to automatically add secrets to tenants.
Docker Registry
First let's add docker registry credentials so it can pull the built jupyter image.
kubectl create secret docker-registry registry-credentials \
--docker-server=registry.bouvais.lu \
--docker-username="" \
--docker-password=""
--namespace=argocd
And to automatically make them available to tenant namespaces using reflector.
It will propagate registry-credentials secret to namespace in format tenant-something.
kubectl annotate secret registry-credentials -n argocd --overwrite \
reflector.v1.k8s.emberstack.com/reflection-allowed="true" \
reflector.v1.k8s.emberstack.com/reflection-auto-enabled="true" \
reflector.v1.k8s.emberstack.com/reflection-auto-namespaces="tenant-[a-z0-9-]+" \
reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces="tenant-[a-z0-9-]+"
From here we can access the notebook with:
kubectl port-forward -n tenant-a deployment/jupyter 8888:8888
For this demo, I will stop here. Meaning accessing the notebook using a manual port forwarding. But in reality, this would need a route, automated forward, CA, ect. But that's out of scope again.
Minio Admin
Let's add Minio Admin credentials as a secret too. Similarly propagating them to tenant namespaces.
kubectl create secret generic minio-admin-credentials \
--from-literal=MINIO_ROOT_USER=something \
--from-literal=MINIO_ROOT_PASSWORD=something \
-n argocd
kubectl annotate secret minio-admin-credentials -n argocd --overwrite \
reflector.v1.k8s.emberstack.com/reflection-allowed="true" \
reflector.v1.k8s.emberstack.com/reflection-auto-enabled="true" \
reflector.v1.k8s.emberstack.com/reflection-auto-namespaces="tenant-[a-z0-9-]+" \
reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces="tenant-[a-z0-9-]+"
Minio User
The tenant chart also automatically create the 2 new buckets and a random credential saved only in the tenant namespace that will use it.
% kubectl get events -n tenant-a --field-selector reason=Completed
LAST SEEN TYPE REASON OBJECT MESSAGE
7m6s Normal Completed job/tenant-a-minio-setup Job completed
% kubectl get secrets -n tenant-a
NAME TYPE DATA AGE
s3-credentials Opaque 5 90s
% kubectl get secret s3-credentials -n tenant-a -o jsonpath='{.data.AWS_SECRET_ACCESS_KEY}' | base64 --decode
echo ""
xXjvsoRXaEI1GtvVfUrMZOkR
Use Jupyter
Now we should have everything to use the notebook.
Let's go to localhost:8888 and run images/jupyter/test_script.py.
SUCCESS: List items in ref bucket: 1 hello tenant-a
SUCCESS: Write to reference bucket blocked: An error occurred (AccessDenied) when calling the PutObject operation: Access Denied.
SUCCESS: Read back from work bucket: hello tenant
SUCCESS: Access to tenant-b blocked: An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied.
We are succesfully:
- Reading but not writing from bucket ref.
- Read and write in work bucket
- Cant read nor write in bucket tenant-b
Docker build
Now let's automate docker images. I will go with a simple Action. Gitea has Action like Github.
It is trigger only on tag *.*.*. It then build the base and then jupyter image and push it to the registry
using the tag. Jupyter image use the just previously build base.
TODO: une NetworkPolicy limite les communications du notebook aux services nécessaires ; Une pipeline doit : • valider les fichiers de configuration ; • construire les images dans le bon ordre ; • exécuter au moins un test ; • publier les images dans un registre ou simuler clairement cette étape ; • mettre à jour la version utilisée par le déploiement.
Configs
I kept configs minimal, but in real here a list of things that could be added:
- A storage limit for Work bucket
- A GPU option for the Jupyter