开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天,点击查看活动详情
记得看代码中的注释哈,理解都在里面 源码基于v1.24
本篇文章着重介绍OpenApi的相关概念和在kubernetes中的运用,该规范用于规范RESTful风格的API描述方法,不仅适合人阅读,也方便程序处理
什么是OpenApi
通常所说的OpenAPI是指OpenAPI规范(OpenAPI Specification),简称OAS,该规范用于规范RESTful风格的API描述方法。
我们有很多种方法来描述一个web服务的API,比如使用word文件描述,但这样的API描述不够通用。OpenAPI规范定义了一种通用的接口描述方法,按照这个规范定义接口,不仅适合人阅读,也方便程序处理。
k8s的OpenApi在哪里?
所在文件:hack\update-openapi-spec.sh 这是生成Swagger文件的脚本
KUBE_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
OPENAPI_ROOT_DIR="${KUBE_ROOT}/api/openapi-spec"
source "${KUBE_ROOT}/hack/lib/init.sh"
在api/openapi-spec/swagger.json下
swagger.json 文件定义了 kubernetes 对外提供的 restful service,客户端可以 依照该规定来向 api server 发http 请求。 在这里,你可以看到绝大多数当前版本内建的API Object,并且每个外部版本+ APIObject组合拥有一套swagger中的一套定义。
swagger字段用于描述规范的版本,字段类型为string。
info字段用于描述API的基本信息,字段类型为Info Object。Info Object类型包含以下两个必须字段:
title:类型为string,表示应用的名称。
version:类型为string,表示应用的版本。
paths字段用于描述API的各个端点及支持的操作,字段类型为Paths Object。Paths Object类型又由Path Item Object类型构成
definitions字段用于定义一组被各个接口引用(消费或产生)的对象,类型为Definitions Object。
在这里的注释,声明了为某些GoStruct去生成OpenApi的代码
GoStruct定义在kubernetes/staging/src/k8s.io/api/apps/v1/types.go
type StatefulSet struct {
metav1.TypeMeta `json:",inline"`
// Standard object's metadata.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
// +optional
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// Spec defines the desired identities of pods in this set.
// +optional
Spec StatefulSetSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
// Status is the current status of Pods in this StatefulSet. This data
// may be out of date by some window of time.
// +optional
Status StatefulSetStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}
生成在kubenetes\pkg\generated\openapi\zz_generated.openapi.go里 很大的一个文件
-
这个文件中,你可以找到当前kubernetes版本中所有内建的apiobject的openapi definition,并且每个外部版本+APIObject的组合会有一个Defition。 这里的 key 和 swagger.json 中的 definitionid 有一对一映射关系,可以查看 kubernetes/staging/src/k8s.iolapiserver/pkg/endpoints/openapi/openapi.go 文件中的friendlyName函数
-
这些Generated code的作用:为每一个以字符串(就是上面那个key)标识的api object,生成其 openapi definition,definition的主体是一个json schema,该 schema定义了这个api object的json表示中可以有哪些属性,属性类型信息等等
在哪里配置调用的呢?
在server.go中
func CreateKubeAPIServerConfig(s completedServerRunOptions) (
*controlplane.Config,
aggregatorapiserver.ServiceResolver,
[]admission.PluginInitializer,
error,
) {
proxyTransport := CreateProxyTransport()
genericConfig, versionedInformers, serviceResolver, pluginInitializers, admissionPostStartHook, storageFactory, err := buildGenericConfig(s.ServerRunOptions, proxyTransport)
if err != nil {
return nil, nil, nil, err
}
...
func buildGenericConfig(
s *options.ServerRunOptions,
proxyTransport *http.Transport,
) (
genericConfig *genericapiserver.Config,
versionedInformers clientgoinformers.SharedInformerFactory,
serviceResolver aggregatorapiserver.ServiceResolver,
pluginInitializers []admission.PluginInitializer,
admissionPostStartHook genericapiserver.PostStartHookFunc,
storageFactory *serverstorage.DefaultStorageFactory,
lastErr error,
) {
genericConfig = genericapiserver.NewConfig(legacyscheme.Codecs)
genericConfig.MergedResourceConfig = controlplane.DefaultAPIResourceConfigSource()
...
/*
在这里对OpenApi进行了配置
*/
// wrap the definitions to revert any changes from disabled features
getOpenAPIDefinitions := openapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(generatedopenapi.GetOpenAPIDefinitions)
/*
交给了genericserver的Config 有了Definition
*/
genericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(getOpenAPIDefinitions, openapinamer.NewDefinitionNamer(legacyscheme.Scheme, extensionsapiserver.Scheme, aggregatorscheme.Scheme))
genericConfig.OpenAPIConfig.Info.Title = "Kubernetes"
if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.OpenAPIV3) {
genericConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config(getOpenAPIDefinitions, openapinamer.NewDefinitionNamer(legacyscheme.Scheme, extensionsapiserver.Scheme, aggregatorscheme.Scheme))
genericConfig.OpenAPIV3Config.Info.Title = "Kubernetes"
}
现在genericserver有了OpenApi的Definition,在genericserver的Run之前有个PrepareRun会进行OpenApi的配置
func (s *GenericAPIServer) PrepareRun() preparedGenericAPIServer {
s.delegationTarget.PrepareRun()
/*
如果是配置了OpenApi就会Install
*/
if s.openAPIConfig != nil && !s.skipOpenAPIInstallation {
s.OpenAPIVersionedService, s.StaticOpenAPISpec = routes.OpenAPI{
Config: s.openAPIConfig,
}.InstallV2(s.Handler.GoRestfulContainer, s.Handler.NonGoRestfulMux)
}
if s.openAPIV3Config != nil && !s.skipOpenAPIInstallation {
if utilfeature.DefaultFeatureGate.Enabled(features.OpenAPIV3) {
s.OpenAPIV3VersionedService = routes.OpenAPI{
Config: s.openAPIV3Config,
}.InstallV3(s.Handler.GoRestfulContainer, s.Handler.NonGoRestfulMux)
}
}
...
func (oa OpenAPI) InstallV2(c *restful.Container, mux *mux.PathRecorderMux) (*handler.OpenAPIService, *spec.Swagger) {
spec, err := builder2.BuildOpenAPISpec(c.RegisteredWebServices(), oa.Config)
if err != nil {
klog.Fatalf("Failed to build open api spec for root: %v", err)
}
spec.Definitions = handler.PruneDefaults(spec.Definitions)
openAPIVersionedService, err := handler.NewOpenAPIService(spec)
if err != nil {
klog.Fatalf("Failed to create OpenAPIService: %v", err)
}
/*
通过访问/openapi/v2就能得到这个spec信息
*/
err = openAPIVersionedService.RegisterOpenAPIVersionedService("/openapi/v2", mux)
if err != nil {
klog.Fatalf("Failed to register versioned open api spec for root: %v", err)
}
return openAPIVersionedService, spec
}
builder2.BuildOpenAPISpec利用kube-openapi库,生成一个open api specoa.config 包含了openAPIModel,它是基于生成的 GetOpenAPIDefinitions做出来的;生成过程还需要知道系统 内APl Object 的uri,从而生成“path”,所以这里给了 webservice参数
Summary
- OpenApi是一种规范,按照这个规范定义接口,不仅适合人阅读,也方便程序处理
- k8s的OpenApi通过代码生成来完成,只需标注
- 在构建apiServer的配置时会对OpenApi进行配置,在
genericserver的prepareRun的方法里面会进行Install,利用了kube-openapi库