Skip to content

Commit 984c7b6

Browse files
committed
Add init container for setting up base config (nginx#2649)
Problem: We are starting to introduce configuration options that exist in the main context. However, that configuration won't be written until the control plane writes it to nginx, meaning it doesn't exist on nginx startup. Therefore nginx uses its default configuration for a brief time, which is incorrect. We want to be able to provide this configuration on startup. Solution: Using an init container, we can mount a ConfigMap containing the dynamic base config, and copy it to the proper location in the filesystem before nginx starts. We can't mount the ConfigMap directly to the proper location because it would be read-only, preventing our control plane from writing to it. This allows us to bootstrap the user config into nginx on startup, while also allowing our control plane to overwrite it if the user ever changes the config after the fact. Removed logic that cleared out nginx files on startup because it would erase this bootstrap config, and it wasn't really needed since we delete nginx files when we write config anyway. Also fixed an issue where the log level was not honored when no Gateway resources existed.
1 parent 00301e6 commit 984c7b6

File tree

18 files changed

+534
-20
lines changed

18 files changed

+534
-20
lines changed

charts/nginx-gateway-fabric/templates/deployment.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,33 @@ spec:
2929
{{- end }}
3030
{{- end }}
3131
spec:
32+
initContainers:
33+
- name: copy-nginx-config
34+
image: {{ .Values.nginxGateway.image.repository }}:{{ default .Chart.AppVersion .Values.nginxGateway.image.tag }}
35+
imagePullPolicy: {{ .Values.nginxGateway.image.pullPolicy }}
36+
command:
37+
- /usr/bin/gateway
38+
- copy
39+
- --source
40+
- /includes/main.conf
41+
- --destination
42+
- /etc/nginx/main-includes/main.conf
43+
securityContext:
44+
seccompProfile:
45+
type: RuntimeDefault
46+
capabilities:
47+
add:
48+
- KILL # Set because the binary has CAP_KILL for the main controller process. Not used by init.
49+
drop:
50+
- ALL
51+
readOnlyRootFilesystem: true
52+
runAsUser: 102
53+
runAsGroup: 1001
54+
volumeMounts:
55+
- name: nginx-includes-configmap
56+
mountPath: /includes
57+
- name: nginx-main-includes
58+
mountPath: /etc/nginx/main-includes
3259
containers:
3360
- args:
3461
- static-mode
@@ -223,6 +250,9 @@ spec:
223250
emptyDir: {}
224251
- name: nginx-includes
225252
emptyDir: {}
253+
- name: nginx-includes-configmap
254+
configMap:
255+
name: nginx-includes
226256
{{- with .Values.extraVolumes -}}
227257
{{ toYaml . | nindent 6 }}
228258
{{- end }}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
name: nginx-includes
5+
namespace: {{ .Release.Namespace }}
6+
labels:
7+
{{- include "nginx-gateway.labels" . | nindent 4 }}
8+
data:
9+
main.conf: |
10+
{{- if and .Values.nginx.config .Values.nginx.config.logging .Values.nginx.config.logging.errorLevel }}
11+
error_log stderr {{ .Values.nginx.config.logging.errorLevel }};
12+
{{ else }}
13+
error_log stderr info;
14+
{{- end }}

charts/nginx-gateway-fabric/templates/scc.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ seccompProfiles:
3232
volumes:
3333
- emptyDir
3434
- secret
35+
- configMap
3536
users:
3637
- {{ printf "system:serviceaccount:%s:%s" .Release.Namespace (include "nginx-gateway.serviceAccountName" .) }}
3738
allowedCapabilities:

cmd/gateway/commands.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"errors"
55
"fmt"
6+
"io"
67
"os"
78
"runtime/debug"
89
"strconv"
@@ -481,6 +482,63 @@ func createSleepCommand() *cobra.Command {
481482
return cmd
482483
}
483484

485+
func createCopyCommand() *cobra.Command {
486+
// flag names
487+
const srcFlag = "source"
488+
const destFlag = "destination"
489+
// flag values
490+
var src, dest string
491+
492+
cmd := &cobra.Command{
493+
Use: "copy",
494+
Short: "Copy a file to a destination",
495+
RunE: func(_ *cobra.Command, _ []string) error {
496+
if len(src) == 0 {
497+
return errors.New("source must not be empty")
498+
}
499+
if len(dest) == 0 {
500+
return errors.New("destination must not be empty")
501+
}
502+
503+
srcFile, err := os.Open(src)
504+
if err != nil {
505+
return fmt.Errorf("error opening source file: %w", err)
506+
}
507+
defer srcFile.Close()
508+
509+
destFile, err := os.Create(dest)
510+
if err != nil {
511+
return fmt.Errorf("error creating destination file: %w", err)
512+
}
513+
defer destFile.Close()
514+
515+
if _, err := io.Copy(destFile, srcFile); err != nil {
516+
return fmt.Errorf("error copying file contents: %w", err)
517+
}
518+
519+
return nil
520+
},
521+
}
522+
523+
cmd.Flags().StringVar(
524+
&src,
525+
srcFlag,
526+
"",
527+
"The source file to be copied",
528+
)
529+
530+
cmd.Flags().StringVar(
531+
&dest,
532+
destFlag,
533+
"",
534+
"The destination for the source file to be copied to",
535+
)
536+
537+
cmd.MarkFlagsRequiredTogether(srcFlag, destFlag)
538+
539+
return cmd
540+
}
541+
484542
func parseFlags(flags *pflag.FlagSet) ([]string, []string) {
485543
var flagKeys, flagValues []string
486544

cmd/gateway/commands_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,51 @@ func TestSleepCmdFlagValidation(t *testing.T) {
437437
}
438438
}
439439

440+
func TestCopyCmdFlagValidation(t *testing.T) {
441+
t.Parallel()
442+
tests := []flagTestCase{
443+
{
444+
name: "valid flags",
445+
args: []string{
446+
"--source=/my/file",
447+
"--destination=dest/file",
448+
},
449+
wantErr: false,
450+
},
451+
{
452+
name: "omitted flags",
453+
args: nil,
454+
wantErr: false,
455+
},
456+
{
457+
name: "source set without destination",
458+
args: []string{
459+
"--source=/my/file",
460+
},
461+
wantErr: true,
462+
expectedErrPrefix: "if any flags in the group [source destination] are set they must all be set; " +
463+
"missing [destination]",
464+
},
465+
{
466+
name: "destination set without source",
467+
args: []string{
468+
"--destination=/dest/file",
469+
},
470+
wantErr: true,
471+
expectedErrPrefix: "if any flags in the group [source destination] are set they must all be set; " +
472+
"missing [source]",
473+
},
474+
}
475+
476+
for _, test := range tests {
477+
t.Run(test.name, func(t *testing.T) {
478+
t.Parallel()
479+
cmd := createCopyCommand()
480+
testFlag(t, cmd, test)
481+
})
482+
}
483+
}
484+
440485
func TestParseFlags(t *testing.T) {
441486
t.Parallel()
442487
g := NewWithT(t)

cmd/gateway/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ func main() {
2323
rootCmd.AddCommand(
2424
createStaticModeCommand(),
2525
createProvisionerModeCommand(),
26+
createCopyCommand(),
2627
createSleepCommand(),
2728
)
2829

config/tests/static-deployment.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,33 @@ spec:
2121
app.kubernetes.io/name: nginx-gateway
2222
app.kubernetes.io/instance: nginx-gateway
2323
spec:
24+
initContainers:
25+
- name: copy-nginx-config
26+
image: ghcr.io/nginxinc/nginx-gateway-fabric:edge
27+
imagePullPolicy: Always
28+
command:
29+
- /usr/bin/gateway
30+
- copy
31+
- --source
32+
- /includes/main.conf
33+
- --destination
34+
- /etc/nginx/main-includes/main.conf
35+
securityContext:
36+
seccompProfile:
37+
type: RuntimeDefault
38+
capabilities:
39+
add:
40+
- KILL # Set because the binary has CAP_KILL for the main controller process. Not used by init.
41+
drop:
42+
- ALL
43+
readOnlyRootFilesystem: true
44+
runAsUser: 102
45+
runAsGroup: 1001
46+
volumeMounts:
47+
- name: nginx-includes-configmap
48+
mountPath: /includes
49+
- name: nginx-main-includes
50+
mountPath: /etc/nginx/main-includes
2451
containers:
2552
- args:
2653
- static-mode
@@ -137,3 +164,6 @@ spec:
137164
emptyDir: {}
138165
- name: nginx-includes
139166
emptyDir: {}
167+
- name: nginx-includes-configmap
168+
configMap:
169+
name: nginx-includes

deploy/aws-nlb/deploy.yaml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,19 @@ subjects:
143143
namespace: nginx-gateway
144144
---
145145
apiVersion: v1
146+
data:
147+
main.conf: |
148+
error_log stderr info;
149+
kind: ConfigMap
150+
metadata:
151+
labels:
152+
app.kubernetes.io/instance: nginx-gateway
153+
app.kubernetes.io/name: nginx-gateway
154+
app.kubernetes.io/version: edge
155+
name: nginx-includes
156+
namespace: nginx-gateway
157+
---
158+
apiVersion: v1
146159
kind: Service
147160
metadata:
148161
annotations:
@@ -290,6 +303,33 @@ spec:
290303
name: nginx-cache
291304
- mountPath: /etc/nginx/includes
292305
name: nginx-includes
306+
initContainers:
307+
- command:
308+
- /usr/bin/gateway
309+
- copy
310+
- --source
311+
- /includes/main.conf
312+
- --destination
313+
- /etc/nginx/main-includes/main.conf
314+
image: ghcr.io/nginxinc/nginx-gateway-fabric:edge
315+
imagePullPolicy: Always
316+
name: copy-nginx-config
317+
securityContext:
318+
capabilities:
319+
add:
320+
- KILL
321+
drop:
322+
- ALL
323+
readOnlyRootFilesystem: true
324+
runAsGroup: 1001
325+
runAsUser: 102
326+
seccompProfile:
327+
type: RuntimeDefault
328+
volumeMounts:
329+
- mountPath: /includes
330+
name: nginx-includes-configmap
331+
- mountPath: /etc/nginx/main-includes
332+
name: nginx-main-includes
293333
securityContext:
294334
fsGroup: 1001
295335
runAsNonRoot: true
@@ -311,6 +351,9 @@ spec:
311351
name: nginx-cache
312352
- emptyDir: {}
313353
name: nginx-includes
354+
- configMap:
355+
name: nginx-includes
356+
name: nginx-includes-configmap
314357
---
315358
apiVersion: gateway.networking.k8s.io/v1
316359
kind: GatewayClass

deploy/azure/deploy.yaml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,19 @@ subjects:
143143
namespace: nginx-gateway
144144
---
145145
apiVersion: v1
146+
data:
147+
main.conf: |
148+
error_log stderr info;
149+
kind: ConfigMap
150+
metadata:
151+
labels:
152+
app.kubernetes.io/instance: nginx-gateway
153+
app.kubernetes.io/name: nginx-gateway
154+
app.kubernetes.io/version: edge
155+
name: nginx-includes
156+
namespace: nginx-gateway
157+
---
158+
apiVersion: v1
146159
kind: Service
147160
metadata:
148161
labels:
@@ -287,6 +300,33 @@ spec:
287300
name: nginx-cache
288301
- mountPath: /etc/nginx/includes
289302
name: nginx-includes
303+
initContainers:
304+
- command:
305+
- /usr/bin/gateway
306+
- copy
307+
- --source
308+
- /includes/main.conf
309+
- --destination
310+
- /etc/nginx/main-includes/main.conf
311+
image: ghcr.io/nginxinc/nginx-gateway-fabric:edge
312+
imagePullPolicy: Always
313+
name: copy-nginx-config
314+
securityContext:
315+
capabilities:
316+
add:
317+
- KILL
318+
drop:
319+
- ALL
320+
readOnlyRootFilesystem: true
321+
runAsGroup: 1001
322+
runAsUser: 102
323+
seccompProfile:
324+
type: RuntimeDefault
325+
volumeMounts:
326+
- mountPath: /includes
327+
name: nginx-includes-configmap
328+
- mountPath: /etc/nginx/main-includes
329+
name: nginx-main-includes
290330
nodeSelector:
291331
kubernetes.io/os: linux
292332
securityContext:
@@ -310,6 +350,9 @@ spec:
310350
name: nginx-cache
311351
- emptyDir: {}
312352
name: nginx-includes
353+
- configMap:
354+
name: nginx-includes
355+
name: nginx-includes-configmap
313356
---
314357
apiVersion: gateway.networking.k8s.io/v1
315358
kind: GatewayClass

0 commit comments

Comments
 (0)