I have to build and interact with things to understand them. At MIT, we focused on combination of thought and practical action to solve current and future problems and our motto (mens et manus) combines application (hand) and theory (mind).
By day, I’m leading adoption of containers and automation technologies to drive big changes in enabling software reusability. By night, I’m using containers to teach me new programming languages, interfaces and networking concepts. Last week, I wanted to learn how reverse proxies work and wanted to use containers and some familiar technology express, flask, nginx and docker to help me.
First, I’m sharing this because I wish this existed out there to learn from, so please head to gitlab https://gitlab.com/tim284/nginx_proxy_test and clone this and let me know if you do anything awesome with it.
Because I like Plutarch in general and studying the Battle of Thermopylae in particular, you will notice a theme. (Please, the movie is all cool, but nothing close to reading the Gates of Fire.)
Containers are a solution to the problem of how to get software to run reliably when moved from one computing environment to another. The basic idea is to have the complexity and overhead that you want. Instead of using a full operating system in a virtual machine, you can use the bits you want and need. A container consists of an entire runtime environment: an application, plus all its dependencies, libraries and other binaries, and configuration files needed to run it, bundled into one package. By containerizing the application platform and its dependencies, differences in OS distributions and underlying infrastructure are abstracted away.
I created two express applications, one Flask and one static site. I run the active apps in containers and use nginx reverse proxy to present them. The static HTML page connects through a docker volume. Free Code Camp wrote a nice tutorial that explains this type of setup.
One of the key concepts I had to learn was how networking works in Docker. This seemed like a right of passage I needed to know to work with containers in general. This article helped me a lot.
Complexity continues to increase with continuous new introduction of new programming languages, hardware, architectures, frameworks, and discontinuous interfaces between tools for each lifecycle stage. Containers allow you to focus on what you are building and quickly adapt new technology. Most important, I can quickly change and reuse things to learn a lot quickly. It can be hard to remain a full stack developer, a dad and a business leader. Docker simplifies a lot of things I don’t have time to learn and accelerates my workflow to allow me to experiment and innovate with different tools, application stacks, and deployment environments.
For this experiment, I use docker-compose to pull in images of nginx, flask, express and the redis database.
Nginx is a popular web server (23.21% of sites) that can also be used as a reverse proxy, load balancer, mail proxy and HTTP cache. The software was created by Igor Sysoev and publicly released in 2004. A company of the same name was founded in 2011 to provide support and Nginx Plus paid software. In March 2019, the company was acquired by F5 Networks for $670 million. What a crazy startup idea: take an open source project, improve and support it and start a company (github, Nginx, etc).
Nginx is built to handle many concurrent connections at the same time. It can handle more than 10,000 simultaneous connections with a low memory footprint (~2.5 MB per 10k inactive HTTP keep-alive connections). This makes it ideal for being the point-of-contact for clients. The server can pass requests to any number of backend servers to handle the bulk of the work, which spreads the load across your infrastructure. This design also provides you with flexibility in easily adding backend servers or taking them down as needed for maintenance.
Another instance where an http proxy might be useful is when using an application servers that might not be built to handle requests directly from clients in production environments. Many frameworks include web servers, but most of them are not as robust as servers designed for high performance like Nginx. Putting Nginx in front of these servers can lead to a better experience for users and increased security. This post from Digital Ocean is awesome at explaining all of this.
A proxy means that information is going through a third party, before getting to the location. Why use it? For example, if you don’t want a service to know your IP, you can use a proxy. A proxy is a server that has been set up specifically for this purpose. If the proxy server you are using is located in, for example, Amsterdam, the IP that will be shown to the outside world is the IP from the server in Amsterdam. The only ones who will know your IP are the ones in control of the proxy server.
Proxying in Nginx is accomplished by manipulating a request aimed at the Nginx server and passing it to other servers for the actual processing. The result of the request is passed back to Nginx, which then relays the information to the client. The other servers in this instance can be remote machines, local servers, or even other virtual servers defined within Nginx. The servers that Nginx proxies requests to are known as upstream servers.
A reverse proxy, by contrast, will not mask outgoing connections (you accessing a webserver), it will mask the incoming connections (people accessing your webserver). You simply provide a URL like example.com, and whenever people access that URL, your reverse proxy will take care of where that request goes.
Here I’m using a reverse proxy so I can have services running on a several ports, but I only expose ports 80 and 443, HTTP and HTTPS respectively. All requests will be coming into my network on those two ports, and the reverse proxy will take care of the rest.
Nginx can proxy requests to servers that communicate using the http(s), FastCGI, SCGI, and uwsgi, or memcached protocols through separate sets of directives for each type of proxy. The Nginx instance is responsible for passing on the request and massaging any message components into a format that the upstream server can understand.
My Nginx config below allowed me to proxy_pass to the upstream servers that Docker created.
Express.js, or simply Express, is a back end web application framework for Node.js, released as free and open-source software under the MIT License. It is designed for building web applications and APIs. It has been called the de facto standard server framework for Node.js. I like it because I can create a web application in several lines.
Flask is the most minimal python web application framework. My app is bare-bones simple and just returns some basic text.
In order to test the most basic feature of nginx, I build a static page.
First I want to see all of my containers running:
➜ proxy_test git:(master) ✗ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ac83546944da nginx "/docker-entrypoint.…" 5 minutes ago Up 5 minutes 0.0.0.0:80->80/tcp, :::80->80/tcp proxy_test_webserver_1 36d93cfe4a41 proxy_test_flask "/bin/sh -c 'python …" 7 hours ago Up 7 hours 0.0.0.0:5000->5000/tcp, :::5000->5000/tcp proxy_test_flask_1 30f29133b42a proxy_test_lochagus "docker-entrypoint.s…" 7 hours ago Up 7 hours 0.0.0.0:49160->8080/tcp, :::49160->8080/tcp proxy_test_lochagus_1 0d1a98a27730 proxy_test_leonidas "docker-entrypoint.s…" 7 hours ago Up 7 hours 0.0.0.0:49161->8080/tcp, :::49161->8080/tcp proxy_test_leonidas_1 4770b2726dfd redis "docker-entrypoint.s…" 7 hours ago Up 7 hours 6379/tcp proxy_test_redis_1
and, does it work?
curl -i localhost produces my static page. but most important,
curl -i localhost/flask produces dynamic content.
HTTP/1.1 200 OK Server: nginx/1.21.0 Date: Sun, 06 Jun 2021 10:09:03 GMT Content-Type: text/html; charset=utf-8 Content-Length: 51 Connection: keep-alive This Compose/Flask demo has been viewed 23 time(s).%
This is just the beginning. Next, I’m going to use gitlab to deploy all of this let’s encrypt to secure all of the traffic and more.