前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一、灰度发布

一、灰度发布

作者头像
zaking
发布2022-05-10 11:23:58
1.5K0
发布2022-05-10 11:23:58
举报
文章被收录于专栏:zaking's

一、灰度发布

  灰度发布是一种发布方式,也叫金丝雀发布,起源是矿工在下井之前会先放一只金丝雀到井里,如果金丝雀不叫了,就代表瓦斯浓度高。原因是金丝雀对瓦斯气体很敏感。灰度发布的做法是:会在现存旧应用的基础上,启动一个新版应用,但是新版应用并不会直接让用户访问。而是先让测试同学去进行测试。如果没有问题,则可以将真正的用户流量慢慢导入到新版,在这中间,持续对新版本运行状态做观察,直到慢慢切换过去,这就是所谓的A/B测试。当然,你也可以招募一些灰度用户,给他们设置独有的灰度标示(Cookie,Header),来让他们可以访问到新版应用,当然,如果中间切换出现问题,也应该将流量迅速地切换到老应用上。

1)准备新版本的service

  拷贝一份deployment文件:

代码语言:javascript
复制
cp deployment-user-v1.yaml deployment-user-v2.yaml

  修改之前写过的内容:

代码语言:javascript
复制
apiVersion: apps/v1  #API 配置版本
kind: Deployment     #资源类型
metadata:
+  name: user-v2     #资源名称
spec:
  selector:
    matchLabels:
+      app: user-v2 #告诉deployment根据规则匹配相应的Pod进行控制和管理,matchLabels字段匹配Pod的label值
  replicas: 3 #声明一个 Pod,副本的数量
  template:
    metadata:
      labels:
+        app: user-v2 #Pod的名称
    spec:   #组内创建的 Pod 信息
      containers:
      - name: nginx #容器的名称
+        image: registry.cn-beijing.aliyuncs.com/zhangrenyang/nginx:user-v2
        ports:
        - containerPort: 80 #容器内映射的端口

  然后service的文件内容是这样的:

代码语言:javascript
复制
apiVersion: v1
kind: Service
metadata:
+  name: service-user-v2
spec:
  selector:
+    app: user-v2
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: NodePort

  启动:

代码语言:javascript
复制
kubectl apply -f deployment-user-v2.yaml service-user-v2.yaml

2)根据cookie切分流量

  基于 Cookie 切分流量。这种实现原理主要根据用户请求中的 Cookie 是否存在灰度标示 Cookie去判断是否为灰度用户,再决定是否返回灰度版本服务

  • nginx.ingress.kubernetes.io/canary:可选值为 true / false 。代表是否开启灰度功能
  • nginx.ingress.kubernetes.io/canary-by-cookie:灰度发布 cookie 的 key。当 key 值等于 always 时,灰度触发生效。等于其他值时,则不会走灰度环境 ingress-gray.yaml

  我们创建一个ingress-gray.yaml文件:

代码语言:javascript
复制
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: user-canary
annotations:
  kubernetes.io/ingress.class: nginx
  nginx.ingress.kubernetes.io/rewrite-target: /
  nginx.ingress.kubernetes.io/canary: "true"
  nginx.ingress.kubernetes.io/canary-by-cookie: "vip_user"
spec:
rules:
- http:
    paths: 
     - backend:
        serviceName: service-user-v2
        servicePort: 80
backend:
   serviceName: service-user-v2
   servicePort: 80

  使文件生效:

代码语言:javascript
复制
kubectl apply -f ./ingress-gray.yaml 

  获取外部接口:

代码语言:javascript
复制
kubectl -n ingress-nginx get svc

  测试:

代码语言:javascript
复制
curl http://172.31.178.169:31234/user
curl http://118.190.156.138:31234/user
curl --cookie "vip_user=always"  http://172.31.178.169:31234/user

3)基于header切分流量

  基于 Header 切分流量,这种实现原理主要根据用户请求中的 header 是否存在灰度标示 header去判断是否为灰度用户,再决定是否返回灰度版本服务。

  修改下上面的ingress-gray.yml文件即可:

代码语言:javascript
复制
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: user-canary
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/canary: "true"
+    nginx.ingress.kubernetes.io/canary-by-header: "name"
+    nginx.ingress.kubernetes.io/canary-by-header-value: "vip"
spec:
  rules:
  - http:
      paths: 
       - backend:
          serviceName: service-user-v2
          servicePort: 80
  backend:
     serviceName: service-user-v2
     servicePort: 80

  同样的:

代码语言:javascript
复制
kubectl apply -f ingress-gray.yaml
curl --header "name:vip"  http://172.31.178.169:31234/user

4)基于权重切分流量 

  这种实现原理主要是根据用户请求,通过根据灰度百分比决定是否转发到灰度服务环境中

  • nginx.ingress.kubernetes.io/canary-weight:值是字符串,为 0-100 的数字,代表灰度环境命中概率。如果值为 0,则表示不会走灰度。值越大命中概率越大。当值 = 100 时,代表全走灰度。

  一样一样的,修改下配置参数罢了:

代码语言:javascript
复制
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: user-canary
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/canary: "true"
+   nginx.ingress.kubernetes.io/canary-weight: "50"
spec:
  rules:
  - http:
      paths: 
       - backend:
          serviceName: service-user-v2
          servicePort: 80
  backend:
     serviceName: service-user-v2
     servicePort: 80

  测试下:

代码语言:javascript
复制
kubectl apply -f ingress-gray.yaml
for ((i=1; i<=10; i++)); do curl http://172.31.178.169:31234/user; done

  k8s 会优先去匹配 header ,如果未匹配则去匹配 cookie ,最后是 weight。

二、滚动发布

  滚动发布,则是我们一般所说的无宕机发布。其发布方式如同名称一样,一次取出一台/多台服务器(看策略配置)进行新版本更新。当取出的服务器新版确保无问题后,接着采用同等方式更新后面的服务器。k8s创建副本应用程序的最佳方法就是部署(Deployment),部署自动创建副本集(ReplicaSet),副本集可以精确地控制每次替换的Pod数量,从而可以很好的实现滚动更新。k8s每次使用一个新的副本控制器(replication controller)来替换已存在的副本控制器,从而始终使用一个新的Pod模板来替换旧的pod模板

  • 创建一个新的replication controller
  • 增加或减少pod副本数量,直到满足当前批次期望的数量
  • 删除旧的replication controller

  滚动发布的优缺点如下:

  • 优点
    • 不需要停机更新,无感知平滑更新。
    • 版本更新成本小,不需要新旧版本共存
  • 缺点
    • 更新时间长:每次只更新一个/多个镜像,需要频繁连续等待服务启动缓冲
    • 旧版本环境无法得到备份:始终只有一个环境存在
    • 回滚版本异常痛苦:如果滚动发布到一半出了问题,回滚时需要使用同样的滚动策略回滚旧版本

  我们下面来尝试下,先扩容为10个副本:

代码语言:javascript
复制
kubectl get deploy
kubectl scale deployment user-v1  --replicas=10

  修改deployment-user-v1.yaml文件:

代码语言:javascript
复制
apiVersion: apps/v1  #API 配置版本
kind: Deployment     #资源类型
metadata:
  name: user-v1     #资源名称
spec:
  minReadySeconds: 1
+ strategy:
+   type: RollingUpdate
+   rollingUpdate:
+     maxSurge: 1
+     maxUnavailable: 0
+ selector:
+   matchLabels:
+     app: user-v1 #告诉deployment根据规则匹配相应的Pod进行控制和管理,matchLabels字段匹配Pod的label值
  replicas: 10 #声明一个 Pod,副本的数量
  template:
    metadata:
      labels:
        app: user-v1 #Pod的名称
    spec:   #组内创建的 Pod 信息
      containers:
      - name: nginx #容器的名称
+       image: registry.cn-beijing.aliyuncs.com/zhangrenyang/nginx:user-v3 #使用哪个镜像
        ports:
        - containerPort: 80 #容器内映射的端口

参数

含义

minReadySeconds

容器接受流量延缓时间:单位为秒,默认为0。如果没有设置的话,k8s会认为容器启动成功后就可以用了。设置该值可以延缓容器流量切分

strategy.type = RollingUpdate

ReplicaSet 发布类型,声明为滚动发布,默认也为滚动发布

strategy.rollingUpdate.maxSurge

最多Pod数量:为数字类型/百分比。如果 maxSurge 设置为1,replicas 设置为10,则在发布过程中pod数量最多为10 + 1个(多出来的为旧版本pod,平滑期不可用状态)。maxUnavailable 为 0 时,该值也不能设置为0

strategy.rollingUpdate.maxUnavailable

升级中最多不可用pod的数量:为数字类型/百分比。当 maxSurge 为 0 时,该值也不能设置为0

  启动:

代码语言:javascript
复制
kubectl apply -f ./deployment-user-v1.yaml
deployment.apps/user-v1 configured

  然后查看状态:

代码语言:javascript
复制
kubectl rollout status deployment/user-v1

三、服务可用性探针

  当 Pod 的状态为 Running 时,该 Pod 就可以被分配流量(可以访问到)了。一个后端容器启动成功,不一定不代表服务启动成功。

3.2.1 存活探针 LivenessProbe

  第一种是存活探针。存活探针是对运行中的容器检测的。如果想检测你的服务在运行中有没有发生崩溃,服务有没有中途退出或无响应,可以使用这个探针。如果探针探测到错误, Kubernetes 就会杀掉这个 Pod;否则就不会进行处理。如果默认没有配置这个探针, Pod 不会被杀死。

3.2.2 可用探针 ReadinessProbe

  第二种是可用探针。作用是用来检测 Pod 是否允许被访问到(是否准备好接受流量)。如果你的服务加载很多数据,或者有其他需求要求在特定情况下不被分配到流量,那么可以用这个探针。如果探针检测失败,流量就不会分配给该 Pod。在没有配置该探针的情况下,会一直将流量分配给 Pod。当然,探针检测失败,Pod 不会被杀死。

