<p>I'm attempting to run several Docker apps in a GKE instance, with a load balancer setup exposing them. Each app comprises a simple node.js app with nginx to serve the site; a simple nginx config exposes the apps with a location block responding to <code>/</code>. This works well locally when developing since I can run each pod on a separate port, and access them simply at 127.0.0.1:8080 or similar.</p>
<p>The problem I'm encountering is that when using the GCP load balancer, whilst I can easily route traffic to the Kubernetes services such that <a href="https://example.com/" rel="nofollow noreferrer">https://example.com/</a> maps to my <code>foo</code> service/pod and <a href="https://example.com/bar" rel="nofollow noreferrer">https://example.com/bar</a> goes to my <code>bar</code> service, the <code>bar</code> pod responds with a 404 since the path, <code>/bar</code> doesn't match the path specified in the location block.</p>
<p>The number of these pods will scale a lot so I do not wish to manually know ahead of time what path each pod will be under, nor do I wish to embody this in my git repo.</p>
<p>Is there a way I can dynamically define the path the location block matches, for example via an environment variable, such that I could define it as part of the Helm charts I use to deploy these services? Alternatively is it possible to match all paths? Is that a viable solution, or just asking for problems?</p>
<p>Thanks for your help.</p>

aodj
<p>Simply use <strong>ingress</strong>. It will allow you to map different paths to different backend <code>Services</code>. It is very well explained both in <a href="https://cloud.google.com/kubernetes-engine/docs/concepts/ingress" rel="nofollow noreferrer">GCP docs</a> as well as in the <a href="https://kubernetes.io/docs/concepts/services-networking/ingress/" rel="nofollow noreferrer">official kubernetes documentation</a>.</p>
<p>Typical ingress object definition may look as follows:</p>
<pre><code>apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: my-ingress
spec:
  backend:
    serviceName: my-products
    servicePort: 60001
  rules:
  - http:
      paths:
      - path: /
        backend:
          serviceName: my-products
          servicePort: 60000
      - path: /discounted
        backend:
          serviceName: my-discounted-products
          servicePort: 80
      - path: /special
        backend:
          serviceName: special-offers
          servicePort: 80
      - path: /news
        backend:
          serviceName: news
          servicePort: 80
           
</code></pre>
<p>When you apply your ingress definition on <strong>GKE</strong>, <strong>load balancer</strong> is created automatically. Note that all <code>Services</code> may use same, standard http port and you don't have to use any custom ports.</p>
<p>You may want to specify <a href="https://cloud.google.com/kubernetes-engine/docs/concepts/ingress#default_backend" rel="nofollow noreferrer">a default backend</a>, present in the above example (<code>backend</code> section right under <code>spec</code>), but it's optional. It will ensure that:</p>
<blockquote>
<p>Any requests that don't match the paths in the rules field are sent to
the Service and port specified in the backend field. For example, in
the following Ingress, any requests that don't match / or /discounted
are sent to a Service named my-products on port 60001.</p>
</blockquote>
<p>The only problem that you may encounter when using default <strong>ingress controller</strong> available on <strong>GKE</strong> is that for the time being <strong>it doesn't support rewrites</strong>.</p>
<p>If your nginx pods expose app content only on <code>&quot;/&quot;</code> path, no support for rewrites shouldn't be a limitation at all and as far as I understand, this applies in your case:</p>
<blockquote>
<p>Each app comprises a simple node.js app with nginx to serve the site;
a simple nginx config exposes the apps with a location block
responding to /</p>
</blockquote>
<p>However if you decide at some point that you need mentioned rewrites because e.g. one of your apps isn't exposed under <code>/</code> but rather <code>/bar</code> within the <code>Pod</code> you may decide to deploy <a href="https://kubernetes.github.io/ingress-nginx/deploy/#gce-gke" rel="nofollow noreferrer">nginx ingress controller</a> which can be also done pretty easily on <strong>GKE</strong>.</p>
<p>So you will only need it in the following scenario: <em>user accesses the ingress IP followed by <code>/foo</code></em> -&gt; <em>request is not only redirected to the specific backend <code>Service</code> that exposes your <strong>nginx</strong> <code>Pod</code>, but also the original path (<code>/foo</code>) needs to be rewritten to the new path (<code>/bar</code>) under which the application is exposed within the <code>Pod</code></em></p>
<h1>UPDATE:</h1>
<blockquote>
<p>Thank you for your reply. The above ingress configuration is very
similar to what I've already configured forwarding /foo and /bar to
different pods. The issue is that the path gets forwarded, and (after
doing some more research on the issue) I believe I need to rewrite the
URL that's sent to the pod, since the location / { ... } block in my
nginx config won't match against the received path of /foo or /bar. –
aodj Aug 14 at 9:17</p>
</blockquote>
<p>Well, you're right. The original access path e.g. <code>/foo</code> indeed gets forwarded to the target <code>Pod</code>. So choosing <code>/foo</code> path apart from leading you to the respective <code>backend</code> defined in the <strong>ingress resource</strong> implicates that the target <strong>nginx server</strong> running in a <code>Pod</code> must serve its content also under <code>/foo</code> path.</p>
<p>I verified it with <strong>GKE ingress</strong> and can confirm by checking <code>Pod</code> logs that an http request sent to the <strong>nginx</strong> <code>Pod</code> thorough the <code>/foo</code> path, indeed comes to the <code>Pod</code> as request for <code>/usr/share/nginx/html/foo</code> while it serves its content under <code>/</code>, not /foo from <code>/usr/share/nginx/html</code>. So requesting for something that don't exist on the target server leads inevitably to <code>404 Error</code>.</p>
<p>As I mentioned before, default ingress controller available on <strong>GKE</strong> doesn't support rewrites so if you want to use it for some reason, reconfiguring your target <strong>nginx servers</strong> seems the only solution to make it work.</p>
<p>Fortunatelly we have another option which is <strong>nginx ingress controller</strong>. It supports rewrites so it can easily solve our problem. We can deploy it on our <strong>GKE cluster</strong> by running two following commands:</p>
<pre><code>kubectl create clusterrolebinding cluster-admin-binding \
  --clusterrole cluster-admin \
  --user $(gcloud config get-value account)

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.34.1/deploy/static/provider/cloud/deploy.yaml
</code></pre>
<p>Yes, it's really that simple! You can take a closer look at the installation process in <a href="https://kubernetes.github.io/ingress-nginx/deploy/#gce-gke" rel="nofollow noreferrer">official docs</a>.</p>
<p>Then we can apply the following <code>ingress</code> resource definition:</p>
<pre><code>apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: &quot;nginx&quot;
    nginx.ingress.kubernetes.io/rewrite-target: /$2
  name: rewrite
  namespace: default
