We all know that Kubernetes resources can be represented in YAML and JSON, and additions and updates to resources can be understood as operations on YAML or JSON documents. Look at the source code can know that Kubernetes bottom of the operation of resources are using JSON to express, because of the complexity of Kubernetes resources and a large number of, it is inevitable to encounter the operation of complex JSON objects. The following will first introduce two schemes for JSON update, and then understand the principle and usage of JSON Patch in combination with the JSON operations involved in the Webhook mechanism in Kubernetes.

Two JSON fixes included in the IETF

There are many schemes to change JSON format files, but only two JSON Patch and JSON Merge Patch are officially included by IEFT.

  • RFC 6902 (JSON Patch)
  • RFC 7396 (JSON Merge Patch)

These scenarios declaratively describe the differences between two JSON documents. Since the official release of two plans for the modification, it is certain that both have advantages and disadvantages, and no one plan is suitable for all cases.

JSON Patch

JSON Patch defines a JSON document structure that is used to express a series of operations applied to JavaScript Object Notation (JSON) documents. It is applicable to THE HTTP PATCH method, which is equivalent to extending the HTTP protocol. For example, meme. Type of “Application/json-patch +JSON” is used to identify such PATCH documents, and the parameter structure defined by it contains methods to perform partial modifications to resources.

For example, modify the following JSON document:

{
	"users": [{"name" : "Alice" , "email" : "[email protected]" },
		{ "name" : "Bob" , "email" : "[email protected]"}}]Copy the code

The Patch document is:

[{"op" : "replace" ,
		"path" : "/users/0/email" ,
		"value" : "[email protected]"
	},
	{
		"op" : "add" ,
		"path" : "/users/-" ,
		"value" : {
			"name" : "Christine"."email" : "[email protected]"}}]Copy the code

The result is:

{
	"users" : [
		{ "name" : "Alice" , "email" : "[email protected]" },
		{ "name" : "Bob" , "email" : "[email protected]" },
		{ "name" : "Christine" , "email" : "[email protected]" }
	]
}
Copy the code

You can see that we have replaced and added data. Among them:

  • Op indicates operations, including test, remove, add, replace, move, and copy.
  • “Path” indicates that the operation points to the target of the JSON document
  • Value indicates the value of the operation

There is no order requirement for the three types of JSON Object definitions. Examples of “add” and “replace” operations have been shown above. “Remove” is equivalent to remove. “Move” is equivalent to removing a JSON Object from one location and adding a JSON Object from another, except that the operation is atomic. A similar operation to “move” is “copy”, which is equivalent to “add”, but the added JSON Object is from the JSON Object in the specified path in the source file.

One particular one is “test”, which is simply “equal”, a comparison value, or a JSON Object.

{ 
	"op": "test"."path": "/a/b/c"."value": "foo" 
}
Copy the code

As shown above, the operation succeeds if the value under path “/a/b/c” in the document is equal to “foo”.

Error control

The specification defines that if a JSON Patch operation violates the specification requirements or is unsuccessful, the operation on the JSON document will be terminated and the operation will be deemed unsuccessful. For details, see the status code of the HTTP patch operation.

atomic

According to the definition of HTTP patch atomicity, atomicity means that the following operations will not change the source document because “test” will fail, and the entire operation with the “replace” operation will also be rolled back.

[{"op": "replace"."path": "/a/b/c"."value": 42 },
     { "op": "test"."path": "/a/b/c"."value": "C"}]Copy the code

JSON Merge Patch

JSON Merge Patch is similar to JSON Patch, but it describes the changed version of the JSON document. Compared to the previous approach, this approach is more like a differential file in Git, whereas JSON PATH is more like an operational database. This method contains no operations, only the nodes of the document. For example, modify the following documents:

{
	"a": "b",
	"c": {
		"d": "e",
		"f": "g"
	}
}
Copy the code

Run the following Patch based on the above:

{
	"a":"z",
	"c": {
		"f": null
	}
}
Copy the code

This approach is easy to understand at first glance, but there are some potential limitations, such as

  • There is no way to set value to NULL, because the null operation means deletion in the merge patch scenario. Of course, if null means the resource does not exist in the program that is processing the JSON document, then this problem can indeed be avoided.
  • You cannot append an array because you must supply the entire array to represent the element changes
  • Once the Patch is wrong, such as the path is wrong, it will also be merged into the correct data, because merge Patch seems to be a very flexible data modification method, but in JSON Patch, if the path is wrong, the operation will fail.

