基于容器服务的持续集成与云端交付(四)- 多种发布方式

2017 年 2 月 28 日

前言

哲学有各种各样的流派,百家争鸣,但是只有一个哲学问题是严肃的,那就是生与死。而云端交付过程中也只有三个问题是严肃的。

  1. 如何重建你的系统 How to recreate your system?
  2. 如何安全地部署你的系统 How to safely change your system?
  3. 部署后的问题监控与解决 When something has gone wrong?

在前面的文章中,我们讲述了什么是云端交付,如何搭建从零搭建一个持续交付系统,而今天我们要谈的是如何安全的部署你的系统,部署这个名词包含了很多的含义,最简单的解释就是如何让你的程序运行在最终的环境上。但是部署的方式上面有非常多的最佳实践。接下来我们来讨论下常见的几种发布方式,以及如何利用容器发布实现最常用的零宕机发布方式蓝绿发布。

发布策略

常见的发布策略有蓝绿发布、金丝雀发布(灰度发布)、ABTest,在国内的开发者中,对这几个概念有独立的理解。蓝绿发布通常被大家成为热部署;金丝雀发布在国内的名头完全被他的变种发布方式盖过了,主要是灰度发布与 ABTtest,下面我们来详细的为大家解释一下他们之间的异同。

蓝绿发布

在发布的过程中用户无感知服务的重启,通常情况下是通过新旧版本并存的方式实现,也就是说在发布的流程中,新的版本和旧的版本是相互热备的,通过切换路由权重的方式(非 0 即 100)实现不同的应用的上线或者下线。

金丝雀发布

通过在线上运行的服务中,新加入少量的新版本的服务,然后从这少量的新版本中快速获得反馈,根据反馈决定最后的交付形态。

灰度发布

灰度发布是通过切换线上并存版本之间的路由权重,逐步从一个版本切换为另一个版本的过程。虽然马丁·扶老耳朵大人认为灰度发布与金丝雀发布是等同的,但是在具体的操作和目的上面个还是有些许差别的。金丝雀发布更倾向于获取快速的反馈,而灰度发布更倾向于从一个版本到另一个版本平稳的切换。

ABTest

ABTest 和灰度发布非常像,但是从发布的目的上,可以简单的区分灰度发布与 ABTest,ABTest 侧重的是从 A 版本或者 B 版本之间的差异,并根据这个结果进行决策。最终选择一个版本进行部署。因此和灰度发布相比,ABTest 更倾向于去决策,和金丝雀发布相比,ABTest 在权重和流量的切换上更灵活。

阿里云容器服务实现蓝绿发布

下面我们通过一个简单的例子来演示蓝绿发布的流程。假设我们要进行蓝绿发布的应用是一个 nginx 的静态页面, 初始的应用模板如下

复制代码
nginx-v1:
image: 'registry.aliyuncs.com/ringtail/nginx:1.0'
labels:
aliyun.routing.port_80: nginx
restart: always

部署后会有“Welcome to nginx”的成功页面提示。如果用户想变更配置, 可以在操作面板上选择“变更配置”,进入信息修改阶段,选择变更的发布策略与新版本服务的配置。

复制代码
nginx-v2:
image: 'registry.aliyuncs.com/ringtail/nginx:2.0'
labels:
aliyun.routing.coexist: true
aliyun.routing.port_80: nginx
restart: always

在蓝绿发布中,新版本与旧版本不能共用同一个名字;如果共享同一个路由地址,那么需要添加 aliyun.routing.coexist 的 label,这个 label 的含义是,当前的服务与其他服务共享路由地址,在蓝绿发布的场景中,为了保证应用的零宕机切换,新版本的服务的路由权重默认为 0,需要通过路由管理页面进行调整,方可进行流量切换。

在进行发布的过程中,会经历两个状态,一个是蓝绿发布中,一个是蓝绿发布待确认。“蓝绿发布中”表示,新版本的服务尚未启动完成;而“蓝绿发布待确认”表示新版本的服务已经启动完成,此时需要进行发布确认或者发布回滚方可进行下一次发布。其中,新版本的应用和旧版本的应用并存,前者用绿色标明,后者用蓝色标明。如果一个服务在前后两个版本中都存在且没有变化,那么会使用黄色标记,表示这个应用在蓝绿发布中不会出现任何变化。

选择“路由列表”之后再点击“设置服务权重”,调整与之对应的路由权重。如图所示,旧版本服务的权重为 100,新版本服务的权重为 0;下面我们将旧版本服务的权重调整为 0,新版本服务的权重调整为 100。

由于默认路由服务是进行会话保持的,您可以打开一个新的浏览器窗口,访问新的版本,结果如下。

当整个发布流程验证完毕后,需要进行发布确认,方可进行下一次发布。

点击发布确认后,查看应用的详情,可以看到应用的服务列表已经更新了,旧的服务已经完全下线删除了。

复杂拓扑下的蓝绿发布

场景分析

