D27 kubernetes 通过Service实现蓝绿发布

1.蓝绿发布的基本原理

	蓝绿发布(Blue-Green-Deployment)是一种应用升级发布方式,这种方式可以瞬间切换到新版本,也可以瞬间回退到老版本。可以做到服务不中断的回滚,提升用户的使用体验。
  • 下面以在kubernetes中升级为例来说明蓝绿发布的过程
    image

为了在k8s中支持蓝绿发布,需要给应用pod添加新的标签也就是version,service中的Selector也要添加version
1.abc服务v1.0运行:version v1.0的abc服务正在接受用户的请求,也就是绿色的pod
2.abc服务v1.1发布:现在新发布了一个version v1.1的abc服务接受用户的请求。也就是蓝色的pod
3.abc服务v1.1接受用户请求:修改service中的version标签为v1.1,这样用户的请求就能转发到新的pod中,实现蓝绿发布。
4.abc服务v1.0下线: 如果用户请求没有问题就可以将version 为v1.0的abc服务删除
5.紧急回退:如果有问题也可以通过修改service中的version标签的值为v1.0进行快速回退(Rollback)
传统的配置中通过配置nginx的配置文件也可以实现蓝绿发布。不过过程比较繁琐也容易出错。也可以使用apisix,通过调用apisix进行动态切换服务ip,相比nginx少了reload和修改配置文件的操作
k8s中的Deployment也支持滚动更新的策略

2.基于service/selector/lablel实现abc服务的蓝绿发布

2.1 准备go代码

package main

import (
	"flag"
	"github.com/gin-gonic/gin"
	"net/http"
	"os"
)

var version = flag.String("v", "v1", "v1")

func main() {
	router := gin.Default()
	router.GET("", func(c *gin.Context) {
		flag.Parse()
		hostname, _ := os.Hostname()
		c.String(http.StatusOK, "This is version:%s running in pod %s", *version, hostname)
	})
	router.Run(":8080")
}

2.2 启动程序,访问测试

image

  • 页面打开 127.0.0.1:8080
    image

  • 可以看到已经有请求进来
    image

2.3 基于Dockerfile制作镜像

2.3.1写一个Dockerfile,与main.go同级目录
suyajun@MacBook-Pro-2 abc % ls -l
total 40
-rw-r--r--@ 1 suyajun  staff  1372 Dec  5 18:59 Dockerfile
-rw-r--r--@ 1 suyajun  staff  1369 Dec  5 16:01 go.mod
-rw-r--r--@ 1 suyajun  staff  7028 Dec  5 16:01 go.sum
-rw-r--r--@ 1 suyajun  staff   361 Dec  5 18:03 main.go

suyajun@MacBook-Pro-2 abc % cat Dockerfile
###################1.构建阶段###################
# 使用 Golang 1.23.4 镜像作为构建环境,这里的go版本要和本地开发环境的版本号一致
FROM uhub.service.ucloud.cn/librarys/golang:1.23.4 AS build
# 构建阶段的工作目录
WORKDIR /go/src/test/
# 将当前目录下的所有文件复制到 Docker 容器的 /go/src/test/ 目录中
COPY . /go/src/test/
# 设置 Go 的代理,使用国内的 goproxy.cn 以提高下载速度
RUN go env -w GOPROXY=https://goproxy.cn,direct
# 使用 GOARCH=amd64 和 GOOS=linux 标志编译 Go 应用为 Linux 下的可执行文件。
# CGO_ENABLED=0 禁用 CGO(C 语言调用 Go 语言),确保静态链接。
# go build -v -o main . 会在工作目录中生成名为 main 的二进制文件。
RUN CGO_ENABLE=0 GOARCH=amd64 GOOS=linux go build -v -o main .


###################2.运行阶段###################
# 使用 Ubuntu 最新版本的镜像作为运行时环境
FROM uhub.service.ucloud.cn/librarys/ubuntu:latest AS api
# 在容器内创建一个 /app 目录,用于存放构建好的应用
RUN mkdir /app
# 将从构建阶段复制过来的 main 文件复制到容器内的 /app 目录
COPY --from=build /go/src/test/main /app
# 设置容器的工作目录为 /app,之后所有命令将在该目录下执行
WORKDIR /app

