最近把公司的旧系统迁移到了云原生架构,同时做了全面的安全加固。

旧系统是传统的单体应用,部署在物理机上,用的是Tomcat + MySQL。系统跑了五六年,功能越来越多,代码越来越乱,安全漏洞也不少。每次升级都要停机维护,扩容也很麻烦。

老板决定上云原生,把系统容器化,部署到Kubernetes上。同时,借这次迁移的机会,做一次全面的安全加固。

我负责整个迁移的技术方案和实施。花了两个月时间,终于把系统安全地迁移到了云原生架构上。本文分享这次迁移的完整过程,包括迁移前的评估、容器化改造、K8s集群安全、镜像安全、网络安全、数据安全、运行时安全、迁移策略、踩坑记录等。

一、迁移前的评估

迁移的第一步,是评估旧系统的现状。

1. 系统梳理

先把旧系统梳理清楚:

  • 有哪些应用?多少个服务?
  • 用了什么技术栈?
  • 依赖哪些中间件?(数据库、缓存、消息队列等)
  • 有哪些定时任务?
  • 有哪些外部接口?
  • 数据量有多大?

我们的旧系统是一个单体Java应用,依赖MySQL、Redis、RabbitMQ,有十几个定时任务,有几个外部系统的接口。数据量不大,数据库大概50G。

2. 安全评估

然后做安全评估,找出旧系统的安全问题:

  • 代码漏洞:用SonarQube扫描,发现了一些SQL注入、XSS的风险
  • 依赖漏洞:用OWASP Dependency Check扫描,发现几个有漏洞的第三方库
  • 配置问题:数据库密码明文写在配置文件里,Tomcat用root运行
  • 网络问题:没有防火墙,端口直接暴露在公网
  • 权限问题:数据库用户权限过大,有DROP权限

这些问题,在迁移的时候都要解决。

3. 迁移方案设计

根据评估结果,设计迁移方案:

  • 应用容器化:把Java应用做成Docker镜像
  • 编排:用Kubernetes部署和管理
  • 数据库:保留MySQL,但部署在K8s上或用云数据库
  • 缓存:用Redis Cluster
  • 消息队列:用RabbitMQ或Kafka
  • 网关:用Ingress或API网关
  • 监控:Prometheus + Grafana
  • 日志:EFK(Elasticsearch + Fluentd + Kibana)

二、容器化改造

接下来是容器化改造。

1. Dockerfile编写

给Java应用写Dockerfile:

# 多阶段构建
FROM maven:3.8-openjdk-11 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests

# 运行阶段
FROM openjdk:11-jre-slim
WORKDIR /app
COPY --from=builder /app/target/app.jar .
# 非root用户运行
RUN useradd -u 10001 appuser
USER appuser
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

注意几点:

  • 用多阶段构建,减小镜像体积
  • 用slim版本的基础镜像,减小体积
  • 用非root用户运行,提高安全性
  • 不要把配置文件和密钥打包进镜像

2. 配置外置

旧系统的配置写在properties文件里,打包在jar包里。迁移后,配置要外置。

  • 用K8s的ConfigMap存非敏感配置
  • 用Secret存敏感配置(密码、密钥等)
  • 应用启动时从环境变量或挂载文件读取配置

3. 健康检查

给应用加健康检查接口:

  • 存活探针(livenessProbe):应用是否还活着
  • 就绪探针(readinessProbe):应用是否准备好接收流量
livenessProbe:
  httpGet:
    path: /actuator/health
    port: 8080
  initialDelaySeconds: 60
  periodSeconds: 10
readinessProbe:
  httpGet:
    path: /actuator/health
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 5

三、K8s集群安全

K8s集群本身的安全很重要。

1. 集群安装安全

  • 用kubeadm安装,启用RBAC
  • 关闭匿名访问
  • 启用审计日志
  • 控制平面节点不跑业务Pod(用taint)
  • etcd加密,定期备份

2. RBAC权限控制