3.2.3 启动探针 StartupProbe

  第三种是启动探针。作用是用来检测 Pod 是否已经启动成功。如果你的服务启动需要一些加载时长(例如初始化日志,等待其他调用的服务启动成功)才代表服务启动成功,则可以用这个探针。如果探针检测失败,该 Pod 就会被杀死重启。在没有配置该探针的情况下,默认不会杀死 Pod 。在启动探针运行时,其他所有的探针检测都会失效。

探针名称

在哪个环节触发

作用

检测失败对Pod的反应

启动探针

Pod 运行时

检测服务是否启动成功

杀死 Pod 并重启

存活探针

Pod 运行时

检测服务是否崩溃,是否需要重启服务

杀死 Pod 并重启

可用探针

Pod 运行时

检测服务是不是允许被访问到

停止Pod的访问调度,不会被杀死重启

检测方式

1、ExecAction

  通过在 Pod 的容器内执行预定的 Shell 脚本命令。如果执行的命令没有报错退出(返回值为0),代表容器状态健康。否则就是有问题的

  我们来新建一个文件,vi shell-probe.yaml,内容如下:

代码语言:javascript
复制
apiVersion: v1
kind: Pod
metadata:
  labels:
    test: shell-probe
  name: shell-probe
spec:
  containers:
  - name: shell-probe
    image: registry.aliyuncs.com/google_containers/busybox
    args:
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 5
      periodSeconds: 5

  然后执行下面的命令,查看情况:

代码语言:javascript
复制
kubectl apply -f liveness.yaml 
kubectl get pods | grep liveness-exec
kubectl describe pods liveness-exec
2、TCPSocketAction

  这种方式是使用 TCP 套接字检测。 Kubernetes 会尝试在 Pod 内与指定的端口进行连接。如果能建立连接(Pod的端口打开了),这个容器就代表是健康的,如果不能,则代表这个 Pod 就是有问题的。

  创建文件如下,tcp-probe.yaml:

代码语言:javascript
复制
apiVersion: v1
kind: Pod
metadata:
  name: tcp-probe
  labels:
    app: tcp-probe
spec:
  containers:
  - name: tcp-probe
    image: nginx
    ports:
    - containerPort: 80
    readinessProbe:
      tcpSocket:
        port: 80
      initialDelaySeconds: 5
      periodSeconds: 10

  类似的:

代码语言:javascript
复制
kubectl apply -f tcp-probe.yaml 
kubectl get pods | grep tcp-probe
kubectl describe pods tcp-probe

  进入到容器内部:

代码语言:javascript
复制
kubectl exec -it tcp-probe  -- /bin/sh

  更新apt-get并安装vim:

代码语言:javascript
复制
apt-get update
apt-get install vim -y
vi  /etc/nginx/conf.d/default.conf

  修改nginx文件配置,把80端口修改为8080,然后重载一下nginx:

代码语言:javascript
复制
nginx -s reload

  看一下状态:

代码语言:javascript
复制
kubectl describe pod tcp-probe
3、HTTPGetAction

  这种方式是使用 HTTP GET 请求。Kubernetes 会尝试访问 Pod 内指定的API路径。如果返回200,代表容器就是健康的。如果不能,代表这个 Pod 是有问题的。

  添加http-probe.yaml文件:

代码语言:javascript
复制
apiVersion: v1
kind: Pod
metadata:
  labels:
    test: http-probe
  name: http-probe
spec:
  containers:
  - name: http-probe
    image: registry.cn-beijing.aliyuncs.com/zhangrenyang/http-probe:1.0.0
    livenessProbe:
      httpGet:
        path: /liveness
        port: 80
        httpHeaders:
        - name: source
          value: probe
      initialDelaySeconds: 3
      periodSeconds: 3

  然后,运行并查看状态:

代码语言:javascript
复制
kubectl apply -f ./http-probe.yaml
kubectl describe pods http-probe
kubectl replace --force -f http-probe.yaml 

  Dockerfile内容如下:

代码语言:javascript
复制
FROM node
COPY ./app /app
WORKDIR /app
EXPOSE 3000
CMD node index.js

  node服务文件如下:

代码语言:javascript
复制
let http = require('http');
let start = Date.now();
http.createServer(function(req,res){
  if(req.url === '/liveness'){
    let value = req.headers['source'];
    if(value === 'probe'){
     let duration = Date.now()-start;
      if(duration>10*1000){
          res.statusCode=500;
          res.end('error');
      }else{
          res.statusCode=200;
          res.end('success');
      }
    }else{
     res.statusCode=200;
     res.end('liveness');
    }
  }else{
     res.statusCode=200;
     res.end('liveness');
  }
}).listen(3000,function(){console.log("http server started on 3000")});

  好了今天的内容就到这里了。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-03-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、灰度发布
    • 1)准备新版本的service
      • 2)根据cookie切分流量
        • 3)基于header切分流量
          • 4)基于权重切分流量 
            • 3.2.1 存活探针 LivenessProbe
            • 3.2.2 可用探针 ReadinessProbe
            • 3.2.3 启动探针 StartupProbe
        • 二、滚动发布
        • 三、服务可用性探针
          • 检测方式
            • 1、ExecAction
            • 2、TCPSocketAction
            • 3、HTTPGetAction
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档