对于用惯了docker cli的用户来说,containerd的命令行工具ctr使用起来不是很顺手,此时别慌,还有另外一个命令行工具项目nerdctl可供我们选择。 nerdctl是一个与docker cli风格兼容的containerd的cli工具。 nerdctl已经作为子项目加入了containerd项目,它的github地址是https://github.com/containerd/nerdctl,而且从最近的nerdctl 0.8开始,nerdctl直接兼容了docker compose的语法(不包含swarm), 这很大提高了直接将containerd作为本地开发、测试和单机容器部署使用的体验。本来k8s后续将不再支持dockershim,docker在k8s社区的地位急剧下降,现在单机直接使用containerd易用性也不断被完善,也许docker的辉煌已经远去了。

实际上nerdctl compose实现的是Compose Specification规范, 这个规范是从自Docker Compose file version 3 specification规范发展而来的。

1.安装nerdctl

containerd 1.5最近刚刚发布,这里先把测试机上的containerd升级到1.5,直接替换containerd的二进制文件,重启containerd的systemd服务即可,升级过程略过。 升级完成后查看客户端和服务端版本均已经是1.5.0:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
ctr version
Client:
  Version:  v1.5.0
  Revision: 8c906ff108ac28da23f69cc7b74f8e7a470d1df0
  Go version: go1.16.3

Server:
  Version:  v1.5.0
  Revision: 8c906ff108ac28da23f69cc7b74f8e7a470d1df0
  UUID: dee82270-b4b4-429c-befa-45df1421da7e

注意在安装nerdctl之前,需要确认安装了它的依赖组件(runc、containerd、cni),containerd和cni的详细安装步骤可以参考本系列文章第2节和第3节中的内容。

安装nerdctl:

1
2
3
wget https://github.com/containerd/nerdctl/releases/download/v0.8.1/nerdctl-0.8.1-linux-amd64.tar.gz
tar -zxvf nerdctl-0.8.1-linux-amd64.tar.gz nerdctl && mv nerdctl /usr/local/containerd/bin/
ln -s /usr/local/containerd/bin/nerdctl /usr/local/bin/nerdctl

打印一下版本信息:

1
2
3
4
5
6
7
8
9
nerdctl version
Client:
 Version:       v0.8.1
 Git commit:    e1601447477c38ceb46c9c88418af399f79b1d6a

Server:
 containerd:
  Version:      v1.5.0
  Revision:     8c906ff108ac28da23f69cc7b74f8e7a470d1df0

2.nerdctl初体验

查看镜像:

1
2
3
nerdctl images
REPOSITORY    TAG           IMAGE ID        CREATED        SIZE
redis         alpine3.13    f9577ac6e68c    11 days ago    10.4 MiB

启动一个redis容器:

1
2
3
4
5
nerdctl run -d --name redis redis:alpine3.13

nerdctl ps
CONTAINER ID    IMAGE                                 COMMAND                   CREATED           STATUS    PORTS    NAMES
709665b06ab1    docker.io/library/redis:alpine3.13    "docker-entrypoint.s…"    28 seconds ago    Up                 redis

进入容器内部:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
nerdctl exec -it redis sh
/data # ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
3: eth0@if19: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
    link/ether 1a:84:4b:bb:1f:43 brd ff:ff:ff:ff:ff:ff
    inet 10.4.0.4/24 brd 10.4.0.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::1884:4bff:febb:1f43/64 scope link tentative dadfailed
       valid_lft forever preferred_lft forever

查看宿主机上的网桥和veth对设备:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
ls /var/lib/cni/networks/bridge/
10.4.0.4  last_reserved_ip.0  lock

ip addr
......
16: nerdctl0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP qlen 1000
    link/ether 3e:92:4a:37:cb:d3 brd ff:ff:ff:ff:ff:ff
    inet 10.4.0.1/24 brd 10.4.0.255 scope global nerdctl0
       valid_lft forever preferred_lft forever
    inet6 fe80::3c92:4aff:fe37:cbd3/64 scope link
       valid_lft forever preferred_lft forever
19: veth98611773@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master nerdctl0 state UP
    link/ether 2e💿71:38:3d:7b brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::2ccd:71ff:fe38:3d7b/64 scope link
       valid_lft forever preferred_lft forever

和docker一样,nerdctl也有一个子命令network:

1
2
3
4
5
nerdctl network ls
NETWORK ID    NAME         FILE
0             bridge
              host
              none