用RBAC做细粒度的权限控制:

  • 不同团队用不同的命名空间
  • 每个命名空间有独立的ServiceAccount
  • 只给必要的权限,遵循最小权限原则
  • 不要给普通用户cluster-admin权限

3. Pod安全策略

用Pod Security Admission(PSA)限制Pod的安全配置:

  • 不允许用root用户运行
  • 不允许特权容器
  • 不允许挂载宿主机目录
  • 限制Linux capabilities
apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    pod-security.kubernetes.io/enforce: restricted

4. 网络策略

用NetworkPolicy限制Pod之间的网络访问:

  • 默认拒绝所有入站流量
  • 只允许必要的访问(比如前端Pod只能访问后端Pod的8080端口)
  • 隔离不同环境(开发、测试、生产)

四、镜像安全

镜像安全是云原生安全的重要环节。

1. 基础镜像安全

  • 用官方镜像或可信的基础镜像
  • 用最小化的基础镜像(alpine、slim、distroless)
  • 定期更新基础镜像,修复漏洞
  • 不要用latest标签,用固定版本

2. 镜像扫描

用镜像扫描工具检查漏洞:

  • Trivy:简单易用,适合CI/CD集成
  • Clair:和Harbor集成
  • Anchore:功能更全面

在CI/CD流水线中加入镜像扫描步骤,有高危漏洞的镜像不允许部署。

3. 镜像签名

用镜像签名验证镜像的来源和完整性:

  • Docker Content Trust
  • Cosign(Sigstore项目)

只允许运行经过签名的镜像,防止被篡改。

4. 私有镜像仓库

用私有镜像仓库(Harbor)管理镜像:

  • 不直接从公网拉取镜像
  • 镜像仓库做访问控制
  • 镜像仓库开启漏洞扫描
  • 定期清理无用镜像

五、网络安全

网络安全是云原生安全的重点。

1. Ingress安全

  • 用HTTPS,配置TLS证书
  • 开启HSTS
  • 配置WAF(Web应用防火墙)
  • 限制请求大小和速率
  • 隐藏服务器版本信息

2. 服务网格

如果服务比较多,可以用服务网格(Istio、Linkerd):

  • 服务间通信加密(mTLS)
  • 细粒度的流量控制
  • 统一的遥测数据
  • 故障注入和熔断

3. 出口流量控制

控制Pod的出口流量:

  • 不允许Pod直接访问公网
  • 通过代理或网关访问外部服务
  • 用Egress Gateway管理出口流量

4. DDoS防护

  • 用云服务商的DDoS防护
  • 配置限流(Rate Limiting)
  • 用CDN缓存静态资源

六、数据安全

数据安全是重中之重。

1. 数据库安全

  • 数据库不暴露在公网,只在集群内部访问
  • 数据库用强密码,定期更换
  • 数据库用户最小权限,只给必要的权限
  • 开启数据库审计日志
  • 定期备份,备份数据加密
  • 数据库传输加密(SSL/TLS)

2. 敏感数据保护

  • 密码、密钥等敏感信息存在Secret中,不要写在代码或配置里
  • 敏感数据加密存储(比如用户密码用bcrypt加密)
  • 个人信息脱敏展示(手机号、身份证号等)
  • 数据传输加密(HTTPS、mTLS)

3. 数据备份和恢复

  • 定期备份数据库和重要数据
  • 备份数据存放在异地
  • 定期做恢复演练,确保备份可用
  • 制定数据恢复的RTO和RPO

七、运行时安全

部署之后,运行时的安全也不能忽视。

1. 容器运行时安全

  • 用Falco监控容器运行时的异常行为
  • 检测容器内的异常进程、文件访问、网络连接
  • 发现异常自动告警或阻断

2. 日志审计

  • 收集所有容器的日志
  • 收集K8s的审计日志
  • 日志集中存储,便于分析
  • 定期审计日志,发现异常行为

3. 漏洞管理

  • 定期扫描镜像和运行中的容器
  • 及时修复高危漏洞
  • 建立漏洞管理流程:发现-评估-修复-验证

