Kubernetes-apiServer源码深度分析(四)OpenApi概述

833 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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中的一套定义。

image.png

  • 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

image.png

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里 很大的一个文件

image.png

  • 这个文件中,你可以找到当前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

  1. OpenApi是一种规范,按照这个规范定义接口,不仅适合人阅读,也方便程序处理
  2. k8s的OpenApi通过代码生成来完成,只需标注
  3. 在构建apiServer的配置时会对OpenApi进行配置,在genericserverprepareRun的方法里面会进行Install,利用了kube-openapi