Bonny: Extending Kubernetes with Elixir — Part 3

Arrr, gopher yourself.

This is part 3 of a multi-part series. Checkout Part 1 to learn about Operators or Part 2 for the beginning of this tutorial. The full source code is available here.

In this post we are going to flesh out the add/modify/delete functions from the previous tutorial and interact with the Kubernetes API to deploy a few versions of our Greeting CRD.

Down right nifty, my dude.

To make the HTTP calls, we are going to use an extremely naive Kubernetes HTTP client. Since the k8s API is RESTful, I tend to prefer a light wrapper around HTTPoison.¹

Let’s get started with the simple client. Download this module and place it in your lib directory. This module exposes three functions post/2, patch/2, and delete/2. Each function will receive two arguments: a map of the k8s resource and the k8s configuration Bonny was started with, which will be used to “sign” the HTTP request.

If you didn’t look at the source code for that HTTP client you just added to lib, here it is:

It’s great for this tutorial, but I wouldn’t go shipping that on a Friday at 4PM.

Open up lib/hello_operator/controllers/v1/greeting.ex and add two rules just below use Bonny.Controller to generate the RBAC:

Now if you’ve dilly-dallied with Roles, RoleBindings, ServiceAccounts, and all that other RBAC-jazz before, I hope you find adding rules here to be much simpler.⁴

The first rule will allow this operator to perform all CRUD operations on Deployment resources, and the second will allow all operations on Service resources.

In the previous tutorial, I included the following examples of a resource manifest and a Bonny event payload:

So swell of you to drop by to say, “Hello”
95% blah blah blah, L#22 is muy bueno

Let’s add a few functions to parse the payload into a Kubernetes Deployment and Service manifest:

Mo’ mustaches.

There is a lot here, so let me break this down. Given this manifest:

Well hello, yourself.

parse/1 will extract the namespace, resource name, and greeting from the event payload that is received as a result of the k8s API receiving an add/modify/delete event for your custom resource.

gen_service/1 creates an HTTP NodePort service on port 5000 that will select pods with the label app=hello-server.

gen_deployment/1 creates an apps/v1 deployment with two replicas bound to port 5000. It will run the greeting-server² with the environment variable GREETING set to the value in spec.greeting, which is “Hello.” Profound, I know.

These three functions and the naive client DRY up the majority of the add/modify/delete events’ code.

Implementing the add/1 function is pretty straightforward. We will:

  • parse the payload
  • get the Kubernetes config to sign our HTTP request
  • create a deployment
  • create a service
add/1 implementation

Pretty easy right? Now for modify and delete. You’ll see that they are both very similar.

modify/1 implementation

You pretty much just copy-pasta’d your way to fame and fortune. modify/1 has the same flow as add/1 except that it patches the resource instead of posting it.

How do we delete? You know… I know, you know.

delete/1 implementation

Same flow again, except here we delete the resources. That’s it. You should now have a functioning operator!

Now, I know what you’re thinking:

Jeez, you made this extending Kubernetes boring-simple.

Yup. I’m old, boring is my M.O.

Your greeting controller should look like the following:

That’s sassy.

Let’s compile the mix project, generate the manifest, and start pillaging some cloud resources.

# You may need to compile the app for the mix task to pick up your controller
mix compile
# Generate the kubernetes manifest. 
mix bonny.gen.manifest
# Apply your CRDs, RBAC, etc
kubectl apply -f ./manifest.yaml

In this example we are starting the operator via iex on your local machine. If you were running the operator in cluster you would need to build a docker image and specify the --image flag.³

Before we start the operator, make sure the greetings CRD was created successfully. Run the following and you should see some details about your CRD:

kubectl describe crd/greetings.hello-operator.example.com

Now start up the operator:

iex -S mix
Holy shit, I know.

You should see a debug message like:

As in the last tutorial, we’ll now open another terminal and create two Greeting resources. This time the Hello Operator will deploy two applications instead of simply printing debug output. This will create hello-server and hola-server.

kubectl apply -f https://raw.githubusercontent.com/coryodaniel/hello_operator/master/greetings.yaml

In your iex session you should see some log messages:

You should now be able to see your greetings:

kubectl get greetings
NAME AGE
hello-server 28s
hola-server 28s

Run kubectl get all and you should see four pods (red), two deployments (blue), and two services (green) running. Your IDs and port numbers will vary:

How kewl is that magnify effect?

On the right side, you’ll see two port mappings from 5000:3xxxx. The 3xxxx address will be accessible on your localhost.

Visit the hello-server. The following command will print a URL for you to open in your browser.

kubectl get svc/hello-server -o go-template --template 'http://localhost:{{(index .spec.ports 0).nodePort}}/greeting/elixir'

You should see: Hello, elixir.

Now visit the hola-server. This command will print a URL for you to open.

kubectl get svc/hola-server -o go-template --template 'http://localhost:{{(index .spec.ports 0).nodePort}}/greeting/elixir'

You should see: Hola, elixir.

Woohoo! You did it!

This tutorial is too gentrified, let’s delete the hello-server greeting.

kubectl delete greeting/hello-server

If you run kubectl get all again, you should see the hello-server deployment and service have been deleted or may still be terminating.

Now let’s clean up your namespace:

# delete the hola server
kubectl delete greeting/hola-server
# delete all the resources created for your operator
kubectl delete -f ./manifest.yaml

That’s it! Tweet me your ideas for any operator you’d like to see a tutorial for or any operators you’ve built using Bonny.

If you are interested in extending Kubernetes, there are a few open issues. I’d love the help!

Footnotes:⁶

  1. If you need a full-fledged k8s client I suggest Kazan. There is also an open issue to create a basic k8s client in Bonny if you’re interested in lending a hand. :)
  2. You could easily replace this with your own applications docker image URL.
  3. The README has notes on generating a Dockerfile and generating a manifest for a docker image.
  4. If it’s not simpler, I suck at life, and sincerely apologize⁵.
  5. JK
  6. Are my footnotes’ numbers out of order? Yes.