<p>I'm creating a web application that comprises a <em>React</em> <strong>frontend</strong> and a <em>node.js</em> (express) <strong>server</strong>. The frontend makes an internal api call to the express server and the express server then makes an external api call to gather some data. The frontend and the server are in different containers within the same Kubernetes pod.</p>

<p>The <strong>frontend service</strong> is an <code>nginx:1.14.0-alpine</code> image. The static files are built (<code>npm build</code>) in a CI pipeline and the <code>build</code> directory is copied to the image during <code>docker build</code>. The <code>package.json</code> contains a proxy key, <code>"proxy": "http://localhost:8080"</code>, that routes traffic from the app to <code>localhost:8080</code> - which is the port that the express server is listening on for an internal api call. I think the <code>proxy</code> key will have no bearing once the files are packaged into static files and served up onto an <code>nginx</code> image?</p>

<p>When running locally, i.e. running <code>npm start</code> instead of <code>npm build</code>, this all works. The express server picks up the api requests sent out by the frontend on port <code>8080</code>.</p>

<p>The <strong>express server</strong> is a simple service that adds authentication to the api call that the frontend makes, that is all. But the authentication relies on secrets as environment variables, making them incompatible with React. The server is started by running <code>node server.js</code>; locally the server service successfully listens (<code>app.listen(8080)</code>)to the api calls from the React frontend, adds some authentication to the request, then makes the the request to the external api and passes the response back to the frontend once it is received.</p>

<p>In production, in a Kubernetes pod, things aren't so simple. The traffic from the <strong>React frontend</strong> proxying through the <strong>node server</strong> needs to be handled by kubernetes now, and I haven't been able to figure it out.</p>

<p>It may be important to note that there are no circumstances in which the <strong>frontend</strong> will make any external api calls directly, they will all go through the <strong>server</strong>.</p>

<p><code>React frontend Dockerfile</code></p>

<pre><code>FROM nginx:1.14.0-alpine

# Copy static files
COPY client/build/ /usr/share/nginx/html/

# The rest has been redacted for brevity but is just copying of favicons etc.
</code></pre>

<p><code>Express Node Server</code></p>

<pre><code>FROM node:10.16.2-alpine

# Create app directory
WORKDIR /app

# Install app dependencies
COPY server/package*.json .

RUN npm install

EXPOSE 8080

CMD [ "node", "server.js" ]
</code></pre>

<p><code>Kubernetes Manifest</code> - Redacted for brevity</p>

<pre><code>apiVersion: apps/v1beta1
kind: Deployment

containers:
      - name: frontend
        image: frontend-image:1.0.0
        imagePullPolicy: IfNotPresent
        ports:
        - name: http
          containerPort: 80
        volumeMounts:
        - mountPath: /etc/nginx/conf.d/default.conf
          name: config-dir
          subPath: my-config.conf

      - name: server
              image: server-image:1.0.0
              imagePullPolicy: IfNotPresent
              volumes:
              - name: config-tmpl
                configMap:
                  name: app-config
                  defaultMode: 0744
              - name: my-config-directory
                emptyDir: {}
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: my-namespace
data:
  my-conf.conf: |-
    server {

        listen 80;

        server_name _;

        location api/ {
          proxy_pass  http://127.0.0.1:8080/;
        }

.....

</code></pre>

Grant
<p>In Kubernetes, thes pod share the same network interface with all container inside it, so for the frontend container <strong>localhost:8080</strong> is the backend, and for the backend container <strong>localhost:80</strong> is the frontend.
As for any container application, you should ensure that they are listening in other interfaces than 127.0.0.1 if you want traffic from outside.</p>

<p>Migrating an aplication from one server - where every application talks from <strong>127.0.0.1</strong> - to a pod was intended to be simple as in a dedicated machine.</p>

<p>Your <strong>nginx.conf</strong> looks a little bit strange, should be <code>location /api/ {</code>.</p>

<p>Here is functional example:</p>

<p><strong>nginx.conf</strong></p>

<pre><code>server {
    server_name   localhost;
    listen        0.0.0.0:80;

    error_page    500 502 503 504  /50x.html;

    location      / {
        root      html;
    }
    location /api/ {
      proxy_pass  http://127.0.0.1:8080/;
    }

}
</code></pre>

<p>Create this <strong>ConfigMap</strong>:</p>

<pre><code>kubectl create -f nginx.conf
</code></pre>

<p><strong>app.js</strong></p>

<pre><code>const express = require('express')
const app = express()
const port = 8080

app.get('/', (req, res) =&gt; res.send('Hello from Express!'))

app.listen(port, () =&gt; console.log(`Example app listening on port ${port}!`))
</code></pre>

<p><strong>Dockerfile</strong></p>

<pre><code>FROM alpine

RUN apk add nodejs npm &amp;&amp; mkdir -p /app

COPY . /app

WORKDIR /app

RUN npm install express --save

EXPOSE 8080

CMD node app.js
</code></pre>

<p>You can build this image or use the one I've made <strong>hectorvido/express</strong>.</p>

<p>Then, create the <strong>pod</strong> YAML definition:</p>

<p><strong>pod.yml</strong></p>

<pre><code>apiVersion: v1
kind: Pod
metadata:
  name: front-back
  labels:
    app: front-back
spec:
  containers:
  - name: front
    image: nginx:alpine
    volumeMounts:
    - name: nginx-conf
      mountPath: /etc/nginx/conf.d/
    ports:
    - containerPort: 80
  - name: back
    image: hectorvido/express
    ports:
    - containerPort: 8080      
  volumes:
  - name: nginx-conf
    configMap:
      name: nginx
</code></pre>

<p>Put on the cluster:</p>

<pre><code>kubectl create -f pod.yml
</code></pre>

<p>Get the IP:</p>

<pre><code>kubectl get pods -o wide
</code></pre>

<p>I tested with Minikube, so if the pod IP was <em>172.17.0.7</em> I have to do:</p>

<pre class="lang-sh prettyprint-override"><code>minikube ssh
curl -L 172.17.0.7/api
</code></pre>

<p>If you had an ingress in the front, it should still working. I enabled an nginx ingress controller on minikube, so we need to create a service and a ingress:</p>

<p><strong>service</strong></p>

<pre class="lang-sh prettyprint-override"><code>kubectl expose pod front-back --port 80
</code></pre>

<p><strong>ingress.yml</strong></p>

<pre><code>apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: front-back
spec:
  rules:
  - host: fb.192-168-39-163.nip.io # minikube ip
    http:
      paths:
      - path: /
        backend:
          serviceName: front-back
          servicePort: 80
</code></pre>

<p>The test still works:</p>

<pre class="lang-sh prettyprint-override"><code>curl -vL http://fb.192-168-39-163.nip.io/api/
</code></pre>
