Kubernetes API Server对象修改的乐观锁控制

本文探讨了Kubernetes API Server如何通过乐观锁防止并发写覆盖问题。详细介绍了`oc edit`行为、HTTP PUT API的乐观锁控制,并通过实例展示了在HTTP PATCH请求中乐观锁的工作方式。结论指出,HTTP PUT和带有resourceVersion的PATCH请求会进行乐观锁控制,而未带resourceVersion的PATCH请求可能引发并发修改覆盖。

Kubernetes API Server对象修改的乐观锁控制

一 背景

项目需要在OpenShift管理的Prometheus的基础上包装通过Web界面告警规则管理的功能,OpenShift 3.11版所内置的Prometheus支持通过一种叫做PrometheusRule的Custom Resource(CR)的对象管理来管理告警规则。业务上存在多人管理同个CR的可能性,所以需要防止对同个CR进行并发竞争修改,造成结果错误。

Kubernetes API Server是支持乐观锁(Optimistic concurrency control)的机制来防止并发写造成的覆盖写问题,详见此文章。通过给资源对象赋予版本号,并且API Server在更新时检查用户上传的对象中的metadata.resourceVersion来核对是否此次修改已经过时来保证修改的正确性。

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  labels:
    prometheus: k8s
    role: alert-rules
  name: arule
  namespace: openshift-monitoring
  resourceVersion: "2218687"
  ...
spec:
 .....

客户端获取到一个带resourceVersion字段的对象后进行修改,然后上传修改时必须同时将resourceVersion字段送回,这样API Server就会自行防止并发更新错误。

二 oc edit的行为

打开两个terminal A和B,都同时运行oc edit prometheusrule arule,在Terminal A进行一个改动,比如将下面的spec里面的user改为otherone,然后保存修改。

# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  resourceVersion: "2322087"
spec:
  groups:
  - name: general.rules
    rules:
    - alert: TargetDown-serviceprom
     ........
      for: 33m
      labels:
        severity: warning
        user: someone    ----> 改为otherone。


在Terminal A执行oc get prometheusrule arule的话,可以看到修改生效。

然后在Terminal B在同样的对象上对for做修改后保存,最后再把对象读回来可以发现在Terminal A对user的修改丢失了。

# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  resourceVersion: "2322087"
spec:
  groups:
  - name: general.rules
    rules:
    - alert: TargetDown-serviceprom
     ........
      for: 33m
      labels:
        severity: warning
        user: someone    ----> 改为otherone。

三 HTTP PUT API的乐观锁

在OpenShift集群中的有访问Custom Resource对象权限的容器里,先用curl将对象下载下来保存成文件rule1.json和rule2.json, 分别对rule1做上一节Terminal A的修改,对rule2.json做Terminal B的修改,

> curl -k -X GET   -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"   https://$KUBERNETES_PORT_443_TCP_ADDR:$KUBERNETES_SERVICE_PORT_HTTPS/apis/monitoring.coreos.com/v1/namespaces/openshift-monitoring/prometheusrules/arule > /tmp/rule1.json 

> curl -k -X GET   -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"   https://$KUBERNETES_PORT_443_TCP_ADDR:$KUBERNETES_SERVICE_PORT_HTTPS/apis/monitoring.coreos.com/v1/namespaces/openshift-monitoring/prometheusrules/arule > /tmp/rule2.json 

然后执行HTTP PUT对Custom Resource对象进行修改,

1> curl -k -XPUT -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"   https://$KUBERNETES_PORT_443_TCP_ADDR:$KUBERNETES_SERVICE_PORT_HTTPS/apis/monitoring.coreos.com/v1/namespaces/openshift-monitoring/prometheusrules/arule -T /tmp/rule1.json
#上面的命令成功执行
2> curl -k -XPUT -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"   https://$KUBERNETES_PORT_443_TCP_ADDR:$KUBERNETES_SERVICE_PORT_HTTPS/apis/monitoring.coreos.com/v1/namespaces/openshift-monitoring/prometheusrules/arule -T /tmp/rule2.json
#报错

