服务器实践笔记

更糟的是上次 UPS 出故障,整个机房断电半小时,回滚数据花了整整一天。这周下定决心,把 IDC 机房的两台 Dell R740 迁移到阿里云。

为什么要迁移

先说下当时的现状:

两台 Dell PowerEdge R740,每台双路 Intel Xeon Gold 6132,256GB 内存,4TB SSD。部署着:

  • 一套自建的 GitLab 私有仓库
  • 两个生产环境的 Spring Boot 应用
  • 一套 ELK 日志系统
  • 一个月活 3 万的小型论坛

问题出在几个地方:

网络带宽只有 100Mbps,论坛用户在南方地区打开首页平均要 3 秒。硬盘利用率到了 87%,每次扩容都要申请采购流程,至少两周。更糟的是上次 UPS 出故障,整个机房断电半小时,回滚数据花了整整一天。

阿里云给我的报价单:一台 8 核 32GB 的 ECS,带宽 5Mbps,按量付费大概 4000 元/月。对比现在的 IDC 托管费加上电费、人员成本,数字上是省钱的。

但成本不是唯一考虑。我要的是深夜不再被电话叫醒的安宁。

选型与架构设计

迁移前花了两周做架构调研。原计划是照搬物理机的配置,后来发现这思路不对。

云平台和物理机的玩法不一样。物理机买回来就是一堆硬件,用不用都在折旧。云服务器按小时计费,不用了就释放,用的时候再拉起来。

最终确定的迁移方案:

graph TD A[物理机集群<br/>2台 R740] --> B[云架构<br/>多区域部署] B --> C[应用层<br/>2台 ECS 弹性伸缩] B --> D[数据库层<br/>RDS 读写分离] B --> E[缓存层<br/>Redis 集群] B --> F[对象存储<br/>OSS 静态资源] B --> G[负载均衡<br/>SLB + HTTPS] C --> D C --> E C --> F

核心改动:

  1. 应用层用 ECS 弹性伸缩代替固定物理机
  2. 数据库迁移到 RDS,主从自动切换
  3. 静态资源放到 OSS,配合 CDN 加速
  4. 用 SLB 做 HTTPS 负载均衡

这个架构看着眼熟,是典型云原生改造路线。但真动手的时候,坑在细节里。

数据迁移:Rsync 还是 DTS

第一刀砍在数据迁移上。

原计划用阿里云的 DTS(数据传输服务),但测试后发现 DTS 对自建 MySQL 5.7 的支持有点小问题。主从同步延迟经常超过 10 秒,而且不支持自定义账号权限。

最后改用 rsync + MySQL 主从同步的土办法。

步骤如下:

  1. 在 ECS 上建一个临时实例
  2. 物理机到临时实例用 rsync 同步文件
  3. 用 mysqldump 导出数据,导入 RDS
  4. 建立主从同步关系
  5. 业务切换前全量同步一次

rsync 的同步命令:

# 压缩同步,排除日志目录
rsync -avz --progress \
  --exclude='logs/*' \
  --exclude='tmp/*' \
  --exclude='node_modules/*' \
  /var/www/app/ \
  [email protected]:/var/www/app/

# 只同步最近修改的文件
rsync -avz --progress --max-size=100M \
  --times --checksum \
  /var/www/app/uploads/ \
  [email protected]:/var/www/app/uploads/

坑在这里:第一次同步用了默认配置,没有带 --checksum 参数,结果部分文件时间戳更新了但内容没变,导致传输了 80GB 冗余数据。

第二次加上了 --checksum--max-size=100M,把 500GB 的数据分片同步,花了 14 小时才完成。

MySQL 导出用的这个配置:

mysqldump -u root -p \
  --single-transaction \
  --quick \
  --lock-tables=false \
  --databases app_db > app_dump.sql

# 恢复时指定字符集
mysql -u root -p --default-character-set=utf8mb4 \
  app_db < app_dump.sql

特别要注意 --default-character-set=utf8mb4,我第一次忘加这个,导入后中文全变成问号,重新导一次又花了 6 小时。

应用改造:容器化不是必须的

原计划把所有应用都打包成 Docker 镜像,后来发现这个选择有点过度。

GitLab 和 ELK 这种成熟方案直接用官方镜像就行了,但自研的 Spring Boot 应用没必要强行容器化。云平台本身提供了隔离,物理机时代担心的"互相影响"在虚拟化层已经解决了。

最终做了个折中:基础环境用 Ansible 自动化部署,应用二进制文件直接上传。这样部署速度快,排障时也不用在 Docker 和宿主机之间反复跳。

Ansible 的部署脚本:

---
- name: 部署应用
  hosts: app_servers
  become: yes
  tasks:
    - name: 创建应用目录
      file:
        path: /var/www/app
        state: directory
        owner: appuser
        group: appuser
        mode: '0755'

    - name: 上传应用包
      copy:
        src: app.jar
        dest: /var/www/app/app.jar
        owner: appuser
        group: appuser
        mode: '0644'

    - name: 配置 JVM 参数
      copy:
        content: |
          JAVA_OPTS="-Xms2g -Xmx4g -XX:+UseG1GC"
        dest: /var/www/app/app.env

    - name: 启动应用
      systemd:
        name: app-service
        state: restarted
        daemon_reload: yes

