diff --git a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl index 101038dc3..6b8ec00eb 100644 --- a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl +++ b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl @@ -331,6 +331,9 @@ http { proxy_set_header Accept-Encoding ""; {{ end }} + {{/* Add any additional configuration defined */}} + {{ $location.ConfigurationSnippet }} + {{ buildProxyPass $backends $location }} {{ else }} #{{ $location.Denied }} diff --git a/core/pkg/ingress/annotations/snippet/main.go b/core/pkg/ingress/annotations/snippet/main.go new file mode 100644 index 000000000..f05a665e3 --- /dev/null +++ b/core/pkg/ingress/annotations/snippet/main.go @@ -0,0 +1,42 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package snippet + +import ( + "k8s.io/kubernetes/pkg/apis/extensions" + + "k8s.io/ingress/core/pkg/ingress/annotations/parser" +) + +const ( + annotation = "ingress.kubernetes.io/configuration-snippet" +) + +type snippet struct { +} + +// NewParser creates a new CORS annotation parser +func NewParser() parser.IngressAnnotation { + return snippet{} +} + +// Parse parses the annotations contained in the ingress rule +// used to indicate if the location/s contains a fragment of +// configuration to be included inside the paths of the rules +func (a snippet) Parse(ing *extensions.Ingress) (interface{}, error) { + return parser.GetStringAnnotation(annotation, ing) +} diff --git a/core/pkg/ingress/annotations/snippet/main_test.go b/core/pkg/ingress/annotations/snippet/main_test.go new file mode 100644 index 000000000..269996a95 --- /dev/null +++ b/core/pkg/ingress/annotations/snippet/main_test.go @@ -0,0 +1,57 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package snippet + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" +) + +func TestParse(t *testing.T) { + ap := NewParser() + if ap == nil { + t.Fatalf("expected a parser.IngressAnnotation but returned nil") + } + + testCases := []struct { + annotations map[string]string + expected string + }{ + {map[string]string{annotation: "more_headers"}, "more_headers"}, + {map[string]string{annotation: "false"}, "false"}, + {map[string]string{}, ""}, + {nil, ""}, + } + + ing := &extensions.Ingress{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Spec: extensions.IngressSpec{}, + } + + for _, testCase := range testCases { + ing.SetAnnotations(testCase.annotations) + result, _ := ap.Parse(ing) + if result != testCase.expected { + t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations) + } + } +} diff --git a/core/pkg/ingress/controller/annotations.go b/core/pkg/ingress/controller/annotations.go index 5a54e83cd..c0727dd87 100644 --- a/core/pkg/ingress/controller/annotations.go +++ b/core/pkg/ingress/controller/annotations.go @@ -34,6 +34,7 @@ import ( "k8s.io/ingress/core/pkg/ingress/annotations/rewrite" "k8s.io/ingress/core/pkg/ingress/annotations/secureupstream" "k8s.io/ingress/core/pkg/ingress/annotations/sessionaffinity" + "k8s.io/ingress/core/pkg/ingress/annotations/snippet" "k8s.io/ingress/core/pkg/ingress/annotations/sslpassthrough" "k8s.io/ingress/core/pkg/ingress/errors" "k8s.io/ingress/core/pkg/ingress/resolver" @@ -52,19 +53,20 @@ type annotationExtractor struct { func newAnnotationExtractor(cfg extractorConfig) annotationExtractor { return annotationExtractor{ map[string]parser.IngressAnnotation{ - "BasicDigestAuth": auth.NewParser(auth.AuthDirectory, cfg), - "ExternalAuth": authreq.NewParser(), - "CertificateAuth": authtls.NewParser(cfg), - "EnableCORS": cors.NewParser(), - "HealthCheck": healthcheck.NewParser(cfg), - "Whitelist": ipwhitelist.NewParser(cfg), - "UsePortInRedirects": portinredirect.NewParser(cfg), - "Proxy": proxy.NewParser(cfg), - "RateLimit": ratelimit.NewParser(), - "Redirect": rewrite.NewParser(cfg), - "SecureUpstream": secureupstream.NewParser(), - "SessionAffinity": sessionaffinity.NewParser(), - "SSLPassthrough": sslpassthrough.NewParser(), + "BasicDigestAuth": auth.NewParser(auth.AuthDirectory, cfg), + "ExternalAuth": authreq.NewParser(), + "CertificateAuth": authtls.NewParser(cfg), + "EnableCORS": cors.NewParser(), + "HealthCheck": healthcheck.NewParser(cfg), + "Whitelist": ipwhitelist.NewParser(cfg), + "UsePortInRedirects": portinredirect.NewParser(cfg), + "Proxy": proxy.NewParser(cfg), + "RateLimit": ratelimit.NewParser(), + "Redirect": rewrite.NewParser(cfg), + "SecureUpstream": secureupstream.NewParser(), + "SessionAffinity": sessionaffinity.NewParser(), + "SSLPassthrough": sslpassthrough.NewParser(), + "ConfigurationSnippet": snippet.NewParser(), }, } } diff --git a/core/pkg/ingress/types.go b/core/pkg/ingress/types.go index 488945574..ef7fc4d83 100644 --- a/core/pkg/ingress/types.go +++ b/core/pkg/ingress/types.go @@ -277,6 +277,9 @@ type Location struct { // UsePortInRedirects indicates if redirects must specify the port // +optional UsePortInRedirects bool `json:"use-port-in-redirects"` + // ConfigurationSnippet contains additional configuration for the backend + // to be considered in the configuration of the location + ConfigurationSnippet string `json:"configuration-snippet"` } // SSLPassthroughBackend describes a SSL upstream server configured diff --git a/examples/README.md b/examples/README.md index dbcf4a8cc..236fb12d0 100644 --- a/examples/README.md +++ b/examples/README.md @@ -80,3 +80,4 @@ Dummy | A simple dummy controller that logs updates | * | Advanced Name | Description | Platform | Complexity Level -----| ----------- | ---------- | ---------------- custom-headers | set custom headers before send traffic to backends | nginx | Advanced +configuration-snippets | customize nginx location configuration using annotations | nginx | Advanced diff --git a/examples/customization/configuration-snippets/nginx/README.md b/examples/customization/configuration-snippets/nginx/README.md new file mode 100644 index 000000000..9adfa78a0 --- /dev/null +++ b/examples/customization/configuration-snippets/nginx/README.md @@ -0,0 +1,44 @@ +# Deploying the Nginx Ingress controller + +This example aims to demonstrate the deployment of an nginx ingress controller and +with the use of an annotation in the Ingress rule be able to customize the nginx +configuration. + +## Default Backend + +The default backend is a Service capable of handling all url paths and hosts the +nginx controller doesn't understand. This most basic implementation just returns +a 404 page: + +```console +$ kubectl apply -f default-backend.yaml +deployment "default-http-backend" created +service "default-http-backend" created + +$ kubectl -n kube-system get po +NAME READY STATUS RESTARTS AGE +default-http-backend-2657704409-qgwdd 1/1 Running 0 28s +``` + +```console +$ kubectl create -f nginx-load-balancer-conf.yaml +``` + +## Controller + +You can deploy the controller as follows: + +```console +$ kubectl apply -f nginx-ingress-controller.yaml +deployment "nginx-ingress-controller" created + +$ kubectl -n kube-system get po +NAME READY STATUS RESTARTS AGE +default-http-backend-2657704409-qgwdd 1/1 Running 0 2m +nginx-ingress-controller-873061567-4n3k2 1/1 Running 0 42s +``` + +## Test + +Check the contents of the annotation is present in the nginx.conf file using: +`kubectl exec nginx-ingress-controller-873061567-4n3k2 -n kube-system cat /etc/nginx/nginx.conf` diff --git a/examples/customization/configuration-snippets/nginx/default-backend.yaml b/examples/customization/configuration-snippets/nginx/default-backend.yaml new file mode 100644 index 000000000..3c40989a3 --- /dev/null +++ b/examples/customization/configuration-snippets/nginx/default-backend.yaml @@ -0,0 +1,51 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: default-http-backend + labels: + k8s-app: default-http-backend + namespace: kube-system +spec: + replicas: 1 + template: + metadata: + labels: + k8s-app: default-http-backend + spec: + terminationGracePeriodSeconds: 60 + containers: + - name: default-http-backend + # Any image is permissable as long as: + # 1. It serves a 404 page at / + # 2. It serves 200 on a /healthz endpoint + image: gcr.io/google_containers/defaultbackend:1.0 + livenessProbe: + httpGet: + path: /healthz + port: 8080 + scheme: HTTP + initialDelaySeconds: 30 + timeoutSeconds: 5 + ports: + - containerPort: 8080 + resources: + limits: + cpu: 10m + memory: 20Mi + requests: + cpu: 10m + memory: 20Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: default-http-backend + namespace: kube-system + labels: + k8s-app: default-http-backend +spec: + ports: + - port: 80 + targetPort: 8080 + selector: + k8s-app: default-http-backend diff --git a/examples/customization/configuration-snippets/nginx/ingress.yaml b/examples/customization/configuration-snippets/nginx/ingress.yaml new file mode 100644 index 000000000..e60d75f90 --- /dev/null +++ b/examples/customization/configuration-snippets/nginx/ingress.yaml @@ -0,0 +1,18 @@ +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: nginx-configuration-snippet + annotations: + kubernetes.io/ingress.class: "nginx" + ingress.kubernetes.io/configuration-snippet: | + more_set_headers "Request-Id: $request_id"; + +spec: + rules: + - host: custom.configuration.com + http: + paths: + - backend: + serviceName: http-svc + servicePort: 80 + path: / diff --git a/examples/customization/configuration-snippets/nginx/nginx-ingress-controller.yaml b/examples/customization/configuration-snippets/nginx/nginx-ingress-controller.yaml new file mode 100644 index 000000000..c4065804a --- /dev/null +++ b/examples/customization/configuration-snippets/nginx/nginx-ingress-controller.yaml @@ -0,0 +1,53 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: nginx-ingress-controller + labels: + k8s-app: nginx-ingress-controller + namespace: kube-system +spec: + replicas: 1 + template: + metadata: + labels: + k8s-app: nginx-ingress-controller + spec: + # hostNetwork makes it possible to use ipv6 and to preserve the source IP correctly regardless of docker configuration + # however, it is not a hard dependency of the nginx-ingress-controller itself and it may cause issues if port 10254 already is taken on the host + # that said, since hostPort is broken on CNI (https://github.com/kubernetes/kubernetes/issues/31307) we have to use hostNetwork where CNI is used + # like with kubeadm + # hostNetwork: true + terminationGracePeriodSeconds: 60 + containers: + - image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.2 + name: nginx-ingress-controller + readinessProbe: + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + livenessProbe: + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + timeoutSeconds: 1 + ports: + - containerPort: 80 + hostPort: 80 + - containerPort: 443 + hostPort: 443 + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + args: + - /nginx-ingress-controller + - --default-backend-service=$(POD_NAMESPACE)/default-http-backend + - --configmap=$(POD_NAMESPACE)/nginx-load-balancer-conf diff --git a/examples/customization/configuration-snippets/nginx/nginx-load-balancer-conf.yaml b/examples/customization/configuration-snippets/nginx/nginx-load-balancer-conf.yaml new file mode 100644 index 000000000..239918267 --- /dev/null +++ b/examples/customization/configuration-snippets/nginx/nginx-load-balancer-conf.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +data: + proxy-set-headers: "kube-system/custom-headers" +kind: ConfigMap +metadata: + name: nginx-load-balancer-conf + namespace: kube-system