Skip to content

Commit a8fff5f

Browse files
committed
added support for external etcd to CacheServer
On-behalf-of: @SAP robert.vasek@sap.com Signed-off-by: Robert Vasek <robert.vasek@clyso.com>
1 parent c5bfbc5 commit a8fff5f

7 files changed

Lines changed: 198 additions & 27 deletions

File tree

config/crd/bases/operator.kcp.io_cacheservers.yaml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1386,6 +1386,42 @@ spec:
13861386
type: object
13871387
type: object
13881388
type: object
1389+
etcd:
1390+
description: |-
1391+
Optional: Etcd configures an external etcd connection for this cache server.
1392+
If not provided, an embedded etcd is used.
1393+
properties:
1394+
endpoints:
1395+
description: Endpoints is a list of http urls at which etcd nodes
1396+
are available. The expected format is "https://etcd-hostname:2379".
1397+
items:
1398+
type: string
1399+
type: array
1400+
tlsConfig:
1401+
description: ClientCert configures the client certificate used
1402+
to access etcd.
1403+
properties:
1404+
secretRef:
1405+
description: SecretRef is the reference to a v1.Secret object
1406+
that contains the TLS certificate.
1407+
properties:
1408+
name:
1409+
default: ""
1410+
description: |-
1411+
Name of the referent.
1412+
This field is effectively required, but due to backwards compatibility is
1413+
allowed to be empty. Instances of this type with an empty value here are
1414+
almost certainly wrong.
1415+
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
1416+
type: string
1417+
type: object
1418+
x-kubernetes-map-type: atomic
1419+
required:
1420+
- secretRef
1421+
type: object
1422+
required:
1423+
- endpoints
1424+
type: object
13891425
image:
13901426
description: 'Optional: Image overwrites the container image used
13911427
to deploy the cache server.'

internal/resources/cacheserver/deployment.go

Lines changed: 67 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package cacheserver
1818

