K8s 核心实战:从零部署 Nginx,掌握 Deployment、Service 与滚动更新
2026/9/14 4:15:15 网站建设 项目流程

K8s 核心实战|部署 Nginx 应用

如果你刚接触 K8s,正愁不知道从哪儿下手,我强烈建议你拿 Nginx 当第一个练手项目。原因很简单:Nginx 轻量、稳定、镜像小,部署起来几乎不会因为应用本身的问题卡住,它能让你把注意力全部放在 K8s 本身的概念和流程上。这篇文章我会从零开始,带你完整走一遍 Deployment、Service、Ingress、滚动更新、扩缩容这些核心操作,把每一步背后的原理和坑都讲透。无论你是刚装好 minikube 的新手,还是在准备 K8s 面试的开发,这篇都能帮你把“部署一个应用”这条链路彻底打通。

1. 部署前的准备:先想清楚三件事

1.1 集群环境怎么选:minikube、kubeadm 还是托管集群?

动手之前得先有个能用的集群。很多人一上来就问“K8s 怎么安装”,实际上 K8s 本身不是装出来的,它是一堆组件协同工作的结果,所以选环境的方式直接决定了你后续踩坑的数量。

如果你只是个人学习、跑通流程,我推荐minikube。它可以在你本机用 Docker 或虚拟机方式起一个单节点集群,一条命令搞定,资源占用也不大,唯一的缺点是它只适合测试,别指望在上面跑什么高可用实验。如果你是想模拟真实的多节点生产环境,那就用kubeadm自己搭,这个过程本身就能让你把 etcd、kubelet、kube-proxy 这些组件彻底搞明白,但耗时也长,第一次搭没有半天时间下不来。如果你所在的公司有云平台,直接买托管集群最省心,控制面都由平台管理,你只需要关心工作节点和应用。

我见过不少人纠结“二进制搭建 K8s 集群”是不是更牛,坦白说,二进制方式更多是为了让你理解组件间的关系,日常工作中基本用不到。学习阶段:先 minikube,再 kubeadm 搭一次,就足够了。

提示:不管用哪种方式,装好之后第一件事就是跑kubectl get nodes确认节点状态是 Ready。看到 NotReady 先别急着往下走,排查网络插件,不然后面所有操作都会莫名其妙地失败。

1.2 Nginx 镜像的版本选择与拉取策略

镜像这块很多人会忽略,实际上它是部署能不能成功的第一道关卡。Nginx 官方镜像在 Docker Hub 上有两个标签需要区分:nginx:latest是完整版,基于 Debian,带了很多调试工具;nginx:alpine是精简版,基于 Alpine Linux,体积只有 20 多 MB,生产环境更推荐。学习阶段我建议直接用nginx:latest,它的日志、配置路径都是最常规的位置,网上查资料最方便,不容易因为精简镜像缺东西而干扰你对 K8s 的理解。

还有一个很多人不知道的小细节:K8s 的镜像拉取策略imagePullPolicy有三种——AlwaysIfNotPresentNever。如果你不写,默认规则是这样的:镜像 tag 是latest或者是:latest结尾,默认 Always;其他 tag 默认 IfNotPresent。这个默认行为导致了一个很常见的坑:你本地方改了镜像重新打成 latest,推送后部署,K8s 仍然拉取的是旧缓存。所以生产环境我习惯给镜像打上明确的版本号,不碰 latest。

2. 部署 Nginx:从 YAML 到运行中的 Pod

2.1 为什么用 Deployment 而不是直接创建 Pod

很多人第一次接触 K8s 会想:我要部署一个 Nginx,那就创建一个 Pod 不就行了吗?不行。直接创建 Pod 是最原始的方式,它不具备自愈能力——Pod 所在的节点挂了,这个 Pod 就彻底消失了,没有任何组件会替你重新拉起它。

Deployment 是 K8s 里管理无状态应用的标准控制器,它的作用你可以理解成一个“监工”。你在 Deployment 里声明了期望状态(比如我要 3 个 Nginx 副本),K8s 的控制器就会持续对比实际状态,发现少了一个就自动补一个,发现多了就回收掉。这种“声明式管理”是整个 K8s 最核心的设计思想,你只需要告诉它“最终要达到什么状态”,至于怎么达到、什么时候达到,全由控制面自己去协调。