# 定义容器启动时的默认执行命令:执行 main 程序并传入参数 "-v" 和 "1.0"
ENTRYPOINT ["./main", "-v", "1.0"]
2.3.2 制作镜像测试程序是否能正常运行
docker build -t abc:v1.0 . && docker run -p 8080:8080 abc:v1.0

[+] Building 42.3s (15/15) FINISHED                                                                                                          docker:desktop-linux
 => [internal] load build definition from Dockerfile                                                                                                         0.0s
 => => transferring dockerfile: 1.45kB                                                                                                                       0.0s
 => [internal] load metadata for uhub.service.ucloud.cn/librarys/ubuntu:latest                                                                               0.4s
 => [internal] load metadata for uhub.service.ucloud.cn/librarys/golang:1.23.4                                                                               0.4s
 => [internal] load .dockerignore                                                                                                                            0.0s
 => => transferring context: 2B                                                                                                                              0.0s
 => [build 1/5] FROM uhub.service.ucloud.cn/librarys/golang:1.23.4@sha256:0ace92a91fb174f5ec759b39ce66ba365237b1b5ee8b35ff311e46659f05ef68                   0.0s
 => [api 1/4] FROM uhub.service.ucloud.cn/librarys/ubuntu:latest@sha256:6e75a10070b0fcb0bead763c5118a369bc7cc30dfc1b0749c491bbb21f15c3c7                     0.0s
 => [internal] load build context                                                                                                                            0.1s
 => => transferring context: 614B                                                                                                                            0.1s
 => CACHED [build 2/5] WORKDIR /go/src/test/                                                                                                                 0.0s
 => [build 3/5] COPY . /go/src/test/                                                                                                                         0.0s
 => [build 4/5] RUN go env -w GOPROXY=https://goproxy.cn,direct                                                                                              0.2s
 => [build 5/5] RUN CGO_ENABLE=0 GOARCH=amd64 GOOS=linux go build -v -o main .                                                                              41.4s
 => CACHED [api 2/4] RUN mkdir /app                                                                                                                          0.0s 
 => CACHED [api 3/4] COPY --from=build /go/src/test/main /app                                                                                                0.0s 
 => CACHED [api 4/4] WORKDIR /app                                                                                                                            0.0s 
 => exporting to image                                                                                                                                       0.0s 
 => => exporting layers                                                                                                                                      0.0s 
 => => writing image sha256:ccab35f1327e70fdb24e66d5bcf28380bce2278336690be3ad9c6b907b0f41dd                                                                 0.0s 
 => => naming to docker.io/library/abc:v1.0                                                                                                                  0.0s
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /                         --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8080

可以看到服务已经起来了
2.3.3 制作v1.0版本的镜像并推送到hub仓库
  • **v1.0 的 dockerfile,v1.0和v1.1的Dockerfile的区别是: **
    v1.0: ENTRYPOINT ["./main", "-v", "1.0"]
    v1.1: ENTRYPOINT ["./main", "-v", "1.1"]
suyajun@MacBook-Pro-2 abc % ls -l
total 40
-rw-r--r--@ 1 suyajun  staff  1372 Dec  5 18:59 Dockerfile
-rw-r--r--@ 1 suyajun  staff  1369 Dec  5 16:01 go.mod
-rw-r--r--@ 1 suyajun  staff  7028 Dec  5 16:01 go.sum
-rw-r--r--@ 1 suyajun  staff   361 Dec  5 18:03 main.go

suyajun@MacBook-Pro-2 abc % cat Dockerfile
###################1.构建阶段###################
# 使用 Golang 1.23.4 镜像作为构建环境,这里的go版本要和本地开发环境的版本号一致
FROM uhub.service.ucloud.cn/librarys/golang:1.23.4 AS build
# 构建阶段的工作目录
WORKDIR /go/src/test/
# 将当前目录下的所有文件复制到 Docker 容器的 /go/src/test/ 目录中
COPY . /go/src/test/
# 设置 Go 的代理,使用国内的 goproxy.cn 以提高下载速度
RUN go env -w GOPROXY=https://goproxy.cn,direct
# 使用 GOARCH=amd64 和 GOOS=linux 标志编译 Go 应用为 Linux 下的可执行文件。
# CGO_ENABLED=0 禁用 CGO(C 语言调用 Go 语言),确保静态链接。
# go build -v -o main . 会在工作目录中生成名为 main 的二进制文件。
RUN CGO_ENABLE=0 GOARCH=amd64 GOOS=linux go build -v -o main .