第2条指令会报出以下错误:

{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {
    
  },
  "status": "Failure",
  "message": "Operation cannot be fulfilled on prometheusrules.monitoring.coreos.com \"arule\": the object has been modified; please apply your changes to the latest version and try again",
  "reason": "Conflict",
  "details": {
    "name": "arule",
    "group": "monitoring.coreos.com",
    "kind": "prometheusrules"
  },
  "code": 409
}

可以看出API Server对HTTP PUT进行了乐观锁控制。

四 讨论

为什么API Server对oc edit并没有进行乐观锁控制,产生了并发修改覆盖。打开oc客户端的最高9级日志oc --v=9 edit,可以看到oc edit使用的是HTTP PATCH指令,

I1108 17:52:19.592131   94141 request.go:897] Request Body: {"spec":{"groups":[{"name":"general.rules","rules":[{"alert":"TargetDown-serviceprom","annotations":{"description":"{{ $value }}% of {{ $labels.job }} targets are down.","summary":"Targets are down"},"expr":"100 * (count(up == 0) BY (job) / count(up) BY (job)) \u003e 10","for":"53m","labels":{"severity":"warning","user":"改为otherone"}}]}]}}
I1108 17:52:19.609156   94141 round_trippers.go:405] PATCH https://oc.exp.myhost.local:443/apis/monitoring.coreos.com/v1/namespaces/openshift-monitoring/prometheusrules/arule 200 OK in 16 milliseconds
I1108 17:52:19.609232   94141 round_trippers.go:411] Response Headers:
I1108 17:52:19.609241   94141 round_trippers.go:414]     Content-Length: 1463
I1108 17:52:19.609248   94141 round_trippers.go:414]     Date: Fri, 08 Nov 2019 09:52:19 GMT
I1108 17:52:19.609255   94141 round_trippers.go:414]     Cache-Control: no-store
I1108 17:52:19.609261   94141 round_trippers.go:414]     Content-Type: application/json

从第一行可以看出,oc edit在修改完成后往API Server发送请求时并没有带上metadata.resourceVersion所以没有办法进行并发版本控制。

尝试在HTTP PUT时不带上resourceVersion(在PUT文件中删除该字段), API Server返回422 Unprocessable Entity

$ curl -k -XPUT -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"   https://$KUBERNETES_PORT_443_TCP_ADDR:$KUBERNETES_SERVICE_PORT_HTTPS/apis/monitoring.coreos.com/v1/namespaces/openshift-monitoring/prometheusrules/arule -T /tmp/rule2.json
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {
    
  },
  "status": "Failure",
  "message": "prometheusrules.monitoring.coreos.com \"arule\" is invalid: metadata.resourceVersion: Invalid value: 0x0: must be specified for an update",
  "reason": "Invalid",
  "details": {
    "name": "arule",
    "group": "monitoring.coreos.com",
    "kind": "prometheusrules",
    "causes": [
      {
        "reason": "FieldValueInvalid",
        "message": "Invalid value: 0x0: must be specified for an update",
        "field": "metadata.resourceVersion"
      }
    ]
  },
  "code": 422
}

在HTTP Patch请求中带上resourceVersion,API Server同样会进行版本检查。

$ curl -k -XPATCH -H"Content-type:application/merge-patch+json"  -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"   https://$KUBERNETES_PORT_443_TCP_ADDR:$KUBERNETES_SERVICE_PORT_HTTPS/apis/monitoring.coreos.com/v1/namespaces/openshift-monitoring/prometheusrules/arule -T /tmp/rule2.json
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {
    
  },
  "status": "Failure",
  "message": "Operation cannot be fulfilled on prometheusrules.monitoring.coreos.com \"arule\": the object has been modified; please apply your changes to the latest version and try again",
  "reason": "Conflict",
  "details": {
    "name": "arule",
    "group": "monitoring.coreos.com",
    "kind": "prometheusrules"
  },
  "code": 409
}