这里就顺带回答一个老被问到的问题:K8s 和 Docker 到底什么关系?Docker 解决的是一台机器上怎么把应用打包、隔离开运行的问题;K8s 解决的是很多台机器上怎么统一调度、管理这些容器的平台问题。Docker 是“工具”,K8s 是“操作系统级别的调度平台”,两者不是替代关系,而是协作关系。

2.2 编写第一个 Nginx Deployment 并创建

下面是一个最精简但五脏俱全的 Nginx Deployment YAML,建议你逐行看注释:

apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment labels: app: nginx spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.27-alpine ports: - containerPort: 80

这里最容易被忽视的是spec.selector.matchLabelstemplate.metadata.labels的对应关系。你可以把 Deployment 理解成通过标签“管理”Pod 的:Deployment 不会直接绑死某个 Pod,而是通过 label selector 去筛选哪些 Pod 归自己管。如果你把两个地方的标签写得不一致,Deployment 会直接报错,因为控制器不知道你究竟要管理哪些 Pod。

创建命令很简单:

kubectl apply -f nginx-deployment.yaml

然后立即查看状态:

kubectl get pods

正常情况下你会看到 3 个 Pod 陆续变成 Running 状态,中间可能会经历 ContainerCreating。如果某个 Pod 一直停留在 ImagePullBackOff,大概率是镜像拉不到,这个我在后面常见问题部分详细说。

创建之后我还建议你养成一个习惯:用kubectl describe pod <pod名>查看事件记录。describe会把它从调度到拉镜像、到启动容器的整个事件流列出来,这是排查问题的第一利器,比kubectl get pods只看到状态要有用得多。

3. 暴露服务:从集群内部到外部访问

3.1 Service 的三种常见类型,怎么选?

Pod 创建出来了,但如果没 Service,外部是访问不到它的。原因有两个:Pod 的 IP 是动态分配的,随时可能变;而且你有 3 个 Nginx 副本,总不能让客户端自己选一个 IP 去访问吧。Service 就是用来解决这个问题的,它给一组 Pod 提供一个稳定的虚拟 IP(ClusterIP),并在内部做负载均衡。

Service 的类型主要有三种,选型的逻辑很简单:

Service 类型访问方式适用场景
ClusterIP仅集群内部访问后端服务之间调用,比如前端访问后端 API
NodePort通过节点 IP + 固定端口访问测试环境、临时验证、没有负载均衡器的场景
LoadBalancer通过云平台负载均衡器访问生产环境对外暴露服务

学习阶段我一般建议先从 NodePort 入手,因为它在任何环境下都能用,不需要依赖云厂商。

apiVersion: v1 kind: Service metadata: name: nginx-service spec: type: NodePort selector: app: nginx ports: - port: 80 targetPort: 80 nodePort: 30080

创建后通过kubectl get svc可以看到 Service 被分配了一个 ClusterIP,然后你就能在浏览器里访问http://<节点IP>:30080看到 Nginx 的欢迎页了。

这里有一个经常被问到的点:porttargetPort的区别。port是 Service 对外暴露的端口,targetPort是 Pod 里容器实际监听的端口。Nginx 容器监听 80,所以 targetPort 是 80;你愿意的话,port可以设置为任意值,K8s 内部会自动做转发。

3.2 从 NodePort 到 Ingress:流量入口的设计思路

NodePort 虽然能访问,但它有两个明显问题:一是端口范围限制在 30000-32767,不够“正规”;二是如果节点数量多了,客户端要记住一堆节点 IP,没法做域名访问和路径转发。生产环境对外暴露 HTTP 服务,更多是用 Ingress。

Ingress 可以理解成一个“智能入口”,它工作在七层,能够根据域名和路径把请求转发到不同的 Service。你访问nginx.example.com它帮你转到 Nginx 服务,访问api.example.com它帮你转到 API 服务,不需要客户端关心背后的 Service 到底是什么、在哪。

要在 K8s 里启用 Ingress,你需要先安装一个 Ingress Controller,最常用的是ingress-nginx。安装完成后,创建一个 Ingress 资源:

apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx-ingress spec: ingressClassName: nginx rules: - host: nginx.example.com http: paths: - path: / pathType: Prefix backend: service: name: nginx-service port: number: 80

配置完成后,只要把nginx.example.com解析到集群节点 IP,就能用域名访问了。这里我特别想强调pathType这个字段,它有PrefixExact两种,Prefix表示路径前缀匹配,Exact表示精确匹配。如果你同时配置了//nginx两条路径,Ingress Controller 会优先匹配更具体的路径,这也是很多人配置了多条规则后转发混乱的原因所在。

