kubernetes源码阅读—— kube-apiserver

介绍

  • kube-apiserver组件负责将Kubernetes的“资源组、资源版本、资源”以RESTful风格的形式对外暴露并提供服务。该组件是Kubernetes系统集群中所有组件沟通的桥梁。
  • kube-apiserver基于go-restful框架
  • kube-apiserver提供了3种HTTP Server:
  1. KubeAPIServer:提供了Kubernetes内置核心资源服务。该服务通过Master对象进行管理,并通过legacyscheme.Scheme资源注册表管理Master相关资源。
  2. APIExtensionsServer:提供了CRD自定义资源服务,开发者可通过CRD对Kubernetes资源进行扩展。该服务通过CustomResourceDefinitions对象进行管理,并通过extensionsapiserver.Scheme资源注册表管理CRD相关资源。
  3. AggregatorServer:提供了AA (APIAggregator)聚合服务,开发者可通过AA对Kubernetes聚合服务进行扩展。API聚合服务通过APIAggregator对象进行管理,并通过aggregatorscheme.Scheme资源注册表管理AA相关资源。

kube-apiserver启动流程

  1. 资源注册。
  2. Cobra命令行参数解析。
  3. 创建APIServer通用配置。
  4. 创建APIExtensionsServer。
  5. 创建KubeAPIServer。
  6. 创建AggregatorServer。
  7. 创建GenericAPIServer。
  8. 启动HTTP服务。
  9. 启动HTTPS服务。
  • 初始化全局变量:
// pkg\api\legacyscheme\scheme.go
var (
    // Scheme is the default instance of runtime.Scheme to which types in the Kubernetes API are already registered.
    // NOTE: If you are copying this file to start a new api group, STOP! Copy the
    // extensions group instead. This Scheme is special and should appear ONLY in
    // the api group, unless you really know what you're doing.
    // TODO(lavalamp): make the above error impossible.
    Scheme = runtime.NewScheme()

    // Codecs provides access to encoding and decoding for the scheme
    Codecs = serializer.NewCodecFactory(Scheme)

    // ParameterCodec handles versioning of objects that are converted to query parameters.
    ParameterCodec = runtime.NewParameterCodec(Scheme)
)
  • 由import机制,pkg/apis下的每个资源组会进行资源注册,如core:
// pkg\apis\core\install\install.go
func init() {
    Install(legacyscheme.Scheme)
}

// Install registers the API group and adds types to a scheme
func Install(scheme *runtime.Scheme) {
    utilruntime.Must(core.AddToScheme(scheme))
    utilruntime.Must(v1.AddToScheme(scheme))
    utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion))
}
  • 程序入口:
// cmd\kube-apiserver\apiserver.go
func main() {
    rand.Seed(time.Now().UnixNano())

    pflag.CommandLine.SetNormalizeFunc(cliflag.WordSepNormalizeFunc)

    //构建命令行对象,返回*cobra.Command
    command := app.NewAPIServerCommand()

    logs.InitLogs()
    defer logs.FlushLogs()

    //command.Execute()最终会调用 Command结构体中定义的Run函数
    if err := command.Execute(); err != nil {
        os.Exit(1)
    }
}
// cmd\kube-apiserver\app\server.go
// NewAPIServerCommand creates a *cobra.Command object with default parameters
func NewAPIServerCommand() *cobra.Command {
    // 初始化各个模块的默认配置
    s := options.NewServerRunOptions()
    cmd := &cobra.Command{
        Use: "kube-apiserver",
        Long: `The Kubernetes API server validates and configures data
for the api objects which include pods, services, replicationcontrollers, and
others. The API Server services REST operations and provides the frontend to the
cluster's shared state through which all other components interact.`,

        // stop printing usage when the command errors
        SilenceUsage: true,
        PersistentPreRunE: func(*cobra.Command, []string) error {
            // silence client-go warnings.
            // kube-apiserver loopback clients should not log self-issued warnings.
            rest.SetDefaultWarningHandler(rest.NoWarnings{})
            return nil
        },
        RunE: func(cmd *cobra.Command, args []string) error {
            verflag.PrintAndExitIfRequested()
            fs := cmd.Flags()
            cliflag.PrintFlags(fs)

            err := checkNonZeroInsecurePort(fs)
            if err != nil {
                return err
            }
            // set default options
            // 完成参数的配置
            completedOptions, err := Complete(s)
            if err != nil {
                return err
            }

            // validate options
            if errs := completedOptions.Validate(); len(errs) != 0 {
                return utilerrors.NewAggregate(errs)
            }
            // 使用配置运行apiserver
            return Run(completedOptions, genericapiserver.SetupSignalHandler())
        },
        Args: func(cmd *cobra.Command, args []string) error {
            for _, arg := range args {
                if len(arg) > 0 {
                    return fmt.Errorf("%q does not take any arguments, got %q", cmd.CommandPath(), args)
                }
            }
            return nil
        },
    }

    fs := cmd.Flags()
    namedFlagSets := s.Flags()
    verflag.AddFlags(namedFlagSets.FlagSet("global"))
    globalflag.AddGlobalFlags(namedFlagSets.FlagSet("global"), cmd.Name())
    options.AddCustomGlobalFlags(namedFlagSets.FlagSet("generic"))
    for _, f := range namedFlagSets.FlagSets {
        fs.AddFlagSet(f)
    }

    usageFmt := "Usage:\n  %s\n"
    cols, _, _ := term.TerminalSize(cmd.OutOrStdout())
    cmd.SetUsageFunc(func(cmd *cobra.Command) error {
        fmt.Fprintf(cmd.OutOrStderr(), usageFmt, cmd.UseLine())
        cliflag.PrintSections(cmd.OutOrStderr(), namedFlagSets, cols)
        return nil
    })
    cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
        fmt.Fprintf(cmd.OutOrStdout(), "%s\n\n"+usageFmt, cmd.Long, cmd.UseLine())
        cliflag.PrintSections(cmd.OutOrStdout(), namedFlagSets, cols)
    })

    return cmd
}


// Run runs the specified APIServer.  This should never exit.
func Run(completeOptions completedServerRunOptions, stopCh <-chan struct{}) error {
    // To help debugging, immediately log version
    klog.Infof("Version: %+v", version.Get())

    // 创建server
    server, err := CreateServerChain(completeOptions, stopCh)
    if err != nil {
        return err
    }

    prepared, err := server.PrepareRun()
    if err != nil {
        return err
    }

    // 启动server
    return prepared.Run(stopCh)
}
  • CreateServerChain先调用CreateKubeAPIServerConfig函数,根据配置参数创建了运行api server所需要的资源。再依次创建apiExtensionsServer,kubeAPIServer和aggregatorServer三个server。
