什么是subresource status
status
是k8s
中常见的一种子资源。
/status
子资源用于更新和获取主资源的状态部- 通常由控制器或操作系统更新,以反映系统中对象的当前状态
- 通过将状态更新与主资源的其他修改隔离开来,可以防止用户意外覆盖状态信息
subresource status的作用与限制
- 在Kubernetes API中,在使用PUT或POST动词对Kubernetes对象进行操作时,必须忽略对象的status字段。这是为了防止在读取-修改-写入(read-modify-write)的场景中意外地覆盖status字段。PUT方法不能更新很多理解,防止意外覆盖status,使用POST也不能写入status字段(如果status没有定义为subresource POST和PUT方法是可以写入的),应该是和status的作用有关,一般由控制器来更新,所以在POST方法中也不能设置status。
- 通过subresource这一概念可以将对象的spec与status分开,设置不同的访问权限。
- 必须提供单独的方法来对
status
进行修改
如何定义一个subresource
在结构体上加上//+kubebuilder:subresource:status
就可以将status
定义为subresource
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +genclient
// +kubebuilder:subresource:status
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// Foo is a specification for a Foo resource
type Foo struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec FooSpec `json:"spec"`
Status FooStatus `json:"status"`
}
// FooSpec is the spec for a Foo resource
type FooSpec struct {
DeploymentName string `json:"deploymentName"`
Replicas *int32 `json:"replicas"`
}
// FooStatus is the status for a Foo resource
type FooStatus struct {
AvailableReplicas int32 `json:"availableReplicas"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// FooList is a list of Foo resources
type FooList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []Foo `json:"items"`
}
crd server
在k8s
中crd
这类资源的创建走的接口是和原生资源不一样的,crd
资源调用的接口是apis/<group>/<version/<cr-name>
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 // vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go
// 创建crdHandler
crdHandler, err := NewCustomResourceDefinitionHandler(
versionDiscoveryHandler,
groupDiscoveryHandler,
s.Informers.Apiextensions().V1().CustomResourceDefinitions(),
delegateHandler,
c.ExtraConfig.CRDRESTOptionsGetter,
c.GenericConfig.AdmissionControl,
establishingController,
c.ExtraConfig.ServiceResolver,
c.ExtraConfig.AuthResolverWrapper,
c.ExtraConfig.MasterCount,
s.GenericAPIServer.Authorizer,
c.GenericConfig.RequestTimeout,
time.Duration(c.GenericConfig.MinRequestTimeout)*time.Second,
apiGroupInfo.StaticOpenAPISpec,
c.GenericConfig.MaxRequestBodyBytes,
)
if err != nil {
return nil, err
}
// url为/apis或者以/apis/为前缀的请求都会调用crdhandler
s.GenericAPIServer.Handler.NonGoRestfulMux.Handle("/apis", crdHandler)
s.GenericAPIServer.Handler.NonGoRestfulMux.HandlePrefix("/apis/", crdHandler)
1 | crdHandler实现了自己的ServeHTTP方法 |
1 | // vendor/k8s.io/apiserver/pkg/endpoints/handlers/create.go |
subresource status是怎么忽略的
1 | 在上面的`Create`方法中会调用, `rest.BeforeCreat`这个方法会执行`customResource`的策略, 最终会调用`func (a customResourceStrategy) PrepareForCreate`。 |
status更新
如果是subresource
,code-gen
生成代码会为status生成UpdateStatus
方法,可以使用这个方法对status进行更新操作。
对status的更新也会增加对象的ResourceVersion
,并产生更新事件。
kubectl edit修改status
如果你使用kubectl edit
命令对status进行修改发现根本不了。使用kubectl edit
修改crd
调用的接口是apis/<group>/<version/<cr-name>
,http方法是PATCH
。假如把status中的availableReplicas更新为2,则patchType ="application/merge-patch+json"
patchBytes = {"status":{"availableReplicas":2}}
1 | // vendor/k8s.io/apiserver/pkg/registry/generic/registry/store.go |
updateStatus
PUT /apis/<group>/<version>/<resources>/<name>/status
, verb=update
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39// vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go
func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
...
switch {
case subresource == "status" && subresources != nil && subresources.Status != nil:
// 进入r.serveStatus
handlerFunc = r.serveStatus(w, req, requestInfo, crdInfo, terminating, supportedTypes)
case subresource == "scale" && subresources != nil && subresources.Scale != nil:
handlerFunc = r.serveScale(w, req, requestInfo, crdInfo, terminating, supportedTypes)
case len(subresource) == 0:
handlerFunc = r.serveResource(w, req, requestInfo, crdInfo, crd, terminating, supportedTypes)
default:
responsewriters.ErrorNegotiated(
apierrors.NewNotFound(schema.GroupResource{Group: requestInfo.APIGroup, Resource: requestInfo.Resource}, requestInfo.Name),
Codecs, schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}, w, req,
)
}
...
}
func (r *crdHandler) serveStatus(w http.ResponseWriter, req *http.Request, requestInfo *apirequest.RequestInfo, crdInfo *crdInfo, terminating bool, supportedTypes []string) http.HandlerFunc {
requestScope := crdInfo.statusRequestScopes[requestInfo.APIVersion]
storage := crdInfo.storages[requestInfo.APIVersion].Status
switch requestInfo.Verb {
case "get":
return handlers.GetResource(storage, requestScope)
case "update":
return handlers.UpdateResource(storage, requestScope, r.admission)
case "patch":
return handlers.PatchResource(storage, requestScope, r.admission, supportedTypes)
default:
responsewriters.ErrorNegotiated(
apierrors.NewMethodNotSupported(schema.GroupResource{Group: requestInfo.APIGroup, Resource: requestInfo.Resource}, requestInfo.Verb),
Codecs, schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}, w, req,
)
return nil
}
}
1 | // vendor/k8s.io/apiserver/pkg/registry/generic/registry/store.go |