而使用oc replace可以从日志观察到其使用HTTP PUT。

I1109 15:09:24.344543    7406 round_trippers.go:386] curl -k -v -XPUT  -H "Content-Type: application/json" -H "User-Agent: oc/v1.11.0+d4cacc0 (darwin/amd64) kubernetes/d4cacc0" -H "Accept: application/json" -H "Authorization: Bearer 7PooYteCfBygkyQIYUqN0bFJvUOlxBHtQ_BsobbS

五 结论

  • Kubernetes API Server要求HTTP PUT必须带上metadata.resourceVersion, 并且根据resourceVersion对PUT进行乐观锁控制,防止并发修改覆盖问题;
  • Kubernetes API Server对带上metadata.resourceVersion的HTTP PATCH请求会进行与PUT相同的版本控制逻辑,而不带resourceVersion的PATCH请求不进行乐观锁控制,可能会导致并发修改覆盖问题。
  • oc edit命令使用的是HTTP PATCH方法,而oc replace命令使用的是HTTP PUT。
  • 如果需要更新乐观锁,无论使用PATCH或是PUT,请求里面必须带上正确的metadata.resourceVersion。
<think>好的,我现在需要回答用户关于client-go如何与Kubernetes API Server进行交互,以及如何使用client-go对Pod、Deployment、Service进行增删改查的问题。首先,我得回忆一下client-go的基本结构和组件。 用户提到的参考资料里提到了client-go的四种客户端:RESTClient、ClientSet、DynamicClient和DiscoveryClient。我应该分别解释这些客户端的作用,然后给出具体的操作示例。比如,ClientSet是使用最广泛的,它提供了类型化的接口,适合操作已知资源。而DynamicClient则适用于处理CRD或者未知资源。 接下来,我需要确保每个操作示例都符合用户的要求,比如使用Go代码示例,并且说明如何初始化客户端。要注意代码的准确性,比如使用正确的包路径和方法。例如,创建Deployment可能需要用到appsv1包中的Deployment结构体,然后通过ClientSet的AppsV1().Deployments(namespace).Create()方法。 同时,用户可能想知道不同客户端之间的区别,比如RESTClient更底层,需要手动处理请求和响应,而ClientSet更高级,封装了具体的资源操作。这部分需要简要说明,帮助用户理解选择不同客户端的场景。 另外,要注意引用用户提供的参考资料,比如在解释client-go的重要性时,引用[^4],说明学习Operator需要先掌握client-go。在提到通过代理暴露API时,引用[^3]中的方法。 最后,生成的相关问题需要围绕client-go的深入使用、版本兼容性、性能优化和应用场景,这些都是用户可能进一步感兴趣的方面。确保问题具有针对性,能够引导用户进一步探索。 现在需要组织这些信息,按照用户的要求分步骤解释,并附上代码示例。同时检查LaTeX格式是否正确,比如行内公式用$...$,独立公式用$$...$$,但在这个问题中可能用不到数学公式,所以主要关注代码块和引用部分。 总结步骤: 1. 介绍client-go的四种客户端类型及其用途。 2. 分别说明如何初始化每种客户端(以ClientSet为例)。 3. 提供Pod、Deployment、Service的增删改查代码示例。 4. 对比不同客户端的适用场景。 5. 引用相关参考资料。 6. 生成相关问题。 需要确保代码示例正确,比如使用正确的函数和参数,比如namespace应该替换为实际的命名空间,错误处理需要简要提及。另外,注意代码块的格式是否正确,使用反引号包裹,并注明语言为go。</think>Client-go 是 Kubernetes 官方提供的 Go 语言客户端库,用于与 Kubernetes API Server 交互。它封装了 RESTful 资源操作,支持对 Pod、Deployment、Service 等资源的增删改查。以下是具体使用方法及功能说明: --- ### 一、Client-go 的核心客户端类型 Client-go 提供了四种客户端用于不同场景[^2]: 1. **RESTClient**:最底层客户端,需手动处理请求和响应。 2. **ClientSet**:类型化客户端,适用于已知资源(如 Pod、Deployment)。 3. **DynamicClient**:动态客户端,处理非结构化数据(如 CRD)。 4. **DiscoveryClient**:用于发现 API Server 支持的资源列表。 --- ### 二、ClientSet 操作示例(常用) #### 1. 初始化客户端 ```go import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" ) // 加载 kubeconfig 配置 config, _ := clientcmd.BuildConfigFromFlags("", "/path/to/kubeconfig") clientset, _ := kubernetes.NewForConfig(config) ``` #### 2. Pod 操作 ```go // 创建 Pod pod := &v1.Pod{...} // 定义 Pod 对象 createdPod, _ := clientset.CoreV1().Pods("default").Create(context.TODO(), pod, metav1.CreateOptions{}) // 查询 Pod pod, _ := clientset.CoreV1().Pods("default").Get(context.TODO(), "my-pod", metav1.GetOptions{}) // 删除 Pod clientset.CoreV1().Pods("default").Delete(context.TODO(), "my-pod", metav1.DeleteOptions{}) ``` #### 3. Deployment 操作 ```go import appsv1 "k8s.io/api/apps/v1" // 创建 Deployment deploy := &appsv1.Deployment{...} clientset.AppsV1().Deployments("default").Create(context.TODO(), deploy, metav1.CreateOptions{}) // 更新副本数 deploy.Spec.Replicas = 3 clientset.AppsV1().Deployments("default").Update(context.TODO(), deploy, metav1.UpdateOptions{}) ``` #### 4. Service 操作 ```go // 创建 Service svc := &v1.Service{...} clientset.CoreV1().Services("default").Create(context.TODO(), svc, metav1.CreateOptions{}) ``` --- ### 三、RESTClient 底层操作示例 适用于需要直接控制 HTTP 请求的场景: ```go import ( "k8s.io/client-go/rest" "k8s.io/apimachinery/pkg/apis/meta/v1" ) config := &rest.Config{Host: "https://api-server:6443", BearerToken: "..."} restClient, _ := rest.RESTClientFor(config) // 查询 Pod 列表 var podList v1.PodList restClient.Get(). Namespace("default"). Resource("pods"). Do(context.TODO()). Into(&podList) ``` --- ### 四、DynamicClient 动态操作 适用于处理自定义资源(CRD)或未知类型资源: ```go import ( "k8s.io/client-go/dynamic" "k8s.io/apimachinery/pkg/runtime/schema" ) gvr := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"} dynamicClient, _ := dynamic.NewForConfig(config) // 查询 Deployment unstructuredDeploy, _ := dynamicClient.Resource(gvr).Namespace("default").Get(context.TODO(), "my-deploy", metav1.GetOptions{}) ``` --- ### 五、关键功能封装 1. **认证与连接管理**:自动处理 TLS 证书、Token 认证[^1]。 2. **资源版本控制**:通过 `ResourceVersion` 实现乐观锁。 3. **Watch 机制**:监听资源变化(如 `clientset.CoreV1().Pods("").Watch(context.TODO(), metav1.ListOptions{})`)。 4. **Informer/Lister 缓存**:减少 API Server 压力,提高查询效率。 --- ### 六、适用场景对比 | 客户端类型 | 适用场景 | |----------------|----------------------------------| | ClientSet | 标准资源操作(如 Pod、Service) | | DynamicClient | 动态资源(如 CRD) | | RESTClient | 需要自定义 HTTP 逻辑 | | DiscoveryClient| 探索 API Server 支持的资源 | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值