 Let's get started. Thank you for joining me on this talk. I hope you're enjoying Bill Bowell as much as I have been. Today we're going to be talking about cross-plane composition functions. And I find these really exciting, so this has been a great talk for me to prepare for and think about. So let's get started. So what we're going to do in this talk is first we're going to talk just briefly about cross-plane itself, the project if you're not familiar with it. We're going to talk about something in cross-plane called compositions, which is a way of making more complex infrastructure. Then we're going to really get into like architecture and design of functions. We're going to talk about running functions, and then we're going to have some go-walkthrough, right? I'll actually show you how to write a function and go. So we'll have some coding at the end of this talk. So that's going to be the outline of the talk. And one disclaimer here. This is actually kind of exciting for everybody in the audience here. This is unreleased software, right? This is coming into the next version of cross-plane, which is going to come in late October. So you folks are like the first people who are actually seeing kind of this put together how this is going to work. So so this is the state of it right now, like as a pull request made this and last week in terms of what's been merged in the cross-plane master. So things can change. It's probably about 90 to 95 percent what the final design is going to be, but just as disclaimer that that's what this talk is covering today. So first, how many people here are familiar with Kubernetes itself just to get an idea of the audience? Okay, so we have about 80 percent of the audience is familiar with Kubernetes. So we know Kubernetes is a cluster scheduler. It does work, but Kubernetes has this, if you were here at the last talk, the speaker talked about writing operators, right? And he talked about CRDs and things like that. So I'm actually going to show you what that looks like in practice. Kubernetes uses cross-plane uses Kubernetes CRDs and controllers very heavily to basically manage anything, right? Like where Kubernetes is concerned with pods and services and things like that. Cross-plane, you can manage AWS buckets, GitHub projects, pretty much anything, right? So basically exposes any external API and exposes it to your users as a Kubernetes resource. Right, so what does that look like? So you've heard about Kubernetes and YAML. So here what we have is, let's say we want to provision a bucket in AWS. What we're going to do is we're going to have a manifest that says what kind of bucket we want to do. And you can see here is that we have groups, versions and kinds, like every single thing. We've heard about CRDs from the last talk. This is a CRD, right? And we're using it to create a bucket. It supports all the Kubernetes, you know, metadata and groups, versions, kinds. It supports labels and annotations, right? So every resource. And then finally, we have this for provider, right? So a cross-plane resource is a high fidelity representation of a cloud provider's API, right? So in this for provider state, it's pretty much all the settings you could do in an S3 bucket. So I've only got a few here, but there's hundreds of settings that you could do, right? And then there's other things, you know, other CRDs for like bucket encryption, bucket object stage, you know, storage and things like that. So that's what cross-plane looks like. And if we want to see what that looks like in like it, you know, running it in real time, what we could do is we could do something like, I have a bucket here, and I'm just going to apply it to my cluster, right? And then we could see what's going on with this bucket. We could say, hey, what's the status of it? We could watch it. And we could see that cross-plane is going to go create it after a few seconds. It's going to go through what it's going to do is it's going to talk to the AWS API, and then it's going to verify that the bucket exists. And then after that, you know, it'll take about 30 to 60 seconds forever, how long it takes the bucket to get provisioned. Yeah, so now it's ready, right? So that's basically the experience of this. You're not running a shell script or something to provision infrastructure. You're applying something to the Kubernetes API, and then in the back end, the controller is going and reconciling that. One of the nice things about this is that we get all kinds of information out of the bucket, right? So we have a spec. So you could see here, if Kubernetes can be wordy here, but our spec is what we want to send to the cluster, and then we have a status which comes back, right? So you could see here in the status, we have all the things that came back from AWS about the bucket. We have it in our Kubernetes object. Something else that's cool is that we have conditions. So you're not running a shell script and interpreting an exit code, right? What you're doing is you're looking at conditions of the resource. So you could filter these. You could have these propagated up to like a Grafana dashboard or something like that. And then finally, we have events. You can emit events. So things like in terms of like audit and control, you want to see what's happening with your resources, right? So you could hook this in pretty much every Kubernetes cluster in a corporate setting has some kind of event monitoring. So that's a high-level view of what cross-plan is, right? Manage anything with a Kubernetes native API, and then we can just delete this, you know? All right. We'll let that get deleted, and we'll move forward with our presentation. So what I did there, I applied that YAML to my cluster. The way Kubernetes works is that as an API server that sits there, and when we install these providers here with cross-plane, they say they install a bunch of CRDs, right? They say I manage buckets, I manage VPCs, I manage EKS clusters. And then the controller will start. It's basically Kubernetes pod that watches the API server for that. So there's a whole bunch of mechanisms within the Kubernetes API server for tracking Delta's changes for updates and things like that. So your controller can just do a watch, and when something changes on the remote, like if the user changes a setting, like maybe makes a, you know, a disk size larger on a node, it'll get it, and then it'll talk to the AWS API, and then it'll propagate its status back to the API server, right? So this is the core of how cross-plane works. So there's a separation between the controllers and the API servers. What's nice about this is that you have the API, the standard Kubernetes API server. So for provisioning infrastructure, everything you have in Kubernetes today, whether it's Argo CD Flux, whether it's Off-Z, Off-N, or something on your Kubernetes API server, you could just reuse that for your infrastructure. And also things like Coverno, OPA, you know, things like that, right? So this is a really powerful framework. I'd like to say that cross-plane is a CNCF project. So I work for UpBound, which is one of the main contributors to cross-plane. But the whole cross-plane project has been donated to the CNCF, right? So I know there's been a lot of talk in the infrastructure space about licensing and things like that. Cross-plane, the trademarks, governance, and security are under the CNCF. We announced, like, two days ago that our core providers have also been donated to the cross-plane org. So everything is either Apache 2 or MPL 2 that are some derived from some Terraform code. But pretty much UpBound's focus is on the enterprise and scaling. But all the core cross-plane components are in the CNCF. And I just put this here on the map because there we are in the little landscape. I know everybody loves the CNCF landscape. So I said, this is like you are here in the galaxy. And I hope I pointed out the right location for the cross-plane logo. I think I did. All right. Introduction, we're finished with. So let's talk about compositions and functions. So I showed you a basic bucket, right? But we know in an infrastructure you want to do more complex things. So in this case, we have something called an XRD, right? Like, you're a platform engineer. If you've probably been following infrastructure, you've probably been hearing this platform engineering, right? Like, you know, the thing that's past DevOps. So I'll show you an example of it. In this case, we have a YAML file. And you can see here the API version is mycompany.com, right? So this is something that your company is defining. You're defining a cluster, an EKS cluster, or a GKE cluster, or something in your environment. And you're creating a custom CRD for that. So in this case, the only thing you allow your developers to set is the Kubernetes version, the number of nodes, and the size of the nodes. And they don't even know the node types. They're just saying it's small, medium, or large, right? So you need to find an API, a CRD, to your users. And when they're, like, installing their pods and ingress and everything, they could just say, hey, get me a cluster, too. What the composition does, very simply, is it explodes that out into a whole set of infrastructure. So, like, if you have an EKS cluster, you know you're going to need IAM roles, OIDC cluster, and node groups, right? This is similar, you know, if you use Terraform modules. This is a similar concept. So this is something called composition. And this is in cross-plane how we build more complex infrastructure today. And the way it works is when a user asks for something, so here a user is going to create something called a claim, and what we have is they're going to ask for an object store, which is an abstracted bucket, and they're going to say desired region, right? So what we do is we take that spec desired region, and we use something called a patch. If you look down here, and what it's going to do is it's going to take that spec desired region, and it's going to patch that back up to this spec for provider, right? So this is how patch and transforms. This is not about patch and transforms as talk, so I could go very deeply into the subject about all the different things you can do with this, and we also support things like selectors and references. So if you have like a security group, you could say match the label of a VPC and will automatically attach that VPC. So there's a lot of things we could do dynamically with infrastructure within cross-plane with just patches and transforms. So we have, you know, pretty much most people now running very complex infrastructures on top of this right now. So, but I am going to talk about some that we have limitations to that, right? Because we know Kubernetes is a declarative system, right? We're declaring things in YAML and patch and transforms can ameliorate some of that. Labels, references, and selectors can also ameliorate some of that, but we still have lots of problems that we kind of work around today, like using a Helm provider to do some of this. One of them is like loops, right? Like just looping around. It's not something you could really do right now in patches and transforms. Conditionals too. The next thing is flexibility. So right now the patch and transform engine is embedded into cross-plane source code itself, right? So in order to add anything, like if you wanted to add a new hashing algorithm to a transform, you have to modify cross-plane itself, and then you have to wait, you know, whatever time it is, it has quarterly releases for that to come out. And then finally, it's while there is some ordering and dependency management within, you know, cross-plane resources are times an infrastructure when we need to run things imperatively. So design solution for this is going to be functions, and we've been talking about this in the cross-plane community now for a couple of years, so it's taken a long time to get to this point. Number one, we don't want to write around DSL. That was like the number one thing. We don't want to have a new language. The next thing is obviously we want to support complex logic, and one of our goals here is we want to enable people to write these functions to write them in any language, right? We don't want to be opinionated on it, and we want them to be easy to write, to share, and run in your clusters. Okay, this was another big goal of our project. And another thing is we wanted to enable multi-step pipelines in cross-plane itself. So right now it's a declarative. You give it something, and it all provisions it out, and it solves all the dependencies for you, but we wanted to have the ability to run like multi-stage pipelines. And then finally, scalability. So a big theme that's going to I think you're going to see in cross-plane for 2024 is just immense scalability. A lot of cross-plane users are very large organizations, and we're seeing massive amounts of resources being provisioned, and there's a huge amount for lots of cross-plane environments in terms of multi-tenancy. So one of our big focuses is scalability, just like running these things at fleet scale. So the focus for this year for us in UpBound and the cross-plane community has really been talking to our customers, talking to cross-plane users, and basically looking for what are the blockers, what are your pain points, and eliminating those. So we have a quarter release. I've put key features here. You can just reference this slide, and I put all the links I put to the design documentation so you could actually see how things are documented. So every quarter we've been adding features that really improve the core engine. So you could see the first alpha release of functions came out in January. So we spent like six months working on it and refining the proposal. So the beta is coming out 114, and it's actually different than the alpha, but I'm probably not going to go into the differences here because the new design is much better. The old design was kind of inside cross-plane, and we'll talk about the new design, how it runs as a deployment outside. So let's talk about functions. So the first thing is the key thing about cross-plane functions is they run as a UNIX pipeline. So that's probably the closest thing you want to think about. So you're going to run your function, your function accepts in data. Like if you think about those manifests we talked about, right? Like you're going to get a big chunk of YAML. And the way cross-plane pipelines work is that you're going to mutate that data and then pass it out the other side. So the closest analogy is UNIX pipelines here. And in 114, the actual patch and transform engine, which is in cross-plane, we're actually going to have a functional version of it, so we're going to pull it out of cross-plane core. So in this example here, what we're going to do is we're going to have patch and transform after the first function is going to create the resources, right? And then the next function that gets called is just going to mutate them. It's going to do something we don't know what. Maybe it'll add labels and resources. Maybe it'll check for any security settings. And then finally go through, you know, end number of settings to the final desired state. Once all the functions are completed running, then that final desired state gets passed on to the provider and then the provider goes to provision it in the cloud. So this is the model that we're approaching here. A simple function. So let's talk about what functions are because, again, one of the main hallmarks of cross-plane in these functions is that we want them to be simple. So I'm going to just show you a simple example of function here. Let's say that we have a desired state here, and I've made this really short, and we're just going to use the example of JQ, right? So I'm just going to cat this. And JQ has the ability that you could add labels on the fly. You can mutate things within JQ. So in this case, we're just going to cat that text. We're going to pipe it through JQ, and it's going to add a label. And when it comes out the other side, it's going to have the label, right? So you can understand this is kind of the core concept of a function. You pass in your desired state. You mutate it, and you send it out the other side. And that's basically what your function needs to know. It needs to know how to process JSON or YAML. So let's talk about function internals. So functions are basically an OCI image, a Docker image that you install in your cluster. Because we're on Kubernetes, everything is a Kubernetes type. So what you do is you just apply a function to a system, right? You just say kubectl apply my function. You point it to a Docker image, and it'll come up. And then I think I have a bunch of functions on my test machine here, right? You can just see them there. You see they point to the Docker image. You can see they're installed in healthy. I can do a function revision, right? So you can see here which functions. You can see that I've been playing with some of the functions so that there's revisions that have been incremented. So every time you install a new function, it's got a revision. This is pretty common in the Kubernetes world that basically have revisions of objects that go forward. So whenever a function gets upgraded, the new function gets installed, and then there's a termination, and then it gets the new function starts. So basically, that's how functions work. You want to install one, you want to monitor them. All you basically have to do is just install a function type that points to a valid OCI image. The other thing the cross-plane package does, because we want this to be an easy developer experience for folks, is cross-plane also installs a bunch of other Kubernetes resources that surround that function. So this is a little bit small text here, but what I want to say just to highlight some things is it creates a secret which contains a mutual TLS credential for you automatically, right? So all the communications between cross-plane and this function are going to go over a mutually TLS secured. It creates a service account. And also, if you have any CRDs that are part of your function, it will install them on the cluster. So basically, this is all handled for you by the cross-plane runtime. All you have to do is package up your function and you can declare it a function type and install it on your cluster. All right. So how does it work? So we said it creates a Kubernetes service when you install it. It creates mutual TLS. So basically, when you create, and these are functions in different languages, it's going to create the function name in the cross-plane system namespace, right? That's going to be the Kubernetes service if you've used DNS with the Kubernetes cluster. It uses something called GRPC, which is protocol buffers. So basically, we have two things. Cross-plane will send a run function request, which contains that protocol version of that JSON YAML that I showed you before about the desired state. And your function is going to return a run function response, right? And that's basically it. It's like a pipe, right? You get text in of one format and you're going to pipe out the other format of this input mutated. They're basically almost the exact same thing, the two structures. And you could see this. We could run multiple, you know, if you have multiple functions running in the cluster and you have them in a pipeline, cross-plane is just going to invoke each one with the data from the last one. So this is what a protocol buffer definition looks like if you've never seen one before. And basically, what we're saying here is that we have some metadata attached to this run function request. And the two most important things there are the state, observed and desired, right? So that's going to be like an array of objects. If you remember that first YAML that I showed you of the bucket, just think of a large array of those objects. And the observed one, if you remember when I showed you that bucket, it had like all the VPC information and the subnets and everything. That's what's in the observed, right? So you're going to get past that information. And then finally, you can define an optional input that you could send to the function aside from all the composition. And that's a Google Protobuf struct. So that's basically a Kubernetes object, right? So that's how Google represents Kubernetes objects. We could use the same thing in protocol buffers, right? So that's what it looks like in Protobuf and kind of what it looks like in YAML is like this, right? You have the input, you have observed, and you have desired resources, right? So it's going to look something like this. Once protocol buffers unpack and then puts it into your function to be processed. So let's talk about pipelines now. So I think we've covered now like how you install functions, what they are, like how they handle data, how cross-plane talks to them. So what's going to happen here is in our composition, we define this in YAML, we're going to create something called the pipeline, right? And we're going to have, if you've used any CI system recently, like GitHub Actions or, you know, that you have like a YAML of steps and, you know, you define a container and you do a bunch of other things. This is a very similar experience. What we do here is that first we enable pipeline mode. And then in the pipeline, what we do is if you remember those functions that we find, each function had a name in Kubernetes, right? Here we have a function ref, right? So in Kubernetes, whenever you see a ref, that means a reference to an object on a Kubernetes cluster, right? So that's kind of a nomenclature. So here what we're saying is a function that's installed, it references a function, and then we have that optional input, right? That was telling you about, right? So then you have another step here, right? So in this example here, there's actually using Go template to render some text, right? Like you could imagine that it's just going to, you know, you probably give it some input and it's going to use some Go templates and, you know, it will render all the YAML that you need to pass it on to the next one. And this example here is, you know, it's going to use like a validation on it, right? So this is how you call it in your... All right. So yeah, those are the basics, right? So I'm going to cover now writing Go, but just to reiterate what we said, you know, we manage objects in Kubernetes. We have the ability and a composition to manage lots of objects. And the whole idea here with these pipelines is we're going to take those and mutate those through a bunch of steps so that you can have some complex infrastructure and basically manage almost anything. One thing I want to say about writing a function in Go, the lead developer, architect of Crossplane, when he's reviewing my slides, he says, please let them know that this is in development right now. That was his specific feedback. So his SDK was... So just letting everyone know that this is... So basically what we want to do here is we want to create a VPC, but for security reasons, a lot of places that run Kubernetes require labels on every single object on the cluster, right? Otherwise, those things get killed. There's a lot of places that have very strict security policies of what could be running on their clusters. So what we're going to do in our composition, instead of having the user do it, we're just going to automatically add some labels and annotations to every resource, right? That's basically... So if you can see here, we got this and you have labels, metadata labels, metadata annotations, right? We're going to add those. So that's the goal of this function that we're going to write. And I'm going to walk you through the Go code and hopefully everybody feels confident after this if they could do it themselves. Right. So there's a Go function template repository. And what you could do is you could clone this. If I click on this, you could see it here, right? So you could just click on Use this template and you could just get a copy of your own. And I'm not going to go over every step here, but basically what you do is you update your input to whatever input you need for your function. You create a run function and then you just basically docker push it somewhere. That's basically it to write a function. So they're very simple, especially in Go and we're working on other SDKs. A big thing has been talking to the community about what languages they prioritize. So I think Python is the next one that's coming. So these are basically the steps here. I know it looks pretty long, but actually it's just a few steps. So I'm going to walk everybody through it myself. So I created that function to add labels and annotations. I have a link here and we're going to walk through what to do. So the first thing is we have to give an input type. So the last talk was talking about well-known Kubernetes API types. This is what it looks like in Go. If you've never written Go of what a Kubernetes CRD looks like. So those two fields up there, Meta V1, those are actually part of the Kubernetes API machinery. So these are the type of it, that group version kind. So this is the object. Just common things about Kubernetes objects. And then what we do is we add these two things to it. We say a label is just going to be a map of a key and a value, basically. And they're both strings. And we do the same thing for annotations. And these JSON markers here is how it's just a Go thing about how when you have data structures, how they get represented into JSON or YAML. So that's basically our input type. You could set this as a Boolean. You could do strings, any type. So we've defined our input for this function. We're going to say you could define what labels you want to add and what annotations. And if you look at Visual Studio Code, and we look at the code. You could see here input V1, beta 1. We have our input here. And I've added them there. That's basically if that's a whole file. The other thing I did was I did set the group name here. Because this is when you do Go Generate, it generates the CRD for you. So Coup Builder does that for you in controller runtime. So the next thing is how to process the input stream. And we have the SDK that does this. So I have a link to there. So basically what we're going to do is we're just going to say get the desired composition resources. And we're just going to range through each one and just add labels and annotations. That's a whole function. Because we have built-in things to add labels. Otherwise you could just do a range and add each key to the map. But that's basically it. And then what we're going to do is we're going to set that and then return it. The response. So that's the entire body of the function. And I could show you that in the Go itself. This is in Go here. So in our input here we're just going to look at fn.go. We get the input in. We do our desired. And then we just return it with a response and an error condition. And that's the entire function in Go. The SDK actually handles bringing in the converting the protobuf message into like a JSON for you to range through. And the other thing you need to know about functions is main.go. You actually don't have to change this. But the main logic of it when it runs it starts off it starts your function up. It sets up TLS and listens on that port. So basically the SDK already has this set up for you. All you have to do is when you invoke this it's going to start up a GRPC port. And any time any traffic comes through it's going to route it to your run function. Unpack the data for your function. And then when you send it back it's going to return it. So that's basically the core of writing a function. You update your input with the types and then you just basically do your fn.go. You mutate the JSON and send it back. So I hope everybody feels that I know if you haven't used go before there are sometimes some odd things about it, but I hope everybody feels at least somewhat confident that they could do this fairly easily. All right. So I'm just going to go through this really quick. We have time to do a running an example here. So the first thing is we're going to install a pre-release of cross-plane until 1.14 is out. You can just use this slide. The main thing here is if you could see there, we're using a pre-release git branch for master. So there's a Helm chart that installs cross-plane into your cluster. You can get the latest tags. Every time we have a pull request it gets tested and pushed up to Docker so you can see the latest tags. So I'm just using a tag from yesterday. The next thing is just ensure when you look at the logs that beta composition functions are enabled. So that's the other thing you want to do for this test. All right. So the first thing with deploying our functions is we're going to deploy three functions. One for patch and transform and one for add labels and annotations in our composition pipeline. So I already have these installed, these functions. And then what we could do here is if we go look at our composition I just have one here. So this is the composition here. And here's our pipeline code where it starts. So the first thing we're going to do in the first step is we're just going to do a base patch and transform. So we're going to take a VPC and an Internet gateway and we're going to patch them for a couple fields here. And then what we're going to do is we're going to actually I have another function here that just kind of echoes things but and then next thing we're going to run our labels and annotations. So we're going to create the resources with patch and transform and then we're going to pass those resources to label those and annotations in our pipeline and hopefully everything will work. All right, so I've applied this. This is just a YAML file. I've also applied the definition. This is a CRD. So basically we're defining a demo. So a user is going to ask for a network, right? The user asks for a network. They're going to get a VPC with an Internet gateway. All right. See here where we're at. Okay. And you see the input type there. Let me just refer back to that when we have our composition here. You can just see here in our input type that I defined, the labels and the annotations, right? This is the kind when I said the group here. It's a CRD of an input type and these are the things we're going to add to it. These labels and annotations. So let's do this. So this will take a few seconds just to provision. But yeah, what I did here is I created a network claim that provisions this and then the composition is going to create the Internet gateway and the VPC. So the Internet gateway depends on the VPC ID. So when we talked about dependencies, the Internet gateway will not be created until the VPC is in a ready state, right? So this is kind of, it's not like a straight dependency, it's something like Terraform where it builds the whole graph for you in advance. The way it works here is if you have a dependency, that dependency won't be ready until you see the VPC got ready then suddenly the Internet gateway started getting provisioned. So that's how dependencies work within cross-plane. It's a little more asynchronous. We'll let that go. That takes a little while to provision in AWS. And I can just do COOP CTL get managed. We could see both of our resources and if I do OEML we should see that our resources now have had the labels and annotations set to them. Yeah, you can see here, right? There's our labels and there's our annotations. So basically our function, we created our resources based on what these are had and then with our built-in function there that we as a platform engineering installed, we could add tags to everybody's resources even if they don't know we're doing it to their resources. But yeah, that's our end result. So that is, I walk through. Cool. So yeah, I will go into a summary now and then we can do like a Q&A. So one cross-plane functions are stateless, right? They don't restore any state. They can be written in any language. You know, they're designed to mutate data, right? They don't do all the work. They just mutate data and let other systems do the work. A composition pipeline executes things in step, which is a little new for Kubernetes, right? Like Kubernetes has been declarative and it gets to a single state. This is a little bit different. And then when you deploy a function, cross-plane actually takes care of all the machinery around it. You know, handling CRPC traffic for you and then SDK will help you there, too. And finally, the V1 beta one release will be in late October with cross-plane 1.14. So you can play with it today, but this will probably be a big announcement coming in late October for cross-plane itself. All right. And just if you want to get involved, the cross-plane Slack has SIPC composition where all the core developers are, if you want to go talk to them. There's a discussion there in the cross-plane. It's a very good design docs across the project. So if you're really interested in learning about cross-plane, it's a great place to learn. Yeah. And then you could clone. And then there's this cool little thing here called I use it for testing sometimes. You could actually wrap any binary to make it look like GRPC, right, just for local testing. So this is something else worth thinking about. Like if you could just take any script and just like have a wrapper for it that handles all the GRPC stuff for you. So we'll probably have something more advanced for this available when we get closer to the release. So yeah, that is my talk. I'll be happy to answer any questions about anything on cross-plane since but yes, thank you for your time. And I'm very excited about this actually. Having worked on cross-plane for about three or four years now this is super interesting. Thank you.