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 containerd you can use the ctr tool to quickly test features and functionality without writing extra code
  • If you want to integrate containerd into your project, you can use a simple client package. In this guide, we will pull and run a Redis server with containerd using 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

results matching ""

    No results matching ""