一、背景与目标
一句话说清:验证如何将一个简单的 Web 应用(班级留言板)通过 Docker 容器化,并部署到 Kubernetes 集群中。
项目简介:班级留言板是一个极简的 Web 应用,前端提供留言输入和列表展示界面,后端提供留言增查接口和健康检查接口。技术栈为 Node.js + Nginx。通过该项目完整实践 Docker 镜像构建、跨节点镜像分发、Kubernetes 资源编排、滚动更新和基础排错。
核心验证点:
-
Dockerfile 编写与镜像构建流程
-
离线环境下镜像的跨节点分发
-
Kubernetes 多项目共存时的端口规划
-
Pod 启动失败的排查思路
二、环境说明
| 项目 | 详细信息 |
|---|---|
| 操作系统 | CentOS 7.9 |
| 内核版本 | 3.10.0-1160.el7.x86_64 |
| 虚拟化环境 | VMware 虚拟机(三台) |
| 节点规划 | Master(192.168.116.168)、Node2(192.168.116.170) |
| 容器运行时 | Docker 20.10.24 |
| Kubernetes 版本 | v1.20.15(kubeadm 搭建) |
| 网络插件 | Flannel(Pod 网段 10.244.0.0/16) |
| 应用技术栈 | Node.js 后端 + Nginx 前端 |
📸 截图 1:集群节点状态
三、操作步骤
第一步:准备项目源码
在 Master 的 /root/message-board/ 目录下创建三个子目录:backend/、frontend/、k8s/。
后端 :Node.js + Express,提供两个核心接口------GET /messages(获取留言列表)和 POST /messages(提交新留言),以及 GET /health 健康检查接口。配套的 Dockerfile 基于 node:20-alpine,仅安装生产依赖。
前端 :原生 HTML + JavaScript 构建的静态页面,通过 Nginx 提供访问。核心逻辑是通过 config.js 读取后端 API 地址,实现与后端的通信。配套的 Dockerfile 基于 nginx:1.25-alpine,将静态文件复制到 Nginx 默认目录。
K8s 资源:包括 Namespace 用于资源隔离、Deployment 管理 Pod 生命周期、Service 通过 NodePort 对外暴露服务、ConfigMap 注入前端配置。
📸 截图 2:项目目录结构
第二步:构建镜像
cd /root/message-board
docker build -t message-api:v1 ./backend
docker build -t message-frontend:v1 ./frontend
docker images | grep message
第三步:部署到 Kubernetes
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/api-deployment.yaml
kubectl apply -f k8s/api-service.yaml
kubectl apply -f k8s/frontend-configmap.yaml
kubectl apply -f k8s/frontend-deployment.yaml
kubectl apply -f k8s/frontend-service.yaml
kubectl get pods -n message-board
📸 截图 4:Pod 运行状态
四、遇到的问题与解决 ⚠️(核心内容)
问题 1:Docker Hub 镜像拉取超时
现象 :执行 docker build 时,卡在 FROM node:20-alpine,报错 Client.Timeout exceeded while awaiting headers。
排查过程 :用 ping 测试 Docker Hub 域名,发现 Master 节点无法访问外网。尝试配置国内镜像加速器,仍然超时------说明是网络层面的根本性不通,而非镜像源问题。
解决方案:改为在 Windows 本机拉取镜像,导出后上传到 Master 加载。
问题 2:nginx 基础镜像同样无法拉取
现象 :构建前端镜像时,FROM nginx:1.25-alpine 同样报网络超时。
解决方案 :用同样的方式从本机导出 nginx:alpine,上传到 Master 加载。注意 Dockerfile 中写的是 nginx:1.25-alpine,而导出的镜像标签是 nginx:alpine,需要用 docker tag 打上正确的标签。
问题 3:Pod 处于 ImagePullBackOff
现象 :虽然 Master 上镜像已就绪,但 Pod 状态一直显示 ContainerCreating,最终变为 ImagePullBackOff。
排查过程 :用 kubectl describe pod 查看事件详情:
关键信息:Failed to pull image "message-api:v1",并且显示 Pod 被调度到了 node2(192.168.116.170),而非 Master。
原因分析:Kubernetes 调度器根据资源情况将 Pod 分配到 node2,但 node2 上并没有这些镜像,且 node2 同样无法访问外网拉取。
解决方案:从 Master 导出镜像,复制到 node2 并加载。
# Master 导出
docker save -o message-api.tar message-api:v1
docker save -o message-frontend.tar message-frontend:v1
# 复制到 node2
scp *.tar root@192.168.116.170:/root/message-board/
# node2 加载
docker load -i message-api.tar
docker load -i message-frontend.tar
加载完成后删除卡住的 Pod,Deployment 会自动重建,新 Pod 即可正常启动。
五、观察与解读
关于镜像拉取策略
Kubernetes 中 imagePullPolicy 有三个可选值:Always(总是从仓库拉)、Never(只用本地)、IfNotPresent(本地没有才拉取)。本项目使用 IfNotPresent,按理说 node2 本地没有镜像时会从 Docker Hub 拉取,但由于网络不通导致失败。这也说明了一个重要原则:在离线环境中,必须确保镜像在集群的每个可能调度的节点上提前就位。
关于 ConfigMap 的设计
前端配置通过 ConfigMap 注入到 Pod 中,而非打包在镜像里。这样做的好处是:修改标题、API 地址等配置时,只需更新 ConfigMap 并重启 Pod,无需重新构建镜像,实现了配置与代码的解耦。
六、小结与下篇预告
本篇小结
-
网络隔离环境下,镜像构建所需的 base 镜像需要提前准备,手动分发到所有节点。
-
Pod 可能被调度到集群中任意节点,必须保证镜像在所有节点可用。
-
kubectl describe pod是排查 Pod 启动失败最有力的工具------事件(Events)部分会直接告诉你失败原因。 -
ConfigMap 实现了配置与镜像的解耦,提升了运维灵活性。
下篇预告
本篇完成了镜像构建和 Pod 的启动,但应用尚未对外暴露访问。下一篇将围绕:
-
Kubernetes Service 如何将应用暴露给外部访问
-
NodePort 端口规划(与已有项目共存)
-
滚动更新与回滚操作
-
ConfigMap 修改和配置热更新
敬请关注:《班级留言板 K8s 部署实践(下)------ 服务暴露与滚动更新》




