October 26, 2020

Out Here In The Fields

…of musings and ramblings

Using multiple SSL certificates and load balancing multiple sites with Traefik

6 min read

Update September 22nd 2019:

It seems that traefik has recently updated their docker registry, in such that when you do pull request will get you traefik version 2.0 by default instead the previous stable version 1.7.xx. Since the article below uses configuration that is applicable for 1.7.x, I have made some update to all pull request in this article to use v1.7.16 to ensure that commands or scripts contained in this article will still work. Check Traefik’s docker page to see the latest version for 1.7.xx branch

What I need is a set of swarm of multiple sites with Apache and load-balance them with traefik. I could serve all of these sites on a single container and scale them, but that would defeat the purpose of establishing microservices, right? So I decided to create 3 sets of Apache instances which I can scale according to my load requirements and all of them will be served via https protocol.

Let’s talk about  the SSL certificates. You can either:

  1. Use traefik integration with letsencrypt to manage the certificate
  2. Get a wildcard certificate, either from letsencrypt or other CAs and use them for all of the subdomains, if you only need subdomains
  3. Or get separate certificates for all the domains you’ll be serving

This post will look into option 2 and 3  as you can use it with Letsencrypt (via certbot requests) or SSL certificate provided by Geotrust, Digicert, or any other CAs.

Readying SSL Certificates

To start, have your certificates ready. If you have a wildcard certificate, you only need to prepare a single pair of certificate file (.crt, .pem) and private key file (.crt, .key). If you have separate certificate for each domain (or subdomain), have each of them ready, and rename them to match the domain or subdomain that you’ll be serving, for example, if you’re going to serve journal.mach5.web.id, you need to name your certificate journal.mach5.web.id.pem (or .crt) and the key journal.mach5.web.id.key. Do this to all of the certificates that we’ll be using.

traefik.toml

Now, let’s look at the first part of my traefik.toml file:

[web]
address = ":8080"
certFile="/certs/m5wildcard_pub.pem"
keyFile="/certs/m5wildcard_privkey.pem"
[web.auth.basic]
usersFile="/conf/.htpasswd"

This part sets up traefik management dashboard, secure it with SSL and password. You can use any of certificate that you have already own, just remember to access the dashboard from that particular address. Moving on…

[docker]
swarmmode = true
domain = "mach5.web.id"
watch = true
# This will hide all docker containers that don't have explicitly
# set label to "enable"
exposedbydefault = false

On the [docker] section we just tell traefik that it will use docker swarm mode, which allows traefik to load-balance containers hosted on the remote nodes instead of the manager node, and only load balance services or containers that we specifically flag to be load-balanced.

[frontends]
 [frontends.website]
 backend = "website"
   [frontends.website.routes.www]
   rule = "Host:www.mach5.co.id"
 [frontends.secure]
 backend = "secure"
   [frontends.secure.routes.secure]
   rule = "Host:secure.mach5.web.id"
 [frontends.cms]
 backend = "cms"
   [frontends.secure.routes.cms]
   rule = "Host:cms.mach5.web.id"
[frontends.journal]
 backend = "journal"
   [frontends.secure.routes.journal]
   rule = "Host:journal.mach5.web.id"

The [frontends] section is where we map traefik frontends to the backend services that we’ll be running on separate containers, based on the label we’ll be giving to each backend docker services that we’ll setup later on, and the URL for those services.

[entryPoints]
  [entryPoints.http]
  address = ":80"
    [entryPoints.http.redirect]
    entryPoint = "https"
  [entryPoints.https]
  address = ":443"
  compress = true
    [entryPoints.https.tls]
    minVersion = "VersionTLS12"
    cipherSuites = [
      "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
      "TLS_RSA_WITH_AES_256_GCM_SHA384"
    ]
      [[entryPoints.https.tls.certificates]]
      certFile = "/certs/www.mach5.web.id.pem"
      keyFile = "/certs/www.mach5.web.id.key"
      [[entryPoints.https.tls.certificates]]
      certFile = "/certs/secure.mach5.web.id.pem"
      keyFile = "/certs/secure.mach5.web.id.key"
      [[entryPoints.https.tls.certificates]]
      certFile = "/certs/cms.mach5.web.id.pem"
      keyFile = "/certs/cms.mach5.web.id.key"