###################2.运行阶段###################
# 使用 Ubuntu 最新版本的镜像作为运行时环境
FROM uhub.service.ucloud.cn/librarys/ubuntu:latest AS api
# 在容器内创建一个 /app 目录,用于存放构建好的应用
RUN mkdir /app
# 将从构建阶段复制过来的 main 文件复制到容器内的 /app 目录
COPY --from=build /go/src/test/main /app
# 设置容器的工作目录为 /app,之后所有命令将在该目录下执行
WORKDIR /app

# 定义容器启动时的默认执行命令:执行 main 程序并传入参数 "-v" 和 "1.0"
ENTRYPOINT ["./main", "-v", "1.0"]
  • 制作v1.0版本的镜像并推送到仓库
docker build -t uhub.service.ucloud.cn/librarys/abc:v1.0 .
docker push uhub.service.ucloud.cn/librarys/abc:v1.0
2.3.4 制作v1.1版本的镜像并推送到hub仓库
  • **v1.0 的 dockerfile,v1.0和v1.1的Dockerfile的区别是: **
    v1.0: ENTRYPOINT ["./main", "-v", "1.0"]
    v1.1: ENTRYPOINT ["./main", "-v", "1.1"]
suyajun@MacBook-Pro-2 abc % ls -l
total 40
-rw-r--r--@ 1 suyajun  staff  1372 Dec  5 18:59 Dockerfile
-rw-r--r--@ 1 suyajun  staff  1369 Dec  5 16:01 go.mod
-rw-r--r--@ 1 suyajun  staff  7028 Dec  5 16:01 go.sum
-rw-r--r--@ 1 suyajun  staff   361 Dec  5 18:03 main.go

suyajun@MacBook-Pro-2 abc % cat Dockerfile
###################1.构建阶段###################
# 使用 Golang 1.23.4 镜像作为构建环境,这里的go版本要和本地开发环境的版本号一致
FROM uhub.service.ucloud.cn/librarys/golang:1.23.4 AS build
# 构建阶段的工作目录
WORKDIR /go/src/test/
# 将当前目录下的所有文件复制到 Docker 容器的 /go/src/test/ 目录中
COPY . /go/src/test/
# 设置 Go 的代理,使用国内的 goproxy.cn 以提高下载速度
RUN go env -w GOPROXY=https://goproxy.cn,direct
# 使用 GOARCH=amd64 和 GOOS=linux 标志编译 Go 应用为 Linux 下的可执行文件。
# CGO_ENABLED=0 禁用 CGO(C 语言调用 Go 语言),确保静态链接。
# go build -v -o main . 会在工作目录中生成名为 main 的二进制文件。
RUN CGO_ENABLE=0 GOARCH=amd64 GOOS=linux go build -v -o main .


###################2.运行阶段###################
# 使用 Ubuntu 最新版本的镜像作为运行时环境
FROM uhub.service.ucloud.cn/librarys/ubuntu:latest AS api
# 在容器内创建一个 /app 目录,用于存放构建好的应用
RUN mkdir /app
# 将从构建阶段复制过来的 main 文件复制到容器内的 /app 目录
COPY --from=build /go/src/test/main /app
# 设置容器的工作目录为 /app,之后所有命令将在该目录下执行
WORKDIR /app

# 定义容器启动时的默认执行命令:执行 main 程序并传入参数 "-v" 和 "1.0"
ENTRYPOINT ["./main", "-v", "1.1"]
  • 制作v1.1版本的镜像并推送到仓库
docker build -t uhub.service.ucloud.cn/librarys/abc:v1.1 .
docker push uhub.service.ucloud.cn/librarys/abc:v1.1

