
K8S实践0在CentOS 7.6上从零搭建Kubernetes v1.14.2集群这份避坑指南请收好如果你正打算在生产或测试环境部署Kubernetes集群又恰好手上是一批CentOS 7.6的机器那我这篇实操记录应该能帮你省掉不少弯路。v1.14.2这个版本虽然不算新但作为kubeadm正式进入GA稳定阶段后的一个代表性版本它兼顾了稳定性和功能的平衡很多企业内部至今还在跑这个系列的集群。这篇文章会完整走一遍环境准备、Docker安装、kubeadm部署、集群初始化、网络插件接入和常见故障排查适合刚接触容器编排的运维同学也适合那些想搞明白K8S安装背后每一步到底在做什么的人。我会把当时踩过的坑、花过时间排查的问题都摊开来讲。不是简单的命令复制粘贴而是解释每条命令背后的原因和验证方法这样你即使换一个版本也能自己排查问题。1. 整体设计与部署方案选型1.1 为什么选择v1.14.2和kubeadm先聊一下版本选型的问题。Kubernetes的版本迭代很快当前最新版本已经在向v1.30迈进了但这不代表旧版本就该被遗忘。v1.14.2是我当时在生产环境验证过的版本它有几个明显的优势第一它的API版本和核心组件没有出现后来那种频繁的破坏性变更学习曲线平缓第二kubeadm在这个版本已经能够很稳定地完成 HA 场景下的部署证书管理也足够清晰第三周边生态兼容性好Calico、Flannel、CoreDNS等组件都有成熟的适配版本不容易出现依赖冲突。如果你是一个K8S新手直接上最新版容易遇到网上资料和命令行参数对不上号的窘境。用v1.14.2起步很多文档、插件版本匹配都能对上等掌握了核心概念之后再升级会顺畅很多。1.2 网络方案选型考量网络方案是部署过程中最先遇见的三岔路口。我当时在Flannel和Calico之间纠结了很久最终选了Calico。原因很简单Calico支持网络策略的精细化控制这对以后做多租户隔离、服务间访问控制非常关键而Flannel配置虽然更简单但也意味着后期如果想做更复杂的网络策略得引入额外的方案来弥补。Calico的劣势是组件多一些etcd、Felix、BIRD等对新手看起来重但它采用的是BGP路由模式Pod之间的通信延迟更低长连接场景下比VXLAN封装开销小很多。如果对网络性能有要求这个选择很值得。1.3 节点规划与资源配置部署集群前一定要想清楚节点怎么规划。我当时用的是三台机器角色主机名配置用途Masterk8s-master14C8G控制平面API Server、Scheduler、Controller Manager、etcdNodek8s-node14C8G运行业务Pod和Ingress ControllerNodek8s-node24C8G运行业务Pod和监控组件为什么Master不建议低于2C4G因为etcd和API Server都很吃内存内存太小会导致etcd频繁触发Raft心跳超时集群会变得极其不稳定。硬盘方面有条件尽量上SSD尤其是etcd所在的节点SSD和机械盘的性能差距在大量Pod频繁变更时会非常明显地暴露出来。另外所有节点建议关闭Swap。K8S的kubelet在默认配置下如果检测到Swap开启会因为cgroup配置冲突直接拒绝启动。2. 系统初始化与环境依赖准备2.1 操作系统基础配置拿到全新的CentOS 7.6机器后不要急着装Docker先把系统层面的基础配置做完。这里有几条命令是每个节点都要执行的顺序无所谓但内容尽量一致# 关闭SELinux setenforce 0 sed -i s/^SELINUXenforcing/SELINUXdisabled/ /etc/selinux/config # 关闭防火墙K8S的Service和Pod网络由iptables管理firewalld会干扰 systemctl stop firewalld systemctl disable firewalld # 配置主机名解析 echo 192.168.1.100 k8s-master1 /etc/hosts echo 192.168.1.101 k8s-node1 /etc/hosts echo 192.168.1.102 k8s-node2 /etc/hosts # 关闭Swap swapoff -a sed -i / swap / s/^/#/ /etc/fstab为什么要动SELinuxK8S的Volume挂载、文件权限管理方式和SELinux的强制访问控制策略在部分场景有冲突虽然也能通过设定semanage规则解决但对于多数企业团队来说直接关闭是效率最高的选择。生产环境如果安全要求极高可以重新开启并逐个配置策略但新手阶段千万别给自己挖坑。防火墙同理。K8S依赖iptables做Service负载均衡和DNAT规则firewalld虽然底层也是iptables但它在动态管理规则时经常会把kube-proxy写好的规则给重置掉导致服务时不时访问不通排查起来非常折磨人。2.2 内核参数调整这一步很容易被忽略但非常关键。K8S的Pod网络和iptables转发依赖几个内核参数的设置cat /etc/sysctl.d/k8s.conf EOF net.bridge.bridge-nf-call-ip6tables 1 net.bridge.bridge-nf-call-iptables 1 net.ipv4.ip_forward 1 vm.swappiness 0 EOF modprobe br_netfilter sysctl -p /etc/sysctl.d/k8s.conf其中net.bridge.bridge-nf-call-iptables这个参数的作用是把桥接的IPv4流量也交给iptables处理。K8S的Pod之间通信很多都会经过网桥如果不开启这个参数就意味着会有部分流量绕过iptables规则直接走二层转发Service的ClusterIP访问规则就完全失效了。你在集群搭建完成后建好Service却发现Pod之间怎么都ping不通十有八九就是这里的问题。vm.swappiness设置为0是为了降低系统使用Swap的倾向前面关闭Swap是一方面设这个参数是为了防止那些没被swapoff彻底禁用的匿名页回收导致性能抖动。2.3 配置YUM源与基础组件CentOS 7.6自带的YUM源在国内访问速度不太乐观Docker的官方仓库也不稳定。我当时的做法是替换为国内镜像源然后安装一些后续排障可能用到的组件# 备份原yum源 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak # 下载阿里云CentOS 7的repo文件 curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo # 安装必要组件 yum install -y wget curl vim net-tools telnet ntpdate lsofntpdate这一条尤其重要。集群节点的时钟如果偏差太大K8S组件之间的通信证书校验会出问题etcd也容易出现选举异常。建议所有节点都配置好NTP同步ntpdate -u ntp.aliyun.com crontab -e # 添加一行每小时同步一次 0 * * * * /usr/sbin/ntpdate -u ntp.aliyun.com /dev/null 213. Docker安装与配置3.1 Docker版本选择与安装K8S v1.14.2官方适配的Docker版本是18.06或18.09我当时选择了Docker CE 18.09.x整体运行非常稳定。不要用太新的Docker版本因为Docker和K8S的版本兼容矩阵是有明确对应关系的新版本Docker底层cgroup、iptables的行为如果有调整kubelet可能无法感知到最终导致Pod创建成功但状态异常。安装Docker时官方repo经常拉不动我直接配了阿里云的Docker镜像源yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo yum makecache fast yum install -y docker-ce-18.09.93.2 Docker的Cgroup驱动调整这是部署过程中最容易踩、但排查时又最隐蔽的一个坑。K8S的kubelet默认使用systemd作为cgroup驱动而Docker的默认驱动是cgroupfs。如果两者不一致kubelet在启动后会报类似failed to set cgroup config for ...的错误Pod创建后一直处于ContainerCreating状态。在启动Docker之前先修改Docker的daemon.jsonmkdir -p /etc/docker cat /etc/docker/daemon.json EOF { registry-mirrors: [https://xxxxx.mirror.aliyuncs.com], exec-opts: [native.cgroupdriversystemd] } EOF systemctl daemon-reload systemctl start docker systemctl enable dockerregistry-mirrors这里填入你自己的阿里云容器镜像加速器地址每个账号都有一个独立地址。没有加速器的话后面pull镜像会非常痛苦。执行完docker info可以验证一下状态看到Cgroup Driver: systemd就说明配置生效了。这里想多提醒一句native.cgroupdriversystemd这个配置一定要在Docker刚装完、还没创建任何容器的时候就设置好否则Docker数据目录里已有的容器元数据会和新cgroup驱动不匹配。3.3 验证Docker环境基础配置做完了先简单验证一下Docker能不能正常跑容器docker run --rm hello-world如果这一步就卡住问题多半在镜像源或者网络。多试几次有报错就能看到具体原因别急着往下走集群搭建最忌讳的就是“一路盲跑最后一起爆炸”。4. kubeadm与K8S核心组件安装4.1 添加Kubernetes YUM源安装Kubernetes的组件最重要的是添加正确的YUM源。当时官方源经常连不上我直接配了阿里云的K8S源cat /etc/yum.repos.d/kubernetes.repo EOF [kubernetes] nameKubernetes baseurlhttps://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled1 gpgcheck0 repo_gpgcheck0 gpgkeyhttps://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg EOF yum makecache fast注意gpgcheck0这个设置是为了跳过GPG密钥校验。如果想更安全可以保留gpgcheck1但需要额外导入密钥文件实际操作中很多人都是在这个环节被密钥问题卡住白白耗掉半小时。4.2 安装kubeadm、kubelet、kubectl确定版本号这一步建议先yum list kubeadm --showduplicates | sort -r查看可以用哪些版本。因为yum默认会装最新版直接安装很可能装上v1.20那时kubelet和kubeadm的启动参数已经大变样了我们这套流程就没用了。指定版本安装yum install -y kubeadm-1.14.2-0 kubelet-1.14.2-0 kubectl-1.14.2-0装完之后有个重要操作需要把kubelet设置成开机自启但此时先不要启动它systemctl enable kubelet为什么先enable不start因为kubelet在集群初始化之前会不断尝试连接API Server日志会持续报错并没有实际意义。只要设好自启等kubeadm init生成配置文件之后再启动kubelet才是正确姿势。4.3 kubelet启动参数相关的隐藏坑v1.14版本的kubelet通过kubeadm生成的systemd unit启动但实际上核心启动参数都写在/etc/sysconfig/kubelet文件里。如果你在多网卡机器上部署必须提前在这里加上--node-ip参数指定K8S内部的节点通信IPcat /etc/sysconfig/kubelet EOF KUBELET_EXTRA_ARGS--node-ip192.168.1.100 EOF如果不显式指定kubelet会自动选择一个默认路由对应的网卡IP。如果你的机器有多块网卡这个自动选择很可能会选错导致Master和Node之间的通信走了一个奇怪的地址Pod状态一直Pending且日志显示节点未就绪。这个问题我踩过当时排查了整整一下午。5. 集群初始化与Pod网络安装5.1 准备镜像kubeadm init过程中要从Google的镜像仓库拉取一堆组件镜像kube-apiserver、kube-controller-manager、kube-scheduler、etcd、pause等。国内网络访问不了Google仓库所以必须先通过阿里云等镜像源把镜像拉下来再重新打成对应的tag。我用脚本批量处理了这一步kubeadm config images list --kubernetes-version v1.14.2 # 输出类似 # k8s.gcr.io/kube-apiserver:v1.14.2 # k8s.gcr.io/kube-controller-manager:v1.14.2 # k8s.gcr.io/kube-scheduler:v1.14.2 # k8s.gcr.io/kube-proxy:v1.14.2 # k8s.gcr.io/pause:3.1 # k8s.gcr.io/etcd:3.3.10 # 从阿里云镜像仓库拉取再打回原tag docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver:v1.14.2 docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver:v1.14.2 k8s.gcr.io/kube-apiserver:v1.14.2以此类推每个镜像都做一遍pulltag。也可以用一个小循环脚本完成关键是确保所有镜像在docker images里都能看到对应的k8s.gcr.io/前缀的tag。这一步有个细节值得注意kubeadm config images list看到的镜像列表中可能包含coredns但coredns并不是属于k8s.gcr.io的仓库而是属于coredns/coredns。具体版本是1.3.1对应v1.14.2。也需要一并拉下来打tag。5.2 执行kubeadm init镜像准备好了开始初始化Master节点。这里我根据自己的实际网络环境指定了Pod网段kubeadm init \ --kubernetes-versionv1.14.2 \ --pod-network-cidr10.244.0.0/16 \ --apiserver-advertise-address192.168.1.100 \ --image-repository registry.cn-hangzhou.aliyuncs.com/google_containers这里重点解释一下参数的选择--pod-network-cidr10.244.0.0/16这个网段必须和你后续使用的网络插件匹配。Flannel默认就是10.244.0.0/16Calico默认用的通常是192.168.0.0/16。我当时最终选择了Calico但这里却用了10.244.0.0/16不对这是我当时留下的一个坑。为了避免误导建议如果后续要装Calico--pod-network-cidr填192.168.0.0/16如果装Flannel则填10.244.0.0/16。--apiserver-advertise-address是你Master节点对外提供API服务的IP。多网卡环境下这个参数必须显式指定否则kubeadm会按默认路由选一个IP万一选成了内网网卡的另一个地址kubectl配置文件里的server地址就会指向一个错误IP。--image-repository这个参数其实在v1.14里还支持可以不用提前手动拉镜像但当时我为了保险起见还是先手动拉了一遍。后来发现其实只要这个参数填对了kubeadm会直接从国内镜像仓库拉取不需要自己手动打tag。初始化成功后终端会输出一段明显的提示信息包括三个关键内容kubeadm init的完整join命令用于后续添加节点、拷贝KUBECONFIG的指令、以及一些安全提示。5.3 配置KUBECONFIGkubeadm init完成后普通用户是无法直接使用kubectl的需要把生成的admin配置复制到当前用户的.kube目录下mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config这一步如果不做执行kubectl get nodes时会报权限不足或者连不上API Server。很多新手以为集群装坏了实际上只是配置没拷贝。配置好后可以先验证一下kubectl get nodes这时Master节点应该已经显示为NotReady状态。这是正常的因为网络插件还没安装节点状态会一直停留在NotReady。5.4 安装Calico网络插件Calico的安装文件在v1.14时期可以直接通过YAML清单完成部署wget https://docs.projectcalico.org/v3.7/manifests/calico.yaml kubectl apply -f calico.yaml但这里有几个版本匹配问题值得注意。Calico v3.7对K8S v1.14有对应的兼容性说明如果你下载的是过新的Calico版本它可能会要求新的RBAC对象或使用新版本的CRD导致一些资源无法识别。选版本时尽量参考官方兼容矩阵。另外如果没修改calico.yaml里的pod网段默认就是192.168.0.0/16。如果你kubeadm init时用的是其他网段记得在YAML文件里找到CALICO_IPV4POOL_CIDR环境变量改成你自己的实际值。我当时就是因为没改这个导致Pod一直分配不到IP网络插件初始化后始终不正常。安装完成后等待Pod启动kubectl get pods -n kube-system看到calico-node和calico-controllers都处于Running状态再执行kubectl get nodesMaster节点应该就会变成Ready。这一步看不到Ready的话一定先查calico的Pod日志而不是反复去调网络插件参数。5.5 扩容Worker节点MasterReady之后接着执行kubeadm init输出到最后的join命令把两个Node节点加进来。join命令长这样kubeadm join 192.168.1.100:6443 --token xxxxxx \ --discovery-token-ca-cert-hash sha256:xxxxxxNode节点加入集群后过30秒左右执行kubectl get nodes应该能看到三个节点都处于Ready状态。如果join时报错说token过期在Master上重新生成一个token即可kubeadm token create --print-join-command注意这个命令打出来的join命令已经包含了最新的token和证书hash直接用就行。6. 部署验证与集群状态检查6.1 验证核心组件健康状态集群搭建完成不等于一切正常先用一套完整的检查流程确认集群真正可用# 查看节点状态 kubectl get nodes # 查看系统组件Pod状态 kubectl get pods -n kube-system -o wide # 查看组件健康情况 kubectl get cskubectl get cs会显示etcd、scheduler、controller-manager的健康状态。如果是Healthy说明控制平面工作正常。但注意有的版本如果API Server启用了匿名认证或安全端口配置变化get cs可能会报错这时候更可靠的方式是直接看各组件Pod的状态。6.2 用示例应用做端到端验证节点就绪只能说明K8S控制平面是通的Pod网络和Service转发是否正常还需要实际部署一个应用来验证。我当时用了一个非常经典的nginx测试kubectl create deployment nginx-test --imagenginx:1.14 kubectl expose deployment nginx-test --port80 --target-port80 --typeNodePort kubectl get svc nginx-test拿到NodePort分配的端口后从集群外部访问任意节点的IP加这个端口能打开nginx默认首页说明整个链路都是通的。如果这一步访问不通优先检查三个方面第一NodePort端口范围默认30000-32767有没有被防火墙拦截第二kube-proxy的Pod是否正常运行第三确认Service的Endpoints不为空执行kubectl get endpoints nginx-test看有没有对应的Pod端点。6.3 验证CoreDNS功能CoreDNS是集群内部的域名解析服务很多应用启动后都会依赖它进行内部名字解析。测试方法很简单kubectl run -it --rm dns-test --imagebusybox -- sh进入容器后执行nslookup kubernetes.default.svc.cluster.local能解析出ClusterIP说明CoreDNS工作正常。如果卡在这里多半是CoreDNS的Pod没有Running或者calico网络还没完全起来。这个地方不要急等待1-2分钟再试因为IP池分配有时候需要几秒到几十秒的时间。7. 常见问题与排查技巧实录7.1 镜像拉取失败类问题我在部署过程中遇到的第一类问题就是镜像拉取失败。解决思路是既然kubeadm config images list能列出需要的所有镜像那就提前按照列表一个一个拉拉不到就去国内镜像源拉。这里列一下常用地址原始地址替代地址k8s.gcr.io/kube-apiserverregistry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserverk8s.gcr.io/kube-controller-managerregistry.cn-hangzhou.aliyuncs.com/google_containers/kube-controller-managerk8s.gcr.io/kube-schedulerregistry.cn-hangzhou.aliyuncs.com/google_containers/kube-schedulerk8s.gcr.io/kube-proxyregistry.cn-hangzhou.aliyuncs.com/google_containers/kube-proxyk8s.gcr.io/etcdregistry.cn-hangzhou.aliyuncs.com/google_containers/etcdk8s.gcr.io/pauseregistry.cn-hangzhou.aliyuncs.com/google_containers/pause确认所有镜像的tag都正确后再执行kubeadm init能极大减少中途失败的几率。7.2 节点NotReady与kubelet报错节点一直NotReady是最让人头疼的问题。排查路径一般是journalctl -u kubelet -f如果日志中反复出现calico相关的网络错误检查Pod网段是否一致如果出现cgroup相关报错检查Docker的cgroup驱动如果出现权限类错误检查SELinux是否关闭。这里有一个秘密武器kubectl describe node xxx。这个命令能看到节点上报的条件和错误信息比盲目看日志高效得多。7.3 Pod一直Pending或ContainerCreatingPod处于Pending状态通常是调度失败。原因可能是节点资源不足、节点存在taint、或者PVC无法绑定。用kubectl describe pod xxx查看事件一般能看到详细的调度失败原因。Pod处于ContainerCreating原因则更多集中在镜像拉取失败、Volume挂载失败、CNI网络配置失败。我遇到最多的就是CNI问题Pod事件里通常有network: failed to set up pod xxx network: failed to set bridge addr之类的报错。这种时候先看calico的Pod是否正常再检查ip a看是否有calico网卡出现。7.4 kubelet频繁重启的排查实录这个场景我特别想分享。有一次搭建完集群发现每个Node的kubelet都在以30秒左右间隔反复重启日志里出现了同一个错误failed to run Kubelet: failed to create kubelet: misconfiguration: kubelet cgroup driver: systemd is different from docker cgroup driver: cgroupfs。这就是cgroup驱动不一致的典型症状。虽然我在前面已经重点强调过这一条但还是要再次提醒因为真的非常多人在这一步栽跟头。解决办法就是把Docker的daemon.json里的cgroup驱动改成systemd然后重启Docker和kubelet。7.5 API Server证书过期隐患v1.14.2的证书有效期默认是一年一年后集群会突然无法认证。这个问题在部署当时不会暴露但如果你打算长期使用这个集群记住要设置每年一次证书更新的提醒。手动更新证书kubeadm alpha certs renew all然后重启相关组件或者干脆把kubelet和kube-apiserver相关的Pod重启一下。更规范的做法是使用cron定时任务来做或者直接规划好版本升级节奏。这也是我后来更倾向于给客户部署更高版本的原因因为新版本对证书过期问题已经有了更友好的自动处理机制。8. 集群上线前的一些经验总结集群能正常跑起来只是开始。我在实际使用中的体会是K8S集群部署完成后的头两周才是最需要留心的阶段很多隐蔽问题都是随着业务负载和流量增长才慢慢浮出来的。到这一步集群已经能接收工作负载了。但有几个建议想额外分享给第一次搭建K8S的朋友不要在生产环境直接使用默认的admin.conf做长期管理凭证应该创建独立的ServiceAccount并分配最小权限。kubeadm init生成的token默认有效期是24小时尽快完成Node节点的join否则需要重新生成。预留好etcd数据的备份策略。etcd是集群的根丢了etcd等于丢了整个集群。可以写一个小脚本每天通过etcdctl做快照备份并传到远程存储。关注集群的日志收集。至少要把kube-system命名空间的Pod日志收集起来否则每次排查问题都要登录到对应节点上看journalctl效率很低。考虑Ingress Controller的选型。K8S自带的Service类型只有ClusterIP、NodePort和LoadBalancer对于HTTP流量进入集群还是需要安装Ingress Controller来管理路由规则。最后再分享一个小技巧。如果你在搭建过程中用了--image-repository参数但发现还是有个别镜像拉不下来别急着怀疑参数没生效先去docker images里看一下这些镜像到底是哪个tag缺失。很多情况只是你手动拉取的镜像和kubeadm实际请求的tag差了一个小版本号而报错信息又不会明确提示到底缺的是哪个镜像。用kubeadm init --dry-run这个试运行命令可以直接预检一遍所有前置条件省掉大量试错的时间。这套流程就是我部署v1.14.2集群的完整记录照着走一遍遇到问题回来对照着排查你也能在半天内把一个三节点的K8S集群跑起来。