The last part of my traefik.toml file contains the configuration of [entrypoints]. Incoming traffic through port 80 will be redirected to port 443 for HTTPS connection. The minVersion parameter sets that our traefik setup only provides, at minimum, TLS v1.2 connection with limited set of ciphers, ensuring visitors are served with a more secure version of the protocol. The [[entryPoints.https.tls.certificates]] is where we setup which certificate points to which backend. This is done by naming each of the certificate and key files to the name of the URL that they’ll be serving. If you have a wildcard certificate, you can configure the [[entryPoints.https.tls.certificates]] like this instead:

 [[entryPoints.https.tls.certificates]] 
 certFile = "/certs/cert.pem" 
 keyFile = "/certs/cert.key"

This is how my final traefik.toml file looks like:

logLevel = "DEBUG"
defaultEntryPoints = ["http", "https"]

# WEB interface of Traefik - it will show web page with overview of frontend and backend configurations
[web]
address = ":8080"
certFile="/certs/m5wildcard_pub.pem" 
keyFile="/certs/m5wildcard_privkey.pem"
[web.auth.basic]
usersFile="/conf/.htpasswd"
# Connection to docker host system (docker.sock)
[docker]
swarmmode = true
domain = "mach5.web.id"
watch = true
# This will hide all docker containers that don't have explicitly
# set label to "enable"
exposedbydefault = false

[frontends]
 [frontends.website]
 backend = "website"
   [frontends.website.ratelimit]
   extractorfunc = "client.ip"
     [frontends.website.ratelimit.rateset.base]
       period = "10s"
       average = 100
       burst = 200
   [frontends.website.routes.www]
   rule = "Host:www.mach5.web.id"
 [frontends.secure]
 backend = "secure"
   [frontends.secure.ratelimit]
   extractorfunc = "client.ip"
     [frontends.secure.ratelimit.rateset.base]
       period = "10s"
       average = 100
       burst = 200
   [frontends.secure.routes.secure]
   rule = "Host:secure.mach5.web.id"
 [frontends.cms]
 backend = "cms"
   [frontends.cms.ratelimit]
   extractorfunc = "client.ip"
     [frontends.cms.ratelimit.rateset.base]
       period = "10s"
       average = 100
       burst = 200
   [frontends.cms.routes.cms]
   rule = "Host:cms.mach5.web.id"

[entryPoints]
  [entryPoints.http]
  address = ":80"
    [entryPoints.http.redirect]
    entryPoint = "https"
  [entryPoints.https]
  address = ":443"
  compress = true
    [entryPoints.https.tls]
    minVersion = "VersionTLS12"
    cipherSuites = [
      "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
      "TLS_RSA_WITH_AES_256_GCM_SHA384"
    ]
      [[entryPoints.https.tls.certificates]]
      certFile = "/certs/www.mach5.web.id.pem"
      keyFile = "/certs/www.mach5.web.id.key"
      [[entryPoints.https.tls.certificates]]
      certFile = "/certs/secure.mach5.web.id.pem"
      keyFile = "/certs/secure.mach5.web.id.key"
      [[entryPoints.https.tls.certificates]]
      certFile = "/certs/cms.mach5.web.id.pem"
      keyFile = "/certs/cms.mach5.web.id.key"

Now, we can start the traefik service:

sudo docker service create --name traefik --constraint=node.role==manager --publish 80:80 --publish 443:443 --publish 8080:8080 --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock --mount type=bind,source=/projects/mach5/traefik/conf/traefik.toml,target=/traefik.toml --mount type=bind,source=/projects/mach5/traefik/conf,target=/conf --mount type=bind,source=/projects/mach5/certs,target=/certs --network web traefik:v1.7.16
Getting the service up

The configuration above need to be paired to docker services with proper traefik frontend rule labels, for example, here’s a php-apache docker service that I pair with the traefik service:

sudo docker service create --name cms --network web --label traefik.enable=true --label traefik.backend=cms --label traefik.frontend.rule=Host:cms.mach5.web.id --label traefik.backend.loadbalancer.stickiness=true --mount type=bind,source=/projects/mach5/httpd/logs/cms,target=/var/log/apache2 --mount type=bind,source=/projects/mach5/httpd/data/cms,target=/var/www/html --mount type=bind,source=/projects/mach5/httpd/conf/cms/sites-enabled,target=/etc/apache2/sites-enabled --mount type=bind,source=/projects/mach5/httpd/conf/cms/security.conf,target=/etc/apache2/conf-enabled/security.conf --mount type=bind,source=/projects/mach5/httpd/conf/php.ini,target=/usr/local/etc/php/php.ini php-apache-laravel

The “traefik.backend” and “traefik.frontend.rule” labels will pair the service to the corresponding traefik frontend with the same rule, which in this case “cms”, and now we should have traefik accepting https sessions for “cms.mach5.web.id” with proper certificates. The same goes for the rest of the URLs.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Copyright © All rights reserved. | Newsphere by AF themes.