一般情况下我不喜欢把部署手册放到blog里。绝大多数情况下官网已经足够详尽,而且blog很可能因为版本陈旧误人子弟。曾经我写过Nginx的二进制部署手册,早就被轻松愉快的yum安装扫进了废纸堆。而使用了Docker和K8s后yum安装方式也被迅速淘汰。Hadoop的部署也被Cloudera全自动化部署替代了。
但kubernetes的部署由于涉及科学上网的问题,把原本几个命令就能解决的问题搞得相当复杂。所以希望这篇也能多少对还在被GFW恶心的人有些帮助。(当然可能更简单的方式是部署在墙外,比如AWS上)
0. 部署目标和硬件准备
0.1 部署目标
由于是测试目的,就不部署高可用了。高可用的部署可以参见最后的参考资料。
物理拓扑结构是1 Master + 3 Worker(Worker数量可轻松扩展)。
0.2 硬件准备
部署的Kubernetes版本是v1.14.2(截止2019/5/29的最新版本),Docker的版本是Docker CE 18.09.6(也是截止2019/5/29的最新版本)。
服务器全是VMWare虚拟机。虚机的硬件和操作系统如下:
HOSTNAME | ip | ROLES | 硬件配置 | 操作系统 |
---|---|---|---|---|
docker-4 | 10.16.34.54 | master | 4核CPU/8GB内存/100GB硬盘 | CentOS 7.4 |
docker-5 | 10.16.34.57 | worker | 4核CPU/8GB内存/100GB硬盘 | CentOS 7.4 |
docker-6 | 10.16.34.58 | worker | 4核CPU/8GB内存/100GB硬盘 | CentOS 7.4 |
docker-7 | 10.16.34.59 | worker | 4核CPU/8GB内存/100GB硬盘 | CentOS 7.4 |
下面所有的命令都是在root账号下执行的。
1. 检查和配置操作系统
1.1 检查操作系统/硬件配置/网络连通性
按照安装 kubeadm - Kubernetes检查操作系统/硬件配置/网络连通性。主要检查节点之中不可以有重复的主机名,MAC 地址,product_uuid。
1.2 配置hostname
根据官方文档的kubeadm问题排查,需要确保hostname -i命令返回可路由的ip。
我拿到的虚机默认只会返回127.0.0.1。这个可能导致了后续配置过程中Worker节点在join后一直NotReady的问题。所以以防万一还是在每个节点上配置一下比较保险。
1 | vi /etc/hosts |
添加内容:
1 |
|
然后重启network
1 | systemctl restart network |
1.3 禁用swap
1 | sudo swapoff -a |
kubelet在swap不禁用的情况下会报错:
kubelet[2856]: error: failed to run Kubelet: Running with swap on is not supported, please disable swap! or set –fail-swap-on
K8S这么设计的原因主要是性能考量:Kubernetes会把每个node实例尽量压榨到利用率100%,包括CPU和内存。而swap出来的虚拟内存的性能远比不上真实内存,会影响调度器对机器余力的判断。
1.4 禁用selinux
1 | 将 SELinux 设置为 permissive 模式(将其禁用) |
禁用SELinux是因为kubelet还不支持。不然容器访问不了宿主机的文件系统,也就没法使用Pod网络。
1.5 RHEL/CentOS7相关iptables配置
1 | cat <<EOF > /etc/sysctl.d/k8s.conf |
1.6 开启端口
理论上需要开这些端口:
Master 节点
1 | sudo firewall-cmd --zone=public --permanent --add-port=6443/tcp |
Worker 节点
1 | sudo firewall-cmd --zone=public --permanent --add-port=10250/tcp |
不过对于测试环境来说,为了以防未知的坑,还是直接关闭掉防火墙比较直接。之后在部署Rook的时候,apply -f operator.yaml后Pod的状态一直为CrashLoopBackOff或Error。
查看Event日志得到了如下的错误信息:
1 | State: Waiting |
通过kubectl get svc -n=kube-system 命令查询service,发现kube-dns还需要开启53/UDP,53/TCP,9153/TCP这三个端口。kubernetes-dashboard也需要443端口。
在关闭防火墙后,rook-ceph部署成功。
综上所述,将本步骤修改为:
1 | systemctl stop firewalld |
2. 安装容器运行时(CRI)-Docker
2.1 安装Docker
1 | yum -y install yum-utils device-mapper-persistent-data lvm2 |
2.2 启动Docker服务
1 | systemctl daemon-reload |
3. 安装Kubernetes
需要在每台机器上都安装以下的软件包:
- kubeadm: 用来初始化集群的指令
- kubelet: 在集群中的每个节点上用来启动pod和container等
- kubectl: 用来与集群通信的命令行工具
3.1 准备repo
这里开始和科学上网有关了。要把repo地址里的packages.cloud.google.com都替换成很阿里云的域名mirrors.aliyun.com/kubernetes。
gpgcheck可以保留为1,不过这里以防万一我改为了不check。
1 | cat <<EOF > /etc/yum.repos.d/kubernetes.repo |
3.2 开始安装kubelet/kubeadm/kubectl
1 | yum -y install kubelet kubeadm kubectl --disableexcludes=kubernetes |
有些blog里提到还需要yum install kubernetes-cni。实际发现执行完上面的命令已经安装好了。大概最新版的kubeadm已经包含了kubernetes-cni。
有些部署手册里依赖的是比较早版本的Kubernetes,可以在安装的时候指定版本:
1 | yum install kubelet=1.11.3-00 |
3.3 启动kubelet服务
1 | systemctl daemon-reload |
由于上一个步骤里yum安装的时候没有指定版本,这时候就可以通过kubectl version查到yum安装的Kubernetes版本。
1 | [root@docker-4 ~]# kubectl version |
connection refused的报错信息可以先无视。
3.4 在Master节点上创建kubeadm init的配置文件kubeadm.yaml
可以把kubernetes的YAML配置文件放在任何路径下。我这里是放到root的HOME目录/root/下。
1 | cd ~ |
然后创建一份kubeadm init的配置文件kubeadm.yaml如下:
1 | apiVersion: kubeadm.k8s.io/v1beta1 |
对于旧版本(例如1.11),apiVersion是kubeadm.k8s.io/v1alpha1:
1 | apiVersion: kubeadm.k8s.io/v1alpha1 |
3.5 确定拉取的镜像版本
如果服务器在墙外,那么就可以kubeadm init –config kubeadm.yaml,然后去泡杯茶慢慢等着了。但如果不是的话,你会看到如下的错误:
1 | error execution phase preflight: [preflight] Some fatal errors occurred: |
原因就是国内连不上gcr.io。
如果你清楚知道kubeadm init使用的每个镜像的版本,那么你可以直接去按下一节的步骤去拉取镜像。
但如果你不确定的话,还是先执行一遍kubeadm init命令,从错误信息里获取当前版本Kubernetes使用的各镜像的版本,以便下一节的pullimages.sh脚本中指定。
kubeadm需要的镜像包括:kube-proxy/kube-scheduler/kube-controller-manager/kube-apiserver/etcd/coredns/pause。
对于v1.14.2,具体版本如下:
kube-proxy:v1.14.2 kube-scheduler:v1.14.2 kube-controller-manager:v1.14.2 kube-apiserver:v1.14.2 etcd:3.3.10 coredns:1.3.1 pause:3.1
3.6 拉取镜像
这个步骤是最麻烦的。
如上一节所示,直接pull的话会失败。网上大多数文章中推荐docker hub上的一个个人的镜像站:anjia0532/gcr.io_mirror:。但这个镜像站已经被Travis CI标记为疑似滥用,所以最新的几个版本都没有同步了。
所以现在推荐使用的是Azure中国的镜像站。就是对于从k8s.gcr.io拉取的docker pull命令,从gcr.azk8s.cn/google-containers拉取。
举个具体的例子。比如要拉取kubernetes dashboard v1.10.1,原本的命令为:
1 | docker pull k8s.gcr.io/kubernetes-dashboard-amd64:v1.10.1 |
现在改为:
1 | docker pull gcr.azk8s.cn/google-containers/kubernetes-dashboard-amd64:v1.10.1 |
然后还可以打一个标记,覆盖k8s.gcr.io的同名镜像。
对于kubeadm需要的镜像,可以通过如下的脚本一次性获取
1 | cd ~ |
添加内容:
1 | #!/bin/bash |
(最后一句rmi的意义暂时没搞懂,为啥最后要把Azure的镜像删除掉。。。但的确能work,所以姑且按照网上的脚本来)
执行脚本:
1 | chmod +x pullimages.sh |
不太确定的一点是要不要在所有的Worker Node上都执行pullimages.sh。如果遇到Worker Node一直是NotReady的话,可以在服务器上也执行一下。
PS. 也可以用阿里云的镜像,例如
1 | docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kubernetes-dashboard-amd64:v1.10.1 |
3.7 在Master节点上执行kubeadm init
先配置停用
1 | vi /etc/sysconfig/kubelet |
将内容修改为:
1 | KUBELET_EXTRA_ARGS="--fail-swap-on=false" |
然后就可以执行kubeadm init命令了。具体执行时间看网速,我这里大概总共3分钟。
1 | kubeadm init --config kubeadm.yaml |
如果成功的话会显示如下内容:
1 | Your Kubernetes control-plane has initialized successfully! |
然后按照提示在Master节点上执行剩下的命令:
1 | mkdir -p $HOME/.kube |
保存好那条kubeadm join的命令。注意只有这个token只有当天有效。隔了24小时之后就需要kubectl create token重新创建token。
3.8 部署网络插件Weave
在进行接下来的步骤之前先不要急,要先确认所有的node状态和pod状态。
依次检查健康状态
1 | kubectl get cs |
节点状态
1 | kubectl get nodes |
如果是测试用的单节点部署,需要运行以下命令,去掉master节点的污点:
1 | kubectl taint nodes --all node-role.kubernetes.io/master- |
系统Pod状态
1 | kubectl get pods -n kube-system |
当前由于没有部署网络插件,所以coredns的Pod的状态还是Pending。
确保除了coredns之外的Pod都是running后,部署Weave插件:
1 | kubectl apply -f https://git.io/weave-kube-1.6 |
通过如下命令,等待Weave的Pod也正常running状态后,才能继续后续的kubeadm join操作
1 | kubectl get pods -n kube-system |
3.9 Worker节点加入
在每个Worker节点上执行1.1到3.3,以及3.6步骤后,执行join命令:
1 | kubeadm join 10.16.34.54:6443 --token hfzcd2.xhqca62fjjbmq7xh \ |
在Master上观察各节点状态,直到全部Ready。
1 | kubectl get nodes |
3.10 设置Worker角色
通过kubeadm join加入的节点的默认角色为none,需要再标记为worker:
1 | kubectl label node docker-5 node-role.kubernetes.io/worker=worker |
最终节点状态:
1 | [root@docker-4 ~]# kubectl get nodes |
最终系统Pod状态:
1 | [root@docker-4 ~]# kubectl get pods -n kube-system |
有些时候状态没Ready不要急,先泡杯茶去。有些操作要花一些时间的。包括之后kubectl的一些操作,拍下回车后有时候会没有UI反馈内容。如果这个时候没有耐心地Ctrl+C中止,可能产生一些不可知的后遗症。在apply多个yaml的时候,也最好在每个步骤结束后确认全部的Pod状态是Running,再进行下一个步骤。
如果泡完茶依然有问题,再按照下一章的排查步骤来排查。
3.11 验证
可以通过部署一个Nginx的Pod来进行验证。
1 | cd ~ |
输入内容:
1 | apiVersion: apps/v1 |
然后执行
1 | kubectl apply -f nginx-deployment.yaml |
最后验证Pod状态:
1 | [root@docker-4 ~]# kubectl get pods |
验证完如果不需要的话可以删除:
1 | kubectl delete -f nginx-deployment.yaml |
4. 问题排查
安装的过程肯定不可能一帆风顺。知道怎么排查很重要。
4.1 排查节点问题
如果怀疑是节点问题,可以通过如下的方式来查看节点状态:
1 | kubectl describe nodes |
重点看Conditions下的Message。
4.2 排查Pod
如果是系统Pod,可以通过如下命令首先查看Pod状态:
1 | kubectl get pods -n kube-system |
然后describe节点查看
1 | kubectl describe pod <Pod名> -n kube-system |
如果是普通Pod,就把命令最后的-n kube-system去掉:
1 | kubectl get pods |
然后describe节点查看
1 | kubectl describe pod <Pod名> |
重点都是看最后的Events。
4.3 其他日志
有些时候Events比较简略,就需要查看日志。特别如果问题是在Worker Node上,没法执行kubectl命令,只能查看日志。
查看方式有两种:
1 | journalctl -l -u kubelet |
或者
1 | tail -f /var/log/messages |
还可以通过grep来缩小排查范围。
4.4 重置状态
在尝试解决网络插件问题的时候,我曾经病急乱投医地装了个flannel。但Pod状态始终处于ContainerCreating状态。后来Weave恢复后尝试通过kubectl delete命令删除flannel,遇到了Pod一直terminating但删除不掉的症状。雪上加霜的是还出现了硬件的告警:“kernel:NMI watchdog: BUG: soft lockup - CPU#1 stuck for 22s”。尝试reboot服务器居然发生了超时:“Failed to start reboot.target: Connection timed out”。
万策已尽,只能请运维直接干掉虚机重装了。换了台机器继续装Master节点。但几个Worker Node已经join了原Master。这时候就要靠reset命令重置:
1 | kubeadm reset |
需要注意reset完可能需要执行以下命令:
1 | echo 1 > /proc/sys/net/ipv4/ip_forward |
要不然可能会遇到以下的报错信息:
1 | [ERROR FileContent--proc-sys-net-ipv4-ip_forward]: /proc/sys/net/ipv4/ip_forward contents are not set to 1 |
Master节点如果不是到我遇到的这个情况也可以reset。
5. 尚未确认的问题
下面是一些我遇到过的问题。在解决过程中进行了不少操作,不太确定到底是其中具体哪个操作起到了决定性作用。所以姑且把我做过的事情都记录一下。
runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized
在Worker节点加入后一直显示NotReady。查看node状态,在message里看到了如上的消息。
尝试过关闭防火墙,怀疑过虚拟机网卡问题。
怀疑可能有两个措施可能最终产生效果:
- 按照1.2配置/etc/hosts
- 在每个Worker Node上也执行pullimages.sh拉取镜像
极客时间的评论里有人因为多网卡而失败过,也摘抄一下备忘吧。
2、卡在多网卡的问题上。
2.1、我的环境是virtual box上虚拟的两个ubuntu,网络设置为nat+host only,集群搭建好之后,死活无法启动dashboard、ceph的容器(好多老外也是这么弄的啊),各种查各种试,也没解决问题。在kubernetes的官网上只说了“If you have more than one network adapter, and your Kubernetes components are not reachable on the default route, we recommend you add IP route(s) so Kubernetes cluster addresses go via the appropriate adapter.”。哪位大神按照这种方式弄好的清指点下,很是困惑啊啊啊啊,谁能解救我下………………
2.2、放弃了2.1的nat+host only,改为了桥接的网络方式,只保留一个network interface,成功。
rpc error: code = DeadlineExceeded异常,导致Pod持续处于ContainerCreating状态
在部署完后发生过所有Node都已经Ready,但apply的Pod(包括系统插件的kubernetes dashboard和自定义的nginx)一直处于ContainerCreating状态的情况。
在Worker上看到的日志中报rpc error: code = DeadlineExceeded:
1 | May 29 01:51:10 docker-7 kubelet: E0529 01:51:10.545968 21276 kuberuntime_manager.go:693] createPodSandbox for pod "kubernetes-dashboard-5f7b999d65-5jwpl_kube-system(a3e6ab24-81d4-11e9-935a-00505695705b)" failed: rpc error: code = DeadlineExceeded desc = context deadline exceeded |
这个问题网上信息非常有限。采取了很多措施,最后也不知道哪个起效了。怀疑是又执行了一遍“1.5 RHEL/CentOS7相关iptables配置”产生了效果。
CrashLoopBackOff状态
装Minikube的时候还遇到过core-dns一直处于CrashLoopBackOff状态,也记录一下:
1 | [root@docker-1 ~]# kubectl get pods --all-namespaces |
message如下:
1 | Error restarting cluster: wait: waiting for component=kube-apiserver: timed out waiting for the condition |
靠停用防火墙后删除重装minikube解决了。暂时不确定是否和防火墙有关。
1 | minikube stop |
最终感谢阿里云提供repo的镜像,微软Azure云提供Docker镜像。
F*ck GFW
6. 参考资料
如果是个人学习目的的话,Minikube就已经够用了,安装比上述步骤简单不少。当然科学上网的问题还是要解决。
Install Minikube - Kubernetes
这篇是基于极客时间课程的搭建步骤,也是相对比较完整的。
centos7快速搭建Kubernetes 1.11.1单机集群-data羊
官方文档的CRI和kubeadm安装手册。如果服务器在墙外直接照着操作就行。
安装 kubeadm - Kubernetes
CRI installation - Kubernetes
kubeadm高可用部署的官方文档。
Creating Highly Available Clusters with kubeadm - Kubernetes
如果没有访问外网的话可以参考这篇。但我部署的时候真不想遇到这种情况。。。
kubeadm init - 在没有互联网连接的情况下运行 kubeadm
补充一个Ansible部署K8S的开源项目(尚未试用)
easzlab/kubeasz: 使用Ansible脚本安装K8S集群,介绍组件交互原理,方便直接,不受国内网络环境影响
还有一个部署生产级别K8S的开源项目(尚未试用)
kubernetes-sigs/kubespray: Deploy a Production Ready Kubernetes Cluster