Skip to content

Commit

Permalink
Merge pull request crossplane#1338 from displague/truncate-names
Browse files Browse the repository at this point in the history
Stacks: Truncate resource names and labels for host-aware environments
  • Loading branch information
jbw976 authored Mar 23, 2020
2 parents b8bcffe + 40d57de commit dca7f3d
Show file tree
Hide file tree
Showing 11 changed files with 986 additions and 87 deletions.
79 changes: 79 additions & 0 deletions docs/troubleshoot.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ indent: true
* [Crossplane Logs](#crossplane-logs)
* [Pausing Crossplane](#pausing-crossplane)
* [Deleting a Resource Hangs](#deleting-a-resource-hangs)
* [Host-Aware Resource Debugging](#host-aware-resource-debugging)

## Using the trace command

Expand Down Expand Up @@ -131,3 +132,81 @@ For example, for a Workload object (`workloads.compute.crossplane.io`) named
```console
kubectl patch workloads.compute.crossplane.io test-workload -p '{"metadata":{"finalizers": []}}' --type=merge
```

## Host-Aware Resource Debugging

Stack resources (including the Stack, service accounts, deployments, and jobs)
are usually easy to identify by name. These resource names are based on the name
used in the StackInstall or Stack resource.

In some cases, the full name of a Stack resource, which could be up to 253
characters long, can not be represented in the created resources. For example,
jobs and deployment names may not exceed 63 characters because these names are
turned into resource label values which impose a 63 character limit. Stack
created resources whose names would otherwise not be permitted in the Kubernetes
API will be truncated with a unique suffix.

When running the Stack Manager in host-aware mode, tenant stack resources
created in the host controller namespace generally reuse the Stack names:
"{tenant namespace}.{tenant name}". In order to avoid the name length
restrictions, these resources may be truncated at either or both of the
namespace segment or over the entire name. In these cases resource labels,
owner references, and annotations should be consulted to identify the
responsible Stack.

* [Relationship Labels](https://github.com/crossplane/crossplane/blob/master/design/one-pager-stack-relationship-labels.md)
* [Owner References](https://kubernetes.io/docs/concepts/workloads/controllers/garbage-collection/#owners-and-dependents)
* Annotations: `tenant.crossplane.io/{singular}-name` and
`tenant.crossplane.io/{singular}-namespace` (_singular_ may be `stackinstall`,
`clusterstackinstall` or `stack`)

### Example

Long resource names may be present on the tenant.

```console
$ name=stack-with-a-really-long-resource-name-so-long-that-it-will-be-truncated
$ ns=this-is-just-a-really-long-namespace-name-at-the-character-max
$ kubectl create ns $ns
$ kubectl crossplane stack install --namespace $ns crossplane/sample-stack-wordpress:0.1.1 $name
```

When used as host resource names, the stack namespace and stack are concatenated
to form host names, as well as labels. These resource names and label values
must be truncated to fit the 63 character limit on label values.

```console
$ kubectl --context=crossplane-host -n tenant-controllers get job -o yaml
apiVersion: v1
items:
- apiVersion: batch/v1
kind: Job
metadata:
annotations:
tenant.crossplane.io/stackinstall-name: stack-with-a-really-long-resource-name-so-long-that-it-will-be-truncated
tenant.crossplane.io/stackinstall-namespace: this-is-just-a-really-long-namespace-name-at-the-character-max
creationTimestamp: "2020-03-20T17:06:25Z"
labels:
core.crossplane.io/parent-group: stacks.crossplane.io
core.crossplane.io/parent-kind: StackInstall
core.crossplane.io/parent-name: stack-with-a-really-long-resource-name-so-long-that-it-wi-alqdw
core.crossplane.io/parent-namespace: this-is-just-a-really-long-namespace-name-at-the-character-max
core.crossplane.io/parent-uid: 596705e4-a28e-47c9-a907-d2732f07a85e
core.crossplane.io/parent-version: v1alpha1
name: this-is-just-a-really-long-namespace-name-at-the-characte-egoav
namespace: tenant-controllers
spec:
backoffLimit: 0
completions: 1
parallelism: 1
selector:
matchLabels:
controller-uid: 8f290bf2-8c91-494a-a76b-27c2ccb9e0a8
template:
metadata:
creationTimestamp: null
labels:
controller-uid: 8f290bf2-8c91-494a-a76b-27c2ccb9e0a8
job-name: this-is-just-a-really-long-namespace-name-at-the-characte-egoav
...
```
41 changes: 39 additions & 2 deletions pkg/controller/stacks/hosted/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,22 @@ import (

"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"

"github.com/crossplane/crossplane/pkg/stacks/truncate"
)

const (
// AnnotationTenantNameFmt with a CR `singular` name applied provides the
// annotation key used to identify tenant resources by name on the host side
// Example: tenant.crossplane.io/stackinstall-name
AnnotationTenantNameFmt = "tenant.crossplane.io/%s-name"

// AnnotationTenantNamespaceFmt with a CR `singular` name applied provides
// the annotation key used to identify tenant resources by namespace on the
// host side
// Example: tenant.crossplane.io/stack-namespace
AnnotationTenantNamespaceFmt = "tenant.crossplane.io/%s-namespace"

errMissingOption = "host aware mode activated but %s is not set"
)

Expand Down Expand Up @@ -59,14 +72,38 @@ func NewConfig(hostControllerNamespace, tenantAPIServiceHost, tenantAPIServicePo
}, nil
}

// ObjectReferenceOnHost maps object with given name and namespace into single controller namespace on Host Cluster.
// ObjectReferenceOnHost maps objects with a given name and namespace into a
// single controller namespace on the Host Cluster.
//
// The resource name on the host cluster may be truncated from the original
// tenant name to fit label value length. The resource name may be used as a
// label, as is the case for jobs and deployments where the admission controller
// generates labels based on the resource name.
func (c *Config) ObjectReferenceOnHost(name, namespace string) corev1.ObjectReference {
return corev1.ObjectReference{
Name: fmt.Sprintf("%s.%s", namespace, name),
Name: truncate.LabelValue(fmt.Sprintf("%s.%s", namespace, name)),
Namespace: c.HostControllerNamespace,
}
}

// ObjectReferenceAnnotationsOnHost returns a map for use as annotations on the
// host to identify the named tenant resource. This annotation is used for
// reference purposes to define a relationship to a single resource of a
// specific kind. For example, this could be used to declare the tenant
// stackinstall resource that is related to a host install job.
//
// On a host the original tenant resource name may be truncated away.
// Annotations provide a way to store the original name without truncation.
func ObjectReferenceAnnotationsOnHost(singular, name, namespace string) map[string]string {
nameLabel := fmt.Sprintf(AnnotationTenantNameFmt, singular)
nsLabel := fmt.Sprintf(AnnotationTenantNamespaceFmt, singular)

return map[string]string{
nameLabel: name,
nsLabel: namespace,
}
}

// NewConfigForHost returns a host aware config given a controller namespace
// and a Host string, assumed to be in the format accepted by rest.Config. It
// returns a nil Config if either the supplied namespace or host are empty.
Expand Down
8 changes: 5 additions & 3 deletions pkg/controller/stacks/install/installjob.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,17 @@ type prepareInstallJobParams struct {
stackManagerPullPolicy corev1.PullPolicy
imagePullPolicy corev1.PullPolicy
labels map[string]string
annotations map[string]string
imagePullSecrets []corev1.LocalObjectReference
}

func prepareInstallJob(p prepareInstallJobParams) *batchv1.Job {
return &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: p.name,
Namespace: p.namespace,
Labels: p.labels,
Name: p.name,
Namespace: p.namespace,
Labels: p.labels,
Annotations: p.annotations,
},
Spec: batchv1.JobSpec{
BackoffLimit: &jobBackoff,
Expand Down
9 changes: 9 additions & 0 deletions pkg/controller/stacks/install/stackinstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,8 @@ func (h *stackInstallHandler) create(ctx context.Context) (reconcile.Result, err
}

func (h *stackInstallHandler) createInstallJob() *batchv1.Job {
var annotations map[string]string

i := h.ext
executorInfo := h.executorInfo
hCfg := h.hostAwareConfig
Expand All @@ -344,6 +346,12 @@ func (h *stackInstallHandler) createInstallJob() *batchv1.Job {
namespace := i.GetNamespace()

if hCfg != nil {
singular := "stackinstall"
if i.PermissionScope() == string(apiextensionsv1beta1.ClusterScoped) {
singular = "clusterstackinstall"
}
annotations = hosted.ObjectReferenceAnnotationsOnHost(singular, name, namespace)

// In Hosted Mode, we need to map all install jobs on tenant Kubernetes into a single namespace on host cluster.
o := hCfg.ObjectReferenceOnHost(name, namespace)
name = o.Name
Expand All @@ -368,6 +376,7 @@ func (h *stackInstallHandler) createInstallJob() *batchv1.Job {
stackManagerPullPolicy: executorInfo.ImagePullPolicy,
imagePullPolicy: i.GetImagePullPolicy(),
labels: stacks.ParentLabels(i),
annotations: annotations,
imagePullSecrets: i.GetImagePullSecrets()})
}

Expand Down
5 changes: 4 additions & 1 deletion pkg/controller/stacks/install/stackinstall_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -788,8 +788,11 @@ func Test_stackInstallHandler_deleteOrphanedCRDs(t *testing.T) {
)

var (
label = fmt.Sprintf(stacks.LabelMultiParentFormat, namespace, resourceName)
nsLabel = fmt.Sprintf(stacks.LabelNamespaceFmt, namespace)

// MultiParentLabels refer to a stack name. While resource() is a
// stackinstall, it is an object that has a name that matches the stack
label = stacks.MultiParentLabel(resource())
)
tests := []struct {
name string
Expand Down
29 changes: 10 additions & 19 deletions pkg/controller/stacks/stack/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
"github.com/crossplane/crossplane/apis/stacks/v1alpha1"
"github.com/crossplane/crossplane/pkg/controller/stacks/hosted"
"github.com/crossplane/crossplane/pkg/stacks"
"github.com/crossplane/crossplane/pkg/stacks/truncate"
)

const (
Expand Down Expand Up @@ -393,7 +394,7 @@ func (h *stackHandler) createNamespaceLabelsCRDHandler() crdHandler {
// single StackInstall removal will delete a CRD until there are no remaining
// stack parent labels.
func (h *stackHandler) createMultipleParentLabelsCRDHandler() crdHandler {
labelMultiParent := fmt.Sprintf(stacks.LabelMultiParentFormat, h.ext.GetNamespace(), h.ext.GetName())
labelMultiParent := stacks.MultiParentLabel(h.ext)

return func(ctx context.Context, crds []apiextensions.CustomResourceDefinition) error {
for i := range crds {
Expand Down Expand Up @@ -790,20 +791,8 @@ func (h *stackHandler) prepareHostAwareDeployment(d *apps.Deployment, tokenSecre
d.Name = o.Name
d.Namespace = o.Namespace

return nil
}
func (h *stackHandler) prepareHostAwareJob(j *batch.Job, tokenSecret string) error {
if h.hostAwareConfig == nil {
return errors.New(errHostAwareModeNotEnabled)
}

if err := h.prepareHostAwarePodSpec(tokenSecret, &j.Spec.Template.Spec); err != nil {
return err
}

o := h.hostAwareConfig.ObjectReferenceOnHost(j.Name, j.Namespace)
j.Name = o.Name
j.Namespace = o.Namespace
a := hosted.ObjectReferenceAnnotationsOnHost("stack", h.ext.GetName(), h.ext.GetNamespace())
meta.AddAnnotations(d, a)

return nil
}
Expand All @@ -817,7 +806,10 @@ func (h *stackHandler) prepareDeployment(d *apps.Deployment) {
controllerDeployment.Spec.DeepCopyInto(&d.Spec)

// force the deployment to use stack opinionated names and service account
name := h.ext.Name + "-controller"
suffix := "-controller"
size := truncate.LabelValueLength - len(suffix)
name, _ := truncate.Truncate(h.ext.Name, size, truncate.DefaultSuffixLength)
name += suffix
matchLabels := map[string]string{"app": name}
labels := stacks.ParentLabels(h.ext)

Expand Down Expand Up @@ -941,11 +933,10 @@ func (h *stackHandler) removeCRDLabels(ctx context.Context) error {
return err
}

stackName := h.ext.GetName()
stackNS := h.ext.GetNamespace()

labelMultiParentNSPrefix := fmt.Sprintf(stacks.LabelMultiParentNSFormat, stackNS)
labelMultiParent := fmt.Sprintf(stacks.LabelMultiParentFormat, stackNS, stackName)
labelMultiParentNSPrefix := stacks.MultiParentLabelPrefix(h.ext)
labelMultiParent := stacks.MultiParentLabel(h.ext)
labelNamespace := fmt.Sprintf(stacks.LabelNamespaceFmt, stackNS)

for i := range crds {
Expand Down
Loading

0 comments on commit dca7f3d

Please sign in to comment.