近期编译镜像时, 出现了镜像体积骤增的情况, 排查后, 发现本质原因居然是由于升级了存储引擎导致的问题, overlayfs 与 overlayfs2 存储引擎实现多层联合文件系统的原理不同, 最终导致了我们遇到的这个问题

昨日重现

镜像引用关系图

1
2
3
4
5
6
7
centos:7.5

base-os (创建了一系列目录, 安装了部分命令工具)

open-jdk (下载并部署 jdk 至 /opt/soft 下, 335MB)

application (应用部署, 且执行了 chown user:user /opt)

引发镜像体积增大的原因: 在 open-jdk dockerfile 中, 将 jdk 文件拷贝至 /opt/soft 下, 该文件夹体积为335MB; 在 application dockerfile 中, 执行了一行 shell 命令 chown -R /opt

从编译镜像最佳实践来说, chown -R /opt 这样的匹配命令是不应该出现的, 其实体积增大的道理也很简单, 由于更改了整个/opt目录的属性, 导致/opt目录下的所有文件被重新拷贝到 application 镜像层中, 也就造成了 335MB 空间的浪费, 体积至少也无端增大了 335MB. 但问题是, 之前在 overlayfs 存储引擎下编译镜像, 却一直相安无事, 所有镜像版本都迭代了很多次, 没有出现上面预期的335MB 的空间浪费. 造成这样的问题, 是由于 overlayfs 和 overlayfs2 在实现多层联合文件系统的原理不同.

overlayfs vs overlayfs2

overlayfs将单个Linux主机上的两个目录分层并将它们呈现为单个目录. 这些目录称为图层, 统一过程称为联合装载. overlayfs将下部目录称为lowerdir上部目录upperdir. 统一视图通过自己的目录公开merged.
虽然overlay驱动程序只能使用一个较低的overlayfs层, 因此需要硬链接来实现多层图像, 但该overlay2驱动程序本身最多支持128个较低的overlayfs层. 此功能为和层相关的Docker命令(如docker build)提供更好的性能docker commit,并且在后备文件系统上占用更少的inode —-引用自https://cloud.tencent.com/developer/section/1091813

问题原因

简单来说, 之前在 application 层执行 chown 一个335MB文件夹之所有没有引发空间浪费问题, 完全是由于使用 overlayfs 的硬链接特性造成的.

之后改为 overlayfs2 存储引擎后, 层与层之间不再使用硬链接形式引用, 所以根据标准情况下镜像层之间的增删改原理推断, application 镜像层至少浪费了 335MB 的空间.(由于 chown 操作导致整个/opt目录文件被拷贝至 application 层)

chown 是个很神奇的命令, 它只修改文件的 metadata 属性, 这完美的切中了 overlayfs 与 overlayfs2 最本质的区别.

科普硬链接

简单来说, 硬链接是有着相同 inode 号仅文件名不同的文件, 因此硬链接存在以下几点特性:

  • 文件有相同的 inode 及 data block
  • 只能对已存在的文件进行创建
  • 不能交叉文件系统进行硬链接的创建
  • 不能对目录进行创建,只可对文件创建
  • 删除一个硬链接文件并不影响其他有相同 inode 号的文件

由于 overlayfs 的硬链接引用的原理, chown 只更改 metadata 数据, 而 metadata 是作用在 inode 节点上的, 一个硬链接文件的属性被修改, 同步的, 所有指向这个 inode 节点的文件的属性都会变化. 也就是说, overlayfs 通过硬链接将文件挂载到新的镜像层之后, 对里面已存在的文件做 chown 操作, 意味着底层(只读层)的文件属性也会被修改, docker 就会认为这个操作没有引发任何文件的变化, 所以就不会再拷贝那335MB 的文件. 而 overlayfs2 的每层都是独立的, 即使文件属性的变化, 也会导致整个文件被拷贝, 所以在 overlayfs2 下, 会产生335MB 的空间浪费


参考文档:

1
2
3
4
OS-Release: CentOS7
kernel: 4.4-LTS
kubernetes version: 1.10.9
kubelet 与 kube-apiserver 通信方式: TLS Bootstrap

问题复现

集群基于Bear Token方式的 TLS Bootstrap 进行通信, 且使用 kubelet 客户端证书过期轮换的特性

根据 1.10.x 版本的官方文档介绍, 配置使用此特性, 这里具体列出 kubelet 上的相关配置

  • 在 kubelet 的启动参数中传入--rotate-certificates 此参数意为声明开启证书轮换机制, 为即将过期的客户端节点颁发新的证书文件
  • 在 kubelet 参数配置模板文件中添加如下配置, 该配置默认开启
1
2
featureGates:
RotateKubeletClientCertificate: true

创建集群角色绑定

1
2
3
kubectl create clusterrolebinding kubelet-bootstrap \
--clusterrole=system:node-bootstrapper \
--group=system:bootstrappers

为节点创建 Bootstrap Token

1
2
3
4
TOKEN=`kubeadm token create \
--description kubelet-bootstrap-token \
--groups system:bootstrappers:node6752 \
--kubeconfig ~/.kube/config`

为节点创建 Bootstrap kubeconfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
kubectl config set-cluster kubernetes \
--certificate-authority=/opt/soft/k8s/pki/ca/ca.crt \
--embed-certs=true \
--server=https://k8smaster:6443 \
--kubeconfig=kubelet-bootstrap-node6752.kubeconfig

