[特殊字符] 第一篇:班级留言板 K8s 部署实践(上)—— Docker 容器化与镜像构建

一、背景与目标

一句话说清:验证如何将一个简单的 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,无需重新构建镜像,实现了配置与代码的解耦。

六、小结与下篇预告

本篇小结

  1. 网络隔离环境下,镜像构建所需的 base 镜像需要提前准备,手动分发到所有节点。

  2. Pod 可能被调度到集群中任意节点,必须保证镜像在所有节点可用。

  3. kubectl describe pod 是排查 Pod 启动失败最有力的工具------事件(Events)部分会直接告诉你失败原因。

  4. ConfigMap 实现了配置与镜像的解耦,提升了运维灵活性。

下篇预告

本篇完成了镜像构建和 Pod 的启动,但应用尚未对外暴露访问。下一篇将围绕:

  • Kubernetes Service 如何将应用暴露给外部访问

  • NodePort 端口规划(与已有项目共存)

  • 滚动更新与回滚操作

  • ConfigMap 修改和配置热更新

敬请关注:《班级留言板 K8s 部署实践(下)------ 服务暴露与滚动更新》