如何使用containerd

containerd简介

Containerd是一个工业标准的容器运行时,重点是它简洁,健壮,便携,在Linux和window上可以作为一个守护进程运行,它可以管理主机系统上容器的完整的生命周期:镜像传输和存储,容器的执行和监控,低级别的存储和网络。
containerd和docker不同,containerd重点是继承在大规模的系统中,例如kubernetes,而不是面向开发者,让开发者使用,更多的是容器运行时的概念,承载容器运行。

如何使用containerd

  • 通过ctr(官方提供的client);
  • 自己写一个client,官方提供了一个client package供开发者调用来写自己的client。
    注:需要安装对应版本的runc和golang版本1.9x。

安装containerd

我们以v1.2.7版本为例

wget https://github.com/containerd/containerd/archive/v1.2.7.zip
unzip v1.2.7.zip

解压后会生成一个containerd-1.2.7包,即containerd的代码包,你可以看到containerd.serviceservice配置文件,如果你使用systemd,可以使用它。
containerd守护进程有一个配置文件在/etc/containerd/config.toml目录下,用户可以根据自己的需要配置各项参数,如下例子:

root = "/var/lib/containerd"
state = "/run/containerd"
oom_score = 0

[grpc]
  address = "/run/containerd/containerd.sock"
  uid = 0
  gid = 0
  max_recv_message_size = 16777216
  max_send_message_size = 16777216

[debug]
  address = ""
  uid = 0
  gid = 0
  level = ""

[metrics]
  address = ""
  grpc_histogram = false

[cgroup]
  path = ""

[plugins]
  [plugins.cgroups]
    no_prometheus = false
  [plugins.cri]
    stream_server_address = ""
    stream_server_port = "10010"
    enable_selinux = false
    sandbox_image = "hyc-cloud-private-integration-docker-local.artifactory.swg-devops.com/ibmcom/pause:3.1"
    stats_collect_period = 10
    systemd_cgroup = false
    [plugins.cri.containerd]
      snapshotter = "overlayfs"
      [plugins.cri.containerd.default_runtime]
        runtime_type = "io.containerd.runtime.v1.linux"
        container_runtime = ""
        runtime_root = ""
      [plugins.cri.containerd.untrusted_workload_runtime]
        runtime_type = ""
        container_runtime = ""
        runtime_root = ""
    [plugins.cri.cni]
      bin_dir = "/opt/cni/bin"
      conf_dir = "/etc/cni/net.d"
    [plugins.cri.registry]
      [plugins.cri.registry.mirrors]
        [plugins.cri.registry.mirrors."docker.io"]
          endpoint = ["https://registry-1.docker.io"]
  [plugins.diff-service]
    default = ["walking"]
  [plugins.linux]
    shim = "containerd-shim"
    runtime = "runc"
    runtime_root = ""
    no_shim = false
    shim_debug = false
  [plugins.scheduler]
    pause_threshold = 0.02
    deletion_threshold = 0
    mutation_threshold = 100
    schedule_delay = "0s"
    startup_delay = "100ms"

可以通过containerd config default > /etc/containerd/config.toml命令生成config.toml配置。

使用containerd

我们自己写一个main.go来调用containerd:

package main
import (
    "log"

    "github.com/containerd/containerd"
)
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()
    return nil
}

通过默认的containerd.sock创建一个client,因为我们正在使用GRPC上的守护进程,所以我们需要创建一个用于调用客户端方法的上下文,对于调用containerd的API来说,containerd也是分namespace的,创建上下文后,我们也应该创建一个namespace。

ctx := namespaces.WithNamespace(context.Background(), "example")

Pull 镜像

创建了client,我们可以使用client来pull镜像啦。

image, err := client.Pull(ctx, "docker.io/library/redis:alpine", containerd.WithPullUnpack)
if err != nil {
    return err
}

containerd client在很多函数调用中使用golang中的Opts 模式。在这个函数中我们传入了 containerd.WithPullUnpack参数,这个参数的作用是,我们在pull image的同时,我们不仅将镜像内容提取并下载到containerd的content存储库中,还将其解压到一个snapshotter中以用作根文件系统。
让我们把代码集合到一起,运行一个完整的例子:
首先将containerd代码包放在gopath目录中,我的路径是 /root/gopath/src/github.com/containerd/containerd

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
}
root@winking1:~/gopath# go build main.go
root@winking1:~/gopath# ls
bin  main  main.go  pkg  src
root@winking1:~/gopath# ./main
2019/06/18 22:53:58 Successfully pulled docker.io/library/redis:alpine image

创建一个 OCI Spec 和 Container

前面我们已经pull了镜像,我们需要生成一个OCI 运行时规范,容器可以基于新容器。containerd为生成OCI运行时规范提供了合理的默认值。
如果你已经有一个OCI规范,可以直接使用containerd.WithSpec(spec)设置。
当为容器创建一个新snapshot的时候,我们需要提供一个snapshot ID和容器基于的镜像,通过提供一个单独的snapshot ID而不是容器的ID,我们可以很容易的重复利用已经存在的snapshot在创建不容的容器的时候。

    container, err := client.NewContainer(
        ctx,
        "redis-server",
        containerd.WithNewSnapshot("redis-server-snapshot", image),
        containerd.WithNewSpec(oci.WithImageConfig(image)),
    )
    if err != nil {
        return err
    }
    defer container.Delete(ctx, containerd.WithSnapshotCleanup)
