k8s缺省使用docker运行时,但是1.12.x开始支持使用containerd运行时,这样调用路径从原来的kubelet->dockershim->dockerd->containerd->runc-shim->runc 优化为kublet->cri->containerd->runc->shim->runc, 而且cri还是作为containerd的一个插件跑在一个进程空间,看起来是提高了性能,但是如果习惯于在节点用docker命令的话就不要切换了,或者只切换部分节点。
我就是只切换了一个节点作为实验,方法其实很简单,只是修改下kubelet的启动命令就好了,但是如果你是用的私有镜像仓库的话会碰到一些问题,这里我主要目的是记录下cri-containerd使用私有镜像仓库时碰到的问题。
修改kubelet的启动命令添加如下参数--container-runtime=remote --runtime-request-timeout=15m --container-runtime-endpoint=unix:///run/containerd/containerd.sock
systemctl daemon-reload
systemctl restart kubelet
注意kubelet是通过/run/containerd/containerd.sock
和containerd通信的,安装有docker(新一点的版本)的节点都会有个daemon进程,如果是全新没有安装docker的节点,只要先创建配置文件,然后手动或者以服务方式启动containerd进程即可。
containterd config default > /etc/containerd/config.toml,
/usr/bin/conainerd
正常情况下如果你只是使用官方镜像库,pod就可以调度到这个使用containerd的节点了,但是如开头说的,如果是私有仓库,还有几个问题要解决
现象是调度到该节点的pod都启动失败,kubectl describe pod/xxx
显示的出错信息server gave HTTP response to HTTPS client
, 我是用docker registry搭建的简易本地仓库,没有启用tls, 很明显cri-containerd是希望ssl加密传输的,不过containerd本身是支持走明文的,因为ctr --address=/run/containerd/containerd.sock images pull --plain-http --user admin:xxxxxx image-registry:5000/gcr.io/google_containers/pause-amd64:3.1
通过添加plain-http
参数可以成功,说明是cri不支持, 而且也没找到类似docker的insecure-registry配置.
只能通过重新配置registry解决:
先生成服务段证书docker-registry-server.pem, 再修改registry的启动命令, (注意保持原来的端口5000, 这样其他仍然采用docker运行时的节点不受影响)
docker run -d -p 5000:443 --restart=always --name image-registry \
-v /cloud/registry/auth/:/auth/ \
-e "REGISTRY_AUTH=htpasswd" \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
-e REGISTRY_STORAGE_DELETE_ENABLED=true \
-v /cloud/registry/:/var/lib/registry/ \
-v /cloud/registry/certs:/certs/ \
-e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/docker-registry-server.pem \
-e REGISTRY_HTTP_TLS_KEY=/certs/docker-registry-server-key.pem \
image-registry:5000/registry:latest
修改了containerd的配置文件指向新的registry端点https://image-registry:5000
后重启containerd, pod启动仍然失败,报错:
failed to pull image "image-registry:5000/gcr.io/google_containers/pause-amd64:3.1":
failed to resolve image "image-registry:5000/gcr.io/google_containers/pause-amd64:3.1":
failed to do request: Head https://172.16.137.128:5000/v2/gcr.io/google_containers/pause-amd64/manifests/3.1:
x509: certificate signed by unknown authority
因为用的是自己生成的自签名证书,所以报错了,同样containerd其实是支持这个特性的, ctr有个参数–skip-verify可以绕开这个问题ctr --address=/run/containerd/containerd.sock images pull --skip-verify --user admin:xxxxx image-registry:5000/gcr.io/google_containers/pause-amd64:3.1
,但是cri没有地方设置ca证书,也没有配置可以设置skip-verify, 这也太弱了吧,觉得不可能阿,google上看到文档里说可以配置:
[plugins.cri.registry.tls_configs]
[plugins.cri.registry.tls_configs."my.custom.registry"]
ca_file = "ca.pem"
cert_file = "cert.pem"
key_file = "key.pem"
按照这个修改配置后重启仍然不行,检查下来原来是最新的版本才支持这个配置, 重新编译:
git clone github.com/containerd/cri
make install.deps
或者
make _output/containerd
然后把containerd, containerd-shim-runc-v1, containerd-shim-runc-v2上传到最新节点上,重起containerd, 注意上传前要先停掉进程。
经过上一步后理应可以成功了,可是仍然报错:
rpc error: code = Unknown desc = failed to get sandbox image "image-registry:5000/gcr.io/google_containers/pause-amd64:3.1":
failed to pull image "image-registry:5000/gcr.io/google_containers/pause-amd64:3.1":
failed to resolve image "image-registry:5000/gcr.io/google_containers/pause-amd64:3.1": no available registry endpoint:
unexpected status code https://image-registry:5000/v2/gcr.io/google_containers/pause-amd64/manifests/3.1: 401 Unauthorized
看起来像是没有配置鉴权信息,可是我确实已经配置了username, password, 也配了auth token的, ctr命令行用-u也是可以的, 怎么还报401?
看代码,最后发现代码里有一个问题(bug?)
pkg/server/images_pull.go
237 func (c *criService) credentials(auth *runtime.AuthConfig) func(string) (string, string, error) {
238 return func(host string) (string, string, error) {
239 if auth == nil {
240 // Get default auth from config.
241 for h, ac := range c.config.Registry.Auths {
242 u, err := url.Parse(h)
243 if err != nil {
244 return "", "", errors.Wrapf(err, "parse auth host %q", h)
245 }
246 if u.Host == host {
247 auth = toRuntimeAuthConfig(ac)
248 break
249 }
250 }
251 }
252 return ParseAuth(auth)
253 }
254 }
第246行直接拿u.Host和host做比较会失败,因为h不带Scheme, rl.Parse(h)解析出来出来的u.Host为空, 修改/etc/containerd/config.toml配置文件绕开这个问题
[plugins."io.containerd.grpc.v1.cri".registry]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = ["https://registry-1.docker.io"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."image-registry:5000"]
endpoint = ["https://image-registry:5000"]
[plugins."io.containerd.grpc.v1.cri".registry.auths]
[plugins."io.containerd.grpc.v1.cri".registry.auths."https://image-registry:5000"]
auth = "XXXXXXXXXXX"
username = "admin"
password = "XXXXX"
[plugins."io.containerd.grpc.v1.cri".registry.tls_configs]
[plugins."io.containerd.grpc.v1.cri".registry.tls_configs."image-registry:5000"]
ca_file = "/etc/containerd/ca.crt"
cert_file = "/etc/containerd/docker-registry-server.pem"
key_file = "/etc/containerd/docker-registry-server-key.pem"
可以看到针对auths的设置明显和mirrors以及tls_confis不一样,要额外制定https Scheme, 应该是一个bug,好在这个版本还没最终release.
总的来说从docker切换到containerd是比较方便的,只是如果使用私有镜像时会有问题: