拓冰建站拓冰建站
首页 / 资讯中心 / 正文

K8s集群搭建与Web服务部署:从kubeadm到滚动更新实战

1. 项目概述前阵子帮团队把一套内部系统从单机Docker Compose迁移到K8s集群整个过程中踩了不少坑也沉淀了一套比较完整的实操思路。这篇博文就围绕“K8s集群搭建与Web服务部署”这个主题从零开始梳理一套可直接复用的部署方案适合正在学习K8s的运维、后端开发以及准备把业务容器化上集群的团队参考。先说清楚这套方案能解决什么问题。单机环境下跑Docker容器最头疼的就是三件事机器挂了服务就断了、流量上来没法横向扩展、发布新版本要停机维护。K8s集群恰恰就是围绕这三个痛点设计的。它能多台机器组成一个资源池统一调度容器运行位置通过副本机制保证服务不中断支持滚动更新和回滚发布过程对用户完全透明。本文会基于kubeadm工具搭建一套3节点集群然后部署一个典型的Web服务Demo把从环境准备到服务上线的完整链路走通。本文涉及的核心技术栈包括Linux系统基础操作与网络配置Docker容器运行时安装与配置kubeadm/kubelet/kubectl工具链Pod、Deployment、Service、Ingress等K8s核心资源对象容器网络插件以Calico为例如果你已经掌握Docker基本用法但对集群编排还比较陌生这篇文章就是为你准备的。下面直接进入正题。2. 环境准备与架构设计2.1 节点规划与角色分配K8s集群最少需要2台机器1个Master 1个Worker但生产环境建议至少3台保证Master节点高可用。本次演示规划了3台虚拟机角色分配如下节点角色主机名配置要求IP地址示例Masterk8s-master4C8G60G磁盘192.168.1.10Workerk8s-node14C8G60G磁盘192.168.1.11Workerk8s-node24C8G60G磁盘192.168.1.12关于配置选择我这里多说几句。K8s本身对硬件要求不算苛刻2C4G也能跑起来但实际部署Web服务后系统组件etcd、kube-apiserver、kube-controller-manager等会占用不少资源。实测下来Master节点4C8G比较稳妥Worker节点至少2C4G。磁盘方面除了系统盘最好给Docker单独挂一块数据盘避免容器日志把系统盘写满。操作系统选择了CentOS 7.9虽然K8s官方已经逐步加大对其他发行版的支持但CentOS 7的教程资料最丰富踩坑时排查问题更容易。如果你用Ubuntu 22.04命令上略有差异但整体思路一致——网络插件、部署方式都是通用的。2.2 版本选型K8s与容器运行时版本选择是整个集群搭建中最容易被忽视的环节。我见过不少新手直接装最新版结果配套组件还没跟上导致各种兼容性问题。这里给出一个经验性的选型建议Kubernetes1.28.x当前稳定版API无破坏性变更kubeadm / kubelet / kubectl与K8s版本保持一致Docker24.0.x或者使用containerd作为运行时Calico3.27.x网络插件操作系统CentOS 7.9 / Ubuntu 22.04 LTS这里需要特别说明一下K8s和Docker的关系。早期K8s直接调用Docker来管理容器但后来K8s引入了CRIContainer Runtime Interface标准Docker由于不兼容该标准K8s社区开发了containerd作为中间层适配。现在主流的做法是直接将containerd作为运行时不再依赖Docker。但考虑到很多人习惯用Docker命令调试镜像也可以在安装Docker的同时启用containerd模式两者互不冲突。2.3 系统初始化三步搞定基础环境所有节点都需要进行基础环境配置我在脚本里整理了完整的初始化步骤#!/bin/bash # 1. 关闭防火墙与SELinux systemctl stop firewalld systemctl disable firewalld setenforce 0 sed -i s/^SELINUXenforcing/SELINUXdisabled/ /etc/selinux/config # 2. 关闭swapK8s要求必须关闭 swapoff -a sed -i / swap / s/^/#/ /etc/fstab # 3. 加载内核模块 cat /etc/modules-load.d/k8s.conf EOF overlay br_netfilter EOF modprobe overlay modprobe br_netfilter # 4. 配置sysctl参数 cat /etc/sysctl.d/k8s.conf EOF net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 EOF sysctl --system # 5. 配置hosts解析 cat /etc/hosts EOF 192.168.1.10 k8s-master 192.168.1.11 k8s-node1 192.168.1.12 k8s-node2 EOF这几步操作很多人不理解为什么要做我逐一解释关闭SELinuxSELinux的强制访问控制会干扰容器挂载目录的权限访问在K8s场景下比较难配置直接关闭最省事。关闭swapK8s调度器在计算节点资源时会参考节点可用内存如果存在swap调度器可能把Pod调度到内存不足的节点上导致OOM风险。加载br_netfilterK8s的Service需要通过iptables做流量转发这个模块负责处理桥接网络的iptables规则。配置hosts集群节点间通信基于主机名解析提前配好避免后续因解析失败导致节点NotReady。2.4 容器运行时安装Docker与containerd双轨方案由于后续要用kubeadm初始化集群这里直接用containerd作为CRI运行时同时安装Docker CLI方便日常调试。两个组件配合使用的方案既保留了Docker的使用习惯又确保K8s通过CRI标准与容器运行时交互。# 安装Docker依赖 yum install -y yum-utils device-mapper-persistent-data lvm2 # 添加Docker仓库 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 安装Docker及containerd yum install -y docker-ce docker-ce-cli containerd.io # 配置containerd使用systemd cgroup驱动 mkdir -p /etc/containerd containerd config default | tee /etc/containerd/config.toml # 修改config.toml找到SystemdCgroup这一行改为true sed -i s/SystemdCgroup false/SystemdCgroup true/ /etc/containerd/config.toml # 镜像加速可选国内环境强烈建议配置 # 在config.toml中修改sandbox_image为国内镜像地址 systemctl daemon-reload systemctl enable --now docker containerd关于镜像加速的补充说明由于K8s核心组件的镜像是托管在Google Container Registry上的国内服务器直接拉取会超时。实测最有效的方案是配置阿里云或腾讯云的镜像加速器或者使用代理节点提前拉取镜像再导出导入。本教程后续所有涉及镜像拉取的步骤都会同步给出替代方案。3. K8s集群搭建从kubeadm init到节点加入3.1 kubeadm、kubelet、kubectl安装K8s的组件安装是我认为整个流程中最容易出现版本错乱的一步。这里必须强调一个原则三个工具的版本必须完全一致否则kubeadm init会直接报错。# 配置K8s yum源 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 install -y kubelet-1.28.2 kubeadm-1.28.2 kubectl-1.28.2 # 启动kubelet此时会启动失败属于正常现象等kubeadm init后会恢复 systemctl enable kubelet systemctl start kubelet这里解释一下为什么kubelet启动会失败。kubelet启动后需要连接kube-apiserver获取Pod清单但此时控制面组件还没初始化连不上是正常的。很多新手在这里卡住看到kubelet状态是failed就以为装错了实际上继续执行kubeadm init就行。3.2 初始化控制面kubeadm init全参数详解在Master节点上执行初始化命令。这里建议提前准备好配置文件比直接敲命令行参数更清晰也方便重复执行。# 生成默认配置文件 kubeadm config print init-defaults kubeadm-init.yaml编辑生成的配置文件将主要参数调整如下apiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.1.10 # 修改为Master节点IP bindPort: 6443 nodeRegistration: criSocket: unix:///var/run/containerd/containerd.sock --- apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: 1.28.2 # 与安装版本一致 controlPlaneEndpoint: 192.168.1.10:6443 networking: podSubnet: 10.244.0.0/16 # Pod网段必须与网络插件一致 serviceSubnet: 10.96.0.0/12 # Service网段需要重点理解的是podSubnet这个参数。它定义了K8s集群内Pod的IP地址范围所有容器都会被分配这个网段内的IP。Calico网络插件会在这个网段内做路由转发所以这个网段不能和宿主机网段冲突也不能和Service网段重叠。我这里用的10.244.0.0/16是Calico默认建议的如果你有自己的规划保持两处一致即可。执行初始化kubeadm init --config kubeadm-init.yaml初始化成功后屏幕会输出三部分关键信息kubeconfig配置命令需要将admin.conf复制到~/.kube/config才能使用kubectlPod网络安装命令需要安装CNI网络插件Worker节点加入命令包含token和CA证书哈希这三部分信息建议截图保存后面每一步都要用到。如果初始化失败可以先执行kubeadm reset清理现场再根据错误信息调整配置。常见的失败原因包括镜像拉取超时、端口被占用、cgroup驱动不匹配等。3.3 配置kubectl与安装Calico网络插件初始化完成后在Master节点执行# 配置kubectl mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config # 查看节点状态 kubectl get nodes此时节点状态是NotReady原因是还没部署网络插件这是正常现象。使用Calico作为CNI插件它的职责是给每个Pod分配IP并在集群节点间打通网络隧道。# 下载Calico部署清单 curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml # 如果上面的地址无法访问可以使用下面这个备用地址 # curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml # 安装Calico kubectl apply -f calico.yaml等待一两分钟后查看节点状态应该会变为Readykubectl get nodes如果节点长时间处于NotReady状态通常是以下原因镜像拉取失败calico镜像在docker hub上国内需要提前拉取Pod网段和Calico配置不一致节点间网络不通检查防火墙和路由排查Pod状态kubectl get pods -n kube-system -o wide关注calico-node这个DaemonSet是否在所有节点上都运行以及状态是否为Running。如果是ImagePullBackOff那就优先解决镜像问题。3.4 Worker节点加入集群在Master节点上重新获取加入命令kubeadm token create --print-join-command在worker节点上执行输出的命令kubeadm join 192.168.1.10:6443 --token token --discovery-token-ca-cert-hash sha256:hash加入成功后在Master节点确认集群状态kubectl get nodes正常情况下三个节点的状态都应该是Ready。如果worker节点状态是NotReady大概率是网络插件没连通或者在worker节点上没配置好kubelet启动参数。此时可以在worker节点上查看kubelet日志journalctl -u kubelet -f -n 100根据日志内容针对性排查。常见的情况包括主机名解析失败、CRI socket路径不对等。4. Web服务部署与核心对象解析4.1 部署策略选择为什么推荐Deployment集群就绪后开始部署Web服务。这里选用一个简单的Nginx应用作为演示后续可以替换成任意业务镜像。K8s中管理无状态应用最常用的资源是Deployment它负责声明期望的副本数并维护Pod的创建、更新和删除。相比直接创建PodDeployment带来了几个关键能力自愈能力Pod异常退出后Deployment会自动重建滚动更新新版本发布时逐步替换旧版本业务不中断版本回滚发布出问题可以快速回退到上一版本扩缩容修改副本数即可实现水平伸缩在真实业务中你几乎不会直接创建裸Pod而是通过Deployment这类控制器来管理Pod。这也是为什么所有K8s教程都强调从Deployment开始学习。4.2 编写YAML清单从零创建一个Web服务下面是一个完整的Deployment清单apiVersion: apps/v1 kind: Deployment metadata: name: web-demo namespace: default labels: app: web-demo spec: replicas: 3 selector: matchLabels: app: web-demo template: metadata: labels: app: web-demo spec: containers: - name: nginx image: nginx:1.25-alpine ports: - containerPort: 80 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mi readinessProbe: httpGet: path: / port: 80 initialDelaySeconds: 3 periodSeconds: 5 livenessProbe: httpGet: path: / port: 80 initialDelaySeconds: 10 periodSeconds: 10这份清单里有几个值得展开的点资源请求与限制requests是调度器为Pod预留的资源量limits是Pod能使用的最大资源量。设置合理值很关键——requests设置太高会浪费集群资源太低则可能导致节点资源超卖。上面设置的100m/128Mi表示这个Pod最少需要0.1核CPU和128MB内存上限是0.5核和512MB。探针配置readinessProbe决定Pod是否可以被Service转发流量livenessProbe决定是否重启容器。这两个配置对生产环境至关重要。没有readiness探针时服务可能尚未就绪就被纳入负载均衡导致部分请求503。没有liveness探针时应用死锁后容器不会被自动重启。执行部署kubectl apply -f deployment.yaml # 查看Deployment状态 kubectl get deployment web-demo # 查看Pod状态 kubectl get pods -l appweb-demo -o wide4.3 Service与Ingress流量如何进入集群Pod创建后我们还需要考虑两个流量入口问题集群内部访问和集群外部访问。这里用Service和Ingress解决。首先创建Service为三个Nginx Pod提供统一入口apiVersion: v1 kind: Service metadata: name: web-demo-service namespace: default spec: type: ClusterIP selector: app: web-demo ports: - port: 80 targetPort: 80Service通过selector将请求转发到匹配的Pod上port是Service对外暴露的端口targetPort是后端Pod监听的端口。这里有几种Service类型可选类型适用场景说明ClusterIP集群内部访问默认类型仅在集群内可达NodePort测试或临时暴露在节点上开放一个端口可外部访问LoadBalancer云环境需要云厂商提供负载均衡器ExternalName外部服务映射将Service映射到外部域名对于生产Web服务推荐使用Ingress统一管理外部流量。Ingress相当于集群内部的七层负载均衡可以根据域名或路径分发请求apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: web-demo-ingress namespace: default spec: rules: - host: demo.example.com http: paths: - path: / pathType: Prefix backend: service: name: web-demo-service port: number: 80使用Ingress的前提是集群内已安装了Ingress Controller比如Nginx Ingress Controller或Traefik。Ingress只是声明规则真正做流量转发的是Controller。4.4 滚动更新与版本回滚实操部署完成只是开始日常频繁操作的是版本更新。假设我们要把Nginx镜像从1.25升级到1.26# 更新镜像版本 kubectl set image deployment/web-demo nginxnginx:1.26-alpine # 查看滚动更新状态 kubectl rollout status deployment/web-demo # 如果有问题回滚到上一个版本 kubectl rollout undo deployment/web-demo滚动更新时Deployment默认配置下会先创建新版本的Pod等新Pod就绪后再逐个下线旧Pod。整个过程中服务不中断用户体验无感知。这个策略的优势在于如果新版镜像有问题可以在回滚前及时发现不会造成大规模故障。5. 常用命令与故障排查技巧5.1 kubectl高频命令速查K8s学习曲线陡峭很大一部分原因是kubectl命令太多。这里整理了一份我日常使用频率最高的命令清单# 查看资源 kubectl get nodes # 查看节点 kubectl get pods -o wide # 查看Pod详情 kubectl get deployment # 查看Deployment kubectl get svc # 查看Service kubectl get events --sort-by.metadata.creationTimestamp # 查看集群事件 # 查看日志 kubectl logs -f pod-name # 实时查看Pod日志 kubectl logs pod-name --previous # 查看上一次崩溃的容器日志 # 进入容器 kubectl exec -it pod-name -- /bin/sh # 查看Pod详细描述 kubectl describe pod pod-name # 端口转发临时访问集群内服务 kubectl port-forward svc/web-demo-service 8080:80 # 资源监控 kubectl top node kubectl top pod # 删除资源 kubectl delete -f deployment.yaml个人经验遇到Pod异常时第一步永远先describe看Events里的记录第二步看日志第三步进容器检查应用本身。按照这个顺序排查90%的问题都能解决。5.2 排障必杀技Pod状态与容器日志分析K8s里Pod有几种常见状态每种状态对应的问题方向不同Pod状态可能原因排查重点Pending资源不足、调度失败查看节点资源余量确认是否被调度器拒绝ContainerCreating镜像拉取失败、Volumes挂载失败查看Events确认镜像地址和存储配置CrashLoopBackOff容器启动后立即退出查看日志确认应用配置和启动命令Running0/1容器启动但就绪探针失败检查readinessProbe配置和应用实际监听端口Error容器运行过程报错查看日志定位中断原因下面演示一个典型的排障过程# 假设web-demo的Pod处于CrashLoopBackOff # 1. 查看Deployment下的Pod列表 kubectl get pods -l appweb-demo # 2. 查看Pod详情关注Events区域 kubectl describe pod web-demo-xxxxx # 3. 查看容器日志 kubectl logs web-demo-xxxxx --previous在一次实际排障中我发现容器不断重启状态是CrashLoopBackOffdescribe显示ContainerStatus有退出码127日志提示exec user process caused no such file or directory。排查后发现是镜像的基础镜像使用了不同架构比如AMD64的镜像跑在了ARM服务器上换用匹配架构的镜像后问题解决。另一个高频坑是就绪探针配置冲突。如果readinessProbe的path写成了/health而应用实际没有这个接口探针一直失败Pod会永远处于未就绪状态Service不会把流量转过来。此时应用本身是正常启动的你还会收到一堆健康检查失败的告警。解决办法就是让探针路径和应用实际暴露的健康检查端点对齐。5.3 etcd备份与集群升级集群稳定运行一段时间后必要的运维操作要跟上。etcd是K8s存储集群所有状态的关键组件备份etcd是容灾恢复的核心环节。# 使用etcdctl进行快照备份 ETCDCTL_API3 etcdctl --endpointshttps://127.0.0.1:2379 \ --cacert/etc/kubernetes/pki/etcd/ca.crt \ --cert/etc/kubernetes/pki/etcd/server.crt \ --key/etc/kubernetes/pki/etcd/server.key \ snapshot save /backup/etcd-snapshot-$(date %Y%m%d).db备份策略建议每天全量备份一次保留最近7天。恢复etcd时需要先停止kube-apiserver和etcd服务然后使用etcdctl snapshot restore恢复数据最后重启组件。如果只是单个节点故障更快的做法是从集群中摘除节点重新初始化后加入。关于集群升级推荐采用蓝绿升级法先备份再逐个节点升级。每个节点上执行yum update kubeadm kubelet kubectl升级kubeadm后先执行kubeadm upgrade plan确认可用版本再执行kubeadm upgrade apply。注意升级Master节点前要确认所有Worker节点健康升级过程中不要同时发布新业务。6. 集群运维心得与避坑记录K8s集群跑起来只是第一步稳定运维才是长期的挑战。分享几条最真实的实践心得。其一资源监控必须从第一天就做好。集群部署完成后第一时间部署Metrics Server或Prometheus设置好告警规则。有一次我的集群因为某个服务内存泄漏导致节点内存耗尽Pod被大量驱逐业务直接抖动。如果提前配置了告警完全可以在问题扩大之前发现。其二日志收集方案要提前规划。K8s的Pod会频繁重建容器日志会跟随Pod销毁而丢失。生产环境建议部署EFKElasticsearch Fluentd Kibana或Loki方案将日志集中采集到持久化存储中。否则等线上出了故障再去翻日志会发现关键Pod已经被调度到别的节点上旧日志无处可查。其三命名空间隔离是团队协作的基础。多团队共用集群时按项目或团队划分Namespace并通过ResourceQuota限制每个Namespace的资源额度。这样既保证了资源分配公平也避免了误操作影响其他业务。默认的所有资源都放在default命名空间下时间一长会特别混乱。其四配置变更必须走GitOps审计链路。所有的Deployment、ConfigMap、Service等资源清单都应该提交到Git仓库统一管理。有人修改了线上配置可以通过历史记录回看和回滚。用kubectl apply -f直接改线上配置当时是方便但后续排查问题时往往说不清到底改了什么。7. 常见问题速查从集群搭建到运行期的坑把实操中最常遇到的10个问题整理成速查表方便大家对照排查问题现象可能原因解决方案kubeadm init拉镜像超时国内网络访问Google镜像仓库受限配置镜像加速器或提前拉取镜像后save/load节点状态NotReady网络插件未安装或podSubnet冲突确认Calico路由正常检查podSubnet配置Pod状态Pending资源不足或节点亲和调度不满足kubectl describe pod查看调度失败原因Pod反复重启镜像启动即崩或Liveness探针失败查看--previous日志调整探针配置删除Pod后立即重建受Deployment/ReplicaSet管理删除Deployment不要直接删PodService无法访问Podselector标签不匹配核对Service的selector和Pod的labelsNodePort外部不通安全组/防火墙未放行打开节点端口和防火墙规则PV无法挂载存储类或权限问题检查StorageClass名称和PVC绑定状态CoreDNS CrashLoop网络插件与CoreDNS冲突检查Pod网段重启CoreDNSkubectl无法连接集群kubeconfig失效重新复制admin.conf到~/.kube/config补充两个非常隐蔽的坑。第一个是关于kubelet的cgroup驱动。Docker默认使用cgroupfs而K8s推荐使用systemd。如果不将containerd的SystemdCgroup设置为truekubelet启动后检查节点信息时会报cgroup driver不一致的错误。这个坑在安装阶段不容易发现等到节点加入集群才会暴露排查起来特别费劲。第二个是关于Service的externalTrafficPolicy。默认情况下NodePort服务的流量可能被转发到任意节点上的Pod对于需要保留客户端真实IP的场景比如日志审计需要将externalTrafficPolicy设置为Local。但这会导致流量被锁定在当前节点无法实现跨节点负载均衡需要综合考虑场景取舍。回头来看K8s集群搭建最难的环节其实不是命令本身而是理解各组件之间的协作关系和网络流量路径。把kubelet如何感知Pod、kube-proxy如何实现Service转发、网络插件如何为Pod分配IP这几个问题想清楚后面的运维会顺手很多。这套环境跑起来之后我还陆续在集群上部署了监控告警、日志采集和CI/CD流水线整个链路的运维效率比单机时代提升了不止一个量级。如果你正在规划上K8s建议从最小集群开始逐步接入业务别一上来就追求生产级高可用——先把核心概念走通再谈规模化。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门