This article is the 23rd day entry of Docker Advent Calendar 2020. By the way, I made an entry on December 24th, and I slipped in because only the 23rd day of the calendar was open.
Nowadays, HTTPS is the norm for websites, isn't it? However, it takes a lot of time and effort to support HTTPS. Money too. I've been wondering if it couldn't be done easily.
** Problems with HTTPS support for Web services **
--You have to get an SSL certificate issued and renew it regularly (it's annoying at this point) --You have to set HTTPS in the web application framework (I'm not familiar with it because I don't usually do it) ――I want to concentrate more on building applications (that's my main business) --If the server load increases, you want to distribute the load by using multiple application servers (a big dream). ――But no one is familiar with it (I don't know much about infrastructure)
I created a Docker container type service to deal with these issues.
EzGate makes it easy to build a reverse proxy that supports HTTPS. It's a so-called SSL accelerator.
** Features **
--Providing reverse proxy function with nginx --Use free Let's Encrypt for SSL certificate for HTTPS --Automatically renewed when the certificate renewal deadline is approaching --Supports HTTP/2 --If you already have an SSL certificate, or you can specify a certificate for your development environment individually. --Can be used even when there are multiple relay destination servers (load distribution configuration) --Can handle multiple domains (multi-tenant configuration)
First, let's take a look at the procedure for setting up a reverse proxy with the simplest configuration with only one web server.
Diagram
Let's say your web server has an IP address of 172.17.0.4
and is listening on port 80.
In this case, simply specify the environment variables in the EzGate container to complete the reverse proxy configuration.
From here, I've actually tried it using the free tier of Google Compute Engine, so I'll show you the procedure. Note that docker must be installed on the virtual machine in advance. The OS is Ubuntu. I think you can try the same procedure on Cent OS.
#Start the wordpress container as a web server for communication confirmation
$ sudo docker run -d --rm --name server1 wordpress:5.6.0-apache
#Confirmation of container startup
$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c0f0d145d86d wordpress:5.6.0-apache "docker-entrypoint..." 18 seconds ago Up 16 seconds 80/tcp server1
#Check the IP address of the container and set it in a variable
$ SERVER1_IP=`sudo docker inspect --format '{{ .NetworkSettings.IPAddress }}' server1|tee /dev/stderr`
172.17.0.4
#Remaining setting information
$ DOMAIN=test.35.197.56.116.sslip.io #The domain of this server. 35.197.56.The 116 part specifies the actual global ID.
$ [email protected] #The email address used to obtain the SSL certificate. Please change it to your own.
#Launch reverse proxy
$ sudo docker run -ti -d --name ez-gate -p80:80 -p443:443 \
-e PROXY_TO=$DOMAIN,$SERVER1_IP:80 \
-e CERT_EMAIL=$MAIL \
neogenia/ez-gate:latest
#Check the container log
$ sudo docker logs -f ez-gate
If the container log comes out in a row and it looks like the following, the startup is successful.
----- finish all setups successfully -----
--- START MONIT -----
* Starting daemon monitor monit [ OK ]
==> /var/log/monit.log <==
[UTC Dec 24 09:56:08] info : New Monit id: f439d27cdf8d377e03482d877b043d2e
Stored in '/var/lib/monit/id'
[UTC Dec 24 09:56:08] info : Starting Monit 5.25.1 daemon
[UTC Dec 24 09:56:08] info : 'c91b02ea822e' Monit 5.25.1 started
[UTC Dec 24 09:56:08] error : 'crond' process is not running
[UTC Dec 24 09:56:08] info : 'crond' trying to restart
[UTC Dec 24 09:56:08] info : 'crond' start: '/etc/init.d/cron start'
==> /var/log/nginx/error.log <==
==> /var/log/nginx/error_test_35_197_56_116_sslip_io.log <==
Open a web browser and access the domain set with $ DOMAIN
.
It is successful when the initial setting screen of Wordpress is displayed.
The SSL certificate is also set correctly.
In this way, all you have to do is set the domain and relay destination ** and it will automatically obtain an SSL certificate ** and operate as a reverse proxy. The relay destination does not have to be a Docker container, it can be another server, and it can be accessed by TCP.
Let's take a look at the container start command again.
#Launch reverse proxy
$ sudo docker run -ti -d --name ez-gate -p80:80 -p443:443 \
-e PROXY_TO=$DOMAIN,$SERVER1_IP:80 \
-e CERT_EMAIL=$MAIL \
neogenia/ez-gate:latest
In PROXY_TO
, set the relay domain and the relay destination server separated by commas.
You can also specify a port number such as : 80
for the relay destination (default is 80
).
For CERT_EMAIL
, set the email address used to obtain the SSL certificate.
An incorrect email address can result in an error.
This is all you need for configuration information.
1: If you get the following error
Traceback (most recent call last):
7: from /var/scripts/reload_config.rb:227:in `<main>'
6: from /var/scripts/reload_config.rb:216:in `backup_dir'
5: from /var/scripts/reload_config.rb:236:in `block in <main>'
4: from /var/scripts/reload_config.rb:236:in `each'
3: from /var/scripts/reload_config.rb:238:in `block (2 levels) in <main>'
2: from /var/scripts/reload_config.rb:96:in `setup_ssl'
1: from /var/scripts/reload_config.rb:28:in `setup'
/var/scripts/reload_config.rb:19:in `shell_exec': ## ERROR ## exit status: 1 command_line: 'APP_DOMAIN=test.35.197.56.116.sslip.io [email protected] /var/scripts/setup_letsencrypt.sh' (RuntimeError)
There is an error in the settings.
For example, the CERT_EMAIL
environment variable may not have the correct email address.
2: If you get the following error
Failed authorization procedure. test.35.197.56.116.sslip.io (http-01): urn:ietf:params:acme:error:dns :: No valid IP addresses found for test.35.197.56.116.sslip.io
Name resolution may have failed on the server side of Let's Encrypt.
sslip.io
may be down, so please try again later or try another free domain.
Also, if you can assign a domain yourself, it's best to assign some suitable subdomain.
3: If you get the following error
An unexpected error occurred:
There were too many requests of a given type :: Error creating new order :: too many certificates already issued for: nip.io: see https://letsencrypt.org/docs/rate-limits/
Let's Encrypt error.
The number of SSL certificate issuances for that domain has been reached.
With popular wildcard DNS such as nip.io
, many people are already trying to issue SSL certificates, so this error is common [^ 1].
[^ 1]: At the time of writing this article, sslip.io
did not give an error, but as more people try this article, the possibility of getting an error increases.
If you can get a free domain such as Freenom and try it out, or assign a domain yourself, it's best to assign some suitable subdomain.
Please note that if you repeat issuing certificates too many times in a short period of time, you may get caught in the limit of the number of Let's Encrypt and an error may occur [^ 2].
[^ 2]: As of the end of 2020, SSL certificates can only be renewed up to 5 times per hour per account and host.
Also, if you want to restart the ez-gate
container, you must first stop the container and do rm
.
$ sudo docker kill ez-gate
$ sudo docker rm ez-gate
In a local development environment (environment that cannot be accessed from the outside with a global IP), Let's Encrypt certificate cannot be issued. You can use mkcert to use the SSL certificate for your local development environment.
You don't have to use a "self-signed certificate" (commonly known as an oleore certificate) anymore!
mkcert registers a certification authority in your local environment and is very easy to install. You can install it on macOS using Homebrew.
brew install mkcert
#Firefox users also need:
brew install nss
See the mkcert Official README for more information.
This is explained when the URL of the development environment is https: // localhost /
.
If you want to access with another IP address, replace localhost
with the IP address.
#Create a folder for storing certificates
$ mkdir certs
#Generate a certificate file for localhost using mkcert
$ mkcert -install #First time only
$ mkcert -key-file certs/key.pem -cert-file certs/cert.pem localhost
#Volume mount the certificate storage folder and specify those files with environment variables
$ sudo docker run -ti -d --name ez-gate -p80:80 -p443:443 \
-e PROXY_TO=localhost,$SERVER1_IP:80 \
-e CERT_FILE=/mnt/cert.pem \
-e KEY_FILE=/mnt/key.pem \
-v `pwd`/certs:/mnt \
neogenia/ez-gate:latest
#Check the container log
$ sudo docker logs -f ezgat
If it starts normally, open a web browser and access https: // localhost /
.
If there is no SSL certificate error, it is successful.
For CERT_FILE
, specify the path of the cert-file generated by mkcert (the path inside the container).
For KEY_FILE
, specify the path of the key-file generated by mkcert (path in the container).
By specifying the above environment variable, Let's Encrypt SSL certificate acquisition will not be performed, and the HTTPS reverse proxy will run using the specified certificate.
If there are multiple relay destination servers, there is no way to specify them with environment variables, so you need to write a configuration file.
Diagram
Now again, here's an example I've actually tried with GCE.
#Start two wordpress containers as a web server(Actually, it is necessary to share sessions and DB to make a cluster configuration)
$ sudo docker run -d --rm --name server1 wordpress:5.6.0-apache
$ sudo docker run -d --rm --name server2 wordpress:5.6.0-apache
#Confirmation of container startup
$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a8c41d95a57d wordpress:5.6.0-apache "docker-entrypoint..." 2 minutes ago Up 2 minutes 80/tcp server2
c0f0d145d86d wordpress:5.6.0-apache "docker-entrypoint..." 3 hours ago Up 3 hours 80/tcp server1
#Check the IP address of each container
$ sudo docker inspect --format '{{ .NetworkSettings.IPAddress }}' server1
172.17.0.4
$ sudo docker inspect --format '{{ .NetworkSettings.IPAddress }}' server2
172.17.0.5
#Create a new configuration file and open it with an editor
$ mkdir mnt
$ vi mnt/config
#Write the following content
domain('test2.35.197.56.116.sslip.io') {
proxy_to "172.17.0.4", "172.17.0.5" #Relay destination server. Multiple can be specified separated by commas
cert_email '[email protected]' #The email address used to obtain the SSL certificate. Please change it to your own.
}
#Exit the editor when you finish writing
#Start the reverse proxy by specifying the config file
$ sudo docker run -ti -d --name ez-gate -p80:80 -p443:443 \
-v `pwd`/mnt:/mnt \
-e CONFIG_PATH=/mnt/config \
neogenia/ez-gate:latest
#Check the container log
$ sudo docker logs -f ez-gate
If it starts normally, open a web browser and try to access it. Let's open the log on the Web server side to confirm that the load is distributed.
#Monitor server1 logs
$ sudo docker logs -f server1
#Open another terminal and monitor server2 logs
$ sudo docker logs -f server2
If you reload the browser in this state, you can confirm that access is coming to both servers.
As a more complicated configuration, you can assign multiple domains and set relay destinations for each.
For example, if it is accessed by www.example.com
, it will be relayed to the server called www
.
When accessed by redmine.example.com
, relay to the server called redmine
,
It is an image like that.
It is a so-called multi-tenant configuration.
Diagram
Just describe the relay destination for each domain as shown below.
domain('www.example.com') {
proxy_to "www" #Relay destination server list. It doesn't have to be IP if the name can be resolved
cert_email '[email protected]' #Email address used to obtain SSL certificate
}
domain('redmine.example.com') {
proxy_to "redmine" #Relay destination server list. It doesn't have to be IP if the name can be resolved
cert_email '[email protected]' #Email address used to obtain SSL certificate
}
The following is a quote from the Official README.
The basic syntax of the configuration file is as follows.
domain('www.example.com') {
proxy_to "webapp1", "webapp2", ...
}
It is possible to write multiple domain ()
.
In addition, you can specify the option of cert_email`` nginx_config
as follows.
domain('www2.example.com') {
proxy_to "apache1", "apache2"
cert_email '[email protected]'
nginx_config <<~_CONFIG_
# change upload size max
client_max_body_size 100M;
_CONFIG_
}
cert_email
takes precedence if the container environment variable CERT_EMAIL
is specified.
If there is any content you want to add to the nginx configuration file, specify it in nginx_config
.
This configuration file is Ruby's internal DSL and is interpreted as a program, so you can read another file or refer to environment variables.
If you change the configuration file, you can reflect the contents of the configuration file without stopping the reverse proxy by executing the reload command as shown below.
docker exec -ti ez-gate /var/scripts/reload_config.rb
If there is an error in the config file, you can rest assured that the reload will fail and the nginx config will not be rewritten and will continue to work.
In the actual development site, there are many cases where configuration management tools such as docker-compose are used instead of docker alone, so by defining environment variables in the configuration configuration file, you can set the same settings as the previous samples. .. A sample YAML file for docker-compose can be found in the Official README (https://github.com/neogenia-jp/EzGate/blob/master/README.ja.md#%E4%BE%8B2).
There is a problem that the certificate is obtained every time the EzGate container is raised or lowered, so there is a way to assign the directory where the certificate file is saved to Docker volume and make it persistent.
Allocate / etc/letsencrypt
to volume and you're good to go.
This time, we introduced a service that can be said to be an infrastructure support service created from troubles at our own development site.
In fact, at our company, we have started the company website, Redmine, Mattermost, etc. in a container with such a multi-tenant configuration. Each domain is assigned and hosted on one server.
In addition, at our development site, highly versatile items are cut out in functional units to improve diversion. We are incorporating it so that it can be used as a microservice.
EzGate is being developed as open source. It is a very good product with a track record of operation in several projects, but There are few documents, and there are still many things that need to be maintained in order to spread to the general public, but If you are interested, please use it. Issues and pull requests are also welcome.
That's all from the field.
Recommended Posts