Skip to content
This repository was archived by the owner on Oct 28, 2024. It is now read-only.

WIP: Adding initial proof of concept creating nested CRDs for each object #1

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 24 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
bin

# Test binary, build with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Kubernetes Generated files - skip generated files, except for vendored files

!vendor/**/zz_generated.*

# editor and IDE paraphernalia
.idea
*.swp
*.swo
*~
35 changes: 35 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Build the manager binary
FROM golang:1.13 as builder

WORKDIR /workspace
# Copy the Go Modules manifests
COPY go.mod go.mod
COPY go.sum go.sum
# cache deps before building and copying source so that we don't need to re-download as much
# and so that source changes don't invalidate our downloaded layer
RUN go mod download

# Copy the go source
COPY main.go main.go
COPY apis/ apis/
COPY cmd/ cmd/
COPY constants/ constants/
COPY controllers/ controllers/
COPY kubeconfig/ kubeconfig/
COPY pki/ pki/
COPY secret/ secret/
COPY utils/ utils/
COPY webhooks/ webhooks/


# Build
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o manager main.go

# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY --from=builder /workspace/manager .
USER nonroot:nonroot

ENTRYPOINT ["/manager"]
82 changes: 82 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@

# Image URL to use all building/pushing image targets
IMG ?= controller:latest
# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
CRD_OPTIONS ?= "crd:trivialVersions=true,crdVersions=v1"
# Selects specific packages to test
PACKAGES ?= "./..."

# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
GOBIN=$(shell go env GOPATH)/bin
else
GOBIN=$(shell go env GOBIN)
endif

all: manager

# Run tests
test: generate fmt vet manifests
go test ${PACKAGES} -coverprofile cover.out

# Build manager binary
manager: generate fmt vet
go build -o bin/manager ./cmd/manager/

# Run against the configured Kubernetes cluster in ~/.kube/config
run: generate fmt vet manifests
go run ./cmd/manager/

# Install CRDs into a cluster
install: manifests
kustomize build config/crd | kubectl apply -f -

# Uninstall CRDs from a cluster
uninstall: manifests
kustomize build config/crd | kubectl delete -f -

# Deploy controller in the configured Kubernetes cluster in ~/.kube/config
deploy: manifests
cd config/manager && kustomize edit set image controller=${IMG}
kustomize build config/default | kubectl apply -f -

# Generate manifests e.g. CRD, RBAC etc.
manifests: controller-gen
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases

# Run go fmt against code
fmt:
go fmt ./...

# Run go vet against code
vet:
go vet ./...

# Generate code
generate: controller-gen
$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."

# Build the docker image
docker-build: test
docker build . -t ${IMG}

# Push the docker image
docker-push:
docker push ${IMG}

# find or download controller-gen
# download controller-gen if necessary
controller-gen:
ifeq (, $(shell which controller-gen))
@{ \
set -e ;\
CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\
cd $$CONTROLLER_GEN_TMP_DIR ;\
go mod init tmp ;\
go get sigs.k8s.io/controller-tools/cmd/[email protected] ;\
rm -rf $$CONTROLLER_GEN_TMP_DIR ;\
}
CONTROLLER_GEN=$(GOBIN)/controller-gen
else
CONTROLLER_GEN=$(shell which controller-gen)
endif
17 changes: 17 additions & 0 deletions PROJECT
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
domain: cluster.x-k8s.io
multigroup: true
repo: sigs.k8s.io/cluster-api-provider-nested
resources:
- group: controlplane
kind: NestedControlPlane
version: v1alpha3
- group: controlplane
kind: NestedControlPlaneTemplate
version: v1alpha3
- group: controlplane
kind: NestedEtcd
version: v1alpha3
- group: controlplane
kind: NestedPKI
version: v1alpha3
version: "2"
33 changes: 33 additions & 0 deletions apis/apis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
Copyright 2020 The Kubernetes 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 apis

import (
"k8s.io/apimachinery/pkg/runtime"

controlplanev1alpha3 "sigs.k8s.io/cluster-api-provider-nested/apis/controlplane/v1alpha3"
// +kubebuilder:scaffold:imports
)

// AddToScheme will register all schemes needed to run CAPN
func AddToScheme(scheme *runtime.Scheme) (err error) {
if err = controlplanev1alpha3.AddToScheme(scheme); err != nil {
return err
}
// +kubebuilder:scaffold:scheme
return
}
36 changes: 36 additions & 0 deletions apis/controlplane/v1alpha3/groupversion_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
Copyright 2020 The Kubernetes 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 v1alpha3 contains API Schema definitions for the controlplane v1alpha3 API group
// +kubebuilder:object:generate=true
// +groupName=controlplane.cluster.x-k8s.io
package v1alpha3

