Skip to content

Commit

Permalink
Limit upstream-host label value to 43 chars
Browse files Browse the repository at this point in the history
  • Loading branch information
dimitar-kostadinov committed Apr 22, 2024
1 parent 18182d3 commit 2f3028a
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 35 deletions.
2 changes: 1 addition & 1 deletion pkg/component/registrycaches/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

package registrycaches

var ComputeName = computeName
var ComputeUpstreamLabel = computeUpstreamLabel
70 changes: 41 additions & 29 deletions pkg/component/registrycaches/registry_caches.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
v1beta1helper "github.com/gardener/gardener/pkg/apis/core/v1beta1/helper"
resourcesv1alpha1 "github.com/gardener/gardener/pkg/apis/resources/v1alpha1"
"github.com/gardener/gardener/pkg/client/kubernetes"
"github.com/gardener/gardener/pkg/component"
"github.com/gardener/gardener/pkg/utils"
Expand Down Expand Up @@ -182,8 +183,9 @@ func (r *registryCaches) computeResourcesDataForRegistryCache(ctx context.Contex
)

var (
name = computeName(cache.Upstream)
configValues = map[string]interface{}{
upstreamLabel = computeUpstreamLabel(cache.Upstream)
name = "registry-" + strings.ReplaceAll(upstreamLabel, ".", "-")
configValues = map[string]interface{}{
"http_addr": fmt.Sprintf(":%d", constants.RegistryCachePort),
"http_debug_addr": fmt.Sprintf(":%d", debugPort),
"proxy_remoteurl": registryutils.GetUpstreamURL(cache.Upstream),
Expand Down Expand Up @@ -225,7 +227,7 @@ func (r *registryCaches) computeResourcesDataForRegistryCache(ctx context.Contex
ObjectMeta: metav1.ObjectMeta{
Name: name + "-config",
Namespace: metav1.NamespaceSystem,
Labels: getLabels(name, cache.Upstream),
Labels: getLabels(name, upstreamLabel),
},
Data: map[string][]byte{
"config.yml": configYAML.Bytes(),
Expand All @@ -237,10 +239,10 @@ func (r *registryCaches) computeResourcesDataForRegistryCache(ctx context.Contex
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: metav1.NamespaceSystem,
Labels: getLabels(name, cache.Upstream),
Labels: getLabels(name, upstreamLabel),
},
Spec: corev1.ServiceSpec{
Selector: getLabels(name, cache.Upstream),
Selector: getLabels(name, upstreamLabel),
Ports: []corev1.ServicePort{{
Name: "registry-cache",
Port: constants.RegistryCachePort,
Expand All @@ -255,17 +257,20 @@ func (r *registryCaches) computeResourcesDataForRegistryCache(ctx context.Contex
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: metav1.NamespaceSystem,
Labels: getLabels(name, cache.Upstream),
Labels: getLabels(name, upstreamLabel),
// TODO: @dimitar-kostadinov remove the `DeleteOnInvalidUpdate` annotation in v0.10.0 release
// StatefulSets for upstreams with more than 43 chars will be recreated
Annotations: map[string]string{resourcesv1alpha1.DeleteOnInvalidUpdate: "true"},
},
Spec: appsv1.StatefulSetSpec{
ServiceName: service.Name,
Selector: &metav1.LabelSelector{
MatchLabels: getLabels(name, cache.Upstream),
MatchLabels: getLabels(name, upstreamLabel),
},
Replicas: ptr.To(int32(1)),
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: utils.MergeStringMaps(getLabels(name, cache.Upstream), map[string]string{
Labels: utils.MergeStringMaps(getLabels(name, upstreamLabel), map[string]string{
v1beta1constants.LabelNetworkPolicyToDNS: v1beta1constants.LabelNetworkPolicyAllowed,
v1beta1constants.LabelNetworkPolicyToPublicNetworks: v1beta1constants.LabelNetworkPolicyAllowed,
}),
Expand Down Expand Up @@ -357,7 +362,7 @@ func (r *registryCaches) computeResourcesDataForRegistryCache(ctx context.Contex
{
ObjectMeta: metav1.ObjectMeta{
Name: registryCacheVolumeName,
Labels: getLabels(name, cache.Upstream),
Labels: getLabels(name, upstreamLabel),
},
Spec: corev1.PersistentVolumeClaimSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
Expand Down Expand Up @@ -419,32 +424,39 @@ func (r *registryCaches) computeResourcesDataForRegistryCache(ctx context.Contex
}, nil
}

func getLabels(name, upstream string) map[string]string {
func getLabels(name, upstreamLabel string) map[string]string {
return map[string]string{
"app": name,
constants.UpstreamHostLabel: upstream,
constants.UpstreamHostLabel: upstreamLabel,
}
}

// computeName computes a name by given upstream.
// The name later on is used by the registry cache Service, config Secret, StatefulSet and VPA.
// computeUpstreamLabel computes upstreamLabel by given upstream.
//
// If length of registry-<escaped_upsteam> is NOT > 52, the name is registry-<escaped_upsteam>.
// Otherwise it is registry-<truncated_escaped_upsteam>-<hash> where <escaped_upsteam> is truncated at 37 chars.
// The returned name is at most 52 chars.
func computeName(upstream string) string {
// The StatefulSet name limit is 63 chars. However Pods for a StatefulSet with name > 52 chars cannot be created due to https://github.com/kubernetes/kubernetes/issues/64023.
// The "controller-revision-hash" label gets added to the StatefulSet Pod. The label value is in format <stateful_set_name>_<hash> where <hash> is 10 or 11 chars.
// A label value limit is 63 chars. That's why a Pod for a StatefulSet with name > 52 chars cannot be created.
const statefulSetNameLimit = 52

escapedUpstream := strings.Replace(upstream, ".", "-", -1)
name := "registry-" + escapedUpstream
if len(name) > statefulSetNameLimit {
// Upstream is a valid DNS subdomain (RFC 1123) and optionally a port (e.g. my-registry.io[:5000])
// It is used as a 'upstream-host' label value on registry cache resources (Service, Secret, StatefulSet and VPA).
// Label values cannot contain ':' char, so if upstream is '<host>:<port>' the label value is transformed to '<host>-<port>'.
// It is also used to build the resources names escaping the '.' with '-'; e.g. `registry-<escaped_upstreamLabel>`.
//
// Due to restrictions of resource names length, if upstream length > 43 it is truncated at 37 chars, and the
// label value is transformed to <truncated-upstream>-<hash> where <hash> is first 5 chars of upstream sha256 hash.
//
// The returned upstreamLabel is at most 43 chars.
func computeUpstreamLabel(upstream string) string {
// A label value length and a resource name length limits are 63 chars. However, Pods for a StatefulSet with name > 52 chars
// cannot be created due to https://github.com/kubernetes/kubernetes/issues/64023.
// The cache resources name have prefix 'registry-', thus the label value length is limited to 43.
const labelValueLimit = 43

upstreamLabel := upstream
portIndex := strings.LastIndexByte(upstream, ':')
if portIndex != -1 {
upstreamLabel = fmt.Sprintf("%s-%s", upstream[:portIndex], upstream[portIndex+1:])
}
if len(upstream) > labelValueLimit {
hash := utils.ComputeSHA256Hex([]byte(upstream))[:5]
upstreamLimit := statefulSetNameLimit - len("registry-") - len(hash) - 1
name = fmt.Sprintf("registry-%s-%s", escapedUpstream[:upstreamLimit], hash)
limit := labelValueLimit - len(hash) - 1
upstreamLabel = fmt.Sprintf("%s-%s", upstreamLabel[:limit], hash)
}

return name
return upstreamLabel
}
16 changes: 11 additions & 5 deletions pkg/component/registrycaches/registry_caches_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ status:
out := `apiVersion: apps/v1
kind: StatefulSet
metadata:
annotations:
resources.gardener.cloud/delete-on-invalid-update: "true"
creationTimestamp: null
labels:
app: ` + name + `
Expand Down Expand Up @@ -606,15 +608,19 @@ status: {}
})
})

DescribeTable("#computeName",
DescribeTable("#computeUpstreamLabel",
func(upstream, expected string) {
actual := ComputeName(upstream)
Expect(len(actual)).NotTo(BeNumerically(">", 52))
actual := ComputeUpstreamLabel(upstream)
Expect(len(actual)).NotTo(BeNumerically(">", 43))
Expect(actual).To(Equal(expected))
},

Entry("short upstream", "docker.io", "registry-docker-io"),
Entry("long upstream", "myproj-releases.common.repositories.cloud.com", "registry-myproj-releases-common-repositories-c-3f834"),
Entry("short upstream", "my-registry.io", "my-registry.io"),
Entry("short upstream ends with port", "my-registry.io:5000", "my-registry.io-5000"),
Entry("short upstream ends like a port", "my-registry.io-5000", "my-registry.io-5000"),
Entry("long upstream", "my-very-long-registry.very-long-subdomain.io", "my-very-long-registry.very-long-subdo-2fae3"),
Entry("long upstream ends with port", "my-very-long-registry.long-subdomain.io:8443", "my-very-long-registry.long-subdomain.-8cb9e"),
Entry("long upstream ends like a port", "my-very-long-registry.long-subdomain.io-8443", "my-very-long-registry.long-subdomain.-e91ed"),
)
})

Expand Down

0 comments on commit 2f3028a

Please sign in to comment.