// CreateServerChain creates the apiservers connected via delegation.
func CreateServerChain(completedOptions completedServerRunOptions, stopCh <-chan struct{}) (*aggregatorapiserver.APIAggregator, error) {
    nodeTunneler, proxyTransport, err := CreateNodeDialer(completedOptions)
    if err != nil {
        return nil, err
    }

    kubeAPIServerConfig, serviceResolver, pluginInitializer, err := CreateKubeAPIServerConfig(completedOptions, nodeTunneler, proxyTransport)
    if err != nil {
        return nil, err
    }

    // If additional API servers are added, they should be gated.
    apiExtensionsConfig, err := createAPIExtensionsConfig(*kubeAPIServerConfig.GenericConfig, kubeAPIServerConfig.ExtraConfig.VersionedInformers, pluginInitializer, completedOptions.ServerRunOptions, completedOptions.MasterCount,
        serviceResolver, webhook.NewDefaultAuthenticationInfoResolverWrapper(proxyTransport, kubeAPIServerConfig.GenericConfig.EgressSelector, kubeAPIServerConfig.GenericConfig.LoopbackClientConfig))
    if err != nil {
        return nil, err
    }
    apiExtensionsServer, err := createAPIExtensionsServer(apiExtensionsConfig, genericapiserver.NewEmptyDelegate())
    if err != nil {
        return nil, err
    }

    kubeAPIServer, err := CreateKubeAPIServer(kubeAPIServerConfig, apiExtensionsServer.GenericAPIServer)
    if err != nil {
        return nil, err
    }

    // aggregator comes last in the chain
    aggregatorConfig, err := createAggregatorConfig(*kubeAPIServerConfig.GenericConfig, completedOptions.ServerRunOptions, kubeAPIServerConfig.ExtraConfig.VersionedInformers, serviceResolver, proxyTransport, pluginInitializer)
    if err != nil {
        return nil, err
    }
    aggregatorServer, err := createAggregatorServer(aggregatorConfig, kubeAPIServer.GenericAPIServer, apiExtensionsServer.Informers)
    if err != nil {
        // we don't need special handling for innerStopCh because the aggregator server doesn't create any go routines
        return nil, err
    }

    return aggregatorServer, nil
}
  • CreateKubeAPIServerConfig创建了kube-apiserver不同模块实例化所需的配置:
// CreateKubeAPIServerConfig creates all the resources for running the API server, but runs none of them
func CreateKubeAPIServerConfig(
    s completedServerRunOptions,
    nodeTunneler tunneler.Tunneler,
    proxyTransport *http.Transport,
) (
    *controlplane.Config,
    aggregatorapiserver.ServiceResolver,
    []admission.PluginInitializer,
    error,
) {
    genericConfig, versionedInformers, serviceResolver, pluginInitializers, admissionPostStartHook, storageFactory, err := buildGenericConfig(s.ServerRunOptions, proxyTransport)
    if err != nil {
        return nil, nil, nil, err
    }

    if _, port, err := net.SplitHostPort(s.Etcd.StorageConfig.Transport.ServerList[0]); err == nil && port != "0" && len(port) != 0 {
        if err := utilwait.PollImmediate(etcdRetryInterval, etcdRetryLimit*etcdRetryInterval, preflight.EtcdConnection{ServerList: s.Etcd.StorageConfig.Transport.ServerList}.CheckEtcdServers); err != nil {
            return nil, nil, nil, fmt.Errorf("error waiting for etcd connection: %v", err)
        }
    }

    capabilities.Initialize(capabilities.Capabilities{
        AllowPrivileged: s.AllowPrivileged,
        // TODO(vmarmol): Implement support for HostNetworkSources.
        PrivilegedSources: capabilities.PrivilegedSources{
            HostNetworkSources: []string{},
            HostPIDSources:     []string{},
            HostIPCSources:     []string{},
        },
        PerConnectionBandwidthLimitBytesPerSec: s.MaxConnectionBytesPerSec,
    })

    s.Metrics.Apply()
    serviceaccount.RegisterMetrics()

    s.Logs.Apply()

    serviceIPRange, apiServerServiceIP, err := controlplane.ServiceIPRange(s.PrimaryServiceClusterIPRange)
    if err != nil {
        return nil, nil, nil, err
    }

    // defaults to empty range and ip
    var secondaryServiceIPRange net.IPNet
    // process secondary range only if provided by user
    if s.SecondaryServiceClusterIPRange.IP != nil {
        secondaryServiceIPRange, _, err = controlplane.ServiceIPRange(s.SecondaryServiceClusterIPRange)
        if err != nil {
            return nil, nil, nil, err
        }
    }

    config := &controlplane.Config{
        GenericConfig: genericConfig,
        ExtraConfig: controlplane.ExtraConfig{
            APIResourceConfigSource: storageFactory.APIResourceConfigSource,
            StorageFactory:          storageFactory,
            EventTTL:                s.EventTTL,
            KubeletClientConfig:     s.KubeletConfig,
            EnableLogsSupport:       s.EnableLogsHandler,
            ProxyTransport:          proxyTransport,

            Tunneler: nodeTunneler,

            ServiceIPRange:          serviceIPRange,
            APIServerServiceIP:      apiServerServiceIP,
            SecondaryServiceIPRange: secondaryServiceIPRange,

            APIServerServicePort: 443,

            ServiceNodePortRange:      s.ServiceNodePortRange,
            KubernetesServiceNodePort: s.KubernetesServiceNodePort,

            EndpointReconcilerType: reconcilers.Type(s.EndpointReconcilerType),
            MasterCount:            s.MasterCount,

            ServiceAccountIssuer:        s.ServiceAccountIssuer,
            ServiceAccountMaxExpiration: s.ServiceAccountTokenMaxExpiration,
            ExtendExpiration:            s.Authentication.ServiceAccounts.ExtendExpiration,

            VersionedInformers: versionedInformers,

            IdentityLeaseDurationSeconds:      s.IdentityLeaseDurationSeconds,
            IdentityLeaseRenewIntervalSeconds: s.IdentityLeaseRenewIntervalSeconds,
        },
    }

    clientCAProvider, err := s.Authentication.ClientCert.GetClientCAContentProvider()
    if err != nil {
        return nil, nil, nil, err
    }
    config.ExtraConfig.ClusterAuthenticationInfo.ClientCA = clientCAProvider

    requestHeaderConfig, err := s.Authentication.RequestHeader.ToAuthenticationRequestHeaderConfig()
    if err != nil {
        return nil, nil, nil, err
    }
    if requestHeaderConfig != nil {
        config.ExtraConfig.ClusterAuthenticationInfo.RequestHeaderCA = requestHeaderConfig.CAContentProvider
        config.ExtraConfig.ClusterAuthenticationInfo.RequestHeaderAllowedNames = requestHeaderConfig.AllowedClientNames
        config.ExtraConfig.ClusterAuthenticationInfo.RequestHeaderExtraHeaderPrefixes = requestHeaderConfig.ExtraHeaderPrefixes
        config.ExtraConfig.ClusterAuthenticationInfo.RequestHeaderGroupHeaders = requestHeaderConfig.GroupHeaders
        config.ExtraConfig.ClusterAuthenticationInfo.RequestHeaderUsernameHeaders = requestHeaderConfig.UsernameHeaders
    }

    if err := config.GenericConfig.AddPostStartHook("start-kube-apiserver-admission-initializer", admissionPostStartHook); err != nil {
        return nil, nil, nil, err
    }

    if nodeTunneler != nil {
        // Use the nodeTunneler's dialer to connect to the kubelet
        config.ExtraConfig.KubeletClientConfig.Dial = nodeTunneler.Dial
    }
    if config.GenericConfig.EgressSelector != nil {
        // Use the config.GenericConfig.EgressSelector lookup to find the dialer to connect to the kubelet
        config.ExtraConfig.KubeletClientConfig.Lookup = config.GenericConfig.EgressSelector.Lookup

        // Use the config.GenericConfig.EgressSelector lookup as the transport used by the "proxy" subresources.
        networkContext := egressselector.Cluster.AsNetworkContext()
        dialer, err := config.GenericConfig.EgressSelector.Lookup(networkContext)
        if err != nil {
            return nil, nil, nil, err
        }
        c := proxyTransport.Clone()
        c.DialContext = dialer
        config.ExtraConfig.ProxyTransport = c
    }

    // Load the public keys.
    var pubKeys []interface{}
    for _, f := range s.Authentication.ServiceAccounts.KeyFiles {
        keys, err := keyutil.PublicKeysFromFile(f)
        if err != nil {
            return nil, nil, nil, fmt.Errorf("failed to parse key file %q: %v", f, err)
        }
        pubKeys = append(pubKeys, keys...)
    }
    // Plumb the required metadata through ExtraConfig.
    config.ExtraConfig.ServiceAccountIssuerURL = s.Authentication.ServiceAccounts.Issuer
    config.ExtraConfig.ServiceAccountJWKSURI = s.Authentication.ServiceAccounts.JWKSURI
    config.ExtraConfig.ServiceAccountPublicKeys = pubKeys

    return config, serviceResolver, pluginInitializers, nil
}
  • CreateKubeAPIServerConfig首先通过buildGenericConfig实例化genericConfig对象,并为genericConfig对象设置默认值:
// BuildGenericConfig takes the master server options and produces the genericapiserver.Config associated with it
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)
    // 用于设置启用/禁用Group、Version和Resource。如果未在命令行参数中指定启用/禁用的GV,则启动默认的GVR。
    // 默认启用Stable和Beta版本的资源,不启用Alpha版本的资源。
    genericConfig.MergedResourceConfig = controlplane.DefaultAPIResourceConfigSource()

    if lastErr = s.GenericServerRunOptions.ApplyTo(genericConfig); lastErr != nil {
        return
    }

    if lastErr = s.SecureServing.ApplyTo(&genericConfig.SecureServing, &genericConfig.LoopbackClientConfig); lastErr != nil {
        return
    }
    if lastErr = s.Features.ApplyTo(genericConfig); lastErr != nil {
        return
    }
    if lastErr = s.APIEnablement.ApplyTo(genericConfig, controlplane.DefaultAPIResourceConfigSource(), legacyscheme.Scheme); lastErr != nil {
        return
    }
    if lastErr = s.EgressSelector.ApplyTo(genericConfig); lastErr != nil {
        return
    }
    //openapi规范相关配置
    genericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(generatedopenapi.GetOpenAPIDefinitions, openapinamer.NewDefinitionNamer(legacyscheme.Scheme, extensionsapiserver.Scheme, aggregatorscheme.Scheme))
    genericConfig.OpenAPIConfig.Info.Title = "Kubernetes"
    genericConfig.LongRunningFunc = filters.BasicLongRunningRequestCheck(
        sets.NewString("watch", "proxy"),
        sets.NewString("attach", "exec", "proxy", "log", "portforward"),
    )

    kubeVersion := version.Get()
    genericConfig.Version = &kubeVersion

    // 与apiserver交互的etcd的相关配置
    storageFactoryConfig := kubeapiserver.NewStorageFactoryConfig()
    storageFactoryConfig.APIResourceConfig = genericConfig.MergedResourceConfig
    completedStorageFactoryConfig, err := storageFactoryConfig.Complete(s.Etcd)
    if err != nil {
        lastErr = err
        return
    }
    storageFactory, lastErr = completedStorageFactoryConfig.New()
    if lastErr != nil {
        return
    }
    if genericConfig.EgressSelector != nil {
        storageFactory.StorageConfig.Transport.EgressLookup = genericConfig.EgressSelector.Lookup
    }
    if lastErr = s.Etcd.ApplyWithStorageFactoryTo(storageFactory, genericConfig); lastErr != nil {
        return
    }

    // Use protobufs for self-communication.
    // Since not every generic apiserver has to support protobufs, we
    // cannot default to it in generic apiserver and need to explicitly
    // set it in kube-apiserver.
    genericConfig.LoopbackClientConfig.ContentConfig.ContentType = "application/vnd.kubernetes.protobuf"
    // Disable compression for self-communication, since we are going to be
    // on a fast local network
    genericConfig.LoopbackClientConfig.DisableCompression = true

    kubeClientConfig := genericConfig.LoopbackClientConfig
    clientgoExternalClient, err := clientgoclientset.NewForConfig(kubeClientConfig)
    if err != nil {
        lastErr = fmt.Errorf("failed to create real external clientset: %v", err)
        return
    }
    versionedInformers = clientgoinformers.NewSharedInformerFactory(clientgoExternalClient, 10*time.Minute)

    // Authentication.ApplyTo requires already applied OpenAPIConfig and EgressSelector if present
    if lastErr = s.Authentication.ApplyTo(&genericConfig.Authentication, genericConfig.SecureServing, genericConfig.EgressSelector, genericConfig.OpenAPIConfig, clientgoExternalClient, versionedInformers); lastErr != nil {
        return
    }

    // 授权相关的配置。BuildAuthorizer实例化认证器
    genericConfig.Authorization.Authorizer, genericConfig.RuleResolver, err = BuildAuthorizer(s, genericConfig.EgressSelector, versionedInformers)
    if err != nil {
        lastErr = fmt.Errorf("invalid authorization config: %v", err)
        return
    }
    if !sets.NewString(s.Authorization.Modes...).Has(modes.ModeRBAC) {
        genericConfig.DisabledPostStartHooks.Insert(rbacrest.PostStartHookName)
    }

    lastErr = s.Audit.ApplyTo(genericConfig)
    if lastErr != nil {
        return
    }

    admissionConfig := &kubeapiserveradmission.Config{
        ExternalInformers:    versionedInformers,
        LoopbackClientConfig: genericConfig.LoopbackClientConfig,
        CloudConfigFile:      s.CloudProvider.CloudConfigFile,
    }
    serviceResolver = buildServiceResolver(s.EnableAggregatorRouting, genericConfig.LoopbackClientConfig.Host, versionedInformers)
    pluginInitializers, admissionPostStartHook, err = admissionConfig.New(proxyTransport, genericConfig.EgressSelector, serviceResolver)
    if err != nil {
        lastErr = fmt.Errorf("failed to create admission plugin initializer: %v", err)
        return
    }

    err = s.Admission.ApplyTo(
        genericConfig,
        versionedInformers,
        kubeClientConfig,
        feature.DefaultFeatureGate,
        pluginInitializers...)
    if err != nil {
        lastErr = fmt.Errorf("failed to initialize admission: %v", err)
    }

    if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.APIPriorityAndFairness) && s.GenericServerRunOptions.EnablePriorityAndFairness {
        genericConfig.FlowControl = BuildPriorityAndFairness(s, clientgoExternalClient, versionedInformers)
    }

    return
}
  • CreateServerChain创建完通用配置后创建了APIExtensionsServer和KubeAPIServer。先看CreateKubeAPIServer:
// cmd\kube-apiserver\app\server.go
// CreateKubeAPIServer creates and wires a workable kube-apiserver
func CreateKubeAPIServer(kubeAPIServerConfig *controlplane.Config, delegateAPIServer genericapiserver.DelegationTarget) (*controlplane.Instance, error) {
    kubeAPIServer, err := kubeAPIServerConfig.Complete().New(delegateAPIServer)
    if err != nil {
        return nil, err
    }

    return kubeAPIServer, nil
}
  • kubeAPIServer先创建genericAPIServer。实例化Instance。通过InstallLegacyAPI函数将没有Group的Resource注册到/api前缀的路径下,路径为/api/<version>/<resource>。通过InstallAPIs函数将有Group的esource注册到/apis前缀的路径下,路径为/apis/<group>/<version>/<resource>:
// pkg\controlplane\instance.go
// New returns a new instance of Master from the given config.
// Certain config fields will be set to a default value if unset.
// Certain config fields must be specified, including:
//   KubeletClientConfig
func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) (*Instance, error) {
    if reflect.DeepEqual(c.ExtraConfig.KubeletClientConfig, kubeletclient.KubeletClientConfig{}) {
        return nil, fmt.Errorf("Master.New() called with empty config.KubeletClientConfig")
    }
    // 创建名为kube-apiserver的服务
    s, err := c.GenericConfig.New("kube-apiserver", delegationTarget)
    if err != nil {
        return nil, err
    }

    if c.ExtraConfig.EnableLogsSupport {
        routes.Logs{}.Install(s.Handler.GoRestfulContainer)
    }

    // Metadata and keys are expected to only change across restarts at present,
    // so we just marshal immediately and serve the cached JSON bytes.
    md, err := serviceaccount.NewOpenIDMetadata(
        c.ExtraConfig.ServiceAccountIssuerURL,
        c.ExtraConfig.ServiceAccountJWKSURI,
        c.GenericConfig.ExternalAddress,
        c.ExtraConfig.ServiceAccountPublicKeys,
    )
    if err != nil {
        // If there was an error, skip installing the endpoints and log the
        // error, but continue on. We don't return the error because the
        // metadata responses require additional, backwards incompatible
        // validation of command-line options.
        msg := fmt.Sprintf("Could not construct pre-rendered responses for"+
            " ServiceAccountIssuerDiscovery endpoints. Endpoints will not be"+
            " enabled. Error: %v", err)
        if c.ExtraConfig.ServiceAccountIssuerURL != "" {
            // The user likely expects this feature to be enabled if issuer URL is
            // set and the feature gate is enabled. In the future, if there is no
            // longer a feature gate and issuer URL is not set, the user may not
            // expect this feature to be enabled. We log the former case as an Error
            // and the latter case as an Info.
            klog.Error(msg)
        } else {
            klog.Info(msg)
        }
    } else {
        routes.NewOpenIDMetadataServer(md.ConfigJSON, md.PublicKeysetJSON).
            Install(s.Handler.GoRestfulContainer)
    }

    // 实例化Instance
    m := &Instance{
        GenericAPIServer:          s,
        ClusterAuthenticationInfo: c.ExtraConfig.ClusterAuthenticationInfo,
    }

    // 添加/api/* 路由,注册没有Group的资源
    // install legacy rest storage
    if c.ExtraConfig.APIResourceConfigSource.VersionEnabled(apiv1.SchemeGroupVersion) {
        legacyRESTStorageProvider := corerest.LegacyRESTStorageProvider{
            StorageFactory:              c.ExtraConfig.StorageFactory,
            ProxyTransport:              c.ExtraConfig.ProxyTransport,
            KubeletClientConfig:         c.ExtraConfig.KubeletClientConfig,
            EventTTL:                    c.ExtraConfig.EventTTL,
            ServiceIPRange:              c.ExtraConfig.ServiceIPRange,
            SecondaryServiceIPRange:     c.ExtraConfig.SecondaryServiceIPRange,
            ServiceNodePortRange:        c.ExtraConfig.ServiceNodePortRange,
            LoopbackClientConfig:        c.GenericConfig.LoopbackClientConfig,
            ServiceAccountIssuer:        c.ExtraConfig.ServiceAccountIssuer,
            ExtendExpiration:            c.ExtraConfig.ExtendExpiration,
            ServiceAccountMaxExpiration: c.ExtraConfig.ServiceAccountMaxExpiration,
            APIAudiences:                c.GenericConfig.Authentication.APIAudiences,
        }
        if err := m.InstallLegacyAPI(&c, c.GenericConfig.RESTOptionsGetter, legacyRESTStorageProvider); err != nil {
            return nil, err
        }
    }

    // 添加/apis/* 路由,注册有Group的资源
    // The order here is preserved in discovery.
    // If resources with identical names exist in more than one of these groups (e.g. "deployments.apps"" and "deployments.extensions"),
    // the order of this list determines which group an unqualified resource name (e.g. "deployments") should prefer.
    // This priority order is used for local discovery, but it ends up aggregated in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go
    // with specific priorities.
    // TODO: describe the priority all the way down in the RESTStorageProviders and plumb it back through the various discovery
    // handlers that we have.
    restStorageProviders := []RESTStorageProvider{
        apiserverinternalrest.StorageProvider{},
        authenticationrest.RESTStorageProvider{Authenticator: c.GenericConfig.Authentication.Authenticator, APIAudiences: c.GenericConfig.Authentication.APIAudiences},
        authorizationrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorization.Authorizer, RuleResolver: c.GenericConfig.RuleResolver},
        autoscalingrest.RESTStorageProvider{},
        batchrest.RESTStorageProvider{},
        certificatesrest.RESTStorageProvider{},
        coordinationrest.RESTStorageProvider{},
        discoveryrest.StorageProvider{},
        extensionsrest.RESTStorageProvider{},
        networkingrest.RESTStorageProvider{},
        noderest.RESTStorageProvider{},
        policyrest.RESTStorageProvider{},
        rbacrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorization.Authorizer},
        schedulingrest.RESTStorageProvider{},
        storagerest.RESTStorageProvider{},
        flowcontrolrest.RESTStorageProvider{},
        // keep apps after extensions so legacy clients resolve the extensions versions of shared resource names.
        // See https://github.com/kubernetes/kubernetes/issues/42392
        appsrest.StorageProvider{},
        admissionregistrationrest.RESTStorageProvider{},
        eventsrest.RESTStorageProvider{TTL: c.ExtraConfig.EventTTL},
    }
    if err := m.InstallAPIs(c.ExtraConfig.APIResourceConfigSource, c.GenericConfig.RESTOptionsGetter, restStorageProviders...); err != nil {
        return nil, err
    }

    if c.ExtraConfig.Tunneler != nil {
        m.installTunneler(c.ExtraConfig.Tunneler, corev1client.NewForConfigOrDie(c.GenericConfig.LoopbackClientConfig).Nodes())
    }

    m.GenericAPIServer.AddPostStartHookOrDie("start-cluster-authentication-info-controller", func(hookContext genericapiserver.PostStartHookContext) error {
        kubeClient, err := kubernetes.NewForConfig(hookContext.LoopbackClientConfig)
        if err != nil {
            return err
        }
        controller := clusterauthenticationtrust.NewClusterAuthenticationTrustController(m.ClusterAuthenticationInfo, kubeClient)

        // prime values and start listeners
        if m.ClusterAuthenticationInfo.ClientCA != nil {
            if notifier, ok := m.ClusterAuthenticationInfo.ClientCA.(dynamiccertificates.Notifier); ok {
                notifier.AddListener(controller)
            }
            if controller, ok := m.ClusterAuthenticationInfo.ClientCA.(dynamiccertificates.ControllerRunner); ok {
                // runonce to be sure that we have a value.
                if err := controller.RunOnce(); err != nil {
                    runtime.HandleError(err)
                }
                go controller.Run(1, hookContext.StopCh)
            }
        }
        if m.ClusterAuthenticationInfo.RequestHeaderCA != nil {
            if notifier, ok := m.ClusterAuthenticationInfo.RequestHeaderCA.(dynamiccertificates.Notifier); ok {
                notifier.AddListener(controller)
            }
            if controller, ok := m.ClusterAuthenticationInfo.RequestHeaderCA.(dynamiccertificates.ControllerRunner); ok {
                // runonce to be sure that we have a value.
                if err := controller.RunOnce(); err != nil {
                    runtime.HandleError(err)
                }
                go controller.Run(1, hookContext.StopCh)
            }
        }

        go controller.Run(1, hookContext.StopCh)
        return nil
    })

    if utilfeature.DefaultFeatureGate.Enabled(apiserverfeatures.APIServerIdentity) {
        m.GenericAPIServer.AddPostStartHookOrDie("start-kube-apiserver-identity-lease-controller", func(hookContext genericapiserver.PostStartHookContext) error {
            kubeClient, err := kubernetes.NewForConfig(hookContext.LoopbackClientConfig)
            if err != nil {
                return err
            }
            controller := lease.NewController(
                clock.RealClock{},
                kubeClient,
                m.GenericAPIServer.APIServerID,
                int32(c.ExtraConfig.IdentityLeaseDurationSeconds),
                nil,
                time.Duration(c.ExtraConfig.IdentityLeaseRenewIntervalSeconds)*time.Second,
                metav1.NamespaceSystem,
                labelAPIServerHeartbeat)
            go controller.Run(wait.NeverStop)
            return nil
        })
        m.GenericAPIServer.AddPostStartHookOrDie("start-kube-apiserver-identity-lease-garbage-collector", func(hookContext genericapiserver.PostStartHookContext) error {
            kubeClient, err := kubernetes.NewForConfig(hookContext.LoopbackClientConfig)
            if err != nil {
                return err
            }
            go apiserverleasegc.NewAPIServerLeaseGC(
                kubeClient,
                time.Duration(c.ExtraConfig.IdentityLeaseDurationSeconds)*time.Second,
                metav1.NamespaceSystem,
                KubeAPIServerIdentityLeaseLabelSelector,
            ).Run(wait.NeverStop)
            return nil
        })
    }

    return m, nil
}
  • InstallLegacyAPI注册没有Group的资源:
// InstallLegacyAPI will install the legacy APIs for the restStorageProviders if they are enabled.
func (m *Instance) InstallLegacyAPI(c *completedConfig, restOptionsGetter generic.RESTOptionsGetter, legacyRESTStorageProvider corerest.LegacyRESTStorageProvider) error {
    legacyRESTStorage, apiGroupInfo, err := legacyRESTStorageProvider.NewLegacyRESTStorage(restOptionsGetter)
    if err != nil {
        return fmt.Errorf("error building core storage: %v", err)
    }

    controllerName := "bootstrap-controller"
    coreClient := corev1client.NewForConfigOrDie(c.GenericConfig.LoopbackClientConfig)
    bootstrapController := c.NewBootstrapController(legacyRESTStorage, coreClient, coreClient, coreClient, coreClient.RESTClient())
    m.GenericAPIServer.AddPostStartHookOrDie(controllerName, bootstrapController.PostStartHook)
    m.GenericAPIServer.AddPreShutdownHookOrDie(controllerName, bootstrapController.PreShutdownHook)

    if err := m.GenericAPIServer.InstallLegacyAPIGroup(genericapiserver.DefaultLegacyAPIPrefix, &apiGroupInfo); err != nil {
        return fmt.Errorf("error in registering group versions: %v", err)
    }
    return nil
}
  • 首先,legacyRESTStorageProvider.NewLegacyRESTStorage函数实例化APIGroupInfo。然后通过调用NewREST方法创建了podTemplate, event,secret,persistentVolume,endpoints等多个资源的资源存储对象RESTStorage。最后将这些资源的路由和其资源存储对象RESTStorage的映射关系存入APIGroupInfo的VersionedResourcesStorageMap字段。:
