Skip to content

Example project showing how to use Nginx as a simple cache for Traefik - incl. a Spring Boot client and server

License

Notifications You must be signed in to change notification settings

jonashackt/traefik-cache-nginx-spring-boot

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

traefik-cache-nginx-spring-boot

Build Status License renovateenabled versionspringboot versiontestcontainers

As Traefik is a really gread & modern loadbalancer, but it sadly doesn´t feature caching right now. So we need to put something in front of it, that is able to do caching - like old Nginx.

And as I like full examples, let´s bring in a client application (weatherclient), which want´s to call a server backend (weatherbackend). Both are implemented as simple Spring Boot microservices, as the following ASCII shows:

                  -----------------------------------------------------------------------------    
                 | Docker Network scope                                                        |  
                 |                                                                             |  
                 |                                                                             |   
                 |                                                                             |
 ============    |   ==============     ================     ===============     ============  |
 =  docker- =    |   =            =     =              =     =             =     =          =  |
 = network- = -----> =   weather  = --> =    Nginx     = --> =   Traefik   = --> =  weather =  |
 =  client  =    |   =    client  =     =  (caching)   =     = (loadbalan.)=     =  backend =  |
 ============    |   ==============     ================     ===============     ============  |
                 |                                                                             |
                 |                                                                             |
                 |                                                                             |
                  -----------------------------------------------------------------------------
                 

It also shows, that we simulate the whole scenario with Docker. To have the chance to execute everything within an intergration test, we use docker-compose-rule and the docker-network-client app. Why?

As the weatherclient only has access to the DNS alias weatherbackend, if it itself is part of the Docker (Compose) network, we need another way to run an Integration test inside the Docker network scope. Therefore we use the docker-compose-rule and the docker-network-client that just calls weatherclient inside the Docker network.

HowTo Use

Everything you need to run a full build and complete test (incl. Integrationtest of docker-network-client firing up all microservices that´ll call each other with client certificate support) is this:

mvn clean install

Only if you want to check everything manually - which you for sure want to do :) - fire up all components with:

docker-compose up -d

Now you can have a look at some of the components of our architecture:

Traefik: http://localhost:8080/dashboard/#/

weatherclient: http://localhost:8085/swagger-ui.html

Nginx: http://localhost:8088/

Alternative variant without Docker-Compose for the Dockerized Spring Boot backends

In a real world DevOps scenario, you may want to run Traefik separate from the Dockerized Spring Boot backends - so that each Backend is able to have it's own CI/CD pipeline for example (e.g. GitLab CI).

Now in this case you don't want a central docker-compose.yml, since you want to run and scale all the services independently.

There's a way to run Traefik and the backends separately, as shown in the docker-compose-traefik-only.yml without-compose.sh files:

version: '3.8'

services:

  traefik:
    image: traefik
    command:
      # - "--logLevel=DEBUG"
      - "--api.insecure=true"
      # Enabling docker provider
      - "--providers.docker=true"
      # Do not expose containers unless explicitly told so
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
    ports:
      - "80:80"
      - "8080:8080"
    networks:
      - traefiknet
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
    restart:
      always

networks:
  traefiknet:
#!/usr/bin/env bash

echo "[-->] Start Traefik using Compose as usual"
docker-compose -f docker-compose-traefik-only.yml up -d

echo "[-->] Startup weatherbackend without Compose - but connect to Traefik"
docker build ./weatherbackend --tag weatherbackend
docker run \
  --rm \
  -d \
  -p 8095 \
  --label="traefik.enable=true" \
  --label="traefik.http.routers.whoami.entrypoints=web" \
  --label="traefik.http.routers.weatherbackend.rule=Host(\`weatherbackend.server.test\`)" \
  --network="traefik-cache-nginx-spring-boot_traefiknet" \
  --name weatherbackend \
  weatherbackend

echo "[-->] Startup weatherclient without Compose - but connect to Traefik"
docker build ./weatherclient --tag weatherclient
docker run \
  --rm \
  -d \
  -p 8085:8085 \
  --network="traefik-cache-nginx-spring-boot_traefiknet" \
  --name weatherclient \
  weatherclient

