Skip to content

Commit 19d8043

Browse files
feat: Run the proxy container as Sidecar Init Container (#624)
Operator will configure proxy containers as sidecar containers in `PodSpec.InitContainers` when supported by the kubernetes cluster. The operator checks the cluster version. If the cluster version is 1.29 or higher, the operator will configure the proxy as an InitContainer sidecar container. If the cluster version is 1.28 or lower, the operator will configure proxy containers in the PodSpec.Container. This is especially helpful for Job and CronJob workloads. Sidecar containers are a new type of containers that start among the InitContainers, run through the lifecycle of the Pod and don’t block pod termination. Kubelet makes a best effort to keep them alive and running while other containers are running. Fixes #381 Co-authored-by: Jonathan Hess <hessjc@google.com> Co-authored-by: Jonathan Hess (he/him) <103529393+hessjcg@users.noreply.github.com>
1 parent 4f337e1 commit 19d8043

File tree

8 files changed

+110
-49
lines changed

8 files changed

+110
-49
lines changed

Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ build: generate build_push_docker ## Builds and pushes the docker image to tag d
9191
@echo "TIME: $(shell date) end make build"
9292

9393
.PHONY: test
94-
test: generate go_test ## Run tests (but not internal/teste2e)
94+
test: generate go_test go_test_k8s_1_28 ## Run tests (but not internal/teste2e)
9595
@echo "TIME: $(shell date) end make test"
9696

9797
.PHONY: deploy
@@ -167,6 +167,11 @@ go_test: ctrl_manifests envtest # Run tests (but not internal/teste2e)
167167
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" \
168168
go test ./internal/.../. -coverprofile cover.out -race | tee test_results.txt
169169

170+
.PHONY: go_test_k8s_1_28
171+
go_test_k8s_1_28: ctrl_manifests envtest # Run tests using older kubernetes version 1.28 (but not internal/teste2e)
172+
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use 1.28.x -p path)" \
173+
go test ./internal/.../. -coverprofile cover.out -race | tee test_results.txt
174+
170175
##
171176
# 3rd Party License Checks
172177
.PHONY: license_check

