client-go实战之八:更新资源时的冲突错误处理

打印 上一主题 下一主题

主题 931|帖子 931|积分 2793

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos
本篇概览


  • 本文是《client-go实战》系列的第七篇,来了解一个常见的错误:版本冲突,以及client-go官方推荐的处理方式
  • 本篇由以下部分组成

  • 什么是版本冲突(from kubernetes官方)
  • 编码,复现版本冲突
  • 版本冲突的解决思路(from kubernetes官方)
  • 版本冲突的实际解决手段(from client-go官方)
  • 编码,演示如何解决版本冲突
  • 自定义入参,对抗更高的并发
什么是版本冲突(from kubernetes官方)


  • 首先,在逻辑上来说,提交冲突是肯定存在的,多人同时获取到同一个资源的信息(例如同一个pod),然后各自在本地修改后提交,就有可能出现A的提交把B的提交覆盖的情况,这一个点就不展开了,数据库的乐观锁和悲观锁都可以用来处理并发冲突
  • kubernetes应对提交冲突的方式是资源版本号,属于乐观锁类型(Kubernetes leverages the concept of resource versions to achieve optimistic concurrency)
  • 基于版本实现并发控制是常见套路,放在kubernetes也是一样,基本原理如下图所示,按照序号看一遍即可理解:左右两人从后台拿到的资源都是1.0版本,然而右侧提交的1.1的时候,服务器上已经被左侧更新到1.1了,于是服务器不接受右侧提交

编码,复现版本冲突


  • 接下来,咱们将上述冲突用代码复现出来,具体的功能如下

  • 创建一个deployment资源,该资源带有一个label,名为biz-version,值为101
  • 启动5个协程,每个协程都做同样的事情:读取deployment,得到label的值后,加一,再提交保存
  • 正常情况下,label的值被累加了5次,那么最终的值应该等于101+5=106
  • 等5个协程都执行完毕后,再读读取一次deployment,看label值是都等于106

  1. $ tree client-go-tutorials
  2. client-go-tutorials
  3. ├── action
  4. │   ├── action.go
  5. │   ├── conflict.go
  6. │   └── list_pod.go
  7. ├── client-go-tutorials
  8. ├── go.mod
  9. ├── go.sum
  10. └── main.go
复制代码

  • 接下来的代码都写在conflict.go中
  • 首先是新增两个常量
  1. const (
  2.         // deployment的名称
  3.         DP_NAME string = "demo-deployment"
  4.         // 用于更新的标签的名字
  5.         LABEL_CUSTOMIZE string = "biz-version"
  6. )
复制代码

  • 然后是辅助方法,返回32位整型的指针,后面会用到
  1. func int32Ptr(i int32) *int32 { return &i }
复制代码

  • 创建deployment的方法,要注意的是增加了一个label,名为LABEL_CUSTOMIZE,其值为101
  1. // 创建deployment
  2. func create(clientset *kubernetes.Clientset) error {
  3.         deploymentsClient := clientset.AppsV1().Deployments(apiv1.NamespaceDefault)
  4.         deployment := &appsv1.Deployment{
  5.                 ObjectMeta: metav1.ObjectMeta{
  6.                         Name:   DP_NAME,
  7.                         Labels: map[string]string{LABEL_CUSTOMIZE: "101"},
  8.                 },
  9.                 Spec: appsv1.DeploymentSpec{
  10.                         Replicas: int32Ptr(1),
  11.                         Selector: &metav1.LabelSelector{
  12.                                 MatchLabels: map[string]string{
  13.                                         "app": "demo",
  14.                                 },
  15.                         },
  16.                         Template: apiv1.PodTemplateSpec{
  17.                                 ObjectMeta: metav1.ObjectMeta{
  18.                                         Labels: map[string]string{
  19.                                                 "app": "demo",
  20.                                         },
  21.                                 },
  22.                                 Spec: apiv1.PodSpec{
  23.                                         Containers: []apiv1.Container{
  24.                                                 {
  25.                                                         Name:  "web",
  26.                                                         Image: "nginx:1.12",
  27.                                                         Ports: []apiv1.ContainerPort{
  28.                                                                 {
  29.                                                                         Name:          "http",
  30.                                                                         Protocol:      apiv1.ProtocolTCP,
  31.                                                                         ContainerPort: 80,
  32.                                                                 },
  33.                                                         },
  34.                                                 },
  35.                                         },
  36.                                 },
  37.                         },
  38.                 },
  39.         }
  40.         // Create Deployment
  41.         fmt.Println("Creating deployment...")
  42.         result, err := deploymentsClient.Create(context.TODO(), deployment, metav1.CreateOptions{})
  43.         if err != nil {
  44.                 return err
  45.         }
  46.         fmt.Printf("Created deployment %q.\n", result.GetObjectMeta().GetName())
  47.         return nil
  48. }