2.4 进行蓝绿发布

2.4.1 将abc发布到k8s集群中
cat abc-pod-v1.0.yaml
apiVersion: v1
kind: Pod
metadata:
  name: abc-v1-0
  labels:
    app: abc
    version: v1.0
spec:
  containers:
    - name: abc
      image: uhub.service.ucloud.cn/librarys/abc:v1.0

cat abc-pod-v1.1.yaml
apiVersion: v1
kind: Pod
metadata:
  name: abc-v1-1
  labels:
    app: abc
    version: v1.1
spec:
  containers:
    - name: abc
      image: uhub.service.ucloud.cn/librarys/abc:v1.1

cat abc-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: abc
spec:
  selector:
    app: abc
    version: v1.0
  ports:
    - name: http
      port: 8080
      targetPort: 8080
      nodePort: 31080
  type: NodePort

# 查看资源清单文件
[root@k8s-master abc]# ls -l
total 12
-rw-r--r--. 1 root root 182 Dec  6 09:52 abc-pod-v1.0.yaml
-rw-r--r--. 1 root root 182 Dec  6 09:52 abc-pod-v1.1.yaml
-rw-r--r--. 1 root root 204 Dec  6 09:52 abc-service.yaml

# 创建资源
[root@k8s-master abc]# kubectl apply -f .
pod/abc-v1-0 created
pod/abc-v1-1 created
service/abc created

# 查看创建的资源
[root@k8s-master abc]# kubectl get all|grep abc
pod/abc-v1-0   1/1     Running   0          3s
pod/abc-v1-1   1/1     Running   0          3s
service/abc             NodePort       10.111.158.99    <none>        8080:31080/TCP   3s

# 查看pod标签
[root@k8s-master abc]# kubectl get pod --show-labels
NAME       READY   STATUS    RESTARTS   AGE     LABELS
abc-v1-0   1/1     Running   0          5m13s   app=abc,version=v1.0
abc-v1-1   1/1     Running   0          5m13s   app=abc,version=v1.1

# 可以看到service中Selector选中的是v1.0
[root@k8s-master abc]# kubectl describe svc abc
Name:                     abc
Namespace:                default
Labels:                   <none>
Annotations:              <none>
Selector:                 app=abc,version=v1.0
Type:                     NodePort
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       10.111.158.99
IPs:                      10.111.158.99
Port:                     http  8080/TCP
TargetPort:               8080/TCP
NodePort:                 http  31080/TCP
Endpoints:                10.244.85.202:8080
Session Affinity:         None
External Traffic Policy:  Cluster
2.4.2 切换流量到v1.1
  • 模拟用户访问
while sleep 1;do curl  localhost:31080;echo ;done
  • 修改abc-service.yaml中的version 为v1.1
[root@k8s-master abc]# cat abc-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: abc
spec:
  selector:
    app: abc
    version: v1.1
  ports:
    - name: http
      port: 8080
      targetPort: 8080
      nodePort: 31080
  type: NodePort

kubectl apply -f abc-service.yaml
  • 验证查看
[root@k8s-master abc]# kubectl describe svc abc|grep Selector
Selector:                 app=abc,version=v1.1

注意:如果是通过浏览器验证可能客户端会有缓存不会立刻生效。用苹果的 safari浏览器 10秒左右会生效
image

发版的时候如何实现:

1、如果service最开始的时候是指向v1.0
2、发布的时候先发布v1.1
3、service的标签指向v1.1
4、观察有问题将service的标签指向v1.0做回退,观察没问题 删除v1.0的pod

如果有平台的发布可以实现灰度发布(这种的需要单独的运维平台)
1、发布的时候通过页面选择其中两个节点进行发布,发布完的不允许再点发布。
2、下线nacos
3、从apisix中将服务摘除
4、删除pod
5、apply 下线的两个节点

在这个基础上,也可以实现金丝雀的发布。比如我们有10个节点(通过更新pod的比例),我们只发布其中4个节点,然后会有40%的流量到新发布的服务上面,60%的流量还在旧的服务上。

posted @ 2024-12-04 20:49  Hello_worlds  阅读(15)  评论(0编辑  收藏  举报