Kubernetes与Docker实战:从容器基础到K8s集群搭建部署
1. 先搞清楚K8s和Docker到底是什么关系但凡搜过K8s相关内容的同学第一个蹦出来的问题基本都是K8s和Docker有啥区别。这个问题不解决后面看啥都别扭。简单说Docker是容器引擎负责把应用打包成容器、跑起来K8s是容器编排平台负责管理成百上千个容器。两者的关系类比一下就是Docker是盖房子用的砖头和水泥K8s是施工总承包不仅要搬砖还得管工期、管进度、管工人排班、管材料供应。你说砖头重不重要重要。但一个小区几十栋楼同时开工光靠搬砖是搞不定的。更准确一点讲Docker解决的是应用怎么打包、怎么隔离的问题——把一个Java应用连同它的环境、依赖、配置全部塞进一个镜像里随便扔到哪台机器上都能跑。但你要是只有三五台服务器、跑十几个容器手动管理还凑合当规模到了几十上百个容器分布在不同的物理机上问题就来了容器挂了谁来拉起流量来了往哪个容器上转发新版本发布怎么做滚动升级不中断服务某台机器负载高了怎么把容器调度到空闲机器上这些问题Docker一个都回答不了。而K8s就是冲着这些问题来的。还有个常见的误解很多人以为K8s是Docker的替代品装上K8s就不用Docker了。这话错得离谱。K8s本身不负责运行容器它只是负责管理和调度。真正干活、真正把容器跑起来的还是容器运行时——可以是Docker也可以是containerd、CRI-O。K8s通过Container Runtime InterfaceCRI这个标准接口和容器运行时通信你说我要跑三个Nginx容器K8s就把这个需求翻译成容器运行时的指令去执行。换句话说Docker偏单机思维K8s是集群思维。入门前先把这个弯转过来后面的学习会顺畅很多。2. K8s到底解决了哪些真实痛点很多人学K8s容易陷入一个误区追着概念跑学了ReplicaSet、Deployment、Service、ConfigMap每个概念都认识但串不起来——不知道这些玩意儿到底为了解决什么问题而生。我的建议是反过来先理解没有K8s的世界是什么样的你就能自然理解每个组件存在的意义。2.1 单机Docker时代的三座大山假设你有一个Web应用用Docker跑在一台服务器上刚开始没毛病。但业务一涨你发现第一单点故障。服务器宕机了应用就没了用户访问直接失败。你必须想尽办法让应用在其他机器上也跑一份但在其他机器上再跑一份这个操作靠人肉执行一次两次行天天这么干准出事。第二流量不均。你有三台机器每台跑了一个应用实例。有的机器CPU都快打满了有的闲着没事干。你得时刻盯着手动迁移容器这活儿深夜干起来特别酸爽。第三发布升级难。新版本要上线最粗暴的做法是先把旧容器全部停掉再起新容器。但这样用户就会看到服务不可用。稍微讲究一点的做法是一个一个来先停一个旧的、起一个新的观察没问题再继续。这个讲究一点就是滚动更新了——但靠人肉盯着做几十个容器得折腾到天亮。这三个问题本质上都在问同一件事容器的生命周期、资源分配、服务暴露能不能由系统自动管理K8s给出了肯定答案。2.2 K8s的解法思路K8s解决上面三个问题的方式一句话总结就是声明期望状态系统负责收敛。你告诉K8s我希望有三个副本的Nginx跑着K8s就去检查当前状态现在是几个不够就补多了就删有挂掉的就重新拉起。你不需要告诉它第2步该干什么第3步该干什么你只负责声明最终要什么样剩下的事情K8s自己想办法。这个思路贯穿K8s的所有核心概念Deployment管副本数量和滚动升级Service提供稳定的访问入口不管后面的Pod怎么变访问地址不变HPAHorizontal Pod Autoscaler根据CPU、内存等指标自动伸缩副本数ConfigMap和Secret把配置从镜像里拆出来改配置不用重新打镜像。每个概念的背后都对应着一个具体的运维痛点。所以我的建议是学每一个K8s对象的时候先问自己一个问题如果没有它我该怎么操作想明白了这个问题概念自然就记住了而且面试的时候也不怕被问你讲一下Deployment和StatefulSet的区别这类题——因为你脑子里有具体的业务场景而不是死记硬背的定义。3. K8s核心架构十分钟看懂所有组件理解了为什么再来看是什么就轻松多了。K8s的架构说白了就是一个管理中枢一堆干活的工人。整个集群分两部分控制平面Control Plane和工作节点Node。控制平面管决策工作节点管执行。3.1 控制平面集群的大脑控制平面通常跑在独立的机器上生产环境建议三台以上做高可用包含四个核心组件kube-apiserver整个集群的唯一入口。所有操作——无论是用户敲的kubectl命令还是各个组件之间的内部通信——都要经过API Server。它负责认证、授权、校验请求然后把数据写入etcd。你可以把它理解成公司的前台总机谁想找哪个部门办事都得先过它这一关。etcd集群的数据库保存着所有配置数据和状态信息。K8s的声明式设计能成立全靠etcd——期望状态存在里面实际状态也从各个节点汇报上来存进去控制平面的组件读它、写它保持集群往期望状态收敛。打过游戏的都知道存档文件丢了等于白打etcd就是K8s的存档文件做备份优先备份它。kube-scheduler负责分活。当一个新Pod要被创建时scheduler会综合考量每台节点的资源余量、亲和性要求、污点容忍等因素决定这个Pod该放到哪台机器上。打个比方这就是项目排期的人哪个开发手上活儿少新需求就派给谁。kube-controller-manager集群的监工里面跑着各种控制器每个控制器负责盯着某一类资源。比如Deployment控制器盯着副本数Node控制器盯着节点健康状态发现实际状态和期望状态不一致就发起调整。3.2 工作节点真正干活的地方工作节点上跑着应用容器核心组件有三个kubelet节点上的驻场代表负责和API Server通信。API Server下发指令说在这台机器上跑一个Nginx容器kubelet负责执行。它还周期性地向API Server汇报节点和Pod的状态。kube-proxy负责网络规则。它实现Service的负载均衡——当一个请求打到Service上kube-proxy通过iptables或IPVS规则把请求转发到后端的某个Pod上。容器运行时真正跑容器的东西可以是Docker、containerd或CRI-O。kubelet下发了跑一个容器的指令最终由容器运行时完成。第一次接触这些概念的时候会感觉组件特别多。我自己的记忆方法是用公司做类比API Server是前台etcd是档案室scheduler是项目排期controller-manager是监工kubelet是驻场代表kube-proxy是门卫保安。串起来就是一台完整的容器公司运作流程。3.3 PodK8s的最小调度单位认识完组件之后还得认识K8s里最核心的概念——Pod。这是新手入门的第一个重要概念也是最容易忽视为什么要这么设计的概念。Pod是K8s里最小的调度和部署单元里面可以装一个或多个容器。这些容器共享同一个网络命名空间共用一个IP共享存储卷。最常见的场景是一个主业务容器加一个辅助容器比如日志收集、流量代理它们俩天然需要在一起运行、共享资源放进同一个Pod最合适。为什么K8s不直接调度容器非要包一层Pod因为多个容器作为一个整体来调度比单独调度灵活得多。比如边车模式——业务容器负责处理请求旁边的日志容器负责采集日志两者必须跑在同一台机器上。如果K8s只认单个容器这个同生共死的亲密关系就没法表达。Pod这一层抽象把必须待在一起的容器们打包成一个原子单位调度、伸缩、故障恢复都以Pod为单位进行。明白Pod是什么之后K8s的世界就算正式对你打开了大门。4. 学习路线从零起步的不踩坑顺序网上关于K8s学习路线的帖子一抓一大把但很多都写得太学院派——从概念到概念学了一个月连一个Pod都没跑起来。根据我自己带人的经验正确的学习姿势应该是边用边学先用起来再深挖原理。如果你完全没有容器基础直接开啃K8s大概率会卡在Docker名词上如果你已经有Docker基础那节奏可以快一些。4.1 阶段一夯实容器基础1周先学Docker的三大核心操作镜像构建Dockerfile怎么写、容器运维docker run/exec/logs、数据卷和网络模式。不需要精通但要能独立写一个Dockerfile、把应用容器化跑起来。这个阶段的目标不是成为Docker专家而是建立容器化思维应用不再直接跑在操作系统上而是跑在镜像里环境、依赖、配置都打包在一起。我见过不少同学跳过这一步直接学K8s结果在写Pod YAML的时候连imagePullPolicy是干嘛的都搞不明白。基础不牢后面每走一步都是坑。4.2 阶段二熟悉K8s核心概念1-2周等容器玩得顺手了就开始接触K8s。这个阶段不需要搭建正式集群先学概念Pod、Deployment、Service、ConfigMap、Secret、Namespace。每学一个概念就对应地去查一个实战场景Deployment怎么滚动更新、Service怎么暴露访问、ConfigMap怎么挂载配置文件。有一种很有效的学习方式先在本地装好minikube把官方文档上的入门教程比如使用Deployment部署一个Nginx亲手敲一遍然后逐行读生成的YAML文件搞清楚每一行配置的含义。光看不练看十遍不如敲一遍记忆深刻。4.3 阶段三搭建真实集群1周概念学完下一步就是亲手搭建集群。很多人卡在这一步——意识上知道K8s是什么但没亲手搭过所以对集群始终没有实感。搭建方式从易到难排序minikube单机版K8s适合学习概念一键安装资源消耗小。缺点是生产环境用不上很多集群功能比如多节点调度体现不出来。kubeadm官方推荐的集群搭建工具一条kubeadm init就能初始化控制平面再在节点上执行kubeadm join加入集群。能搭建真正的多节点集群适合个人学习和中小型生产使用。这也是大多数人搭建集群的方式。二进制部署不用kubeadm直接从官网下载各组件的二进制文件、手动编写配置文件、通过systemd管理服务。这种方式最麻烦但最能让你理解各组件的协作关系面试二进制搭建K8s集群这类问题能答得明白。不建议新手一上来就挑战二进制除非你已经有kubeadm部署经验想在原理上再深挖一层。4.4 阶段四实战进阶持续集群跑起来了接下来就围绕实际业务需求学习和练习Ingress配置域名访问、PVC挂载持久化存储、Helm打包应用、基于K8s的CI/CD流水线。这个阶段没有终点你会持续遇到新问题而解决每个问题的过程都是学习。5. 环境准备与集群搭建手把手实操概念铺垫够了直接进入实操。我推荐用kubeadm搭建一个一主一从的测试集群这个方案能让你完整感受集群是怎么组织起来的又不会太复杂。下面记录的是我用Rocky Linux 10.2做完整搭建的过程操作步骤同样适用于CentOS、Ubuntu等主流发行版。5.1 环境清单与准备动作至少准备两台虚拟机一台做控制平面节点一台做工作节点。我自己的配置是每台2核4G内存、40G硬盘对于测试环境完全够用。操作系统用了Rocky Linux 10.2内核版本足够新对容器支持完善。开始之前有几项每个节点都必须做的初始化操作关闭Swap。K8s要求节点不能开启Swap因为kubelet依赖Linux的cgroup来管理资源限制开启Swap会让资源统计失真。执行swapoff -a临时关闭后还要编辑/etc/fstab注释掉swap相关行否则重启后又开启。加载内核模块。K8s的网络和容器运行依赖几个内核模块执行cat EOF | sudo tee /etc/modules-load.d/containerd.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter配置sysctl。让iptables能正确转发流量cat EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf net.bridge.bridge-nf-call-iptables 1 net.ipv4.ip_forward 1 net.bridge.bridge-nf-call-ip6tables 1 EOF sudo sysctl --system这步不做的话后续部署网络插件的时候会遇到Pod网络不通的问题而且报错信息不太直观排查起来很费劲。5.2 安装容器运行时这里有个容易纠结的问题用Docker还是用containerd我的建议是直接装containerd。K8s从1.24版本开始完全移除了对Docker Shim的支持虽然通过CRI-Docker还能让K8s用Docker但这不明智——多一层转换就多一些故障风险而且Docker在企业生产里的地位已经被containerd全面取代。K8s要的是符合CRI标准的容器运行时containerd就是最标准的那个。安装containerdyum install -y yum-utils yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo yum install -y containerd.io装好之后先别急需要改一下配置。containerd默认的配置文件不启用Cgroup管理而K8s要求容器运行时使用systemd作为cgroup driver否则会报failed to run Kubelet错误containerd config default | sudo tee /etc/containerd/config.toml然后编辑/etc/containerd/config.toml找到SystemdCgroup这一行把false改成true[plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc] [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options] SystemdCgroup true同时建议把沙箱镜像改成国内能拉的地址否则后面初始化集群时拉pause镜像会卡住。找到sandbox_image这一行sandbox_image registry.aliyuncs.com/google_containers/pause:3.10改完配置启动containerdsystemctl start containerd systemctl enable containerd5.3 安装kubeadm、kubelet、kubectlK8s的这些组件需要从官方源下载国内直连速度不稳定建议配上镜像源。我用的是阿里云的源cat EOF | sudo tee /etc/yum.repos.d/kubernetes.repo [kubernetes] nameKubernetes baseurlhttps://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled1 gpgcheck1 repo_gpgcheck1 gpgkeyhttps://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg EOF注意一个小坑kubernetes-el7-x86_64这个路径虽然写着el7但兼容el8/el9/el10这些较新的发行版我实测Rocky Linux 10.2安装没有任何问题。然后安装指定版本的组件。这里我强烈建议固定版本不要装latest。K8s社区迭代很快不同版本之间的API可能有差异固定版本能避免昨天还能用的命令今天就不行了的窘境yum install -y kubelet-1.30.0 kubeadm-1.30.0 kubectl-1.30.0装好后先把kubelet设置成开机自启但先不启动等集群初始化后再让它工作systemctl enable kubelet5.4 初始化控制平面节点在所有节点都装好上面这些组件之后在主节点上执行初始化。需要注意kubeadm init默认会去registry.k8s.io拉取控制平面组件的镜像国内网络环境下大概率拉不动。解决方法是先用kubeadm config images pull把镜像拉下来或者直接用--image-repository参数指定镜像仓库kubeadm init \ --apiserver-advertise-address192.168.10.10 \ --image-repository registry.aliyuncs.com/google_containers \ --pod-network-cidr10.244.0.0/16几个参数说一下--apiserver-advertise-address填主节点的IP指定API Server监听的地址--image-repository指定镜像仓库这里用的是阿里云的K8s镜像仓库速度快--pod-network-cidr是Pod网络的CIDR需要和后面装的网络插件匹配。这里用了10.244.0.0/16是Flannel网段的默认值初始化成功后终端会输出一段提示包含两部分关键信息一个是kubeadm join命令后面加入工作节点时要用注意保存另一个是配置kubectl的命令mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config执行完之后可以用kubectl get nodes查看集群状态此时主节点的状态应该是NotReady因为网络插件还没装。5.5 安装网络插件K8s的Pod需要跨节点通信这个功能由CNIContainer Network Interface插件实现。常用的有Flannel、Calico、Cilium。测试环境我选Flannel配置简单、理解容易kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml装完之后等一两分钟再执行kubectl get nodes主节点状态就变成Ready了。如果一直是NotReady排查顺序是先看kubelet日志journalctl -u kubelet -f再看Pod状态kubectl get pods -n kube-flannel绝大多数问题是镜像拉取失败或者网络插件YAML里的CIDR和初始化时不一致。5.6 工作节点加入在工作节点上执行初始化时保存的join命令就可以了kubeadm join 192.168.10.10:6443 --token xxxxxx --discovery-token-ca-cert-hash sha256:xxxxxx加入完成后回到主节点执行kubectl get nodes能看到两个节点都是Ready状态。到这里一个最精简的K8s集群就跑起来了。5.7 踩坑记录我在这步卡过的三个问题第一个坑是kubeadm init拉取镜像失败。这个最常见建议初始化前先手动拉一遍镜像kubeadm config images pull --image-repository registry.aliyuncs.com/google_containers确认没问题再执行init。第二个坑是初始化后kubectl命令报connection refused。原因通常是忘了拷贝admin.conf到~/.kube/config或者KUBECONFIG环境变量没设置。检查一下环境变量和文件路径就能解决。第三个坑是忽略版本兼容比如kubeadm和kubelet版本不一致导致控制平面初始化了但节点始终不Ready。用kubeadm version分别检查一下主节点和工作节点的版本必须保持大版本一致。6. 第一个实战Deployment部署Nginx跑通声明式流程集群搭好了接下来做第一个完整的实战用Deployment在集群里部署一个Nginx服务。这个例子虽然简单但能完整体验K8s核心工作流。6.1 创建Deployment告诉K8s我要什么先创建一个deployment.yaml文件apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80逐行解释一下apiVersion是API版本Deployment在apps/v1kind是资源类型metadata.name是Deployment的名字spec.replicas是期望的副本数spec.selector.matchLabels告诉Deployment我要管哪些Podspec.template是Pod模板里面定义了Pod的标签和要运行的容器。执行部署kubectl apply -f deployment.yaml查看部署状态kubectl get deployment kubectl get pods这时候你会看到有三个Pod状态都是Running。这就是前面说的声明式——你只说了我要3个NginxK8s自动把Pod创建出来并调度到了合适的节点上。6.2 验证自愈能力K8s最打动人的特性现在手动干掉一个Pod看K8s的反应kubectl delete pod nginx-deployment-xxxxxxxxxx再执行kubectl get pods你会发现系统自动创建了一个新Pod来补位而且新Pod的IP和之前的不同。整个过程完全不需要你介入这就是ReplicaSet控制器的职责期望3个实际变成2个了立即拉起一个补上。这个特性在物理机时代是奢侈的。你可以想象一下凌晨三点某台服务器上的应用挂了你人不在机房系统已经自动把应用在另一台机器上拉起来了——K8s的控制器就是这么干活的。6.3 暴露服务让集群外能访问Pod默认是集群内部的资源外部访问不到。需要创建Service来暴露它apiVersion: v1 kind: Service metadata: name: nginx-service spec: type: NodePort selector: app: nginx ports: - port: 80 targetPort: 80 nodePort: 30080selector.app: nginx会把流量路由到带有app: nginx标签的Pod上port是Service自己的端口targetPort是Pod里容器的端口nodePort是暴露到宿主机上的端口范围是30000-32767。执行kubectl apply -f service.yaml然后在浏览器访问任意节点的IP加端口比如http://192.168.10.10:30080就能看到Nginx的欢迎页了。6.4 从命令式到声明式的思维转变很多新手会习惯性地用kubectl run nginx --imagenginx这种命令式的方式创建资源。我强烈建议从一开始就习惯用YAML文件加kubectl apply的声明式流程。原因有三个第一YAML文件是可版本化的基础设施能提交到Git仓库里团队其他人可以review、可以审计出了问题可以追到底是谁在什么时候改了什么。第二kubectl apply是幂等的——你执行多少遍结果都一样。而kubectl run每次执行都会创建新的资源容易产生混乱。第三YAML文件里面能被注释、能被解释为什么这么配、当时怎么想的都能记录在案。半年之后回来看还能想起来当初的意图。我自己见过太多只会kubectl run的人一旦遇到稍微复杂的部署场景就傻眼多容器Pod怎么写健康检查怎么配资源限制怎么加这些都是YAML能回答的问题。所以哪怕是最简单的部署也建议写成YAML再apply养成好习惯比什么都重要。第二个实战建议是把刚才的Nginx换成你自己的应用试试。写一个简单的Spring Boot或Gin镜像用同样的方式部署上去改一改replicas参数试试扩容缩容看看Pod是怎么分布在各个节点上的。这一步做完容器化应用上K8s的路径就基本打通了——后面学Ingress、ConfigMap、持久化存储这些进阶内容的时候已经有了真实的手感。首次跑通一个Pod、看到浏览器里弹出Nginx欢迎页那种感觉后面再遇到多深的坑也值得继续挖下去。