Continuing from part I of our series, we will next step through building a simple, front end web application, deploying it to the Kubernetes cluster, then exposing it to the outside world via a Kubernetes Ingress.
The source related to this post is contained in the building-frontend-app
branch of the repo.
Building a Web Front End
To create the initial web application, we use React, starting with create-react-app using the Typescript template. We will also use react-router to provide routes for navigation within the application.
We can use the https://
protocol, even running the web application in “dev” mode, by adding some additional configuration to the packages/browser-frontend/.env
file that brings in the certificates that we created previously.
BROWSER=none
HTTPS=true
SSL_CRT_FILE=../../certs/testing-local.crt
SSL_KEY_FILE=../../certs/testing-local.key
We can run the web application locally (using npm start
) and then navigate to the address:
This gives us the additional benefit where changes to the code are automatically refreshed in the browser.
Building the Front End Container Image
The next step is to build a container image (using Docker) that will be deployed to the Kubernetes cluster.
We can build the web application assets by running the command:
npm run build
This builds the React application into a set of static files (including index.html
, JavaScript, CSS, and other files). These files will be served to a client (a browser). A popular mechanism to do this is using the open-source NGINX package.
The packages/browser-frontend/Dockerfile
describes the container to build using a pre-existing NGINX image. It also describes copying the web assets from the build and a nginx.conf
configuration file to the image.
FROM nginx:latest
COPY ./deploy/frontend-nginx.conf /etc/nginx/nginx.conf
COPY ./build/ /var/www/site/
In the web application, two routes are included, one to the root (/
) and one to an “About” page (/about
). When serving content for a SPA web site, the server needs to recognize that the “About” page really comes from the default /index.html
file (not from an /about.html
or /about/index.html
file). So the NGINX configuration, specified in packages/browser-frontend/deploy/frontend-nginx.conf
, includes definitions to map routes back to the correct content.
http {
include mime.types;
sendfile on;
server {
listen 80 default_server;
root /var/www/site;
index index.html index.htm;
location ~* .(?:manifest|appcache|html?|xml|json)$ {
expires -1;
}
location ~* .(?:css|js)$ {
try_files $uri =404;
expires 1y;
access_log off;
add_header Cache-Control "public";
}
location ~ ^.+..+$ {
try_files $uri =404;
}
location / {
try_files $uri $uri/ /index.html;
}
}
}
Use this script to build the static web site and the container image:
packages/browser-frontend/deploy/build-frontend-image.ps1
To incorporate this image into Kubernetes, the following configuration is used to define a deployment and a service.
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-deployment
spec:
selector:
matchLabels:
app: frontend
replicas: 1
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: nginx
image: frontend-webserver:v1.3
imagePullPolicy: Never
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: frontend-service
spec:
selector:
app: frontend
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
The deployment describes the state of the pod including the container image to be used. The service then exposes the deployment as a network service within the cluster.
The following scripts can be used to initialize these components:
packages/nginx-ingress/create-namespace.ps1
– creates a separate namespace in which to install the example application. This makes deleting the entire application from Kubernetes easier.packages/browser-frontend/deploy/initialize-frontend.ps1
– applies the configuration for the front end application using the image that was built previously.
We can now verify that the front end application is running with a few commands:
> kubectl get deployment -n azure-dapr-identity-example
NAME READY UP-TO-DATE AVAILABLE AGE
frontend-deployment 1/1 1 1 62s
> kubectl get service -n azure-dapr-identity-example
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
frontend-service ClusterIP 10.103.234.151 <none> 80/TCP 81s
> kubectl get pod -n azure-dapr-identity-example
NAME READY STATUS RESTARTS AGE
frontend-deployment-68dbc6f5dd-2qj82 1/1 Running 0 4m5s
> kubectl port-forward service/frontend-service 32001:http -n azure-dapr-identity-example
Forwarding from 127.0.0.1:32001 -> 80
Forwarding from [::1]:32001 -> 80
The last command temporarily forwards requests from your local machine on port 32001 to the http
(80) port of the frontend-service
(press Ctrl
+C
to stop it). Navigate in a browser to http://localhost:32001/ and you should see the web application.
The application doesn’t do anything interesting, yet, but you can navigate to the two routes. Refreshing the page returns the correct content as well.
The NGINX Ingress Controller
An Ingress allows the outside world to interact with services within the Kubernetes cluster. The IngressClass defines the configuration for a particular type of ingress including the type of Controller that implements the class.
In our example, we are using the NGINX Ingress Controller for Kubernetes.
Note that this ingress controller is different from the NGINX Ingress Controller maintained by the Kubernetes team. The differences are documented here.
The simplest way to use this ingress controller is using a Helm chart. We customize the chart with some annotations:
controller:
name: example-app-ingress
healthStatus: true
defaultTLS:
cert: $CERT_VALUE
key: $KEY_VALUE
service:
httpPort:
port: 31002
targetPort: 80
httpsPort:
port: 31001
targetPort: 443
And use the following script
packages/nginx-ingress/initialize-nginx-ingress.ps1
to update $CERT_VALUE
and $KEY_VALUE
properties with a base-64 encoded version of the certificate and private keys we created previously and then apply the chart to the Kubernetes cluster.
Finally, we use the Ingress definition to map requests coming into the Ingress Controller to the front end application:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: frontend-ingress
spec:
ingressClassName: nginx
tls:
- hosts:
- testing.local
secretName: example-app-ingress-nginx-ingress-default-server-tls
rules:
- host: testing.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
The frontend-service
exposes the static web site within the Kubernetes cluster. This ingress definition routes requests that match the testing.local
domain and requested paths to the web site service. In our case, we are just sending all incoming requests to frontend-service
. We’ll add a Web API later that will be another destination for routed traffic.
This definition will also configure TLS-termination for us. An incoming request from outside the cluster will be encrypted, but within the cluster, the requests can flow to the service unencrypted (over the http
protocol). We will discuss later how this changes once we incorporate DAPR services.
This ingress definition can be applied using the command:
kubectl apply -f frontend-ingress.yaml -n azure-dapr-identity-example
Once this is all configured, you can navigate in the browser to the application at https://testing.local:31001/.