Software versions are given below.
In DigitalOcean's managed Kubernetes, I have a running nginx-ingress with two containers routed properly with my DNS and a jetstack cert-manager. One container is running a test app and the other my API. They work great. I am trying to connect my Angular client in a third container, but the Ingress routing is not working. It returns a 502 Bad Gateway. After trying many things, I now think the problem is related to a specific nginx-ingress configuration that is needed for SPAs (Single Page Applications).
NanoDano on DevDungeon remarks, "Create a config in /etc/nginx/conf.d/. ... The most important part for Angular in particular is to include the try_files line which will ensure that even if someone visits a URL directly, the server will rewrite it properly so the Angular app behaves properly." This "try_files" configuration is also mentioned by Andre Dublin on his GitHub page, and by Niklas Heidloff on his website.
In examples I've found on how to do this, the explanation is given from the perspective that you are doing a multistage build to combine your application, ingress, and the ingress configuration into one docker container via Dockerfile, such as this example by Lukas Marx on Malcoded. Or that you manually edit the configurations after ingress is started, which is suggested in this unresolved Stackflow.
In my situation, I already have Nginx-Ingress and I only need to dynamically add a configuration to properly route the Angular SPA. Using kubectl port-forward, I have confirmed the Angular app is served both from its pod and from its cluster IP service. It is working. When I attempt to connect to the app via Ingress, I get a 502 Bad Gateway error, which I've discussed on this StackOverflow.
A configuration can be added to an existing Nginx-Ingress using a ConfigMap as described on this StackOverflow. My Nginx-Ingress was created and configured following this example from this DigitalOcean tutorial, see Step 2, which installs with the following:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.34.1/deploy/static/provider/do/deploy.yaml
I attempted to add the "try_files" configuration. First, I created this ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
data:
nginx.conf: |
server {
listen 80;
server_name pvgd.markwilx.tech;
index index.html index.htm;
root /home/node/app;
location / {
try_files $uri $uri/ /index.html =404;
}
include /etc/nginx/extra-conf.d/*.conf;
}
Second, I extracted Ingress Deployment from the deploy.yaml file in the kubectl command above and modified it (six lines are added toward the bottom, and marked by comment):
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
helm.sh/chart: ingress-nginx-3.27.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.45.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
revisionHistoryLimit: 10
minReadySeconds: 0
template:
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
spec:
dnsPolicy: ClusterFirst
containers:
- name: controller
image: k8s.gcr.io/ingress-nginx/controller:v0.45.0@sha256:c4390c53f348c3bd4e60a5dd6a11c35799ae78c49388090140b9d72ccede1755
imagePullPolicy: IfNotPresent
lifecycle:
preStop:
exec:
command:
- /wait-shutdown
args:
- /nginx-ingress-controller
- --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller
- --election-id=ingress-controller-leader
- --ingress-class=nginx
- --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
- --validating-webhook=:8443
- --validating-webhook-certificate=/usr/local/certificates/cert
- --validating-webhook-key=/usr/local/certificates/key
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
runAsUser: 101
allowPrivilegeEscalation: true
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: LD_PRELOAD
value: /usr/local/lib/libmimalloc.so
livenessProbe:
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 5
readinessProbe:
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 3
ports:
- name: http
containerPort: 80
protocol: TCP
- name: https
containerPort: 443
protocol: TCP
- name: webhook
containerPort: 8443
protocol: TCP
volumeMounts:
- name: webhook-cert
mountPath: /usr/local/certificates/
readOnly: true
- name: nginx-config # ADDED THIS CONFIGURATION
mountPath: /etc/nginx/nginx.conf # ADDED THIS CONFIGURATION
subPath: nginx.conf # ADDED THIS CONFIGURATION
resources:
requests:
cpu: 100m
memory: 90Mi
nodeSelector:
kubernetes.io/os: linux
serviceAccountName: ingress-nginx
terminationGracePeriodSeconds: 300
volumes:
- name: webhook-cert
secret:
secretName: ingress-nginx-admission
- name: nginx-config # ADDED THIS CONFIGURATION
configMap: # ADDED THIS CONFIGURATION
name: nginx-config # ADDED THIS CONFIGURATION
Third, I reapplied this code executing it with:
kubectl apply -f ingress-deployment-mod.yaml
This does not appear to be working. Indeed, it seems to have broken everything else. If you have any suggestions as to what I might be doing wrong to create a route between the Internet and my Angular app via Nginx-Ingress, I'd appreciate your insights.
Thanks, Mark
Versions
- node.js 10.19.0
- npm 7.7.6
- yarn 1.22.10
- ng 11.2.2
- docker 19.03.8
- kubectl 1.20.1
- doctl 1.57.0
- kubernetes 1.20.2-do.0
- helm 3.5.3
- nginx controller 0.45.0
- jetstack 1.3.0