containerd
An industry-standard container runtime with an emphasis on simplicity, robustness and portability
As of February 28, 2019, containerd is officially a graduated project within the Cloud Native Computing Foundation, following Kubernetes, Prometheus, Envoy, and CoreDNS.
containerd is available as a daemon for Linux and Windows. It manages the complete container lifecycle of its host system, from image transfer and storage to container execution and supervision to low-level storage to network attachments and beyond.
Getting started with containerd
There are many different ways to use containerd:
- If you are a developer working on
containerdyou can use thectrtool to quickly test features and functionality without writing extra code - If you want to integrate
containerdinto your project, you can use a simple client package. In this guide, we will pull and run a Redis server withcontainerdusing that client package.
We will assume that you are running a modern Linux host for this example with a compatible build of runc.
{
sudo apt update
sudo apt install runc -y
runc -v
}
runc version spec: 1.0.1-dev
Install the Go tools
{
wget https://www.dropbox.com/s/qjhohtzlzt3bb24/go1.13.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.13.linux-amd64.tar.gz
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
source ~/.bashrc
}
Test your installation
mkdir -p ~/go/src/hello
cd ~/go/src/hello
cat << EOF > hello.go
package main
import "fmt"
func main() {
fmt.Printf("hello, world\n")
}
EOF
go build
./hello
hello, world
Installing containerd
{
sudo apt update
# Install packages to allow apt to use a repository over HTTPS
sudo apt install apt-transport-https ca-certificates curl software-properties-common -y
# Add Docker’s official GPG key
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
# Add Docker apt repository.
sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
# Install containerd
sudo apt update
sudo apt install containerd.io -y
# Configure containerd
mkdir -p /etc/containerd
sudo sh -c "containerd config default > /etc/containerd/config.toml"
# Restart containerd
sudo systemctl restart containerd
}
Connecting to containerd
cat << EOF > main.go
package main
import (
"context"
"log"
"github.com/containerd/containerd"
"github.com/containerd/containerd/namespaces"
)
func main() {
if err := redisExample(); err != nil {
log.Fatal(err)
}
}
func redisExample() error {
client, err := containerd.New("/run/containerd/containerd.sock")
if err != nil {
return err
}
defer client.Close()
ctx := namespaces.WithNamespace(context.Background(), "example")
image, err := client.Pull(ctx, "docker.io/library/redis:alpine", containerd.WithPullUnpack)
if err != nil {
return err
}
log.Printf("Successfully pulled %s image\n", image.Name())
return nil
}
EOF
This code will pull the redis image based on alpine linux from Dockerhub and then print the name of the image on the console’s output.
{
sudo apt install gcc -y
go get github.com/containerd/containerd
go build main.go
sudo ./main
}
2019/09/20 12:43:56 Successfully pulled docker.io/library/redis:alpine image
Code Walk-through
Import the containerd root package that contains the client.
import (
"context"
"log"
"github.com/containerd/containerd"
"github.com/containerd/containerd/namespaces"
)
This will create a new client with the default containerd socket path.
client, err := containerd.New("/run/containerd/containerd.sock")
ctx := namespaces.WithNamespace(context.Background(), "example")
Because we are working with a daemon over GRPC we need to create a context for use with calls to client methods.containerd is also namespaced for callers of the API. We should also set a namespace for our guide after creating the context. Having a namespace for our usage ensures that containers, images, and other resources without containerd do not conflict with other users of a single daemon.
Now that we have a client to work with we need to pull an image. We can use the redis image based on Alpine Linux from the DockerHub.
image, err := client.Pull(ctx, "docker.io/library/redis:alpine", containerd.WithPullUnpack)
if err != nil {
return err
}
The containerd client uses the Opts pattern for many of the method calls. We use the containerd.WithPullUnpack so that we not only fetch and download the content into containerd’s content store but also unpack it into a snapshotter for use as a root filesystem.
Full Example
cat << EOF > main.go
package main
import (
"context"
"fmt"
"log"
"syscall"
"time"
"github.com/containerd/containerd"
"github.com/containerd/containerd/cio"
"github.com/containerd/containerd/oci"
"github.com/containerd/containerd/namespaces"
)
func main() {
if err := redisExample(); err != nil {
log.Fatal(err)
}
}
func redisExample() error {
// create a new client connected to the default socket path for containerd
client, err := containerd.New("/run/containerd/containerd.sock")
if err != nil {
return err
}
defer client.Close()
// create a new context with an "example" namespace
ctx := namespaces.WithNamespace(context.Background(), "example")
// pull the redis image from DockerHub
image, err := client.Pull(ctx, "docker.io/library/redis:alpine", containerd.WithPullUnpack)
if err != nil {
return err
}
// create a container
container, err := client.NewContainer(
ctx,
"redis-server",
containerd.WithImage(image),
containerd.WithNewSnapshot("redis-server-snapshot", image),
containerd.WithNewSpec(oci.WithImageConfig(image)),
)
if err != nil {
return err
}
defer container.Delete(ctx, containerd.WithSnapshotCleanup)
// create a task from the container
task, err := container.NewTask(ctx, cio.NewCreator(cio.WithStdio))
if err != nil {
return err
}
defer task.Delete(ctx)
// make sure we wait before calling start
exitStatusC, err := task.Wait(ctx)
if err != nil {
fmt.Println(err)
}
// call start on the task to execute the redis server
if err := task.Start(ctx); err != nil {
return err
}
// sleep for a lil bit to see the logs
time.Sleep(3 * time.Second)
// kill the process and get the exit status
if err := task.Kill(ctx, syscall.SIGTERM); err != nil {
return err
}
// wait for the process to fully exit and print out the exit status
status := <-exitStatusC
code, _, err := status.Result()
if err != nil {
return err
}
fmt.Printf("redis-server exited with status: %d\n", code)
return nil
}
EOF
{
go build main.go
sudo ./main
}
2019/09/20 12:53:19 failed to dial "/run/containerd/containerd.sock": context deadline exceeded