从单体到云原生:一个外包程序员的血泪进化史
上周五晚上十点半,我还在用 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 面板一开,流量、错误率、延迟一目了然,还能设告警自动通知钉钉群。
血泪教训:云原生不是银弹
当然,坑也没少踩:
- 配置管理混乱:一开始把数据库密码写在 YAML 里,差点泄露。后来全迁到 Vault,K8s 用 initContainer 注入。
- 服务网格太重:试了 Istio,结果 overhead 太高,小业务扛不住。最后用 Nginx Ingress + 自定义 middleware 搞定认证和限流。
- 成本失控:K8s 集群开太多节点,账单吓人。后来用 HPA + cluster autoscaler,闲时缩到 1 节点,省了 60% 云费用。
最惨的是上周,因为没设资源 limits,一个服务 OOM 把整个节点拖垮。当时真想砸电脑,但转念一想——这不就是成长的代价吗?
结语:架构演进,本质是人的演进建
从单体到云原生,表面看是技术升级,其实是团队协作方式、运维理念、甚至公司文化的变革。工具只是载体,真正的“云原生”在于:快速试错、快速反馈、快速恢复。
我现在接外包,都会先问客户:“你们有 SRE 吗?有 CI/CD 流水线吗?能接受灰度发布吗?” 如果答案是否,我会劝他们先做容器化,别一上来就搞 Service Mesh。
至于我?副业收入已经快赶上主业了。下个月面试新公司,简历上终于能写“主导云原生架构迁移”了。虽然过程狗血,但结果还不错。
对了,如果你也在搞类似项目,或者想用 Rust 写后端服务,欢迎交流!我的 Vim 配置都开源了,.vimrc 里全是注释,保证你看得懂(不像某些人的 dotfiles,写得跟加密文件似的)。
毕竟,程序员嘛,一边骂着“这破系统谁设计的”,一边默默把它改得更好——这就是我们的浪漫。

评论 0