4. 升级与回滚:让发布过程可控

4.1 滚动更新的机制与参数调优

应用部署只是开始,真正日常操作最多的是“发布新版本”。Deployment 的滚动更新机制就是用来平滑切换版本的:它不会先把旧 Pod 全部杀掉再起新的,而是逐个替换,保持服务不中断。

我现在把 Nginx 从 1.27 升级到 1.28,只需要一条命令:

kubectl set image deployment/nginx-deployment nginx=nginx:1.28-alpine

然后观察更新过程:

kubectl rollout status deployment/nginx-deployment

你会看到输出里滚动显示有多少个 Pod 已更新、多少个还没更新。这时候再kubectl get pods,会发现新旧 Pod 是同时存在的,这正是滚动更新的特征。

这里有两个参数控制滚动更新的“激进程度”,在 Deployment 的 spec 里配置:

spec: strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0

maxSurge表示更新过程中最多可以比期望副本数多出多少个 Pod,maxUnavailable表示最多允许有多少个 Pod 处于不可用状态。生产环境我习惯设maxSurge: 1, maxUnavailable: 0,目标是一次只多起一个新 Pod,等它 Ready 了再杀掉一个旧 Pod,整个过程始终保持还有 3 个可用副本。如果把 maxUnavailable 设为 3,理论上更新会更快,但会出现短暂没有可用服务的情况,这就要看你服务的容忍度来权衡了。

4.2 版本回滚:改了出问题怎么办?

有一次我在生产环境升级了一个配置,结果新版本起不来,Pod 一直 CrashLoopBackOff。这时候如果手动把 YAML 改回去再 apply 一次,也能恢复,但更规范的做法是用 K8s 原生的回滚机制。

Deployment 每次更新都会记录一个版本号,查看历史:

kubectl rollout history deployment/nginx-deployment

如果你发现第 3 个版本有问题,想回退到第 2 个版本:

kubectl rollout undo deployment/nginx-deployment --to-revision=2

回滚完成后,再次确认状态和 Pod 是否健康。有个细节要注意:回滚操作本身也会产生一个新的 revision,所以 rollback 之后历史列表里会多出一条记录,这是正常现象,不用困惑。

5. 扩缩容与优雅终止:应对流量变化的底气

5.1 手动扩缩容:从 3 个副本到 10 个副本

Nginx 部署好了,突然有活动流量暴增,需要临时扩容,这时候 Deployment 的价值就体现出来了。你不需要去一台台服务器上手动装 Nginx、修改负载均衡配置,只需要:

kubectl scale deployment/nginx-deployment --replicas=10

再看kubectl get pods,K8s 会立即开始调度新的 Pod 到合适的节点上。缩容同理,把数字改小就行。

扩缩容这块有个面试常问的点:K8s 的调度器是怎么决定新 Pod 跑到哪个节点上的?简单说,它综合了节点资源余量、污点和容忍、节点亲和性等因素,先过滤掉不符合条件的节点,再对符合条件的节点打分,最终选最高分的节点。实际工作中,如果某个节点的 CPU、内存明显比别的节点紧张,调度器会自动避开它。

手动扩缩容适合“你知道接下来流量会涨”的场景,比如大促前提前扩容。如果是流量不可预测,那要上 HPA(HorizontalPodAutoscaler),让它根据 CPU 利用率或自定义指标自动扩缩容,这是后话,但底层原理你已经会了。

5.2 优雅终止:为什么不要直接 kill Pod?

你的应用接入了用户请求,关闭 Pod 就不是简单地把容器杀掉那么简单。K8s 在删除 Pod 时有一套“优雅终止”流程:先发送 SIGTERM 信号给容器,等一个宽限期,宽限期到了才会发 SIGKILL 强制杀掉。

这也是一个很常见的面试题:为什么 Nginx 收到 SIGTERM 后还能把正在处理的请求处理完?因为 Nginx 的 master 进程收到 SIGTERM 后,会停止接收新连接,同时等待 worker 进程处理完当前请求再退出。如果宽限期设置太短(默认是 30 秒),老连接还没来得及处理完就被强杀,用户就会看到连接中断。

相关参数有两个,在 Pod spec 里配置:

