Skip to content

Commit d113609

Browse files
Add support for enabling Consul dataplane as a sidecar container (#4678)
* Add support for enabling Consul dataplane as a sidecar container * Add sidecar initial probe check delay configuration * Add sidecar probe period, failure threshold, and timeout configurations * Update default sidecar probe configurations for improved reliability * Add tests for Consul dataplane sidecar lifecycle configuration and probe handling * added changelog * Add configuration for Consul dataplane as sidecar init container in values.yaml * Bump kind version to v0.27.0 in CI inputs for compatibility updates
1 parent e416dd1 commit d113609

12 files changed

Lines changed: 397 additions & 20 deletions

File tree

.changelog/4678.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:feature
2+
Added boolean annotation "consul.hashicorp.com/enable-consul-dataplane-as-sidecar" for registering consul-dataplane as init container so that consul-dataplane container is initialised and started before application container. Default value is "false" i.e the feature is disabled by default. Also made the probe properties configurable through annotations.
3+
```
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Copyright (c) HashiCorp, Inc.
22
# SPDX-License-Identifier: MPL-2.0
33

4-
kindVersion: v0.23.0
4+
kindVersion: v0.27.0
55
kindNodeImage: kindest/node:v1.30.2@sha256:ecfe5841b9bee4fe9690f49c118c33629fa345e3350a0c67a5a34482a99d6bba
66
kubectlVersion: v1.30.2

charts/consul/templates/connect-inject-deployment.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,17 @@ spec:
284284
-enable-auto-encrypt \
285285
{{- end }}
286286
-enable-telemetry-collector={{ .Values.global.metrics.enableTelemetryCollector}} \
287+
288+
{{- if .Values.connectInject.dataplaneAsSidecarInitContainer.defaultEnabled }}
289+
-default-enable-consul-dataplane-as-sidecar=true \
290+
-default-sidecar-probe-check-initial-delay-seconds={{ .Values.connectInject.dataplaneAsSidecarInitContainer.initialProbeCheckDelaySeconds }} \
291+
-default-sidecar-probe-period-seconds={{ .Values.connectInject.dataplaneAsSidecarInitContainer.probeCheckPeriodSeconds }} \
292+
-default-sidecar-probe-failure-threshold="{{ .Values.connectInject.dataplaneAsSidecarInitContainer.probeCheckFailureThreshold }}" \
293+
-default-sidecar-probe-check-timeout-seconds="{{ .Values.connectInject.dataplaneAsSidecarInitContainer.probeCheckTimeoutSeconds }}" \
294+
{{- else }}
295+
-default-enable-consul-dataplane-as-sidecar=false \
296+
{{- end }}
297+
287298
startupProbe:
288299
httpGet:
289300
path: /readyz/ready

charts/consul/values.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3015,6 +3015,31 @@ connectInject:
30153015
# A value of zero disables the probe.
30163016
defaultLivenessFailureSeconds: 0
30173017

3018+
dataplaneAsSidecarInitContainer:
3019+
# If true, the consul-dataplane will run as an init container with the restart policy set to "Always" and using startup probe to determine readiness.
3020+
# Also, Kubelet will wait for the consul-dataplane init container to be ready before starting the application container.
3021+
# @type: boolean
3022+
defaultEnabled: false
3023+
3024+
# Configures how long the k8s startup probe will wait initially before performing the first check.
3025+
# @type: integer
3026+
initialProbeCheckDelaySeconds: 1
3027+
3028+
# Configures how often the k8s startup probe will check the readiness of the consul-dataplane init container.
3029+
# @type: integer
3030+
probeCheckPeriodSeconds: 1
3031+
3032+
# Configures the failure threshold for the k8s startup probe.
3033+
# @type: integer
3034+
probeCheckFailureThreshold: 10
3035+
3036+
# Configures the timeout for each k8s startup probe check.
3037+
# @type: integer
3038+
probeCheckTimeoutSeconds: 5
3039+
3040+
3041+
3042+
30183043
# The resource settings for the Connect injected init container. If null, the resources
30193044
# won't be set for the initContainer. The defaults are optimized for developer instances of
30203045
# Kubernetes, however they should be tweaked with the recommended defaults as shown below to speed up service registration times.

control-plane/connect-inject/constants/annotations_and_labels.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,12 @@ const (
226226
// ManagedByValue is the value for keyManagedBy.
227227
//TODO(zalimeni) rename this to ManagedByLegacyEndpointsValue.
228228
ManagedByValue = "consul-k8s-endpoints-controller"
229+
230+
AnnotationEnableConsulDataplaneAsSidecar = "consul.hashicorp.com/enable-consul-dataplane-as-sidecar"
231+
AnnotationSidecarInitialProbeCheckDelaySeconds = "consul.hashicorp.com/sidecar-initial-probe-check-delay-seconds"
232+
AnnotationSidecarProbePeriodSeconds = "consul.hashicorp.com/sidecar-probe-period-seconds"
233+
AnnotationSidecarProbeFailureThreshold = "consul.hashicorp.com/sidecar-probe-failure-threshold"
234+
AnnotationSidecarProbeCheckTimeoutSeconds = "consul.hashicorp.com/sidecar-probe-check-timeout-seconds"
229235
)
230236

231237
// Annotations used by Prometheus.

control-plane/connect-inject/lifecycle/lifecycle_configuration.go

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ import (
1414

1515
// Config represents configuration common to connect-inject components related to proxy lifecycle management.
1616
type Config struct {
17-
DefaultEnableProxyLifecycle bool
18-
DefaultEnableShutdownDrainListeners bool
19-
DefaultShutdownGracePeriodSeconds int
20-
DefaultStartupGracePeriodSeconds int
21-
DefaultGracefulPort string
22-
DefaultGracefulShutdownPath string
23-
DefaultGracefulStartupPath string
17+
DefaultEnableProxyLifecycle bool
18+
DefaultEnableShutdownDrainListeners bool
19+
DefaultShutdownGracePeriodSeconds int
20+
DefaultStartupGracePeriodSeconds int
21+
DefaultGracefulPort string
22+
DefaultGracefulShutdownPath string
23+
DefaultGracefulStartupPath string
24+
DefaultEnableConsulDataplaneAsSidecar bool
2425
}
2526

2627
// EnableProxyLifecycle returns whether proxy lifecycle management is enabled either via the default value in the meshWebhook, or if it's been
@@ -123,3 +124,17 @@ func (lc Config) GracefulStartupPath(pod corev1.Pod) string {
123124

124125
return lc.DefaultGracefulStartupPath
125126
}
127+
128+
// EnableConsulDataplaneAsSidecar returns whether register consul-dataplane as sidecar in kubernetes is enabled either via the default value in the meshWebhook, or if it's been
129+
// overridden via the annotation.
130+
func (lc Config) EnableConsulDataplaneAsSidecar(pod corev1.Pod) (bool, error) {
131+
enabled := lc.DefaultEnableConsulDataplaneAsSidecar
132+
if raw, ok := pod.Annotations[constants.AnnotationEnableConsulDataplaneAsSidecar]; ok && raw != "" {
133+
enableConsulDataplaneAsSidecar, err := strconv.ParseBool(raw)
134+
if err != nil {
135+
return false, fmt.Errorf("%s annotation value of %s was invalid: %s", constants.AnnotationEnableConsulDataplaneAsSidecar, raw, err)
136+
}
137+
enabled = enableConsulDataplaneAsSidecar
138+
}
139+
return enabled, nil
140+
}

control-plane/connect-inject/lifecycle/lifecycle_configuration_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,3 +468,77 @@ func minimal() *corev1.Pod {
468468
},
469469
}
470470
}
471+
472+
func TestLifecycleConfig_EnableConsulDataplaneAsSidecar(t *testing.T) {
473+
cases := []struct {
474+
Name string
475+
Pod func(*corev1.Pod) *corev1.Pod
476+
LifecycleConfig Config
477+
Expected bool
478+
Err string
479+
}{
480+
{
481+
Name: "Enabled via meshWebhook default",
482+
Pod: func(pod *corev1.Pod) *corev1.Pod {
483+
return pod
484+
},
485+
LifecycleConfig: Config{
486+
DefaultEnableConsulDataplaneAsSidecar: true,
487+
},
488+
Expected: true,
489+
Err: "",
490+
},
491+
{
492+
Name: "Enabled via annotation",
493+
Pod: func(pod *corev1.Pod) *corev1.Pod {
494+
pod.Annotations[constants.AnnotationEnableConsulDataplaneAsSidecar] = "true"
495+
return pod
496+
},
497+
LifecycleConfig: Config{
498+
DefaultEnableConsulDataplaneAsSidecar: false,
499+
},
500+
Expected: true,
501+
Err: "",
502+
},
503+
{
504+
Name: "Disabled via annotation",
505+
Pod: func(pod *corev1.Pod) *corev1.Pod {
506+
pod.Annotations[constants.AnnotationEnableConsulDataplaneAsSidecar] = "false"
507+
return pod
508+
},
509+
LifecycleConfig: Config{
510+
DefaultEnableConsulDataplaneAsSidecar: true,
511+
},
512+
Expected: false,
513+
Err: "",
514+
},
515+
{
516+
Name: "Invalid annotation value",
517+
Pod: func(pod *corev1.Pod) *corev1.Pod {
518+
pod.Annotations[constants.AnnotationEnableConsulDataplaneAsSidecar] = "not-a-bool"
519+
return pod
520+
},
521+
LifecycleConfig: Config{
522+
DefaultEnableConsulDataplaneAsSidecar: false,
523+
},
524+
Expected: false,
525+
Err: "consul.hashicorp.com/enable-consul-dataplane-as-sidecar annotation value of not-a-bool was invalid: strconv.ParseBool: parsing \"not-a-bool\": invalid syntax",
526+
},
527+
}
528+
529+
for _, tt := range cases {
530+
t.Run(tt.Name, func(t *testing.T) {
531+
require := require.New(t)
532+
lc := tt.LifecycleConfig
533+
534+
actual, err := lc.EnableConsulDataplaneAsSidecar(*tt.Pod(minimal()))
535+
536+
if tt.Err == "" {
537+
require.Equal(tt.Expected, actual)
538+
require.NoError(err)
539+
} else {
540+
require.EqualError(err, tt.Err)
541+
}
542+
})
543+
}
544+
}

control-plane/connect-inject/webhook/consul_dataplane_sidecar.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,32 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor
276276
},
277277
ReadOnlyRootFilesystem: ptr.To(true),
278278
}
279+
enableConsulDataplaneAsSidecar, err := w.LifecycleConfig.EnableConsulDataplaneAsSidecar(pod)
280+
if err != nil {
281+
return corev1.Container{}, err
282+
}
283+
if enableConsulDataplaneAsSidecar {
284+
restartPolicy := corev1.ContainerRestartPolicyAlways
285+
container.RestartPolicy = &restartPolicy
286+
287+
// Configure the startup probe to check the sidecar proxy health.
288+
container.StartupProbe = &corev1.Probe{
289+
ProbeHandler: corev1.ProbeHandler{
290+
Exec: &corev1.ExecAction{
291+
Command: []string{
292+
// Absolute path to the binary from the Dockerfile
293+
"/usr/local/bin/consul-dataplane",
294+
// Built-in subcommand to check Envoy health
295+
"-check-proxy-health",
296+
},
297+
},
298+
},
299+
InitialDelaySeconds: w.getSidecarProbeCheckInitialDelaySeconds(pod),
300+
PeriodSeconds: w.getSidecarProbePeriodSeconds(pod),
301+
FailureThreshold: w.getSidecarProbeFailureThreshold(pod),
302+
TimeoutSeconds: w.getSidecarProbeTimeoutSeconds(pod),
303+
}
304+
}
279305
return container, nil
280306
}
281307

@@ -615,6 +641,17 @@ func (w *MeshWebhook) getLivenessFailureSeconds(pod corev1.Pod) int32 {
615641
return 0
616642
}
617643

644+
func (w *MeshWebhook) getSidecarProbeCheckInitialDelaySeconds(pod corev1.Pod) int32 {
645+
seconds := w.DefaultSidecarProbeCheckInitialDelaySeconds
646+
if v, ok := pod.Annotations[constants.AnnotationSidecarInitialProbeCheckDelaySeconds]; ok {
647+
seconds, _ = strconv.Atoi(v)
648+
}
649+
if seconds > 0 {
650+
return int32(seconds)
651+
}
652+
return 0
653+
}
654+
618655
// getMetricsPorts creates container ports for exposing services such as prometheus.
619656
// Prometheus in particular needs a named port for use with the operator.
620657
// https://github.com/hashicorp/consul-k8s/pull/1440
@@ -648,3 +685,34 @@ func (w *MeshWebhook) getMetricsPorts(pod corev1.Pod) ([]corev1.ContainerPort, e
648685
},
649686
}, nil
650687
}
688+
689+
func (w *MeshWebhook) getSidecarProbePeriodSeconds(pod corev1.Pod) int32 {
690+
seconds := w.DefaultSidecarProbePeriodSeconds
691+
if v, ok := pod.Annotations[constants.AnnotationSidecarProbePeriodSeconds]; ok {
692+
seconds, _ = strconv.Atoi(v)
693+
}
694+
if seconds > 0 {
695+
return int32(seconds)
696+
}
697+
return 0
698+
}
699+
func (w *MeshWebhook) getSidecarProbeFailureThreshold(pod corev1.Pod) int32 {
700+
threshold := w.DefaultSidecarProbeFailureThreshold
701+
if v, ok := pod.Annotations[constants.AnnotationSidecarProbeFailureThreshold]; ok {
702+
threshold, _ = strconv.Atoi(v)
703+
}
704+
if threshold > 0 {
705+
return int32(threshold)
706+
}
707+
return 0
708+
}
709+
func (w *MeshWebhook) getSidecarProbeTimeoutSeconds(pod corev1.Pod) int32 {
710+
seconds := w.DefaultSidecarProbeCheckTimeoutSeconds
711+
if v, ok := pod.Annotations[constants.AnnotationSidecarProbeCheckTimeoutSeconds]; ok {
712+
seconds, _ = strconv.Atoi(v)
713+
}
714+
if seconds > 0 {
715+
return int32(seconds)
716+
}
717+
return 0
718+
}

0 commit comments

Comments
 (0)