diff --git a/go.mod b/go.mod index d632670038..abf3a6a96a 100644 --- a/go.mod +++ b/go.mod @@ -47,6 +47,7 @@ require ( github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.5 // indirect github.com/go-openapi/swag v0.19.15 // indirect + github.com/gobuffalo/flect v0.2.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect diff --git a/go.sum b/go.sum index 5ab97a262e..da77261fca 100644 --- a/go.sum +++ b/go.sum @@ -146,6 +146,8 @@ github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyr github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/flect v0.2.4 h1:BSYA8+T60cdyq+vynaSUjqSVI9mDEg9ZfQUXKmfjo4I= +github.com/gobuffalo/flect v0.2.4/go.mod h1:1ZyCLIbg0YD7sDkzvFdPoOydPtD8y9JQnrOROolUcM8= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= diff --git a/pkg/webhooks/webhooks.go b/pkg/webhooks/webhooks.go new file mode 100644 index 0000000000..3dbb7a9604 --- /dev/null +++ b/pkg/webhooks/webhooks.go @@ -0,0 +1,133 @@ +/* +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 webhooks + +import ( + "context" + "flag" + "fmt" + "runtime/debug" + + "k8s.io/client-go/kubernetes" + "knative.dev/pkg/configmap" + "knative.dev/pkg/configmap/informer" + "knative.dev/pkg/controller" + knativeinjection "knative.dev/pkg/injection" + "knative.dev/pkg/injection/sharedmain" + "knative.dev/pkg/logging" + "knative.dev/pkg/system" + "knative.dev/pkg/webhook" + "knative.dev/pkg/webhook/certificates" + "knative.dev/pkg/webhook/configmaps" + "knative.dev/pkg/webhook/resourcesemantics/defaulting" + "knative.dev/pkg/webhook/resourcesemantics/validation" + + "github.com/aws/karpenter-core/pkg/apis" + "github.com/aws/karpenter-core/pkg/cloudprovider" + "github.com/aws/karpenter-core/pkg/operator/injection" + "github.com/aws/karpenter-core/pkg/utils/env" +) + +type WebhookOpts struct { + KarpenterService string + WebhookPort int + MemoryLimit int64 +} + +var ( + component = "webhook" + opts = WebhookOpts{} +) + +func init() { + flag.StringVar(&opts.KarpenterService, "karpenter-service", env.WithDefaultString("KARPENTER_SERVICE", ""), "The Karpenter Service name for the dynamic webhook certificate") + flag.IntVar(&opts.WebhookPort, "port", env.WithDefaultInt("PORT", 8443), "The port the webhook endpoint binds to for validation and mutation of resources") + flag.Int64Var(&opts.MemoryLimit, "memory-limit", env.WithDefaultInt64("MEMORY_LIMIT", -1), "Memory limit on the container running the webhook. The GC soft memory limit is set to 90% of this value.") +} + +func Initialize(injectCloudProvider func(cloudprovider.Context) cloudprovider.CloudProvider) { + config := knativeinjection.ParseAndGetRESTConfigOrDie() + + // Set up logger and watch for changes to log level + clientSet := kubernetes.NewForConfigOrDie(config) + cmw := informer.NewInformedWatcher(clientSet, system.Namespace()) + ctx := injection.LoggingContextOrDie(component, config, cmw) + ctx = knativeinjection.WithNamespaceScope(ctx, system.Namespace()) + + ctx = webhook.WithOptions(ctx, webhook.Options{ + Port: opts.WebhookPort, + ServiceName: opts.KarpenterService, + SecretName: fmt.Sprintf("%s-cert", opts.KarpenterService), + }) + // TODO: This can be removed if we eventually decide that we need leader election. Having leader election has resulted in the webhook + // having issues described in https://github.com/aws/karpenter/issues/2562 so these issues need to be resolved if this line is removed + ctx = sharedmain.WithHADisabled(ctx) // Disable leader election for webhook + + // Register the cloud provider to attach vendor specific validation logic. + // TODO(https://github.com/aws/karpenter/issues/2052) + injectCloudProvider(cloudprovider.Context{ + Context: ctx, + ClientSet: clientSet, + WebhookOnly: true, + }) + + if opts.MemoryLimit > 0 { + newLimit := int64(float64(opts.MemoryLimit) * 0.9) + logging.FromContext(ctx).Infof("Setting GC memory limit to %d, container limit = %d", newLimit, opts.MemoryLimit) + debug.SetMemoryLimit(newLimit) + } + + // Controllers and webhook + sharedmain.MainWithConfig(ctx, "webhook", config, + certificates.NewController, + newCRDDefaultingWebhook, + newCRDValidationWebhook, + newConfigValidationController, + ) +} + +func newCRDDefaultingWebhook(ctx context.Context, w configmap.Watcher) *controller.Impl { + return defaulting.NewAdmissionController(ctx, + "defaulting.webhook.provisioners.karpenter.sh", + "/default-resource", + apis.Resources, + InjectContext, + true, + ) +} + +func newCRDValidationWebhook(ctx context.Context, w configmap.Watcher) *controller.Impl { + return validation.NewAdmissionController(ctx, + "validation.webhook.provisioners.karpenter.sh", + "/validate-resource", + apis.Resources, + InjectContext, + true, + ) +} + +func newConfigValidationController(ctx context.Context, cmw configmap.Watcher) *controller.Impl { + return configmaps.NewAdmissionController(ctx, + "validation.webhook.config.karpenter.sh", + "/config-validation", + configmap.Constructors{ + logging.ConfigMapName(): logging.NewConfigFromConfigMap, + }, + ) +} + +func InjectContext(ctx context.Context) context.Context { + return ctx +}