Skip to content

Commit

Permalink
feat: run without kube-apiserver (#38)
Browse files Browse the repository at this point in the history
## What type of PR is this?
/kind feature

## What this PR does / why we need it:
Make it possible for Karbour to run without relying on a kube-apiserver
and remove the following startup parameters:
* --kubeconfig
* --authorization-kubeconfig
* --authentication-kubeconfig
  • Loading branch information
panshuai111 authored May 12, 2023
1 parent abc6aad commit 820835a
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 54 deletions.
159 changes: 159 additions & 0 deletions cmd/app/options/recommended.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// Copyright The Karbour Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package options

import (
"fmt"

"github.com/spf13/pflag"

"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/options"
"k8s.io/apiserver/pkg/storage/storagebackend"
"k8s.io/apiserver/pkg/util/feature"
utilflowcontrol "k8s.io/apiserver/pkg/util/flowcontrol"
clientgoinformers "k8s.io/client-go/informers"
clientgoclientset "k8s.io/client-go/kubernetes"
"k8s.io/component-base/featuregate"
"k8s.io/klog/v2"
)

// RecommendedOptions contains the recommended options for running an API server.
// If you add something to this list, it should be in a logical grouping.
// Each of them can be nil to leave the feature unconfigured on ApplyTo.
type RecommendedOptions struct {
ServerRun *options.ServerRunOptions
Etcd *options.EtcdOptions
SecureServing *options.SecureServingOptionsWithLoopback
Audit *options.AuditOptions
Features *options.FeatureOptions

// FeatureGate is a way to plumb feature gate through if you have them.
FeatureGate featuregate.FeatureGate
// ExtraAdmissionInitializers is called once after all ApplyTo from the options above, to pass the returned
// admission plugin initializers to Admission.ApplyTo.
ExtraAdmissionInitializers func(c *server.RecommendedConfig) ([]admission.PluginInitializer, error)
Admission *options.AdmissionOptions
// API Server Egress Selector is used to control outbound traffic from the API Server
EgressSelector *options.EgressSelectorOptions
// Traces contains options to control distributed request tracing.
Traces *options.TracingOptions
}

func NewRecommendedOptions(prefix string, codec runtime.Codec) *RecommendedOptions {
sso := options.NewSecureServingOptions()
sso.HTTP2MaxStreamsPerConnection = 1000

return &RecommendedOptions{
ServerRun: options.NewServerRunOptions(),
Etcd: options.NewEtcdOptions(storagebackend.NewDefaultConfig(prefix, codec)),
SecureServing: sso.WithLoopback(),
Audit: options.NewAuditOptions(),
Features: options.NewFeatureOptions(),
FeatureGate: feature.DefaultFeatureGate,
ExtraAdmissionInitializers: func(c *server.RecommendedConfig) ([]admission.PluginInitializer, error) { return nil, nil },
Admission: options.NewAdmissionOptions(),
EgressSelector: options.NewEgressSelectorOptions(),
Traces: options.NewTracingOptions(),
}
}

func (o *RecommendedOptions) AddFlags(fs *pflag.FlagSet) {
o.ServerRun.AddUniversalFlags(fs)
o.Etcd.AddFlags(fs)
o.SecureServing.AddFlags(fs)
o.Audit.AddFlags(fs)
o.Features.AddFlags(fs)
o.Admission.AddFlags(fs)
o.EgressSelector.AddFlags(fs)
o.Traces.AddFlags(fs)
}

// ApplyTo adds RecommendedOptions to the server configuration.
// pluginInitializers can be empty, it is only need for additional initializers.
func (o *RecommendedOptions) ApplyTo(config *server.RecommendedConfig) error {
if err := o.ServerRun.ApplyTo(&config.Config); err != nil {
return err
}

if err := o.Etcd.Complete(config.Config.StorageObjectCountTracker, config.Config.DrainedNotify(), config.Config.AddPostStartHook); err != nil {
return err
}
if err := o.Etcd.ApplyTo(&config.Config); err != nil {
return err
}
if err := o.EgressSelector.ApplyTo(&config.Config); err != nil {
return err
}
if err := o.Traces.ApplyTo(config.Config.EgressSelector, &config.Config); err != nil {
return err
}
if err := o.SecureServing.ApplyTo(&config.Config.SecureServing, &config.Config.LoopbackClientConfig); err != nil {
return err
}
if err := o.Audit.ApplyTo(&config.Config); err != nil {
return err
}
if err := o.Features.ApplyTo(&config.Config); err != nil {
return err
}

kubeClientConfig := config.LoopbackClientConfig
client, err := clientgoclientset.NewForConfig(kubeClientConfig)
if err != nil {
return err
}
config.ClientConfig = kubeClientConfig
config.SharedInformerFactory = clientgoinformers.NewSharedInformerFactory(client, kubeClientConfig.Timeout)

if initializers, err := o.ExtraAdmissionInitializers(config); err != nil {
return err
} else if err := o.Admission.ApplyTo(&config.Config, config.SharedInformerFactory, config.ClientConfig, o.FeatureGate, initializers...); err != nil {
return err
}
if feature.DefaultFeatureGate.Enabled(features.APIPriorityAndFairness) {
if config.ClientConfig != nil {
if config.MaxRequestsInFlight+config.MaxMutatingRequestsInFlight <= 0 {
return fmt.Errorf("invalid configuration: MaxRequestsInFlight=%d and MaxMutatingRequestsInFlight=%d; they must add up to something positive", config.MaxRequestsInFlight, config.MaxMutatingRequestsInFlight)
}
config.FlowControl = utilflowcontrol.New(
config.SharedInformerFactory,
clientgoclientset.NewForConfigOrDie(config.ClientConfig).FlowcontrolV1beta3(),
config.MaxRequestsInFlight+config.MaxMutatingRequestsInFlight,
config.RequestTimeout/4,
)
} else {
klog.Warningf("Neither kubeconfig is provided nor service-account is mounted, so APIPriorityAndFairness will be disabled")
}
}
return nil
}

func (o *RecommendedOptions) Validate() []error {
errors := []error{}
errors = append(errors, o.ServerRun.Validate()...)
errors = append(errors, o.Etcd.Validate()...)
errors = append(errors, o.SecureServing.Validate()...)
errors = append(errors, o.Audit.Validate()...)
errors = append(errors, o.Features.Validate()...)
errors = append(errors, o.Admission.Validate()...)
errors = append(errors, o.EgressSelector.Validate()...)
errors = append(errors, o.Traces.Validate()...)

return errors
}
85 changes: 31 additions & 54 deletions cmd/app/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ import (

"github.com/KusionStack/karbour/cmd/app/options"
"github.com/KusionStack/karbour/pkg/apiserver"
clientset "github.com/KusionStack/karbour/pkg/generated/clientset/versioned"
informers "github.com/KusionStack/karbour/pkg/generated/informers/externalversions"
karbouropenapi "github.com/KusionStack/karbour/pkg/generated/openapi"
"github.com/KusionStack/karbour/pkg/scheme"
filtersutil "github.com/KusionStack/karbour/pkg/util/filters"
Expand All @@ -37,36 +35,32 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/endpoints/openapi"
"k8s.io/apiserver/pkg/features"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/filters"
genericoptions "k8s.io/apiserver/pkg/server/options"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/klog/v2"
netutils "k8s.io/utils/net"
)

const defaultEtcdPathPrefix = "/registry/karbour"

// Options contains state for master/api server
type Options struct {
ServerRunOptions *genericoptions.ServerRunOptions
RecommendedOptions *genericoptions.RecommendedOptions
RecommendedOptions *options.RecommendedOptions
SearchStorageOptions *options.SearchStorageOptions

SharedInformerFactory informers.SharedInformerFactory
StdOut io.Writer
StdErr io.Writer
StdOut io.Writer
StdErr io.Writer

AlternateDNS []string
}

// NewOptions returns a new Options
func NewOptions(out, errOut io.Writer) *Options {
func NewOptions(out, errOut io.Writer) (*Options, error) {
o := &Options{
ServerRunOptions: genericoptions.NewServerRunOptions(),
RecommendedOptions: genericoptions.NewRecommendedOptions(
RecommendedOptions: options.NewRecommendedOptions(
defaultEtcdPathPrefix,
scheme.Codecs.LegacyCodec(scheme.Versions...),
),
Expand All @@ -75,13 +69,23 @@ func NewOptions(out, errOut io.Writer) *Options {
StdErr: errOut,
}
o.RecommendedOptions.Etcd.StorageConfig.EncodeVersioner = schema.GroupVersions(scheme.Versions)
return o
o.RecommendedOptions.Etcd.StorageConfig.Paging = utilfeature.DefaultFeatureGate.Enabled(features.APIListChunking)
// TODO have a "real" external address
if err := o.RecommendedOptions.SecureServing.MaybeDefaultWithSelfSignedCerts("localhost", o.AlternateDNS, []net.IP{netutils.ParseIPSloppy("127.0.0.1")}); err != nil {
return nil, fmt.Errorf("error creating self-signed certificates: %v", err)
}
return o, nil
}

// NewApiserverCommand provides a CLI handler for 'start master' command
// with a default Options.
func NewApiserverCommand(stopCh <-chan struct{}) *cobra.Command {
o := NewOptions(os.Stdout, os.Stderr)
o, err := NewOptions(os.Stdout, os.Stderr)
if err != nil {
klog.Background().Error(err, "Unable to initialize command options")
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
}

cmd := &cobra.Command{
Short: "Launch an API server",
Long: "Launch an API server",
Expand All @@ -105,15 +109,13 @@ func NewApiserverCommand(stopCh <-chan struct{}) *cobra.Command {

// AddFlags add flags to command
func (o *Options) AddFlags(fs *pflag.FlagSet) {
o.ServerRunOptions.AddUniversalFlags(fs)
o.RecommendedOptions.AddFlags(fs)
o.SearchStorageOptions.AddFlags(fs)
}

// Validate validates Options
func (o *Options) Validate(args []string) error {
errors := []error{}
errors = append(errors, o.ServerRunOptions.Validate()...)
errors = append(errors, o.RecommendedOptions.Validate()...)
errors = append(errors, o.SearchStorageOptions.Validate()...)
return utilerrors.NewAggregate(errors)
Expand All @@ -126,60 +128,36 @@ func (o *Options) Complete() error {

// Config returns config for the api server given Options
func (o *Options) Config() (*apiserver.Config, error) {
// TODO have a "real" external address
if err := o.RecommendedOptions.SecureServing.MaybeDefaultWithSelfSignedCerts("localhost", o.AlternateDNS, []net.IP{netutils.ParseIPSloppy("127.0.0.1")}); err != nil {
return nil, fmt.Errorf("error creating self-signed certificates: %v", err)
}

o.RecommendedOptions.Etcd.StorageConfig.Paging = utilfeature.DefaultFeatureGate.Enabled(features.APIListChunking)
o.RecommendedOptions.ExtraAdmissionInitializers = func(c *genericapiserver.RecommendedConfig) ([]admission.PluginInitializer, error) {
client, err := clientset.NewForConfig(c.LoopbackClientConfig)
if err != nil {
return nil, err
}
informerFactory := informers.NewSharedInformerFactory(client, c.LoopbackClientConfig.Timeout)
o.SharedInformerFactory = informerFactory
return []admission.PluginInitializer{}, nil
}

serverConfig := genericapiserver.NewRecommendedConfig(scheme.Codecs)

if err := o.ServerRunOptions.ApplyTo(&serverConfig.Config); err != nil {
return nil, err
config := &apiserver.Config{
GenericConfig: genericapiserver.NewRecommendedConfig(scheme.Codecs),
ExtraConfig: &apiserver.ExtraConfig{},
}

if err := o.RecommendedOptions.ApplyTo(serverConfig); err != nil {
if err := o.RecommendedOptions.ApplyTo(config.GenericConfig); err != nil {
return nil, err
}

extraConfig := &apiserver.ExtraConfig{}
if err := o.SearchStorageOptions.ApplyTo(extraConfig); err != nil {
if err := o.SearchStorageOptions.ApplyTo(config.ExtraConfig); err != nil {
return nil, err
}

serverConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(karbouropenapi.GetOpenAPIDefinitions, openapi.NewDefinitionNamer(scheme.Scheme))
serverConfig.OpenAPIConfig.Info.Title = "Karbour"
serverConfig.OpenAPIConfig.Info.Version = "0.1"
serverConfig.LongRunningFunc = filters.BasicLongRunningRequestCheck(
config.GenericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(karbouropenapi.GetOpenAPIDefinitions, openapi.NewDefinitionNamer(scheme.Scheme))
config.GenericConfig.OpenAPIConfig.Info.Title = "Karbour"
config.GenericConfig.OpenAPIConfig.Info.Version = "0.1"
config.GenericConfig.LongRunningFunc = filters.BasicLongRunningRequestCheck(
sets.NewString("watch", "proxy"),
sets.NewString("attach", "exec", "proxy", "log", "portforward"),
)
if utilfeature.DefaultFeatureGate.Enabled(features.OpenAPIV3) {
serverConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config(karbouropenapi.GetOpenAPIDefinitions, openapi.NewDefinitionNamer(scheme.Scheme))
serverConfig.OpenAPIV3Config.Info.Title = "Karbour"
serverConfig.OpenAPIV3Config.Info.Version = "0.1"
config.GenericConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config(karbouropenapi.GetOpenAPIDefinitions, openapi.NewDefinitionNamer(scheme.Scheme))
config.GenericConfig.OpenAPIV3Config.Info.Title = "Karbour"
config.GenericConfig.OpenAPIV3Config.Info.Version = "0.1"
}
serverConfig.BuildHandlerChainFunc = func(handler http.Handler, c *genericapiserver.Config) http.Handler {
config.GenericConfig.BuildHandlerChainFunc = func(handler http.Handler, c *genericapiserver.Config) http.Handler {
handler = genericapiserver.DefaultBuildHandlerChain(handler, c)
handler = proxyutil.WithProxyByCluster(handler)
handler = filtersutil.SearchFilter(handler)
return handler
}

config := &apiserver.Config{
GenericConfig: serverConfig,
ExtraConfig: extraConfig,
}
return config, nil
}

Expand All @@ -197,7 +175,6 @@ func (o *Options) RunServer(stopCh <-chan struct{}) error {

server.GenericAPIServer.AddPostStartHookOrDie("start-server-informers", func(context genericapiserver.PostStartHookContext) error {
config.GenericConfig.SharedInformerFactory.Start(context.StopCh)
o.SharedInformerFactory.Start(context.StopCh)
return nil
})

Expand Down

0 comments on commit 820835a

Please sign in to comment.