func (c LegacyRESTStorageProvider) NewLegacyRESTStorage(restOptionsGetter generic.RESTOptionsGetter) (LegacyRESTStorage, genericapiserver.APIGroupInfo, error) {
    apiGroupInfo := genericapiserver.APIGroupInfo{
        PrioritizedVersions:          legacyscheme.Scheme.PrioritizedVersionsForGroup(""),
        VersionedResourcesStorageMap: map[string]map[string]rest.Storage{}, //第一个string存储version,第二个string存储资源名
        Scheme:                       legacyscheme.Scheme,
        ParameterCodec:               legacyscheme.ParameterCodec,
        NegotiatedSerializer:         legacyscheme.Codecs,
    }
    ......
    restStorage := LegacyRESTStorage{}
    
    podTemplateStorage, err := podtemplatestore.NewREST(restOptionsGetter)
    ......
    serviceClusterIPAllocator, err := ipallocator.NewAllocatorCIDRRange(&serviceClusterIPRange, func(max int, rangeSpec string) (allocator.Interface, error) {
        mem := allocator.NewAllocationMap(max, rangeSpec)
        // TODO etcdallocator package to return a storage interface via the storageFactory
        etcd, err := serviceallocator.NewEtcd(mem, "/ranges/serviceips", api.Resource("serviceipallocations"), serviceStorageConfig)
        if err != nil {
            return nil, err
        }
        serviceClusterIPRegistry = etcd
        return etcd, nil
    })
    if err != nil {
        return LegacyRESTStorage{}, genericapiserver.APIGroupInfo{}, fmt.Errorf("cannot create cluster IP allocator: %v", err)
    }
    restStorage.ServiceClusterIPAllocator = serviceClusterIPRegistry
    .....
    restStorageMap := map[string]rest.Storage{
        "pods":             podStorage.Pod,
        "pods/attach":      podStorage.Attach,
        "pods/status":      podStorage.Status,
        "pods/log":         podStorage.Log,
        "pods/exec":        podStorage.Exec,
        "pods/portforward": podStorage.PortForward,
        "pods/proxy":       podStorage.Proxy,
        "pods/binding":     podStorage.Binding,
        "bindings":         podStorage.LegacyBinding,

        "podTemplates": podTemplateStorage,
    ......
    apiGroupInfo.VersionedResourcesStorageMap["v1"] = restStorageMap
    return restStorage, apiGroupInfo, nil
    
// LegacyRESTStorage returns stateful information about particular instances of REST storage to
// master.go for wiring controllers.
// TODO remove this by running the controller as a poststarthook
type LegacyRESTStorage struct {
    ServiceClusterIPAllocator          rangeallocation.RangeRegistry
    SecondaryServiceClusterIPAllocator rangeallocation.RangeRegistry
    ServiceNodePortAllocator           rangeallocation.RangeRegistry
}
  • 每个资源都通过NewREST创建了RESTStorage。RESTStorage包含NewFunc, NewListFunc, CreateStrategy, UpdateStrategy, DeleteStrategy等对应增删改查等动作的处理方法。例如podTemplate:
// pkg\registry\core\podtemplate\storage\storage.go
// NewREST returns a RESTStorage object that will work against pod templates.
func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) {
    store := &genericregistry.Store{
        NewFunc:                  func() runtime.Object { return &api.PodTemplate{} },
        NewListFunc:              func() runtime.Object { return &api.PodTemplateList{} },
        DefaultQualifiedResource: api.Resource("podtemplates"),

        CreateStrategy: podtemplate.Strategy,
        UpdateStrategy: podtemplate.Strategy,
        DeleteStrategy: podtemplate.Strategy,

        ReturnDeletedObject: true,

        TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
    }
    options := &generic.StoreOptions{RESTOptions: optsGetter}
    if err := store.CompleteWithOptions(options); err != nil {
        return nil, err
    }
    return &REST{store}, nil
}

// REST implements a RESTStorage for pod templates.
type REST struct {
    *genericregistry.Store
}
  • InstallLegacyAPI最后通过m.GenericAPIServer.InstallLegacyAPIGroup函数将APIGroupInfo对象中的<资源组>/<资源版本>/<资源>/<子资源>(包括资源存储对象)注册到KubeAPIServer Handlers方法。
// staging\src\k8s.io\apiserver\pkg\server\genericapiserver.go
func (s *GenericAPIServer) InstallLegacyAPIGroup(apiPrefix string, apiGroupInfo *APIGroupInfo) error {
    if !s.legacyAPIGroupPrefixes.Has(apiPrefix) {
        return fmt.Errorf("%q is not in the allowed legacy API prefixes: %v", apiPrefix, s.legacyAPIGroupPrefixes.List())
    }

    openAPIModels, err := s.getOpenAPIModels(apiPrefix, apiGroupInfo)
    if err != nil {
        return fmt.Errorf("unable to get openapi models: %v", err)
    }

    if err := s.installAPIResources(apiPrefix, apiGroupInfo, openAPIModels); err != nil {
        return err
    }

    // Install the version handler.
    // Add a handler at /<apiPrefix> to enumerate the supported api versions.
    s.Handler.GoRestfulContainer.Add(discovery.NewLegacyRootAPIHandler(s.discoveryAddresses, s.Serializer, apiPrefix).WebService())

    return nil
}

// getOpenAPIModels is a private method for getting the OpenAPI models
func (s *GenericAPIServer) getOpenAPIModels(apiPrefix string, apiGroupInfos ...*APIGroupInfo) (openapiproto.Models, error) {
    if s.openAPIConfig == nil {
        return nil, nil
    }
    pathsToIgnore := openapiutil.NewTrie(s.openAPIConfig.IgnorePrefixes)
    resourceNames := make([]string, 0)
    for _, apiGroupInfo := range apiGroupInfos {
        groupResources, err := getResourceNamesForGroup(apiPrefix, apiGroupInfo, pathsToIgnore)
        if err != nil {
            return nil, err
        }
        resourceNames = append(resourceNames, groupResources...)
    }

    // Build the openapi definitions for those resources and convert it to proto models
    openAPISpec, err := openapibuilder.BuildOpenAPIDefinitionsForResources(s.openAPIConfig, resourceNames...)
    if err != nil {
        return nil, err
    }
    for _, apiGroupInfo := range apiGroupInfos {
        apiGroupInfo.StaticOpenAPISpec = openAPISpec
    }
    return utilopenapi.ToProtoModels(openAPISpec)
}

// getResourceNamesForGroup is a private method for getting the canonical names for each resource to build in an api group
func getResourceNamesForGroup(apiPrefix string, apiGroupInfo *APIGroupInfo, pathsToIgnore openapiutil.Trie) ([]string, error) {
    // Get the canonical names of every resource we need to build in this api group
    resourceNames := make([]string, 0)
    for _, groupVersion := range apiGroupInfo.PrioritizedVersions {
        for resource, storage := range apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version] {
            path := gpath.Join(apiPrefix, groupVersion.Group, groupVersion.Version, resource)
            if !pathsToIgnore.HasPrefix(path) {
                kind, err := genericapi.GetResourceKind(groupVersion, storage, apiGroupInfo.Scheme)
                if err != nil {
                    return nil, err
                }
                sampleObject, err := apiGroupInfo.Scheme.New(kind)
                if err != nil {
                    return nil, err
                }
                name := openapiutil.GetCanonicalTypeName(sampleObject)
                resourceNames = append(resourceNames, name)
            }
        }
    }

    return resourceNames, nil
}