4. 应急响应

  • 制定安全事件应急响应预案
  • 定期做应急演练
  • 安全事件发生时,能快速定位、隔离、修复

八、迁移策略

说说具体的迁移策略。

1. 蓝绿部署

用蓝绿部署的方式迁移:

  • 旧系统(蓝)继续运行
  • 新系统(绿)部署在K8s上
  • 先把流量切一部分到新系统
  • 验证没问题后,全部切到新系统
  • 旧系统保留一段时间,确认没问题后下线

2. 数据迁移

数据迁移是最麻烦的部分:

  • 全量迁移:先把旧数据全量导入新数据库
  • 增量同步:用Canal或Debezium同步增量数据
  • 切换:停机几分钟,确认数据同步完成后,切换到新系统

我们的数据量不大,全量迁移用了1小时,增量同步了3天,切换的时候停机了10分钟。

3. 回滚方案

迁移一定要有回滚方案:

  • 如果新系统出问题,能快速切回旧系统
  • 数据双向同步,确保回滚时数据不丢
  • 回滚操作提前演练

4. 灰度发布

迁移完成后,新功能用灰度发布:

  • 先给内部用户用
  • 再给一小部分外部用户用
  • 最后全量发布

九、踩坑记录

说说迁移过程中踩的坑。

坑一:时区问题

容器里的时区是UTC,和国内差8小时。

解决:Dockerfile里设置时区,或者挂载宿主机的localtime。

ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

坑二:文件权限问题

容器用非root用户运行,挂载的目录没有写权限。

解决:在Dockerfile里创建用户,并给目录授权。或者用initContainer修改目录权限。

坑三:JVM内存问题

容器里的JVM,默认用的是宿主机的内存,不是容器的内存限制。

解决:设置JVM参数,或者用Java 10+的容器感知功能(UseContainerSupport)。

-XX:MaxRAMPercentage=75.0

坑四:数据库连接池问题

Pod重启时,数据库连接没有正常释放,导致连接数耗尽。

解决:配置连接池的最大生存时间,定期回收连接。

坑五:日志文件过大

容器日志写到stdout,但旧应用还在写文件日志,导致容器磁盘满。

解决:把应用日志也改成输出到stdout,用EFK收集。或者限制日志文件大小,定期轮转。

十、迁移后的效果

说说迁移后的效果。

1. 安全性提升

  • 代码漏洞修复了
  • 依赖漏洞更新了
  • 网络隔离了,端口不暴露在公网
  • 权限最小化了
  • 有了完整的监控和审计

2. 可用性提升

  • 滚动更新,不再停机维护
  • 自动扩缩容,高峰期自动加节点
  • 健康检查,异常Pod自动重启
  • 多副本,单节点故障不影响服务

3. 效率提升

  • 部署更快了,从几小时降到几分钟
  • 扩容更方便了,从几天降到几分钟
  • 环境一致性好了,不再有"我本地能跑"的问题

4. 成本变化

  • 服务器利用率提高了,从30%升到60%
  • 但云原生的运维复杂度增加了,需要学习K8s
  • 总体来说,长期成本是降低的

十一、写在最后

云原生迁移,不是简单地把应用放进容器里,而是一次全面的架构升级和安全加固。

从旧系统到新系统,涉及应用改造、集群搭建、安全加固、数据迁移、监控运维等方方面面。每一个环节都不能马虎,尤其是安全,云原生环境下,安全问题的影响面更大。

这次迁移,我们花了两个月时间,踩了不少坑,但最终顺利完成了。迁移后的系统,更安全、更稳定、更高效。

2022年了,云原生已经成为主流。越来越多的企业在做云原生迁移。但迁移不是目的,提升业务价值才是目的。在迁移的过程中,不要忘了安全,安全是1,其他都是0。

最后,用一句话总结:"云原生迁移,安全先行。先评估,再设计,小步走,勤验证,才能安全平稳地完成迁移。"

愿大家的云原生迁移之路,都能顺利平安。