Skip to content
Open
107 changes: 107 additions & 0 deletions examples/custom-resources/foreign-namespace-upstreams/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Upstreams in foreign namespaces

In this example we use the [VirtualServer and
VirtualServerRoute](https://docs.nginx.com/nginx-ingress-controller/configuration/virtualserver-and-virtualserverroute-resources/)
resources to configure load balancing for the modified cafe application from the [Basic
Configuration](../basic-configuration/) example. We have put the load balancing configuration as well as the deployments
and services into multiple namespaces. Instead of one namespace, we now use three: `tea`, `coffee`, and `cafe`.

- In the tea namespace, we create the tea deployment and service.
- In the coffee namespace, we create the coffee deployment and service.
- In the cafe namespace, we create the cafe secret with the TLS certificate and key and the load-balancing configuration
for the cafe application. That configuration references the coffee and tea configurations.

## Prerequisites

1. Follow the [installation](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/)
instructions to deploy the Ingress Controller with custom resources enabled.
1. Save the public IP address of the Ingress Controller into a shell variable:

```console
IC_IP=XXX.YYY.ZZZ.III
```

1. Save the HTTPS port of the Ingress Controller into a shell variable:

```console
IC_HTTPS_PORT=<port number>
```

## Step 1 - Create Namespaces

Create the required tea, coffee, and cafe namespaces:

```console
kubectl create -f namespaces.yaml
```

## Step 2 - Deploy the Cafe Application

1. Create the tea deployment and service in the tea namespace:

```console
kubectl create -f tea.yaml
```

1. Create the coffee deployment and service in the coffee namespace:

```console
kubectl create -f coffee.yaml
```

## Step 3 - Configure Load Balancing and TLS Termination

1. Create the secret with the TLS certificate and key in the cafe namespace:

```console
kubectl create -f cafe-secret.yaml
```

1. Create the VirtualServer resource for the cafe app in the cafe namespace:

```console
kubectl create -f cafe-virtual-server.yaml
```

## Step 4 - Test the Configuration

1. Check that the configuration has been successfully applied by inspecting the events of the VirtualServer:

```console
kubectl describe virtualserver cafe -n cafe
```

```text
. . .
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal AddedOrUpdated 1m nginx-ingress-controller Configuration for cafe/cafe was added or updated
```

1. Access the application using curl. We'll use curl's `--insecure` option to turn off certificate verification of our
self-signed certificate and `--resolve` option to set the IP address and HTTPS port of the Ingress Controller to the
domain name of the cafe application:

To get coffee:

```console
curl --resolve cafe.example.com:$IC_HTTPS_PORT:$IC_IP https://cafe.example.com:$IC_HTTPS_PORT/coffee --insecure
```

```text
Server address: 10.16.1.193:80
Server name: coffee-7dbb5795f6-mltpf
...
```

If your prefer tea:

```console
curl --resolve cafe.example.com:$IC_HTTPS_PORT:$IC_IP https://cafe.example.com:$IC_HTTPS_PORT/tea --insecure
```

```text
Server address: 10.16.0.157:80
Server name: tea-7d57856c44-674b8
...
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
name: cafe
namespace: cafe
spec:
host: cafe.example.com
tls:
secret: cafe-secret
routes:
- path: /coffee
action:
pass: coffee
- path: /tea
action:
pass: tea
upstreams:
- name: coffee
service: coffee/coffee-svc
port: 80
- name: tea
service: tea/tea-svc
port: 80
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: coffee
namespace: coffee
spec:
replicas: 3
selector:
matchLabels:
app: coffee
template:
metadata:
labels:
app: coffee
spec:
containers:
- name: coffee
image: nginxdemos/nginx-hello:plain-text
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: coffee-svc
namespace: coffee
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: coffee

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: coffee
namespace: coffee2
spec:
replicas: 3
selector:
matchLabels:
app: coffee
template:
metadata:
labels:
app: coffee
spec:
containers:
- name: coffee
image: nginxdemos/nginx-hello:plain-text
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: coffee-svc
namespace: coffee2
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: v1
kind: Namespace
metadata:
name: cafe
---
apiVersion: v1
kind: Namespace
metadata:
name: tea
---
apiVersion: v1
kind: Namespace
metadata:
name: coffee
34 changes: 34 additions & 0 deletions examples/custom-resources/foreign-namespace-upstreams/tea.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: tea
namespace: tea
spec:
replicas: 1
selector:
matchLabels:
app: tea
template:
metadata:
labels:
app: tea
spec:
containers:
- name: tea
image: nginxdemos/nginx-hello:plain-text
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: tea-svc
namespace: tea
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: tea
47 changes: 33 additions & 14 deletions internal/configs/virtualserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,17 @@ func GenerateEndpointsKey(
return fmt.Sprintf("%s/%s:%d", serviceNamespace, serviceName, port)
}

// ParseServiceReference returns the namespace and name from a service reference.
func ParseServiceReference(serviceRef, defaultNamespace string) (namespace, serviceName string) {
if strings.Contains(serviceRef, "/") {
parts := strings.Split(serviceRef, "/")
if len(parts) == 2 {
return parts[0], parts[1]
}
}
return defaultNamespace, serviceRef
}

type upstreamNamer struct {
prefix string
namespace string
Expand Down Expand Up @@ -353,7 +364,8 @@ func (vsc *virtualServerConfigurator) generateEndpointsForUpstream(
upstream conf_v1.Upstream,
virtualServerEx *VirtualServerEx,
) []string {
endpointsKey := GenerateEndpointsKey(namespace, upstream.Service, upstream.Subselector, upstream.Port)
serviceNamespace, serviceName := ParseServiceReference(upstream.Service, namespace)
endpointsKey := GenerateEndpointsKey(serviceNamespace, serviceName, upstream.Subselector, upstream.Port)
externalNameSvcKey := GenerateExternalNameSvcKey(namespace, upstream.Service)
endpoints := virtualServerEx.Endpoints[endpointsKey]
if !vsc.isPlus && len(endpoints) == 0 {
Expand Down Expand Up @@ -659,7 +671,8 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(
upstreamName := virtualServerUpstreamNamer.GetNameForUpstreamFromAction(r.Action)
upstream := crUpstreams[upstreamName]

proxySSLName := generateProxySSLName(upstream.Service, vsEx.VirtualServer.Namespace)
serviceNamespace, serviceName := ParseServiceReference(upstream.Service, vsEx.VirtualServer.Namespace)
proxySSLName := generateProxySSLName(serviceName, serviceNamespace)

loc, returnLoc := generateLocation(r.Path, upstreamName, upstream, r.Action, vsc.cfgParams, errorPages, false,
proxySSLName, r.Path, vsLocSnippets, vsc.enableSnippets, len(returnLocations), isVSR, "", "", vsc.warnings)
Expand Down Expand Up @@ -812,7 +825,8 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(
} else {
upstreamName := upstreamNamer.GetNameForUpstreamFromAction(r.Action)
upstream := crUpstreams[upstreamName]
proxySSLName := generateProxySSLName(upstream.Service, vsr.Namespace)
serviceNamespace, serviceName := ParseServiceReference(upstream.Service, vsr.Namespace)
proxySSLName := generateProxySSLName(serviceName, serviceNamespace)

loc, returnLoc := generateLocation(r.Path, upstreamName, upstream, r.Action, vsc.cfgParams, errorPages, false,
proxySSLName, r.Path, locSnippets, vsc.enableSnippets, len(returnLocations), isVSR, vsr.Name, vsr.Namespace, vsc.warnings)
Expand Down Expand Up @@ -2535,8 +2549,10 @@ func generateLocation(path string, upstreamName string, upstream conf_v1.Upstrea

checkGrpcErrorPageCodes(errorPages, isGRPC(upstream.Type), upstream.Name, vscWarnings)

_, serviceName := ParseServiceReference(upstream.Service, "")

return generateLocationForProxying(path, upstreamName, upstream, cfgParams, errorPages.pages, internal,
errorPages.index, proxySSLName, action.Proxy, originalPath, locationSnippets, isVSR, vsrName, vsrNamespace), nil
errorPages.index, proxySSLName, action.Proxy, originalPath, locationSnippets, isVSR, vsrName, vsrNamespace, serviceName), nil
}

func generateProxySetHeaders(proxy *conf_v1.ActionProxy) []version2.Header {
Expand Down Expand Up @@ -2621,7 +2637,7 @@ func generateProxyAddHeaders(proxy *conf_v1.ActionProxy) []version2.AddHeader {

func generateLocationForProxying(path string, upstreamName string, upstream conf_v1.Upstream,
cfgParams *ConfigParams, errorPages []conf_v1.ErrorPage, internal bool, errPageIndex int,
proxySSLName string, proxy *conf_v1.ActionProxy, originalPath string, locationSnippets []string, isVSR bool, vsrName string, vsrNamespace string,
proxySSLName string, proxy *conf_v1.ActionProxy, originalPath string, locationSnippets []string, isVSR bool, vsrName string, vsrNamespace string, serviceName string,
) version2.Location {
return version2.Location{
Path: generatePath(path),
Expand Down Expand Up @@ -2652,7 +2668,7 @@ func generateLocationForProxying(path string, upstreamName string, upstream conf
HasKeepalive: upstreamHasKeepalive(upstream, cfgParams),
ErrorPages: generateErrorPages(errPageIndex, errorPages),
ProxySSLName: proxySSLName,
ServiceName: upstream.Service,
ServiceName: serviceName,
IsVSR: isVSR,
VSRName: vsrName,
VSRNamespace: vsrNamespace,
Expand Down Expand Up @@ -2823,7 +2839,8 @@ func generateSplits(
path := fmt.Sprintf("/%vsplits_%d_split_%d", internalLocationPrefix, scIndex, i)
upstreamName := upstreamNamer.GetNameForUpstreamFromAction(s.Action)
upstream := crUpstreams[upstreamName]
proxySSLName := generateProxySSLName(upstream.Service, upstreamNamer.namespace)
serviceNamespace, serviceName := ParseServiceReference(upstream.Service, upstreamNamer.namespace)
proxySSLName := generateProxySSLName(serviceName, serviceNamespace)
newRetLocIndex := retLocIndex + len(returnLocations)
loc, returnLoc := generateLocation(path, upstreamName, upstream, s.Action, cfgParams, errorPages, true,
proxySSLName, originalPath, locSnippets, enableSnippets, newRetLocIndex, isVSR, vsrName, vsrNamespace, vscWarnings)
Expand Down Expand Up @@ -3056,7 +3073,8 @@ func generateMatchesConfig(route conf_v1.Route, upstreamNamer *upstreamNamer, cr
path := fmt.Sprintf("/%vmatches_%d_match_%d", internalLocationPrefix, index, i)
upstreamName := upstreamNamer.GetNameForUpstreamFromAction(m.Action)
upstream := crUpstreams[upstreamName]
proxySSLName := generateProxySSLName(upstream.Service, upstreamNamer.namespace)
serviceNamespace, serviceName := ParseServiceReference(upstream.Service, upstreamNamer.namespace)
proxySSLName := generateProxySSLName(serviceName, serviceNamespace)
newRetLocIndex := retLocIndex + len(returnLocations)
loc, returnLoc := generateLocation(path, upstreamName, upstream, m.Action, cfgParams, errorPages, true,
proxySSLName, route.Path, locSnippets, enableSnippets, newRetLocIndex, isVSR, vsrName, vsrNamespace, vscWarnings)
Expand Down Expand Up @@ -3099,7 +3117,8 @@ func generateMatchesConfig(route conf_v1.Route, upstreamNamer *upstreamNamer, cr
path := fmt.Sprintf("/%vmatches_%d_default", internalLocationPrefix, index)
upstreamName := upstreamNamer.GetNameForUpstreamFromAction(route.Action)
upstream := crUpstreams[upstreamName]
proxySSLName := generateProxySSLName(upstream.Service, upstreamNamer.namespace)
serviceNamespace, serviceName := ParseServiceReference(upstream.Service, upstreamNamer.namespace)
proxySSLName := generateProxySSLName(serviceName, serviceNamespace)
newRetLocIndex := retLocIndex + len(returnLocations)
loc, returnLoc := generateLocation(path, upstreamName, upstream, route.Action, cfgParams, errorPages, true,
proxySSLName, route.Path, locSnippets, enableSnippets, newRetLocIndex, isVSR, vsrName, vsrNamespace, vscWarnings)
Expand Down Expand Up @@ -3288,9 +3307,9 @@ func createUpstreamsForPlus(
}

upstreamName := upstreamNamer.GetNameForUpstream(u.Name)
upstreamNamespace := virtualServerEx.VirtualServer.Namespace
upstreamNamespace, upstreamServiceName := ParseServiceReference(u.Service, virtualServerEx.VirtualServer.Namespace)

endpointsKey := GenerateEndpointsKey(upstreamNamespace, u.Service, u.Subselector, u.Port)
endpointsKey := GenerateEndpointsKey(upstreamNamespace, upstreamServiceName, u.Subselector, u.Port)
endpoints := virtualServerEx.Endpoints[endpointsKey]

backupEndpoints := []string{}
Expand All @@ -3312,15 +3331,15 @@ func createUpstreamsForPlus(
}

upstreamName := upstreamNamer.GetNameForUpstream(u.Name)
upstreamNamespace := vsr.Namespace
serviceNamespace, serviceName := ParseServiceReference(u.Service, vsr.Namespace)

endpointsKey := GenerateEndpointsKey(upstreamNamespace, u.Service, u.Subselector, u.Port)
endpointsKey := GenerateEndpointsKey(serviceNamespace, serviceName, u.Subselector, u.Port)
endpoints := virtualServerEx.Endpoints[endpointsKey]

// BackupService
backupEndpoints := []string{}
if u.Backup != "" {
backupEndpointsKey := GenerateEndpointsKey(upstreamNamespace, u.Backup, u.Subselector, *u.BackupPort)
backupEndpointsKey := GenerateEndpointsKey(vsr.Namespace, u.Backup, u.Subselector, *u.BackupPort)
backupEndpoints = virtualServerEx.Endpoints[backupEndpointsKey]
}
ups := vsc.generateUpstream(vsr, upstreamName, u, isExternalNameSvc, endpoints, backupEndpoints)
Expand Down
Loading
Loading