kubectl config set-credentials kubelet-bootstrap \
--token=$TOKEN \
--kubeconfig=kubelet-bootstrap-node6752.kubeconfig

kubectl config set-context default \
--cluster=kubernetes \
--user=kubelet-bootstrap \
--kubeconfig=kubelet-bootstrap-node6752.kubeconfig

kubectl config use-context default --kubeconfig=kubelet-bootstrap-node6752.kubeconfig

kubelet-bootstrap-node6752.kubeconfig Bootstrap kubeconfig 文件分发给 kubelet 客户端

在kubelet客户端指定配置文件

  • –bootstrap-kubeconfig=”/path/to/bootstrap/kubeconfig”
  • –kubeconfig=kubelet.kubeconfig

注意: 这里指定的--kubeconfig文件并不存在, 当 kubelet 完成认证, 拿到下发的证书后, 证书会存放到--cert-dir指定的文件夹, 然后根据实际证书来创建 kubeconfig 文件并写入到--kubeconfig指定的路径

复现问题

在启动 kubelet 服务后, 在 master 端可以看到 csr 请求, 手动审批

1
kubectl certificate approve node-csr-32YapU7hg6hB_87Hyus7NkD6a7-ew8sX2qP09ikjUh6

审批后, 在 kubelet 客户端指定的证书目录下, 你可以看到如下文件:

1
2
3
4
kubelet-client.crt (由 kube-controller-manager 颁发的客户端证书)
kubelet-client.key (本地自动创建的客户端秘钥)
kubelet.crt (kubelet本地自签名服务端证书)
kubelet.key (本地自动创建的服务端秘钥)

在 kubelet 指定的 --kubeconfig 文件, 也被写入了对应的内容, 问题就出现在了这里

其中users.user.client-certificateusers.user.client-key的配置中, 分别指定了kubelet-client.crtkubelet-client.key

1
2
3
4
5
users:
- name: default-auth
user:
client-certificate: /path/pki/kubelet-client.crt
client-key: /path/pki/kubelet-client.key

但问题是, 当kubelet-client.crt证书过期后, 由 Kubernetes 证书轮换特性续期的新证书, 却是以软连接的方式提供的, 而且, 软连接的名字和kubeconfig中配置的证书名字不一致. 以下是 kube-controller-manager 颁发的新证书形式

1
2
kubelet-client-2018-12-11-11-14-18.pem
kubelet-client-current.pem -> /path/pki/kubelet-client-2018-12-11-11-14-18.pem
  • 新的证书只有一个文件, 格式为 pem. 该文件中既包含了证书内容也包含了 key 的内容
  • 新证书的实际文件名为: kubelet-client-DATE.pem
  • 新证书的软连接为: kubelet-client-current.pem

可以看出, 当证书续期之后, 新证书的引用路径(文件名)与最开始自动创建的 kubeconfig 文件中指定的证书文件不一致. 这个问题将导致原有证书过期且 kubelet 重启后, 将无法再与 apiserver 通信, apiserver 端也将标记该节点为NotReady

解决方案

解决方案1: 手动修改 kubeconfig 文件

执行以下操作:

1
2
3
4
5
cat kubelet-client.crt kubelet.key > kubelet-origin.pem
ln -s kubelet-origin.pem /path/to/pki/kubelet-client-current.pem
sed -i 's/kubelet-client.crt/kubelet-client-current.pem/g' /path/to/kubelet.kubeconfig
sed -i 's/kubelet-client.key/kubelet-client-current.pem/g' /path/to/kubelet.kubeconfig
systemctl restart kubelet

原理就是在证书续期之前就手动构造证书续期之后的结构, 这样在证书续期后, 就可以无缝使用新证书了

解决方案2: 升级集群版本

通过查看官方文档, 在1.11.0版本中, 该问题得到了修复, 你也可以通过升级集群版本来解决这个问题

注意, 集群升级后, 原有的证书都还是继续使用的, 所以升级并解决不了原来的问题, 除非你将节点删除后, 重新使用 BearToken 的方式重新申请加入集群


参考文档:

在重启systemd-journald后, 意外的发现, 本台服务器上的其他由 systemd 托管的部分服务, 却意外被 stop 掉了, 由于大部分服务的 unit 文件中都配置了 Restart 规则为 on-failure. 所以如果是异常的退出, 这些服务应该会重启才对. 很明显, 这些服务是被”正常”关闭的.

在查看被 stop 的应用状态时, 发现了以下线索

1
2
3
4
5
$ sudo systemctl status kube-apiserver
......
Active: inactive (dead) since Thu 2018-11-22 13:53:46 CST; 25min ago
Main PID: 90457 (code=killed, signal=PIPE)
......

code=killed, signal=PIPE 这些信息告诉我们, 这个进程是被SIGPIPE信号关闭的. 在网上查阅资料时, 发现 github 中大量的项目 issue 下都有相关的问题. 而问题的矛头都指向了systemd-journald这个服务的重启事件!

问题的原因在于: systemd-journald这个服务重启的时候, 会给所有的进程发送SIGPIPE信号, 而在默认的 systemd 定义中, SIGPIPE 信号属于正常退出的范围. 所以即使 unit 文件配置了Restart on-failure也不会被重启

该问题是 systemd 的已知 BUG 之一. 规避该问题的方式有很多种, 其中以下这种方式并没有生效

1
2
$ systemctl show kube-apiserver | grep PIPE
IgnoreSIGPIPE=yes

