背景
今年年初,我们将预发布环境迁移至VPC,测试了平滑迁移服务到VPC的可行性。当时的结论是:要达到用户无感知,迁移过程非常繁琐,除非阿里云在基础设施一层提供支持,否则很难应用到生产环境。详见《如何将服务从经典网络迁移到VPC》。
但在今年年中,阿里云推出了一系列有利于VPC迁移的功能,我们认为将整个生产环境迁移至VPC的条件已经成熟。
迁移
迁移难点
在《如何将服务从经典网络迁移到VPC》结尾提到,将服务迁移平滑迁移至VPC最大的障碍在于:
- 迁移数据源到VPC
使用DTS可以很方便地同步数据源至VPC实例,但需要创建VPN使VPC服务能够访问到经典网络的数据源,且在最终切换时仍需要短暂停服。
- 切换流量到VPC
由于SLB后端不能同时接入经典网络和VPC ECS,以至于只能移除经典网络节点再挂入VPC节点。这个过程中会造成分钟级的停服,且无法实现灰度引流。
今年6月,阿里云提供了三个新功能
- 数据源混访
一键迁移经典网络数据源到VPC,且保留经典网络地址不变。数据源迁移到VPC后,依然可以被经典网络服务访问,业务配置不需要修改。相当于阿里云帮我们创建VPN且配置了地址映射,对已有服务完全透明,非常方便。
- SLB混挂
VPC和经典网络的ECS可以添加到同一个SLB。这是最重要的一个改进,避免了分钟级的停服,又方便实现灰度测试。
- ClassicLink
使经典网络的ECS可以和VPC中的云资源通过私网互通,避免自行搭建VPN网络来连通两个网络。
这三项功能,尤其是数据源混访和SLB混挂,能很好地解决以往迁移中遇到的两个难点。由于业务模型中不存在ECS之间的访问,实际迁移中我们几乎没有使用ClassicLink。
要说明的是,如果业务模型比较简单且没有平滑迁移的需求,则无需应用本文后面描述的高级策略,可直接使用阿里云的提供的“ECS迁移”功能,在控制台将ECS停机后一键迁移至VPC。
迁移方案
我们的业务模型如图1,nginx和service都是以docker容器的形式运行在ECS上。
在迁移顺序上有两种选择:
- 自底向上迁移
先数据源切换至VPC,再迁移service,测试正常后最后迁移nginx到VPC。这种方式先迁移了底层服务,由于没有上层nginx引流,无法判断服务是否正常,所以需要做好测试。而且首先迁移数据源的风险也很高,数据源切换到VPC,所有服务通过经典网络地址访问数据源,需要先确认阿里云提供的这种跨网络访问的性能是否满足要求。
- 自上而下迁移
先迁移ngix到VPC,该nginx访问经典网络service。确认正常后再迁移service到VPC,最后迁移数据源。这种方式的问题在于,nginx迁移到VPC后,是无法访问经典网络SLB的,必须创建一个VPC SLB,将经典网络service挂载到这个SLB上,然后再修改VPC nginx指向VPC SLB。
我们的业务调用比较复杂,上图是简化后模型,实际上service-1可能还调用了service-2,而service-2可能还调用了service-3甚至service-4,所以我们的迁移主要以第1种方式为主。
数据源跨区访问性能
对于上面第1种方式中提到风险,我们新开了RDS和Redis实例以测试数据源切换到VPC后的访问性能。
RDS
迁移RDS到VPC后,从VPC节点测试RDS性能:
sysbench --test=oltp --oltp-table-size=1000000 --mysql-host=vpc-id.mysql.rds.aliyuncs.com --mysql-db=dbtest --mysql-user=service --mysql-password=password prepare
sysbench --test=oltp --oltp-table-size=1000000 --oltp-test-mode=complex --oltp-read-only=off --num-threads=6 --max-time=60 --max-requests=0 --mysql-host=vpc-id.mysql.rds.aliyuncs.com --mysql-db=dbtest --mysql-user=service --mysql-password=password run
OLTP test statistics:
queries performed:
read: 112476
write: 40170
other: 16068
total: 168714
transactions: 8034 (133.80 per sec.)
deadlocks: 0 (0.00 per sec.)
read/write requests: 152646 (2542.13 per sec.)
other operations: 16068 (267.59 per sec.)
Test execution summary:
total time: 60.0464s
total number of events: 8034
total time taken by event execution: 360.1141
per-request statistics:
min: 35.28ms
avg: 44.82ms
max: 100.08ms
approx. 95 percentile: 54.56ms
Threads fairness:
events (avg/stddev): 1339.0000/123.78
execution time (avg/stddev): 60.0190/0.01
迁移RDS到VPC后,从经典网络节点测试RDS性能:
sysbench --test=oltp --oltp-table-size=1000000 --mysql-host=classic-id.mysql.rds.aliyuncs.com --mysql-db=dbtest --mysql-user=service --mysql-password=password prepare
sysbench --test=oltp --oltp-table-size=1000000 --oltp-test-mode=complex --oltp-read-only=off --num-threads=6 --max-time=60 --max-requests=0 --mysql-host=classic-id.mysql.rds.aliyuncs.com --mysql-db=dbtest --mysql-user=service --mysql-password=password run
OLTP test statistics:
queries performed:
read: 116620
write: 41650
other: 16660
total: 174930
transactions: 8330 (138.75 per sec.)
deadlocks: 0 (0.00 per sec.)
read/write requests: 158270 (2636.16 per sec.)
other operations: 16660 (277.49 per sec.)
Test execution summary:
total time: 60.0381s
total number of events: 8330
total time taken by event execution: 360.0424
per-request statistics:
min: 34.20ms
avg: 43.22ms
max: 193.89ms
approx. 95 percentile: 52.10ms
Threads fairness:
events (avg/stddev): 1388.3333/97.04
execution time (avg/stddev): 60.0071/0.01
Redis
迁移Redis到VPC后,从VPC节点测试Redis性能:
redis-benchmark -n 10000 -q -h vpc-id.redis.rds.aliyuncs.com -a password
SET: 43103.45 requests per second
GET: 42735.04 requests per second
INCR: 40000.00 requests per second
LPUSH: 39215.69 requests per second
RPUSH: 27654.01 requests per second
迁移Redis到VPC后,从经典网络节点测试Redis性能:
redis-benchmark -n 10000 -q -h classic-id.redis.rds.aliyuncs.com -a password
SET: 38610.04 requests per second
GET: 40485.83 requests per second
INCR: 41841.00 requests per second
LPUSH: 37593.98 requests per second
RPUSH: 25839.79 requests per second
测试数据显示,从经典网络访问VPC的数据源,其网络延迟几乎可以忽略不计。后来我们实际迁移的体验也证实了这一点。
自动化运维工具
一个服务迁移要到VPC,其对应的容器需要build两个镜像,经典网络镜像和VPC镜像,分别推送到经典网络和VPC各自的docker registry。两个镜像中配置不同,配置包括数据源地址和其他服务API接口地址,VPC镜像中使用VPC数据源地址和VPC私网SLB地址。我们很早就将代码与配置解耦,代码在git仓库,而配置则由运维平台统一管理。这种做法给迁移提供了很多便利,整个过程对开发是透明的。否则开发需要两个分支来维护不同的镜像,将大大增加开发成本。
绑定API地址
当在一个服务中调用其他API服务时,应避免将API服务的IP(私网SLB地址)直接写入配置文件,否则非常难以维护。例如要替换一个SLB,则需修改所有调用该SLB的服务配置,而且还很难知道这个SLB究竟被哪些服务调用。
我们的做法是,在配置文件中只填写API服务的域名(自定义“.lan”内网域名),由运维平台在服务部署时通过运维数据库为服务绑定所需IP(通过dokcer的add-host参数实现)。
数据源地址渲染
我们使用模板来渲染数据源地址,在service的配置文件中只需要填写抽象的键值。
production:
database: test
host: #host#
username: #username#
password: #password#
在docker build之前,运维平台会使用运维数据库将这些键值渲染成实际的值。迁移VPC时只需要将配置文件渲染两次,分别推送到不同的docker registry。和绑定API地址
一样,最终我们只需维护两套运维配置数据即可。
调用关系图
由于采用微服务架构,我们服务很多,调用关系也错综复杂。所前所述,服务所依赖的数据源和API服务都记录在运维数据库中,那么我们通过分析数据库就可以绘制出类似于图2的服务调用关系图。
有了服务调用关系图,在VPC迁移时我们才能知道应该先迁移哪些服务、迁移某个服务时需要先解决哪些依赖。例如我们采用自底向上迁移的顺序,那么就应该先迁移最靠近数据源且依赖其他API服务最少的服务。图2是service-1的调用关系图,service-1依赖两个redis实例和两个rds实例,同时也依赖服务service-2和service-3。如果要迁移service-1,除了准备好相应的数据源,还必须先迁移service-2和service3。
流量资费
最后,自建VPN打通经典网络和VPC的方案会产生额外的流量资费,而我们在迁移中采用的以下三种跨网访问方式:
- SLB混挂
- 数据源混访
- 经典网络通过ClassicLink接入VPC网络
经工单确认,都不会产生流量资费。