spec:
  rules:
  - http:
      paths:
      - backend:
          serviceName: nginx-deployment-1
          servicePort: 80
        path: /foo(/|$)(.*)
      - backend:
          serviceName: nginx-deployment-2
          servicePort: 80
        path: /bar(/|$)(.*)
</code></pre>
<p>Note that we used <code>kubernetes.io/ingress.class: &quot;nginx&quot;</code> annotation to select our newly deployed <strong>nginx-ingress controller</strong> to handle this <strong>ingress resource</strong> rather than the default <strong>GKE-ingress controller</strong>.</p>
<p>Rewrites that were used will make sure that the original access path gets rewritten before reaching the target <strong>nginx <code>Pod</code></strong>. So it's perfectly fine that both sets of <code>Pods</code> exposed by <code>nginx-deployment-1</code> and <code>nginx-deployment-2</code> <code>Services</code> serve their contents under <code>&quot;/&quot;</code>.</p>
<p>If you want to quickly check how it works on your own, you can use the following <code>Deployments</code>:</p>
<p><strong>nginx-deployment-1.yaml</strong></p>
<pre><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment-1
  labels:
    app: nginx-1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx-1
  template:
    metadata:
      labels:
        app: nginx-1
    spec:
      initContainers:
      - name: init-myservice
        image: nginx:1.14.2
        command: ['sh', '-c', &quot;echo DEPLOYMENT-1 &gt; /usr/share/nginx/html/index.html&quot;]
        volumeMounts:
        - mountPath: /usr/share/nginx/html
          name: cache-volume
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
        volumeMounts:
        - mountPath: /usr/share/nginx/html
          name: cache-volume
      volumes:
      - name: cache-volume
        emptyDir: {}
</code></pre>
<p><strong>nginx-deployment-2.yaml</strong></p>
<pre><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment-2
  labels:
    app: nginx-2
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx-2
  template:
    metadata:
      labels:
        app: nginx-2
    spec:
      initContainers:
      - name: init-myservice
        image: nginx:1.14.2
        command: ['sh', '-c', &quot;echo DEPLOYMENT-2 &gt; /usr/share/nginx/html/index.html&quot;]
        volumeMounts:
        - mountPath: /usr/share/nginx/html
          name: cache-volume
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
        volumeMounts:
        - mountPath: /usr/share/nginx/html
          name: cache-volume
      volumes:
      - name: cache-volume
        emptyDir: {}
</code></pre>
<p>And expose them via <code>Services</code> by running:</p>
<pre><code>kubectl expose deployment nginx-deployment-1 --type NodePort --target-port 80 --port 80
kubectl expose deployment nginx-deployment-2 --type NodePort --target-port 80 --port 80
</code></pre>
<p>You may even omit <code>--type NodePort</code> as <strong>nginx-ingress</strong> controller accepts also <code>ClusterIP</code> <code>Services</code>.</p>
