Skip to content

Commit b1ab75e

Browse files
committed
Add prependedContainers
1 parent 8c6fcc1 commit b1ab75e

14 files changed

+274
-44
lines changed

docs/sidecar-configuration-format.md

+22
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,28 @@ initContainers:
9797
- name: some-initcontainer
9898
image: init:1.12.2
9999
imagePullPolicy: IfNotPresent
100+
101+
# prependedContainers will be prepended to the top of the list of normal
102+
# containers. This primarily allows exploitation of this workaround for ensuring
103+
# sidecars finish starting before the other containers in the pod are launched:
104+
# https://medium.com/@marko.luksa/delaying-application-start-until-sidecar-is-ready-2ec2d21a7b74
105+
prependedContainers:
106+
- name: prepended-nginx-container
107+
image: nginx:1.12.2
108+
imagePullPolicy: IfNotPresent
109+
ports:
110+
- containerPort: 80
111+
volumeMounts:
112+
- name: nginx-conf
113+
mountPath: /etc/nginx
114+
lifecycle:
115+
postStart:
116+
exec:
117+
command:
118+
- /bin/sh
119+
- -c
120+
- |
121+
while ! nc -w 1 127.0.0.1 80; do sleep 1; done
100122
```
101123
102124
## Configuring new sidecars

internal/pkg/config/config.go

+29-12
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,18 @@ var (
3232

3333
// InjectionConfig is a specific instance of a injected config, for a given annotation
3434
type InjectionConfig struct {
35-
Name string `json:"name"`
36-
Inherits string `json:"inherits"`
37-
Containers []corev1.Container `json:"containers"`
38-
Volumes []corev1.Volume `json:"volumes"`
39-
Environment []corev1.EnvVar `json:"env"`
40-
VolumeMounts []corev1.VolumeMount `json:"volumeMounts"`
41-
HostAliases []corev1.HostAlias `json:"hostAliases"`
42-
HostNetwork bool `json:"hostNetwork"`
43-
HostPID bool `json:"hostPID"`
44-
InitContainers []corev1.Container `json:"initContainers"`
45-
ServiceAccountName string `json:"serviceAccountName"`
35+
Name string `json:"name"`
36+
Inherits string `json:"inherits"`
37+
Containers []corev1.Container `json:"containers"`
38+
Volumes []corev1.Volume `json:"volumes"`
39+
Environment []corev1.EnvVar `json:"env"`
40+
VolumeMounts []corev1.VolumeMount `json:"volumeMounts"`
41+
HostAliases []corev1.HostAlias `json:"hostAliases"`
42+
HostNetwork bool `json:"hostNetwork"`
43+
HostPID bool `json:"hostPID"`
44+
InitContainers []corev1.Container `json:"initContainers"`
45+
ServiceAccountName string `json:"serviceAccountName"`
46+
PrependedContainers []corev1.Container `json:"prependedContainers"`
4647

4748
version string
4849
}
@@ -68,7 +69,7 @@ func (c *InjectionConfig) String() string {
6869
return fmt.Sprintf("%s%s: %d containers, %d init containers, %d volumes, %d environment vars, %d volume mounts, %d host aliases%s",
6970
c.FullName(),
7071
inheritsString,
71-
len(c.Containers),
72+
len(c.Containers)+len(c.PrependedContainers),
7273
len(c.InitContainers),
7374
len(c.Volumes),
7475
len(c.Environment),
@@ -272,6 +273,22 @@ func (c *InjectionConfig) Merge(child *InjectionConfig) error {
272273
}
273274
}
274275

276+
// merge prepended containers
277+
for _, cv := range child.PrependedContainers {
278+
contains := false
279+
280+
for bi, bv := range c.PrependedContainers {
281+
if bv.Name == cv.Name {
282+
contains = true
283+
c.PrependedContainers[bi] = cv
284+
}
285+
}
286+
287+
if !contains {
288+
c.PrependedContainers = append(c.PrependedContainers, cv)
289+
}
290+
}
291+
275292
// merge serviceAccount settings to the left
276293
if child.ServiceAccountName != "" {
277294
c.ServiceAccountName = child.ServiceAccountName

internal/pkg/config/config_test.go

+32-18
Original file line numberDiff line numberDiff line change
@@ -112,27 +112,29 @@ var (
112112
},
113113
// test simple inheritance
114114
"simple inheritance from complex-sidecar": testhelper.ConfigExpectation{
115-
Name: "inheritance-complex",
116-
Version: "v1",
117-
Path: fixtureSidecarsDir + "/inheritance-1.yaml",
118-
EnvCount: 2,
119-
ContainerCount: 5,
120-
VolumeCount: 2,
121-
VolumeMountCount: 0,
122-
HostAliasCount: 1,
123-
InitContainerCount: 1,
115+
Name: "inheritance-complex",
116+
Version: "v1",
117+
Path: fixtureSidecarsDir + "/inheritance-1.yaml",
118+
EnvCount: 2,
119+
ContainerCount: 5,
120+
VolumeCount: 2,
121+
VolumeMountCount: 0,
122+
HostAliasCount: 1,
123+
InitContainerCount: 1,
124+
PrependedContainerCount: 1,
124125
},
125126
// test deep inheritance
126127
"deep inheritance from inheritance-complex": testhelper.ConfigExpectation{
127-
Name: "inheritance-deep",
128-
Version: "v2",
129-
Path: fixtureSidecarsDir + "/inheritance-deep-2.yaml",
130-
EnvCount: 3,
131-
ContainerCount: 6,
132-
VolumeCount: 3,
133-
VolumeMountCount: 0,
134-
HostAliasCount: 3,
135-
InitContainerCount: 2,
128+
Name: "inheritance-deep",
129+
Version: "v2",
130+
Path: fixtureSidecarsDir + "/inheritance-deep-2.yaml",
131+
EnvCount: 3,
132+
ContainerCount: 6,
133+
VolumeCount: 3,
134+
VolumeMountCount: 0,
135+
HostAliasCount: 3,
136+
InitContainerCount: 2,
137+
PrependedContainerCount: 2,
136138
},
137139
"service-account": testhelper.ConfigExpectation{
138140
Name: "service-account",
@@ -194,6 +196,18 @@ var (
194196
HostNetwork: true,
195197
HostPID: true,
196198
},
199+
"prepended-containers": testhelper.ConfigExpectation{
200+
Name: "prepended-containers",
201+
Version: "latest",
202+
Path: fixtureSidecarsDir + "/prepended-containers.yaml",
203+
EnvCount: 0,
204+
ContainerCount: 2,
205+
VolumeCount: 0,
206+
VolumeMountCount: 0,
207+
HostAliasCount: 0,
208+
InitContainerCount: 0,
209+
PrependedContainerCount: 2,
210+
},
197211
}
198212
)
199213

internal/pkg/config/watcher/loader_test.go

+15
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,21 @@ var (
133133
InitContainerCount: 1,
134134
},
135135
},
136+
137+
"configmap-prepended-containers": []testhelper.ConfigExpectation{
138+
testhelper.ConfigExpectation{
139+
Name: "prepended-containers",
140+
Version: "latest",
141+
Path: fixtureSidecarsDir + "/prepended-containers.yaml",
142+
VolumeCount: 0,
143+
EnvCount: 0,
144+
ContainerCount: 2,
145+
VolumeMountCount: 0,
146+
HostAliasCount: 0,
147+
InitContainerCount: 0,
148+
PrependedContainerCount: 2,
149+
},
150+
},
136151
}
137152
)
138153

internal/pkg/testing/config.go

+11-10
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,17 @@ type ConfigExpectation struct {
1212
// version is the parsed version string, or "latest" if omitted
1313
Version string
1414
// Path is the path to the YAML to load the sidecar yaml from
15-
Path string
16-
EnvCount int
17-
ContainerCount int
18-
VolumeCount int
19-
VolumeMountCount int
20-
HostAliasCount int
21-
HostNetwork bool
22-
HostPID bool
23-
InitContainerCount int
24-
ServiceAccount string
15+
Path string
16+
EnvCount int
17+
ContainerCount int
18+
VolumeCount int
19+
VolumeMountCount int
20+
HostAliasCount int
21+
HostNetwork bool
22+
HostPID bool
23+
InitContainerCount int
24+
ServiceAccount string
25+
PrependedContainerCount int
2526

2627
// LoadError is an error, if any, that is expected during load
2728
LoadError error

pkg/server/webhook.go

+24-4
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,27 @@ func setEnvironment(target []corev1.Container, addedEnv []corev1.EnvVar, basePat
210210
return patch
211211
}
212212

213-
func addContainers(target, added []corev1.Container, basePath string) (patch []patchOperation) {
213+
func addContainers(target, prepended, appended []corev1.Container, basePath string) (patch []patchOperation) {
214214
first := len(target) == 0
215215
var value interface{}
216-
for _, add := range added {
216+
for i := len(prepended) - 1; i >= 0; i-- {
217+
add := prepended[i]
218+
value = add
219+
path := basePath
220+
if first {
221+
first = false
222+
value = []corev1.Container{add}
223+
} else {
224+
path = path + "/0"
225+
}
226+
patch = append(patch, patchOperation{
227+
Op: "add",
228+
Path: path,
229+
Value: value,
230+
})
231+
}
232+
233+
for _, add := range appended {
217234
value = add
218235
path := basePath
219236
if first {
@@ -464,7 +481,7 @@ func createPatch(pod *corev1.Pod, inj *config.InjectionConfig, annotations map[s
464481
// this mutates inj.InitContainers with our environment vars
465482
mutatedInjectedInitContainers := mergeEnvVars(inj.Environment, inj.InitContainers)
466483
mutatedInjectedInitContainers = mergeVolumeMounts(inj.VolumeMounts, mutatedInjectedInitContainers)
467-
patch = append(patch, addContainers(pod.Spec.InitContainers, mutatedInjectedInitContainers, "/spec/initContainers")...)
484+
patch = append(patch, addContainers(pod.Spec.InitContainers, []corev1.Container{}, mutatedInjectedInitContainers, "/spec/initContainers")...)
468485
}
469486

470487
{ // container injections
@@ -475,7 +492,10 @@ func createPatch(pod *corev1.Pod, inj *config.InjectionConfig, annotations map[s
475492
// this mutates inj.Containers with our environment vars
476493
mutatedInjectedContainers := mergeEnvVars(inj.Environment, inj.Containers)
477494
mutatedInjectedContainers = mergeVolumeMounts(inj.VolumeMounts, mutatedInjectedContainers)
478-
patch = append(patch, addContainers(pod.Spec.Containers, mutatedInjectedContainers, "/spec/containers")...)
495+
mutatedInjectedPrependedContainers := mergeEnvVars(inj.Environment, inj.PrependedContainers)
496+
mutatedInjectedPrependedContainers = mergeVolumeMounts(inj.VolumeMounts, mutatedInjectedPrependedContainers)
497+
// then, add containers to the patch
498+
patch = append(patch, addContainers(pod.Spec.Containers, mutatedInjectedPrependedContainers, mutatedInjectedContainers, "/spec/containers")...)
479499
}
480500

481501
{ // pod level mutations

pkg/server/webhook_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ var (
3333
obj7 = "test/fixtures/k8s/object7.yaml"
3434
obj7v2 = "test/fixtures/k8s/object7-v2.yaml"
3535
obj7v3 = "test/fixtures/k8s/object7-badrequestformat.yaml"
36+
obj8 = "test/fixtures/k8s/object8.yaml"
3637
ignoredNamespace = "test/fixtures/k8s/ignored-namespace-pod.yaml"
3738
badSidecar = "test/fixtures/k8s/bad-sidecar.yaml"
3839

@@ -51,6 +52,7 @@ var (
5152
{configuration: obj7, expectedSidecar: "init-containers:latest"},
5253
{configuration: obj7v2, expectedSidecar: "init-containers:v2"},
5354
{configuration: obj7v3, expectedSidecar: "", expectedError: ErrRequestedSidecarNotFound},
55+
{configuration: obj8, expectedSidecar: "prepended-containers:latest"},
5456
{configuration: ignoredNamespace, expectedSidecar: "", expectedError: ErrSkipIgnoredNamespace},
5557
{configuration: badSidecar, expectedSidecar: "", expectedError: ErrRequestedSidecarNotFound},
5658
}
@@ -60,6 +62,7 @@ var (
6062
{name: "missing-sidecar-config", allowed: true, patchExpected: false},
6163
{name: "sidecar-test-1", allowed: true, patchExpected: true},
6264
{name: "env-override", allowed: true, patchExpected: true},
65+
{name: "prepended-containers", allowed: true, patchExpected: true},
6366
{name: "service-account", allowed: true, patchExpected: true},
6467
{name: "service-account-already-set", allowed: true, patchExpected: true},
6568
{name: "service-account-set-default", allowed: true, patchExpected: true},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
[
2+
{
3+
"op": "add",
4+
"path": "/spec/containers",
5+
"value": [
6+
{
7+
"image": "alpine:latest",
8+
"name": "prepended-container-2",
9+
"ports": [
10+
{
11+
"containerPort": 1234
12+
}
13+
],
14+
"resources": {}
15+
}
16+
]
17+
},
18+
{
19+
"op": "add",
20+
"path": "/spec/containers/0",
21+
"value": {
22+
"image": "foobar:2020",
23+
"imagePullPolicy": "IfNotPresent",
24+
"name": "prepended-container-1",
25+
"ports": [
26+
{
27+
"containerPort": 666
28+
}
29+
],
30+
"resources": {}
31+
}
32+
},
33+
{
34+
"op": "add",
35+
"path": "/spec/containers/-",
36+
"value": {
37+
"image": "nginx:1.12.2",
38+
"imagePullPolicy": "IfNotPresent",
39+
"name": "sidecar-add-vm",
40+
"ports": [
41+
{
42+
"containerPort": 80
43+
}
44+
],
45+
"resources": {}
46+
}
47+
},
48+
{
49+
"op": "add",
50+
"path": "/spec/containers/-",
51+
"value": {
52+
"image": "foo:69",
53+
"name": "sidecar-existing-vm",
54+
"ports": [
55+
{
56+
"containerPort": 420
57+
}
58+
],
59+
"resources": {}
60+
}
61+
},
62+
{
63+
"op": "add",
64+
"path": "/metadata/annotations/injector.unittest.com~1status",
65+
"value": "injected"
66+
}
67+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
# this is an AdmissionRequest object
3+
# https://godoc.org/k8s.io/api/admission/v1beta1#AdmissionRequest
4+
object:
5+
metadata:
6+
annotations:
7+
injector.unittest.com/request: "prepended-containers"
8+
spec:
9+
containers: []
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
apiVersion: v1
3+
kind: ConfigMap
4+
metadata:
5+
name: test-prepended-containers
6+
namespace: default
7+
data:
8+
test-tumblr1: |
9+
name: prepended-containers
10+
containers:
11+
- name: sidecar-add-vm
12+
image: nginx:1.12.2
13+
imagePullPolicy: IfNotPresent
14+
ports:
15+
- containerPort: 80
16+
- name: sidecar-existing-vm
17+
image: foo:69
18+
ports:
19+
- containerPort: 420
20+
prependedContainers:
21+
- name: prepended-container-1
22+
image: foobar:2020
23+
imagePullPolicy: IfNotPresent
24+
ports:
25+
- containerPort: 666
26+
- name: prepended-container-2
27+
image: alpine:latest
28+
ports:
29+
- containerPort: 1234

test/fixtures/k8s/object8.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
name: object8
2+
namespace: unittest
3+
annotations:
4+
"injector.unittest.com/request": "prepended-containers"

0 commit comments

Comments
 (0)