// installAPIResources is a private method for installing the REST storage backing each api groupversionresource
func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *APIGroupInfo, openAPIModels openapiproto.Models) error {
    var resourceInfos []*storageversion.ResourceInfo
    // 遍历apiGroupInfo的每个groupVersion
    for _, groupVersion := range apiGroupInfo.PrioritizedVersions {
        if len(apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version]) == 0 {
            klog.Warningf("Skipping API %v because it has no resources.", groupVersion)
            continue
        }
        apiGroupVersion := s.getAPIGroupVersion(apiGroupInfo, groupVersion, apiPrefix)
        if apiGroupInfo.OptionsExternalVersion != nil {
            apiGroupVersion.OptionsExternalVersion = apiGroupInfo.OptionsExternalVersion
        }
        apiGroupVersion.OpenAPIModels = openAPIModels

        if openAPIModels != nil && utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
            typeConverter, err := fieldmanager.NewTypeConverter(openAPIModels, false)
            if err != nil {
                return err
            }
            apiGroupVersion.TypeConverter = typeConverter
        }

        apiGroupVersion.MaxRequestBodyBytes = s.maxRequestBodyBytes
        
        // 将每个groupVersion的Resource路径和增删改查动作的处理注册进restful Container
        r, err := apiGroupVersion.InstallREST(s.Handler.GoRestfulContainer)
        if err != nil {
            return fmt.Errorf("unable to setup API %v: %v", apiGroupInfo, err)
        }
        resourceInfos = append(resourceInfos, r...)
    }

    if utilfeature.DefaultFeatureGate.Enabled(features.StorageVersionAPI) &&
        utilfeature.DefaultFeatureGate.Enabled(features.APIServerIdentity) {
        // API installation happens before we start listening on the handlers,
        // therefore it is safe to register ResourceInfos here. The handler will block
        // write requests until the storage versions of the targeting resources are updated.
        s.StorageVersionManager.AddResourceInfo(resourceInfos...)
    }

    return nil
}

func (s *GenericAPIServer) getAPIGroupVersion(apiGroupInfo *APIGroupInfo, groupVersion schema.GroupVersion, apiPrefix string) *genericapi.APIGroupVersion {
    storage := make(map[string]rest.Storage)
    for k, v := range apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version] {
        storage[strings.ToLower(k)] = v
    }
    version := s.newAPIGroupVersion(apiGroupInfo, groupVersion)
    version.Root = apiPrefix
    version.Storage = storage
    return version
}
  • installAPIResources中从restStorage知道每个资源可以执行哪些操作,存入action,再将action对应为REST action,并对每一个操作注册handler,注册到route,再将route注册到webservice。
