 Okay, for our last talk of the day, I'm going to have Marcin talk to us about writing CNI, and how easy that is. Oops, we missed that, sorry. That's the thing, that's the creepy thing, sorry. We had that sorted, but it's not. No. And you just need to slide there. There you go. Good. Working? Yep. Hello. My name is Marcin. I'm working for Red Hat. I'm taking care of the networking site of the Overhead and CubeViewer projects. And today, I'll tell you about how to write a CNI, and we'll quickly implement a basic CNI plugin. I will try to make it as simple as possible, so please bear with any simplifications that I will make. I'll probably make quite a few of them. Okay, in the demo, I'll first go, I will first have two slides about containers and network namespaces. We'll briefly talk about what a CNI is, and then we'll go over to the live demo. Okay, what is a container? It's a combination of some C groups for metering and limiting, some storage, and a set of namespaces. The namespaces are to keep things private to the container, and we have some protest namespaces, mount namespaces, and what's most important to us in this presentation, the network namespaces. How many of you are using network namespaces, or are familiar with that? Not everyone? Okay, so by default, in a Linux instance, the whole networking stack is shared, so you can see all the network interfaces, the routing tables, all other resources, so network namespaces change that. A network namespace is a private network stack, so you have your own set of network interfaces, your own private routing tables, IP tables, everything's separate. You can have the network interfaces, they are private to a network namespace, so as in this example, you can, for example, move a network interface to a different namespace. A very common scenario that we will use is to employ virtual Ethernet pairs to communicate between any two namespaces. So let's now go over to what happens when a container is created in terms of networking. When a container is created, the network runtime creates a network namespace for our container. This is a simplification for Kubernetes, for example, we have a network namespace for a pod, but let's ignore it for now. And after the network is, the container is created, our network namespace is created, at this stage the network namespace is not yet connected anywhere. It doesn't have any network interfaces, probably besides the loopback interface, and at this stage the container runtime has to implement the networking. Most container runtimes use a plugin architecture in Racket or Kubernetes, CNI is used, so CNI is just a specification that defines the interface between the container runtime and the network implementation. CNI is short for container network interface. It is a simple specification between the container runtime and the network implementation. The main part, of course, is the specification, but it's not only this. CNI also has a few plugins, basic plugins supported by the community. It also has some additional libraries that make writing of plugins easier, like it has a skeleton that you can use to just populate the important bits. And it's part of CNCF, which is also home to Kubernetes. How does it work? When the container is created, the network name space is created. The container runtime looks for a network configuration file. Inside the network configuration file, it finds the information on how to find the networking plugin. Using this information, it invokes the CNI plugin and network configuration is done. Let's look at the CNI plugin invocation. The plugin receives two pieces of information. One is the network configuration file. This is exactly the same file which was used by the container runtime to find the CNI plugin. The other piece of information is the runtime information. This is the information about the container which is being created. The network configuration JSON file, it's streamed into the CNI plugin in its standard input. The runtime information is a set of environment variables, such as container ID or network name space. And this is passed into the container as a set of environment variables. Next, the CNI plugin configures the network. It has to do several items. One is it has to insert network interface into the network name space. Second, it has to do IP address management, so it has to give an IP address to that interface. It has to configure the routing. It also has to modify the networking on the host in such a manner that the container is reachable from all other containers on the cluster. After it's done, it returns a result to the container runtime. The result is in the JSON format, and it's streamed to the container runtime in the plugin's standard output. Let's look at an example of the network configuration file. There are actually only three mandatory parameters. CNI version, this is the version of the CNI specification that we implement, name and type. Type is important here. Type is... It is actually the name of the plugin binary which we will invoke. So, if we are using demo, our binary has also to be named demo. There are also some additional optional parameters like IPAM, DNS, and the file can also contain any number of additional parameters. Not useful for the container runtime, it's also passed to the plugin, and actually in the demo we will make use of this. The runtime information, CNI command, this is a variable that tells us what the container runtime wants the plugin to do. We have four values, add, delete, check and version, add. It obviously means we are adding a container, delete, we are deleting a container, check if the network configuration is OK and version this just returns the supported versions. Container ID, pretty obvious, NetNS, this is the network namespace, IFName, this is the name of the network interface which the container runtime expects us to create. CNI path, this is the path to the CNI plugins directory, and CNI ROX, this is just some additional information that the container runtime wants to pass to us. In Kubernetes, for example, you can find the pod name that is being created in here. CNI result, this is the CNI result for adding a container. We have the CNI version, a list of created interfaces, IPs, routes and DNS information. We'll go over that in more detail in the demo. So, let us now go over to the demo. First, I will briefly talk about what I will do. When we start the demo, our container and our network namespace will already be created. First, we will write a simple network configuration file. The second step, just to make it a little bit easier, we'll use named network namespaces. What we will get from Kubernetes is an unnamed network namespace. Just for the sake of making it easier, we'll create a named one. Next, we'll create a bridge to which we will connect and we'll add an IP address to it. We'll create a virtual SNET pair. We'll move one end of the pair to our bridge and we'll move the other end to our network namespace. Remember that the container runtime gives us the name of the interface it expects to be inserted into the namespace. So, next step, we will rename it. We'll configure the connection, we'll add the IP, we'll configure routing and this is it. During the live demo, I will code the plugin and I will make some typos. I don't plan to make any, but still I will make some. So, let's make it a group effort. If you spot any typo, let me know right away and to make it worthwhile, I have some swag to give away. I have some heads and some lighters which are also, I mean, bottle openers. So, if you want to grab some, spot a typo. Okay, this is my host. It's Kubernetes setup all in one. Sure, I thought it's already big. Is it okay now? Let's first go to our configuration directory, etccni. This is the directory where we will find our network configuration. Now, does anybody know what property Kubernetes uses to set our configuration file? Yes? Oh, you don't count. Okay. Okay, I see no other answers. I mean, no other people knowing this and this is the correct answer. No such property. We can only specify the directory where a configure, but it only, yes, please grab some swag after the meeting. We can only specify the directory. Kubernetes will look at all the files in alphabetical order and it will pick the first one. So, let's modify the one that will specify our plugin. Notice it's the second one alphabetically, so it will not be used yet. We have the version. We have the name. My demo plugin. And we have the type. Let's call it demo. And as I said, we can have any number of additional attributes here. We want to connect to a bridge, so I will add a variable for this. Let's call it demo viewer. And we want to connect to it. We want to be able to set up the routing, so let's also pass in the gateway and make it 10.001. No typos. Okay, I will not rename the second file yet or delete it. We don't have our plugin yet, so let's first write this. By the way, we don't have the bridge either, so let's create it now and let's give it an IP address. And we'll go over to the directory with the plugins. Our plugin is called demo. It's slightly prepopulated to avoid typos. I will explain the already existing items as I go along. So what we want to do first, we want to grab our network configuration file from standard input. We'll do that by just looking at def sdn, just to make it a bit more readable. I'll comment. Next, we want to read our custom attributes. Okay. Wait, wait, wait. And you can center it. Does it work? Okay, sorry for this. I didn't see that it's not visible. Okay, next, what we want to do is retrieve our custom variables. So what we need is our bridge and our gateway. Excuse me? Comments don't count. Please grab your property after the demo anyway. I will run out of swag if you... So we have our network configuration in the conf variable. This is JSON. So let's retrieve the bridge parameter. We will echo conf and we'll pass it to jq. jq is a command line utility for parasing JSON. Very handy if you don't know it. Minus r is for row values. And we want to get bridge. Let me just repeat that. And the second one we want to get is gateway. Next step, we will... As I said, we want to use a namespace. It will be easier to code. So choose a name for our namespace. NSName. Obviously, it should be something unique. So we'll just go with the container ID here. CNI container ID. CNI container ID is the environment variable set by the container runtime. And next, IPAM. To save time, I will choose the simplest IP address management method available. And by that, I mean I will hard-code it. If you want to use it in production, don't do this. It will not work. I mean, it will only work for the first container. We only have 10 minutes left. So let's speed up. Now we have an if. We inspect the CNI command. If it's add, it means we are adding a container. So the next step should be to create our namespace. You can create a network namespace by creating a link to the namespace. The namespace is given to us in the CNI netNS variable. And we create it in var run netNS. And we already have a name for it. That's the NSName variable. Now, usually we won't have the directory by default. So let's just create it too. And this should create our namespace. The next step would be to create our vesper. We can do this by IP link add ves. We'll name the end of the vesper that we'll put into our root namespace and the other end ves NS. Type ves peer name NS. If you are not familiar with that, it's IP link add. The name of one end of the vesper, we indicate the type and we say that the peer name, the other end, should be named ves NS. And now we'll handle ves root. What we want to do, we want to add it to our bridge. IP link set ves root master. And we have our bridge name in the bridge variable. What we want to do next, we want to set it to up. And let's just set the bridge to up too. I can't remember if I did it bridge to up. Next step, we have to take care of the other end of the vesper. We'll have to move it to the namespace. So we do that by saying IP link set the device name net NS and we have our namespace NS name. Let me just check if this is how we named the variable. Yes. Oh, yeah, I wanted to... You can get some of this work even so you are a CNI maintainer. I wanted to write what we are doing. So move ves NS to namespace. Now we want to rename it. We want to rename it to whatever the container runtime is expecting. So we'll do that by saying first we have to set the device to down. IP link set ves NS down. Note that we have already moved the interface to our namespace. So we cannot just do IP link set because that does things in our root namespace. We have to tell the IP tool that we are using a different namespace. And we do that by IP net NS with the namespace name. So once it's down we can rename it. Of course, we have to do it in the namespace again. We have to set the name. And we rename it to CNI ISName. And we want to bring it up again. We have to use the new name now. Now IPAM. Usually IPAM is more complicated than this. And let's add some routing. And we have the gateway variable. We are almost done. Now we have to return the result back to our container runtime. To do this we'll need two more values. We'll need to retrieve the MAC of the added interface and the interface index. I pre-populated that to avoid typos. I couldn't arrange enough swag in the project. This is the template for our output. We have the CNI version. We have the interfaces. We only add one. So we have the name, MAC and Sandbox. Sandbox is just the network namespace. So it's a repetition of the CNI net NS variable which we have received. IPs. Interface is the interface index. Note that this is an integer value. So no quotes here. It's a little bit misleading but our maintainer can answer the questions about this after the session. So we use print just to populate the values. This is the interface name, the MAC network namespace. I'm trying to speed up a little bit because we are running out of time. We return the result by just echoing it to standard output. And we are almost done. Now, because you haven't spotted all the typos, because it was so quiet, what we might want to do, we want to add some logging. So we'll just redirect our error stream to a file, not to devs. We also might want to echo the action which the CNI wants us to perform. And because it will be useful later on, I will also echo the output. Below we can find some actions that we will do during a delete. We just want to clean up the namespaces and the Vester. Let's just save it. And let's try to run it. Before we run it, we need to swap the configuration files. So let's just move this one to a different name. So alphabetically, ours is first. So let's start a pod. I'll create a Kubernetes deployment with a local image. I mean local, from a local repository. And the moment of truth, get pod. And it says container creating. So we probably made a typo. Excuse me. We already had that. I had a shabang. Faro log. And it says network is unreachable. Timor, let me just see if the... Let's delete the deployment. Clean is just a script that I wrote to clean the environment. So that we start off with a clean one. And time's up. Let's just... And it's running. No, not yet. Please give me one more minute. Let's just try if we can ping it. We are offline, so it's... Yes. If we have...