The downside of this approach is, that one cannot use the scaling option of Docker-Compose now so easily.

Nginx + Traefik + weatherbackend in logical scope (aka host) with the help of Docker DNS

Additionally, in real world scenarios, Nginx + Traefik + weatherbackend would reside on a separate host with their own DNS configuration. So there´s a second "logical" scope here, which we could have implemented with tools like Vagrant - but this would have been overkill here.

Trying to imitate a machine, where Traefik + weatherbackend + Nginx are running all on one machine with DNS configured, we configure the Docker DNS alias for weatherbackend to the Traefik container, which routes it then to the weatherbackend. This is done with the help of this Docker Compose configuration (see the docs):

  traefik:
    ...
    networks:
      default:
        aliases:
          - weatherbackend.server.test
    ...

Instead of configuring the weatherbackend directly to have the DNS alias weatherbackend.server.test, we use the Traefik Docker Compose service here - which has a similar effect to a scoped machine around Nginx, Traefik & weatherbackend. We should see the call now in Traefik GUI:

first-call-weatherbackend-through-traefik-with-docker-dns-configured

Now installing Nginx as cache

To see what´s happening, we need to activate Nginx´ logging to the Docker log system. As we use the alpine image here, we need to create our own Dockerfile for that:

FROM nginx:alpine

# forward request and error logs to docker log collector
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
    && ln -sf /dev/stderr /var/log/nginx/error.log

CMD ["nginx-debug", "-g", "daemon off;"]

Additionally, we need to imitate the Scope of Nginx + Traefik + weatherbackend again - now that Nginx is at front of all three:

  nginx:
    build: ./nginx
    ...
    ...
    networks:
      default:
        aliases:
          - weatherbackend.server.test
    ...

Now, the WeatherclientController.class needs to access Nginx instead of Traefik directly. This is done with the like the first tutorial steps with Traefik discribe - like the famous curl:

curl -H Host:weatherbackend.server.test http://nginx:80 -v

We therefore set the Host header when using Spring´s RestTemplate:

import org.springframework.http.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.PostConstruct;

@RestController
public class WeatherclientController {

    private RestTemplate restTemplate = new RestTemplate();

    /*
     * Without the System property, we won´t be able to set the Host header, see
     * https://stackoverflow.com/questions/43223261/setting-host-header-for-spring-resttemplate-doesnt-work/43224279
     * and https://stackoverflow.com/a/8172736/4964553
     */
    @PostConstruct
    public void setProperty() {
        System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
    }

    @GetMapping("/forecast/{cityname}")
    @ResponseStatus(HttpStatus.OK)
    public String forecast(@PathVariable("cityname") String cityname) {

        HttpHeaders headers = new HttpHeaders();
        headers.set("Host", "weatherbackend.server.test");

        ResponseEntity<String> responseEntity = restTemplate.exchange("http://nginx:80/weather/" + cityname,
                HttpMethod.GET,
                new HttpEntity<String>(null, headers),
                String.class);

        return responseEntity.getBody();
    }
}

As you maybe already noticed, setting the Host header in this way is only possible, if we set System.setProperty("sun.net.http.allowRestrictedHeaders", "true");, like we do it in Spring style:

    /*
     * Without the System property, we won´t be able to set the Host header, see
     * https://stackoverflow.com/questions/43223261/setting-host-header-for-spring-resttemplate-doesnt-work/43224279
     * and https://stackoverflow.com/a/8172736/4964553
     */
    @PostConstruct
    public void setProperty() {
        System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
    }

Now our setup works perfectly fine. If you fire many requests with the help of weatherclient, using Springfox Swagger GUI at http://localhost:8085/swagger-ui.html#!/weatherclient45controller/forecastUsingGET and do a docker logs NginxContainerIdHere --follow to see the logs of Nginx and a docker logs weatherbackendContainerIdHere to see the logs of the weatherbackend, you´ll notice only one call in Traefik and one call of the weatherbackend:

one-call-in-traefik

one-call-of-weatherbackend

But you´ll notice many calls inside Nginx:

many-calls-in-nginx

About

Example project showing how to use Nginx as a simple cache for Traefik - incl. a Spring Boot client and server

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published