To sum up, if faced with a scenario with simple structure and no strong verification requirements, JSON Merge Patch can be selected. However, in a more responsible scenario, JSON Patch is recommended, because this method ensures atomic execution and error reporting.

JSON patch in Kubernetes

Webhook is a reverse API, equivalent to the server calling the client back. In the cloud native scenario, the server refers to API-server, and the client refers to the specific Webhook server. For example, the Webhook server in ISIto is ISITOD. The Webhook server in chao-mesh is Chao-controller-Manager. These two examples are given because they are classic scenarios that use Webhook to do sidecar injection. Both have open ports of the type 443 to receive requests from the API-server.

Because sidecar essentially silently changes resource data committed to Kubernetes, JSON modifications are involved.

The Webhook in Kubernetes adopts the first scheme (JSON Patch) mentioned above. Exact location of the code in the staging/SRC/k8s. IO/apiserver/PKG/admission/plugins/webhook mutating/dispatcher. Go

func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *admissionregistrationv1.MutatingWebhook, invocation *generic.WebhookInvocation, attr *generic.VersionedAttributes, o admission.ObjectInterfaces, round, idx int) (bool, error){... patchObj, err := JSONpatch.DecodePatch(result.Patch)var patchedJS []byte
	JSONSerializer := JSON.NewSerializer(JSON.DefaultMetaFactory, o.GetObjectCreater(), o.GetObjectTyper(), false)
	switch result.PatchType {
	case admissionv1.PatchTypeJSONPatch:
		objJS, err := runtime.Encode(JSONSerializer, attr.VersionedObject)
		iferr ! =nil {
			return false, apierrors.NewInternalError(err)
		}
		patchedJS, err = patchObj.Apply(objJS)
		iferr ! =nil {
			return false, apierrors.NewInternalError(err)
		}
	default:
		return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("unsupported patch type %q", result.PatchType)}
	}
	
	...
	
	if newVersionedObject, _, err = JSONSerializer.Decode(patchedJS, nil, newVersionedObject); err ! =nil {
		return false, apierrors.NewInternalError(err)
	}

	...
	annotator.addPatchAnnotation(patchObj, result.PatchType)
	...
}
Copy the code

JSONpatch package in jsonpatch.decodePatch (result.patch) is an open source third-party library (github.com/evanphx/JSON-patch), which is a JSONpatch library. It provides the JSON Patch operation and Merge Patch operation.

If you look at the code of the library, you will see that the library has implementations for each of the operations mentioned above, as shown in the figure below:

Correspondingly, as webook servers, Istiod and Chaos-Controller-Manager both make processing logic for modifying object resource operations. Here, the typical scenario of modifying the annotation of POD is taken as an example.

istiod

func updateAnnotation(target map[string]string, added map[string]string) (patch []rfc6902PatchOperation) {
	var keys []string
	for k := range added {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	for _, key := range keys {
		value := added[key]
		if target == nil {
			target = map[string]string{}
			patch = append(patch, rfc6902PatchOperation{
				Op:   "add",
				Path: "/metadata/annotations",
				Value: map[string]string{
					key: value,
				},
			})
		} else {
			op := "add"
			iftarget[key] ! ="" {
				op = "replace"
			}
			patch = append(patch, rfc6902PatchOperation{
				Op:    op,
				Path:  "/metadata/annotations/" + escapeJSONPointerValue(key),
				Value: value,
			})
		}
	}
	return patch
}

func escapeJSONPointerValue(in string) string {
	step := strings.Replace(in, "~"."~ 0".- 1)
	return strings.Replace(step, "/"."~ 1".- 1)}Copy the code

chaos-controller-manager

func updateAnnotations(target map[string]string, added map[string]string) (patch []patchOperation) {
	for key, value := range added {
		if target == nil || target[key] == "" {
			target = map[string]string{}
			patch = append(patch, patchOperation{
				Op:   "add",
				Path: "/metadata/annotations",
				Value: map[string]string{
					key: value,
				},
			})
		} else {
			patch = append(patch, patchOperation{
				Op:    "replace",
				Path:  "/metadata/annotations/" + key,
				Value: value,
			})
		}
	}
	return patch
}
Copy the code

The code logic is relatively simple, and it can be seen that “add” and “replace” of JSONPatch are used to modify the annotation in POD. Relatively speaking, IStiOD has better processing for JSONPatch, and the ordering of keys can ensure the sequence of annotations. In tests, the CHAOS – Mesh logic overrides all the original pod annotations when there is no target[key] in the PodSpec.