复制代码

  • 按照名称删除deployment的方法,实战的最后会调用,将deployment清理掉
  1. // 按照名称删除
  2. func delete(clientset *kubernetes.Clientset, name string) error {
  3.         deletePolicy := metav1.DeletePropagationBackground
  4.         err := clientset.AppsV1().Deployments(apiv1.NamespaceDefault).Delete(context.TODO(), name, metav1.DeleteOptions{PropagationPolicy: &deletePolicy})
  5.         if err != nil {
  6.                 return err
  7.         }
  8.         return nil
  9. }
复制代码

  • 再封装一个get方法,用于所有更新操作完成后,获取最新的deployment,检查其label值是否符合预期
  1. // 按照名称查找deployment
  2. func get(clientset *kubernetes.Clientset, name string) (*v1.Deployment, error) {
  3.         deployment, err := clientset.AppsV1().Deployments(apiv1.NamespaceDefault).Get(context.TODO(), name, metav1.GetOptions{})
  4.         if err != nil {
  5.                 return nil, err
  6.         }
  7.         return deployment, nil
  8. }
复制代码

  • 接下来是最重要的更新方法,这里用的是常见的先查询再更新的方式,查询deployment,取得标签值之后加一再提交保存
  1. // 查询指定名称的deployment对象,得到其名为biz-version的label,加一后保存
  2. func updateByGetAndUpdate(clientset *kubernetes.Clientset, name string) error {
  3.         deployment, err := clientset.AppsV1().Deployments(apiv1.NamespaceDefault).Get(context.TODO(), name, metav1.GetOptions{})
  4.         if err != nil {
  5.                 return err
  6.         }
  7.         // 取出当前值
  8.         currentVal, ok := deployment.Labels[LABEL_CUSTOMIZE]
  9.         if !ok {
  10.                 return errors.New("未取得自定义标签")
  11.         }
  12.         // 将字符串类型转为int型
  13.         val, err := strconv.Atoi(currentVal)
  14.         if err != nil {
  15.                 fmt.Println("取得了无效的标签,重新赋初值")
  16.                 currentVal = "101"
  17.         }
  18.         // 将int型的label加一,再转为字符串
  19.         deployment.Labels[LABEL_CUSTOMIZE] = strconv.Itoa(val + 1)
  20.         _, err = clientset.AppsV1().Deployments(apiv1.NamespaceDefault).Update(context.TODO(), deployment, metav1.UpdateOptions{})
  21.         return err
  22. }
复制代码

  • 最后,是主流程代码,为了能在现有工程框架下运行,这里新增一个struct,并实现了action接口的DoAction方法,这个DoAction方法中就是主流程
[code]type Confilct struct{}func (conflict Confilct) DoAction(clientset *kubernetes.Clientset) error {        fmt.Println("开始创建deployment")        // 开始创建deployment        err := create(clientset)        if err != nil {                return err        }        // 如果不延时,就会导致下面的更新过早,会报错

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

我可以不吃啊

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表