spec: terminationGracePeriodSeconds: 30 containers: - name: nginx lifecycle: preStop: exec: command: ["/bin/sh", "-c", "sleep 5"]

terminationGracePeriodSeconds控制总的宽限期,preStop钩子可以在 SIGTERM 发送前执行额外操作。我见过有人为了让 Nginx 更平滑地摘流量,在 preStop 里 sleep 5 秒,让负载均衡器先把该节点摘掉,再让容器退。对于生产环境的服务,这两个参数值得认真调一调,它们直接影响你的发布过程中有没有用户感知到抖动。

6. 常见问题与排查技巧实录

6.1 镜像拉取失败:ImagePullBackOff 的常见原因

这是新手最容易遇到的问题。部署完执行kubectl get pods,发现 Pod 状态是 ImagePullBackOff。别慌,先执行:

kubectl describe pod <pod名>

看 Events 部分,一般会有具体原因。最常见的几个原因:镜像名写错了;镜像 tag 不存在;私有仓库需要认证信息但没配;国内网络拉取 Docker Hub 镜像超时。

最后一个问题在真实环境里很常见。解决办法包括:用国内镜像源地址作为前缀,比如docker.io换成你本地能访问的镜像仓库地址;或者配置 imagePullSecret,让 K8s 有权限从你的私有仓库拉取镜像。这块没有统一答案,看你的网络环境。

6.2 节点端口访问不到:Service 正常但浏览器打不开

这个问题的排查思路要按顺序来:

  1. 先确认 Pod 正常:kubectl get pods是不是 Running。
  2. 再确认 Service 有没有绑定到 Pod:kubectl get endpoints,看 ENDPOINTS 列有没有 IP。
  3. 然后确认节点防火墙有没有放行对应端口。
  4. 最后确认 NodePort 端口号是不是在 30000-32767 范围内。

我遇到过一个隐蔽的情况:公司在云平台上有安全组规则,NodePort 虽然配置了,但云平台的安全组只放行了 80/443,导致外网访问不了。这种问题光查 K8s 内部是查不出来的,得去云控制台看。所以排查问题要有一个意识:K8s 内网通不代表公网通,链路每一跳都要验证。

6.3 Pod 一直 CrashLoopBackOff:日志说了什么?

CrashLoopBackOff 表示容器启动后就崩溃,K8s 反复重启。第一步是看日志:

kubectl logs <pod名>

如果是 Nginx 容器,最可能的原因是端口冲突、配置文件错误或者权限问题。有一个小技巧:如果容器崩溃太快来不及看日志,用kubectl logs --previous看上一次启动的日志。这个命令在调试那种启动即退出的容器时非常救命。

6.4 关于 K8s 和 Docker 的区别,一张表说清楚

这个搜索词频率实在太高了,也确实是很多人的概念盲区。我直接用一个表格帮你建立清晰认知:

维度DockerKubernetes
本质容器运行时,单机工具容器编排平台,集群管理系统
作用层面构建、运行、分发容器镜像调度、扩展、网络、存储、自愈
单机 vs 集群只在单台机器上工作管理整个集群的机器
类比生产线上的工人整个工厂的车间调度系统

两者不是竞争关系,而是配合关系。K8s 通过容器运行时接口调用 Docker(或 containerd)来真正创建和运行容器。你可以在 K8s 集群里用 Docker 打包镜像,但 K8s 管理的是镜像背后的整个生命周期。

7. 从部署 Nginx 到真正理解 K8s:我的体会

Nginx 部署这个例子看起来简单,但它把 K8s 最核心的几个机制全部串起来了:声明式 API 的思想(你写 YAML 声明期望状态)、控制器模式(Deployment 持续调和状态)、服务发现与负载均衡(Service)、流量治理(Ingress)、发布策略(滚动更新)、自愈和伸缩(控制器自动恢复、手动扩缩容)。这些概念理解透了,后面再学 StatefulSet、DaemonSet、Helm、HPA 都会轻松很多。

最后分享一个我个人的学习习惯:K8s 的命令和 YAML 字段不可能全记住,也不需要全记住。关键是掌握排查思路——看到某个状态,知道该用哪条命令去看哪些信息,能定位到问题出在哪一层。这个能力比死记硬背所有命令要有用得多。你拿 Nginx 练完这一遍,把kubectl getdescribelogsrollout这四条命令用熟练,K8s 的门就算真正踏进去了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询