用kubernetes对服务进行灰度发布和蓝绿部署

2,948 阅读4分钟

基本概念

灰度发布(又名金丝雀发布)是指在黑与白之间,能够平滑过渡的一种发布方式。在其上可以进行A/B testing,即让一部分用户继续用产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。

蓝绿部署是不停老版本,部署新版本然后进行测试,确认OK,将流量切到新版本,然后老版本同时也升级到新版本。

灰度是不同版本共存,蓝绿是新旧版本切换,2种模式的出发点不一样。

环境准备

1. 下载安装minikube

minikube是一个可以快速在单机上部署kubernetes并运行容器的工具,由于条件有限,我都是用它来学习Kubernetes知识。

Mac OSX

curl -Lo minikube https://kubernetes.oss-cn-hangzhou.aliyuncs.com/minikube/releases/v1.13.0/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/

Linux

curl -Lo minikube https://kubernetes.oss-cn-hangzhou.aliyuncs.com/minikube/releases/v1.13.0/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/

2. 启动

本机环境需提前安装好docker-engine

minikube start --driver=none

启动成功之后,查看启动的pods

kubectl get pods -A

NAMESPACE              NAME                                        READY   STATUS    RESTARTS   AGE
kube-system            coredns-6c76c8bb89-nz22j                    1/1     Running   2          24h
kube-system            etcd-minikube                               1/1     Running   2          24h
kube-system            kube-apiserver-minikube                     1/1     Running   2          24h
kube-system            kube-controller-manager-minikube            1/1     Running   2          24h
kube-system            kube-proxy-2qwq9                            1/1     Running   2          24h
kube-system            kube-scheduler-minikube                     1/1     Running   2          24h
kube-system            storage-provisioner                         1/1     Running   3          24h
kubernetes-dashboard   dashboard-metrics-scraper-c95fcf479-7qnhf   1/1     Running   0          24h
kubernetes-dashboard   kubernetes-dashboard-5c448bc4bf-6gccm       1/1     Running   0          24h

3. 准备镜像

准备Dockerfile文件

Dockerfile

FROM python:alpine 
RUN pip install flask -i https://pypi.douban.com/simple
COPY app.py /app.py
EXPOSE 5000
ENTRYPOINT ["python", "/app.py"]

app.py

from flask import Flask
from flask import request
from flask import jsonify
import socket


app = Flask(__name__)

@app.route('/')
def index():
    return jsonify({'Version': 'v1',
                    'Hostname': socket.gethostname(),
                    'Address': socket.gethostbyname(socket.gethostname()),
                    'Message': 'Hello, World',
                    })
		    


if __name__ == '__main__':
    app.run('0.0.0.0', 5000)

构建第一个镜像:

docker build . -t flask-app:v1

构建第二个镜像:

先将app.py里面的v1改为v2

docker build . -t flask-app:v2

如此我们便有了两个版本的镜像。

蓝绿部署

准备deployment和service文件,内容如下:

deployment-v1.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: flask-app
      version: v1
  template:
    metadata:
      labels:
        app: flask-app
        version: v1
    spec:
      containers:
      - image: flask-app:v1
        name: flask-app
        ports:
        - containerPort: 5000

deployment-v2.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask-app-v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: flask-app
      version: v2
  template:
    metadata:
      labels:
        app: flask-app
        version: v2
    spec:
      containers:
      - image: flask-app:v2
        name: flask-app
        ports:
        - containerPort: 5000

service.yaml

apiVersion: v1
kind: Service
metadata:
  name: flask-app
spec:
  ports:
  - name: "http"
    port: 5000
    protocol: TCP
    targetPort: 5000
  selector:
    app: flask-app
    version: v1
  type: NodePort

执行: kubectl apply -f deployment-v1.yaml

执行: kubectl apply -f service.yaml

执行: minikube ip 查看节点ip, kubectl get svc查看nodeport端口

执行: curl $http://${node_ip}:${node_port}

输出:

{"Address":"172.17.0.4","Hostname":"flask-app-69cd76c45c-kpzn5","Message":"Hello, World","Version":"v1"}

执行: kubectl apply -f deployment-v2.yaml

将service.yaml里面的v1全部改为v2

执行: kubectl apply -f service.yaml

继续执行: curl $http://${node_ip}:${node_port}

输出:

{"Address":"172.17.0.8","Hostname":"flask-app-v2-6748f77677-vb4rx","Message":"Hello, World","Version":"v2"}

灰度发布

进行灰度发布之前先清理一下之前的deployment和service

kubectl delete -f deployment-v1.yaml
kubectl delete -f deployment-v2.yaml
kubectl delete -f service.yaml

准备几个文件 deployment-canary-v1.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask-app-v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: flask-app
  template:
    metadata:
      labels:
        app: flask-app
    spec:
      containers:
      - image: flask-app:v1
        name: flask-app
        ports:
        - containerPort: 5000

deployment-canary-v2.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask-app-v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: flask-app
  template:
    metadata:
      labels:
        app: flask-app
    spec:
      containers:
      - image: flask-app:v2
        name: flask-app
        ports:
        - containerPort: 5000

service-canary.yaml

apiVersion: v1
kind: Service
metadata:
  name: flask-app
spec:
  ports:
  - name: "http"
    port: 5000
    protocol: TCP
    targetPort: 5000
  selector:
    app: flask-app
  type: NodePort

执行:

kubectl apply -f deployment-canary-v1.yaml
kubectl apply -f deployment-canary-v2.yaml
kubectl apply -f service-canary.yaml

执行: kubectl get svc查看nodeport

执行:

while true; do curl http://${node_ip}:${node_port}; done

输出:

{"Address":"172.17.0.7","Hostname":"flask-app-69cd76c45c-knn4c","Message":"Hello, World","Version":"v1"}
{"Address":"172.17.0.4","Hostname":"flask-app-v2-7c957654cc-2mtbb","Message":"Hello, World","Version":"v2"}
{"Address":"172.17.0.4","Hostname":"flask-app-v2-7c957654cc-2mtbb","Message":"Hello, World","Version":"v2"}
{"Address":"172.17.0.4","Hostname":"flask-app-v2-7c957654cc-2mtbb","Message":"Hello, World","Version":"v2"}
{"Address":"172.17.0.7","Hostname":"flask-app-69cd76c45c-knn4c","Message":"Hello, World","Version":"v1"}
{"Address":"172.17.0.7","Hostname":"flask-app-69cd76c45c-knn4c","Message":"Hello, World","Version":"v1"}
{"Address":"172.17.0.4","Hostname":"flask-app-v2-7c957654cc-2mtbb","Message":"Hello, World","Version":"v2"}

利用service roundrobin负载均衡,可以看到v1与v2的服务共存。