大于大多数场景而言,对客户提供服务的软件的形态有三种。一种是前端类服务,用户可以直接或者间接通过网页、接口调用使用该服务提供的能力;一种是后端类服务,用户无法直接使用该服务提供的功能,该服务主要的使用者是其他服务,并通过其他服务最终将处理后的结果反馈给用户;第三种是调度任务类服务,即不被用户使用也不被其他服务调用,它的生命周期只存在在一个任务的执行生命周期中,通常任务的执行周期完毕,服务的生命周期就停止,通常为无状态资源密集性服务。

对于上述三种场景,以路由权重切换为主要实现方式的发布策略例如蓝绿发布、A/BTest 等通常情况下比较适用于前端类服务与后端类服务。下面我们用一个简单的例子来拆解下如何使用阿里云容器服务来实现这两类服务的蓝绿发布。

在上一篇发布策略的文章中,有的开发者会问我的应用的拓扑关系不是单体的,不能通过单一容器级别的路由权重切换解决。在此要明确的一件事情,蓝绿发布是一种发布策略,部署的最小维度是容器,而发布的最小维度是应用。蓝绿发布的原理是老的应用的版本不变,新的应用版本进行部署,如果新版本与老版本之间应用的名字以及相关的配置没有改变,那么会认为这个应用是新老版本中共用的,无需变更;需要进行变更的应用通过名字的不同进行区分。简单的来讲,蓝绿发布是一个应用级别的更新操作,你可以对一个服务进行两个版本之间的切换,服务是一个逻辑的概念,而不是容器这样一个实体的概念,蓝绿发布可以做复杂拓扑的应用更新操作。

DEMO

下面我们通过一个拓扑结构复杂一点的例子来讲述蓝绿发布。应用的拓扑结构如下:

serviceB 会调用 serviceA,这两个都是 python 构建的,代码如下:

serviceA 通过接口返回数据“world!”

复制代码
from flask import Flask
app = Flask(__name__)
@app.route('/')
def world():
return 'world!'
if __name__ == '__main__':
app.run(port=5000,debug=True,host='0.0.0.0')

serviceB 调用 serviceA 的接口,并将返回的数据字段前面添加“Hello ”

复制代码
from flask import Flask
import requests
import os
dep_endpoint = os.getenv('dep_endpoint');
app = Flask(__name__)
@app.route('/')
def hello():
msg = requests.get(dep_endpoint);
return 'Hello ' + msg.text;
if __name__ == '__main__':
app.run(port=5001,debug=True,host='0.0.0.0')

传统应用之间的调用方式可以是通过配置 IP 地址或者域名来调用,也可以通过服务注册中心中的地址的方式调用,但是对于一个无状态的多实例的服务,常见的做法是使用客户端的负载均衡器或者服务器的服务均衡器端点来实现。在容器服务中使用的方式是使用服务器端的负载均衡端点的方式,提供内部调用的路由端点,来实现后端服务的负载均衡。大致的调用方式如下。

复制代码
serviceA-v1:
image: 'registry-internal.cn-hangzhou.aliyuncs.com/ringtail/demo-service-a:1.0'
labels:
aliyun.scale: 3
aliyun.routing.port_5000: servicea.local
serviceB-v1:
image: 'registry-internal.cn-hangzhou.aliyuncs.com/ringtail/demo-service-b:1.0'
environment:
- 'dep_endpoint=http://servicea.local'
labels:
aliyun.scale: 2
aliyun.routing.port_5001: serviceb
external_links:
- "servicea.local"

serviceB 通过 external_links 的方式将 serviceA 的内部路由端点引入,在环境变量中将

dep_endpoint= http://servicea.local ,使用 servicea.local 作为调用的地址。

访问 serviceB 的对外访问地址,可以得到:

我们最开始基本结构的应用就已经部署完毕了,下面开始进行不同服务的蓝绿发布。

前端服务的蓝绿发布

首先我们先看如果做前端服务的蓝绿发布,也就是说要对 serviceB 进行蓝绿发布的流程。大致的结构图如下。

在这个应用中前端服务的蓝绿发布,也就是对 serviceB 进行蓝绿发布,下面我们修改 serviceB 的代码

复制代码
from flask import Flask
import requests
import os
dep_endpoint = os.getenv('dep_endpoint');
app = Flask(__name__)
@app.route('/')
def hello():
msg = requests.get(dep_endpoint);
return 'Welcome to the ' + msg.text; //ץද"Hello " => "Welcome to the "
if __name__ == '__main__':
app.run(port=5001,debug=True,host='0.0.0.0')

修改编排模板,进行蓝绿发布

复制代码
serviceA-v1:
image: 'registry-internal.cn-hangzhou.aliyuncs.com/ringtail/demo-service-a:1.0'
labels:
aliyun.scale: 3
aliyun.routing.port_5000: servicea.local
serviceB-v2:
image: 'registry-internal.cn-hangzhou.aliyuncs.com/ringtail/demo-service-b:2.0'
environment:
- 'dep_endpoint=http://servicea.local'
labels:
aliyun.scale: 2
aliyun.routing.port_5001: serviceb
external_links:
- "servicea.local"

进行蓝绿发布更新后,可以看到更新后的服务列表,其中黄色的 serviceA-v1 表示当前的应用在蓝绿发布的过程中不会产生变化,serviceB-v1 为老版本,serviceB-v2 为新版本

