zhangguanzhang's Blog

不走etcd v2 api下二进制跑flannel的总结

字数统计: 1.3k阅读时长: 6 min
2019/03/15

前言

这几天给线上搭建 k8s 集群,文件和 systemd 参数大多是从 kubeadm 的 staticPod 的 yml 里扣出来的,起初是除了 flanneld 全部是 systemd 管理二进制。脚本问题导致 kube-proxy 的 kubeocnfig 少执行了 use-context 所以显示的是匿名用户无法 list node 信息,kube-proxy 是在运行但是无法维持 svc 的网络,flanneld 因为是 pod 要 watch 节点是通过 kubernetes 这个 svc 走的连接导致不通,然后 pod 状态变成退出,而 kubelet 在不手动清理掉退出状态的 flannel 容器就不会创建新的 flannel 的 pod,而改退出状态容器的 gc 时间又是全局这样不好,遂萌生了之前一个未完成的坑: flanneld扣成二进制跑

尝试

不要在 etcd 写 flannel 配置了

国内和看到的一些个人博客的 flanneld 全部都是通过 etcdctl v2 去写 pod 子网(而且不用 cni plugins 还去改 docker0 的段就不说了..),新版本 k8s 都是 v3 的 api 存储,而且 v2 和 v3 共存会导致无法恢复备份,并且 etcd 官方打算后期废除掉 v2 的存储。感觉市面上还 etcdctl v2 去 set 网络贼 low,这几天查了下资料成功搞出来了,记录下过程。

尝试和源码相关

先说下之前的尝试:之前就把 kube-flannel.yml 文件和参数照搬写成 systemd 脚本启动,yml 里的 clusterrole 和 sa 啥的都创建好然后用 sa 的 token 生成了 kubeconfig 给 flannel 用。但是运行不起来,报错env variables POD_NAME and POD_NAMESPACE must be set

今天刚开始谷歌搜关键字搜到了这个 issue https://github.com/coreos/flannel/issues/932 ,说手动添加上面两个变量来欺骗集群。但是开发者给出答案说只需要设置下每个 node 环境变量 NODE_NAME 为名字就行了,issue 里还贴了源码对应的逻辑判断
https://github.com/coreos/flannel/blob/master/subnet/kube/kube.go#L94

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
nodeName := os.Getenv("NODE_NAME")
if nodeName == "" {
podName := os.Getenv("POD_NAME")
podNamespace := os.Getenv("POD_NAMESPACE")
if podName == "" || podNamespace == "" {
return nil, fmt.Errorf("env variables POD_NAME and POD_NAMESPACE must be set")
}

pod, err := c.Pods(podNamespace).Get(podName, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("error retrieving pod spec for '%s/%s': %v", podNamespace, podName, err)
}
nodeName = pod.Spec.NodeName
if nodeName == "" {
return nil, fmt.Errorf("node name not present in pod spec '%s/%s'", podNamespace, podName)
}
}

找了个flannel的pod默认情况下的环境变量

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
"Env": [
"POD_NAME=kube-flannel-ds-amd64-gmzsf",
"POD_NAMESPACE=kube-system",
"KUBE_DNS_SERVICE_PORT_DNS=53",
"KUBE_DNS_SERVICE_PORT_DNS_TCP=53",
"KUBE_DNS_PORT_53_UDP_PORT=53",
"KUBE_DNS_PORT_53_TCP_PORT=53",
"KUBE_DNS_PORT_9153_TCP_ADDR=10.96.0.10",
"KUBERNETES_SERVICE_HOST=10.96.0.1",
"KUBERNETES_PORT_443_TCP_PROTO=tcp",
"KUBERNETES_PORT_443_TCP_PORT=443",
"KUBE_DNS_SERVICE_HOST=10.96.0.10",
"KUBE_DNS_SERVICE_PORT=53",
"KUBE_DNS_SERVICE_PORT_METRICS=9153",
"KUBE_DNS_PORT_53_TCP=tcp://10.96.0.10:53",
"KUBE_DNS_PORT_9153_TCP_PROTO=tcp",
"KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1",
"KUBE_DNS_PORT=udp://10.96.0.10:53",
"KUBE_DNS_PORT_53_UDP_ADDR=10.96.0.10",
"KUBE_DNS_PORT_53_TCP_PROTO=tcp",
"KUBE_DNS_PORT_9153_TCP_PORT=9153",
"KUBERNETES_SERVICE_PORT=443",
"KUBERNETES_SERVICE_PORT_HTTPS=443",
"KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443",
"KUBE_DNS_PORT_53_UDP=udp://10.96.0.10:53",
"KUBE_DNS_PORT_53_UDP_PROTO=udp",
"KUBE_DNS_PORT_53_TCP_ADDR=10.96.0.10",
"KUBE_DNS_PORT_9153_TCP=tcp://10.96.0.10:9153",
"KUBERNETES_PORT=tcp://10.96.0.1:443",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"FLANNEL_ARCH=amd64"
]

发现根本没环境变量NODE_NAME但是有变量POD_NAMEPOD_NAMESPACE,也就是进入到代码if里,但是systemd跑下三个环境变量都没有,也就抛出错误退出。如果我们设置了环境变量NODE_NAME应该就会像开发者回复那样运行。

kube-flannel.yml思考了下逻辑:

  • net-conf.json包含所有 flanneldCIDR 信息文件挂载在目录/etc/kube-flannel/,手动创建目录和存放文件。当然可以看到源码 https://github.com/coreos/flannel/blob/master/subnet/kube/kube.go#L54 是固定文件名的
  • cni-conf.json文件同样挂载在目录/etc/kube-flannel/,但是initContainer逻辑复制到目录/etc/cni/net.d/10-flannel.conflist了,systemd脚本里可以写prestart exec啥的模仿,这里我偷懒直接改名放进去
  • flanneld的pod会在宿主机目录 /run/flannel 生成自己分配到的 cidr 和 mtu 信息文件,文件是连接 apiserver 后生成的,所以我们只需要创建目录
  • kube-flannel.yml 里的 clusterrole 和 sa 啥的都创建好然后用 sa 的 token 生成了 kubeconfig 给 flannel 使用
  • 准备二进制文件然后就是 systemd 脚本,明确 bind 哪张网卡,然后 ip ,还有健康检查 bind 的 ip 和 port 来用于监控。吐槽下 flannel 从 prometheus 的爸爸 coreos 出生居然到现在还没有 metrics 指标功能
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
cat<<EOF>/usr/lib/systemd/system/flanneld.service
[Unit]
Description=Network fabric for containers
Documentation=https://github.com/coreos/flannel
After=network.target
After=network-online.target
Wants=network-online.target

[Service]
Type=notify
Restart=always
RestartSec=5
# Kubernetes knows the nodes by their FQDN so we have to use the FQDN
#Environment=NODE_NAME=my-node.foo.bar.com
# Note that we don't specify any etcd option. This is because we want to talk
# to the apiserver instead. The apiserver then talks to etcd on flannel's
# behalf.
Environment=NODE_NAME={{ nodename }}
ExecStart=/usr/local/bin/flanneld \
--kube-subnet-mgr=true \
--kubeconfig-file=/etc/kubernetes/flanneld.kubeconfig \
--ip-masq=true \
--iface={{ INTERFACE_NAME }} \
--public-ip {{ inventory_hostname }} \
--healthz-ip {{ inventory_hostname }} \
--healthz-port {{ flanneld_healthz_port }} \
--v=2

k8s
k8s

上面部署已集成在我写的ansible部署里 https://github.com/zhangguanzhang/Kubernetes-ansible

CATALOG
  1. 1. 前言
  2. 2. 尝试
    1. 2.1. 不要在 etcd 写 flannel 配置了
    2. 2.2. 尝试和源码相关