Skip to content

Commit 6a5a199

Browse files
authored
Initial version of the Datum API server and controller manager, based on the generic API server capabilities of the Kubernetes project. (#33)
This work is largely based on the work done in kubernetes/kubernetes#126260 as part of kubernetes/enhancements#4080. ## API Server One key difference in the API server is overriding the building of the server handler chain in order to insert custom middleware into the request path to enable a better user experience for end users listing projects, and support authorization by injecting organization metadata into the request. Very few native Kubernetes types have been included in the API server - Datum API types will be defined via CRDs coming from our own as well as third party operators. ## Controller Manager Only a handful of controllers have been registered to support garbage collection, namespace lifecycle needs, and quota enforcement. ## Other Future work will include investigating the [integration test framework](https://github.com/kubernetes/kubernetes/tree/master/test/integration) available in the Kubernetes project to determine if we can leverage it here.
2 parents 4db44f2 + 72981eb commit 6a5a199

File tree

15 files changed

+2721
-35
lines changed

15 files changed

+2721
-35
lines changed

LICENSE

Lines changed: 661 additions & 0 deletions
Large diffs are not rendered by default.

cmd/datum-apiserver/Dockerfile

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
FROM golang:1.23 AS build-stage
2+
3+
WORKDIR /workspace
4+
5+
COPY go.mod go.mod
6+
COPY go.sum go.sum
7+
RUN go mod download
8+
9+
COPY cmd/datum-apiserver/ cmd/datum-apiserver/
10+
COPY pkg pkg/
11+
12+
RUN --mount=type=cache,target=/go/pkg/mod/ \
13+
--mount=type=cache,target="/root/.cache/go-build" \
14+
CGO_ENABLED=0 GOOS=linux \
15+
go build \
16+
-ldflags="-X 'k8s.io/component-base/version/verflag.programName=Datum'" \
17+
-o /datum-apiserver cmd/datum-apiserver/main.go
18+
19+
FROM gcr.io/distroless/base-debian11
20+
ARG cmd
21+
22+
WORKDIR /
23+
24+
COPY --from=build-stage /datum-apiserver /datum-apiserver
25+
26+
ENTRYPOINT ["/datum-apiserver"]
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package app
2+
3+
import (
4+
"k8s.io/apimachinery/pkg/util/sets"
5+
"k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle"
6+
mutatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating"
7+
validatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/validating"
8+
"k8s.io/kubernetes/pkg/kubeapiserver/options"
9+
)
10+
11+
// DefaultOffAdmissionPlugins get admission plugins off by default for kube-apiserver.
12+
func DefaultOffAdmissionPlugins() sets.Set[string] {
13+
defaultOnPlugins := sets.New[string](
14+
lifecycle.PluginName, // NamespaceLifecycle
15+
// defaulttolerationseconds.PluginName, // DefaultTolerationSeconds
16+
mutatingwebhook.PluginName, // MutatingAdmissionWebhook
17+
validatingwebhook.PluginName, // ValidatingAdmissionWebhook
18+
// resourcequota.PluginName, // ResourceQuota
19+
// certapproval.PluginName, // CertificateApproval
20+
// certsigning.PluginName, // CertificateSigning
21+
// ctbattest.PluginName, // ClusterTrustBundleAttest
22+
// certsubjectrestriction.PluginName, // CertificateSubjectRestriction
23+
// validatingadmissionpolicy.PluginName, // ValidatingAdmissionPolicy, only active when feature gate ValidatingAdmissionPolicy is enabled
24+
)
25+
26+
return sets.New(options.AllOrderedPlugins...).Difference(defaultOnPlugins)
27+
}

cmd/datum-apiserver/app/config.go

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
package app
2+
3+
import (
4+
"net/http"
5+
6+
corev1 "k8s.io/api/core/v1"
7+
apiextensionsapiserver "k8s.io/apiextensions-apiserver/pkg/apiserver"
8+
"k8s.io/apimachinery/pkg/runtime"
9+
"k8s.io/apiserver/pkg/endpoints/filterlatency"
10+
genericapifilters "k8s.io/apiserver/pkg/endpoints/filters"
11+
genericfeatures "k8s.io/apiserver/pkg/features"
12+
"k8s.io/apiserver/pkg/server"
13+
genericfilters "k8s.io/apiserver/pkg/server/filters"
14+
"k8s.io/apiserver/pkg/server/routine"
15+
flowcontrolrequest "k8s.io/apiserver/pkg/util/flowcontrol/request"
16+
"k8s.io/apiserver/pkg/util/webhook"
17+
"k8s.io/client-go/discovery"
18+
utilversion "k8s.io/component-base/version"
19+
aggregatorapiserver "k8s.io/kube-aggregator/pkg/apiserver"
20+
aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme"
21+
"k8s.io/kubernetes/pkg/api/legacyscheme"
22+
"k8s.io/kubernetes/pkg/controlplane"
23+
controlplaneapiserver "k8s.io/kubernetes/pkg/controlplane/apiserver"
24+
"k8s.io/kubernetes/pkg/controlplane/apiserver/options"
25+
generatedopenapi "k8s.io/kubernetes/pkg/generated/openapi"
26+
admissionregistrationrest "k8s.io/kubernetes/pkg/registry/admissionregistration/rest"
27+
apiserverinternalrest "k8s.io/kubernetes/pkg/registry/apiserverinternal/rest"
28+
authenticationrest "k8s.io/kubernetes/pkg/registry/authentication/rest"
29+
authorizationrest "k8s.io/kubernetes/pkg/registry/authorization/rest"
30+
coordinationrest "k8s.io/kubernetes/pkg/registry/coordination/rest"
31+
discoveryrest "k8s.io/kubernetes/pkg/registry/discovery/rest"
32+
eventsrest "k8s.io/kubernetes/pkg/registry/events/rest"
33+
flowcontrolrest "k8s.io/kubernetes/pkg/registry/flowcontrol/rest"
34+
rbacrest "k8s.io/kubernetes/pkg/registry/rbac/rest"
35+
svmrest "k8s.io/kubernetes/pkg/registry/storagemigration/rest"
36+
37+
datumfilters "go.datumapis.com/datum/pkg/server/filters"
38+
)
39+
40+
type Config struct {
41+
Options options.CompletedOptions
42+
43+
Aggregator *aggregatorapiserver.Config
44+
ControlPlane *controlplaneapiserver.Config
45+
APIExtensions *apiextensionsapiserver.Config
46+
47+
ExtraConfig
48+
}
49+
50+
type ExtraConfig struct {
51+
}
52+
53+
type completedConfig struct {
54+
Options options.CompletedOptions
55+
56+
Aggregator aggregatorapiserver.CompletedConfig
57+
ControlPlane controlplaneapiserver.CompletedConfig
58+
APIExtensions apiextensionsapiserver.CompletedConfig
59+
60+
ExtraConfig
61+
}
62+
63+
type CompletedConfig struct {
64+
// Embed a private pointer that cannot be instantiated outside of this package.
65+
*completedConfig
66+
}
67+
68+
func (c *CompletedConfig) GenericStorageProviders(discovery discovery.DiscoveryInterface) ([]controlplaneapiserver.RESTStorageProvider, error) {
69+
return []controlplaneapiserver.RESTStorageProvider{
70+
c.ControlPlane.NewCoreGenericConfig(),
71+
apiserverinternalrest.StorageProvider{},
72+
authenticationrest.RESTStorageProvider{Authenticator: c.ControlPlane.Generic.Authentication.Authenticator, APIAudiences: c.ControlPlane.Generic.Authentication.APIAudiences},
73+
authorizationrest.RESTStorageProvider{Authorizer: c.ControlPlane.Generic.Authorization.Authorizer, RuleResolver: c.ControlPlane.Generic.RuleResolver},
74+
coordinationrest.RESTStorageProvider{},
75+
rbacrest.RESTStorageProvider{Authorizer: c.ControlPlane.Generic.Authorization.Authorizer},
76+
svmrest.RESTStorageProvider{},
77+
flowcontrolrest.RESTStorageProvider{InformerFactory: c.ControlPlane.Generic.SharedInformerFactory},
78+
admissionregistrationrest.RESTStorageProvider{Authorizer: c.ControlPlane.Generic.Authorization.Authorizer, DiscoveryClient: discovery},
79+
eventsrest.RESTStorageProvider{TTL: c.ControlPlane.EventTTL},
80+
discoveryrest.StorageProvider{},
81+
}, nil
82+
}
83+
84+
func (c *Config) Complete() (CompletedConfig, error) {
85+
return CompletedConfig{&completedConfig{
86+
Options: c.Options,
87+
88+
Aggregator: c.Aggregator.Complete(),
89+
ControlPlane: c.ControlPlane.Complete(),
90+
APIExtensions: c.APIExtensions.Complete(),
91+
92+
ExtraConfig: c.ExtraConfig,
93+
}}, nil
94+
}
95+
96+
func NewConfig(opts options.CompletedOptions) (*Config, error) {
97+
c := &Config{
98+
Options: opts,
99+
}
100+
101+
apiResourceConfigSource := controlplane.DefaultAPIResourceConfigSource()
102+
apiResourceConfigSource.DisableResources(corev1.SchemeGroupVersion.WithResource("serviceaccounts"))
103+
104+
genericConfig, versionedInformers, storageFactory, err := controlplaneapiserver.BuildGenericConfig(
105+
opts,
106+
[]*runtime.Scheme{legacyscheme.Scheme, apiextensionsapiserver.Scheme, aggregatorscheme.Scheme},
107+
apiResourceConfigSource,
108+
generatedopenapi.GetOpenAPIDefinitions,
109+
)
110+
if err != nil {
111+
return nil, err
112+
}
113+
114+
genericConfig.BuildHandlerChainFunc = DefaultBuildHandlerChain
115+
116+
serviceResolver := webhook.NewDefaultServiceResolver()
117+
kubeAPIs, pluginInitializer, err := controlplaneapiserver.CreateConfig(opts, genericConfig, versionedInformers, storageFactory, serviceResolver, nil)
118+
if err != nil {
119+
return nil, err
120+
}
121+
c.ControlPlane = kubeAPIs
122+
c.ControlPlane.Generic.EffectiveVersion = utilversion.DefaultKubeEffectiveVersion()
123+
124+
authInfoResolver := webhook.NewDefaultAuthenticationInfoResolverWrapper(kubeAPIs.ProxyTransport, kubeAPIs.Generic.EgressSelector, kubeAPIs.Generic.LoopbackClientConfig, kubeAPIs.Generic.TracerProvider)
125+
apiExtensions, err := controlplaneapiserver.CreateAPIExtensionsConfig(*kubeAPIs.Generic, kubeAPIs.VersionedInformers, pluginInitializer, opts, 3, serviceResolver, authInfoResolver)
126+
if err != nil {
127+
return nil, err
128+
}
129+
c.APIExtensions = apiExtensions
130+
131+
// TODO(jreese) create an admission plugin that will prohibit the creation of
132+
// a Secret with a type of `kubernetes.io/service-account-token`
133+
c.APIExtensions.GenericConfig.DisabledPostStartHooks.Insert("start-legacy-token-tracking-controller")
134+
135+
aggregator, err := controlplaneapiserver.CreateAggregatorConfig(*kubeAPIs.Generic, opts, kubeAPIs.VersionedInformers, serviceResolver, kubeAPIs.ProxyTransport, kubeAPIs.Extra.PeerProxy, pluginInitializer)
136+
if err != nil {
137+
return nil, err
138+
}
139+
c.Aggregator = aggregator
140+
c.Aggregator.ExtraConfig.DisableRemoteAvailableConditionController = true
141+
// TODO(jreese) better version handling
142+
c.Aggregator.GenericConfig.EffectiveVersion = utilversion.DefaultKubeEffectiveVersion()
143+
144+
return c, nil
145+
}
146+
147+
// Taken from https://github.com/kubernetes/kubernetes/blob/50fc400f178d2078d0ca46aee955ee26375fc437/staging/src/k8s.io/apiserver/pkg/server/config.go#L1004
148+
//
149+
// Modified to inject the following filters at the necessary locations:
150+
// - datumfilters.OrganizationContextAuthorizationDecorator
151+
// - datumfilters.ProjectListOrganizationConstraintDecorator
152+
// - datumfilters.OrganizationContextHandler
153+
//
154+
// This is done to improve the UX that customers will experience while
155+
// interacting with the Datum API server.
156+
//
157+
// Some handlers have not been added as a result of not having access to
158+
// lifecycleSignals in server.Config. TODO(jreese) need to look into this more
159+
func DefaultBuildHandlerChain(apiHandler http.Handler, c *server.Config) http.Handler {
160+
handler := apiHandler
161+
162+
handler = filterlatency.TrackCompleted(handler)
163+
handler = genericapifilters.WithAuthorization(handler, c.Authorization.Authorizer, c.Serializer)
164+
handler = datumfilters.OrganizationContextAuthorizationDecorator(handler)
165+
handler = filterlatency.TrackStarted(handler, c.TracerProvider, "authorization")
166+
167+
if c.FlowControl != nil {
168+
workEstimatorCfg := flowcontrolrequest.DefaultWorkEstimatorConfig()
169+
requestWorkEstimator := flowcontrolrequest.NewWorkEstimator(
170+
c.StorageObjectCountTracker.Get, c.FlowControl.GetInterestedWatchCount, workEstimatorCfg, c.FlowControl.GetMaxSeats)
171+
handler = filterlatency.TrackCompleted(handler)
172+
handler = genericfilters.WithPriorityAndFairness(handler, c.LongRunningFunc, c.FlowControl, requestWorkEstimator, c.RequestTimeout/4)
173+
handler = filterlatency.TrackStarted(handler, c.TracerProvider, "priorityandfairness")
174+
} else {
175+
handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight, c.MaxMutatingRequestsInFlight, c.LongRunningFunc)
176+
}
177+
178+
handler = filterlatency.TrackCompleted(handler)
179+
handler = genericapifilters.WithImpersonation(handler, c.Authorization.Authorizer, c.Serializer)
180+
handler = filterlatency.TrackStarted(handler, c.TracerProvider, "impersonation")
181+
182+
handler = filterlatency.TrackCompleted(handler)
183+
handler = genericapifilters.WithAudit(handler, c.AuditBackend, c.AuditPolicyRuleEvaluator, c.LongRunningFunc)
184+
handler = filterlatency.TrackStarted(handler, c.TracerProvider, "audit")
185+
186+
failedHandler := genericapifilters.Unauthorized(c.Serializer)
187+
failedHandler = genericapifilters.WithFailedAuthenticationAudit(failedHandler, c.AuditBackend, c.AuditPolicyRuleEvaluator)
188+
189+
failedHandler = filterlatency.TrackCompleted(failedHandler)
190+
handler = filterlatency.TrackCompleted(handler)
191+
handler = genericapifilters.WithAuthentication(handler, c.Authentication.Authenticator, failedHandler, c.Authentication.APIAudiences, c.Authentication.RequestHeaderConfig)
192+
handler = filterlatency.TrackStarted(handler, c.TracerProvider, "authentication")
193+
194+
handler = genericfilters.WithCORS(handler, c.CorsAllowedOriginList, nil, nil, nil, "true")
195+
196+
// WithWarningRecorder must be wrapped by the timeout handler
197+
// to make the addition of warning headers threadsafe
198+
handler = genericapifilters.WithWarningRecorder(handler)
199+
200+
// WithTimeoutForNonLongRunningRequests will call the rest of the request handling in a go-routine with the
201+
// context with deadline. The go-routine can keep running, while the timeout logic will return a timeout to the client.
202+
handler = genericfilters.WithTimeoutForNonLongRunningRequests(handler, c.LongRunningFunc)
203+
204+
handler = genericapifilters.WithRequestDeadline(handler, c.AuditBackend, c.AuditPolicyRuleEvaluator,
205+
c.LongRunningFunc, c.Serializer, c.RequestTimeout)
206+
handler = genericfilters.WithWaitGroup(handler, c.LongRunningFunc, c.NonLongRunningRequestWaitGroup)
207+
// if c.ShutdownWatchTerminationGracePeriod > 0 {
208+
// handler = genericfilters.WithWatchTerminationDuringShutdown(handler, c.lifecycleSignals, c.WatchRequestWaitGroup)
209+
// }
210+
if c.SecureServing != nil && !c.SecureServing.DisableHTTP2 && c.GoawayChance > 0 {
211+
handler = genericfilters.WithProbabilisticGoaway(handler, c.GoawayChance)
212+
}
213+
handler = genericapifilters.WithCacheControl(handler)
214+
handler = genericfilters.WithHSTS(handler, c.HSTSDirectives)
215+
// if c.ShutdownSendRetryAfter {
216+
// handler = genericfilters.WithRetryAfter(handler, c.lifecycleSignals.NotAcceptingNewRequest.Signaled())
217+
// }
218+
handler = genericfilters.WithHTTPLogging(handler)
219+
if c.FeatureGate.Enabled(genericfeatures.APIServerTracing) {
220+
handler = genericapifilters.WithTracing(handler, c.TracerProvider)
221+
}
222+
handler = genericapifilters.WithLatencyTrackers(handler)
223+
// WithRoutine will execute future handlers in a separate goroutine and serving
224+
// handler in current goroutine to minimize the stack memory usage. It must be
225+
// after WithPanicRecover() to be protected from panics.
226+
if c.FeatureGate.Enabled(genericfeatures.APIServingWithRoutine) {
227+
handler = routine.WithRoutine(handler, c.LongRunningFunc)
228+
}
229+
230+
handler = datumfilters.OrganizationProjectListConstraintDecorator(handler)
231+
handler = genericapifilters.WithRequestInfo(handler, c.RequestInfoResolver)
232+
handler = genericapifilters.WithRequestReceivedTimestamp(handler)
233+
// handler = genericapifilters.WithMuxAndDiscoveryComplete(handler, c.lifecycleSignals.MuxAndDiscoveryComplete.Signaled())
234+
handler = genericfilters.WithPanicRecovery(handler, c.RequestInfoResolver)
235+
handler = genericapifilters.WithAuditInit(handler)
236+
237+
handler = datumfilters.OrganizationContextHandler(handler, c.Serializer)
238+
239+
return handler
240+
}

0 commit comments

Comments
 (0)