Deploying a React app using Min.io

In previous posts I had written about how to set up a Kubernetes cluster on self-hosted hardware with the purpose of hosting applications on a Local Area Network (LAN) to use as an intranet of sorts. Today I'll cover how to deploy client-side applications onto Kubernetes cluster which was provisioned.

Servers to Cloud Platforms

First, let's talk about how the deployments would be handled on a cloud platform. During the .com days, a server would have to be provisioned to host web applications (i.e. Apache/Nginx). The static website would then have to be copied into a static HTML folder (i.e. /var/www/html) in order to be served to clients via the web server.
The process of web hosting was improved with the rise of containerization. Instead of having to set up and manage a web server directly on your server hardware, now you can pull a pre-configured image and mount your static web content onto a container which would drastically improve website deployment times.
The rise of cloud platforms furthers the improvement by abstracting away the deployment environment from the developer entirely so that more focus can be placed on the website assets themselves instead of provisioning and configuring servers.
Cloud platforms implement hosted storage using a standard called Object-Based-Storage. Object-based-storage utilizes web endpoints to control and manage assets uploaded to a server. Site content can be managed and served directly using the HTTP protocol. AWS S3 is a perfect example of how object-based-storage works.

Minio

Deploying the Services

Before we can deploy a static website to our Kubernetes cluster, we will first have to provision a Minio server.
The Minio documentation utilizes a Minio Operator and a kubectl krew plugin to provision servers. Utilizing these tools will be covered in a later production release document. For the sake of this tutorial, deploying Minio will be handled with the Minio helm chart

Utilizing Terraform, the Minio server can be deployed to Helm with the following snippet:

provider "helm" {}

resource "helm_release" "rel_minio" {
  name       = "files"
  chart      = "minio"
  repository = "https://charts.bitnami.com/bitnami"
}

The helm chart can be deployed with the following commands:

terraform plan
terraform apply

Once the helm deployment has been completed, the service will be available from the Kubernetes cluster. In order to interact with the services, the credentials will have to be read in from the Kubernetes secrets which are generated by the helm chart. The following commands retrieve the secrets from Kubernetes and store them in environment variables:

export minio_access_key=$(kubectl get secret files-minio --namespace econovizer -o=jsonpath='{.data.access-key}' | base64 --decode)
export minio_secret_key=$(kubectl get secret files-minio --namespace econovizer -o=jsonpath='{.data.secret-key}' | base64 --decode)

We then have to port-forward the Kubernetes service in order to access it.

kubectl port-forward services/files-minio 9000:9000

Configuring the Minio Client

Minio provides a CLI called mc which can be utilized to interact with the Minio server. We have to call the mc tool with the $minio_secret_key and the $minio_access_key environment variables we created earlier.

mc alias set local http://127.0.0.1:9000 $minio_access_key $minio_secret_key

With the client configured we can now create a bucket for hosting our static site.

mc mb local/static

Before assets can be served from the bucket, the bucket needs to be configured for public asses.

mc policy set download local/static

Creating the React Application

With the hosting environment established, we can now create our static website. The easiest way to set up the static website is using Create React App.

npx create-react-app my-app

This command will create a React application with the name my-app in the current folder. We need to change into the my-app folder -- cd my-app. Build the project with the command npm run build. The build command creates a new folder build.

With the build folder created, we can deploy the build to our bucket with the mc command

mc cp -r build/* local/static/
mc ls local/static # list the files which were just uploaded to the local/static bucket

Ideally from this point, you would be able to access the static site from http://localhost:9000/static, however Minio has a limitation which prevents it from serving up files unless they were referenced directly.
http://localhost:9000/static will return an XML document containing a ListBucketResult instead of index.html. http://localhost:9000/static/index.html will return the desired web page. Since the URL would end with index.html, React would be looking for a web root and fail to load.

Fortunately, the issue could be fixed with a proxy application: s3www.

To simplify the deployment of s3www, I created a Terraform template which deploys the following resources to Kubernetes:

  • s3www pod deployment
  • s3www Kubernetes service
  • Ingress which proxies the s3www service

This file can be run with the following commands:

terraform init
terraform plan -var "acces_key=$minio_access_key" -var "secret_key=$minio_secret_key" -var 'namespace=my-namespace' -out deployment.plan
terraform apply deployment.plan

Once Terraform is complete, the React application will be available from your Kubernetes cluster via Ingress. (i.e. http://host.docker.internal/)

References

22