import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)

var (
// GroupVersion is group version used to register these objects
GroupVersion = schema.GroupVersion{Group: "controlplane.cluster.x-k8s.io", Version: "v1alpha3"}

// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}

// AddToScheme adds the types in this group-version to the given scheme.
AddToScheme = SchemeBuilder.AddToScheme
)
107 changes: 107 additions & 0 deletions apis/controlplane/v1alpha3/nestedcontrolplane_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
Copyright 2020 The Kubernetes 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 v1alpha3

import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// NestedControlPlaneSpec defines the desired state of NestedControlPlane
type NestedControlPlaneSpec struct {
// ClusterNamespace allows you to specify the namespace where the control
// plane components are run, if you don't specify one then the namespace
// will be in the format of {namespace}-{vcp.uid}. This also makes up the
// prefixes used for all Namespaces in the cluster.
// e.g. {namespace}-{vcp.uid}-{vc-namespace}
// +optional
ClusterNamespace string `json:"clusterNamespace,omitempty"`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the vcp.uid? We should make these doc comments as clear as possible for the 'layman' user and avoid ambiguous terms if possible 😄

This also makes up the prefixes used for all Namespaces in the cluster. e.g. {namespace}-{vcp.uid}-{vc-namespace}

This seems okay for now. In future though it may be worth having some kind of namespacePrefix field to explicitly set this to some other value. It may be worth noting here that whilst it is true today, it should not be relied upon? If so, can we expose the chosen prefix through a status field to make it easier for other consumers of this API to make use of/build upon?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I think there needs to be a length restriction on this field. We should also call out how the length of this field influences the restrictions on namespace names within nested clusters too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for the namespacePrefix

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to call out this is typically defaulted and that it needs to have a max length validation and warning about it's usage for conflicting namespace prefixes, also rename to NamespacePrefix


// ClusterDomain allows you to specify the domain name of the nested
// cluster. e.g. a pod dns will be
// {some-pod}.{some-namespace}.svc.{ClusterDomain}
// +kubebuilder:default=cluster.local
ClusterDomain string `json:"clusterDomain,omitempty"`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should also have // +optional tags? Though perhaps kubebuilder automatically marks this as optional if it is omitempty? 👀


// TemplateRef allows you to specify the control plane template to create
// the control plane.
// +optional
TemplateRef *corev1.ObjectReference `json:"configRef,omitempty"`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to clarify on what a template is here:
is the NestedControlPlane controller meant to create additional CR instances out of this templateRef as in https://github.com/kubernetes-sigs/cluster-api/blob/master/controllers/machineset_controller.go#L344?
Or is this referencing a CR which fields input will just be consumed by the controller? If this is the latter we should may reconsider having "template" in the naming.

Copy link
Contributor

@charleszheng44 charleszheng44 Oct 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This TemplateRef will refer to an NestedControlPlaneTemplate object. There may exist multiple NestedControlPlaneTemplate objects, and each NestedControlPlane object will refer to one of them. The NestedControlPlane controller will then create CR NestedKAS, NestedEtcd and NestedKCM, And the NestedKAS, NestedEtcd and the NestedKCM controllers will actually start each component.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a reference to an object that itself describes how to provision the cluster? Does this refer to a NestedControlPlaneTemplate? I see this is marked optional - what happens if it is not specified?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should create a default NestedControlPlaneTemplate, which will be used when the TemplateRef is not set.


// PKIExpireDays allows you to specify the period of time the nested
// cluster PKI, if not set the PKI will expire after 1 year.
// +optional
// +kubebuilder:default=365
PKIExpireDays int64 `json:"pkiExpireDays,omitempty"`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw PKI and had some quick thoughts:

  • it feels rather coarse to set the expiry at the granularity of the whole PKI, rather than specific credentials
  • are you thinking about rotation yet?
  • Why put this in NestedControlPlaneSpec instead of NestedPKI?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should err on the side of caution with adding fields. If we don't need to expose this as a user-configurable option yet, I think we should not do it. The above questions raise great points, and I am not sure we have enough of an understanding of the problem space to know where best to put it just yet.

Why put this in NestedControlPlaneSpec instead of NestedPKI?

👍 - I also think this is a question we should answer more generally. What is the purpose of the NestedControlPlane vs the NestedControlPlaneTemplate resource? What defines what should go where?


// TransparentMetaPrefixes allows you to specify the key prefix of labels
// or annotations that should be back populated to Nested Cluster. These
// meta data are generated by super master controllers, which are needed by
// nested cluster to interact with external systems.
// +optional
TransparentMetaPrefixes []string `json:"transparentMetaPrefixes,omitempty"`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not too sure what this means - does "back populated" mean synced? It'd really help if we had a proposal document that explained some of this stuff and set out some definitions.


// OpaqueMetaPrefixes allows you to specify the key prefix of labels or
// annotations that are not visible to Nested Cluster but are kept in
// super master. For example, the annotations added by syncer controller.
// +optional
OpaqueMetaPrefixes []string `json:"opaqueMetaPrefixes,omitempty"`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be removed an implemented somewhere else since this specific to VC

}

