How to set up express.js with SSL inside K8s deployment

7/29/2020

I'm trying to get https working on express.js for node inside K8s deployment, but I can't figure out how.

The express app should work like this: 1. When you access the /healthz endpoint, return 200 (both http & https requests) 2. Redirect any other http trafic to https

The problem I'm facing is that the app is then listening on two different ports (8080 and 8443), but the K8s Ingress points to only single port - I was thinking about maybe solving this problem with Nginx as a reverse proxy, but than, how would I "link" the two containers together?

This is my app.ts:

import express from "express"
import { createServer as httpServer } from "http"
import { createServer as httpsServer } from "https"
import { readFileSync } from "fs"

const app = express()

const options = {
  key: readFileSync(`${__dirname}/cert/[company].key`),
  cert: readFileSync(`${__dirname}/cert/[company].crt`),
}

app.use("/healthz", (req, res) => {
  res.status(200).send("healthy")
})
app.use("*", (req, res) => {
  if (!req.secure) {
    return res.redirect(["https://", req.get("Host"), req.baseUrl].join(""));
  }
})
app.get("/", (req, res) => {
  res.status(200).send("Hello from Express!")
})

httpServer(app).listen(8080)
httpsServer(options, app).listen(8443)

Here's the Dockerfile:

FROM node:14.4.0-alpine
...
EXPOSE 8080 8443
CMD [ "yarn", "start" ]

Here's the Ingress.yaml:

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
...
spec:
  tls:
    - secretName: [secret-name]
  rules:
    - host: [company]
      http:
        paths:
          - backend:
              serviceName: [service-name]
              servicePort: 8080

Could you please point me the right direction, how to approach/solve this problem?

-- Esskimo
express
https
kubernetes
node.js

1 Answer

7/30/2020

Answer

On Kubernetes, your Node.js server does not need to handle TLS. The canonical approach is to use Ingress for TLS termination. This is because your Node.js server can sit behind a ClusterIP Service and only be accessible from outside of the cluster through the Ingress Controller Service.

Assuming your TLS secret is valid, and your Ingress Controller is set up correctly (e.g. nginx), the YAML you have linked should work.

Make sure that the Service defined in the Ingress backend is of type ClusterIP, because it need only be accessed by the Ingress Controller Pod(s).

You would need to update your Node.js server to serve HTTP on a single port (e.g. 8080) for all of your endpoints.

If you wish /healthz to allow HTTP traffic, then you will need to create a second Ingress resource without TLS:

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
...
spec:
  rules:
  - http:
      paths:
      - path: /healthz
        pathType: Prefix
        backend:
          serviceName: [service]
          servicePort: 8080

More Info

Regarding your question about "linking" the nginx Ingress Controller with your Node.js app - the Ingress Controller would point to the ClusterIP Service specified in the Ingress resources' backend fields.

Remember to deploy your Ingress resources in the same Namespaces as the Services they point to for simplicity. Otherwise, you would have to make a Service of type ExternalName in the Ingress resource's Namespace which points to the Service in the other Namespace. See here.

Your Ingress Controller can be in any Namespace, assuming it is configured to watch all Namespaces for Ingress resources.

-- Serge
Source: StackOverflow