Featured image of post Creating a Kubernetes Cluster on Clever Cloud with Linux Apps

Creating a Kubernetes Cluster on Clever Cloud with Linux Apps

Guide for deploying a Kubernetes control plane on Clever Cloud using the new Linux type apps. Educational project and nod to the upcoming managed Kubernetes.

Ecrit par ~ zwindler ~

Introduction

After testing static apps at Clever Cloud, I wanted to push further the exploration of new features.

Clever Cloud has indeed recently launched a new type of application: Linux apps. These allow deploying generic applications on a Linux machine, without a predefined runtime. A bit like having your own VM, but managed by Clever Cloud.

So, for “fun” (and out of pure technical curiosity), I decided to create a complete Kubernetes control plane on this type of instance. Why?

First because I’m always talking about Kubernetes. And second, as a little nod to the friends at Clever Cloud: they’ve been announcing for months their managed Kubernetes offering that should be coming out (soon™).

And by pushing the Linux runtimes to their limits, it allowed me to discover quite a few features that (I admit) I didn’t know about at Clever.

Disclaimer: this project is purely educational and experimental. Don’t use it, even for personal use!

The Project: k8s-on-clever-linux

I created a dedicated GitHub repository for this project: k8s-on-clever-linux.

The principle is simple: deploy all Kubernetes control plane components (etcd, kube-apiserver, kube-controller-manager, kube-scheduler) as processes on a Clever Cloud Linux app.

The project is heavily inspired by my demystifions-kubernetes tutorial. And actually, to test the proof of concept, that’s what I simply went through, to validate feasibility.

However, in the case of Clever, rather than launching all components one by one by running a bash script (which is done in github.com/zwindler/demystifions-kubernetes), I was a bit smarter…

Discovering Mise

But first, some context:

Clever Cloud platform provides a multi-runtime environment, including many tools to deploy and run your applications. The Linux runtime is a versatile solution to build and deploy any kind of application. The Mise package manager helps you to install and manage any supported dependencies Clever Cloud doesn’t provide by default such as Dart, Gleam, Zig for example.

Already, it mentions something called Mise package manager. What’s that?

It’s a project by Jeff Dickey (jdx.dev). On our Linux apps, it will allow us to install some of the dependencies on one hand, and orchestrate the launching of commands to build, then to run our project in the Linux instance.

Application Breakdown

Now that we know more, here’s how I broke down the application with Mise:

Initialization (mise.toml)

Even if all prerequisites aren’t all available in Mise, I can still delegate the download of some of them, which will lighten the dependency download script compared to démystifions kubernetes. Always good to have:

[tools]
cfssl = "latest"
etcd = "latest"
kubectl = "latest"
helm = "latest"

Build Phase (.mise-tasks/build)

  • Downloads all Kubernetes binaries
  • Generates certificates for component authentication
  • Configures static parameters
  • Caching: this phase is only executed when code changes

This way, if the application reboots, we don’t lose certificates and we save some time for redeployment.

Run Phase (.mise-tasks/run)

  • Launch all components of our Kubernetes control plane
  • Generate a bootstrap token to add Nodes
  • Launch an HTTP server that listens on port 8080 and serves… nothing.

Why an HTTP server? Well simply because

Linux runtime only requires a CC_RUN_COMMAND to execute, with a working web application listening on 0.0.0.0:8080.

If I don’t have anything listening on port 8080, Clever will log that my app is down (when it’s not).

Focus on Mise for Installing Dependencies

Concretely, what does it look like when you launch an app in Clever Cloud with a mise.toml?

Well, simply like this:

2025-07-20T15:54:04.304Z mise etcd@3.6.2      install
2025-07-20T15:54:05.302Z mise etcd@3.6.2      download etcd-v3.6.2-linux-amd64.tar.gz
2025-07-20T15:54:06.403Z mise etcd@3.6.2      checksum etcd-v3.6.2-linux-amd64.tar.gz
2025-07-20T15:54:06.422Z mise etcd@3.6.2      extract etcd-v3.6.2-linux-amd64.tar.gz
2025-07-20T15:54:06.705Z mise etcd@3.6.2    ✓ installed

The list of tools available with mise is here:

And we can note that tools come from different sources such as aqua, pipx, ubi, asdf… It makes me want to test this for the dependencies I’m missing so expect an upcoming article on the subject :).

But… Where Are the Kubernetes Control Plane Binaries?

Initially, I had put all that in the run phase. Except that David suggested I discover another Clever Cloud feature that I had never used: Workers.

Workers are background processes that run in parallel with your main application.

For our Kubernetes cluster, it’s perfect! Rather than launching all components in a bash script, each control plane component becomes a dedicated worker:

  • Worker 0: ./run-scripts/start-etcd.sh
  • Worker 1: ./run-scripts/start-kube-apiserver.sh
  • Worker 2: ./run-scripts/start-kube-controller-manager.sh
  • Worker 3: ./run-scripts/start-kube-scheduler.sh
  • Worker 4: ./run-scripts/post-boot.sh - bootstrap tokens, RBAC, worker scripts

With the bonus of being able to configure restart behavior:

  • CC_WORKER_RESTART=on-failure (default): restart only on error
  • CC_WORKER_RESTART=always: always restart
  • CC_WORKER_RESTART_DELAY=1: delay in seconds before restart

Installation and Configuration

OK, clearly, this part isn’t the most user-friendly of the tutorial. If you want more details on what’s done and why, you can take a look directly at the repository github.com/zwindler/k8s-on-clever-linux.

The main steps are:

  • Create the Linux app clever create --type linux --github zwindler/k8s-on-clever-linux

  • Configure TCP redirection

Linux runtime only requires a CC_RUN_COMMAND to execute, with a working web application listening on 0.0.0.0:8080.

As a reminder, Clever Cloud expects you to expose an unsecured HTTP app on port 8080. But I want to expose the Kubernetes API server in HTTPS. So we’ll enable “TCP redirection”: Clever Cloud assigns a random port (in the 5XXX range) that redirects to port 4040 of your application (and so I need to tell the API server not to listen on 6443, but 4040):

$ clever tcp-redirs add --namespace default
Successfully added tcp redirection on port: 5131
  • Domain configuration

Once the TCP port is known, we can configure our domain.

clever domain add k8soncleverlinux.domain.org

In my case, the API server will be accessible on the domain k8soncleverlinux.zwindler.fr and on port 5131. Since these two values will be different for you, I quickly coded a script that uses default values for me, but allows you to override them if you specify values on YOUR Clever Cloud app:

clever env set K8S_DOMAIN k8soncleverlinux.zwindler.fr
clever env set K8S_TCP_PORT 5131

I’m really nice.

  • Worker configuration: for this I made a script ./setup-clever-workers.sh

This script configures the 5 workers I mentioned above which are managed by systemd.

Deployment and Testing

If everything went well, the app should launch correctly, Mise should launch the build phase (mainly downloading missing dependencies), then the run phase (dependencies known to Mise, and workers once run completes):

2025-07-19T13:10:45.130Z Successfully deployed in 0 minutes and 18 seconds

Background workers should start all components of our Kubernetes control plane. After a few seconds (1 minute max), we should have a functional control plane, and a bunch of certificates and other secrets.

But how to test it? Via SSH on the Clever Cloud instance! Because yes, you can SSH into this instance :)

$ clever ssh
Last login: Fri Jul 18 20:17:00 UTC 2025 on pts/0

$ kubectl version
Client Version: v1.33.2
Kustomize Version: v5.6.0
Server Version: v1.33.2

🎉 It works! We have a Kubernetes control plane running on Clever Cloud!

And if you’ve done your homework (i.e., correctly configured your domain and port), you can even use it from outside by copying the admin.conf file locally and modifying the endpoint in the YAML:

$ kubectl cluster-info
Kubernetes control plane is running at https://k8soncleverlinux.zwindler.fr:5131

Adding a Worker Node

Second hitch in my story. A control plane without workers isn’t great.

And I can’t use Clever’s Linux app type because I don’t have enough privileges to change kernel options (routing) and install containerd.

But I wasn’t going to stop there. In my “Démystifions Kubernetes” project, I install a worker directly on the control plane machine and use admin.conf (cluster-admin) to enroll the current host as a Node.

It’s dirty, but when I’m at a conference and I have 20 minutes to speedrun, it’s acceptable.