1919
import (
2020
"fmt"
21+
"strings"
2122

2223
"k8c.io/reconciler/pkg/reconciling"
2324

@@ -37,8 +38,7 @@ const (
3738
ServerContainerName = "cache-server"
3839

3940
// embeddedEtcdStoragePath is the emptyDir path in the Deployment where the
40-
// etcd data is temporarily stored. kcp's cache-server as of v0.30 does not
41-
// support external etcd yet.
41+
// embedded etcd data is temporarily stored.
4242
embeddedEtcdStoragePath = "/var/etcd"
4343
)
4444

@@ -62,13 +62,15 @@ func getCertificateMountPath(certName operatorv1alpha1.Certificate) string {
6262
return fmt.Sprintf("/etc/cache-server/tls/%s", certName)
6363
}
6464

65+
func getEtcdCertificateMountPath() string {
66+
return "/etc/cache-server/etcd"
67+
}
68+
6569
func getCAMountPath(caName operatorv1alpha1.CA) string {
6670
return fmt.Sprintf("/etc/cache-server/tls/ca/%s", caName)
6771
}
6872

6973
func DeploymentReconciler(server *operatorv1alpha1.CacheServer) reconciling.NamedDeploymentReconcilerFactory {
70-
const etcdScratchVolume = "etcd-scratch"
71-
7274
return func() (string, reconciling.DeploymentReconciler) {
7375
return resources.GetCacheServerDeploymentName(server), func(dep *appsv1.Deployment) (*appsv1.Deployment, error) {
7476
labels := resources.GetCacheServerResourceLabels(server)
@@ -84,28 +86,12 @@ func DeploymentReconciler(server *operatorv1alpha1.CacheServer) reconciling.Name
8486

8587
dep.Spec.Template.SetLabels(labels)
8688

87-
secretMounts := []utils.SecretMount{{
88-
VolumeName: "serving-cert",
89-
SecretName: resources.GetCacheServerCertificateName(server, operatorv1alpha1.ServerCertificate),
90-
MountPath: getCertificateMountPath(operatorv1alpha1.ServerCertificate),
91-
}}
92-
9389
// TODO: Why do we discard the imagePullSecrets?
9490
image, _ := resources.GetImageSettings(server.Spec.Image)
9591

96-
args := getArgs(server)
97-
volumes := []corev1.Volume{{
98-
Name: etcdScratchVolume,
99-
VolumeSource: corev1.VolumeSource{
100-
EmptyDir: &corev1.EmptyDirVolumeSource{},
101-
},
102-
}}
103-
volumeMounts := []corev1.VolumeMount{{
104-
Name: etcdScratchVolume,
105-
MountPath: embeddedEtcdStoragePath,
106-
}}
92+
volumes, volumeMounts := getVolumeMounts(server)
10793

108-
for _, sm := range secretMounts {
94+
for _, sm := range getSecretMounts(server) {
10995
v, vm := sm.Build()
11096
volumes = append(volumes, v)
11197
volumeMounts = append(volumeMounts, vm)
@@ -115,7 +101,7 @@ func DeploymentReconciler(server *operatorv1alpha1.CacheServer) reconciling.Name
115101
Name: ServerContainerName,
116102
Image: image,
117103
Command: []string{"/cache-server"},
118-
Args: args,
104+
Args: getArgs(server),
119105
VolumeMounts: volumeMounts,
120106
Resources: defaultResourceRequirements,
121107
SecurityContext: &corev1.SecurityContext{
@@ -188,18 +174,72 @@ func DeploymentReconciler(server *operatorv1alpha1.CacheServer) reconciling.Name
188174
}
189175
}
190176

177+
func getVolumeMounts(server *operatorv1alpha1.CacheServer) (volumes []corev1.Volume, volumeMounts []corev1.VolumeMount) {
178+
const etcdScratchVolume = "etcd-scratch"
179+
180+
if server.Spec.Etcd == nil {
181+
volumes = []corev1.Volume{{
182+
Name: etcdScratchVolume,
183+
VolumeSource: corev1.VolumeSource{
184+
EmptyDir: &corev1.EmptyDirVolumeSource{},
185+
},
186+
}}
187+
volumeMounts = []corev1.VolumeMount{{
188+
Name: etcdScratchVolume,
189+
MountPath: embeddedEtcdStoragePath,
190+
}}
191+
}
192+
193+
return
194+
}
195+
191196
func getArgs(server *operatorv1alpha1.CacheServer) []string {
192197
args := []string{
193-
// Configure (lack of) persistence.
194-
"--root-directory=",
195-
fmt.Sprintf("--embedded-etcd-directory=%s", embeddedEtcdStoragePath),
196-
197198
// Certificate flags (server, service account signing).
198199
fmt.Sprintf("--tls-cert-file=%s/tls.crt", getCertificateMountPath(operatorv1alpha1.ServerCertificate)),
199200
fmt.Sprintf("--tls-private-key-file=%s/tls.key", getCertificateMountPath(operatorv1alpha1.ServerCertificate)),
201+
// Configure (lack of) persistence.
202+
"--root-directory=",
203+
}
204+
205+
if server.Spec.Etcd == nil {
206+
// The CacheServer is configured with an embedded etcd store.
207+
args = append(args,
208+
fmt.Sprintf("--embedded-etcd-directory=%s", embeddedEtcdStoragePath),
209+
)
210+
} else {
211+
// The CacheServer is configured with a dedicated etcd store.
212+
args = append(args,
213+
fmt.Sprintf("--etcd-servers=%s", strings.Join(server.Spec.Etcd.Endpoints, ",")),
214+
)
215+
if server.Spec.Etcd.TLSConfig != nil {
216+
args = append(args,
217+
fmt.Sprintf("--etcd-cafile=%s/ca.crt", getEtcdCertificateMountPath()),
218+
fmt.Sprintf("--etcd-certfile=%s/tls.crt", getEtcdCertificateMountPath()),
219+
fmt.Sprintf("--etcd-keyfile=%s/tls.key", getEtcdCertificateMountPath()),
220+
)
221+
}
200222
}
201223

202224
args = append(args, utils.GetLoggingArgs(server.Spec.Logging)...)
203225

204226
return args
205227
}
228+
229+
func getSecretMounts(server *operatorv1alpha1.CacheServer) []utils.SecretMount {
230+
secretMounts := []utils.SecretMount{{
231+
VolumeName: "serving-cert",
232+
SecretName: resources.GetCacheServerCertificateName(server, operatorv1alpha1.ServerCertificate),
233+
MountPath: getCertificateMountPath(operatorv1alpha1.ServerCertificate),
234+
}}
235+
236+
if server.Spec.Etcd != nil && server.Spec.Etcd.TLSConfig != nil {
237+
secretMounts = append(secretMounts, utils.SecretMount{
238+
VolumeName: "etcd-cert",
239+
SecretName: server.Spec.Etcd.TLSConfig.SecretRef.Name,
240+
MountPath: getEtcdCertificateMountPath(),
241+
})
242+
}
243+
244+
return secretMounts
245+
}

sdk/apis/operator/v1alpha1/cacheserver_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ type CacheServerSpec struct {
4545

4646
// Optional: DeploymentTemplate configures the Kubernetes Deployment created for this cache server.
4747
DeploymentTemplate *DeploymentTemplate `json:"deploymentTemplate,omitempty"`
48+
49+
// Optional: Etcd configures an external etcd connection for this cache server.
50+
// If not provided, an embedded etcd is used.
51+
Etcd *EtcdConfig `json:"etcd,omitempty"`
4852
}
4953

5054
// CacheServerStatus defines the observed state of CacheServer

sdk/apis/operator/v1alpha1/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sdk/applyconfiguration/operator/v1alpha1/cacheserverspec.go

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/e2e/cacheserver/cacheserver_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,68 @@ func TestCacheWithRootShard(t *testing.T) {
9292
t.Fatalf("Failed to list secrets in kcp: %v", err)
9393
}
9494
}
95+
96+
func TestCacheWithExternalEtcdAndRootShard(t *testing.T) {
97+
switch utils.GetKcpRelease() {
98+
// We skip release "" because it currently points to kcp 0.30 which
99+
// doesn't currently support cache-server with an external etcd cluster.
100+
case "release-0.28", "release-0.29", "release-30", "": // TODO(gman0): remove the empty ("") kcp release!
101+
t.Skip("running external etcd with cache-server is only supported with kcp >=0.31")
102+
return
103+
}
104+
105+
ctrlruntime.SetLogger(logr.Discard())
106+
107+
client := utils.GetKubeClient(t)
108+
ctx := context.Background()
109+
110+
// create namespace
111+
namespace := utils.CreateSelfDestructingNamespace(t, ctx, client, "cache-with-etcd-rootshard")
112+
113+
// deploy the cache server
114+
cacheServer := utils.DeployCacheServerWithExternalEtcd(ctx, t, client, namespace.Name)
115+
116+
// deploy a root shard that uses our cache
117+
rootShard := utils.DeployRootShard(ctx, t, client, namespace.Name, "example.localhost", func(rs *operatorv1alpha1.RootShard) {
118+
rs.Spec.Cache.Reference = &corev1.LocalObjectReference{
119+
Name: cacheServer.Name,
120+
}
121+
})
122+
123+
// create a kubeconfig to access the root shard
124+
configSecretName := fmt.Sprintf("%s-shard-kubeconfig", rootShard.Name)
125+
126+
rsConfig := operatorv1alpha1.Kubeconfig{}
127+
rsConfig.Name = configSecretName
128+
rsConfig.Namespace = namespace.Name
129+
130+
rsConfig.Spec = operatorv1alpha1.KubeconfigSpec{
131+
Target: operatorv1alpha1.KubeconfigTarget{
132+
RootShardRef: &corev1.LocalObjectReference{
133+
Name: rootShard.Name,
134+
},
135+
},
136+
Username: "e2e",
137+
Validity: metav1.Duration{Duration: 2 * time.Hour},
138+
SecretRef: corev1.LocalObjectReference{
139+
Name: configSecretName,
140+
},
141+
Groups: []string{"system:masters"},
142+
}
143+
144+
t.Log("Creating kubeconfig for RootShard...")
145+
if err := client.Create(ctx, &rsConfig); err != nil {
146+
t.Fatal(err)
147+
}
148+
utils.WaitForObject(t, ctx, client, &corev1.Secret{}, types.NamespacedName{Namespace: rsConfig.Namespace, Name: rsConfig.Spec.SecretRef.Name})
149+
150+
t.Log("Connecting to RootShard...")
151+
rootShardClient := utils.ConnectWithKubeconfig(t, ctx, client, namespace.Name, rsConfig.Name, logicalcluster.NewPath("root"))
152+
153+
// proof of life: list something every logicalcluster in kcp has
154+
t.Log("Should be able to list Secrets.")
155+
secrets := &corev1.SecretList{}
156+
if err := rootShardClient.List(ctx, secrets); err != nil {
157+
t.Fatalf("Failed to list secrets in kcp: %v", err)
158+
}
159+
}

test/utils/deploy.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,3 +284,15 @@ func DeployCacheServer(ctx context.Context, t *testing.T, client ctrlruntimeclie
284284

285285
return server
286286
}
287+
288+
func DeployCacheServerWithExternalEtcd(ctx context.Context, t *testing.T, client ctrlruntimeclient.Client, namespace string, patches ...func(*operatorv1alpha1.CacheServer)) operatorv1alpha1.CacheServer {
289+
t.Helper()
290+
291+
etcd := DeployEtcd(t, "kachy-etcd", namespace)
292+
293+
return DeployCacheServer(ctx, t, client, namespace, append(patches, func(server *operatorv1alpha1.CacheServer) {
294+
server.Spec.Etcd = &operatorv1alpha1.EtcdConfig{
295+
Endpoints: []string{etcd},
296+
}
297+
})...)
298+
}

0 commit comments

Comments
 (0)