Angular — How To Read Environment Info at runtime for CI/CD
If You are using NGINX as webserver and Kubernetes for the deployment
Angular provides configuration options at build time which means you need to define different environment files for each environment and Angular takes appropriate configuration while building the project by providing
--configuration flag to the
ng build. You can check out this article about reading environment info during build time.
But, the twelve-factor methodology and today's DevOps strategies suggest that we need to build once and run everywhere which means you will have only one chance to provide configuration file. Angular configuration options are not enough. You need to provide configuration or environment info at runtime. In this post, we will see how we can achieve that and read configuration settings or environment info at runtime.
- Example Project
- The problem we are facing
- How To Debug
Here is an example project for the demonstration. You can clone and run it on your machine.
// clone the project
git clone https://github.com/bbachi/angular-envread-runtime.git// for local development
This is a simple Angular project which loads the configuration file app.config.json from the /assets folder.
We are using APP_INITIALIZER to load this app.config.json file before bootstrapping and use those settings. Here are the app.module.ts and app.service.ts files.
Once you load the settings and you can read these settings in app.component.ts like below.
Based on the configuration, you can see the header color, heading, and table. For example, If it is a development environment header color is black and heading is development. You can see a similar screen as below.
If you change the backgroundColor and heading to red and production respectively. You can see a screen as below.
The problem we are facing
The twelve-factor methodology suggests that we need to build once run anywhere but in this case, we are building for each environment. For simplicity, we are considering only two environments development and production. Since we are serving the Angular app with NGINX and we can only provide configuration at build time rather than run time. We can’t even provide environment variables at release since the browser doesn’t read those environment variables either.
We are building for each environment because we had to pass environment-specific information at build time. We need to find a way that we should pass this info at run time. If we pass at the run time all we need to build once and run anywhere as we see in the below diagram.
Let’s see how we can solve this problem. One way to solve this is to read the browser URL with the window location.href and put all the configuration in the app and load appropriate configuration based on some specific part of the URL such as dev, prod, etc.
If you using Java or Nodejs with the Angular app we can get this configuration form the server before bootstrapping the app with the APP_INITIALIZER. But how do we do this if we are using NGINX as the webserver?
We can use Kubernetes configmap to inject configuration into the Pods volume which is mounted at /usr/share/nginx/html/assets folder so that the Angular app gets it before bootstrapping the app with the help of APP_INITIALIZER. Let’s look at the below diagram to understand better.
Let’s implement the solution with the Kubernetes configMap object. ConfigMap object makes your containers portable by decoupling configuration from them. This is how it works.
The first thing we need to do is build the docker image and push it to DockerHub. Here is the multi-stage Dockerfile which builds the Angular app in the first stage and take those static assets and put it in the root folder of NGINX.
These are the instructions to build the Docker image and push it to the DockerHub. You can actually see it on the DockerHub in the following image. This is a public image you can directly pull it from the registry.
// build the image
docker build -t bbachin1/envdemo .// list images
docker images// login and push it to docker hub
docker push bbachin1/envdemo
Now we need to create a deployment, service and configmap objects. we are putting all these objects in one file called manifest.yml. We are creating Configmap first with the required config.json. If you look at the deployment object Kubernetes pulls the above image bbachin1/envdemo from the Docker Hub and creates 5 replicas. Finally, we have a service object with the Nodeport type which exposes to the outside world.
We have loaded configmap into the volume which is mounted on the path /usr/share/nginx/html/assets/ folder. We create all these objects in the namespace development.
Here are the instructions to create objects and verify them.
// create objects
kubectl create -f manifest.yml// delete objects
kubectl delete -f manifest.yml// get the deployment
kubectl get deploy -n development// get the service
kubectl get svc -n development// get the pods
kubectl get po -n development
Get the Kubernetes public address from this command
kubectl cluster-info and get the port from the service object
kubectl get svc -n development and access the application running in the development namespace with this address
http://<public address of minikube>:<svc port>/appui
In the above case, you can access the application at
http://192.168.64.6:31935/appui Make sure you change from https to http. Notice that all the configuration is loaded from the configmap such as header backgroundColor, title, etc.
Let’s create the production deployment from the manifest-prod.yml file and follow the above steps to run the app on your local.
// create objects
kubectl create -f manifest-prod.yml// delete objects
kubectl delete -f manifest-prod.yml// get the deployment
kubectl get deploy -n production// get the service
kubectl get svc -n production// get the pods
kubectl get po -n production
The service in the production namespace is running at the port 31633.
How To Debug
These are some of the debugging options if you have any problem implementing this solution.
First, we need to verify the configmap is created in the right way and in the right namespace.
// verify if configmap is created or not
kubectl get cm -n development// verify the data in the configmap
kubectl describe cm -n development
Once you verify the configmap. You can then check the mounted volume that is loaded with the configmap.
// get the one of the pod
kubectl get po -n development// exec into one of the pod
kubectl exec -it <podname> /bin/sh -n development
# cd /usr/share/nginx/html/envapp/assets
# cat app.config.json
- Angular provides configuration options at build time which means you need to define different environment files for each environment.
- We need to build once run everywhere is the recommended strategy.
- We can’t use the Angular environment option if we want to build once and deploy everywhere since we have to provide a separate configuration for each environment.
- Configmap provides a solution to decouple the configuration from the running the containers.
- If you are serving your Angular application with NGINX and you need a way to pass configuration at runtime then, ConfigMaps is the easiest solution.
- You should load the configmap into the volume which can be mounted on host path and Angular get that JSON from that path.
- You can delete the existing configmap and recreate one and the changes can be reflected in the running container without restarting the pods.
Use Configmaps if you want to decouple the configuration from your containers and inject appropriate configuration at runtime.