ramoska blog logo showing grasshoper

ramoska

Preparing OTel Playground on microk8s

Posted on January 2024 in Web Development

Previous experience

I have been using microk8s on Linux for a while in one of my previous jobs. At that time I was looking forward to migrating to K8s (sadly didn't happen before me leaving the company) from our old infrastructure. Since we didn't have any experienced sysadmins it looked like a good idea to have something we as developers could use and config ourselves. It could have potentially solved server updates and dependency issues:

  • use latest Docker image to have latest PHP (or any other programming language) version
  • containerised application dependencies do not interfere with dependencies of other applications:
    • same physical server can run multiple versions of PHP
    • it can even run multiple versions of the same application
    • local development environment can be very close to production one
    • multiple different applications can run on the same development machine

All of these above lead to my team to start using microk8s. At first as a local development environment, and later to use it as a reference point to build production environment.

Trying it now

So all of that got me familiar with microk8s. But all that experience was on Ubuntu (and whichever distro was running on our servers). But now I'm on MacOS. So let's check it if it works here as well (but since I'm writing this post, it is pretty safe to assume it does). And it is pretty straightforward to have microk8s running on MacOS. It's just single command:

brew install ubuntu/microk8s/microk8s multipass

Multipass was required by microk8s and it's just a lightweight VM system, which is necessary to run docker engine. And just test that microk8s is running: microk8s install. And after running microk8s status something similar sould be displayed:

microk8s is running
high-availability: no
  datastore master nodes: 127.0.0.1:19001
  datastore standby nodes: none
addons:
  enabled:
    dns                  # (core) CoreDNS
    ha-cluster           # (core) Configure high availability on the current node
    ...
    registry             # (core) Private image registry exposed on localhost:32000
    storage              # (core) Alias to hostpath-storage add-on, deprecated
  disabled:
    cert-manager         # (core) Cloud native certificate management
    ...
    rook-ceph            # (core) Distributed Ceph storage using Rook

No hassle, no fuss. It's almost boring. Hats off to the team behind microk8s and multipass.

Build the playground

Since everything is going so smoothly, it's good opportunity to try and build local playground for OpenTelemetry. I'll need 3 things to make it work:

  • PHP application
  • Database
  • Redis for message queue
  • All of that running in separate namespace inside microk8s

All of that should be services in microk8s IIRC. At least PHP application will have deployments, from which application service will be created. And IIRC I'll need ingress controller? Yet again, IIRC, at least for application it has to be deployments. From them service will be configured. And ingress controller will marshal traffic to the services (Redis and database will be services). So time to dig into it.

Database

So we'll start with database because I remember the least about it. First things first, I need persistence between restarts/updates, so I'll need to have volume mounted to the database pod. And service to expose it to the other applications on cluster.

After looking into persisted volumes on k8s I have to admit I had little to no luck having it setup in my desired way. I wanted that volume to be mounted on the local path inside project dir, or at least in some default location for volumes that I could reuse with a name or something similar. But in the end all I could find was how to provide absolute path. Not wasting more time on that I'm looking into something called stateful sets. And it seems that it's nothing different from using volumes. Although PersistentVolumeClaim looks somewhat promising. And seems that was the way to go. Have deployment, namespace, and PersistentVolumeClaim all green in dashboard. Time to create service and I'll done with database. Everything is almost verbatim copy from tutorial for Wordpress with MySQL in k8s docs.

Redis

This one shouldn't be that hard. I just need deployment and service running without any persistence (at least for now).

Application

This one forced me to reconsider my approach to persistence. As I mentioned I wanted to have DB volume mounted to to local path relative to project dir. But that would mean hard-coding full path, which is not feng shui even in local development. So rethinking it I decided to have somewhat of a template for k8s config, introduce Makefile to generate actual config from it, and have a command in it to generate it replacing placeholders with actual paths. All these shenanigans are necessary to have proper development environment, when I can edit code on my machine and have it executed in container (I really don't want to ssh into container to edit code).

So after dealing with persistence next thing on the list is to have PHP images built and pushed to local registry (which microk8s has and use to build pods). I have 2 options here on how to deal with images and deployments:

  1. having single latest image and force microk8s to always pull it and also somehow force microk8s to trigger deployment update
  2. have image with version tag (git commit hash) and update gitignore'ed k8s config with new image tag (that way I would for sure trigger deployment update when applying config):

Second option seems to be more hassle free. I already imagine 2 new Makefile commands to build image and push it to local registry. Image itself will be defined in Dockerfile, in docker directory inside project's root dir (there's a link to repo for all of this). And let's make it easier to manage with some commands in Makefile. And because of my setup I had to step on some rakes.

First of all getting "http://localhost:32000" that refuses connections didn't foreshadow anything good. At first I thought that it might be missing service in microk8s. Or some hiccup inside microk8s itself. So restarting registry addon didn't help. Maybe ingress addon is necessary for microk8s to accept requests? Not in this case. One of search results hinted me that I might be barking at the wrong tree. My microk8s is running inside multipass VM. And I need to use that machine's IP address instead of localhost. Typing 192.168.64.2:32000 in browser's address bar and it's working! So how do I get that IP address in cli? Some multipass info and grep can bring you a long way. But now podman makes HTTPS calls. So it seems like I'll be using podman machine ssh and add microk8s registry as insecure one. Image is now being built and sent to registry.

On to the next step which is create dummy PHP application, deploy to microk8s, and make it respond to requests from browser. Let's create Symfony project and check whether it's working.

symfony new api --version="^7.0" \
  && cd api \
  && symfony server:start

Opening it in browser shows me Symfony welcome page. So far so good. Now interesting part of k8s configs. Docker image will contain nothing except what is necessary to run Symfony application. Then I'm going to mount api dir to /var/www/html inside container. And that should work. Then there's only deployment, service, and ingress left. And how naive I was. I forgot that PHP-FPM needs reverse proxy to work. So that also needs nginx container running in same pod. And that too long to figure out. But in the end it's working. And it was too long ago to remember that /etc/hosts doesn't handle wildcards. I'm so out touch with devops stuff.

After battling layers of virtualisation, networking, and fpm/nginx I finally have app, that I can access from browser using domain name, and source running from my machine using mounted volumes. I also felt nostalgic about Ubuntu, where I had not to deal with multipass and could use Docker directly, didn't need to mount volumes to VM, so I could mount them to k8s pod.. Good old times.

Final thoughts

I didn't know how good I had it when I was using microk8s on Ubuntu. It was so easy to setup and use. Also I used to tinker with networking and stuff a lot more than I do now. But finally got it running on my MacOS. I guess I would have even less luck if my machine was using Apple Silicon. I ran into things I forgot about (PHP-FPM and nginx reverse proxy things), things I ran into only because I decided to use microk8s on MacOS (volume mounting is the first thing that comes to mind). At some point I was even considering to use Minikube or some other approach. But I was too invested in microk8s and had a gut feeling that it should work with some additional effort. And it did.

In the end I have working playground for OTel experiments. In next post I'll prepare somewhat of primitive API using current standard approach to logging and metrics. And then I'll experiment with OTel SDKs to see how it works and how to incorporate it into existing codebase.

This is part 2 of the OpenTelemetry and Observability series.

Previous post:
OpenTelemetry: starting series
Next post:
Preparing OTel Playground on microk8s: Part 2

Tags: Kubernetes, K8s, microK8s, PHP, PHP-FPM, Symfony, nginx