# IDVOM TP 2025
## Changelog
### 2025-10-27
- Fixed some bugs in catalog and orders, added some README
### 2025-10-29
- Added some information about the probes
## Prereqs
The given files can be found here: https://github.com/fayak/IDVOM
- Docker
- Helm
- k3s
### Make sure your k3s is running
```
$ sudo k3s kubectl get node
NAME STATUS ROLES AGE VERSION
titanium Ready control-plane,master 11d v1.33.4+k3s1
```
note: You may have to restart k3s service on reboot of your laptop/vm
note: You may have to add `--disable-network-policy` to k3s CLI arguments (ie `curl -sfL https://get.k3s.io | sh -s - --disable-network-policy`)
## Submission
### Architecture
```
├── catalog
│ ├── app
│ │ ├── db.py
│ │ ├── main.py
│ │ ├── models.py
│ │ └── schemas.py
│ ├── Dockerfile
│ └── pyproject.toml
├── gateway
│ ├── Dockerfile
│ ├── gateway
│ ├── go.mod
│ ├── go.sum
│ └── main.go
├── orders
│ ├── app
│ │ ├── db.py
│ │ ├── main.py
│ │ ├── models.py
│ │ └── schemas.py
│ ├── Dockerfile
│ └── pyproject.toml
└── helm
├── ...
└── templates
├── catalog
│ └── ...
├── gateway
│ └── ...
├── orders
│ └── ...
└── ...
```
### How to submit ?
The submission will be done by email, to `idvom@zarak.fr`.
The deadline is set to the 11th of January, 23h59.
## First step
Build all images and publish them on docker hub for them to be available in the future steps
Some prebuilt images exist on:
- `zarakprod/catalog`
- `zarakprod/gateway`
- `zarakprod/orders`
*These images were built for AMD64 architecture, and will not work on ARM. Sorry MAC users*
## Second step - The gateway
Create some manifests in the `helm/templates/gateway` directory to deploy the gateway
You'll have to:
- Write a `Deployment` in `deployment.yaml`
- Write a `Service` in `service.yaml` to expose the component internally (ClusterIP)
- Write a `ConfigMap` in `configmap.yaml` to put the configuration's env var to be used by the `Deployment` [if needed]
- Write a `Secret` in `secret.yaml` to put the configurations's secrets env var to be used by the `Deployment` [if needed]
Have a look at the gateway's README for more information on the env variables. You must create some admin user to test your application.
You may find this useful to help you decide on the labels to define : https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/
The Gateway must be exposed to the cluster on the port 8080 while the application listens on port 8000. Use the Service to map the ports.
The Gateway exposes a `/health` endpoint. Use it to define a LivenessProbe.
Confirm that everything works as expected:
```
$ sudo k3s kubectl get pod
NAME READY STATUS RESTARTS AGE
gateway-5994884b4-dvgxg 1/1 Running 0 7m13s
```
Get the Service's IP+port and make sure it behaves as expected:
```
$ curl $gateway/health
ok
$ curl $gateway/whoami -u zarak
Enter host password for user 'zarak': *******
zarak
```
_Note about the Secret: It is **not** a good practice to write a Secret this way, and to put the secret value directly in the code. However, doing it in a cleaner way requires to use an operator, and to have external dependencies, etc ... This is not the point of this exercise, so we'll stick with this approach for now_
## Third step - Install some dependency
The next 2 components of the application will require a database. We have 2 options available here:
- Install a database manually
- Use an operator to install and manage it for us
We'll go for the 2nd option in this exercise. The goal of the 3rd step is to install CNPG (**C**loud **N**ative **P**ost**g**reSQL) operator and to use it to create a database for us.
Have a look at https://github.com/cloudnative-pg/charts
To run `helm` commands, you'll have to specify the kubeconfig to use. If you're runing `k3s`, it's located in `/etc/rancher/k3s/k3s.yaml`. You can then use `helm --kubeconfig /etc/rancher/k3s/k3s.yaml`
_You may have a look at the default values with `helm show values cnpg/cloudnative-pg` or by looking at the chart's `values.yaml` from the sources._
Install the operator in the namespace `cnpg-system` using its helm chart.
_If you encounter an error `The CustomResourceDefinition "poolers.postgresql.cnpg.io" is invalid: metadata.annotations: Too long: may not be more than 262144 bytes`, have a look at [this issue](https://github.com/cloudnative-pg/charts/issues/325#issuecomment-2569739488)_
Confirm it's running by checking the operator's pod status.
I also very much recommend you to install the `CNPG` [plugin](https://cloudnative-pg.io/documentation/1.20/kubectl-plugin/) for `kubectl`, to run `kubectl cnpg` subcommands.
## Fourth step - Create a database
Using CNPG's CRD, create a working PostgreSQL database by writing the proper manifests in `helm/templates`. The CNPG operator must create a database in the cluster that will be used by the app.
You need 2 replicas for better availability.
Once done, running `kubectl cnpg status <database name>`
should yield something similar to:
```
Cluster Summary
Name <namespace>/<database name>
System ID: 7565628524122476572
PostgreSQL Image: ghcr.io/cloudnative-pg/postgresql:18.0-system-trixie
Primary instance: <database name>-1
Primary promotion time: 2025-10-26 20:22:44 +0000 UTC (36s)
Status: Cluster in healthy state
Instances: 2
Ready instances: 2
Size: 98M
Current Write LSN: 0/4000060 (Timeline: 1 - WAL File: 000000010000000000000004)
Continuous Backup not configured
Streaming Replication status
Replication Slots Enabled
Name Sent LSN Write LSN Flush LSN Replay LSN Write Lag Flush Lag Replay Lag State Sync State Sync Priority Replication Slot
---- -------- --------- --------- ---------- --------- --------- ---------- ----- ---------- ------------- ----------------
<database name>-2 0/4000060 0/4000060 0/4000060 0/4000060 00:00:00 00:00:00 00:00:00 streaming async 0 active
Instances status
Name Current LSN Replication role Status QoS Manager Version Node
---- ----------- ---------------- ------ --- --------------- ----
<database name>-1 0/4000060 Primary OK BestEffort 1.27.1 titanium
<database name>-2 0/4000060 Standby (async) OK BestEffort 1.27.1 titanium
```
This shows a cluster in a healthy state with 2 replicas.
## Fifth step - The Catalog service
Now is time to deploy the next service: the catalog.
Write its deployment manifests in `helm/templates/catalog`.
The manifests will be somewhat similar to the ones for the gateway, but you'll have to plug the DB in.
To test things, you'll likely want to step up from hitting the `Service` directly: add an `Ingress` to the deployment of the Gateway. This will allow you to test if the gateway is indeed well connected to the Catalog.
The Catalog exposes a `/health` endpoint. Use it to define a LivenessProbe.
You can assert it by running:
```
$ curl 127.1/api/catalog/products -u zarak:$secret --json "$(jo name=computer price_cents=100000)"
{"id":1,"name":"computer","price_cents":100000,"stock":0}
$ curl 127.1/api/catalog/products -u zarak:$secret
[{"id":1,"name":"computer","price_cents":100000,"stock":0}]
```
_note: this is also a good occasion to check that you can use a customly defined account_
## Sixth step - The Orders
Reaching this point, you should have an up-and-working database cluster, a Gateway and a Catalog.
You should have created a product already in your catalog, but now, it's time to deploy the last microservice, the Orders.
Similarly to the previous microservices, deploy the Orders in `helm/templates/orders` and make sure it works:
1. Create a product in your catalog and add some stock: `curl 127.1/api/catalog/products -u zarak:$secret --json "$(jo name=computer price_cents=100000)" && curl 127.1/api/catalog/products/1 -u zarak:$secret -XPUT --json "$(jo stock=10 price_cents=100000 name=computer)"`
2. Check your orders: `curl 127.1/api/orders/orders/me -u zarak:$secret`. It should be empty
3. Place an order: `curl 127.1/api/orders/orders -u zarak:$secret --json "$(jo items="$(jo -a "$(jo product_id=1 qty=1)")")"`
4. Check your orders: `curl 127.1/api/orders/orders/me -u zarak:$secret`. You should see the order placed above
## Seventh step - Helmify it all
You may have not used Helm variables or templating abilities in this project yet. It is now time to change this, and to convert the statically defined manifests into proper Helm templates.
Follow the [helm best practices and convention](https://helm.sh/docs/chart_best_practices/conventions/) and the [helm doc on charts tips and tricks](https://helm.sh/docs/howto/charts_tips_and_tricks/). It will take you few minutes of reading but will get you started and on good tracks
What should be put into the values, and what makes a good Helm Chart ?
Anything that looks like a tunable, a value that may change from a deployment (a release) to another is a good candidate for being into the values.
You can have a look at pre-existing good Helm Charts for suggestions, like `grafana/grafana`, but those are very complex and may take you too much time to analyze.
For this step, I'm only expecting at least these elements to be tunable via the values:
- the image used for the 3 services, the tag and imagePullPolicy
- the number of replicas
- whether or not I want the Ingress to be deployed
- the size for the storage of the database and its number of replicas
- Define some resources limits and requests for the CPU and Memory on each Deployment. Follow [this good practice as well](https://home.robusta.dev/blog/stop-using-cpu-limits)
## BONUS: Eight step - Going further
This step is not mandatory. These are elements to go further if you are interested.
- Define the labels of the pods via templates defined in `_helpers.tpl`
- Define some (anti)affinity on the Deployments
- Define it on the database as well
- Look into defining a SecurityContext
- Create an [HPA](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) and a [PDB](https://kubernetes.io/docs/tasks/run-application/configure-pdb/)
- Deploy a Prometheus Operator and add the possibility to deploy [ServiceMonitor](https://prometheus-operator.dev/docs/api-reference/api/#monitoring.coreos.com/v1.ServiceMonitor) to scrape you app's metrics