zhangguanzhang's Blog

Internal error occurred: jsonpatch add operation does not apply: doc is missi...

字数统计: 750阅读时长: 3 min
2021/03/22

由来

今天在折腾 admission webhook 注入一些属性的时候遇到了 Error from server (InternalError): error when creating "xxx.yml": Internal error occurred: jsonpatch add operation does not apply: doc is missing path: "/spec/template/spec/dnsConfig/options"。折腾半天才发现在代码里使用 jsonPatch 的话不能直接绕过结构体实例去 patch。

排查过程

WebHook 接收和响应都是一个 AdmissionReview 对象,请求是里面的 AdmissionRequest,响应是里面的 AdmissionResponse。
比如说我们创建了个 MutatingWebhookConfiguration 指定让 apps/v1deploy 传到我们的 webhook, 我们要修改 deploy 的一些属性,我个人是增加 dns 的 single-request-reopen的属性的,得在 AdmissionResponse 里传一个 jsonPatch的切片。
代码里这块是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type patchOperation struct {
Op string `json:"op"`
Path string `json:"path"`
Value interface{} `json:"value,omitempty"`
}
...
patch := []patchOperation{
{
Op: "add",
Path: "/spec/template/spec/dnsConfig/options",
Value: []map[string]string{
{"name": "single-request-reopen"},
},
},

后面发现创建 deploy 就报开头的错误。然后日志里打印了下:

1
apiVersion:apps/v1 resource: default/nginx-deployment, AdmissionResponse: patch=[{"op":"replace","path":"/spec/template/spec/dnsConfig/options","value":[{"name":"single-request-reopen"}]}]

然后把这个 patch 在 kubectl 上测试了下是可以的:

1
2
3
$ kubectl -n kube-system patch deployments tiller-deploy --type=json -p='[{"op":"add","path":"/spec/template/spec/dnsConfig/options","value":[{"name":"single-request-reopen"}]}]'

deployment.extensions/tiller-deploy patched

刚开始把 kube-apiserver 开 -v=8 发现 panic的信息,然后升了下版本还是没用。然后在源码里找了下这个报错:

1
2
$ find -type f -name '*.go' -exec grep -l 'replace operation does not apply: doc is missing path' {} \;
./vendor/github.com/evanphx/json-patch/patch.go

发现这个错误是引入的库抛出来的,和 k8s 无关,想了下后换个 key 试试看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
			{
Op: "add",
Path: "/spec/template/spec/securityContext/runAsNonRoot",
Value: true,
},
...
for i := range deployment.Spec.Template.Spec.Containers {
patch = append(patch, patchOperation{
Op: "add",
Path: fmt.Sprintf("/spec/template/spec/containers/%d/lifecycle/postStart/exec/command", i),
Value: []string{
"/bin/sh",
"-c",
"/bin/echo 'options single-request-reopen' >> /etc/resolv.conf",
},
},
})
}

发现后面 command 这个也不行,explain 看了下,试试上层的看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for i := range deployment.Spec.Template.Spec.Containers {
patch = append(patch, patchOperation{
Op: "add",
Path: fmt.Sprintf("/spec/template/spec/containers/%d/lifecycle", i),
Value: corev1.Lifecycle{
PostStart: &corev1.Handler{
Exec: &corev1.ExecAction{
Command: []string{
"/bin/sh",
"-c",
"/bin/echo 'options single-request-reopen' >> /etc/resolv.conf",
},
},
},
},
})
}

发现可以,应该是得给一个嵌套的结构体实例,而不是直接绕过这个结构体,去 patch 里面属性的 value,换下前面的 dnsConfig 试试:

1
2
3
4
5
6
7
8
9
10
11
12
{
Op: "add",
Path: "/spec/template/spec/dnsConfig",
Value: corev1.PodDNSConfig{
Options: []corev1.PodDNSConfigOption{
{
Name: "single-request-reopen",
Value: nil,
},
},
},
},

发现也可以了。

参考

关于我提的 issue

CATALOG
  1. 1. 由来
    1. 1.1. 排查过程
  2. 2. 参考