Other Blog Posts
Running UMN MapServer on Kubernetes
2024-09-15
UMN MapServer is a well known Open Source platform for publishing spatial data and interactive mapping applications to the web. Going back to the mid-1990's it is still widely used and of course you can run it in a container on Kubernetes.
Because there are binary packages for many Linux distributions, creating a MapServer container image is really simple.
We build our image as a FastCGI service using spawn-fcgi
.
Using spawn-fcgi to run a standalone MapServer process
If we start the mapserv
binary using spawn-fcgi
from a login shell on any Linux system like this:
spawn-fcgi -a 0.0.0.0 -p 9990 -n -- /usr/bin/mapserv
and then look at the process tree with ps -ef --forest
we will find the following lines in the output:
root 1631 1 0 07:13 tty1 00:00:00 /bin/login -p --
thomas 54480 1631 0 14:09 tty1 00:00:00 \_ -bash
thomas 55007 54480 0 14:10 tty1 00:00:00 \_ /usr/bin/mapserv
We can see one process of our mapserv
executable.
The -n
switch will prevent spawn-fcgi from forking a mapserv process and instead run in foreground.
This is exactly what we want to have in our container.
The MapServer Dockerfile
Let's put this in a Dockerfile:
FROM alpine:latest
RUN apk upgrade -U
RUN apk add -U --update --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing mapserver
RUN apk add -U --update spawn-fcgi
RUN mkdir /opt/mapserver
RUN chown 1000:1000 /opt/mapserver
USER 1000
ENV MAPSERVER_CONFIG_FILE="/opt/mapserver/mapserver.conf"
CMD ["/usr/bin/spawn-fcgi", "-a", "0.0.0.0", "-p", "9990", "-n", "--", "/usr/bin/mapserv"]
The result is a simply structured and lightweight container image that solves one concern. This allows us to handle horizontal scaling and also reverse proxy configuration independently. Arguably putting a reverse proxy in the container image would slightly simplify the communication given that HTTP is more commonly available than FastCGI. However any increase in load will typically affect MapServer most. Thus we will need to scale up MapServer more quickly than the reverse proxy, and we avoid some overhead by keeping the reverse proxy software outside of our container image.
We're using Alpine here, but a lot of Linux distros have Mapserver packages. By using a MapServer package from the distribution we rely on the distro's package maintainers to provide the latest security patches.
Where to put mapfiles and geodata
In the Dockerfile we created an /opt/mapserver
mount point where we will provide application specific MapServer configuration (mapserver.conf
and mapfiles) and optional geodata, e.g. shapefiles.
Assuming we pushed our MapServer image to a container registry and created a persistent volume claim containing the map data we can use the following Kubernetes deployment:
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mapserver
spec:
selector:
matchLabels:
app: mapserver
replicas: 3
template:
metadata:
labels:
app: mapserver
spec:
containers:
- name: mapserver
image: <your.container.registry>/mapserver:latest
imagePullPolicy: Always
ports:
- containerPort: 9990
volumeMounts:
- mountPath: /opt/mapserver
name: mapdata
volumes:
- name: mapdata
persistentVolumeClaim:
claimName: mapdata-pvc
---
apiVersion: v1
kind: Service
metadata:
labels:
app: mapserver
name: mapserver
spec:
ports:
- port: 9990
protocol: TCP
targetPort: 9990
selector:
app: mapserver
type: ClusterIP
In the deployment we scale MapServer to 3 replicas. In addition we create a service for our pod for communicating with the reverse proxy.
Reverse Proxy Configuration for the MapServer FastCGI service
Next we configure a reverse proxy that communicates with our container using the FastCGI protocol. In particular we look at how to configure Nginx and Apache.
Nginx
For Nginx we can define a location:
location /map/ {
include /etc/nginx/fastcgi_params;
fastcgi_pass mapserver:9990;
fastcgi_param SCRIPT_FILENAME /usr/lib/cgi-bin/mapserv$fastcgi_script_name;
}
Apache
For Apache, you will need to enable mod_proxy_fcgi
and then add a configuration line like this:
ProxyPass "/app/" "fcgi://mapserver:9990/";
Traefik
At the time of writing this post a FastCGI implementation for Traefik is not yet available. Currently the progress is tracked here.