通过路由列表选择相应的权重管理,进行权重的调整,将 serviceA-v1 的权重调整为 0,将 serviceA-v2 的权重调整为 100,此时访问 serviceB 的网址可以发现。

验证完毕,点击确认发布完成,完成蓝绿发布。

后端服务的蓝绿发布

我们再看下如何做后端服务的蓝绿发布,也就是说对 serviceA 进行蓝绿发布。大致的结构图如下。

修改编排模板

复制代码
serviceA-v2:
image: 'registry-internal.cn-hangzhou.aliyuncs.com/ringtail/demo-service-a:2.0'
labels:
aliyun.scale: 3
aliyun.routing.coexist: true
aliyun.routing.port_5000: servicea.local
serviceB-v2:
image: 'registry-internal.cn-hangzhou.aliyuncs.com/ringtail/demo-service-b:2.0'
environment:
- 'dep_endpoint=http://servicea.local'
labels:
aliyun.scale: 2
aliyun.routing.coexist: true
aliyun.routing.port_5001: serviceb
external_links:
- "servicea.local"

部署后的服务列表:

此时可以发现 serviceB-v2 在本次发布中不会进行变更,调整服务的权重

此时再访问 serviceB 的地址,可以得到如下的结果

总结

蓝绿发布是一种用于升级与更新的发布策略,对于增量升级有比较好的支持,但是对于涉及数据表结构变更等等不可逆转的升级,并不完全合适用蓝绿发布来实现,需要结合一些业务的逻辑以及数据迁移与回滚的策略才可以完全满足需求,希望给位开发者可以在自己的业务场景中,更灵活的使用和实现蓝绿发布。

附录

代码仓库地址: https://github.com/ringtail/BlueGreenDevelopment

个人简介

莫源,阿里云高级研发工程师。在加入阿里巴巴之前,先后在北京天方地圆科技有限公司、微软亚洲研究院任职。现主要负责阿里云容器服务产品的底层服务发现系统、集群管理系统的研发,从事容器的持续交付、持续集成的方案的设计与实现。在云计算、分布式系统、图像识别与虚拟现实方向有多年的开发经验。


感谢木环对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2017 年 2 月 28 日 16:263413

评论

发布
暂无评论
发现更多内容

Docker 镜像构建之 docker commit

哈喽沃德先生

Docker 容器 微服务 容器技术 容器化

第 0 期架构师训练营第3周作业1

傅晶

组合模式

用户密码验证函数

周冬辉

加密

漫画解读:唐僧师徒如何帮助大唐官网打造CDN+OSS完美架构?

巨侠说

Week11总结

熊威

白板技术实践:在线教育平台如何保障课件数据安全

ZEGO即构

加密解密 OSS 鉴权

Apache Pulsar 社区周报:08-15 ~ 08-21

Apache Pulsar

云原生 Apache Pulsar 消息系统 消息中间件

奈学:Executor线程池的概述

奈学教育

线程池 Executor

奈学:Executor线程池的概述

古月木易

线程池 Executor

云上度假村木莲庄酒店助你远离城市的喧嚣

InfoQ_967a83c6d0d7

架构师 0 期第十一周命题作业

何伟敏

市值管理机器人,刷量机器人,做市机器人

WX13823153201

市值管理机器人

数据隔离、访问授权,用好大数据为什么这么难?

华为云开发者社区

大数据 数据湖 华为云 DLI 数据隔离

Week11作业1

熊威

90%的开发都没搞懂的CI和CD!

ci DevOps 持续集成 持续交付 持续部署

技术揭秘:华为云DLI背后的核心计算引擎

华为云开发者社区

大数据 spark 数据湖 华为云 DLI

前端训练营(15)-动画

罗思雨

前端进阶训练营

如何在3秒内打开一个网址

BabyKing

第十一周学习总结

菲尼克斯

Newbe.Claptrap 框架入门,第四步 —— 利用 Minion,商品下单

newbe36524

云计算 微服务 dock .net core ASP.NET Core

一周信创舆情观察(8.10~8.23)

统小信uos

第十一周命题作业

菲尼克斯

实用!教学白板跨国低时延互动技术实现指南

ZEGO即构

OSS 全站加速 集群

netdata安装到redhat7.6最简手册

橙子冰

netdata

安全架构和高可用系统的架构

周冬辉

高可用系统的架构

java安全编码指南之:拒绝Denial of Service

程序那些事

Java 安全编码指南 java安全编码 DOS攻击 zip炸弹

【华为云数据库技术大公开】机房失火后,还能拯救你的数据吗?

华为云开发者社区

数据库 机房 华为云 数据存储 云数据库

有了MDL锁视图,业务死锁从此一目了然

华为云开发者社区

MySQL 数据库 华为云 MDL锁视图 元数据

浅谈业务系统设计哲学

滴滴普惠出行

Docker 之常见应用部署

哈喽沃德先生

Docker 容器 微服务 容器技术 容器化

CUDA,cuDNN,pytorch 在win10环境下的下载安装

Qx

教程 PyTorch

基于容器服务的持续集成与云端交付(四)- 多种发布方式-InfoQ