Here, I made the effort to look at how to enroll Nodes with a bootstrap token and it’s quite interesting.

The official kube documentation is quite well done and I might write a separate article on the subject, it’s actually quite easy, with a flag to add to the API server, some RBAC and a very specific secret to generate.

I’ll spare you the details, the project will automatically generate a script to add external nodes:

2025-07-19T13:51:47.848Z ✓ Worker setup script generated: setup-worker-node.sh
2025-07-19T13:51:47.849Z 📋 Next steps: 
2025-07-19T13:51:47.849Z 1. Copy setup-worker-node.sh to your external worker node
2025-07-19T13:51:47.849Z 2. (Optional) Edit NODE_NAME_OVERRIDE and NODE_IP_OVERRIDE if auto-detection is incorrect
2025-07-19T13:51:47.849Z 3. Ensure your worker node has sudo privileges and internet access
2025-07-19T13:51:47.849Z 4. Run the script on your worker node: sudo ./setup-worker-node.sh
2025-07-19T13:51:47.849Z 5. Check the node joined with: kubectl get nodes
# Automatically generated script
#!/bin/bash
# External Worker Node Setup Script for Kubernetes

API_SERVER_ENDPOINT="https://k8soncleverlinux.zwindler.fr:5131"
BOOTSTRAP_TOKEN="xxxxxx.xxxxxxxxxxxxxxxx"
# ... (complete configuration and certificates/secrets included)

Just copy this script to any Linux VM and run it. The script:

  • Installs containerd, kubelet, kube-proxy
  • Automatically configures authentication via bootstrap token
  • Starts services
✓ Worker node setup completed!

$ kubectl get nodes
NAME       STATUS     ROLES    AGE   VERSION
coucou1    NotReady   <none>   13m   v1.33.3

The node appears as “NotReady” because the CNI plugin is missing. Flannel works perfectly:

$ kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

$ kubectl get nodes
NAME       STATUS   ROLES    AGE   VERSION
coucou1    Ready    <none>   30m   v1.33.3

$ kubectl get pods -A
NAMESPACE      NAME                    READY   STATUS    RESTARTS   AGE
kube-flannel   kube-flannel-ds-b65jg   1/1     Running   0          14m

The cluster is functional 😎😎😎!

We now have a complete Kubernetes cluster with control plane on Clever Cloud and external worker… Before the official release of Clever’s managed kube.

Limitations and Possible Evolutions

Since the external worker isn’t in the Clever Cloud network, the API server can’t communicate with the kubelet (in the api-server -> kubelet direction only). This limits features like kubectl logs, kubectl exec, etc. A possible solution would be to use Clever Cloud’s Network Groups to create a secure private network.

If this project continues to amuse me, several improvements are possible:

  • exploration of Network Groups (which I just mentioned)
  • integration of Kine. This would allow using Clever Cloud’s existing databases (PostgreSQL, MySQL) as a DB for Kubernetes, which would possibly make this install “HA” which would be as stupid as it is funny
  • creation of missing aqua/ubi packages (kubernetes binaries and cfssljson)

Conclusion

I started this project as a joke and I ended up discovering many cool little features at Clever Cloud that I didn’t know existed (or that I had seen but not explored).

Even though it took me a few evenings, it was super interesting. The first iteration was very quickly functional and I got caught up in the game of successive improvements.

The complete code is available on GitHub if you want to reproduce the experience or contribute!

And you, what crazy projects do you want to test with Linux apps? 😄 As for me, I’m waiting for my access to Clever Cloud’s managed k8s now 😝 (what do you mean, I’m pushy???)

Licensed under CC BY-SA 4.0

Vous aimez ce blog ou cet article ? Partagez-le avec vos amis !   Twitter Linkedin email Facebook

Vous pouvez également vous abonner à la mailing list des articles ici

L'intégralité du contenu appartenant à Denis Germain (alias zwindler) présent sur ce blog, incluant les textes, le code, les images, les schémas et les supports de talks de conf, sont distribués sous la licence CC BY-SA 4.0.

Les autres contenus (thème du blog, police de caractères, logos d'entreprises, articles invités...) restent soumis à leur propre licence ou à défaut, au droit d'auteur. Plus d'informations dans les Mentions Légales

Built with Hugo
Theme Stack designed by Jimmy