从单体到云原生:一个外包程序员的血泪进化史

罗娟
2026-01-13 05:12
阅读 601

上周五晚上十点半,我还在用 Vim 调一行该死的 YAML 配置,Pod 死活起不来,报错信息就一句 CrashLoopBackOff。那一刻我真的想把键盘扔出窗外——但转念一想,这不就是我最近接的一个外包项目吗?客户要“现代化改造”,说白了就是把他们十年前那套 PHP 单体应用拆成云原生架构。而我这个在大厂摸鱼三年、副业靠 Rust 挣外快的斜杠程序员,居然真接了这活儿。

说来惭愧,我在公司主要写 CRUD 接口,三年没碰过部署流水线。但架不住客户给得多,而且我也在考虑跳槽——现在哪家公司在招后端不问你“有没有微服务和 K8s 经验”?于是硬着头皮上了。今天就聊聊这段从单体到云原生的“渡劫”经历,顺便吐吐槽、晒晒工具链,给同样想搞副业或转型的兄弟们避个坑。


起点:那个跑在物理机上的“祖传系统”

客户的旧系统,典型的 LAMP 架构:一台物理服务器,Apache + MySQL + PHP 全堆一块儿。前端页面、支付逻辑、用户管理、报表导出……全在一个代码库里。部署?直接 scp 上传,重启 Apache。监控?看日志里有没有 ERROR。扩容?加内存。

去年双11前,系统崩了三次。原因?库存扣减时并发太高,MySQL 锁表,整个网站卡死。产品经理甩锅给“技术债太多”,运维小哥半夜被叫起来手动 kill 进程。客户老板急了,拍板:“上云原生!学人家 Netflix!”

我心想:Netflix 有几百个 SRE,你连 Dockerfile 都没写过……


第一步:容器化不是万能药,但没它真不行

我先干的第一件事,不是拆服务,而是容器化。别笑,很多团队以为“上 Kubernetes”就是云原生,其实第一步是让应用可移植。

我用 Docker 把 PHP 应用打包,数据库单独起一个容器。虽然还是单体,但至少开发、测试、生产环境一致了。以前本地跑得好好的,上线就 500,现在不会了——感谢上帝,也感谢 docker-compose.yml

# docker-compose.yml(简化版)
version: '3'
services:
  web:
    build: .
    ports:
      - "80:80"
    depends_on:
      - db
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: secret

这里踩了个坑:PHP 应用里硬编码了 localhost:3306 连数据库。容器网络里得改成 db:3306。改完之后,本地测试通过,但客户测试环境又挂了——因为他们用的是老版本 Docker,网络驱动不兼容。最后加了个 .env 文件动态配置 DB_HOST,才搞定。

教训:容器化不是 Dockerfile 写完就完事,得考虑整个工具链的兼容性,尤其是客户那边的“古董级”运维环境。


第二步:拆!但别乱拆

很多人一听到微服务就兴奋地开始拆:用户服务、订单服务、商品服务……结果拆完发现接口调用链路长到爆炸,一个下单要跨 8 个服务,延迟从 200ms 涨到 2s。

我学聪明了:先用 领域驱动设计(DDD) 划边界。翻了三天业务代码+跟客户产品对需求,发现核心其实是两个上下文:交易用户管理。其他都是附属功能。

于是只拆出两个服务:

  • auth-service:负责登录、权限、用户资料
  • order-service:负责下单、支付、库存

数据库也跟着拆:用户表归 auth,订单表归 order。中间用 事件驱动 解耦——比如用户注册成功后,发个 Kafka 消息,order-service 监听并预创建购物车。

// 最近在学 Rust,顺手用 actix 写了个轻量级 auth 服务 demo
#[post("/register")]
async fn register(user: web::Json<NewUser>) -> impl Responder {
    // 1. 写 DB
    let user_id = db.insert_user(&user).await.unwrap();
    // 2. 发事件
    kafka.send("user.registered", user_id).await;
    HttpResponse::Ok().json(user_id)
}

说实话,用 Rust 写这种高并发服务真香,内存安全+零成本抽象,比 Java Spring Boot 轻快多了。虽然生态还不成熟,但作为 side project 完全够用。


第三步:工具链升级——DevOps 不是口号

光有微服务不够,你得能快速交付、快速回滚、快速观测。这时候工具就关键了。

我给客户搭了一套极简 CI/CD:

  • GitLab CI:代码推到 main 自动构建镜像、推到 Harbor
  • Argo CD:监听 Harbor 镜像变更,自动滚动更新 K8s Deployment
  • Prometheus + Grafana:监控 CPU、内存、HTTP 状态码
  • Loki + Promtail:日志聚合,再也不用 kubectl logs -f 翻半天

运维同事一开始抵触:“这玩意儿比 Ansible 复杂多了!” 但自从有一次,我们 3 分钟内回滚了有问题的版本(以前要手动 scp + 重启,至少 20 分钟),他就闭嘴了。

阶段 部署时间 回滚时间 故障定位速度
单体物理机 15分钟 20分钟 >1小时
云原生 <2分钟 <3分钟 <5分钟

数据不会骗人。运营同学也爽了——以前每次大促前都要提心吊胆,现在 Grafana 面板一开,流量、错误率、延迟一目了然,还能设告警自动通知钉钉群。


血泪教训:云原生不是银弹

当然,坑也没少踩:

  1. 配置管理混乱:一开始把数据库密码写在 YAML 里,差点泄露。后来全迁到 Vault,K8s 用 initContainer 注入。
  2. 服务网格太重:试了 Istio,结果 overhead 太高,小业务扛不住。最后用 Nginx Ingress + 自定义 middleware 搞定认证和限流。
  3. 成本失控:K8s 集群开太多节点,账单吓人。后来用 HPA + cluster autoscaler,闲时缩到 1 节点,省了 60% 云费用。

最惨的是上周,因为没设资源 limits,一个服务 OOM 把整个节点拖垮。当时真想砸电脑,但转念一想——这不就是成长的代价吗?


结语:架构演进,本质是人的演进建

从单体到云原生,表面看是技术升级,其实是团队协作方式、运维理念、甚至公司文化的变革。工具只是载体,真正的“云原生”在于:快速试错、快速反馈、快速恢复

我现在接外包,都会先问客户:“你们有 SRE 吗?有 CI/CD 流水线吗?能接受灰度发布吗?” 如果答案是否,我会劝他们先做容器化,别一上来就搞 Service Mesh。

至于我?副业收入已经快赶上主业了。下个月面试新公司,简历上终于能写“主导云原生架构迁移”了。虽然过程狗血,但结果还不错。

对了,如果你也在搞类似项目,或者想用 Rust 写后端服务,欢迎交流!我的 Vim 配置都开源了,.vimrc 里全是注释,保证你看得懂(不像某些人的 dotfiles,写得跟加密文件似的)。

毕竟,程序员嘛,一边骂着“这破系统谁设计的”,一边默默把它改得更好——这就是我们的浪漫。

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