Multidomain and Path Routing with Nginx
In this tutorial we will use Section and Nginx to create microservices with multidomain and multipath routing. We will outline how we can have multiple services and deployments all talking to each other within a Section application.
You will create two different deployments - hello-node-deployment.yaml
and hello-ruby-deployment.yaml
, as well as a service for each. Each deployment will return “hello world” in either node or ruby based on the routing.
There are many scenarios that can benefit from this architecture. For example Section has three different frontend microservices all serving different applications (repositories) to the same website. These include www.section.io, www.section.io/docs and www.section.io/engineering-education which are all static websites. Our single Section application also includes an application programming interface (API) that we use to fetch data from the Google Analytics API in order to display authors number of page views on the EngEd program. Each of these repositories have their own CI/CD pipelines allowing us to have a combination of public and private repos with added flexibility.
note
Before starting, create a new Section Project and then delete the default Deployment and ingress-upstream
Service to prepare the project for your new deployment.
Add the Domains
Add the domains you’d like to handle the routing for in your Section application. In this example we will add the domains www.example-domain-1.com and www.example-domain-2.com.
Setup Project Structure
Follow the Getting Started steps in Docs if you’re just starting out with Section. If you already have a Section application, point your ingress-service
container to a Nginx container. It may help to use Kustomization if you aren’t already. With the use of kustomization, we can utilize the configMapGenerator
to volume mount our nginx configuration.
Create the following /k8s
directory and structure:
.
└── /k8s/
├── /base
├── hello-node-deployment.yaml
├── hello-ruby-deployment.yaml
├── hello-node-service.yaml
├── hello-ruby-service.yaml
├── ingress-service.yaml
├── kustomization.yaml
├── router.yaml
└── router.conf
For this example we will create deployment-app1.yaml
and deployment-app2.yaml` as two nginx pods running different versions.
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-node-deployment
labels:
app: hello-node
spec:
replicas: 1
selector:
matchLabels:
app: hello-node
template:
metadata:
labels:
app: hello-node
spec:
containers:
- name: hello-node
image: pvermeyden/nodejs-hello-world:a1e8cf1edcc04e6d905078aed9861807f6da0da4
imagePullPolicy: Always
ports:
- containerPort: 80
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "500m"
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-ruby-deployment
labels:
app: hello-ruby
spec:
replicas: 1
selector:
matchLabels:
app: hello-ruby
template:
metadata:
labels:
app: hello-ruby
spec:
containers:
- name: hello-ruby
image: sebp/ruby-hello-world
imagePullPolicy: Always
ports:
- containerPort: 80
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "500m"
apiVersion: v1
kind: Service
metadata:
labels:
app: hello-world-service
name: hello-world-service
namespace: default
spec:
ports:
- name: 80-80
port: 80
protocol: TCP
targetPort: 80
selector:
app: hello-world
sessionAffinity: None
type: ClusterIP
apiVersion: v1
kind: Service
metadata:
labels:
app: hello-ruby-service
name: hello-ruby-service
namespace: default
spec:
ports:
- name: 80-80
port: 80
protocol: TCP
targetPort: 80
selector:
app: hello-ruby
sessionAffinity: None
type: ClusterIP
Use configMapGenerator
to apply the Nginx Configuration
Create a volume mount and configmap to load a custom nginx configuration for router.yaml
and define the resources you’d like kustomization to manage.
configMapGenerator:
- name: router-config-mount
files:
- ./router.conf
resources:
- hello-world-deployment.yaml
- hello-world-service.yaml
- hello-ruby-deployment.yaml
- hello-ruby-service.yaml
- router.yml
- ingress-service.yml
The configMapGenerator
defines the name and location of the file to mount. In this case the file path is ./router.conf
with the name router-config-mount
.
Apply the configMap
via volumes
and volumeMounts
In our router.yaml
below we will add the configMap we just created via volumes
and volumeMounts
.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.21.6
imagePullPolicy: Always
volumeMounts:
- name: router-config
mountPath: "/etc/nginx/conf.d"
resources:
requests:
memory: ".5Gi"
cpu: "500m"
limits:
memory: ".5Gi"
cpu: "500m"
ports:
- containerPort: 80
volumes:
- name: router-config
configMap:
name: router-config-mount
Nginx configuration
Create a router.conf
file in the k8s/base
directory. This configuration controls the routing for the two different domains. Both domains return the same applications for each location block. However these can point to different services. The important thing to note here is that we can use proxy_pass
to point our routing to different services that we’ve created.
server {
listen 80;
listen [::]:80;
server_name www.example-domain-1.com;
location /node {
proxy_pass http://hello-world-service;
}
location /ruby {
proxy_pass http://hello-ruby-service;
}
}
server {
listen 80;
listen [::]:80;
server_name www.example-domain-2.com;
location /node {
proxy_pass http://hello-world-service;
}
location /ruby {
proxy_pass http:/hello-ruby-service;
}
}
By adding multiple server blocks, we can create routing for different domains. Each server block can contain multiple location blocks. In each location block we can make use of Nginx Reverse Proxy by using the proxy_pass
key we can specify the name of a service we want the location to route to.
location / {
proxy_pass http://hello-world-service;
}
In the above example we are handling the routing for both domains www.example-domain-1.com
and www.example-domain-2.com
. When a request comes from www.example-domain-1.com/node
we route the request using proxy_pass
to our service hello-node-service.yaml
, this returns “hello world” from NodeJS.
Similarly, a request from www.example-domain-1.com/ruby
points to our service hello-ruby-service.yaml
, returning us “hello world” from Ruby. As you can see the code above has the same configuration for both domains. However, in a real world scenario these can all have different configurations with different types of applications.
This architecture allows us to have multiple applications and microservices all in one Section application. With relatively simple configuration we can communicate back and forth between different pods and services.