diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9d76b1d600..5dd01830b9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,8 +8,8 @@ We'd love to accept your patches! Before we can take them, we have to jump a cou Please fill out either the individual or corporate Contributor License Agreement (CLA). - * If you are an individual writing original source code and you're sure you own the intellectual property, then you'll need to sign an [individual CLA](http://code.google.com/legal/individual-cla-v1.0.html). - * If you work for a company that wants to allow you to contribute your work, then you'll need to sign a [corporate CLA](http://code.google.com/legal/corporate-cla-v1.0.html). + * If you are an individual writing original source code and you're sure you own the intellectual property, then you'll need to sign an [individual CLA](https://identity.linuxfoundation.org/projects/cncf). + * If you work for a company that wants to allow you to contribute your work, then you'll need to sign a [corporate CLA](https://identity.linuxfoundation.org/node/285/organization-signup). Follow either of the two links above to access the appropriate CLA and instructions for how to sign and return it. Once we receive it, we'll be able to accept your pull requests. diff --git a/cmd/nginx/flags.go b/cmd/nginx/flags.go index b2e491cf55..d32028a3b6 100644 --- a/cmd/nginx/flags.go +++ b/cmd/nginx/flags.go @@ -149,6 +149,8 @@ Feature backed by OpenResty Lua libraries. Requires that OCSP stapling is not en enableMetrics = flags.Bool("enable-metrics", true, `Enables the collection of NGINX metrics`) + metricsPerHost = flags.Bool("metrics-per-host", true, + `Export metrics per-host`) httpPort = flags.Int("http-port", 80, `Port to use for servicing HTTP traffic.`) httpsPort = flags.Int("https-port", 443, `Port to use for servicing HTTPS traffic.`) @@ -227,6 +229,7 @@ Feature backed by OpenResty Lua libraries. Requires that OCSP stapling is not en ElectionID: *electionID, EnableProfiling: *profiling, EnableMetrics: *enableMetrics, + MetricsPerHost: *metricsPerHost, EnableSSLPassthrough: *enableSSLPassthrough, EnableSSLChainCompletion: *enableSSLChainCompletion, ResyncPeriod: *resyncPeriod, diff --git a/cmd/nginx/main.go b/cmd/nginx/main.go index 295b2f42e1..5f5b4b9cb2 100644 --- a/cmd/nginx/main.go +++ b/cmd/nginx/main.go @@ -131,7 +131,7 @@ func main() { mc := metric.NewDummyCollector() if conf.EnableMetrics { - mc, err = metric.NewCollector(conf.ListenPorts.Status, reg) + mc, err = metric.NewCollector(conf.ListenPorts.Status, conf.MetricsPerHost, reg) if err != nil { klog.Fatalf("Error creating prometheus collector: %v", err) } diff --git a/docs/user-guide/nginx-configuration/annotations.md b/docs/user-guide/nginx-configuration/annotations.md index f9d3cd728b..7854f28552 100644 --- a/docs/user-guide/nginx-configuration/annotations.md +++ b/docs/user-guide/nginx-configuration/annotations.md @@ -46,6 +46,7 @@ You can add these Kubernetes annotations to specific Ingress objects to customiz |[nginx.ingress.kubernetes.io/cors-max-age](#enable-cors)|number| |[nginx.ingress.kubernetes.io/force-ssl-redirect](#server-side-https-enforcement-through-redirect)|"true" or "false"| |[nginx.ingress.kubernetes.io/from-to-www-redirect](#redirect-from-to-www)|"true" or "false"| +|[nginx.ingress.kubernetes.io/http2-push-preload](#http2-push-preload)|"true" or "false"| |[nginx.ingress.kubernetes.io/limit-connections](#rate-limiting)|number| |[nginx.ingress.kubernetes.io/limit-rps](#rate-limiting)|number| |[nginx.ingress.kubernetes.io/permanent-redirect](#permanent-redirect)|string| @@ -298,6 +299,14 @@ CORS can be controlled with the following annotations: !!! note For more information please see [https://enable-cors.org](https://enable-cors.org/server_nginx.html) +### HTTP2 Push Preload. + +Enables automatic conversion of preload links specified in the “Link” response header fields into push requests. + +!!! example + + * `nginx.ingress.kubernetes.io/http2-push-preload: "true"` + ### Server Alias To add Server Aliases to an Ingress rule add the annotation `nginx.ingress.kubernetes.io/server-alias: ""`. diff --git a/images/nginx/Makefile b/images/nginx/Makefile index 37d7f81995..6f050bde32 100644 --- a/images/nginx/Makefile +++ b/images/nginx/Makefile @@ -13,7 +13,7 @@ # limitations under the License. # 0.0.0 shouldn't clobber any released builds -TAG ?= 0.72 +TAG ?= 0.73 REGISTRY ?= quay.io/kubernetes-ingress-controller ARCH ?= $(shell go env GOARCH) DOCKER ?= docker diff --git a/images/nginx/rootfs/build.sh b/images/nginx/rootfs/build.sh index 4627062a0b..b73842f057 100755 --- a/images/nginx/rootfs/build.sh +++ b/images/nginx/rootfs/build.sh @@ -19,7 +19,7 @@ set -o errexit set -o nounset set -o pipefail -export NGINX_VERSION=1.15.7 +export NGINX_VERSION=1.15.8 export NDK_VERSION=0.3.1rc1 export SETMISC_VERSION=0.32 export MORE_HEADERS_VERSION=0.33 @@ -36,7 +36,7 @@ export LUA_UPSTREAM_VERSION=0.07 export NGINX_INFLUXDB_VERSION=0e2cb6cbf850a29c81e44be9e33d9a15d45c50e8 export GEOIP2_VERSION=3.2 export NGINX_AJP_VERSION=bf6cd93f2098b59260de8d494f0f4b1f11a84627 -export LUAJIT_VERSION=c58fe79b870f1934479bf14fe8035fc3d9fdfde2 +export LUAJIT_VERSION=520d53a87dd44c637dddb6de313204211c2b212b export BUILD_PATH=/tmp/build @@ -128,7 +128,7 @@ mkdir --verbose -p "$BUILD_PATH" cd "$BUILD_PATH" # download, verify and extract the source files -get_src 8f22ea2f6c0e0a221b6ddc02b6428a3ff708e2ad55f9361102b1c9f4142bdf93 \ +get_src a8bdafbca87eb99813ae4fcac1ad0875bf725ce19eb265d28268c309b2b40787 \ "https://nginx.org/download/nginx-$NGINX_VERSION.tar.gz" get_src 49f50d4cd62b166bc1aaf712febec5e028d9f187cedbc27a610dfd01bdde2d36 \ @@ -197,7 +197,7 @@ get_src a77bf0d7cf6a9ba017d0dc973b1a58f13e48242dd3849c5e99c07d250667c44c \ get_src d81b33129c6fb5203b571fa4d8394823bf473d8872c0357a1d0f14420b1483bd \ "https://github.com/cloudflare/lua-resty-cookie/archive/v0.1.0.tar.gz" -get_src 21dab7625a028d4560d0215c4bc3b82f6153344f933abb99dc9fd5f0d19519ab \ +get_src d04df883adb86c96a8e0fe6c404851b9c776840dbb524419c06ae3fac42c4e64 \ "https://github.com/openresty/luajit2/archive/$LUAJIT_VERSION.tar.gz" get_src c673fcee37c1c4794f921b6710b09e8a0e1e58117aa788f798507d033f737192 \ diff --git a/internal/ingress/annotations/annotations.go b/internal/ingress/annotations/annotations.go index 5365fbe9a0..0ada5d6a6a 100644 --- a/internal/ingress/annotations/annotations.go +++ b/internal/ingress/annotations/annotations.go @@ -37,6 +37,7 @@ import ( "k8s.io/ingress-nginx/internal/ingress/annotations/cors" "k8s.io/ingress-nginx/internal/ingress/annotations/customhttperrors" "k8s.io/ingress-nginx/internal/ingress/annotations/defaultbackend" + "k8s.io/ingress-nginx/internal/ingress/annotations/http2pushpreload" "k8s.io/ingress-nginx/internal/ingress/annotations/influxdb" "k8s.io/ingress-nginx/internal/ingress/annotations/ipwhitelist" "k8s.io/ingress-nginx/internal/ingress/annotations/loadbalancing" @@ -80,6 +81,7 @@ type Ingress struct { DefaultBackend *apiv1.Service Denied error ExternalAuth authreq.Config + HTTP2PushPreload bool Proxy proxy.Config RateLimit ratelimit.Config Redirect redirect.Config @@ -122,6 +124,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor { "CustomHTTPErrors": customhttperrors.NewParser(cfg), "DefaultBackend": defaultbackend.NewParser(cfg), "ExternalAuth": authreq.NewParser(cfg), + "HTTP2PushPreload": http2pushpreload.NewParser(cfg), "Proxy": proxy.NewParser(cfg), "RateLimit": ratelimit.NewParser(cfg), "Redirect": redirect.NewParser(cfg), diff --git a/internal/ingress/annotations/http2pushpreload/main.go b/internal/ingress/annotations/http2pushpreload/main.go new file mode 100644 index 0000000000..a86b3653f0 --- /dev/null +++ b/internal/ingress/annotations/http2pushpreload/main.go @@ -0,0 +1,39 @@ +/* +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 http2pushpreload + +import ( + extensions "k8s.io/api/extensions/v1beta1" + + "k8s.io/ingress-nginx/internal/ingress/annotations/parser" + "k8s.io/ingress-nginx/internal/ingress/resolver" +) + +type http2PushPreload struct { + r resolver.Resolver +} + +// NewParser creates a new http2PushPreload annotation parser +func NewParser(r resolver.Resolver) parser.IngressAnnotation { + return http2PushPreload{r} +} + +// Parse parses the annotations contained in the ingress rule +// used to add http2 push preload to the server +func (h2pp http2PushPreload) Parse(ing *extensions.Ingress) (interface{}, error) { + return parser.GetBoolAnnotation("http2-push-preload", ing) +} diff --git a/internal/ingress/annotations/http2pushpreload/main_test.go b/internal/ingress/annotations/http2pushpreload/main_test.go new file mode 100644 index 0000000000..c5ce586b2d --- /dev/null +++ b/internal/ingress/annotations/http2pushpreload/main_test.go @@ -0,0 +1,62 @@ +/* +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 http2pushpreload + +import ( + "testing" + + api "k8s.io/api/core/v1" + extensions "k8s.io/api/extensions/v1beta1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/ingress-nginx/internal/ingress/annotations/parser" + "k8s.io/ingress-nginx/internal/ingress/resolver" +) + +func TestParse(t *testing.T) { + annotation := parser.GetAnnotationWithPrefix("http2-push-preload") + ap := NewParser(&resolver.Mock{}) + if ap == nil { + t.Fatalf("expected a parser.IngressAnnotation but returned nil") + } + + testCases := []struct { + annotations map[string]string + expected bool + }{ + {map[string]string{annotation: "true"}, true}, + {map[string]string{annotation: "1"}, true}, + {map[string]string{annotation: ""}, false}, + {map[string]string{}, false}, + {nil, false}, + } + + ing := &extensions.Ingress{ + ObjectMeta: meta_v1.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/internal/ingress/controller/controller.go b/internal/ingress/controller/controller.go index cd970ee140..ff8a3b7d71 100644 --- a/internal/ingress/controller/controller.go +++ b/internal/ingress/controller/controller.go @@ -87,7 +87,8 @@ type Configuration struct { EnableProfiling bool - EnableMetrics bool + EnableMetrics bool + MetricsPerHost bool EnableSSLChainCompletion bool @@ -472,6 +473,7 @@ func (n *NGINXController) getBackendServers(ingresses []*ingress.Ingress) ([]*in loc.ConfigurationSnippet = anns.ConfigurationSnippet loc.CorsConfig = anns.CorsConfig loc.ExternalAuth = anns.ExternalAuth + loc.HTTP2PushPreload = anns.HTTP2PushPreload loc.Proxy = anns.Proxy loc.RateLimit = anns.RateLimit loc.Redirect = anns.Redirect diff --git a/internal/ingress/metric/collectors/socket.go b/internal/ingress/metric/collectors/socket.go index 7421602f1d..406a8b7679 100644 --- a/internal/ingress/metric/collectors/socket.go +++ b/internal/ingress/metric/collectors/socket.go @@ -76,12 +76,12 @@ type SocketCollector struct { metricMapping map[string]interface{} hosts sets.String + + metricsPerHost bool } var ( requestTags = []string{ - "host", - "status", "method", @@ -95,7 +95,7 @@ var ( // NewSocketCollector creates a new SocketCollector instance using // the ingress watch namespace and class used by the controller -func NewSocketCollector(pod, namespace, class string) (*SocketCollector, error) { +func NewSocketCollector(pod, namespace, class string, metricsPerHost bool) (*SocketCollector, error) { socket := "/tmp/prometheus-nginx.socket" listener, err := net.Listen("unix", socket) if err != nil { @@ -113,9 +113,16 @@ func NewSocketCollector(pod, namespace, class string) (*SocketCollector, error) "controller_pod": pod, } + requestTags := requestTags + if metricsPerHost { + requestTags = append(requestTags, "host") + } + sc := &SocketCollector{ listener: listener, + metricsPerHost: metricsPerHost, + responseTime: prometheus.NewHistogramVec( prometheus.HistogramOpts{ Name: "response_duration_seconds", @@ -219,8 +226,8 @@ func (sc *SocketCollector) handleMessage(msg []byte) { continue } + // Note these must match the order in requestTags at the top requestLabels := prometheus.Labels{ - "host": stats.Host, "status": stats.Status, "method": stats.Method, "path": stats.Path, @@ -228,6 +235,9 @@ func (sc *SocketCollector) handleMessage(msg []byte) { "ingress": stats.Ingress, "service": stats.Service, } + if sc.metricsPerHost { + requestLabels["host"] = stats.Host + } collectorLabels := prometheus.Labels{ "namespace": stats.Namespace, diff --git a/internal/ingress/metric/collectors/socket_test.go b/internal/ingress/metric/collectors/socket_test.go index f347f09dc6..b9b3c68482 100644 --- a/internal/ingress/metric/collectors/socket_test.go +++ b/internal/ingress/metric/collectors/socket_test.go @@ -288,7 +288,7 @@ func TestCollector(t *testing.T) { t.Run(c.name, func(t *testing.T) { registry := prometheus.NewPedanticRegistry() - sc, err := NewSocketCollector("pod", "default", "ingress") + sc, err := NewSocketCollector("pod", "default", "ingress", true) if err != nil { t.Errorf("%v: unexpected error creating new SocketCollector: %v", c.name, err) } diff --git a/internal/ingress/metric/main.go b/internal/ingress/metric/main.go index df763b679c..c25343aeaa 100644 --- a/internal/ingress/metric/main.go +++ b/internal/ingress/metric/main.go @@ -59,7 +59,7 @@ type collector struct { } // NewCollector creates a new metric collector the for ingress controller -func NewCollector(statusPort int, registry *prometheus.Registry) (Collector, error) { +func NewCollector(statusPort int, metricsPerHost bool, registry *prometheus.Registry) (Collector, error) { podNamespace := os.Getenv("POD_NAMESPACE") if podNamespace == "" { podNamespace = "default" @@ -77,7 +77,7 @@ func NewCollector(statusPort int, registry *prometheus.Registry) (Collector, err return nil, err } - s, err := collectors.NewSocketCollector(podName, podNamespace, class.IngressClass) + s, err := collectors.NewSocketCollector(podName, podNamespace, class.IngressClass, metricsPerHost) if err != nil { return nil, err } diff --git a/internal/ingress/types.go b/internal/ingress/types.go index 05a5895cfb..c70a5526fd 100644 --- a/internal/ingress/types.go +++ b/internal/ingress/types.go @@ -238,6 +238,10 @@ type Location struct { // authentication using an external provider // +optional ExternalAuth authreq.Config `json:"externalAuth,omitempty"` + // HTTP2PushPreload allows to configure the HTTP2 Push Preload from backend + // original location. + // +optional + HTTP2PushPreload bool `json:"http2PushPreload,omitempty"` // RateLimit describes a limit in the number of connections per IP // address or connections per second. // The Redirect annotation precedes RateLimit diff --git a/internal/ingress/types_equals.go b/internal/ingress/types_equals.go index 6e65a5c72c..eea824aad8 100644 --- a/internal/ingress/types_equals.go +++ b/internal/ingress/types_equals.go @@ -374,6 +374,9 @@ func (l1 *Location) Equal(l2 *Location) bool { if !(&l1.ExternalAuth).Equal(&l2.ExternalAuth) { return false } + if l1.HTTP2PushPreload != l2.HTTP2PushPreload { + return false + } if !(&l1.RateLimit).Equal(&l2.RateLimit) { return false } diff --git a/rootfs/etc/nginx/template/nginx.tmpl b/rootfs/etc/nginx/template/nginx.tmpl index 877186bcef..c8473afa73 100644 --- a/rootfs/etc/nginx/template/nginx.tmpl +++ b/rootfs/etc/nginx/template/nginx.tmpl @@ -1122,6 +1122,10 @@ stream { rewrite_log on; {{ end }} + {{ if $location.HTTP2PushPreload }} + http2_push_preload on; + {{ end }} + port_in_redirect {{ if $location.UsePortInRedirects }}on{{ else }}off{{ end }}; set $proxy_upstream_name "{{ buildUpstreamName $location }}"; diff --git a/test/e2e/annotations/http2pushpreload.go b/test/e2e/annotations/http2pushpreload.go new file mode 100644 index 0000000000..66211e77fb --- /dev/null +++ b/test/e2e/annotations/http2pushpreload.go @@ -0,0 +1,49 @@ +/* +Copyright 2018 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 annotations + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "k8s.io/ingress-nginx/test/e2e/framework" +) + +var _ = framework.IngressNginxDescribe("Annotations - HTTP2 Push Preload", func() { + f := framework.NewDefaultFramework("http2pushpreload") + + BeforeEach(func() { + f.NewEchoDeploymentWithReplicas(2) + }) + + AfterEach(func() { + }) + + It("enable the http2-push-preload directive", func() { + host := "http2pp.foo.com" + annotations := map[string]string{ + "nginx.ingress.kubernetes.io/http2-push-preload": "true", + } + + ing := framework.NewSingleIngress(host, "/", host, f.IngressController.Namespace, "http-svc", 80, &annotations) + f.EnsureIngress(ing) + + f.WaitForNginxServer(host, + func(server string) bool { + return Expect(server).Should(ContainSubstring("http2_push_preload on;")) + }) + }) +})