 Okay. So, hi everybody. I'm sure you've already, you've heard that my name is Lance Ball. I'm a software engineer at Red Hat. I am the co-lead of the Functions Working Group for Knative along with Maricio Salatino. And today I just want to share with you some things that I learned about while digging into the Meta-Controller project while at the same time working really hard on Knative functions. The main thing that I got from this experiment myself was that these two things actually work really well together. So first, let's talk about Kubernetes controllers. I know this is probably a subject that many of you are already familiar with, but just to set a little bit of a baseline, controllers allow you to manage the state of your cluster by running in a control loop, examining the current state, and working to achieve some sort of desired state. It's a pretty simple concept from the Kubernetes controller documentation. The classic example is a thermostat. A thermostat just measures the current temperature and takes some action to achieve the desired state, which is some other temperature, turn on the blower or the radiator or whatever. So this idea is really simple, but also quite powerful. And it's kind of the foundation around a lot of what Kubernetes is built on. And in fact, even more importantly, it allows this entire ecosystem to exist, like Knative, the fundamental architectural components of Knative are controllers and custom resources. So together, these two things bring a lot of really nice power. But the downside is that there can potentially be a lot of sort of friction and mental overhead to achieve something that's really, really simple, potentially. Let's say I have a controller and I want to just spin up a pod every time a new resource is deployed. That's all I want to do. Learning the Kubernetes APIs and all of this just to do that very simple thing is kind of a lot. I think I know a little bit about Kubernetes and I'll just say that anything that I can do to make my life easier and have to learn less is great for me anyway. So how nice would it be if we could just use simple functions to write our controllers, right? Something that just, well, as long as it's in Go, Java, Node.js, TypeScript, Python, or Rust, something that just gets some JSON in that describes the state of the cluster and returns some JSON back describing what we want the state to be. Really simple. We can do that. We can do it with metacontrollers and functions. So I'm going to show you how to do that today. The Metacontroller API is really nice because it makes it easy to do all of this stuff that I just described, JSON in and JSON out. It's basically a webhook that is implemented in the, so basically to write a Metacontroller API, you create a webhook, right? It's just a little thing that runs that receives some JSON and returns some JSON describing the desired state. So what does that look like? Well, there's two different types of controllers when you look at Metacontrollers. The first is a composite controller. Its primary purpose is to manage a set of objects based on the desired state specified in a parent object. So existing controllers that you're probably familiar with, like a jobs controller, is exactly this, right? In the job, you specify the workload and the controller is doing some action to spin up the pods to make that job happen. So let's look at an example of a composite controller in Metacontroller. So here's a composite controller that acts kind of like a regular Kubernetes job controller, but instead of jobs, it's managing index jobs and I have a thing somewhere. Here we go. So instead of jobs, it's managing index jobs and you can see the parent resource that we care about here is index jobs and it has these child resources pods. So it's just like a regular jobs controller, except for the pods that get spun up to do the work, get an index, right? And then down at the bottom is our little sync hook. So this is the thing that gets called by Metacontroller anytime a new index job is created, okay? So this controller manages a custom resource, the index job resource, right? You can say that it owns that resource. If some other controller comes along and starts messing around with the index jobs, you can imagine that our controller probably is going to get into some kind of bad state. So what can we do if there's a resource that already exists that we want to have some add some functionality to, some behavior to? Well, that's where decorator controllers come in. So a decorator controller adds new behavior to existing resources. A K-native service is a good example of this, right? If I were to mess around with the K-native service using that composite controller, I would imagine that the probably the K-native serving controller would, you know, either override my changes or get into some sort of bad state. Not so with decorator controllers. With decorator controllers, you can define rules for which resources you want to watch. You can filter those resources on labels or annotations. And then basically what this allows you to do is add new behavior to existing resources without having to re-implement the resource, right? With the index job controller that I just showed you, that's basically a re-implementation of the job controller. Why would I want to re-implement it if I don't have to? If I could just add some existing behavior to a current resource, right? So let's take a look at an example of that. This example is, like the first one, this is also from the Meta Controller website. This is an example of a decorator controller that watches for stateful sets. You can see here the resources that I care about are stateful sets. And I'm filtering on these annotation selectors. So if the stateful set has a service prepod label and service prepod ports annotations, then my little webhook down here, my sync webhook is called with that stateful set. And then what it does is it spins up a service for every stateful set, for every pod in a stateful set that has those annotations, right? So my function will get, or my controller doing the work will get a single resource just as JSON as the function parameter describing the stateful set. And then the function returns a set of services, right? Associated with the pods. So here again, this is really nice and simple. And one of the other nice things about this is that, you know, I don't have to actually keep track of all this stuff, right? So if my controller, my little webhook there, is returning some things that are associated with this resource, meta controller itself keeps up with that and manages those resources so that when I delete the parent object, for example, the stateful set here, when I delete the parent object, the resources that I've created, the attachments that I've created for it, the services automatically get deleted by meta controller, which is really nice. Again, it just sort of makes things a little bit easier, reduces the amount of friction to get going with a controller. So we've seen some YAML now that declares what our controllers are interested in and the resources that it's going to be managing. We have some kind of basic understanding of these concepts, but we haven't really seen any of the code that actually does this management. So, you know, what does that code do? What do these sync hooks do? On the meta controller website, there are a lot of different examples with a bunch of different ways to deploy a sync hook for your meta controller. But as I was working on this, I was starting to think that maybe actually K-native functions would be a good way to implement the work part of this, you know, of a meta controller. So I want to show you an example of that. But first, in case you're not familiar with K-native functions, let's take a quick look at what they are and how they work. So K-native functions provide a really simple programming model for dealing with K-native services and events and all the sort of K-native abstractions. They're small, focused pieces of code. You can write them in Go, Java, JavaScript, Python, TypeScript, Rust. They're deployed as K-native services. So you're just writing a little function. The K-native client CLI allows you to, or the K-native func CLI, allows you to just with one command line turn that into an OCI container image that then gets pushed and deployed as a K-native service. Also, if you don't like the languages that I just mentioned, we have this concept of language packs, which allows you to extend the base set of languages and templates and add additional features and behavior. And I'll show you how language pack works in a minute. But first, let's just take a look at a basic K-native function as it comes out of the box. Do a little bit of a demo here. You can create functions on the command line or in the IDE. And I've been playing around with the IDE lately. So I'm going to do this in VS Code. Let's see. Can I move this over without dorking out? Yes. Okay. All right. So over here, you know, it's going to be really hard for me to see. I should mirror my laptop, I guess, huh? Because I'm not going to be able to see this way over there. Let's do this on the command line. I know, but if I mirror it, then everyone has to see my notes, right? Okay. So there's the funk command line. And you can just do funk create. Is that too small? Make it a little bigger? Funk create minus l. You know, I can create one and go minus. That's why it's not. Oh my God. I'm spazzing out up here. Funk create. No, I think I'm okay. I'm okay for no. There we go. Okay. So no JS. Actually, you see this? Node minus t cloud events. And viewer. Right? So if I do this, I'm going to make viewer two because I've already got a viewer function. It should just create a, sorry. Oh, no, JS. I have to come down here and look at what I'm doing. Oh, yeah, yeah, yeah. Okay. Thank you. Yeah. Why don't I just stand right here? Thank you. Now I can see. Okay. Yeah, I'm good. Okay. Thanks for all the help, y'all. Okay. All right. Cloud events. Hello. Am I doing something wrong? Oh, right. It's node. See how easy it is? Okay. So it's created the project. It's created the project in the hello directory. We can see that it looks a lot like just any other node JS project. If I look at the index.js file, we can see a lot of comments in there. And it's basically just a function in this case called handle. It receives a context object, which is basically describing a little bit what the context of that request was. And if it's a cloud event was sent as part of this request, the cloud event is provided as the second parameter to the function. And that's what we then export, right? Okay. Back to the slides. All of that just for the slides. I'm just gonna stand right here. Y'all okay with that? Okay. So we saw that. I mentioned that you can extend the language templates to come out of the box. And you do that using this thing called language packs. And on the Knative Functions team, we've started to play around with these language packs and put some experimental stuff up on this URL. GitHub.com slash Knative Sandbox slash Funktastic. And I want to create a meta controller project using a template that I built for this thing. But before I do that, I want to kind of describe what it is that I'm gonna create. That's not it. So all right. So here's the meta controller. This is the kind of the thing that we apply when we apply that YAML. I was showing you for the decorator controller or the composite controller. We have a sync hook function that is deployed as a part of that, which I will show you in a minute. I want my meta controller to observe or watch Knative Services. All right. I only care about services that are functions. So one of the things that the Funk CLI does is it annotates, well adds labels and annotations to the services. And we can identify a Knative Service that is a function through this label function.knative.dev. So it will only send my sync hook Knative Services that have this label. And then what the sync hook will do is create a trigger for every function that gets deployed or updated. A trigger is created or updated. It's kind of a dumb trigger. So for every single event that comes in through the system, through the default broker, they'll all get sent to the Knative Service. So let's see if we can do that. All right. So here's how we would create the function, the meta controller function. This one I don't have to type. So we use the Funk CLI, create the languages, Node.js. The type or the template is meta controller. We're going to point it at the repository. This is basically just a tiny URL for that GitHub URL I was just showing you earlier. And then we're going to call it meta controller. And we actually, let me go ahead and bring up VS code now. And we can, well, let's do it like this. So this is what the YAML looks like from my controller. You can see that it's a decorator controller. The resources I'm interested in are Knative Services. I want to match on the label function.knative.dev is true. And then the attachments that I'm going to apply to these services are triggers. And then here's my little web hook. So, and this is the code itself. I'll just go back to the VS code and show you this. So you can see here, it's just a function. In this case, it's not a function that's receiving a cloud event. So we're just getting the context. The context contains an object in JSON or actually in a JavaScript function. It just gets turned into a JavaScript object that represents the service itself that we're interested in. And you can see, you know, I want to be kind of smart about not attaching a trigger to the meta controller function itself. And then finally, we return an array of attachments. In this case, it's only one trigger per service. So we're just returning a single trigger. And we're naming that trigger off of the service name. And we're, yeah, the sync here is the subscriber is the service itself. Okay. All right. So now let's deploy it. All right. I've already created the meta controller project template using the funk CLI. I was worried a little bit about networking stuff and things like that. I didn't really want to, let's remove that old one, that first one I created. So I've already built it. Like I said earlier, when you create a function that gets built as a container image, I was worried about networking. So I've already built it and pushed it to a registry. So I can just deploy it using funk deploy and tell it, don't build it or push it. I've already done that. So, oh, and actually, let me go down here and k logs. Okay, get pods. All right. So I'm going to deploy this meta controller function. And you can see that it immediately is deployed to the cluster and is now running. All right. So the next thing I need to do is deploy. I just did that. Oh, I need to deploy the, um, the, the YAML itself. So that's just the cube cuddle apply, uh, the decorator controller. I showed you that earlier. Uh, okay. So now we've deployed the controller, um, and the, the sync web hook. We also want to deploy a couple of other things in order to test this out. Uh, first of all, we're going to need the default event broker. I showed you this in the, in the diagram, a simple event source, and then finally deploy a function to see if we get a trigger attached to it. So let's go do those things. Uh, the first thing I said was to create a broker. Um, one of the nice things about the, the Knative CLI, uh, is that, um, there's so much you can do just with the CLI. You don't have to touch YAML. So to create a default broker. Um, well, I already did that earlier apparently. Uh, and then to create the, uh, the source ping create. So this is, I'm, I'm creating a ping source and ping source is just the source that comes out of an event source that comes out of the box with Knative. And it's really nice and useful for testing and that sort of thing, like what we're doing right now. Uh, so this is a ping source that will every minute, um, uh, send a cloud event that has the message hello as its data value. Uh, and it'll send it directly to the, the sync. Also note down here where we're watching our pods, you can see that, that the meta controller itself has terminated now. That's a feature of, you know, Knative spin down to zero. Uh, so the, the meta controller, uh, pod is not up and running, but you will see in a moment, once I, um, get my, my, uh, my viewer function going, we'll see that spin right back up. Okay, so we, we've created the ping source, we've created the broker. Uh, now let's go up here and I've got this viewer function. Um, it's a, a little go function. So if I, uh, look at handle.go, uh, we can see it's not really doing much. It receives a cloud event and prints it to the console and returns. That's it. Um, so I'm gonna func deploy this. I've already built this one as well. Now what should happen is, uh, we see that the viewer spun up and we see that the meta controller, uh, spun up as well because I've now deployed this function. The meta controller in theory has created a new trigger for me. Let's see, uh, KN trigger list. And I see that there is a trigger now created for this, uh, viewer function. And if I look at the logs for the viewer function, I should see, at least in a minute, um, a, a new cloud event come in theory. If my ping source is working correctly, I'll do a little tap dance to take the minute. There we go. All right. All I had to do was take, do a little tap dance. Um, okay. So that's it. Move my computer back around here now and stand up. So, okay. Did I lose the signal? There we go. Okay. That's it. Um, I've done all these things. Oh yeah. Okay. So the other thing I wanted to do was show you, uh, the cleanup. Um, right? So like I said, meta controller itself, you saw the, the sync function wasn't doing any cleanup at all, but meta controller itself does a nice job of, uh, doing that for me. So somehow I've got my emojis up there. Now if I do, uh, KN service, delete viewer, oh, I am. Yeah. I shouldn't have gotten so excited about getting back up here on the stage. Okay. Uh, so I deleted the, the viewer function. Now if I do KN trigger list, no triggers found. So the cleanup, um, happened as expected. Uh, all right. So, um, I just want to say one of my biggest takeaways from all of this, uh, and sorry for the fumbling around with the, the, the, um, the CLI and everything, but, uh, my biggest takeaway from all of this is that these two things actually seem to work really well together. I had a lot of fun playing with meta controller and I've had a huge amount of fun working on K native functions. So I just want to end it by sharing this with you. I wish I had audio. This is, if you weren't alive in 1981, this is like the most classic Reese's peanut butter cup commercial. And so like peanut butter and chocolate, meta controllers and K native functions go great together.