
1. 拿到K8s之前先把这些决策做对入行这些年我见过太多团队在Kubernetes上栽跟头绝大多数问题不是出在敲命令那一下而是在装集群之前就把方向定歪了。这里说的定方向包括版本选型、运行时选型、网络方案选型、安装工具选型哪一项拍脑袋后面都有一连串的坑在等着。先说版本选型。很多人习惯装最新版这个习惯在K8s这里要改一改。Kubernetes的版本迭代频率很高一年大概三个大版本每个版本只维护约14个月。我在生产环境部署时通常选择比最新版低一两个小版本的稳定版比如当前最新是1.32我就选1.29或1.30。原因很直接K8s的组件很多kube-apiserver、kube-controller-manager、kube-scheduler、kubelet、kube-proxy还有CNI插件、Ingress Controller、存储插件CSI这些生态组件都需要时间跟上新版本的API变更。选稍微成熟的版本生态兼容性最稳。然后是容器运行时。Kubernetes从1.24版本开始彻底移除了对Docker的dockershim支持很多人被这个变动搞得措手不及。现在主流的运行时是containerd。我早期用Docker作为运行时迁移到containerd后踩过一些坑后面会详细讲。简单说containerd更轻量、更稳定排查问题时的日志链路也更清晰是当前生产环境的事实标准。再确认几个基本的决策项安装工具生产环境如果要快速搭建推荐kubeadm它是官方维护的稳定性有保障升级路径清晰。二进制手动部署只推荐在学习原理时用生产环境维护成本太高。网络方案CNI我推荐Calico。它支持BGP和IPIP两种模式性能好网络策略能力强。Flannel简单但功能有限Cilium是后起之秀功能很强但要依赖eBPF对内核版本有要求。高可用方案至少三台master节点做HA用HAProxy、Keepalived或云厂商的负载均衡器。后面我再细说架构。存储方案如果用的是公有云直接用云的持久化卷云盘、文件存储走CSI插件。如果是自建机房可以考虑自建NFS或Ceph我环境里用的是NFS加Local PV混合方案。在做任何安装操作之前我建议你先画一张集群规划表把节点的IP、主机名、操作系统版本、内核版本、角色分工全部列清楚。这一步花半小时后面能省下几天的排错时间。2. 动手装集群以kubeadm为中心的三层推进2.1 基础环境与系统参数调优在所有节点上的统一执行正式初始化之前有一堆系统层面的准备工作要做。这些操作必须在集群的所有节点上统一执行包括master和worker。我见过不少人在一台节点上改完配置忘了同步其他节点结果集群网络时好时坏排查到怀疑人生。第一步关闭交换分区。Kubernetes从1.8版本开始要求关闭swap。原因是kubelet的可用内存计算如果不准确调度器就会做出错误判断。执行swapoff -a sed -i /swap/s/^/#/ /etc/fstab这里sed命令的作用是把fstab里swap那行注释掉确保重启后不会自动挂载swap。第二步加载内核模块和调整系统参数。主要涉及overlay和br_netfilter前者是容器文件系统overlay2依赖的后者是iptables转发到网桥时需要启用的。cat EOF | tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF modprobe overlay modprobe br_netfilter然后写入关键参数cat EOF | tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 vm.swappiness 0 EOF sysctl --system这里解释一下net.bridge.bridge-nf-call-iptables 1的意思当流量经过Linux网桥时也走一遍iptables规则。K8s的Service转发依赖iptables或IPVS如果不开启跨节点的Pod通信会出现诡异的失败。第三步配置主机名和hosts解析。hostnamectl set-hostname k8s-master01 # 每个节点执行对应的hostname cat /etc/hosts EOF 192.168.1.11 k8s-master01 192.168.1.12 k8s-master02 192.168.1.13 k8s-master03 192.168.1.21 k8s-node01 192.168.1.22 k8s-node02 EOF很多问题都出在hosts解析上比如kubeadm初始化时报connection refused多半就是hostname解析不到本机IP。不要图省事跳过这步。第四步安装容器运行时。以containerd为例先安装依赖再安装containerdapt-get update apt-get install -y containerd装完先别急着启动直接启动的话会生成一个默认配置但这个配置里systemd cgroup驱动是关闭的Kubernetes的kubelet默认使用systemd cgroup驱动两边不一致会导致节点上的Pod异常。必须修改配置containerd config default | tee /etc/containerd/config.toml然后编辑/etc/containerd/config.toml改两处关键内容。第一处是[plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options] SystemdCgroup true第二处是sandbox_imagepause镜像默认配置里指向的地址在国内环境下经常拉不到我一般改成sandbox_image registry.aliyuncs.com/google_containers/pause:3.9这个pause容器每个Pod都会有一个相当于Pod的基础设施容器它的作用是为同Pod的容器共享网络、PID、IPC等命名空间所有Pod的创建都依赖它这个镜像拉不下来Pod铁定起不来。改完后重启containerdsystemctl restart containerd systemctl enable containerd2.2 用kubeadm初始化集群的完整流程Kubernetes自身的三个核心组件安装相对简单直接用apt或yum装。配置好软件源后执行apt-get install -y kubelet kubeadm kubectl这里注意kubelet需要随着系统启动而启动但要等kubeadm init生成配置后它才会进入正常运行状态所以在安装完后直接systemctl enable kubelet但先不用start。在master节点上执行初始化kubeadm init \ --control-plane-endpoint k8s-master01:6443 \ --kubernetes-version v1.29.0 \ --pod-network-cidr10.244.0.0/16 \ --service-cidr10.96.0.0/12 \ --image-repository registry.aliyuncs.com/google_containers几个参数说明一下。--control-plane-endpoint在高可用场景里填的是VIP虚拟IP或负载均衡器地址单master搭建时填master本身。--pod-network-cidr必须规划好这个网段是给集群内所有Pod分配IP用的不能跟现有网络冲突我用的是10.244.0.0/16Flannel默认网段如果你用Calico建议用192.168.0.0/16或自定义网段。--image-repository指定镜像仓库地址解决从Google仓库拉不到镜像的问题。初始化成功后会输出一段内容包含三个重要信息kubeconfig配置命令、加入集群的token命令、安装CNI插件的提示。记得保存。mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config接下来安装CNI网络插件。之前我提过选Calico但它提供的YAML文件里默认的Pod网段是192.168.0.0/16如果你的pod-network-cidr不是这个网段要手动修改。下载并应用curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml # 编辑calico.yaml把CALICO_IPV4POOL_CIDR改成与kubeadm init一致 kubectl apply -f calico.yaml确认集群状态正常等待node处于Readykubectl get nodes kubectl get pods -n kube-system等Calico的Pod全部Running集群的基础安装就完成了。worker节点加入集群很简单把初始化输出的kubeadm join命令在worker上执行即可。如果你要搭建高可用集群用负载均衡器提供VIP然后在其他master节点上依次执行kubeadm join带上--control-plane参数把etcd和control plane组件都加进来即可。3. 生产环境的底座HA高可用、存储、命名空间与监控3.1 高可用架构不能只堆节点数量很多人以为高可用就是多买几台机器把master堆起来其实关键在API Server的入口是否统一。我在生产环境采用的三master架构是以一个负载均衡器比如HAProxy作为流量入口后端挂三个kube-apiserver实例。所有访问集群的请求包括kubectl、kubelet、调度器都走这个统一入口任何一个master节点挂了流量会自动切到其他节点。关键点来了kubeadm生成的证书里API server证书的SANSubject Alternative Name只包含kubernetes.default.svc、kubernetes.default、kubernetes、localhost、节点IP、主机名这些。如果你用VIP作为control-plane-endpoint需要在初始化时指定kubeadm init \ --control-plane-endpoint 10.10.10.100:6443 \ --apiserver-cert-extra-sans 10.10.10.100,192.168.1.11,192.168.1.12,192.168.1.13--apiserver-cert-extra-sans把VIP地址加进证书SAN否则kubelet通过VIP访问apiserver时会报证书校验失败。这个坑我踩过一次当时排查了半天最后发现是证书SAN列表里没有VIP。etcd的部署方式也要考虑。kubeadm初始化的集群默认把etcd以Pod形式跑在master节点上如果是三masteretcd天然形成三节点集群。高可用集群初始化完成后需要把所有节点的kubelet配置里的--cluster-dns和--cluster-domain检查一遍默认就行。我用这套架构运行了几年期间有一次master节点物理宕机整个集群没有任何感知业务流量完全没中断。这就是高可用架构的意义——不是给运维看的是给业务兜底的。3.2 存储层Stateful应用必须提前规划生产环境迟早会遇到有状态应用MySQL、Redis、Elasticsearch、对象存储网关等。这些应用的Pod挂掉重建没关系但数据不能丢。K8s的存储体系分三层底层存储系统NFS、Ceph、云盘、持久卷PV、持久卷声明PVC。一个Pod要使用存储就声明一个PVCK8s自动把它绑定到一个匹配的PV上。如果你的集群跑在云上建议直接用云厂商的CSI驱动创建一个StorageClass使用的时候声明storageClassName即可。自建机房的话我推荐这个组合临时性数据或无高要求的用Local PV节点本地磁盘需要共享数据或动态扩容的用NFS。下面给一个NFS动态供给的StorageClass配置nfs-client-provisionerapiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-storage provisioner: k8s-sigs.io/nfs-subdir-external-provisioner parameters: pathPattern: ${.PVC.namespace}/${.PVC.name} archiveOnDelete: true配置完StorageClass后创建一个PVC测试apiVersion: v1 kind: PersistentVolumeClaim metadata: name: test-pvc spec: accessModes: - ReadWriteOnce storageClassName: nfs-storage resources: requests: storage: 1Gi关于存储还有一个容易忽略的问题很多中间件比如MySQL的binlog、Elasticsearch的索引对磁盘IO要求很高NFS在这种场景下性能其实一般。不要把NFS当作所有应用唯一的存储依赖关键业务数据尽量用高性能的Local SSD或云盘。3.3 用命名空间、ResourceQuota与LimitRange划清资源边界生产环境一个集群通常会跑多个业务线运维最怕的就是一个应用把整台机器的CPU或内存耗尽其他应用跟着遭殃。K8s提供了三把锁命名空间做逻辑隔离ResourceQuota做总量控制LimitRange做单Pod配额。先给每个业务线建独立命名空间比如dev、prod-payment、prod-user等。然后用ResourceQuota限制每个命名空间的总资源apiVersion: v1 kind: ResourceQuota metadata: name: prod-payment-quota namespace: prod-payment spec: hard: requests.cpu: 20 requests.memory: 40Gi limits.cpu: 40 limits.memory: 80Gi persistentvolumeclaims: 10有了这个配额除非你主动调整否则这个命名空间里的所有应用加起来的CPU请求不得超过20核内存请求不得超过40GBPVC总数不超过10个。再配一个LimitRange防止单个Pod的规格过大或者没有设置limit导致服务质量QoS不受控apiVersion: v1 kind: LimitRange metadata: name: mem-limit-range namespace: prod-payment spec: limits: - max: cpu: 4 memory: 8Gi min: cpu: 100m memory: 128Mi default: cpu: 500m memory: 1Gi defaultRequest: cpu: 200m memory: 512Mi type: Container这里有一个被我反复强调的原则生产环境的Pod必须显式配置requests和limits。requests是调度器做调度依据的数值limits是运行时强制限制的数值。如果你只写requestsPod可能会因为内存超限被内核OOM Killer干掉如果只写limits调度器可能把Pod调度到一个资源已经紧张的节点导致节点整体超售。3.4 集群监控与日志没有监控就谈不上运维K8s集群跑起来之后你第一件事不是去部署业务而是先把监控和日志系统搭好。我目前使用的组合是Prometheus加Grafana做指标监控Loki或ELK做日志收集Alertmanager做告警。监控的落地思路是这样的用Prometheus Operator安装它会自动创建一组CRDCustomResourceDefinition你可以声明一个ServiceMonitor来定义要采集的指标。K8s自身的核心组件apiserver、kubelet、etcd大多已经暴露了/metrics接口Prometheus直接抓取就行。然后再加上kube-state-metrics负责暴露集群对象的状态指标和node-exporter负责暴露节点系统指标一套基础监控就齐了。告警规则至少要覆盖这些场景节点状态NotReady、Pod反复重启、CPU和内存使用率超过阈值、PVC容量使用率接近100%、证书即将过期。证书过期这个问题特别隐蔽我遇到过apiserver证书过期导致整个集群不可用的情况所以现在会专门配一条证书剩余天数的告警。日志方面我建议在宿主机上以DaemonSet方式部署日志采集器比如Filebeat或Promtail。容器会写stdout日志到宿主机目录采集器直接读文件转走。有些应用会把日志写到容器内部路径而不是stdout这种场景要把日志目录挂到hostPath或PVC上否则Pod一删日志就没了。4. 把应用真正跑起来从Deployment到Ingress的完整路径4.1 Deployment和Service声明式编排的基本盘资源对象创建的方式很简单写一个YAML然后kubectl apply -f即可。我拿一个典型的Web应用举例它的Deployment长这样apiVersion: apps/v1 kind: Deployment metadata: name: web-api namespace: prod-payment labels: app: web-api spec: replicas: 3 selector: matchLabels: app: web-api strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 maxSurge: 1 template: metadata: labels: app: web-api spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchLabels: app: web-api topologyKey: kubernetes.io/hostname containers: - name: web-api image: registry.example.com/payment/web-api:v1.2.0 imagePullPolicy: IfNotPresent ports: - containerPort: 8080 env: - name: DB_HOST valueFrom: configMapKeyRef: name: app-config key: DB_HOST resources: requests: cpu: 500m memory: 512Mi limits: cpu: 1 memory: 1Gi readinessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 5 periodSeconds: 10 livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 15 periodSeconds: 20这里面的podAntiAffinity是一个很容易被忽略但非常重要的配置它让K8s优先把三个副本调度到不同节点上避免一个节点挂了导致整个服务同时挂掉。如果你的副本数多于节点数这个约束会变成尽力而为调度器会尽量分散。maxUnavailable: 1和maxSurge: 1是滚动更新策略意思是升级时最多允许一个旧Pod不可用同时最多多出一个新Pod这样整个升级过程始终有至少两个副本对外服务。如果你的服务只有两个副本升级策略设置不当会直接导致服务短暂不可用。readinessProbe和livenessProbe这两个探针必须配别再说不配也跑得好好的。readiness决定流量是否打到Pod上liveness决定Pod需要不需要重启。只要你的应用提供健康检查接口就把这两个探针配上这是K8s自愈机制能生效的前提。没有探针K8s就是一个盲人不知道你的Pod到底活着没有。Deployment创建后Pod有了但还不能对外访问。Service就是这一层的抽象它通过Label Selector关联一组Pod分配一个稳定的ClusterIP然后用iptables或IPVS规则做负载均衡apiVersion: v1 kind: Service metadata: name: web-api-svc namespace: prod-payment spec: selector: app: web-api type: ClusterIP ports: - port: 80 targetPort: 8080Service的流量转发原理值得理解一下。ClusterIP其实只是个虚拟IP真正干活的是节点上的kube-proxy。它监听API Server中Service和Endpoint的变化然后写iptables规则把访问ClusterIP的流量DNAT到某个真实Pod IP上。iptables规则是随机选端点的所以天然负载均衡。如果集群规模较大或对转发性能有更高要求可以把kube-proxy的mode改成ipvsIPVS支持更多调度算法并且性能更好kubectl edit cm -n kube-system kube-proxy # 把 mode: 改为 mode: ipvs # 然后滚动重启 kube-proxy Pod4.2 ConfigMap与Secret配置和敏感信息分离把配置硬编码在镜像里是初学者最容易犯的错误。改一个数据库地址就要重新打镜像部署流程又长又容易出错。正确做法是用ConfigMap管理非敏感配置用Secret管理敏感信息。ConfigMap的创建可以有多种方式从文件、从目录、从字面量都可以。我这里举一个从字面量创建并挂载的例子kubectl create configmap app-config \ --from-literalDB_HOSTmysql-svc.prod-payment.svc.cluster.local \ --from-literalDB_PORT3306 \ -n prod-payment然后把这个ConfigMap通过volume的方式挂载到Pod中应用直接读文件获取配置volumes: - name: app-config-volume configMap: name: app-config containers: - name: web-api volumeMounts: - name: app-config-volume mountPath: /etc/configSecret的用法类似区别在于数据会被base64编码并且在etcd中是加密存储前提是你启用了encryption-at-rest。创建方式kubectl create secret generic db-secret \ --from-literalDB_PASSWORDStr0ngPssw0rd \ -n prod-payment然后通过环境变量方式注入env: - name: DB_PASSWORD valueFrom: secretKeyRef: name: db-secret key: DB_PASSWORDSecret确实能比明文安全一些但它不是银弹。它的本质是减少敏感信息的暴露面而不是绝对加密。集群里能访问etcd或拥有get secret权限的账号还是能看到明文。生产环境要配合RBAC严格控制secret的访问权限。4.3 Ingress和Ingress Controller外部流量的统一入口Service的ClusterIP只能在集群内部访问。要将服务暴露给外部有几种方案NodePort、LoadBalancer、Ingress。NodePort简单但每个服务都要占用节点端口生产环境端口多了难管理LoadBalancer需要云平台支持自建机房没有这个条件Ingress是最灵活也最常用的方案。先理清概念Ingress是一个K8s资源对象它定义的是一组路由规则Ingress Controller是一个Pod它真正读取Ingress规则把流量转发到对应的Service上。你光定义Ingress没有Controller它是不生效的。生产环境我用的Ingress Controller是Ingress-NGINX它基于Nginx实现。部署方式kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.9.5/deploy/static/provider/baremetal/deploy.yaml部署完成后如果要让外部访问到Ingress Controller这个Controller本身需要暴露出来自建机房通常用NodePort方式暴露如果Ingress Controller的Service是LoadBalancer类型公有云会自动分配负载均衡IP。一个Ingress规则示例apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: web-api-ingress namespace: prod-payment annotations: nginx.ingress.kubernetes.io/rewrite-target: / nginx.ingress.kubernetes.io/proxy-body-size: 50m spec: ingressClassName: nginx rules: - host: api.example.com http: paths: - path: / pathType: Prefix backend: service: name: web-api-svc port: number: 80Ingress支持TLS终止生产环境直接在上层加证书kubectl create secret tls example-tls --key server.key --cert server.crt -n prod-payment然后在Ingress中配置spec: tls: - hosts: - api.example.com secretName: example-tls除了基础路由Ingress-NGINX还支持很多实用的注解配置比如限制上传大小、开启gzip、配置跨域、限流等。我遇到过一些团队把这类逻辑放到应用代码里处理其实在Ingress层统一处理更高效也方便多个服务复用。4.4 灰度发布与HPA生产环境的优雅升级方式我们前面讲Deployment的滚动更新能解决服务不中断的问题但解决不了新版本有Bug影响部分用户的问题。灰度发布金丝雀发布是把新版本先放量给一小部分用户验证没问题再全量切换。用Ingress-NGINX做灰度发布非常简单只需要在Ingress上加注解apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: web-api-canary-ingress namespace: prod-payment annotations: nginx.ingress.kubernetes.io/canary: true nginx.ingress.kubernetes.io/canary-weight: 10 spec: ingressClassName: nginx rules: - host: api.example.com http: paths: - path: / pathType: Prefix backend: service: name: web-api-canary-svc port: number: 80这个配置表示访问api.example.com的流量有10%会被转发到web-api-canary-svc也就是新版本应用。验证没问题后把权重调到100%再逐步切到正式版本或者直接把主Ingress后端的Service切到新版本。弹性伸缩HPA是K8s在生产环境非常实用的能力。HPA通过Metrics Server持续收集Pod的CPU、内存使用率当超过阈值时自动增加副本数负载降低时再缩回来。前提是先安装Metrics Serverkubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml然后创建HPAkubectl autoscale deployment web-api --min3 --max10 --cpu-percent70 -n prod-payment这个命令的意思是web-api这个Deployment最少3个副本最多10个副本当所有Pod的平均CPU使用率超过70%时自动增加副本数。我用这套机制应对过几次营销活动高峰流量翻倍时Pod自动扩容流量过去后自动缩回人工介入为零。HPA有一个注意点要让HPA正常工作Pod必须配置了requests.cpu因为HPA的计算基准是当前使用量除以request值。你没写requestsHPA就无据可算。5. 生产环境踩坑实录两个典型的排错链路5.1 Pod一直Pending或者ContainerCreating怎么一步步定位Pod起不来是K8s运维遇到频率最高的问题。我整理一下标准排查链路以Pod一直Pending为例。第一步看Pod事件。这是信息量最大的入口。kubectl describe pod pod-name -n namespace输出末尾的Events部分会直接告诉你卡在哪一步。常见的Pending原因有0/3 nodes are available: 1 node(s) had untolerated taint——节点有污点需要容忍或取消污点。0/3 nodes are available: insufficient cpu——节点CPU资源不足调度不进去。0/3 nodes are available: 3 node(s) didnt match Pods node affinity/selector——节点亲和性不匹配。第二步如果是ContainerCreating要看具体是哪一层卡住。kubectl describe显示的具体原因一般在Events里Failed to pull image——镜像拉取失败。先手动在节点上docker pull或crictl pull验证镜像是否存在、仓库是否可达。ContainerCreating卡了很久去节点上看kubelet日志journalctl -u kubelet -f或者用crictl ps -a查看容器状态。我遇到一个典型案例Pod一直ContainerCreatingdescribe显示sandbox image registry.aliyuncs.com/google_containers/pause:3.9: failed to pull但手动在节点上拉pause镜像又是成功的。后来查了containerd日志发现是containerd配置里sandbox_image字段的值和节点上实际存在的镜像tag不一致kubelet每次创建Pod时用错误地址去拉。改完配置重启containerd后问题解决。第三步检查节点状态。有些问题要到节点层面看kubectl get nodes kubectl describe node node-name看节点上的Condition是否正常有没有MemoryPressure、DiskPressure等异常状态。如果节点处于NotReady去节点上看kubelet状态systemctl status kubelet journalctl -u kubelet -n 100比较常见的节点NotReady原因有kubelet没起来、证书过期、CNI插件异常、网络不通、磁盘空间满。这套链路走下来95%以上的Pod创建问题都能定位到。难的不是命令而是别跳过步骤。我看到太多次直接把Pod删了重建——这确实偶尔能解决但那叫碰运气不叫运维。5.2 一个隐蔽案例集群DNS间歇性解析失败之前遇到过一个问题集群里的应用间歇性报域名解析失败有时几分钟一次有时几十分钟一次重启Pod后短暂恢复又复发。当时排查了很久这里还原一下思路。排查链路先看CoreDNS Pod的状态kubectl get pods -n kube-system -l k8s-appkube-dns看起来正常三个副本都是Running。看应用Pod的DNS配置kubectl exec -it pod-name -- cat /etc/resolv.conf显示nameserver是10.96.0.10这是Service的ClusterIP正常。在故障发生时手动在应用Pod里执行DNS解析测试kubectl exec -it pod-name -- nslookup kubernetes.default.svc.cluster.local发现有时解析超时有时返回错误的结果。这就奇怪了Pod和CoreDNS都是好的问题出在链路中间。检查CoreDNS日志kubectl logs -n kube-system -l k8s-appkube-dns --tail50发现大量超时记录下游是10.244.0.0/16网段的地址在重复发查询。最后查到问题根源CoreDNS的ConfigMap里配置了forward . /etc/resolv.conf而CoreDNS Pod所在节点的/etc/resolv.conf里配置的nameserver物理机DNS在特定情况下响应不稳定。当CoreDNS要解析外部域名时需要依赖上游DNS上游一旦抖动所有依赖外部域名的解析就会间歇性失败。解决办法把CoreDNS的配置改为使用公共DNS或不依赖物理机DNSapiVersion: v1 kind: ConfigMap metadata: name: coredns namespace: kube-system data: Corefile: | .:53 { errors health ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa } forward . 223.5.5.5 8.8.8.8 cache 30 }改完后滚动重启CoreDNSkubectl rollout restart deployment -n kube-system coredns这个案例给我的教训是K8s集群内的DNS链路很长Pod - kube-dns Service - CoreDNS - 上游DNS任何一个环节抖动都会被应用感知到。排查这类问题要顺着链路从下往上或者从上往下一层一层加监控不能只看Pod状态就下结论。6. 生产上线前把这几件小事做扎实很多集群装完、示例应用跑通大家就认为大功告成了。但一个要承接真实业务的集群在上线前还得有一系列加固工作。RBAC权限控制是第一步。默认情况下admin账号拥有所有权限但如果团队人多必须做到权限最小化。给不同角色分配不同权限比如给研发同学分配只能操作指定命名空间的权限apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: dev name: dev-role rules: - apiGroups: [] resources: [pods, services, configmaps, secrets, pods/log, pods/exec] verbs: [get, list, watch, create, update, patch, delete] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: namespace: dev name: dev-binding subjects: - kind: User name: alice apiGroup: rbac.authorization.k8s.io roleRef: kind: Role name: dev-role apiGroup: rbac.authorization.k8s.io把镜像仓库和镜像命名规范定好。生产环境的镜像一定要用私有仓库镜像命名建议包含项目名和环境信息比如registry.example.com/payment/web-api:v1.2.0不要用latest否则无法追溯版本。设置PodDisruptionBudgetPDB。这个很多人没听说过但它很重要。当节点需要维护比如内核升级要重启节点时如果你直接kubectl drain节点K8s会把Pod驱逐到其他节点有PDB保护的服务不会一次性被全部驱逐apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: web-api-pdb namespace: prod-payment spec: minAvailable: 2 selector: matchLabels: app: web-api把资源备份和恢复演练做一遍。K8s集群本身的备份主要是etcd。etcd里存着集群所有的资源定义和状态apiserver的配置、Secret等都在里面。备份方式# 在任一master节点上 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恢复方式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 restore /backup/etcd-snapshot-$(date %Y%m%d).db --data-dir/var/lib/etcd-backup记住一个关键点etcd备份要定时做并且备份文件要放到和集群不同的存储位置最好是有异地副本。etcd数据损坏或丢失比丢一个Pod严重得多它意味着整个集群的配置都找不回来。安全组和防火墙规则也要过一遍。每个节点上必须放行的端口包括master节点的6443API Server、2379和2380etcd、10250kubelet、10259kube-scheduler、10257kube-controller-managerworker节点需要10250和30000-32767端口段NodePort服务。把这些端口限制在集群内部网络或可信来源不要对公网开放。做完这些工作一个K8s集群才算真正具备承接生产流量的条件。我在实际运维中还有一个习惯把集群的所有版本信息、配置变更、排错记录整理到一个文档里每次操作前先看文档确认现状操作后及时更新。这个动作看起来简单但在事故追溯和多团队协作时能省非常多时间。集群管理本质上不是技术问题而是敬畏心的问题——每一条命令都有后果每一个组件都有依赖你以为的不可能往往就是下一秒的事故现场。