这里踩了个坑:systemd 的服务文件里必须指定 User=appuser,否则以 root 启动的 Java 应用有文件权限问题。第一次上线后论坛用户上传的图片无法读取,排查了半天才发现这个细节。

负载均衡:HTTPS 证书让我头疼三天

SLB 配置不难,难的是 HTTPS 证书。

原来在 Nginx 上用的 Let’s Encrypt 证书,签发脚本定期自动更新。云平台不支持这种方式,得用证书管理服务,但要上传的证书格式是 .pfx

转换过程:

# 合并证书和私钥
cat fullchain.pem privkey.pem > combined.pem

# 转换成 PKCS12 格式
openssl pkcs12 -export \
  -in combined.pem \
  -out cert.pfx \
  -name "app-cert"

# 转换时设置的密码要记住,上传时要用

问题出在证书链。Let’s Encrypt 的证书链中间有两级 CA 证书,转换时如果只合并不全,浏览器会报"证书不受信任"的错误。

用这个命令检查证书链:

openssl s_client -connect api.example.com:443 -servername api.example.com

# 看 Verify return code 应该是 0

折腾了三天,最后才发现是证书链里少了一级中间证书。在云平台控制台手动合并完整的证书链后,浏览器那把红色的小锁子终于变绿了。

切割流量的那个晚上

迁移完成后,测试环境跑了两周才敢动生产环境。那个晚上做了个灰度发布:

  1. DNS 解析 TTL 调到 60 秒
  2. SLB 新加一个后端服务器指向新的 ECS
  3. 把 5% 的流量切过去
  4. 监控错误日志和响应时间
  5. 每小时增加 20% 流量

Nginx 的分流配置:

upstream app_cluster {
    server 192.168.1.10 weight=1;   # 物理机
    server 10.0.1.20 weight=4;     # 云 ECS
}

location / {
    proxy_pass http://app_cluster;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
}

第一小时日志里多了几个 502 错误,是 ECS 的应用还没完全启动。预热脚本加了一分钟延迟重试,错误就没了。

凌晨 2 点,流量全部切到云平台。看着监控曲线平稳,我在办公室喝了杯咖啡,第一次享受云服务器带来的宁静。

迁移后的实际效果

两周过去了,对比一下迁移前后的数据:

指标物理机云平台变化
月度运维成本8500 元5800 元↓ 32%
平均响应时间1.2 秒0.8 秒↓ 33%
部署时间30 分钟8 分钟↓ 73%
故障恢复时间4 小时15 分钟↓ 94%
磁盘扩容时间14 天5 分钟↓ 99.9%

但数据背后有些话要说:

运维成本确实降了,但不是 magically 自消失。省下来的是 IDC 托管费和硬件折旧,云服务的费用结构变了。弹性伸缩的实例在高峰期会自动拉起来,月底账单比预计的高了 20%。

部署时间缩短是因为 Ansible 脚本,不是云平台本身。物理机上也用同样的脚本,差距主要在不用亲自去机房插网线。

故障恢复时间大幅减少是真的。上次数据库主从切换,RDS 30 秒就自动完成了,这在物理机时代要手动操作至少半小时。

一些反思

这次迁移后,我对云和物理机的看法变了一些:

云不是万能药。像 GitLab 这种重 IO、重存储的服务,放在物理机上可能更经济。云平台的存储费用比硬件贵一倍,但如果算上维护成本,又是一笔账。

自动化的优先级高于云化。如果代码发布、监控、告警都没自动化,上云只是把物理机的运维问题搬到了云端。我这次上云前先把 Ansible 脚本写好了,迁移过程才比较顺。

业务规模决定技术选择。当前这套架构对 3 万月活足够了,如果流量翻十倍,可能就要重新考虑。云平台的好处就是可以快速调整,这点是物理机比不了的。

还要解决的问题

迁移完成不代表结束,有几个问题还在解决中:

  1. 监控告警系统还在用原来的 Zabbix,考虑换成云监控
  2. 备份策略需要重新设计,现在的混合环境比较复杂
  3. 成本优化还没做,EC2 实例有点浪费,应该用竞价实例
  4. 安全组规则太多,正在梳理有没有冗余的规则

这些问题留着慢慢解决,技术迁移就是个持续优化的过程。

最后说两句

从裸机到云服务器,这次迁移让我明白一件事:技术选型没有标准答案,都是权衡。

云平台让我少了几次深夜去机房的痛苦,但也带来新的复杂度。运维成本降低了 30%,但真正省下来的是那段时间里可以做更有价值的事。

如果下次还有机会选择,我还是会选云服务器。不是因为它是最好的,而是因为它适合现在的业务和团队规模。

服务器只是工具,解决问题才是目的。

可用性说明:本文发布于 2021 年 5 月,距今已超过五年。文中涉及的软件版本、接口、下载地址、命令参数和操作界面可能已经发生变化,部分方案在当前环境下可能失效。请结合官方最新文档核对后再操作,生产环境使用前务必先行验证。

版权声明: 本文首发于 指尖魔法屋-服务器实践笔记https://blog.thinkmoon.cn/post/120-server-baremetal-cloud-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!