internal/controller/authproxyworkload_controller_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ func reconciler(p *cloudsqlapi.AuthProxyWorkload, cb client.Client, defaultProxy
490490
r := &AuthProxyWorkloadReconciler{
491491
Client: cb,
492492
recentlyDeleted: &recentlyDeletedCache{},
493-
updater: workload.NewUpdater("cloud-sql-proxy-operator/dev", defaultProxyImage),
493+
updater: workload.NewUpdater("cloud-sql-proxy-operator/dev", defaultProxyImage, false),
494494
}
495495
req := ctrl.Request{
496496
NamespacedName: types.NamespacedName{

internal/controller/pod_controller_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ func podWebhookController(cb client.Client) (*PodAdmissionWebhook, context.Conte
151151
r := &PodAdmissionWebhook{
152152
Client: cb,
153153
decoder: d,
154-
updater: workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage),
154+
updater: workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage, false),
155155
}
156156

157157
return r, ctx, nil
@@ -293,7 +293,7 @@ func podDeleteControllerForTest(c client.Client) (*podDeleteController, context.
293293
r := &podDeleteController{
294294
Client: c,
295295
Scheme: c.Scheme(),
296-
updater: workload.NewUpdater("cloud-sql-proxy-operator/dev", "proxy:1.0"),
296+
updater: workload.NewUpdater("cloud-sql-proxy-operator/dev", "proxy:1.0", false),
297297
}
298298
return r, ctx
299299
}

internal/controller/setup.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ import (
1919
"github.com/GoogleCloudPlatform/cloud-sql-proxy-operator/internal/workload"
2020
"k8s.io/apimachinery/pkg/runtime"
2121
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
22+
utilversion "k8s.io/apimachinery/pkg/util/version"
23+
"k8s.io/apimachinery/pkg/version"
24+
"k8s.io/client-go/kubernetes"
2225
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
2326
ctrl "sigs.k8s.io/controller-runtime"
2427
"sigs.k8s.io/controller-runtime/pkg/manager"
@@ -37,13 +40,33 @@ func InitScheme(scheme *runtime.Scheme) {
3740
//+kubebuilder:scaffold:scheme
3841
}
3942

43+
func getKubernetesVersion(mgr manager.Manager) (*version.Info, error) {
44+
kubeClient, err := kubernetes.NewForConfig(mgr.GetConfig())
45+
if err != nil {
46+
return nil, err
47+
}
48+
49+
return kubeClient.Discovery().ServerVersion()
50+
}
51+
52+
func UseSidecar(v *version.Info) bool {
53+
return utilversion.MustParseSemantic(v.GitVersion).AtLeast(utilversion.MustParseSemantic("v1.29.0"))
54+
}
55+
4056
// SetupManagers was moved out of ../main.go here so that it can be invoked
4157
// from the testintegration tests AND from the actual operator.
4258
func SetupManagers(mgr manager.Manager, userAgent, defaultProxyImage string) error {
43-
u := workload.NewUpdater(userAgent, defaultProxyImage)
59+
var err error
60+
kubeVersion, err := getKubernetesVersion(mgr)
61+
if err != nil {
62+
setupLog.Error(err, "unable to get kubernetes version", "controller", "AuthProxyWorkload")
63+
return err
64+
}
65+
setupLog.Info("Kubernetes", "version", kubeVersion.String())
66+
67+
u := workload.NewUpdater(userAgent, defaultProxyImage, UseSidecar(kubeVersion))
4468

4569
setupLog.Info("Configuring reconcilers...")
46-
var err error
4770

4871
_, err = NewAuthProxyWorkloadReconciler(mgr, u)
4972
if err != nil {

internal/testhelpers/resources.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,11 @@ func (cc *TestCaseClient) ExpectPodContainerCount(ctx context.Context, podSelect
440440
return fmt.Errorf("got 0 pods, want at least 1 pod")
441441
}
442442
for _, pod := range pods.Items {
443-
got := len(pod.Spec.Containers)
443+
// The use len(Containers) + len(InitContainers) as the total number
444+
// of containers on the pod. This way the assertion works the same
445+
// when it runs with k8s <= 1.28 using regular containers for the proxy
446+
// as well as with k8s >= 1.29 using init containers for the proxy.
447+
got := len(pod.Spec.Containers) + len(pod.Spec.InitContainers)
444448
if got != count {
445449
countBadPods++
446450
}
@@ -679,7 +683,7 @@ func NewAuthProxyWorkload(key types.NamespacedName) *cloudsqlapi.AuthProxyWorklo
679683
}
680684

681685
// CreateAuthProxyWorkload creates an AuthProxyWorkload in the kubernetes cluster.
682-
func (cc *TestCaseClient) CreateAuthProxyWorkload(ctx context.Context, key types.NamespacedName, appLabel string, connectionString string, kind string) (*cloudsqlapi.AuthProxyWorkload, error) {
686+
func (cc *TestCaseClient) CreateAuthProxyWorkload(ctx context.Context, key types.NamespacedName, appLabel, connectionString, kind string) (*cloudsqlapi.AuthProxyWorkload, error) {
683687
p := NewAuthProxyWorkload(key)
684688
AddTCPInstance(p, connectionString)
685689
cc.ConfigureSelector(p, appLabel, kind)

internal/testintegration/integration_test.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,14 @@ func TestUpdateWorkloadContainerWhenDefaultProxyImageChanges(t *testing.T) {
265265
// Check that the pods have the expected default proxy image
266266
pods, err := testhelpers.ListPods(ctx, tcc.Client, tcc.Namespace, d.Spec.Selector)
267267
for _, p := range pods.Items {
268-
if got, want := p.Spec.Containers[1].Image, workload.DefaultProxyImage; got != want {
268+
want := workload.DefaultProxyImage
269+
var got string
270+
if len(p.Spec.Containers) > 1 {
271+
got = p.Spec.Containers[1].Image
272+
} else {
273+
got = p.Spec.InitContainers[0].Image
274+
}
275+
if got != want {
269276
t.Errorf("got %v, want %v image before operator upgrade", got, want)
270277
}
271278
}
@@ -305,7 +312,15 @@ func TestUpdateWorkloadContainerWhenDefaultProxyImageChanges(t *testing.T) {
305312
// Check that the new pods have the new default proxy image
306313
pods, err = testhelpers.ListPods(ctx, tcc.Client, tcc.Namespace, d.Spec.Selector)
307314
for _, p := range pods.Items {
308-
if got, want := p.Spec.Containers[1].Image, newDefault; got != want {
315+
want := newDefault
316+
var got string
317+
if len(p.Spec.Containers) > 1 {
318+
got = p.Spec.Containers[1].Image
319+
} else {
320+
got = p.Spec.InitContainers[0].Image
321+
}
322+
323+
if got != want {
309324
t.Errorf("got %v, want %v image before operator upgrade", got, want)
310325
}
311326
}

internal/workload/podspec_updates.go

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,19 @@ type Updater struct {
8989

9090
// defaultProxyImage is the current default proxy image for the operator
9191
defaultProxyImage string
92+
93+
// useSidecar specifies whether to use the Kubernetes SidecarContainers feature
94+
useSidecar bool
9295
}
9396

9497
// NewUpdater creates a new instance of Updater with a supplier
95-
// that loads the default proxy impage from the public docker registry
96-
func NewUpdater(userAgent string, defaultProxyImage string) *Updater {
97-
return &Updater{userAgent: userAgent, defaultProxyImage: defaultProxyImage}
98+
// that loads the default proxy image from the public docker registry
99+
func NewUpdater(userAgent, defaultProxyImage string, useSidecar bool) *Updater {
100+
return &Updater{
101+
userAgent: userAgent,
102+
defaultProxyImage: defaultProxyImage,
103+
useSidecar: useSidecar,
104+
}
98105
}
99106

100107
// ConfigError is an error with extra details about why an AuthProxyWorkload
@@ -513,19 +520,13 @@ func (s *updateState) update(wl *PodWorkload, matches []*cloudsqlapi.AuthProxyWo
513520

514521
s.initState(matches)
515522
podSpec := wl.PodSpec()
516-
containers := podSpec.Containers
517-
518-
var nonAuthProxyContainers []corev1.Container
519-
for i := 0; i < len(containers); i++ {
520-
if !strings.HasPrefix(containers[i].Name, ContainerPrefix) {
521-
nonAuthProxyContainers = append(nonAuthProxyContainers, containers[i])
522-
}
523-
}
523+
allContainers := append(podSpec.Containers, podSpec.InitContainers...)
524524

525-
for i := 0; i < len(nonAuthProxyContainers); i++ {
526-
c := nonAuthProxyContainers[i]
527-
for j := 0; j < len(c.Ports); j++ {
528-
s.addWorkloadPort(c.Ports[j].ContainerPort)
525+
for _, container := range allContainers {
526+
if !strings.HasPrefix(container.Name, ContainerPrefix) {
527+
for _, port := range container.Ports {
528+
s.addWorkloadPort(port.ContainerPort)
529+
}
529530
}
530531
}
531532

@@ -541,7 +542,12 @@ func (s *updateState) update(wl *PodWorkload, matches []*cloudsqlapi.AuthProxyWo
541542

542543
newContainer := corev1.Container{}
543544
s.updateContainer(inst, &newContainer)
544-
containers = append(containers, newContainer)
545+
546+
if s.updater.useSidecar {
547+
podSpec.InitContainers = append([]corev1.Container{newContainer}, podSpec.InitContainers...)
548+
} else {
549+
podSpec.Containers = append(podSpec.Containers, newContainer)
550+
}
545551

546552
// Add pod annotation for each instance
547553
k, v := s.updater.PodAnnotation(inst)
@@ -550,17 +556,13 @@ func (s *updateState) update(wl *PodWorkload, matches []*cloudsqlapi.AuthProxyWo
550556
// Add the envvar containing the proxy quit urls to the workloads
551557
s.addQuitEnvVar()
552558

553-
podSpec.Containers = containers
554-
555559
if len(ann) != 0 {
556560
wl.SetPodTemplateAnnotations(ann)
557561
}
558562

559-
for i := range podSpec.Containers {
560-
c := &podSpec.Containers[i]
561-
s.updateContainerEnv(c)
562-
s.applyContainerVolumes(c)
563-
}
563+
s.processContainers(&podSpec.Containers)
564+
s.processContainers(&podSpec.InitContainers)
565+
564566
s.applyVolumes(&podSpec)
565567

566568
// only return ConfigError if there were reported
@@ -574,6 +576,15 @@ func (s *updateState) update(wl *PodWorkload, matches []*cloudsqlapi.AuthProxyWo
574576
return nil
575577
}
576578

579+
// processContainers applies container envs and volumes to all containers
580+
func (s *updateState) processContainers(containers *[]corev1.Container) {
581+
for i := range *containers {
582+
c := &(*containers)[i]
583+
s.updateContainerEnv(c)
584+
s.applyContainerVolumes(c)
585+
}
586+
}
587+
577588
// updateContainer Creates or updates the proxy container in the workload's PodSpec
578589
func (s *updateState) updateContainer(p *cloudsqlapi.AuthProxyWorkload, c *corev1.Container) {
579590
// if the c was fully overridden, just use that c.
@@ -610,8 +621,11 @@ func (s *updateState) updateContainer(p *cloudsqlapi.AuthProxyWorkload, c *corev
610621
}
611622

612623
c.Name = ContainerName(p)
613-
c.ImagePullPolicy = "IfNotPresent"
614-
624+
c.ImagePullPolicy = corev1.PullIfNotPresent
625+
if s.updater.useSidecar {
626+
policy := corev1.ContainerRestartPolicyAlways
627+
c.RestartPolicy = &policy
628+
}
615629
s.applyContainerSpec(p, c)
616630

617631
// Build the c

internal/workload/podspec_updates_test.go

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ func TestUpdatePodWorkload(t *testing.T) {
140140
wantContainerName = "csql-default-" + wantsName
141141
wantsInstanceName = "project:server:db"
142142
wantsInstanceArg = fmt.Sprintf("%s?port=%d", wantsInstanceName, wantsPort)
143-
u = workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage)
143+
u = workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage, false)
144144
)
145145
var err error
146146

@@ -195,7 +195,7 @@ func TestUpdateWorkloadFixedPort(t *testing.T) {
195195
"DB_HOST": "127.0.0.1",
196196
"DB_PORT": strconv.Itoa(int(wantsPort)),
197197
}
198-
u = workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage)
198+
u = workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage, false)
199199
)
200200

201201
// Create a pod
@@ -263,7 +263,7 @@ func TestWorkloadNoPortSet(t *testing.T) {
263263
"DB_PORT": strconv.Itoa(int(wantsPort)),
264264
}
265265
)
266-
u := workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage)
266+
u := workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage, false)
267267

268268
// Create a pod
269269
wl := podWorkload()
@@ -321,7 +321,7 @@ func TestContainerImageChanged(t *testing.T) {
321321
var (
322322
wantsInstanceName = "project:server:db"
323323
wantImage = "custom-image:latest"
324-
u = workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage)
324+
u = workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage, false)
325325
)
326326

327327
// Create a pod
@@ -363,7 +363,7 @@ func TestContainerImageEmpty(t *testing.T) {
363363
var (
364364
wantsInstanceName = "project:server:db"
365365
wantImage = workload.DefaultProxyImage
366-
u = workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage)
366+
u = workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage, false)
367367
)
368368
// Create a AuthProxyWorkload that matches the deployment
369369

@@ -422,7 +422,7 @@ func TestContainerReplaced(t *testing.T) {
422422
wantContainer = &corev1.Container{
423423
Name: "sample", Image: "debian:latest", Command: []string{"/bin/bash"},
424424
}
425-
u = workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage)
425+
u = workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage, false)
426426
)
427427

428428
// Create a pod
@@ -476,7 +476,7 @@ func TestResourcesFromSpec(t *testing.T) {
476476
},
477477
}
478478

479-
u = workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage)
479+
u = workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage, false)
480480
)
481481

482482
// Create a pod
@@ -757,7 +757,7 @@ func TestProxyCLIArgs(t *testing.T) {
757757

758758
for _, tc := range testcases {
759759
t.Run(tc.desc, func(t *testing.T) {
760-
u := workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage)
760+
u := workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage, false)
761761

762762
// Create a pod
763763
wl := &workload.PodWorkload{Pod: &corev1.Pod{
@@ -892,7 +892,7 @@ func TestPodTemplateAnnotations(t *testing.T) {
892892
"cloudsql.cloud.google.com/instance2": "2," + workload.DefaultProxyImage,
893893
}
894894

895-
u = workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage)
895+
u = workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage, false)
896896
)
897897

898898
// Create a pod
@@ -926,7 +926,7 @@ func TestPodTemplateAnnotations(t *testing.T) {
926926

927927
func TestTelemetryAddsTelemetryContainerPort(t *testing.T) {
928928

929-
var u = workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage)
929+
var u = workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage, false)
930930

931931
// Create a pod
932932
wl := podWorkload()
@@ -994,7 +994,7 @@ func TestTelemetryAddsTelemetryContainerPort(t *testing.T) {
994994
func TestQuitURLEnvVar(t *testing.T) {
995995

996996
var (
997-
u = workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage)
997+
u = workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage, false)
998998
)
999999

10001000
// Create a pod
@@ -1035,7 +1035,7 @@ func TestQuitURLEnvVar(t *testing.T) {
10351035

10361036
func TestPreStopHook(t *testing.T) {
10371037

1038-
var u = workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage)
1038+
var u = workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage, false)
10391039

10401040
// Create a pod
10411041
wl := podWorkload()
@@ -1123,7 +1123,7 @@ func TestWorkloadUnixVolume(t *testing.T) {
11231123
wantWorkloadEnv = map[string]string{
11241124
"DB_SOCKET_PATH": wantsUnixSocketPath,
11251125
}
1126-
u = workload.NewUpdater("authproxyworkload/dev", workload.DefaultProxyImage)
1126+
u = workload.NewUpdater("authproxyworkload/dev", workload.DefaultProxyImage, false)
11271127
)
11281128

11291129
// Create a pod
@@ -1201,7 +1201,7 @@ func TestWorkloadUnixVolume(t *testing.T) {
12011201
func TestUpdater_CheckWorkloadContainers(t *testing.T) {
12021202
var (
12031203
wantsInstanceName = "project:server:db"
1204-
u = workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage)
1204+
u = workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage, false)
12051205
)
12061206

12071207
// Create a AuthProxyWorkloads to match the pods

0 commit comments

Comments
 (0)