默认情况下, IgnoreSIGPIPE=yes是配置好的, 理论上会忽略掉接收到的SIGPIPE信号, 但实际情况表明, 并没有被忽略

解决方案:

  • 在 systemd 的 unit 文件中的 Restart 策略设置为 Always, 这样即使是正常的关闭, 也会重启服务
  • 在 systemd 的 unit 文件中配置 RestartForceExitStatus=SIGPIPE 指定收到这个信号时, 强制重启服务

坏消息:

RestartForceExitStatus 的配置在 systemd 215中才被加入, 对于使用之前版本的情况来说, 并没有什么帮助, 可能Always是唯一的选择


病友:

参考文档:

问题现象: docker container logs 查看容器日志却没有任何返回结果. 开始以为是程序没有 stdout 标准输出, 但是后来测试, 将echo "log"作为死循环运行在容器中, 再次执行docker container logs依然没有任何日志输出. 找到一台”干净的”docker 服务器测试却可以正常显示日志

这台无法查看日志回显的服务器, Docker 的日志引擎配置为journald, 查看 journald 服务日志发现大量类似日志:

1
2
3
systemd-journal: Suppressed 9567 messages from /system.slice/
systemd-journal: Suppressed 6735 messages from /system.slice/
......

根据字面意思理解就是日志被丢弃了, 看来是由于 journald 服务的问题导致的日志问题

在 journald 中, 有如下两个参数跟此问题相关:

  • RateLimitInterval
  • RateLimitBurst

RateLimitInterval是指定时间间隔, 意思就是说, 在RateLimitInterval这段时间内的日志总量(总条数)控制在RateLimitBurst以内. 这两个参数搭配使用可以用来控制日志速率, 避免由于大量日志输出导致的一系列性能问题.

该问题的根源在于该主机目前日志输出的速率超出了 journald 默认的配置, 你可以自定定义该速率, 也可以将RateLimitInterval设置为0, 以禁用速率控制

接下来重启 journald 以生效配置

1
systemctl restart systemd-journald

注意: 重启 journald 服务可能会引发一个 systemd 的 bug, 详情请参考下篇文章<systemd-journald的SIGPIPE信号BUG>

参考文档:

默认情况下, heapster 使用 http 协议访问每个 kubelet 节点的10255这个非安全端口, 在kubelet 关闭此端口的情况下, 仅允许通过 https 通信时, heapster 找 kubelet拿数据成了大问题

基础环境:

auth mode: RBAC
kubelet port: only https(10250)
kubelet ca: SelfSign

创建 ServiceAccount

1
2
3
4
5
apiVersion: v1
kind: ServiceAccount
metadata:
name: heapster
namespace: kube-system

创建 ClusterRoleBinding

官方文档中的配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: heapster
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:heapster
subjects:
- kind: ServiceAccount
name: heapster
namespace: kube-system

官方文档中的配置在 heapster 使用 http 协议访问 kubelet 10255 端口的时候时没有任何问题的, 但是用这份配置访问 kubelet 的 10250 端口, 你在 heapster 的 console log 日志中, 可以看到 403 Forbidden 的报错, 因为绑定的角色权限没有达到要求, 所以你需要下面这份绑定文件

1
2
3
4
5
6
7
8
9
10
11
12
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: heapster
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:kubelet-api-admin
subjects:
- kind: ServiceAccount
name: heapster
namespace: kube-system

其实只是绑定的角色从 system:heapster 换成了 system:kubelet-api-admin

创建 heapster 服务

以下是官方示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: heapster
namespace: kube-system
spec:
replicas: 1
template:
metadata:
labels:
task: monitoring
k8s-app: heapster
spec:
serviceAccountName: heapster
containers:
- name: heapster
image: k8s.gcr.io/heapster-amd64:v1.5.4
imagePullPolicy: IfNotPresent
command:
- /heapster
- --source=kubernetes:https://kubernetes.default
- --sink=influxdb:http://monitoring-influxdb.kube-system.svc:8086

我们需要对如下参数做修改:

  • –source=kubernetes:https://这里写你master的域名:6443
  • –sink=influxdb:http://这里写你influxdb的地址或域名:8086

--sink 配置中有如下参数可以选择

  • inClusterConfig - Use kube config in service accounts associated with Heapster’s namespace. (default: true)
  • kubeletPort - kubelet port to use (default: 10255)
  • kubeletHttps - whether to use https to connect to kubelets (default: false)
  • insecure - whether to trust Kubernetes certificates (default: false)
  • auth - client auth file to use. Set auth if the service accounts are not usable.
  • useServiceAccount - whether to use the service account token if one is mounted at /var/run/secrets/kubernetes.io/serviceaccount/token (default: false)

需要设置访问 kubelet 的安全端口, 需要如下参数

  • kubeletPort 10250 访问10250端口
  • kubeletHttps true 以https协议访问
  • insecure true 信任kubernetes证书
  • useServiceAccount true 使用挂载进来的SA账户进行访问

最终的配置结果如下:

1
--source=kubernetes:https://k8s-master:6443?useServiceAccount=true&kubeletHttps=true&kubeletPort=10250&insecure=true

这里着重说明一下insecure配置, 一般情况下, kubelet 节点在没有静态配置服务端证书的情况下, kubelet 节点会自动生成一个自签名的服务端证书到指定或默认的目录下. 这个证书是独立于kubernetes集群的证书, 不是kubernetes集群根证书所签发的. 所以理论上, 在 kubernetse 的双向认证中, 当 apiserver 向 kubelet主动发起 https 请求后, 服务端首先会将自己的服务端证书发送给客户端(apiserver), 一般情况下, apiserver 是需要校验服务端证书是否可信的. 但是在 apiserver 与 kubelet 通信中比较特殊, 默认情况下, apiserver 是不会校验 kubelet 的服务端证书的, 因为 kubelet 一般是独立于k8集群的自签名证书, 如果真的校验, 结果肯定是服务端证书不可信, 所以索性默认就不校验了, 而是直接将 apiserver 持有的 kubelet 客户端证书发送给 kubelet, 供 kubelet 校验, 此时, kubelet 依据配置中指定的ca证书对 apiserver 发来的客户端证书进行校验.

话题再回到insecure配置. 如果你设置这个参数为true, 还要使用 https 协议去访问 kubelet, 那么 heapster 日志中将会报证书验证失败的错误. heapster 会取到每个 kubelet 的IP地址, 然后进行访问, 但问题是, kubelet 在自签名证书时, 默认依据的是自己的主机名, 所以 heapster 以 https 访问 kubelet, 一是证书不是自己持有的根证书签发的,不可信; 二是服务端的地址限定是基于DNS的主机名, 并没有设置IP, 所以也不会校验通过


参考文档:

手动部署的k8s集群, 需要为master节点手动设置taints

设置taint

语法:

1
2
3
4
5
kubectl taint node [node] key=value[effect]   
其中[effect] 可取值: [ NoSchedule | PreferNoSchedule | NoExecute ]
NoSchedule: 一定不能被调度
PreferNoSchedule: 尽量不要调度
NoExecute: 不仅不会调度, 还会驱逐Node上已有的Pod

示例:

1
2
3
kubectl taint node node1 key1=value1:NoSchedule
kubectl taint node node1 key1=value1:NoExecute
kubectl taint node node1 key2=value2:NoSchedule

查看taint:

1
kubectl describe node node1

删除taint:

1
2
3
4
kubectl taint node node1 key1:NoSchedule-  # 这里的key可以不用指定value
kubectl taint node node1 key1:NoExecute-
# kubectl taint node node1 key1- 删除指定key所有的effect
kubectl taint node node1 key2:NoSchedule-

master节点设置taint

1
kubectl taint nodes master1 node-role.kubernetes.io/master=:NoSchedule

注意⚠️ : 为master设置的这个taint中, node-role.kubernetes.io/masterkey, value为空, effectNoSchedule

如果输入命令时, 你丢掉了=符号, 写成了node-role.kubernetes.io/master:NoSchedule, 会报error: at least one taint update is required错误

容忍tolerations主节点的taints

以上面为 master1 设置的 taints 为例, 你需要为你的 yaml 文件中添加如下配置, 才能容忍 master 节点的污点

在 pod 的 spec 中设置 tolerations 字段

1
2
3
4
5
tolerations:
- key: "node-role.kubernetes.io/master"
operator: "Equal"
value: ""
effect: "NoSchedule"

1
2
3
4
5
root@k8s-master:~# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master Ready master 50d v1.11.2
k8s-node1 NotReady <none> 50d v1.11.2
k8s-node2 NotReady <none> 50d v1.11.2

在 kubeadm 引导的 k8s 集群中, 查看 nodes 信息时, ROLES 一列标记了 master 节点的身份, 其他节点默认没有标记. 在手动安装的 k8s 集群中, 你看到情况可能是如下这样的:

1
2
3
4
5
root@k8s-master:~# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master Ready <none> 50d v1.11.2
k8s-node1 NotReady <none> 50d v1.11.2
k8s-node2 NotReady <none> 50d v1.11.2

没错, 连 master 节点的标记都没有, 我们可以手动给任意 node 设置 ROLES

原理就是给 node 打标签, 只不过是特殊的标签

1
2
3
4
5
6
7
8
9
10
11
12
13
root@k8s-master:~# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master Ready master 50d v1.11.2
k8s-node1 NotReady <none> 50d v1.11.2
k8s-node2 NotReady <none> 50d v1.11.2
root@k8s-master:~# kubectl label node k8s-node1 node-role.kubernetes.io/worker=worker
node/k8s-node1 labeled
root@k8s-master:~# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master Ready master 50d v1.11.2
k8s-node1 NotReady worker 50d v1.11.2
k8s-node2 NotReady <none> 50d v1.11.2
root@k8s-master:~#

参考文档:

https://kubernetes.io/docs/reference/setup-tools/kubeadm/implementation-details/

安装 docker-ce 时, 依赖安装 libcgroup 失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Total download size: 65 k
Installed size: 130 k
Is this ok [y/d/N]: y
Downloading packages:
libcgroup-0.41-15.el7.x86_64.rpm | 65 kB 00:00:00
Member: libcgroup.x86_64 0:0.41-15.el7 - u
Adding Package libcgroup-0.41-15.el7.x86_64 in mode u
Running transaction check
Transaction check time: 0.108
Running transaction test
Transaction test succeeded
Transaction test time: 0.069
Running transaction
groupadd: cannot open /etc/group
error: %pre(libcgroup-0.41-15.el7.x86_64) scriptlet failed, exit status 10
Error in PREIN scriptlet in rpm package libcgroup-0.41-15.el7.x86_64
Warning: scriptlet or other non-fatal errors occurred during transaction.
Verifying : libcgroup-0.41-15.el7.x86_64 1/1
What is this? libcgroup-0.41-15.el7.x86_64
VerifyTransaction time: 0.003
Transaction time: 0.810

