Kubernetes Homelab 实验环境

Homelab 搭建也不能什么都没有,在之前解决了网络拓扑问题之后,便开始了 Kubernetes 在 Homelab 下的搭建运行。

和云环境之上的 Kubernetes 不同,还是踩到了一些小坑。

安装 Kubernetes

Kubernetes 在 1.7 版本时推出了 kubeadm,用于简化集群的安装和配置,这就解决了 Kubernetes
安装困难的一大痛点。

准备环境

Homelab 的好处就是可以开许多自己想要的虚拟机,这里我们为 Kubernetes 准备两台虚拟机:

  • K8S-MASTER-LAVA
    主节点
  • K8S-NODE-BEAT
    工作节点

安装 Kubeadm

这里我使用的是 Debian 9 环境,通过 Google 的官方源就可以安装 kubeadm

 1apt-get update
 2apt-get install -yq \
 3    apt-transport-https \
 4    ca-certificates \
 5    curl \
 6    gnupg2
 7apt-get update
 8
 9curl -fsSL  https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
10
11cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
12deb https://apt.kubernetes.io/ kubernetes-xenial main
13EOF
14
15apt-get update
16apt-get install -y \
17    ipvsadm ipset \ # for ipvs mode
18    kubelet kubeadm kubectl
19apt-mark hold kubelet kubeadm kubectl

初始化集群

官方安装指南的网络插件默认都使用 10.244.0.0/16 网段,这一网段和我的内网规划存在冲突,
因此自定义了一个网段 10.64.0.0/16 来代替默认设置。

登入主机 K8S-MASTER-LAVA,运行如下脚本:

1NETWORK_PLUGIN=https://raw.githubusercontent.com/coreos/flannel/a70459be0084506e4ec919aa1c114638878db11b/Documentation/kube-flannel.yml
2NETWORK_CIDR=10.64.0.0/16
3
4kubeadm init --pod-network-cidr=$NETWORK_CIDR
5
6export KUBECONFIG=/etc/kubernetes/admin.conf
7curl -sSL $NETWORK_PLUGIN | sed "s|10.244.0.0/16|${NETWORK_CIDR}|g" |
8  kubectl apply -f -

添加工作节点到集群

集群初始化完成后可以通过输出的一段命令来添加工作节点。

登入主机 K8S-NODE-BEAT,运行如下命令:

1kubeadm join K8S-MASTER-LAVA:6443 --token <YOUR_TOKEN> \
2  --discovery-token-ca-cert-hash sha256:<YOUR_CA_CERT_HASH>

遇到的问题

DNS 解析失败

在完成集群安装后,我部署了 gitlab-runner(k8s executor),在测试运行时却发生了问题,
在 gitlab-runner 中运行 Dind(Docker in Docker)时,产生的容器无法访问网络。

通过 tcpdump 等工具进一步排查后,发现网络是没有问题的,K8S 内的容器可以访问外网,但是无法解析域名

然后我找到了 /etc/resolv.conf,发现有一些小小的不同,search 字段的最后多了一个 lan:

1nameserver 10.96.0.10
2search default.svc.cluster.local svc.cluster.local cluster.local lan
3options ndots:5

关于 resolv.conf 的 search 和 ndots 这里不再详述,读者可以自行搜索。

正常情况下,这个 lan 的搜索域应该是可以正常处理的,但是通过 tcpdump 排查时,发现在查询一个域名时, 少发了一个查询请求,比如我们查询 example.com,正常情况下应有如下 FQDN 查询:

1example.com.example.com.default.svc.cluster.local.
2example.com.svc.cluster.local.
3example.com.cluster.local.
4example.com.lan.
5example.com.

但是 tcpdump 的结果少了最后一条 example.com. 这就导致我们无法访问外网。

最后问题的根源在于使用的基础 Docker 镜像为 alpine,这是一个精简镜像,使用 msul 作为 C 库。

msul 在 1.1.13 版本之前是不支持 search 和 ndots 选项的,之后的版本支持,
但是行为却和 glibc 不同

这里的问题在于搜索域列表达到四个时,不会查询根域 cert-manager#1161 这里也有相关的讨论。

解决方案有三种:

  • 修改 resolv.conf,使得列表域不超过三个
  • 不使用基于 msul 的镜像 alpine,使用 debian:9-slim
  • 部署 POD 时,自定义 dnsConfig,如下所示

custom-pod-dns.yml

 1apiVersion: v1
 2kind: Pod
 3metadata:
 4  namespace: default
 5  name: custom-pod-dns
 6spec:
 7  containers:
 8    - name: custom-pod-dns
 9      image: alpine
10      command:
11        - sleep
12        - "3600"
13  dnsPolicy: "None"
14  dnsConfig:
15    nameservers:
16      - 8.8.8.8
17    searches:
18      - lan
19    options:
20      - name: ndots
21        value: "2"

无法访问公网

同样是运行 DIND,发现集群 POD 的 DIND 运行的容器无法访问外网,但是 curl 拿到的错误提示为, Unknown SSL protocol error in connectioncurl -vv 查看发现证书握手到一半就停止了。

抓包诊断发现也是发包到一半就没了,开始以为是 NAT 问题,尝试了修改了宿主机的 docker 的 CIDR,但无法解决问题,后来想到可能是 MTU 导致的问题,Google 之找到了类似的问题。

分析原因,是因为 flannel 的集群网络默认 MTU 为 1450,由于 flannel 基于 vxlan,因此无法改变 MTU, 因此只能改自己的 MTU。

可以在 DIND 的运行参数上加上 --mtu=1450 即可解决此问题,但是同样存在连锁问题, 当使用 DIND 运行 docker-compose 时,docker-compose 并不会使用 dockerd 设置的 MTU,而是使用默认的 1500。

解决方法是配置 docker-compose 的网络使用指定 MTU:

1...
2networks:
3  default:
4    driver: bridge
5    driver_opts:
6      com.docker.network.driver.mtu: 1450

但这种方法入侵性太强,我更倾向于更干净的方式,比如考虑不使用 flannel 作为网络插件, 使用 Cilium 或者 Calico,Calico 的三层模式需要路由器 BGP 的支持,要求比较高和繁琐, 所以我选择了 Cilium 基于 BPF 的方案。

Cilium 支持三层平面网络,性能也不差,更拥抱 Service Mesh,紧密结合 Envoy,与 Calico 偏重 IDC 方向不同,Cilium 是偏重应用架构的容器网络方案。

Cilium 1.3 更是可以使用 Go 作为 Envoy 的扩展,可以自行编写 filter chain 扩展,有关这方面的内容可另写一篇文章详述,这里就此带过。

顺带一提,Cilium 也是 DigitalOcean Kubernetes Cluster 采用的网络方案,同时也被 Google 大力推广。

使用 Cilium 作为集群的网络方案后,docker-compose MTU 的问题也就此解决。

参考

View Comments