传统业务系统迁移到 Kubernetes 集群的完整指南
第一部分:迁移的理论步骤(“云原生”化流程)
将传统业务系统迁移到 Kubernetes 集群,本质上是将应用逐步“云原生”化的过程。核心思路是:先将应用“容器化”,再将其“编排”到 K8s 集群中。以下是标准步骤:
1.1 将应用封装进容器(镜像构建)
应用容器化是迁移的第一步,需要设计并规划好 Docker 镜像的构建方案。由于 Docker 镜像具有分层特性,建议按以下层次构建(分层构建有利于复用和加速构建):
- 操作系统层:制作公司常用的系统版本(如 Rocky Linux、Ubuntu),可在官方基础镜像上添加自己需要的软件包(如网络工具、vim 等)。
- 运行环境层:在操作系统层之上,打包业务常用的运行环境,例如:
- JDK 7 / JDK 8 / JDK 11
- JDK 8 + Tomcat 8 / Tomcat 9
- Python 3 环境
- Nginx + PHP-FPM 等
这些可作为通用基础镜像模板供不同项目使用。
- 应用层:在通用运行环境的基础上,根据具体应用进行调整(如添加特定依赖、配置文件),最后将编译好的代码(JAR/WAR 或源码)放入镜像中。
1.2 将容器放入 Pod 中
应用容器化后,就需要考虑如何在Pod中运行。Pod 是 Kubernetes 管理的最小单元,Kubernetes 不直接管理容器,而是管理 Pod,一个 Pod 可以包含一个或多个容器。
需要决策:
- 单容器 Pod还是多容器 Pod(例如主容器 + 辅助容器如日志收集 sidecar)?
- 为 Pod 设置资源限制(CPU/内存 requests 和 limits)。
- 配置健康检查(livenessProbe 和 readinessProbe)。
- 确定是否需要数据持久化(挂载 Volume)。
1.3 使用控制器 (Controllers) 管理 Pod
单一 Pod 如果出现故障,会影响业务连续性,因此需要多副本(类似传统集群)。Kubernetes 提供了多种Controller,需根据应用类型选择合适的控制器,只需在 Pod 模板上封装对应配置即可:
| 控制器 | 用途 |
|---|---|
| Deployment | 封装了 Pod 的副本管理、滚动更新、回滚、扩缩容等功能,适用于无状态应用(最常用)。 |
| DaemonSet | 保证集群中每个 Node 上有且只有一个 Pod 在运行,常用于节点监控、日志收集等。 |
| StatefulSet | 为有状态应用提供稳定的网络标识(如主机名)和有序部署/扩缩容,适用于数据库、消息队列等。 |
| Job | 运行一次性任务(如批处理),任务完成后 Pod 自动退出。 |
| CronJob | 运行定时任务(类似 Linux crontab)。 |
1.4 使用 Service 管理 Pod 访问
使用 Deployment 通过多副本保证了 Pod 的高可用和横向扩展,此时需要负载均衡将流量分发到多个 Pod。KubernetesService就是实现此功能的资源对象,它为 Pod 提供稳定的访问入口(ClusterIP)。
Service 的负载均衡实现方式支持两种模式:iptables和ipvs(后者性能更高)。Service 类型主要有:
- ClusterIP(默认):仅集群内部访问。
- NodePort:在每个 Node 上开放一个端口,供外部访问。
- LoadBalancer:对接云厂商的负载均衡器。
1.5 使用 Ingress 提供外部访问(七层路由)
集群内部可以直接使用 Service 名称(DNS 名)进行通信,但外部访问集群内部服务时,由于网络隔离,通常需要通过 NodePort 或 LoadBalancer 暴露端口。但这些属于四层负载均衡(TCP/UDP),若要实现七层(HTTP/HTTPS)路由(如按域名、路径转发),Kubernetes 提供了Ingress资源。
Ingress 需要配合Ingress Controller(如 Nginx Ingress Controller、Traefik)才能生效。注意:Ingress Controller 是独立组件,不包含在 kube-controller-manager 中,需要单独部署。
1.6 使用 PV / PVC 管理持久化数据
容器中的存储是临时的,Pod 重启后数据会丢失。对于需要持久化数据的应用(如数据库、文件上传),必须使用外部存储方案。Kubernetes 通过PersistentVolume(PV)和PersistentVolumeClaim(PVC)抽象存储资源:
- PV是集群中的存储资源(由管理员预先创建或动态供给)。
- PVC是用户对存储的请求(声明),Pod 通过 PVC 挂载卷。
这样应用无需关心底层存储实现(NFS、Ceph、云存储等)。
也结合存储类实现自动划分PV
1.7 使用 ConfigMap 管理应用配置文件
在 DevOps 实践中,强调代码与配置分离。Kubernetes 提供了ConfigMap和Secret用于管理配置信息(环境变量、配置文件等):
- ConfigMap存储非敏感配置(如应用参数)。
- Secret存储敏感信息(如密码、Token)。
可以从文件、目录或字面值创建 ConfigMap/Secret,然后在 Pod 中通过环境变量或挂载卷的方式使用。
1.8 日志收集
为确保日志能够集中管理,建议应用将日志输出到标准输出(stdout)和标准错误(stderr),这样 Kubernetes 默认日志机制(如kubectl logs)能够捕获。然后可集成 EFK(Elasticsearch + Fluentd + Kibana)或 ELK(Elasticsearch + Logstash + Kibana)等日志系统统一收集、存储和展示。
1.9 监控告警
集群和应用的状态监控至关重要。一般集成Prometheus采集指标,配合Grafana进行可视化展示,并设置告警规则(通过 AlertManager)。可以监控 Pod 资源使用、应用性能、集群健康等。
1.10 关于有状态应用(如数据库)的迁移策略
数据库等有状态服务迁移到 K8s 较为复杂(涉及数据一致性、主从同步、备份恢复等)。一个常见的策略是:在迁移初期将数据库保留在 K8s 集群外部(如使用云数据库或自建物理机),让应用先通过 Service 访问外部数据库(使用 ExternalName 或 Endpoints)。待应用稳定运行后,再评估是否使用StatefulSet+PersistentVolume将其也容器化。
第二部分:服务部署与迁移实战案例
以下案例逐步展示如何将不同类型的应用部署到 Kubernetes 集群,包括 WordPress(MySQL + WordPress)、LNMP 环境(Nginx+PHP-FPM + MySQL)、Spring Boot 应用和 Tomcat 应用。
前置条件:已搭建好 Kubernetes 集群,并配置了 NFS 存储类(
storageClassName: "nfs",关于存储类的创建可前往:Kubernetes(K8s)笔记Day06查看)或替换为其他 StorageClass。所有 YAML 文件需在 Master 节点(或任意可访问集群的机器)上执行kubectl apply -f <file>。
案例 1:LAMP 架构部署 WordPress(使用 MySQL 官方镜像 + WordPress 镜像)
本案例通过 MySQL 与 WordPress 官方镜像部署持久化的博客网站,使用 Deployment + PVC 保存数据,并通过 NodePort 对外访问。
这个案例把应用运行所需的参数(比如数据库地址、账号密码),从代码内部“抽”出来,放到代码外部(比如环境变量里)去设置,是配置外部化的典范,利用公共镜像做到了“即插即用”。
如果业务本身遵循云原生设计(或官方已提供支持),迁移就是改几个变量的事,这也是迁移收益最高、风险最低的方式
1.1 部署 MySQL
创建wp-mysql.yaml文件,包含 Service、PVC 和 Deployment:
需要提前将mysql:5.7镜像上传到hd2,hd3
[root@hd1 wp]# vim wp-mysql.yaml#创建serviceapiVersion:v1kind:Servicemetadata:name:mysql-svcspec:type:ClusterIPports:-port:3306targetPort:3306selector:app:wp-mysql---#创建数据库需要的存储----pvcapiVersion:v1kind:PersistentVolumeClaimmetadata:name:mysql-pvclabels:app:wp-mysqlspec:accessModes:-ReadWriteManystorageClassName:"nfs"resources:requests:storage:2Gi---#创建控制器apiVersion:apps/v1kind:Deploymentmetadata:name:wp-mysqlspec:selector:matchLabels:app:wp-mysqlreplicas:1template:metadata:labels:app:wp-mysqlspec:containers:-name:wp-mysqlimage:docker.io/mysql:5.7imagePullPolicy:IfNotPresentports:-containerPort:3306env:-name:MYSQL_ROOT_PASSWORDvalue:"123456"-name:MYSQL_DATABASEvalue:"wordpress"-name:MYSQL_USERvalue:"wordpress"-name:MYSQL_PASSWORDvalue:"wordpress"volumeMounts:-name:mysqlmountPath:/var/lib/mysqlvolumes:-name:mysqlpersistentVolumeClaim:claimName:mysql-pvc说明:
- Service
mysql-svc仅供集群内部访问(ClusterIP)。 - PVC
mysql-pvc申请 2Gi 存储,访问模式为ReadWriteMany(NFS 支持)。 - Deployment 仅 1 个副本,环境变量注入数据库初始配置。
1.2 部署 WordPress
创建wp-wordpress.yaml文件:
[root@hd1 wp]# vim wp-wordpress.yaml#创建wordpress的serviceapiVersion:v1kind:Servicemetadata:name:wordpressspec:type:NodePortports:-port:80nodePort:30001selector:app:wordpress---#创建wordpress的存储pvcapiVersion:v1kind:PersistentVolumeClaimmetadata:name:wordpress-pvclabels:app:wordpressspec:accessModes:-ReadWriteManystorageClassName:"nfs"resources:requests:storage:2Gi---#创建wordpress业务系统以及控制器apiVersion:apps/v1kind:Deploymentmetadata:name:wp-wordpressspec:selector:matchLabels:app:wordpressreplicas:1template:metadata:labels:app:wordpressspec:containers:-name:wp-wordpressimage:wordpress:6.2.1-apacheimagePullPolicy:IfNotPresentports:-containerPort:80env:-name:WORDPRESS_DB_HOSTvalue:"mysql-svc"# 通过 Service 名称访问 MySQL-name:WORDPRESS_DB_USERvalue:"wordpress"-name:WORDPRESS_DB_PASSWORDvalue:"wordpress"volumeMounts:-name:wordpressmountPath:/var/www/html# 挂载 WordPress 程序文件(可持久化)volumes:-name:wordpresspersistentVolumeClaim:claimName:wordpress-pvc说明:
- Service 类型为
NodePort,对外暴露端口30001。 - WordPress 通过环境变量
WORDPRESS_DB_HOST指向 MySQL 的 Service 名称(集群内 DNS 解析)。 - PVC 挂载
/var/www/html以保存上传的插件、主题等。
1.3 初始化和测试
- 依次执行:
kubectl apply-fwp-mysql.yaml kubectl apply-fwp-wordpress.yaml - 等待 Pod 就绪后,打开浏览器访问
http://<任意NodeIP>:30001,进入 WordPress 安装界面(如图 1)。 - 填写站点信息、管理员账户,点击“安装”。
- 登录后即可看到博客后台(如图 2、图 3)。
注意:若使用 NFS 作为 PV 供给,需确保 NFS 服务正常运行且 PVC 绑定成功。
案例 2:LNMP 架构(Nginx + PHP-FPM + MySQL)运行 WordPress
本案例更贴近传统 PHP 项目部署方式:业务组件(Nginx+PHP)和 MySQL 跑在容器里,但 PHP 业务源码(WordPress 代码) 放在容器外,通过 PVC 挂载进容器。
这个案例是最保守的“平移”策略,适合应急过渡
2.1 部署 Nginx + PHP-FPM
创建lnmp-nginx-php.yaml:
[root@hd1 lnmp]# vim lnmp-nginx-php.yamlapiVersion:v1kind:Servicemetadata:name:nginx-php-svclabels:app:lnmp-nginx-phpspec:type:NodePortports:-port:80nodePort:30001selector:app:lnmp-nginx-php---apiVersion:v1kind:PersistentVolumeClaimmetadata:name:lnmp-web-pvclabels:app:wordpressspec:accessModes:-ReadWriteManystorageClassName:"nfs"resources:requests:storage:2Gi---apiVersion:apps/v1kind:Deploymentmetadata:name:lnmp-nginx-phpspec:selector:matchLabels:app:lnmp-nginx-phpreplicas:1template:metadata:labels:app:lnmp-nginx-phpspec:containers:-name:lnmp-nginximage:richarvey/nginx-php-fpm# 集成了 Nginx + PHP-FPM 的镜像imagePullPolicy:IfNotPresentports:-containerPort:80-containerPort:9000volumeMounts:-name:nginx-datamountPath:/var/www/html/# 网站根目录volumes:-name:nginx-datapersistentVolumeClaim:claimName:lnmp-web-pvc镜像:richarvey/nginx-php-fpm是一个由第三方开发者 richarvey 在 Docker Hub 上构建并维护的公开镜像,可以直接使用docker pull 拉取。GitHub 源码仓库地址:https://github.com/richarvey/nginx-php-fpm
2.2 部署 MySQL
创建lnmp-mysql.yaml:
[root@hd1 lnmp]# vim lnmp-mysql.yamlapiVersion:v1kind:Servicemetadata:name:lnmp-mysql-svcspec:type:ClusterIPports:-port:3306targetPort:3306selector:app:lnmp-mysql---apiVersion:v1kind:PersistentVolumeClaimmetadata:name:lnmp-mysql-pvclabels:app:lnmp-mysqlspec:accessModes:-ReadWriteManystorageClassName:"nfs"resources:requests:storage:2Gi---apiVersion:apps/v1kind:Deploymentmetadata:name:lnmp-mysqlspec:selector:matchLabels:app:lnmp-mysqlreplicas:1template:metadata:labels:app:lnmp-mysqlspec:containers:-name:lnmp-mysqlimage:docker.io/mysql:5.7imagePullPolicy:IfNotPresentports:-containerPort:3306env:-name:MYSQL_ROOT_PASSWORDvalue:"123456"-name:MYSQL_DATABASEvalue:"wordpress"-name:MYSQL_USERvalue:"wordpress"-name:MYSQL_PASSWORDvalue:"wordpress"volumeMounts:-name:mysqlmountPath:/var/lib/mysqlvolumes:-name:mysqlpersistentVolumeClaim:claimName:lnmp-mysql-pvc2.3 部署 WordPress 源码
WordPress 源码需要提前放入 PVC 挂载的目录中(NFS 共享目录)。
下载 WordPress 安装包(官网:https://cn.wordpress.org):
[root@hd1 ~]# tar xf wordpress-6.7.1-zh_CN.tar.gz[root@hd1 ~]# cd wordpress/# 将解压后的所有内容移动到 PVC 对应的 NFS 目录(路径根据实际 PVC 挂载点调整)[root@hd1 wordpress]# mv ./* /data/nfs_pro/default-lnmp-web-pvc-pvc-33ebc2d2-9e4f-42d9-9110-7519f9d5b890/说明:该路径是 NFS 服务器上为
lnmp-web-pvc分配的实际存储路径,可通过kubectl get pv查看。依次部署:
kubectl apply-flnmp-nginx-php.yaml kubectl apply-flnmp-mysql.yaml访问
http://<NodeIP>:30001,即可看到 WordPress 安装界面(如下图)。点击“现在就开始”,填写数据库连接信息(数据库主机为
lnmp-mysql-svc,用户名wordpress,密码wordpress,数据库名wordpress),如下图所示:提交后继续安装,设置站点标题、管理员账号等,最终登录后台:
案例 3:部署 Spring Boot Web 应用
Spring Boot 是目前 Java 后端开发的主流框架。本案例演示如何将 Spring Boot 应用打包为 Docker 镜像并部署到 K8s 中。
本案例实现了完全无状态、声明式管理、随时弹性伸缩的云原生最佳实践。
3.1 准备 Spring Boot 项目
已有 Spring Boot 项目(springboot-web-demo),编译生成 JAR 包springboot-web-demo-1.0-SNAPSHOT.jar。
3.2 编写 Dockerfile
在当前目录创建dockerfile(注意文件名,通常为Dockerfile):
FROM openjdk:8-jdk COPY target/springboot-web-demo-1.0-SNAPSHOT.jar /springboot-web.jar ENTRYPOINT ["java", "-jar", "/springboot-web.jar"]对于spring boot的web项目,它把整个项目(包括内置的 Tomcat 代码)打包成了一个可执行文件,你只需要把它放在容器的工作目录(如 /app)下,用 java -jar 启动即可
构建镜像并推送到私有仓库(示例仓库地址为www.zutuanxue.com/library/spring-web:v1):
dockerbuild-tspring-web:v1.dockertag spring-web:v1 www.zutuanxue.com/library/spring-web:v1dockerpush www.zutuanxue.com/library/spring-web:v1# 若需推送3.3 编写 Kubernetes 部署文件spring-web.yaml
cat spring-web.yamlapiVersion:v1kind:Servicemetadata:name:spring-web-svcspec:type:NodePortports:-port:8181targetPort:8080nodePort:30600selector:app:spring-web---apiVersion:apps/v1kind:Deploymentmetadata:name:spring-webspec:selector:matchLabels:app:spring-webreplicas:3template:metadata:labels:app:spring-webspec:containers:-name:spring-webimage:www.zutuanxue.com/library/spring-web:v1imagePullPolicy:IfNotPresentports:-containerPort:8080说明:
- Service 将外部端口
30600映射到 Pod 的8080端口(应用默认端口)。 - Deployment 副本数为 3,实现高可用。
- 未配置持久化存储(无状态应用)。
3.4 部署并测试
kubectl apply-fspring-web.yaml kubectl get pod kubectl get svc打开浏览器访问http://<NodeIP>:30600/hello?name=zutuanxue(假设应用有/hello接口)。即可看到响应。
案例 4:构建 Tomcat 应用(部署 WAR 包)
对于传统的 Java Web 应用(WAR 包),需要部署到 Tomcat 容器中。
本案例是中间状态,容器化程度加深(代码进镜像),但启动命令仍依赖容器脚本
4.1 准备 WAR 包,并编写 Dockerfile 构建镜像
假设已有 WAR 文件jspdemo_war.war,编写 Dockerfile:
FROM tomcat COPY jspdemo_war.war /usr/local/tomcat/webapps/jspdemo_war.war ENTRYPOINT ["catalina.sh","run"]将当前目录下的 jspdemo_war.war 文件,永久性地写入到镜像文件系统中的 /usr/local/tomcat/webapps/ 目录下
Java Web 项目(打 WAR 包)要放在 Tomcat 的专用部署目录(/usr/local/tomcat/webapps/)
构建并推送镜像(示例仓库www.zutuanxue.com/library/tomcat-java:v1):
dockerbuild-ttomcat-java:v1.dockertag tomcat-java:v1 www.zutuanxue.com/library/tomcat-java:v1dockerpush www.zutuanxue.com/library/tomcat-java:v1将构建的镜像上传至私人仓库,然后在yaml文件中引用,这是生产环境最主流的方式,将我们的私人仓库变成唯一的可信源
4.2 编写 Kubernetes 部署文件tomcat-java.yaml
cat tomcat-java.yamlapiVersion:v1kind:Servicemetadata:name:tomcat-java-svcspec:type:NodePortports:-port:8282targetPort:8080nodePort:30700selector:app:tomcat-java---apiVersion:apps/v1kind:Deploymentmetadata:name:tomcat-javaspec:selector:matchLabels:app:tomcat-javareplicas:3template:metadata:labels:app:tomcat-javaspec:containers:-name:tomcat-javaimage:www.zutuanxue.com/library/tomcat-java:v1imagePullPolicy:IfNotPresentports:-containerPort:80804.3 部署并测试
kubectl apply-ftomcat-java.yaml访问http://<NodeIP>:30700/jspdemo_war(项目名),输入用户名lisi和对应密码(示例中说明“用户名与密码为:lisi”,具体密码需根据实际应用设定,可能为默认或配置,此处按文档描述)。
第三部分:内容补充
3.1 命名空间(Namespace)
生产环境中通常按环境(开发、测试、生产)或团队划分命名空间,以隔离资源。在所有 YAML 文件中可指定metadata.namespace,或通过kubectl apply -f <file> -n <namespace>。
3.2 滚动更新与回滚
Deployment 支持滚动更新(默认策略),更新镜像时只需修改 YAML 中的image标签,重新apply即可。若更新失败,可使用kubectl rollout undo deployment/<name>回滚到上一版本。
3.3 配置管理进阶
除了环境变量,还可将 ConfigMap 挂载为配置文件(如application.properties)。例如:
volumes:-name:configconfigMap:name:app-configvolumeMounts:-name:configmountPath:/config3.4 健康检查配置示例
在 Pod 的containers中添加:
livenessProbe:httpGet:path:/healthport:8080initialDelaySeconds:30periodSeconds:10readinessProbe:httpGet:path:/readyport:8080initialDelaySeconds:5periodSeconds:53.5 资源限制示例
resources:requests:memory:"256Mi"cpu:"250m"limits:memory:"512Mi"cpu:"500m"总结
将传统业务系统迁移到 Kubernetes 是一个系统化工程,需要依次完成容器化→Pod 设计→控制器选择→服务暴露→存储与配置管理→可观测性等环节。对于不同类型应用(无状态 Web、有状态数据库、PHP、Java),Kubernetes 均提供了灵活的编排能力。实际落地时,应结合业务特点,选择合适的控制器和存储方案,并逐步推进,尤其对于数据库等关键组件,可采取“先外后内”的保守策略。