Failed:
libcgroup.x86_64 0:0.41-15.el7

看到报错信息groupadd: cannot open /etc/group, 安装程序操作了这个文件, 但是报这个文件不可访问的错误

实际检查中, 该文件已经存在

将安装包单独下载下来, 手动安装查看详细操作

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
yum install --downloadonly libcgroup
Loaded plugins: fastestmirror, langpacks, priorities
Loading mirror speeds from cached hostfile
* elrepo: mirrors.neusoft.edu.cn
Resolving Dependencies
--> Running transaction check
---> Package libcgroup.x86_64 0:0.41-15.el7 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

==================================================================================================================================================================
Package Arch Version Repository Size
==================================================================================================================================================================
Installing:
libcgroup x86_64 0.41-15.el7 base 65 k

Transaction Summary
==================================================================================================================================================================
Install 1 Package

Total download size: 65 k
Installed size: 130 k
Background downloading packages, then exiting:
libcgroup-0.41-15.el7.x86_64.rpm | 65 kB 00:00:00
exiting because "Download Only" specified

找到文件位置

1
2
find / -name "libcgroup-0.41-15.el7.x86_64.rpm" -type f
/var/cache/yum/x86_64/7/base/packages/libcgroup-0.41-15.el7.x86_64.rpm

手动安装

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
rpm -ivvvh /var/cache/yum/x86_64/7/base/packages/libcgroup-0.41-15.el7.x86_64.rpm
D: ============== /var/cache/yum/x86_64/7/base/packages/libcgroup-0.41-15.el7.x86_64.rpm
D: loading keyring from pubkeys in /var/lib/rpm/pubkeys/*.key
D: couldn't find any keys in /var/lib/rpm/pubkeys/*.key
D: loading keyring from rpmdb
D: opening db environment /var/lib/rpm cdb:0x401
D: opening db index /var/lib/rpm/Packages 0x400 mode=0x0
D: locked db index /var/lib/rpm/Packages
D: opening db index /var/lib/rpm/Name 0x400 mode=0x0
D: read h# 671 Header SHA1 digest: OK (6c239af7e9e88852422394529fbf054ef1cd4921)
D: added key gpg-pubkey-baadae52-49beffa4 to keyring
D: Using legacy gpg-pubkey(s) from rpmdb
D: Expected size: 66752 = lead(96)+sigs(1284)+pad(4)+data(65368)
D: Actual size: 66752
warning: /var/cache/yum/x86_64/7/base/packages/libcgroup-0.41-15.el7.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
D: added binary package [0]
D: found 0 source and 1 binary packages
D: opening db index /var/lib/rpm/Conflictname 0x400 mode=0x0
D: ========== +++ libcgroup-0.41-15.el7 x86_64/linux 0x2
D: opening db index /var/lib/rpm/Basenames 0x400 mode=0x0
D: read h# 606 Header SHA1 digest: OK (83b77b161083033277b34d5b3705b5e3a2a52ab3)
D: Requires: /bin/sh YES (db files)
D: Requires: /bin/sh YES (cached)
D: read h# 36 Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
D: Requires: /sbin/ldconfig YES (db files)
D: Requires: /sbin/ldconfig YES (cached)
D: opening db index /var/lib/rpm/Providename 0x400 mode=0x0
D: read h# 36 Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
D: Requires: ld-linux-x86-64.so.2()(64bit) YES (db provides)
D: read h# 36 Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
D: Requires: ld-linux-x86-64.so.2(GLIBC_2.3)(64bit) YES (db provides)
D: read h# 36 Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
D: Requires: libc.so.6()(64bit) YES (db provides)
D: read h# 36 Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
D: Requires: libc.so.6(GLIBC_2.11)(64bit) YES (db provides)
D: read h# 36 Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
D: Requires: libc.so.6(GLIBC_2.14)(64bit) YES (db provides)
D: read h# 36 Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
D: Requires: libc.so.6(GLIBC_2.2.5)(64bit) YES (db provides)
D: read h# 36 Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
D: Requires: libc.so.6(GLIBC_2.3)(64bit) YES (db provides)
D: read h# 36 Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
D: Requires: libc.so.6(GLIBC_2.3.4)(64bit) YES (db provides)
D: read h# 36 Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
D: Requires: libc.so.6(GLIBC_2.4)(64bit) YES (db provides)
D: read h# 36 Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
D: Requires: libc.so.6(GLIBC_2.8)(64bit) YES (db provides)
D: read h# 36 Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
D: Requires: libpthread.so.0()(64bit) YES (db provides)
D: read h# 36 Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
D: Requires: libpthread.so.0(GLIBC_2.2.5)(64bit) YES (db provides)
D: Requires: rpmlib(CompressedFileNames) <= 3.0.4-1 YES (rpmlib provides)
D: Requires: rpmlib(FileDigests) <= 4.6.0-1 YES (rpmlib provides)
D: Requires: rpmlib(PayloadFilesHavePrefix) <= 4.0-1 YES (rpmlib provides)
D: read h# 36 Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
D: Requires: rtld(GNU_HASH) YES (db provides)
D: read h# 344 Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
D: Requires: shadow-utils YES (db provides)
D: read h# 365 Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
D: Requires: systemd YES (db provides)
D: Requires: systemd YES (cached)
D: Requires: systemd YES (cached)
D: Requires: rpmlib(PayloadIsXz) <= 5.2-1 YES (rpmlib provides)
D: opening db index /var/lib/rpm/Obsoletename 0x400 mode=0x0
D: ========== recording tsort relations
D: ========== tsorting packages (order, #predecessors, #succesors, depth)
D: 0 0 0 1 +libcgroup-0.41-15.el7.x86_64
D: installing binary packages
D: Selinux disabled.
D: closed db index /var/lib/rpm/Obsoletename
D: closed db index /var/lib/rpm/Conflictname
D: closed db index /var/lib/rpm/Providename
D: closed db index /var/lib/rpm/Basenames
D: closed db index /var/lib/rpm/Name
D: closed db index /var/lib/rpm/Packages
D: closed db environment /var/lib/rpm
D: opening db environment /var/lib/rpm cdb:0x401
D: opening db index /var/lib/rpm/Packages (none) mode=0x42
D: sanity checking 1 elements
D: opening db index /var/lib/rpm/Name (none) mode=0x42
D: running pre-transaction scripts
D: computing 5 file fingerprints
D: opening db index /var/lib/rpm/Basenames (none) mode=0x42
D: opening db index /var/lib/rpm/Group (none) mode=0x42
D: opening db index /var/lib/rpm/Requirename (none) mode=0x42
D: opening db index /var/lib/rpm/Providename (none) mode=0x42
D: opening db index /var/lib/rpm/Conflictname (none) mode=0x42
D: opening db index /var/lib/rpm/Obsoletename (none) mode=0x42
D: opening db index /var/lib/rpm/Triggername (none) mode=0x42
D: opening db index /var/lib/rpm/Dirnames (none) mode=0x42
D: opening db index /var/lib/rpm/Installtid (none) mode=0x42
D: opening db index /var/lib/rpm/Sigmd5 (none) mode=0x42
D: opening db index /var/lib/rpm/Sha1header (none) mode=0x42
Preparing... D: computing file dispositions
D: 0x00000803 4096 72372888 146033401 /
################################# [100%]
D: ========== +++ libcgroup-0.41-15.el7 x86_64-linux 0x2
D: Expected size: 66752 = lead(96)+sigs(1284)+pad(4)+data(65368)
D: Actual size: 66752
D: libcgroup-0.41-15.el7.x86_64: Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
D: install: libcgroup-0.41-15.el7 has 5 files
D: %pre(libcgroup-0.41-15.el7.x86_64): scriptlet start
D: %pre(libcgroup-0.41-15.el7.x86_64): execv(/bin/sh) pid 43192
+ getent group cgred
+ groupadd -r cgred
groupadd: cannot open /etc/group
D: %pre(libcgroup-0.41-15.el7.x86_64): waitpid(43192) rc 43192 status a00
error: %pre(libcgroup-0.41-15.el7.x86_64) scriptlet failed, exit status 10
error: libcgroup-0.41-15.el7.x86_64: install failed
D: running post-transaction scripts
D: closed db index /var/lib/rpm/Sha1header
D: closed db index /var/lib/rpm/Sigmd5
D: closed db index /var/lib/rpm/Installtid
D: closed db index /var/lib/rpm/Dirnames
D: closed db index /var/lib/rpm/Triggername
D: closed db index /var/lib/rpm/Obsoletename
D: closed db index /var/lib/rpm/Conflictname
D: closed db index /var/lib/rpm/Providename
D: closed db index /var/lib/rpm/Requirename
D: closed db index /var/lib/rpm/Group
D: closed db index /var/lib/rpm/Basenames
D: closed db index /var/lib/rpm/Name
D: closed db index /var/lib/rpm/Packages
D: closed db environment /var/lib/rpm

可以看到, 在安装的过程中, 是执行了groupadd -r cgred导致的报错

手动执行 groupadd -r cgred, 依然报错 groupadd: cannot open /etc/group

查看文件属性

lsattr /etc/group
—-i———– /etc/group

原来是有特殊权限, 修改他

chattr -i /etc/group

官方文档中, 对 k8s 的证书制作提供了三种方式, 主流使用有两种

  • openssl
  • cfssl

具体证书制作步骤在此就不详细说明, 重点注意参考官方文档, 使用 openssl 方式制作证书时需要注意的事项

官方文档中, 使用 OpenSSL 制作证书, 在 KeyUsage 的设置上, 指定了如下功能:

1
keyUsage=keyEncipherment,dataEncipherment

在制作完 crt 证书后, 使用 openssl x509 -in /path/to/xxx.crt -text -noout 命令查看证书信息:

1
2
X509v3 Key Usage:
Key Encipherment, Data Encipherment

没毛病, 就是之前指定的两个功能, 一个不多一个不少, 但问题也出现在这里.

在实际使用中, 使用仅带有keyUsage=keyEncipherment,dataEncipherment这两个功能的证书引导集群, 并没有出现什么问题, 但是在使用 Java 客户端访问 apiserver 时, 却报以下错误:

1
io.kubernetes.client.ApiException: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: KeyUsage does not allow digital signatures

报错信息很明确, 证书没有提供digital signatures的功能. 查询另一篇证书说明文档(https://kubernetes.io/docs/setup/certificates/) 里面说的很清楚

1
2
3
kind	Key usage
server digital signature, key encipherment, server auth
client digital signature, key encipherment, client auth

不管是服务端证书还是客户端证书, 都至少需要提供digital signature, key encipherment这两个功能, 但是制作证书的官方文档中, openssl 方式创建的证书, 却没有显式指定 digital signatures 功能. 在后期的制作中, openssl 也没有默认给我们加入此功能, 结果导致 java 客户端无法完成 ssl 认证.

但是官方文档中, 使用 cfssl 工具也没有显示指定加入 digital signatures 功能, 但是制作证书时, 默认为我们加入了该功能. 就冲这点, 也强烈建议使用 cfssl 工具制作证书😆

解决方法:

  • 使用 openssl 签发证书的话, 将 keyUsage=keyEncipherment,dataEncipherment 修改为–> keyUsage=digitalSignature,keyEncipherment,dataEncipherment 即可
  • 使用 cfssl 签发证书, 默认就会加入digitalSignature功能

解决方案:
方案一: 重新制作所有证书, 加入digital signatures功能, 并为集群更换证书
方案二: 重新制作 apiserver 服务端证书和 java 客户端使用的客户端证书, 加入digital signatures功能, 替换所有 apiserver 和 java 使用的证书, 即可完成最小代价的替换

附录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
KeyUsage ::= BIT STRING {
digitalSignature (0),
nonRepudiation (1), -- recent editions of X.509 have
-- renamed this bit to contentCommitment
keyEncipherment (2),
dataEncipherment (3),
keyAgreement (4),
keyCertSign (5),
cRLSign (6),
encipherOnly (7),
decipherOnly (8) }

Bits in the KeyUsage type are used as follows:

The digitalSignature bit is asserted when the subject public key
is used for verifying digital signatures, other than signatures on
certificates (bit 5) and CRLs (bit 6), such as those used in an
entity authentication service, a data origin authentication
service, and/or an integrity service.

参考文档:

k8s证书制作文档

k8s证书使用说明文档

KeyUsage 文档:

环境:

  • Kubernetes
  • Bridge+DHCP

kubelet 日志如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Oct 10 16:52:58 k8node3 kubelet[38221]: E1010 16:52:58.848278   38221 kuberuntime_sandbox.go:54] CreatePodSandbox for pod "log-pilot-msvj7_default(88d40907-cc60-11e8-9598-ecebb88a11d4)" failed: rpc error: code = Unknown desc = NetworkPlugin cni failed to set up pod "log-pilot-msvj7_default" network: error calling DHCP.Allocate: no more tries
Oct 10 16:52:58 k8node3 kubelet[38221]: E1010 16:52:58.848293 38221 kuberuntime_manager.go:646] createPodSandbox for pod "log-pilot-msvj7_default(88d40907-cc60-11e8-9598-ecebb88a11d4)" failed: rpc error: code = Unknown desc = NetworkPlugin cni failed to set up pod "log-pilot-msvj7_default" network: error calling DHCP.Allocate: no more tries
Oct 10 16:52:58 k8node3 kubelet[38221]: E1010 16:52:58.848349 38221 pod_workers.go:186] Error syncing pod 88d40907-cc60-11e8-9598-ecebb88a11d4 ("log-pilot-msvj7_default(88d40907-cc60-11e8-9598-ecebb88a11d4)"), skipping: failed to "CreatePodSandbox" for "log-pilot-msvj7_default(88d40907-cc60-11e8-9598-ecebb88a11d4)" with CreatePodSandboxError: "CreatePodSandbox for pod \"log-pilot-msvj7_default(88d40907-cc60-11e8-9598-ecebb88a11d4)\" failed: rpc error: code = Unknown desc = NetworkPlugin cni failed to set up pod \"log-pilot-msvj7_default\" network: error calling DHCP.Allocate: no more tries"
Oct 10 16:53:43 k8node3 kubelet[38221]: E1010 16:53:43.433003 38221 cni.go:259] Error adding network: error calling DHCP.Allocate: no more tries
Oct 10 16:53:43 k8node3 kubelet[38221]: E1010 16:53:43.433036 38221 cni.go:227] Error while adding to cni network: error calling DHCP.Allocate: no more tries
Oct 10 16:53:43 k8node3 kubelet[38221]: E1010 16:53:43.505258 38221 remote_runtime.go:92] RunPodSandbox from runtime service failed: rpc error: code = Unknown desc = NetworkPlugin cni failed to set up pod "log-pilot-msvj7_default" network: error calling DHCP.Allocate: no more tries
Oct 10 16:53:43 k8node3 kubelet[38221]: E1010 16:53:43.505313 38221 kuberuntime_sandbox.go:54] CreatePodSandbox for pod "log-pilot-msvj7_default(88d40907-cc60-11e8-9598-ecebb88a11d4)" failed: rpc error: code = Unknown desc = NetworkPlugin cni failed to set up pod "log-pilot-msvj7_default" network: error calling DHCP.Allocate: no more tries
Oct 10 16:53:43 k8node3 kubelet[38221]: E1010 16:53:43.505327 38221 kuberuntime_manager.go:646] createPodSandbox for pod "log-pilot-msvj7_default(88d40907-cc60-11e8-9598-ecebb88a11d4)" failed: rpc error: code = Unknown desc = NetworkPlugin cni failed to set up pod "log-pilot-msvj7_default" network: error calling DHCP.Allocate: no more tries
Oct 10 16:53:43 k8node3 kubelet[38221]: E1010 16:53:43.505388 38221 pod_workers.go:186] Error syncing pod 88d40907-cc60-11e8-9598-ecebb88a11d4 ("log-pilot-msvj7_default(88d40907-cc60-11e8-9598-ecebb88a11d4)"), skipping: failed to "CreatePodSandbox" for "log-pilot-msvj7_default(88d40907-cc60-11e8-9598-ecebb88a11d4)" with CreatePodSandboxError: "CreatePodSandbox for pod \"log-pilot-msvj7_default(88d40907-cc60-11e8-9598-ecebb88a11d4)\" failed: rpc error: code = Unknown desc = NetworkPlugin cni failed to set up pod \"log-pilot-msvj7_default\" network: error calling DHCP.Allocate: no more tries"
Oct 10 16:54:29 k8node3 kubelet[38221]: E1010 16:54:29.112837 38221 cni.go:259] Error adding network: error calling DHCP.Allocate: no more tries
Oct 10 16:54:29 k8node3 kubelet[38221]: E1010 16:54:29.112865 38221 cni.go:227] Error while adding to cni network: error calling DHCP.Allocate: no more tries
Oct 10 16:54:29 k8node3 kubelet[38221]: E1010 16:54:29.190742 38221 remote_runtime.go:92] RunPodSandbox from runtime service failed: rpc error: code = Unknown desc = NetworkPlugin cni failed to set up pod "log-pilot-msvj7_default" network: error calling DHCP.Allocate: no more tries
Oct 10 16:54:29 k8node3 kubelet[38221]: E1010 16:54:29.190800 38221 kuberuntime_sandbox.go:54] CreatePodSandbox for pod "log-pilot-msvj7_default(88d40907-cc60-11e8-9598-ecebb88a11d4)" failed: rpc error: code = Unknown desc = NetworkPlugin cni failed to set up pod "log-pilot-msvj7_default" network: error calling DHCP.Allocate: no more tries
Oct 10 16:54:29 k8node3 kubelet[38221]: E1010 16:54:29.190815 38221 kuberuntime_manager.go:646] createPodSandbox for pod "log-pilot-msvj7_default(88d40907-cc60-11e8-9598-ecebb88a11d4)" failed: rpc error: code = Unknown desc = NetworkPlugin cni failed to set up pod "log-pilot-msvj7_default" network: error calling DHCP.Allocate: no more tries
Oct 10 16:54:29 k8node3 kubelet[38221]: E1010 16:54:29.190899 38221 pod_workers.go:186] Error syncing pod 88d40907-cc60-11e8-9598-ecebb88a11d4 ("log-pilot-msvj7_default(88d40907-cc60-11e8-9598-ecebb88a11d4)"), skipping: failed to "CreatePodSandbox" for "log-pilot-msvj7_default(88d40907-cc60-11e8-9598-ecebb88a11d4)" with CreatePodSandboxError: "CreatePodSandbox for pod \"log-pilot-msvj7_default(88d40907-cc60-11e8-9598-ecebb88a11d4)\" failed: rpc error: code = Unknown desc = NetworkPlugin cni failed to set up pod \"log-pilot-msvj7_default\" network: error calling DHCP.Allocate: no more tries"
Oct 10 16:55:13 k8node3 kubelet[38221]: E1010 16:55:13.604965 38221 cni.go:259] Error adding network: error calling DHCP.Allocate: no more tries
Oct 10 16:55:13 k8node3 kubelet[38221]: E1010 16:55:13.605013 38221 cni.go:227] Error while adding to cni network: error calling DHCP.Allocate: no more tries
Oct 10 16:55:13 k8node3 kubelet[38221]: E1010 16:55:13.692412 38221 remote_runtime.go:92] RunPodSandbox from runtime service failed: rpc error: code = Unknown desc = NetworkPlugin cni failed to set up pod "log-pilot-msvj7_default" network: error calling DHCP.Allocate: no more tries

cni 插件日志如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2018/10/10 16:55:15 d61d6dd5cbda1c51769489334a566a1c4ca6f258efee60375aaf2e71bc116c32/zzcloudnet: acquiring lease
2018/10/10 16:55:20 resource temporarily unavailable
2018/10/10 16:55:29 resource temporarily unavailable
2018/10/10 16:55:43 resource temporarily unavailable
2018/10/10 16:56:00 bb55ff4ecac38220cbe88d0e34c7bc4296c6faf181fb24499694f9c76881e02c/zzcloudnet: acquiring lease
2018/10/10 16:56:05 resource temporarily unavailable
2018/10/10 16:56:15 resource temporarily unavailable
2018/10/10 16:56:28 resource temporarily unavailable
2018/10/10 16:56:45 07c373e24b35ec12d66f991b32a206217288ba018648071cea4f87fc9e055658/zzcloudnet: acquiring lease
2018/10/10 16:56:50 resource temporarily unavailable
2018/10/10 16:57:00 resource temporarily unavailable
2018/10/10 16:57:12 resource temporarily unavailable
2018/10/10 16:57:30 c3d52c2c4ea0db096b079da202ba0733b91e9c68d3b2db7ba6dc4d67c71e6546/zzcloudnet: acquiring lease
2018/10/10 16:57:35 resource temporarily unavailable
2018/10/10 16:57:44 resource temporarily unavailable

DHCP 服务端抓包 tcpdump -n -i cni0 发现, 服务端接收到了客户端的 DISCOVER, 并且服务端给了 OFFER, 但却迟迟收不到客户端发来的 REQUEST, 由此可以定位到问题, 要么包在服务端发不出去, 要么是客户端收不着, 着重检查两者之间的防火墙设置以及网络连通性.