Skip to content

Commit 7606af7

Browse files
authored
feat: external-dns cluster controller (#12)
* deploy HelmRelease for external-dns * finish cluster controller implementation * actually start cluster controller * reconcile clusters if external-dns config changes (requires controller-utils update) * remove label selector from DNSServiceConfig The label selector is incompatible with the current implementation that loads the config on-demand during each reconciliation. We would either need to load it once during startup, which would mean that the controller needs to be restarted for changes to take effect, or we would need a more complicated setup with an own controller for the config. Since clusters can already be filtered by their purposes, there is probably no need for a label selector anyway, at least not for now. * fix bugs and add first unit tests * add more unit tests * remove library function which should not be used in this context * fix linting issues * fix helm release * fix controller commands * fix DNSServiceConfig being cluster-scoped * enable copying any amount of secrets * implement helm values replace feature * make purposeSelector behavior more consistent * add documentation * bump build submodule * implement review feedback
1 parent c3ebee8 commit 7606af7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2126
-359
lines changed

README.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,32 @@
44

55
## About this project
66

7-
Platform Service DNS discovers endpoints of remote services
7+
PlatformService DNS is a `PlatformService` as described in [the OpenMCP Architecture Docs](https://github.com/openmcp-project/docs/blob/main/architecture/general/open-mcp-landscape-overview.md).
8+
9+
It is a k8s controller that reconciles `Cluster` resources (from our [Cluster API](https://github.com/openmcp-project/docs/blob/main/adrs/cluster-api.md)) and deploys the [external-dns](https://github.com/kubernetes-sigs/external-dns) operator with a configuration depending on the `Cluster`'s purpose(s). The main goal of the service is to help setting up cross-cluster DNS routing for dynamically managed clusters, especially to enable `ValidatingWebhookConfiguration` resources pointing to webhooks served on other clusters.
810

911
## Requirements and Setup
1012

11-
*Insert a short description what is required to get your project running...*
13+
In combination with the [openMCP Operator](https://github.com/openmcp-project/openmcp-operator), this controller can be deployed via a simple k8s resource:
14+
```yaml
15+
apiVersion: openmcp.cloud/v1alpha1
16+
kind: PlatformService
17+
metadata:
18+
name: dns
19+
spec:
20+
image: "ghcr.io/openmcp-project/images/platform-service-dns:v0.1.0"
21+
```
22+
23+
To run it locally, run
24+
```shell
25+
go run ./cmd/platform-service-dns/main.go init --environment default --provider-name dns --kubeconfig path/to/kubeconfig
26+
```
27+
to deploy the CRDs that are required for the controller and then
28+
```shell
29+
go run ./cmd/platform-service-dns/main.go run --environment default --provider-name dns --kubeconfig path/to/kubeconfig
30+
```
31+
32+
Note that a `DNSServiceConfig` resources is required for the platform service. See the [documentation](docs/README.md) for further details regarding resources and configuration.
1233

1334
## Support, Feedback, Contributing
1435

Taskfile.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ includes:
1212
CODE_DIRS: '{{.ROOT_DIR}}/cmd/... {{.ROOT_DIR}}/internal/... {{.ROOT_DIR}}/api/...'
1313
COMPONENTS: platform-service-dns
1414
REPO_URL: 'https://github.com/openmcp-project/platform-service-dns'
15-
GENERATE_DOCS_INDEX: "false"
15+
GENERATE_DOCS_INDEX: "true"
1616
CHART_COMPONENTS: "[]"
1717
CRDS_COMPONENTS: platform-service-dns
1818
CRDS_PATH: '{{.ROOT_DIR}}/api/crds/manifests'

api/crds/crds.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package crds
2+
3+
import (
4+
"embed"
5+
6+
crdutil "github.com/openmcp-project/controller-utils/pkg/crds"
7+
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
8+
)
9+
10+
//go:embed manifests
11+
var CRDFS embed.FS
12+
13+
func CRDs() ([]*apiextv1.CustomResourceDefinition, error) {
14+
return crdutil.CRDsFromFileSystem(CRDFS, "manifests")
15+
}

api/crds/manifests/dns.openmcp.cloud_dnsserviceconfigs.yaml

Lines changed: 78 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
33
kind: CustomResourceDefinition
44
metadata:
55
annotations:
6-
controller-gen.kubebuilder.io/version: v0.18.0
6+
controller-gen.kubebuilder.io/version: v0.19.0
77
labels:
88
openmcp.cloud/cluster: platform
99
name: dnsserviceconfigs.dns.openmcp.cloud
@@ -53,9 +53,21 @@ spec:
5353
description: ExternalDNSPurposeConfig holds a purpose selector and
5454
the DNS configuration to apply if the selector matches.
5555
properties:
56-
config:
57-
description: HelmValues are the helm values to deploy external-dns
58-
with, if the purpose selector matches.
56+
helmReleaseReconciliationInterval:
57+
description: |-
58+
HelmReleaseReconciliationInterval is the interval at which the HelmRelease for external-dns is reconciled.
59+
If not set, the global HelmReleaseReconciliationInterval is used.
60+
type: string
61+
helmValues:
62+
description: |-
63+
HelmValues are the helm values to deploy external-dns with, if the purpose selector matches.
64+
There are a few special strings which will be replaced before creating the HelmRelease:
65+
- <provider.name> will be replaced with the provider name resource.
66+
- <provider.namespace> will be replaced with the namespace that hosts the platform service.
67+
- <environment> will be replaced with the environment name of the operator.
68+
- <cluster.name> will be replaced with the name of the reconciled Cluster.
69+
- <cluster.namespace> will be replaced with the namespace of the reconciled Cluster.
70+
type: string
5971
name:
6072
description: |-
6173
Name is an optional name.
@@ -67,65 +79,33 @@ spec:
6779
If not set, all Clusters are matched.
6880
properties:
6981
and:
70-
items: {}
82+
items:
83+
type: object
7184
type: array
7285
name:
7386
type: string
74-
not: {}
87+
not:
88+
type: object
7589
or:
76-
items: {}
90+
items:
91+
type: object
7792
type: array
7893
type: object
79-
x-kubernetes-validations:
80-
- message: Exactly one of 'and', 'or', 'not' or 'name' must
81-
be set
82-
rule: size(self.filter(property, size(self[property]) > 0))
83-
== 1
8494
required:
85-
- config
95+
- helmValues
8696
type: object
8797
type: array
8898
externalDNSSource:
8999
description: ExternalDNSSource is the source of the external-dns helm
90100
chart.
91101
properties:
92-
copyAuthSecret:
102+
chartName:
93103
description: |-
94-
SecretCopy defines the name of the secret to copy and the name of the copied secret.
95-
If target is nil or target.name is empty, the secret will be copied with the same name as the source secret.
96-
properties:
97-
source:
98-
description: ObjectReference is a reference to an object in
99-
any namespace.
100-
properties:
101-
name:
102-
description: Name is the name of the object.
103-
type: string
104-
namespace:
105-
description: Namespace is the namespace of the object.
106-
type: string
107-
required:
108-
- name
109-
- namespace
110-
type: object
111-
target:
112-
description: ObjectReference is a reference to an object in
113-
any namespace.
114-
properties:
115-
name:
116-
description: Name is the name of the object.
117-
type: string
118-
namespace:
119-
description: Namespace is the namespace of the object.
120-
type: string
121-
required:
122-
- name
123-
- namespace
124-
type: object
125-
required:
126-
- source
127-
- target
128-
type: object
104+
ChartName specifies the name of the external-dns chart.
105+
Depending on the source, this can also be a relative path within the repository.
106+
When using a source that needs a version (helm or oci), append the version to the chart name using '@', e.g. '[email protected]' or omit for latest version.
107+
minLength: 1
108+
type: string
129109
git:
130110
description: |-
131111
GitRepositorySpec specifies the required configuration to produce an
@@ -648,59 +628,63 @@ spec:
648628
- interval
649629
- url
650630
type: object
631+
required:
632+
- chartName
651633
type: object
652634
x-kubernetes-validations:
653-
- message: Exactly one of 'helm', 'git', or 'oci' must be set
654-
rule: size(self.filter(property, (property != "copyAuthSecret")
655-
&& (size(self[property]) > 0))) == 1
656-
selector:
635+
- message: exactly one of the fields in [helm git oci] must be set
636+
rule: '[has(self.helm),has(self.git),has(self.oci)].filter(x,x==true).size()
637+
== 1'
638+
helmReleaseReconciliationInterval:
657639
description: |-
658-
Selector is a label selector.
659-
If not nil, only Clusters that match the selector will be reconciled by the controller.
660-
properties:
661-
matchExpressions:
662-
description: matchExpressions is a list of label selector requirements.
663-
The requirements are ANDed.
664-
items:
665-
description: |-
666-
A label selector requirement is a selector that contains values, a key, and an operator that
667-
relates the key and values.
640+
HelmReleaseReconciliationInterval is the interval at which the HelmRelease for external-dns is reconciled.
641+
The value can be overwritten for specific purposes using ExternalDNSForPurposes.
642+
If not set, a default of 1h is used.
643+
type: string
644+
secretsToCopy:
645+
description: |-
646+
SecretsToCopy specifies an optional list of secrets which will be copied from the provider namespace into the namespaces of the reconciled Clusters.
647+
This can, for example, be used to distribute credentials for the registry holding the external-dns helm chart.
648+
items:
649+
description: |-
650+
SecretCopy defines the name of the secret to copy and the name of the copied secret.
651+
If target is nil or target.name is empty, the secret will be copied with the same name as the source secret.
652+
properties:
653+
source:
654+
description: LocalObjectReference is a reference to an object
655+
in the same namespace as the resource referencing it.
668656
properties:
669-
key:
670-
description: key is the label key that the selector applies
671-
to.
672-
type: string
673-
operator:
657+
name:
658+
default: ""
674659
description: |-
675-
operator represents a key's relationship to a set of values.
676-
Valid operators are In, NotIn, Exists and DoesNotExist.
660+
Name of the referent.
661+
This field is effectively required, but due to backwards compatibility is
662+
allowed to be empty. Instances of this type with an empty value here are
663+
almost certainly wrong.
664+
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
677665
type: string
678-
values:
666+
type: object
667+
x-kubernetes-map-type: atomic
668+
target:
669+
description: LocalObjectReference is a reference to an object
670+
in the same namespace as the resource referencing it.
671+
properties:
672+
name:
673+
default: ""
679674
description: |-
680-
values is an array of string values. If the operator is In or NotIn,
681-
the values array must be non-empty. If the operator is Exists or DoesNotExist,
682-
the values array must be empty. This array is replaced during a strategic
683-
merge patch.
684-
items:
685-
type: string
686-
type: array
687-
x-kubernetes-list-type: atomic
688-
required:
689-
- key
690-
- operator
675+
Name of the referent.
676+
This field is effectively required, but due to backwards compatibility is
677+
allowed to be empty. Instances of this type with an empty value here are
678+
almost certainly wrong.
679+
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
680+
type: string
691681
type: object
692-
type: array
693-
x-kubernetes-list-type: atomic
694-
matchLabels:
695-
additionalProperties:
696-
type: string
697-
description: |-
698-
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
699-
map is equivalent to an element of matchExpressions, whose key field is "key", the
700-
operator is "In", and the values array contains only "value". The requirements are ANDed.
701-
type: object
702-
type: object
703-
x-kubernetes-map-type: atomic
682+
x-kubernetes-map-type: atomic
683+
required:
684+
- source
685+
- target
686+
type: object
687+
type: array
704688
required:
705689
- externalDNSSource
706690
type: object

0 commit comments

Comments
 (0)