root@winking1:~/gopath# ./main
2019/06/18 23:41:33 Successfully pulled docker.io/library/redis:alpine image
2019/06/18 23:41:33 Successfully created container with ID redis-server and snapshot with ID redis-server-snapshot

创建运行的task

Container和task的不同,容器是分配和附加资源的元数据对象,task是系统中动态的运行的的进程,task在每次运行后应该被删除掉,但是容器能够被多次使用,更新,查询。
我们创建的新的task的实际上是一个运行在系统中的进程,我们使用cio.WithStdio所以所有的来自容器中的IO是从main.go这个进程发送的,这是个cio.Opt配置Streams,通过NewCreator为这个新的task返回一个cio.IO。
如果你熟悉OCI运行时,如果当前有个任务处于“已创建”状态,这意味着命名空间,根文件系统和各种容器级别设置已经完成初始化,但用户定义的进程(在此示例中为“redis-server”)尚未启动。这使用户有机会去设置容器的网络或添加不同的工具来监视容器。 containerd也借此机会监控您的容器。此时会设置等待容器退出状态和cgroup指标等内容。

    task, err := container.NewTask(ctx, cio.NewCreator(cio.WithStdio))
    if err != nil {
        return err
    }
    defer task.Delete(ctx)

如果您熟悉prometheus,可以通过curl得到容器度量标准(在我们创建的config.toml中)以查看容器的度量准:

[metrics]
  address = "127.0.0.1:1338"
  grpc_histogram = false
curl 127.0.0.1:1338/v1/metrics

现在我们在创建状态下有一个任务,我们需要确保等待任务退出。必须等待任务完成,以便我们可以关闭我们的示例并清理我们创建的资源。你总是希望在调用任务开始之前确保等待。如果任务有一个像/ bin / true这样的简单程序在调用start后立即退出,这可以确保你不会遇到任何比赛。

    exitStatusC, err := task.Wait(ctx)
    if err != nil {
        return err
    }

    if err := task.Start(ctx); err != nil {
        return err
    }

Killing the task

    time.Sleep(3 * time.Second)

    if err := task.Kill(ctx, syscall.SIGTERM); err != nil {
        return err
    }

    status := <-exitStatusC
    code, exitedAt, err := status.Result()
    if err != nil {
        return err
    }
    fmt.Printf("redis-server exited with status: %d\n", code)

我们等待我们设置的退出状态通道以确保任务已完全退出并获得退出状态。如果您必须重新加载容器或错过等待任务,则删除也将在您最终删除任务时返回退出状态。我们让你满意。

status, err := task.Delete(ctx)

完整代码示例:

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
}
root@winking1:~/gopath# go build main.go
root@winking1:~/gopath# ./main
1:C 19 Jun 2019 07:35:53.211 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1:C 19 Jun 2019 07:35:53.211 # Redis version=5.0.5, bits=64, commit=00000000, modified=0, pid=1, just started
1:C 19 Jun 2019 07:35:53.211 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
1:M 19 Jun 2019 07:35:53.213 # You requested maxclients of 10000 requiring at least 10032 max file descriptors.
1:M 19 Jun 2019 07:35:53.213 # Server can't set maximum open files to 10032 because of OS error: Operation not permitted.
1:M 19 Jun 2019 07:35:53.213 # Current maximum open files is 1024. maxclients has been reduced to 992 to compensate for low ulimit. If you need higher maxclients increase 'ulimit -n'.
1:M 19 Jun 2019 07:35:53.214 * Running mode=standalone, port=6379.
1:M 19 Jun 2019 07:35:53.214 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
1:M 19 Jun 2019 07:35:53.214 # Server initialized
1:M 19 Jun 2019 07:35:53.214 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
1:M 19 Jun 2019 07:35:53.214 * Ready to accept connections
1:signal-handler (1560929756) Received SIGTERM scheduling shutdown...
1:M 19 Jun 2019 07:35:56.324 # User requested shutdown...
1:M 19 Jun 2019 07:35:56.325 * Saving the final RDB snapshot before exiting.
1:M 19 Jun 2019 07:35:56.330 * DB saved on disk
1:M 19 Jun 2019 07:35:56.331 # Redis is now ready to exit, bye bye...
redis-server exited with status: 0
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • 从Docker 1.11开始,Docker容器运行已经不是简单的通过Docker daemon来启动,而是集成了c...
    泛轻舟gen阅读 26,793评论 4 11
  • 前言 Docker已经推出了5年,在这5年中它极大的改变了互联网产品的架构,推进了新的产品开发、测试和运维方法。但...
    marshalzxy阅读 3,874评论 0 19
  • CRI - Container Runtime Interface(容器运行时接口) CRI中定义了容器和镜像的服...
    ywhu阅读 21,414评论 3 10
  • 容器技术近年来越来越火,作为云原生技术的最底层基石,要开发云原生应用,就有必要对于容器技术有一个更加深入的了解。容...
    暴走的初号机阅读 29,360评论 2 24
  • 与老师约了19号科目一考试。 希望这第一关梦顺利过。 大家都笑我:科目一就是理论,刷两遍题目就过了,你还真当回事,...
    冰水珊瑚阅读 220评论 1 1