 All right, I think it's time for us to get started. Thanks for all of you that walked all the way here. I know it's a pretty significant walk, so it's nice that with all your other options, you decided to come here about container D. I'm going to try and keep the slide portion to a minimum. If you are really interested in a lot more detail on container D architecture and design, I'm going to briefly kind of overview that. But I'm going to really focus more on the client API. Actually, we're going to build a very simple client together, and then I'm going to cheat and jump to a more complex client that I'm not going to write live because we don't have a lot of time. We'll demonstrate some of those capabilities. That's going to be the focus. Again, if you want to hear more about architecture and design, Steven Day, who's here and myself, have given two or three talks over the last few months. At least the Moby Summit talk is online, so you can find that and kind of get a deeper dive into architecture. But again, I'm one of the maintainers. We have a couple more maintainers here. We've got maintainers from the CRI Container D project here. So if everything goes horribly bad up here, we can just chat around about Container D together. But hopefully you'll get something out of how you can actually use Container D in your project for your use case. So why did Container D come about? Michelle covered a very brief highlight on that earlier in the keynotes. Again, it came out of this desire across the ecosystem for this boring, stable infrastructure idea. The Docker engine obviously grew with a lot of features and capabilities. That wasn't as interesting to the entire ecosystem. And so you saw Lib Container, which was originally part of the Docker engine, got spun out. That's what created the OCI RunC and that work. And then Container D was a lightweight management layer above that that became, as of last December, a much more ambitious project. So last December, it was announced that Docker would be spinning out Container D as a separate project, contributing that somewhere. In March, that somewhere was announced to be the CNCF at the Berlin Kubecon. So again, the intent was here, like Craig said in his tweet, let's create some boring core infrastructure that everyone can use, usable by any higher layer container native orchestration piece. And then more recently, along with that year ago promise, was that the governance model would move away from BDFL. And so as of a few weeks ago, the Moby Technical Steering Committee has been formed. I'm part of that. If you want, again, more information on that, I'm in the middle of writing a blog post about that that should be out soon. But again, these are all the pieces. Boring core container infrastructure, governance that's open, contributed to CNCF, because that's really why we have Container D today. Who's using it today? There are a few core use cases already using it actively. We've talked to a lot of people who are earlier on, which I'm not going to talk about here. But hopefully, we'll see this list grow. Obviously, a very important use case is CRI Container D, implementing that container runtime interface. So Kubernetes can use Container D as its kubelet runtime. LinuxKit, the project out of the Moby project for a minimal secure OS image assembler, uses Container D as its core runtime. And again, higher layers can use that. So if you build LinuxKit, build a Kubernetes assembly, it will actually use Container D as the runtime. BuildKit is a new project. There's a great blog post from Tonnes, one of the Docker engineers, who assembled BuildKit. Again, a container image construction builder that's separate from Docker and can use Container D directly. And then obviously, the Docker engine itself, as Container D has moved from that earlier version into the full 1.0 version, you'll see 17.12 if you're talking Docker CE versions. And above, we'll be using this Container 1.0 code base. So that's really where you see Container D used today. As I said, I'm not going to try and unpack this whole architecture. There's a couple of things that are important to say here. One is that the Container D architecture really applies a lot of the learnings from the history of the Docker engine, all the OCI work. So a lot of the concepts used in Container D are either, hey, this didn't work so well in the early versions of the Docker engine. Let's try and do that better. So the Snapshotter model is very powerful. The graph drivers were very tangled in with the rest of the engine. The Snapshotter design is much clearer. Again, if you want more on that, we can provide more resources. And then using known good technology, again, using the OCI runtime underneath, using GRPC, exporting metrics with Prometheus, and not opinionated toward a specific platform. So Docker engine can use it, and that it's opinionated capabilities on top. Kubernetes can use it. But again, I'm going to skip over a lot of the detail here. And we're going to jump in to actually using the API, which will bring out some more of these components in a more hands-on fashion. The API, I think, has been kind of one of the shining examples of what Container D has done to apply some of the learnings of the earlier runtimes. One was to have an API that was simple and clear, had stability guarantees. Again, using GRPC and Protobuf versioning, we can provide that now. So Container D10 is out. So now the API is versioned. You can guarantee to use that API. We have CI tests that make sure it doesn't break. And it's also the API uses a pass-through model. So we're not trying to abstract the whole OCI JSON configuration. So if it changes, then we have to change. So it gives our API more of just that lightweight metadata and passing through the OCI config to the OCI runtime. And then, of course, we have the Godoc published, which gives you a nice way to learn about the API, and we'll provide those links as well. And a couple of early responses were just, this is a great API. Love it. Clean. And that's important for what we're trying to accomplish. So we are doing fairly well on time. What I'm going to do is switch to an editor that has a skeleton of what I want to do. You can go to this GitHub repository, example CTR, under my GitHub ID. You can see the finished example there. But the part we're going to code is just quite simple. We're going to connect to the daemon. We're going to pull an image, create a container, and start the task. I'm also going to set a namespace. Again, one of the nice features of container D is namespacing, similar to the Kubernetes model of when I set my namespace, now I only see resources associated with that namespace. So let's try this out. So here, is the font going to be OK for folks in the back? All right. If not, there are some seats further forward. What I'm going to do, first of all, is we're going to have to connect to the Unix socket where container D is listening. So I'm just going to set that as a console. Run container D, container D.soc is going to be my default address. I'm going to set a default image that I'm going to pull and run. Again, container D is not opinionated, so it's not going to add in Docker Hub or the tag. So I have to provide all of that. So I'm going to talk to Docker Hub, one of the library images. I'm going to run the Redis Alpine tag. So those are just some defaults that will help me out. To connect to the daemon, very simply, I provided the address. So that's the address I'm going to connect to. That's going to return to me a client. And he wants to see me put an error handling on every single call. That'll be fun, right? Yeah, exactly. If you're coding go, it's painful to skip this. So we'll do that. I couldn't connect, and here's my error, and we'll go away. So that's all we had to do to connect to container D, fairly straightforward. I now want to set a namespace. I need another import for that from the namespaces package. And so namespaces with namespace, we'll do context background. And let's just call it example CTR. And that will return the context that I'm now going to use for all the rest of the API calls. So at this point, I want to pull this image that I've already talked about. One of the things I'll do with my client is I'm first going to say, get image with this context, the name of the image, the reference to it. Because it's possible in this namespace, I've already pulled the image. So this obviously won't talk to Docker Hub and pull the layers and manifest. I'm going to cheat a little bit and just assume that if I get an error, it means it's not pulled. So I'll actually do pull image. Again, fairly straightforward. I'm going to pull that image. And I'm going to provide a with pull unpack, which means that container D, the snapshotter, is going to unpack these layers of my image. If I get an error at this point, we're probably going to have to just get out of here because that means I couldn't even get the image, couldn't pull image, and the error. So at this point, I've connected to my client. I've set my namespace. I've pulled an image. The next thing I want to do is create a container. And this, again, is a fairly straightforward context, kind of a container ID. I'll call it example CTR. Now the interesting thing is I have a set of, if you look in the Godoc, you'll find a bunch of with helpers, container D with. And one of the things we're going to want is a snapshot, I'm sorry, with new snapshot. So that means that now my image is unpacked in the snapshot driver, in this case the overlay. So if you use Docker, graph driver, the overlay to driver, that's your default in container D. So now I'm saying my root file system, I want that to be based on a new snapshot that I'm going to call example CTR. And the parent will be this image that I've just pulled. The next thing I need to do, so if you've looked at run C or the OCI runtime spec, you know that to run a container I need two pieces of information I've now provided kind of this root file system with the snapshotter. I also need a OCI runtime config. That's the JSON file that has what program's going to be run, what namespaces, Linux kernel namespaces I'm going to isolate in. All those settings are within the OCI config. That's bound up in the image, so there's a nice helper that I can use to, I'm sorry, I actually need the OCI package here, because we're going to basically say OCI with image config and not make any modifications to it. And so we'll just be, this Redis Alpine image already says its configuration is in its image. So let's do with, actually I need to tell container D, I want a new spec, and here's where I'm using the OCI with image config and the image. Who thinks that's right? Oh, yep, thank you. Yeah, see this is fun, isn't it? If you want to see this done well, look at any Liz Rice talk on YouTube. This is my Liz Rice impersonation talk. All right, so new container, with new snapshot, with new spec, OCI, with image config, and what do I still have wrong? Yep. OK, well that was a little more fun. What, and the world just happened there. I think I did a, yeah, there we go. So we have a container, that was a little more exciting. But at this point, obviously we don't have a task, we don't have a running container, this is just the metadata is now set up. Let's make sure we have a reasonable container object before we go on. All right, so at this point we have the container. At this point we are going to use that container to create a task, and this is fairly straightforward. I'm going to create it so I don't care about the output. So again, we need a new package, CIO, it's not about the person in your company, the chief information officer, it's about container D's IO. And so when I create this task, I'm basically saying, I don't care about standard in, standard error, standard out, just get rid of it for me, pipe it to DevNol. This will create a task object. And couldn't create task. So we have one step left. We are actually going to start this task, and this doesn't require anything other than the context. And we're just going to check and make sure that it actually started. And couldn't start task. And actually we should really delete the task, and we should really delete the container at this point. That makes it easier. We can obviously go, this one I always forget, with snapshot cleanup. Yes. So that means that that root file system I assembled will actually get cleaned up and deleted, so I don't have to use the snapshot tooling to go do that. And I really should have done this as well here, just to make it cleaner. All right, so in 60 lines of code, we've connected to container D, we've set up a namespace, pulled an image, created a container, created a task, and started it. Who believes this will actually run? Any votes? All right, so I call this file skeleton.go. I've already rendered in everything into this project. Again, you can see this on GitHub under example CTR.go. I didn't want to check in the blank skeleton, so I just created that for this. All right, that's built. You can see, I already have container D running here. This is version 1.0 that we just released a few days ago. I'm pseudoing because I need to talk to container D socket, demon socket, which is protected by root. All right, obviously we didn't output anything because we know all the information, but we should have a started container. Sure enough, example CTR. If you wonder why I didn't have to say anything about the namespace, I've already put that in a variable in my environment, so container D underscore namespace. I didn't have to type, I could have typed dash namespace, example CTR, and seen the same container. It's running the Redis Alpine image. If we look at tasks, we should actually see that there's one running. If we PS on the system, you'll see that Redis server is actually running. Okay, so we wrote the container D simple client. It actually worked. It started a container. Hopefully you see again just the simplicity of the API and how simple it was to assemble that quickly. I'm going to stop the task with the, again, CTR is a simple command line tool. It's not really something we officially see as part of the container D promises about stability, but obviously for administrative tasks, it's quite easy to use. So again, I can stop my Redis server. Sorry, I can kill my Redis server task and I can remove that container and you can see that it's gone. Let's go back to the slides for just a second because I have an advanced example that as I mentioned, I'm not going to have time to add these other API calls, so I've done it for you. We'll look at the code and then we'll run it, but we added some more concepts to the container D client. If you know that I've worked on username spaces and various container runtime contexts, you're not going to get out of here without a username space example. So I've added that to this example. I've also added a with helper in my own code. So again, we used some with helpers that container D provided us. Now I'm going to add my own with helper to do a bind mount for my host and then we can also do things like an optional custom command to override the image config. So if you do Docker run in some image and a command, it runs that command instead of the default one in the image, so we've added that to this example. So let's go back to our editor. Let's look at advanced. I'll try and skip over. This is the ugliness that gets produced when you don't use a flag parsing library. I thought it would be easy and then it got really ugly, so don't follow this example. Use a nice 2E library for that. What I've done is I've basically said when you call my example CTR, now you're gonna provide a username. I'm gonna look up to see if that username has any sub UIDs and GIDs, so if you know anything about username spaces, that'll give me some ranges that I can then apply to the container config and then obviously I've let you specify an image and a command. You can see here I'm using a package that I just copied over from the Moby project that I wrote when I originally did user namespaces, so the ID tools, new ID mappings just does that look into sub UID and sub GID and gives me those mappings. A bunch of this is the same, obviously, connecting, setting my namespace. I've created a small object so I can have this runContainer function and not have to pass around all the client and context. But you can see the same things are happening. Getting an image, pulling an image, creating a container, creating a task. Creating a task got a little more interesting because now I actually want to modify the OCI config to add in the username space configuration. Actually, in this case, sorry, the task, I need to tell RunC, by the way, don't make the IOs owned by Root or else my username space process won't be able to read that. We'll look at new container, which actually does the other thing I was about to mention. But once we've done that, all we're doing is creating the task and checking for exit status because now if you give me a custom command, I want to know that it actually ran and give you the output. So again, this client is a little more involved. Here's the part I meant to talk about. So here, from line 175 to and below, I'm actually saying, did I find some ID mappings for this user? Okay, if so, let me grab those maps and pass it into the OCI helper with username space. And so again, at this point, I'm modifying that image's default OCI config to add the username space options. And then again, we pass that spec on and actually create the container the same way we did before. All right, so hopefully that wasn't too much of a whirlwind tour through that. Again, this is all in that GitHub project. So you can look at this at your own leisure. I've already built this example CTR2. I've called it example CTR2 here. Again, I need to pseudo that. S2P is my local user. I know I have UID maps already on the system for that. I can provide, let's just run alpine latest. And we can do, actually, let's run it like this first because then you can actually, we can verify that it's running in the username space. So let's try this out. Oh yeah, alpine latest isn't pulled. So it's actually pulling alpine latest over conference Wi-Fi, which shouldn't take too long. Yes, I am a gambler. Images LS, it looks like it's there. Oh, you know what? Don't pay attention to any of this. Yes, you'll find out in a minute why I had to do this, but this container D path was modified. Because in a minute, I'm gonna run this inside Linux kit. Hopefully we're gonna have time to wrap that up. And I had to run a separate container D because we're working on a bug with username spaces inside Linux kit. So I had to point it at a different. So let's try this again. All right, so now we have a running container. If I look at the task, there's its PID. If I grep 31698 out of AUX, you can see that UID 100,000 is running the shell in this alpine container. Okay, so simple enough, we now ran a container with username spaces using my advanced quote unquote advanced client. And that was easy enough. Let's actually get rid of this task and delete the container. As you can see, I also added something to the ID so I can run more than one container and not clash on the name. That's just the PID of the example CTR. So the other thing I talked about, let's look back at the editor for a second. I have a Utils program. Utils, it has a couple more tools. You could build out your client a little more with stop, delete. So again, you don't have to use the CTR tool for that. But here I've added with mounts. And the way I've done this just for demo purposes is actually look for a file in the local directory called mounts. I expect that inside it I'll have a target, the type amount like bind and the destination. And then I'll basically modify the OCI spec. So you can see at the end here, I'm appending a new mount to the specs mount. So this is my own with mounts helper that I've created. So now my client has the ability to do mounts. And let's just, actually I think I already had a mounts file here in the local directory. I'm gonna mount Etsy host, bind mount that. Let's do something more interesting. Let's just mount that over home in Alpine so you can make sure that it works. And so at this point, if I do ls home with this mounts file. Oh yeah, so s to p was actually mounted in there. Let's do home ssp. And there's a couple files that are readable to the user namespace. So again, it's not showing a ton of content in there but a file named Fred was located in that folder. But the problem is when I'm doing this, one of the trickiest problems with user namespaces is that because my process is running as an unprivileged user, usually the volume I'm mounting doesn't have the right permissions. And now if I run multiple containers with different mappings, I can't actually come up with privileges and permissions that make sense. And that's pretty frustrating to a lot of user namespace interested parties who want to use it in a broader way. And so what I'd like to kind of finish with is that Linux kit is an interesting way to try out new features in the Linux kernel. That's one use case of many. And so there is a shift FS patch set that is attempting to solve this problem of mounts inside a user namespace. And so what I've done is I've already I've already created a Linux kit assembly. Again, if you look in my GitHub project, I don't have time at this point to go through that. You'll see the Linux kit directory and you'll see a YAML file in there called shift FS. And that's what's being used to build this Linux kit image. There's also scripts to build that there. Here you can see I'm running Linux kit. As I said, I'm already running my own container D here. That example CTR is using. So if I run example CTR 2 here, if you look in that YAML file, I created two users, Bob and Alice. They have different UID mappings. So if I run a container for Bob, we should see a container was created. And if we look at that one, PID 1094, it's running as UID 100,000. If we look at sub UID, we can see that's Bob's range. If I go over here and run another one with Alice, and I now have this other container running with PID 1170, you can see that's running with UID 200,000. Now, what if I mount the same volume into each of their containers, or bind mount a directory from the host, they're not gonna have permissions to access that. I was gonna show both modes where I could have access and where I could, but we're running out of time pretty quickly. So let me kill these tasks. If you'll give me a minute to type. And just remove the containers just to make the output clear when we start the other ones. Linux kid isn't being friendly to my output here, but that's fine. So at this point, I should have no containers running. As I said, the example CTR 2 looks for a mounts file. So if I do mounts, shift FS and copy that here to where I'm gonna run it. And we look at this file, you can see it's gonna take home from var external volume. Again, this is inside my Linux kit VM. Use the shift FS driver that I built into Linux kit and mount that on home in the container. So now if we start Bob's container, and we start Alice's container from here, we have both of them running. I've got a simple script so I don't have to type the whole command, but if I go, if I exact into those containers, so I'm gonna go into, I think this is Bob's the first one I started. And I look at home, it's empty right now, but if I touch a file, I can actually, I can do that. And it actually looks like it's owned by root here. We'll go, obviously we can go out into var external home. Sorry, external volume. And we can see that that was actually created with UID 100,000, 100,000, which is Bob's ID within the container. Obviously we can do the same thing in Alice's container. Let's look 1404. As I'm running out of time, my typing is getting worse and worse. So we can actually see Fred, but he doesn't match any ID in our container, so he's assigned the nobody user. I'm gonna touch Alice in here. Was I in home? Let's do that again. And if we go back to var external volume, we can see that that was created with 200,000, 200,000. So again, I've had to rush through this part because we're basically out of time. But again, Shift FS is giving us a way to do that without being blocked by the permissions. Shift FS needs more work upstream, but Linus Kit gives us an easy way to play with those features. And with my container decline, I can actually try out those features. So a few final slides. There are some resources here. I'll post these slides as soon as we're out of this talk. If you wanna see more interesting uses of the API beyond kind of what I've just showed you, look at CRI Container D. You can look at my Bucket Bench project which has a Container D driver using the same API. You can look at Lib Container D in the MOBI project. These are all users of the API. Obviously above here, the Getting Started Guide and the Godoc are also great resources. So with that, I think I am more than over with time. So thank you very much for your attention and time. Thanks.