Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto-discover caBundle when missing (take 2) #655

Merged
merged 38 commits into from
Sep 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
e837649
checkpoint: tests passin'
Aug 24, 2021
e10fb5b
minor cleanups
Aug 24, 2021
9dac2a7
add another test for aws required fields
Aug 25, 2021
36a0171
validate caBundle
Aug 25, 2021
fd42609
fix PR comment
Aug 25, 2021
cd41be0
checkpoint
Aug 26, 2021
e156de6
checkpoint - add test for defaulting in CABundle
Aug 26, 2021
9f651dd
remove commented-out code
Aug 26, 2021
7821221
fix broken os.FS unit test mock
Aug 29, 2021
959e010
clean up stray debug lines
Aug 29, 2021
b10e020
remove unnecessary inject
Aug 29, 2021
0fcbf2e
remove unnecessary filesys.Inject
Aug 29, 2021
3bed884
minor formatting
Aug 29, 2021
3ac42bc
remove unnecessary import
Aug 29, 2021
85a42ec
fix stray debugging change
Aug 29, 2021
597cda4
add helpful comment that explains why test actually works
Aug 29, 2021
7b16a0c
address PR comments
Aug 30, 2021
77261ba
address pr fback
Aug 30, 2021
26b3a26
switch to afero everywhere because io/fs just doesn't like
Aug 30, 2021
bfc49a0
minor cleanup
Aug 30, 2021
6020a69
Address PR feedback from rustrial@
Aug 30, 2021
fdde3e2
address etarn pr fback
Aug 30, 2021
04f03a7
update docs
Aug 30, 2021
2a06043
remove no longer valid test
Aug 31, 2021
c6b5a17
remove stray line
Aug 31, 2021
6102681
pr fback
Aug 31, 2021
e34f3e6
revert changes
Aug 31, 2021
aab7f06
undo my changes hrere
Aug 31, 2021
a102b01
start to migrate clusterCA default code to cloudprovider logic
Aug 31, 2021
89d9b13
move CABundle dynamic loading to launch template generation time
Sep 1, 2021
9ccb362
switch to using client-go to get CABundle
Sep 1, 2021
11b0254
revert
Sep 1, 2021
7e8689d
remove stray line
Sep 1, 2021
a871be6
use GetConfigOrDie() from controllerruntime ...
Sep 2, 2021
ae1a43b
Fix problems with previous commit
Sep 2, 2021
cbe7b46
add helpful comment
Sep 2, 2021
a23cfe0
fix pr comments
Sep 2, 2021
e75ebd1
fix bug in error handling
Sep 2, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions cmd/controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/awslabs/karpenter/pkg/controllers/allocation"
"github.com/awslabs/karpenter/pkg/controllers/node"
"github.com/awslabs/karpenter/pkg/controllers/termination"
"github.com/awslabs/karpenter/pkg/utils/restconfig"
"github.com/go-logr/zapr"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
Expand Down Expand Up @@ -66,10 +67,14 @@ func main() {
config := controllerruntime.GetConfigOrDie()
clientSet := kubernetes.NewForConfigOrDie(config)

// 1. Setup logger and watch for changes to log level
// 1. Set up logger and watch for changes to log level
ctx := LoggingContextOrDie(config, clientSet)

// 2. Setup controller runtime controller
// 2. Put REST config in context, as it can be used by arbitrary
// parts of the code base
ctx = restconfig.Inject(ctx, config)

// 3. Set up controller runtime controller
cloudProvider := registry.NewCloudProvider(ctx, cloudprovider.Options{ClientSet: clientSet})
manager := controllers.NewManagerOrDie(config, controllerruntime.Options{
Logger: zapr.NewLogger(logging.FromContext(ctx).Desugar()),
Expand Down
260 changes: 260 additions & 0 deletions go.sum

Large diffs are not rendered by default.

76 changes: 27 additions & 49 deletions pkg/apis/provisioning/v1alpha3/provisioner_defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,65 +17,43 @@ package v1alpha3
import (
"context"
"encoding/base64"
"errors"
"io/ioutil"
"os"
)

const (
InClusterCABundlePath = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
"github.com/awslabs/karpenter/pkg/utils/restconfig"
"k8s.io/client-go/transport"
"knative.dev/pkg/logging"
"knative.dev/pkg/ptr"
)

// SetDefaults for the provisioner
func (p *Provisioner) SetDefaults(ctx context.Context) {}

// SetDefaults for the provisioner, cascading to all subspecs
func (s *ProvisionerSpec) SetDefaults(ctx context.Context) {}

// WithDefaults returns a copy of this Provisioner with some empty/missing
// properties replaced by (potentially dynamic) cloud provider agnostic default values.
// The returned copy might be complemented by dynamic default values which
// must not be hoisted (saved) into the original Provisioner CRD as those
// default values might change over time (e.g. rolling upgrade of CABundle, ...).
func (p *Provisioner) WithDynamicDefaults(ctx context.Context) (_ Provisioner, err error) {
provisioner := *p.DeepCopy()
provisioner.Spec, err = provisioner.Spec.withDynamicDefaults()
return provisioner, err
}

// WithDefaults returns a copy of this ProvisionerSpec with some empty
// properties replaced by default values.
func (s *ProvisionerSpec) withDynamicDefaults() (_ ProvisionerSpec, err error) {
spec := *s.DeepCopy()
spec.Cluster, err = spec.Cluster.withDynamicDefaults()
return spec, err
}

// WithDefaults returns a copy of this Cluster with some empty
// properties replaced by default values. Notably, it will try
// to load the CABundle from the in-cluster configuraiton if it
// is not explicitly set.
func (c *Cluster) withDynamicDefaults() (_ Cluster, err error) {
cluster := *c.DeepCopy()
cluster.CABundle, err = cluster.getCABundle()
return cluster, err
}

func (c *Cluster) getCABundle() (*string, error) {
func (c *Cluster) GetCABundle(ctx context.Context) (*string, error) {
if c.CABundle != nil {
// If CABundle is explicitly provided use that one. An empty string is
// a valid value here if the intention is to disable the in-cluster CABundle
// and using the HTTP client's default trust-store (CABundle) instead.
// If CABundle is explicitly provided, use that one. An empty
// string is a valid value here if the intention is to disable
// the in-cluster CABundle, and using the HTTP client's
// default trust-store (CABundle) instead.
return c.CABundle, nil
}
// Otherwise, fallback to the in-cluster configuration.
binary, err := ioutil.ReadFile(InClusterCABundlePath)

// Discover CA Bundle from the REST client. We could alternatively
// have used the simpler client-go InClusterConfig() method.
// However, that only works when Karpenter is running as a Pod
// within the same cluster it's managing.
restConfig := restconfig.Get(ctx)
if restConfig == nil {
return nil, nil
}
transportConfig, err := restConfig.TransportConfig()
if err != nil {
logging.FromContext(ctx).Debugf("Unable to discover caBundle, loading transport config, %v", err)
return nil, err
}
_, err = transport.TLSConfigFor(transportConfig) // fills in CAData!
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil, nil
}
logging.FromContext(ctx).Debugf("Unable to discover caBundle, loading TLS config, %v", err)
return nil, err
}
encoded := base64.StdEncoding.EncodeToString(binary)
return &encoded, nil
logging.FromContext(ctx).Debugf("Discovered caBundle, length %d", len(transportConfig.TLS.CAData))
return ptr.String(base64.StdEncoding.EncodeToString(transportConfig.TLS.CAData)), nil
}
3 changes: 2 additions & 1 deletion pkg/cloudprovider/aws/cloudprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
v1 "k8s.io/api/core/v1"
"knative.dev/pkg/apis"
"knative.dev/pkg/logging"
"knative.dev/pkg/ptr"
)

const (
Expand Down Expand Up @@ -174,7 +175,7 @@ func (c *CloudProvider) ValidateConstraints(ctx context.Context, constraints *v1

// Validate cloud provider specific components of the cluster spec.
func (c *CloudProvider) ValidateSpec(ctx context.Context, spec *v1alpha3.ProvisionerSpec) (errs *apis.FieldError) {
if spec.Cluster.Name == nil || len(*spec.Cluster.Name) == 0 {
if ptr.StringValue(spec.Cluster.Name) == "" {
errs = errs.Also(apis.ErrMissingField("name")).ViaField("cluster")
}
return errs
Expand Down
51 changes: 29 additions & 22 deletions pkg/cloudprovider/aws/launchtemplate.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"context"
"encoding/base64"
"fmt"
"text/template"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
Expand All @@ -35,18 +34,6 @@ import (

const (
launchTemplateNameFormat = "Karpenter-%s-%s"
bottlerocketUserData = `
[settings.kubernetes]
api-server = "{{.Cluster.Endpoint}}"
{{if .Cluster.CABundle}}{{if len .Cluster.CABundle}}cluster-certificate = "{{.Cluster.CABundle}}"{{end}}{{end}}
cluster-name = "{{if .Cluster.Name}}{{.Cluster.Name}}{{end}}"
{{if .Constraints.Labels }}[settings.kubernetes.node-labels]{{ end }}
{{ range $Key, $Value := .Constraints.Labels }}"{{ $Key }}" = "{{ $Value }}"
{{ end }}
{{if .Constraints.Taints }}[settings.kubernetes.node-taints]{{ end }}
{{ range $Taint := .Constraints.Taints }}"{{ $Taint.Key }}" = "{{ $Taint.Value}}:{{ $Taint.Effect }}"
{{ end }}
`
)

type LaunchTemplateProvider struct {
Expand Down Expand Up @@ -103,10 +90,16 @@ func (p *LaunchTemplateProvider) Get(ctx context.Context, provisioner *v1alpha3.
return nil, err
}

// 3. Get userData for Node
userData, err := p.getUserData(ctx, provisioner, constraints)
if err != nil {
return nil, err
}

// 4. Ensure the launch template exists, or create it
launchTemplate, err := p.ensureLaunchTemplate(ctx, &launchTemplateOptions{
Cluster: provisioner.Spec.Cluster,
UserData: p.getUserData(provisioner, constraints),
UserData: userData,
AMIID: amiID,
SecurityGroups: securityGroups,
})
Expand Down Expand Up @@ -197,14 +190,28 @@ func (p *LaunchTemplateProvider) getSecurityGroupIds(ctx context.Context, provis
return securityGroupIds, nil
}

func (p *LaunchTemplateProvider) getUserData(provisioner *v1alpha3.Provisioner, constraints *Constraints) string {
t := template.Must(template.New("userData").Parse(bottlerocketUserData))
func (p *LaunchTemplateProvider) getUserData(ctx context.Context, provisioner *v1alpha3.Provisioner, constraints *Constraints) (string, error) {
var userData bytes.Buffer
if err := t.Execute(&userData, struct {
Constraints *Constraints
Cluster v1alpha3.Cluster
}{constraints, provisioner.Spec.Cluster}); err != nil {
panic(fmt.Sprintf("Parsing user data from %v, %v, %s", provisioner, constraints, err.Error()))
userData.WriteString(fmt.Sprintf("[settings.kubernetes]\napi-server = \"%s\"\n", provisioner.Spec.Cluster.Endpoint))
userData.WriteString(fmt.Sprintf("cluster-name = \"%s\"\n", *provisioner.Spec.Cluster.Name))
caBundle, err := provisioner.Spec.Cluster.GetCABundle(ctx)
if err != nil {
return "", fmt.Errorf("getting user data, %w", err)
}
if caBundle != nil {
userData.WriteString(fmt.Sprintf("cluster-certificate = \"%s\"\n", *caBundle))
}
if len(constraints.Labels) > 0 {
userData.WriteString("[settings.kubernetes.node-labels]\n")
for k, v := range constraints.Labels {
userData.WriteString(fmt.Sprintf("\"%s\" = \"%v\"\n", k, v))
}
}
if len(constraints.Taints) > 0 {
userData.WriteString("[settings.kubernetes.node-taints]\n")
for _, taint := range constraints.Taints {
userData.WriteString(fmt.Sprintf("\"%s\" = \"%s:%s\"\n", taint.Key, taint.Value, taint.Effect))
}
}
return base64.StdEncoding.EncodeToString(userData.Bytes())
return base64.StdEncoding.EncodeToString(userData.Bytes()), nil
}
6 changes: 3 additions & 3 deletions pkg/cloudprovider/aws/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ var _ = Describe("Allocation", func() {
launchTemplate := input.LaunchTemplateConfigs[0].LaunchTemplateSpecification
Expect(*launchTemplate.LaunchTemplateName).To(Equal(pods[0].Spec.NodeSelector[LaunchTemplateNameLabel]))
})
It("should allow a pod to override the launch template id and use the default launch template version", func() {
It("should allow a pod to override the launch template name and use the default launch template version", func() {
// Setup
provisioner.Spec.Labels = map[string]string{LaunchTemplateNameLabel: randomdata.SillyName()}
ExpectCreated(env.Client, provisioner)
Expand All @@ -352,7 +352,7 @@ var _ = Describe("Allocation", func() {
Expect(*launchTemplate.LaunchTemplateName).To(Equal(pods[0].Spec.NodeSelector[LaunchTemplateNameLabel]))
Expect(*launchTemplate.Version).To(Equal(DefaultLaunchTemplateVersion))
})
It("should allow a pod to override the launch template id and use the provisioner's launch template version", func() {
It("should allow a pod to override the launch template name and use the provisioner's launch template version", func() {
// Setup
provisioner.Spec.Labels = map[string]string{
LaunchTemplateNameLabel: randomdata.SillyName(),
Expand All @@ -372,7 +372,7 @@ var _ = Describe("Allocation", func() {
})
})
Context("Subnets", func() {
It("should default to the clusters subnets", func() {
It("should default to the cluster's subnets", func() {
// Setup
provisioner.Spec.InstanceTypes = []string{"m5.large"} // limit instance type to simplify ConsistOf checks
ExpectCreated(env.Client, provisioner)
Expand Down
10 changes: 1 addition & 9 deletions pkg/controllers/allocation/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,15 +172,7 @@ func (c *Controller) provisionerFor(ctx context.Context, name types.NamespacedNa
if err := c.KubeClient.Get(ctx, name, provisioner); err != nil {
return nil, err
}

// Hydrate provisioner with (dynamic) default values, which must not
// be persisted into the original CRD as they might change with each reconciliation
// loop iteration.
defaulted, err := provisioner.WithDynamicDefaults(ctx)
if err != nil {
return &defaulted, fmt.Errorf("setting dynamic default values, %w", err)
}
return &defaulted, nil
return provisioner, nil
}

// podToProvisioner is a function handler to transform pod objs to provisioner reconcile requests
Expand Down
1 change: 0 additions & 1 deletion pkg/controllers/allocation/scheduling/scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ func (s *Scheduler) getDaemons(ctx context.Context, node *v1.Node) ([]*v1.Pod, e
return pods, nil
}


// IsSchedulable returns true if the pod can schedule to the node
func IsSchedulable(pod *v1.Pod, node *v1.Node) bool {
// Tolerate Taints
Expand Down
21 changes: 21 additions & 0 deletions pkg/utils/restconfig/restconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package restconfig

import (
"context"

"k8s.io/client-go/rest"
)

type contextKey struct{}

func Inject(ctx context.Context, config *rest.Config) context.Context {
return context.WithValue(ctx, contextKey{}, config)
}

func Get(ctx context.Context) *rest.Config {
retval := ctx.Value(contextKey{})
if retval == nil {
return nil
}
return retval.(*rest.Config)
}
8 changes: 4 additions & 4 deletions website/content/en/docs/provisioner-crd.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ spec:
# Provisioned nodes will have these labels
labels:
##### AWS Specific #####
# Constrain node launch template, default="bottlerocket"
node.k8s.aws/launch-template-id: "bottlerocket-qwertyuiop"
# Constrain node launch template, default="$LATEST"
node.k8s.aws/launch-template-version: "my-special-version"
# Constrain node launch template ('$Default' version always used).
# If not specified, Karpenter will generate a Bottlerocket-
# based launch template dynamically.
node.k8s.aws/launch-template-name: "my-launch-template-name"
# Constrain node capacity type, default="on-demand"
node.k8s.aws/capacity-type: "spot"
```