可以看一下默认的bridge网络的cni配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
[
    {
        "CNI": {
            "cniVersion": "0.4.0",
            "name": "bridge",
            "nerdctlID": 0,
            "plugins": [
                {
                    "type": "bridge",
                    "bridge": "nerdctl0",
                    "isGateway": true,
                    "ipMasq": true,
                    "hairpinMode": true,
                    "ipam": {
                        "type": "host-local",
                        "routes": [
                            {
                                "dst": "0.0.0.0/0"
                            }
                        ],
                        "ranges": [
                            [
                                {
                                    "subnet": "10.4.0.0/24",
                                    "gateway": "10.4.0.1"
                                }
                            ]
                        ]
                    }
                },
                {
                    "type": "portmap",
                    "capabilities": {
                        "portMappings": true
                    }
                },
                {
                    "type": "firewall"
                },
                {
                    "type": "tuning"
                }
            ]
        },
        "NerdctlID": 0
    }
]

可以看出nertctl启动的容器的网络的背后实际上仍然是CNI在工作。 nerctl run命令在启动一个容器时,可以通过--net指定容器连接到一个网络,取值可以是bridge、host、或none,默认是bridge。所以宿主机上出现了nerdctl0网桥和eth98611773@if3。

3.nerdctl兼容docker compose

这里编写一个用于测试的docker-compose.yml文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
version: '3'
services:
  db:
    image: postgres:10.1
    environment:
      - POSTGRES_USER=miniflux
      - POSTGRES_PASSWORD=secret
    volumes:
      - /home/miniflux/miniflux-db:/var/lib/postgresql/data
  db-migrate:
    image: miniflux/miniflux:2.0.29
    command: ["/usr/bin/miniflux", "-migrate"]
    depends_on:
      - db
    environment:
      - DATABASE_URL=postgres://miniflux:secret@db/miniflux?sslmode=disable
  miniflux:
    image: miniflux/miniflux:2.0.29
    ports:
      - "8080:8080"
    depends_on:
      - db-migrate
    environment:
      - DATABASE_URL=postgres://miniflux:secret@db/miniflux?sslmode=disable

miniflux是一个开源的RSS阅读器,主要有两个服务组成miniflux web服务和postgres数据库,可以部署Miniflux作为我们自己的RSS服务。 上面的docker compose文件之前已经使用docker-compose在docker上部署过,这里我们实验一下nerdctl在containerd上部署。

在宿主机上创建挂载出来的数据目录:

1
2
3
4
cd /home
mkdir miniflux
cd miniflux
mkdir miniflux-db

使用nerdctl执行compose命令:

1
2
3
4
5
6
nerdctl compose -f docker-compose.yml up -d
INFO[0000] Creating network miniflux_default
INFO[0000] Ensuring image postgres:10.1
INFO[0000] Ensuring image miniflux/miniflux:latest
INFO[0000] Creating container miniflux_miniflux_1
INFO[0000] Creating container miniflux_db_1
1
2
3
4
5
nerdctl ps -a
CONTAINER ID    IMAGE                                 COMMAND                   CREATED          STATUS                      PORTS                     NAMES
219d71b12daa    docker.io/miniflux/miniflux:2.0.29    "/usr/bin/miniflux"       6 minutes ago    Up                          0.0.0.0:8080->8080/tcp    miniflux_miniflux_1
3b5275fdb361    docker.io/miniflux/miniflux:2.0.29    "/usr/bin/miniflux -…"    6 minutes ago    Exited (1) 6 minutes ago                              miniflux_db-migrate_1
540239bdd876    docker.io/library/postgres:10.1       "docker-entrypoint.s…"    6 minutes ago    Up                                                    miniflux_db_1

注:实际使用nerdctl compose启动容器时,因为postgres启动比较慢,而compose里的depends_on只是描述container启动后就认为依赖OK了,所以可能会出现miniflux_db-migrate_1和miniflux_miniflux_1无法启动的情况。 此时,手动按顺序执行nerdctl start miniflux_db-migrate_1 && nerdctl logs miniflux_db-migrate_1nerdctl start miniflux_miniflux_1 && nerdctl logs miniflux_miniflux_1最终确保miniflux_miniflux_1和miniflux_db_1都进入up状态即可。

创建admin用户:

1
2
3
nerdctl exec -it <container-name> /usr/bin/miniflux -create-admin
Enter Username: admin
Enter Password:

部署成功后可以使用http://ip:8080访问。

总结

可以看出nerdctl的使用方式和docker cli几乎一致,在启动容器和管理容器方面,从docker过渡到使用nerdctl+containerd几乎没有任何学习成本。 有了nerdctl,单机玩耍containerd不再是梦。

参考