DevOps - 从渐进式交付说起(含实践 Demo)
作者:CODING - 王炜
1. 开篇
如果让你主导一款千万、甚至亿级用户产品的功能迭代,你会怎么做?你需要面对的挑战可能来自于:
商业战略的变化带来新的产品诉求,而产品的任何改动哪怕仅是界面调整,都将面临无数存量用户的挑战
这时候,作为产品负责人,你会选择稳定压倒一切?还是自我革新,继续追求用户和市场的价值呢?笔者通过对 Facebook、Twitter 等互联网巨头的调研,试图窥探他们在瞬息万变的市场中仍然保持“稳定”迭代的秘密 - 渐进式交付
通过本篇文章,你将收获:
- 什么是渐进式交付,为什么 DevOps 能够天然与其结合
- 为什么渐进式交付能赋予大规模组织下的产品持续交付及稳定迭代的能力
- 小项目,大项目同样适用的实践经验
2. 什么是渐进式交付
移动互联网时代的爆发,诞生了一大批巨型互联网企业和项目,部分大型项目的技术复杂程度和组织复杂程度甚至不亚于传统的工业项目,为了实现对这些项目的管理和迭代,我们试图将目光投向已经完成工业革命的传统工业寻找答案。
而“渐进式交付”一词最早起源于大型、复杂的工业化项目,比如:铁路、汽车和军事产业、新基建的 5G 网络产业等等。
它和 DevOps 的目标一致,试图将复杂的工程化项目进行分阶段拆解,通过持续进行小型迭代闭环,降低交付成本和节约交付时间
可查询的资料显示,“渐进式交付”流行于互联网产品是在近两年 Kubernetes 以及云原生大规模使用之后。这两项技术的出现,为“渐进式交付”在互联网的应用提供了基础设施和实现方法。
DevOps 是“渐进式交付”的实现手段,而其中的“流水线”为“渐进式交付”提供了实现途径
在产品的迭代过程,可以将“渐进式交付”的具体行为附着在“流水线”中,将整条交付“流水线”看做是产品迭代的一个过程和一次“渐进式交付”周期。
说了这么多“渐进式交付”的理论基础,在实践中又是以哪些技术方法落地呢?
- A/B 测试
- 金丝雀 / 灰度发布
以 Facebook 为例,每次发布重大功能,都会经历一次典型的“渐进式交付”过程:
- 迭代发布
- 公司全员 A/B 测试
- 特定用户 A/B 测试
- 灰度发布
- 全量发布
这种渐进式交付的好处是,对于新迭代正式推向市场前提供了灰度用户的数据支撑,帮助决策者充分了解用户倾向和市场诉求。
在“渐进式交付”的过程中,A/B 测试环节以及灰度发布环节都可以根据用户数据和市场反馈决定是否进入全量发布,这种方式既能够保证迭代敏捷进行,又能够保证迭代的用户和市场安全性。
2.1 A/B 测试
例如通过对用户画像中地理位置和性别组合条件进行 A/B 测试,使其访问新版本,而其他的用户则继续访问旧版。一段时间后,研究用户行为数据和用户体验报告,决定功能是否继续进入下一个发布环节。
2.2 金丝雀 / 灰度发布
使用特定分流技术使流量由新老版本共同承担,如典型的“MurmurHash”算法
3. 技术价值和商业价值
从原理上来看,这些技术并不是多么新的技术,比如 A/B 测试,我们用最原始的方式:业务代码增加逻辑判断条件也可实现,但为什么没有大规模运用呢?
原因很简单:纯业务代码的实现依赖于技术开发,需求方无法自主控制 A/B 测试的环境和条件,这种过度依赖于技术开发并不能带来规模化的运用。
我们需要的是一种完全脱离业务代码的实现方式,最好能以自动化/半自动化实现,并且尽量能把这个动作加入到已有的内部流程内
现在,有了云原生和 Kubernetes 的支持,结合 DevOps 的流水线,自动化的渐进式交付成为了可能。
我们参考 Facebook 的发布方式,设计了这个 Pipeline Demo
它主要实现了:
- 提交代码后自动执行单元测试,并构建 Docker 镜像
- 将 Docker 镜像推送到私有制品库,自动触发流水线
- 执行 K8S Job Migrate 数据库(如果有改动),并部署新版到预发布环境
- 人工确认:发布生产环境前是否进行 A/B 测试
- A/B 测试通过后,设置灰度发布的比例,自动灰度发布
- 人工确认:是否全量发布生产环境
- 生产环境自动配置限流和熔断策略,保证生产稳定
最终实现的效果图:
1. 提交代码后自动构建镜像、单元测试、推送到镜像仓库并触发 CD 流水线。
2. 执行 K8S Job 对预发布环境数据库自动 Migrate,并发布到预发布环境。
3. 此时通过访问 http://dev.coding
可以访问到新发布的服务。
4. 人工确认:发布生产环境前是否进行 A/B 测试。
5. 在本例,发布后以 Header
包含 location=shenzhen 作为区分 A/B 测试流量。
在浏览器内直接请求 http://pro.coding
,流量仍然分流到生产环境。在 Postman 携带 location=shenzhen 的 Header 请求,可以发现流量被分流到“A/B 测试环境”。
6. 人工确认:设置期望的灰度发布比例,自动灰度发布,如选择灰度发布比例为 50%。
7. 请求 http://pro.coding
,灰度发布环境和生产环境将以 1:1 的流量比例对外提供服务。
8. 人工确认:全量发布生产环境。
9. 此时请求 http://pro.coding
访问的是生产环境新版本。
10. 自动配置限流和熔断策略,保证生产稳定性。通过压力测试可看到并发请求有一部分被限流。
HttpCode=429,代表 Too Many Requests ,拒绝服务。
对于开发人员,这种渐进式交付经过多轮的灰度、A/B 测试,最大程度减少代码 BUG 发布到生产环境
对于运维人员,这种几乎全自动的交付方式改变了手动修改 yaml 文件,手动 apply 的麻烦,最大程度减少发布产生的人为错误。通过自动触发的方式,减小了与开发的沟通成本
对于产品经理和运营人员,产品迭代不再是靠内部团队“YY”,而是基于实际用户体验数据,了解新功能对于用户和市场的反馈,最大程度减小新功能的用户和市场风险
4. 动手实践
4.1 概览
- 准备一个 K8S 集群,推荐使用腾讯云容器服务;
- K8S 集群部署 Traefik 替换 nginx-Ingress 作为 Ingress Gateway,提供更好的流量治理能力;
- 开通 CODING DevOps,提供镜像构建和流水线的部署能力;
- 克隆示例代码并推送到自己的 CODING 代码仓库;
- 复制模板创建部署流水线;
- 尽情测试。
4.2 实践步骤
4.2.1 克隆源代码并创建构建计划
克隆源代码并推送到自己的 CODING 仓库
git clone https://e.coding.net/wangweicoding/cd-production.git
git remote remove origin
git remote add origin 你的 CODING 仓库地址
git push origin master
创建构建计划
- 使用
cd-production
代码仓库内的 Jenkinsfile 创建构建计划
4.2.2 开通腾讯云容器服务
- 开通集群,并在 CODING DevOps 新建 K8S 集群凭证(如有必要,请允许集群外网访问)
4.2.3 通过 CODING DevOps 初始化 Traefik
- 复制代码仓库
cd-production
的coding-templates/traefik.json
内容,并在部署控制台
创建 pipeline,点击“编辑 JSON 配置”,将内容复制到输入框。
点击“Update Pipeline”后,自动创建了对应的 Pipeline。
注意请将每一个阶段的云账号修改为自己的云账号。
再点击“保存”即完成 Traefik 初始化的 Pipeline 创建,返回后,点击“立即启动”完成集群 Traefik 的初始化。
进入“腾讯云”容器服务,打开集群Service
,点击命名空间 traefik-system
,找到名为 traefik-ingress
的 IP 地址,并在本机新建两个 Host
规则:
IP地址 dev.coding
IP地址 pro.coding
这样在本地通过访问 dev.coding
就可以访问发布的服务了。
4.2.4 通过 CODING DevOps 初始化 Mysql
- 以上述同样的操作复制代码仓库
cd-production
的coding-templates/mysql.json
内容,并在每一个阶段修改为自己的“云账号”,创建 Pipeline 并启动完成集群 Mysql 的初始化。
4.2.5 创建渐进式交付流水线
在创建渐进式交付流水线之前,请先开通
CODING 制品库
,开通完成后,请按照指引在本地使用cd-production
的Dockerfile
构建镜像并推送至“制品库”
随后以上述同样的操作复制代码仓库 cd-production
的 coding-templates/devops.json
内容,创建渐进式交付的 Pipeline。
请将“配置”阶段的“启动所需制品”修改为自己的 CODING 项目
、Git
仓库、镜像仓库、镜像。
请将“配置”阶段的“自动触发器”修改为自己的 CODING 项目
、镜像仓库、镜像。
请将每一个阶段的云账号
修改为自己的云账号。
尝试修改项目 index.html
并推送,触发流水线。
5. 项目说明与核心原理
5.1 项目说明
├── Dockerfile
├── Jenkinsfile # CODING CI 构建脚本
├── Pipfile
├── Pipfile.lock
├── README.md
├── app.py
├── coding-templates
│ ├── devops.json # CODING CD 渐进式交付模板
│ ├── mysql.json # CODING CD Mysql 初始化模板
│ └── traefik.json # CODING CD Traefik 初始化模板
├── config.py
├── database_version.py
├── devops
│ ├── README.md
│ ├── mysql
│ │ ├── dev
│ │ │ ├── mysql-deployment.yaml
│ │ │ └── mysql-pv.yaml
│ │ └── pro
│ │ ├── mysql-deployment.yaml
│ │ └── mysql-pv.yaml
│ └── traefik
│ ├── deployment
│ │ ├── configmap.yaml
│ │ └── deployment.yaml
│ ├── deployment.yaml
│ ├── open-treafik.yaml
│ └── router
│ ├── dev
│ │ └── flask-dev.yaml # Dev 环境的 Traefik IngressRoute 规则
│ └── pro
│ ├── circuitbreaker.yaml # Pro 环境的 Traefik 熔断规则
│ ├── flask-abtest.yaml # Pro 环境的 A/B Testcase
│ ├── flask-pro-all.yaml # Pro 环境的 Traefik IngressRoute 规则
│ ├── flask-pro.yaml
│ ├── mysql-ratelimit.yaml
│ ├── mysql-tcp-router.yaml
│ └── ratelimit.yaml # Pro 环境的 Traefik 限流规则
├── flask_test.py
├── k8s-canary # 灰度发布环境的 K8S Manifest
│ ├── deployment.yaml
│ └── nodeport-canary.yaml
├── k8s-dev # Dev 环境的 K8S Manifest
│ ├── deployment.yaml
│ ├── migrate-mysql-job.yaml # Dev 环境的 Migrate Database K8S Job
│ ├── nodeport-service.yaml
│ └── service.yaml
├── k8s-pro # Pro 环境的 K8S Manifest
│ ├── deployment.yaml
│ ├── migrate-mysql-job.yaml
│ └── nodeport-service.yaml
├── manage.py
├── migrations
│ ├── README
│ ├── alembic.ini
│ ├── env.py
│ ├── script.py.mako
│ └── versions
│ ├── 95585fe4b611_initial_migration.py
│ └── fece98dad497_second_migrate.py
├── requirements.txt
└── templates
└── index.html # 项目发布首页
5.2 核心原理
在这个例子中,我们使用了 Traefik 作为集群网关,使用 Router
对 Host dev.coding
和 pro.coding
进行匹配,使流量按照不同发布阶段进行不同的分配。
5.2.1 Dev 环境架构图
访问 dev.coding
时,Router
匹配到此 Host
规则,将流量转发到名为k8s-flask-nodeport
的 Service
(即 Dev 环境的 Service)。
Traefik Router 核心配置代码为:
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: k8s-flask-dev
namespace: dev
spec:
entryPoints:
- web
routes:
- kind: Rule
match: Host(`dev.coding`)
services:
- name: k8s-flask-nodeport
port: 8080
namespace: dev
5.2.2 A/B 测试环境架构图
访问 pro.coding
时,Router
匹配到此 Host
规则,并检查 Header
是否匹配,并将根据匹配结果决定将流量转发到 k8s-flask-canary
或者 k8s-flask
两个不同环境的 Services。
A/B 测试 Traefik Router 核心配置代码为:
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: k8s-flask-abtest
namespace: pro
spec:
entryPoints:
- web
routes:
#A/B test
- kind: Rule
match: Host(`pro.coding`) && Headers(`location`, `shenzhen`)
services:
- name: k8s-flask-canary
port: 8080
namespace: pro
5.2.3 灰度发布架构图
访问 pro.coding
时,Router
匹配到此 Host
规则,并根据配置的 Weight
权重,将流量按比例转发到 k8s-flask-canary
或者 k8s-flask
Service
例如以 1:1 的比例分配灰度比例,Traefik Router 核心配置代码为:
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: k8s-flask-pro
namespace: pro
spec:
entryPoints:
- web
routes:
#canary deploy
- kind: Rule
match: Host(`pro.coding`)
services:
- name: k8s-flask
port: 8080
namespace: pro
weight: 50 # 权重比例
- name: k8s-flask-canary
port: 8080
namespace: pro
weight: 50 # 权重比例
当全量发布生产环境的时候,只需要将 Canary
环境的 Weight
权重设置为 0,即所有流量都转发到生产环境。
5.2.4 熔断和限流架构图
在生产环境,我们一般使用限流和熔断技术来应对流量激增,牺牲部分用户的体验来保证生产环境的稳定。
Traefik 内熔断和限流是通过配置 middlewares
来实现,对流量进行匹配后,再进行中间件二次流量确认。
Traefik Middlewares 限流核心配置:
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: flask-k8s-traffic
namespace: pro
spec:
rateLimit:
# 1s 内接收的请求数的平均值不大于500个,高峰最大1000个请求
burst: 1000
average: 500
Traefik Middlewares 熔断核心配置:
# Latency Check
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: k8s-flask-breaker
namespace: pro
spec:
circuitBreaker:
expression: LatencyAtQuantileMS(50.0) > 100
# 50% 的请求比例响应时间大于 100MS 时熔断
6. 小结
Kubernetes 和 Service Mesh 的出现,给 DevOps 带来了更多可能,渐进式交付只是一种借助其便利性的比较典型的发布方式。
我们借助了 Traefik 作为集群网关,通过分流技术实现了 A/B 测试和灰度发布,当然,你也可以引入 Service Mesh
使用 Istio 管理集群流量,借助 Virtual Service
和 Destination Rule
实现同样效果。
借助 CODING DevOps 的能力,我们将“推送代码”、“构建镜像”、“触发部署流程”进行打通,实现了自动化的 DevOps 能力。
当然,还可以做到更多有价值的发布流程,比如:
- 运营、产品人员可以实现很方便地随时修改 A/B Testcase 进行分流测试,只需要配置一个修改 A/B Testcase 的 Pipeline,输入相关的分流指标,并运行即可生效;
- 除了 A/B Testcase,灰度发布也变成了实时可控的数值,甚至可以实现一个“渐进式灰度发布”的 Pipeline,增加
Wait
阶段实现灰度比例随着时间推移自动增加; - 可以很方便地实现一个自动回滚的 Pipeline,通过输入版本号就可以实现自动回滚到对应的版本,如使用数据库 ORM 产品,甚至可以实现数据库的自动回滚;
- Traefik 提供的熔断和限流能力,结合 Pipeline 的 Webhook 触发以及监控系统如 Prometheus 联动,可以实现业务系统压力较大的时候自动触发熔断和限流 Pipeline 改变限流策略,保证生产环境的稳定性。
7. 资源链接和参考资料
- Demo Git 仓库地址
- CODING 持续集成(CI)
- CODING 持续部署(CD)
- Tencent TKE
- Facebook 的增长神器 —— 灰度发布 + A/B testing
- Traefik 文档
DevOps - 从渐进式交付说起(含实践 Demo)的更多相关文章
- 基于微服务的DevOps落地指南 交付效率提升40%
基于微服务的DevOps落地指南 交付效率提升40% 2015-2016年,珍爱线下门店已新增覆盖城市9个,与此同时,CRM系统大小故障却发生了数十起... ... 珍爱网是以“网络征选+人工红娘”模 ...
- Kubernetes 中的渐进式交付:蓝绿部署和金丝雀部署
渐进式交付是持续交付的下一步, 它将新版本部署到用户的一个子集,并在将其滚动到全部用户之前对其正确性和性能进行评估, 如果不匹配某些关键指标,则进行回滚. 这里有一些有趣的项目,使得渐进式交付在 Ku ...
- 使用 Flux,Helm v3,Linkerd 和 Flagger 渐进式交付 Kubernetes
介绍 本指南将引导您在 Kubernetes 集群上设置渐进式交付 GitOps 管道. GitOps Helm 研讨会 原文地址:GitOps Progressive Deliver with Fl ...
- [转]DevOps实战:百度持续交付体系与最佳实践大解密!
本文转自:http://dbaplus.cn/news-21-471-1.html “互联网+”时代,软件产品要想满足快速增长的用户需求,高效.快速的迭代转型必不可少,面对时刻发生改变的互联网及业务模 ...
- 基于Docker持续交付平台建设的实践
导读:中国五矿和阿里巴巴联手打造的钢铁服务专业平台五阿哥,通过集结阿里巴巴在大数据.电商平台和互联网产品技术上的优势,为终端用户带来一站式采购体验.本文是五阿哥运维技术团队针对Docker容器技术在如 ...
- DevOps之持续交付
持续交付 持续交付是一种可以帮助团队以更短的周期交付软件的方法,该方法确保了团队可以在任何时间发布出可靠的软件.该方法意在以更快速度更高频率进行软件的构建.测试和发布. 通过对生产环境中的应用程序进行 ...
- Azure DevOps 中 Dapr项目自动部署流程实践
注:本文中主要讨论 .NET6.0项目在 k8s 中运行的 Dapr 的持续集成流程, 但实际上不是Dapr的项目部署到K8s也是相同流程,只是k8s的yaml配置文件有所不同 流程选择 基于 Dap ...
- 使用 flux2+kustomize+helm+github 进行多集群 GitOps 云原生渐进式交付
对于此示例,我们假设有两个集群的场景:暂存(staging)和生产(production). 最终目标是利用 Flux 和 Kustomize 来管理两个集群,同时最大限度地减少重复声明. 我们将配置 ...
- 正则表达式的实践demo
正则表达式十分强大,几乎在所有框架中处处可以看到,下载框架源码仔细阅读肯定可以发现.在项目应用中也经常需要正则的帮助,举个栗子,我们常需要用到的表单验证输入....其实还有很多,不一一道出,在这里我搜 ...
随机推荐
- spring学习笔记(二)spring中的事件及多线程
我们知道,在实际开发中为了解耦,或者提高用户体验,都会采用到异步的方式.这里举个简单的例子,在用户注册的sh时候,一般我们都会要求手机验证码验证,邮箱验证,而这都依赖于第三方.这种情况下,我们一般会通 ...
- 数据结构之栈(stack)的实现
一.栈 1.定义 栈的英文为(stack),是一种数据结构 栈是一个先入后出(FILO-First In Last Out)的有序列表. 栈(stack)是限制线性表中元素的插入和删除只能在线性表的同 ...
- Android 8.1 关机充电动画(三)Android模式
system:Android 8.1 platform:RK3326/PX30 uboot kernel system/core/healthd Android 8.1 关机充电动画(一)模式选择 A ...
- 设计模式之GOF23访问者模式
访问者模式Visitor 模式动机:对于存储在一个集合中的对象,他们可能具有不同的类型(即使有一个公共的接口),对于该集合中的对象,可以接受一类称为访问者的对象来访问,不同访问者的访问方式也不同 定义 ...
- Mysql 常用函数(15)- upper 函数
Mysql常用函数的汇总,可看下面系列文章 https://www.cnblogs.com/poloyy/category/1765164.html upper 的作用 将所有字符串更改为大写,然后返 ...
- CentOS7.2 安装 MongoDB 3.4
服务器版本 CentOS7.2 MongoDB版本 3.4 1/ 下载所需资源 阿里镜像地址 http://mirrors.aliyun.com/mongodb/yum/redhat/7/mongod ...
- javaweb学习之路(3)Cookie
1.Cookies的原理 1)首先浏览器向服务器发出请求. 2)服务器就会根据需要生成一个Cookie对象,并且把数据保存在该对象内. 3)然后把该Cookie对象放在响应头,一并发送回浏览器. 4) ...
- python实现简单投资复利函数以及实现摇骰子猜大小函数
复利函数: #!/user/bin/env python #-*-coding:utf-8 -*- #Author: qinjiaxi def invest(amount, rate, time): ...
- tomcat 添加 ssl 证书
1. 将证书提供方给的证书(server.crt)及密钥文件(server.key)上传到服务器 tomcat 的 conf 目录 2. 在tomcat conf 目录下执行如下命令 (1) 生成P1 ...
- js中的小案例(一)
效果图: html代码: <div id="date"> <p> <span id="prev">上一月</span& ...