// NestedControlPlaneStatus defines the observed state of NestedControlPlane
type NestedControlPlaneStatus struct {
// ClusterNamespace denotes the namespace where the control plane components
// are deployed into.
// +optional
ClusterNamespace string `json:"clusterNamespace,omitempty"`

// Ready denotes that the NestedControlPlane API Server is ready to
// receive requests and that the VPC infra is ready.
// +optional
Ready bool `json:"ready"`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this field should exist as-is. If we are going to have an overarching concept of readiness, it should be denoted as a condition. That said, I think we should be careful to clearly define what we call ready or not. If an apiserver is failing 50% of requests, is it ready?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can probably use ReadyReplicas since we'll likely have multiple replicas, this is pulled from the Control Plane types for CAPI -https://cluster-api.sigs.k8s.io/developer/architecture/controllers/control-plane.html#required-status-fields


Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should probably also include a field to signal error here. E.g

	// ErrorMessage indicates that there is a terminal problem reconciling the
	// state, and will be set to a descriptive error message.

Probably to be done in hand with conditions.
	// +optional
	FailureMessage *string `json:"failureMessage,omitempty"`

Though this can probably be detailed later when introducing conditions

// Conditions specifies the cpnditions for the managed control plane
// Conditions clusterv1.Conditions `json:"conditions,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we going to use the Conditions? I think it could be useful for debugging and monitoring.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, we need to have conditions on these types. This should follow the base required fields from CAPI - https://cluster-api.sigs.k8s.io/developer/architecture/controllers/control-plane.html

}

// +kubebuilder:storageversion
// +kubebuilder:subresource:status
// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Namespaced,categories=cluster-api;capn;capi,shortName=ncp
// +kubebuilder:printcolumn:name="ClusterNamespace",type="string",JSONPath=".status.clusterNamespace",description="Namespace of the cluster",priority=1

// NestedControlPlane is the Schema for the nestedcontrolplanes API
type NestedControlPlane struct {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just out of curiosity if there has been any discussion for naming nested vs hosted?

metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec NestedControlPlaneSpec `json:"spec,omitempty"`
Status NestedControlPlaneStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// NestedControlPlaneList contains a list of NestedControlPlane
type NestedControlPlaneList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []NestedControlPlane `json:"items"`
}

func init() {
SchemeBuilder.Register(&NestedControlPlane{}, &NestedControlPlaneList{})
}
56 changes: 56 additions & 0 deletions apis/controlplane/v1alpha3/nestedcontrolplane_webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
Copyright 2020 The Kubernetes 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 v1alpha3

import (
"crypto/sha256"
"encoding/hex"
"strings"

ctrl "sigs.k8s.io/controller-runtime"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
)

// log is for logging in this package.
var nestedcontrolplanelog = logf.Log.WithName("nestedcontrolplane-resource")

func (r *NestedControlPlane) SetupWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(r).
Complete()
}

// TODO(christopherhein): we need to add validations here on whether the root
// namespace isn't taken

// +kubebuilder:webhook:path=/mutate/controlplane/v1alpha3/nestedcontrolplane,mutating=true,failurePolicy=fail,groups=controlplane.cluster.x-k8s.io,resources=nestedcontrolplanes,verbs=create;update,versions=v1alpha3,name=mnestedcontrolplane.kb.io

var _ webhook.Defaulter = &NestedControlPlane{}

// Default implements webhook.Defaulter so a webhook will be registered for the type
func (r *NestedControlPlane) Default() {
nestedcontrolplanelog.Info("default", "name", r.Name)

// if clusterNamespace not set then set using {namespace}-{vcp.uid}-{vc-namespace}
if r.Spec.ClusterNamespace == "" {
digest := sha256.Sum256([]byte(r.GetUID()))
namespaceSlice := []string{r.Namespace, hex.EncodeToString(digest[0:])[0:6], r.Name}
namespace := strings.Join(namespaceSlice, "-")
r.Spec.ClusterNamespace = namespace
}
}
Loading