// staging\src\k8s.io\apiserver\pkg\endpoints\groupversion.go
// InstallREST registers the REST handlers (storage, watch, proxy and redirect) into a restful Container.
// It is expected that the provided path root prefix will serve all operations. Root MUST NOT end
// in a slash.
func (g *APIGroupVersion) InstallREST(container *restful.Container) ([]*storageversion.ResourceInfo, error) {
    prefix := path.Join(g.Root, g.GroupVersion.Group, g.GroupVersion.Version)
    installer := &APIInstaller{
        group:             g,
        prefix:            prefix,
        minRequestTimeout: g.MinRequestTimeout,
    }

    apiResources, resourceInfos, ws, registrationErrors := installer.Install()
    versionDiscoveryHandler := discovery.NewAPIVersionHandler(g.Serializer, g.GroupVersion, staticLister{apiResources})
    versionDiscoveryHandler.AddToWebService(ws)
    container.Add(ws)
    return removeNonPersistedResources(resourceInfos), utilerrors.NewAggregate(registrationErrors)
}
// staging\src\k8s.io\apiserver\pkg\endpoints\installer.go
// Install handlers for API resources.
func (a *APIInstaller) Install() ([]metav1.APIResource, []*storageversion.ResourceInfo, *restful.WebService, []error) {
    var apiResources []metav1.APIResource
    var resourceInfos []*storageversion.ResourceInfo
    var errors []error
    // 创建前缀为prefix/group/version的newWebService
    ws := a.newWebService()

    // Register the paths in a deterministic (sorted) order to get a deterministic swagger spec.
    paths := make([]string, len(a.group.Storage))
    var i int = 0
    for path := range a.group.Storage {
        paths[i] = path
        i++
    }
    sort.Strings(paths)
    // 遍历指定Version, Group下的Resource路径,注册其hander
    for _, path := range paths {
        apiResource, resourceInfo, err := a.registerResourceHandlers(path, a.group.Storage[path], ws)
        if err != nil {
            errors = append(errors, fmt.Errorf("error in registering resource: %s, %v", path, err))
        }
        if apiResource != nil {
            apiResources = append(apiResources, *apiResource)
        }
        if resourceInfo != nil {
            resourceInfos = append(resourceInfos, resourceInfo)
        }
    }
    return apiResources, resourceInfos, ws, errors
}



// staging\src\k8s.io\apiserver\pkg\endpoints\installer.go
// newWebService creates a new restful webservice with the api installer's prefix and version.
func (a *APIInstaller) newWebService() *restful.WebService {
    ws := new(restful.WebService)
    ws.Path(a.prefix)
    // a.prefix contains "prefix/group/version"
    ws.Doc("API at " + a.prefix)
    // Backwards compatibility, we accepted objects with empty content-type at V1.
    // If we stop using go-restful, we can default empty content-type to application/json on an
    // endpoint by endpoint basis
    ws.Consumes("*/*")
    mediaTypes, streamMediaTypes := negotiation.MediaTypesForSerializer(a.group.Serializer)
    ws.Produces(append(mediaTypes, streamMediaTypes...)...)
    ws.ApiVersion(a.group.GroupVersion.String())

    return ws
}


func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService) (*metav1.APIResource, *storageversion.ResourceInfo, error) {
    ......
    // what verbs are supported by the storage, used to know what verbs we support per path
    creater, isCreater := storage.(rest.Creater)
    namedCreater, isNamedCreater := storage.(rest.NamedCreater)
    lister, isLister := storage.(rest.Lister)
    getter, isGetter := storage.(rest.Getter)
    getterWithOptions, isGetterWithOptions := storage.(rest.GetterWithOptions)
    gracefulDeleter, isGracefulDeleter := storage.(rest.GracefulDeleter)
    collectionDeleter, isCollectionDeleter := storage.(rest.CollectionDeleter)
    updater, isUpdater := storage.(rest.Updater)
    patcher, isPatcher := storage.(rest.Patcher)
    watcher, isWatcher := storage.(rest.Watcher)
    connecter, isConnecter := storage.(rest.Connecter)
    storageMeta, isMetadata := storage.(rest.StorageMetadata)
    storageVersionProvider, isStorageVersionProvider := storage.(rest.StorageVersionProvider)
    ......
    // Handler for standard REST verbs (GET, PUT, POST and DELETE).
    // Add actions at the resource path: /api/apiVersion/resource
    actions = appendIf(actions, action{"LIST", resourcePath, resourceParams, namer, false}, isLister)
    actions = appendIf(actions, action{"POST", resourcePath, resourceParams, namer, false}, isCreater)
    actions = appendIf(actions, action{"DELETECOLLECTION", resourcePath, resourceParams, namer, false}, isCollectionDeleter)
    // DEPRECATED in 1.11
    actions = appendIf(actions, action{"WATCHLIST", "watch/" + resourcePath, resourceParams, namer, false}, allowWatchList)

    // Add actions at the item path: /api/apiVersion/resource/{name}
    actions = appendIf(actions, action{"GET", itemPath, nameParams, namer, false}, isGetter)
    if getSubpath {
        actions = appendIf(actions, action{"GET", itemPath + "/{path:*}", proxyParams, namer, false}, isGetter)
    }
    actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer, false}, isUpdater)
    actions = appendIf(actions, action{"PATCH", itemPath, nameParams, namer, false}, isPatcher)
    actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer, false}, isGracefulDeleter)
    ......
    for _, action := range actions {
        ......
        switch action.Verb {
        case "GET": // Get a resource.
            var handler restful.RouteFunction
            if isGetterWithOptions {
                handler = restfulGetResourceWithOptions(getterWithOptions, reqScope, isSubresource)
            } else {
                handler = restfulGetResource(getter, reqScope)
            }

            if needOverride {
                // need change the reported verb
                handler = metrics.InstrumentRouteFunc(verbOverrider.OverrideMetricsVerb(action.Verb), group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, handler)
            } else {
                handler = metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, handler)
            }
            if enableWarningHeaders {
                handler = utilwarning.AddWarningsHandler(handler, warnings)
            }

            doc := "read the specified " + kind
            if isSubresource {
                doc = "read " + subresource + " of the specified " + kind
            }
            route := ws.GET(action.Path).To(handler).
                Doc(doc).
                Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
                Operation("read"+namespaced+kind+strings.Title(subresource)+operationSuffix).
                Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...).
                Returns(http.StatusOK, "OK", producedObject).
                Writes(producedObject)
            if isGetterWithOptions {
                if err := AddObjectParams(ws, route, versionedGetOptions); err != nil {
                    return nil, nil, err
                }
            }
            addParams(route, action.Params)
            routes = append(routes, route)
        case "LIST": // List all resources of a kind.
            ......
        case "PUT": // Update a resource.
            ......
        case "PATCH": // Partially update a resource
            ......
        case "POST": // Create a resource.
            ......
        case "DELETE": // Delete a resource.
            ......
        ......
        for _, route := range routes {
            route.Metadata(ROUTE_META_GVK, metav1.GroupVersionKind{
                Group:   reqScope.Kind.Group,
                Version: reqScope.Kind.Version,
                Kind:    reqScope.Kind.Kind,
            })
            route.Metadata(ROUTE_META_ACTION, strings.ToLower(action.Verb))
            ws.Route(route)
        }
    }
    ......
    return &apiResource, resourceInfo, nil
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容