Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add annotations for proxy_pass customization #2343

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/user-guide/nginx-configuration/annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ You can add these Kubernetes annotations to specific Ingress objects to customiz
|[nginx.ingress.kubernetes.io/influxdb-port](#influxdb)|string|
|[nginx.ingress.kubernetes.io/influxdb-host](#influxdb)|string|
|[nginx.ingress.kubernetes.io/influxdb-server-name](#influxdb)|string|
|[nginx.ingress.kubernetes.io/proxy-pass-address](#custom-proxy-pass)|string|
|[nginx.ingress.kubernetes.io/proxy-pass-port](#custom-proxy-pass)|string|
|[nginx.ingress.kubernetes.io/proxy-to-local-node](#custom-proxy-pass)|"true" or "false"|

### Rewrite

Expand Down Expand Up @@ -580,3 +583,19 @@ To use the module in the Kubernetes Nginx ingress controller, you have two optio
- Deploy Telegraf as a sidecar proxy to the Ingress controller configured to listen UDP with the [socket listener input](https://github.com/influxdata/telegraf/tree/release-1.6/plugins/inputs/socket_listener) and to write using
anyone of the [outputs plugins](https://github.com/influxdata/telegraf/tree/release-1.6/plugins/outputs)

### Custom Proxy Pass

We can override the `proxy_pass` value in the nginx config per location using the `proxy-pass-address`, `proxy-pass-port`,
and `proxy-to-local-node` annotations.

`proxy-pass-address` and `proxy-pass-port` directly set the `proxy_pass` value, i.e:
```yaml
nginx.ingress.kubernetes.io/proxy-pass-address: l5d.linkerd.svc.cluster.local
nginx.ingress.kubernetes.io/proxy-pass-port: "4140"
```

will produce the string `proxy_pass http://l5d.linkerd.svc.cluster.local:4140`

If `proxy-to-local-node` is true, the `proxy_pass` address will be set to the value parsed by `os.Getenv("NODE_NAME")`,
which can be set to the name of the node using the downward API; this is useful when combining the nginx-ingress-controller
with a service mesh running as a daemonset such as linkerd.
3 changes: 3 additions & 0 deletions internal/ingress/annotations/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import (
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/annotations/portinredirect"
"k8s.io/ingress-nginx/internal/ingress/annotations/proxy"
"k8s.io/ingress-nginx/internal/ingress/annotations/proxypass"
"k8s.io/ingress-nginx/internal/ingress/annotations/ratelimit"
"k8s.io/ingress-nginx/internal/ingress/annotations/redirect"
"k8s.io/ingress-nginx/internal/ingress/annotations/rewrite"
Expand Down Expand Up @@ -78,6 +79,7 @@ type Ingress struct {
ExternalAuth authreq.Config
HealthCheck healthcheck.Config
Proxy proxy.Config
ProxyPass proxypass.Config
RateLimit ratelimit.Config
Redirect redirect.Config
Rewrite rewrite.Config
Expand Down Expand Up @@ -120,6 +122,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
"ExternalAuth": authreq.NewParser(cfg),
"HealthCheck": healthcheck.NewParser(cfg),
"Proxy": proxy.NewParser(cfg),
"ProxyPass": proxypass.NewParser(cfg),
"RateLimit": ratelimit.NewParser(cfg),
"Redirect": redirect.NewParser(cfg),
"Rewrite": rewrite.NewParser(cfg),
Expand Down
79 changes: 79 additions & 0 deletions internal/ingress/annotations/proxypass/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
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 proxypass

import (
extensions "k8s.io/api/extensions/v1beta1"

"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)

// Config describes the per location proxyPass config
type Config struct {
// Address of the upstream proxy.
Address string `json:"address"`
// Port of the upstream proxy.
Port string `json:"port"`
// Replaces the address of the upstream proxy with the local node
// address, as set by a "NODE_NAME" environment variable.
ProxyToLocalNode bool `json:"proxyToLocalNode"`
}

// Equal tests for equality between two proxyPass config types
func (r1 *Config) Equal(r2 *Config) bool {
if r1 == r2 {
return true
}
if r1 == nil || r2 == nil {
return false
}
if r1.Address != r2.Address {
return false
}
if r1.Port != r2.Port {
return false
}
if r1.ProxyToLocalNode != r2.ProxyToLocalNode {
return false
}

return true
}

type proxypass struct {
r resolver.Resolver
}

// NewParser creates a new proxyPass annotation parser
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return proxypass{r}
}

// ParseAnnotations parses the annotations contained in the ingress
// rule used to define the proxyPass value
func (a proxypass) Parse(ing *extensions.Ingress) (interface{}, error) {
ppa, _ := parser.GetStringAnnotation("proxy-pass-address", ing)
ppp, _ := parser.GetStringAnnotation("proxy-pass-port", ing)
ptln, _ := parser.GetBoolAnnotation("proxy-to-local-node", ing)

return &Config{
Address: ppa,
Port: ppp,
ProxyToLocalNode: ptln,
}, nil
}
85 changes: 85 additions & 0 deletions internal/ingress/annotations/proxypass/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
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 proxypass

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) {
proxyPassAddressAnnotation := parser.GetAnnotationWithPrefix("proxy-pass-address")
proxyPassPortAnnotation := parser.GetAnnotationWithPrefix("proxy-pass-port")
proxyToLocalNodeAnnotation := parser.GetAnnotationWithPrefix("proxy-to-local-node")

ap := NewParser(&resolver.Mock{})
if ap == nil {
t.Fatalf("expected a parser.IngressAnnotation but returned nil")
}

testCases := []struct {
annotations map[string]string
expected *Config
}{
{nil, &Config{}},
{map[string]string{}, &Config{}},

// Everything passed in
{map[string]string{proxyPassAddressAnnotation: "localhost", proxyPassPortAnnotation: "4140", proxyToLocalNodeAnnotation: "true"}, &Config{Address: "localhost", Port: "4140", ProxyToLocalNode: true}},
// Only address omitted
{map[string]string{proxyPassPortAnnotation: "4140", proxyToLocalNodeAnnotation: "true"}, &Config{Address: "", Port: "4140", ProxyToLocalNode: true}},
// Address and port omitted
{map[string]string{proxyToLocalNodeAnnotation: "true"}, &Config{Address: "", Port: "", ProxyToLocalNode: true}},

// Everything passed in, proxyToLocalNode explicitly false
{map[string]string{proxyPassAddressAnnotation: "localhost", proxyPassPortAnnotation: "4140", proxyToLocalNodeAnnotation: "false"}, &Config{Address: "localhost", Port: "4140", ProxyToLocalNode: false}},
// Address omitted, proxyToLocalNode explicitly false
{map[string]string{proxyPassPortAnnotation: "4140", proxyToLocalNodeAnnotation: "false"}, &Config{Address: "", Port: "4140", ProxyToLocalNode: false}},
// Address and port omitted, proxyToLocalNode explicitly false
{map[string]string{proxyToLocalNodeAnnotation: "false"}, &Config{Address: "", Port: "", ProxyToLocalNode: false}},

// Only proxyToLocalNode omitted
{map[string]string{proxyPassAddressAnnotation: "localhost", proxyPassPortAnnotation: "4140"}, &Config{Address: "localhost", Port: "4140", ProxyToLocalNode: false}},

// Only address passed in
{map[string]string{proxyPassAddressAnnotation: "localhost"}, &Config{Address: "localhost", Port: "", ProxyToLocalNode: false}},
// Only port passed in
{map[string]string{proxyPassPortAnnotation: "4140"}, &Config{Address: "", Port: "4140", ProxyToLocalNode: 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)
config := result.(*Config)
if !config.Equal(testCase.expected) {
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
}
}
}
2 changes: 2 additions & 0 deletions internal/ingress/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]
loc.CorsConfig = anns.CorsConfig
loc.ExternalAuth = anns.ExternalAuth
loc.Proxy = anns.Proxy
loc.ProxyPass = anns.ProxyPass
loc.RateLimit = anns.RateLimit
loc.Redirect = anns.Redirect
loc.Rewrite = anns.Rewrite
Expand Down Expand Up @@ -475,6 +476,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]
CorsConfig: anns.CorsConfig,
ExternalAuth: anns.ExternalAuth,
Proxy: anns.Proxy,
ProxyPass: anns.ProxyPass,
RateLimit: anns.RateLimit,
Redirect: anns.Redirect,
Rewrite: anns.Rewrite,
Expand Down
11 changes: 11 additions & 0 deletions internal/ingress/controller/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,17 @@ func buildProxyPass(host string, b interface{}, loc interface{}, dynamicConfigur
}
}

if location.ProxyPass.Address != "" || location.ProxyPass.Port != "" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you ever use a port alone?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured that the address could be omitted if ProxyToLocalNode was set along with the port.

adr := location.ProxyPass.Address
if location.ProxyPass.ProxyToLocalNode {
adr = os.Getenv("NODE_NAME")
}

if adr != "" {
upstreamName = fmt.Sprintf("%s:%s", adr, location.ProxyPass.Port)
}
}

// defProxyPass returns the default proxy_pass, just the name of the upstream
defProxyPass := fmt.Sprintf("%v %s://%s;", proxyPass, proto, upstreamName)

Expand Down
4 changes: 4 additions & 0 deletions internal/ingress/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"k8s.io/ingress-nginx/internal/ingress/annotations/log"
"k8s.io/ingress-nginx/internal/ingress/annotations/luarestywaf"
"k8s.io/ingress-nginx/internal/ingress/annotations/proxy"
"k8s.io/ingress-nginx/internal/ingress/annotations/proxypass"
"k8s.io/ingress-nginx/internal/ingress/annotations/ratelimit"
"k8s.io/ingress-nginx/internal/ingress/annotations/redirect"
"k8s.io/ingress-nginx/internal/ingress/annotations/rewrite"
Expand Down Expand Up @@ -206,6 +207,9 @@ type Location struct {
// vhost of the incoming request.
// +optional
UpstreamVhost string `json:"upstream-vhost"`
// ProxyPass allows customization of the proxy_pass directive.
// +optional
ProxyPass proxypass.Config `json:"proxyPass,omitempty"`
// BasicDigestAuth returns authentication configuration for
// an Ingress rule.
// +optional
Expand Down
3 changes: 3 additions & 0 deletions internal/ingress/types_equals.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,9 @@ func (l1 *Location) Equal(l2 *Location) bool {
if !(&l1.Proxy).Equal(&l2.Proxy) {
return false
}
if !(&l1.ProxyPass).Equal(&l2.ProxyPass) {
return false
}
if l1.UsePortInRedirects != l2.UsePortInRedirects {
return false
}
Expand Down
Loading