Zuul is an edge server that seats between the outside world and the downstream services and can handle cross-cutting concerns like security, geolocation, rate limiting, metering, routing, request / response normalization (encoding, headers, urls). Developed and used by Netflix to handle tens of billions of requests daily, it has also been integrated for Spring Boot / Spring Cloud applications by Pivotal.
A core component of the Zuul server is the Zuul filters, which Zuul provides four types of:
Filter type
Description
pre filters
Executed before the request is routed.
routing filters
Handles the actual routing of the request.
post filters
Executed after the request has been routed.
error filters
Executed if an error happens while handling the request.
This post shows how to configure a Spring CloudNetflixZuul server to route requests to a demo downstream service using the provided routing filter RibbonRoutingFilter and how to dynamically refresh the Zuul routes using Spring CloudEureka and Spring Cloud Config servers.
A Eureka server instance for the Spring CloudNetflixZuul servers to read the registry from and to match routes with services.
(Optional) Spring Cloud Config server instance for the Zuul servers to read externally configured routes and refresh them when they are updated.
(Optional) A RabbitMQ host for the Config server to publish changes to and for the subscribed Zuul servers to get notifications from, when the routes are updated.
3. CREATE THE ZUUL SERVER
This command will create a Maven project in a folder named zuulserver with most of the dependencies used in the accompanying source code for this post.
Some of its relevant files are:
pom.xml:
spring-cloud-starter-zuul will bring the required Zuul edge server dependencies while spring-cloud-starter-eureka dependency will allow the Zuul server to proxy requests to the registered services with Eureka first looking up their metadata using the service id mapped to the route used in the request.
ZuulServerApplication.java:
This is the execution entry point of the Zuul server webapp, @EnableZuulProxy set this application up to proxy requests to other services.
application.yml:
The Eureka client is configured to retrieve the registry from the server at the specified location but Zuul itself doesn’t register with it.
Note: In case the Eureka server is not running at localhost:8000, this service would have to be started using: -Deureka.client.serviceUrl.defaultZone=http://<localhost>:<port>/eureka/.
Zuul configuration defines the route zuulDemo1 mapped to /zuul1/**. There are a couple of options to proxy requests to this Zuul’s path to a service:
Using the serviceId. The Zuul server uses this value to retrieve the service metadata from the Eureka registry, in case of multiple servers are found, load-balancing between them is already taken care of and proxies the requests accordingly.
Using the url. The url of the destination is explicitly configured.
4. CREATE THE DEMO SERVICE
Creates a Maven project in a folder named demo-zuul-api-1 with the dependencies needed to demo this post.
pom.xml:
spring-boot-starter-web is included to implement an endpoint using SpringMVCRest and also allows to enable and use Spring Boot actuators. spring-cloud-starter-eureka is included to register this service with a Eureka server in order for the Zuul server to discover it and proxy the requests that matches the configured path.
ActorResource.java
A simple /actors/{id} implementation using SpringMVCRest.
application.yml:
Familiar Eureka client configuration that has been covered here and here.
Note: In case the Eureka server is not running at localhost:8000, this service would have to be started using: -Deureka.client.serviceUrl.defaultZone=http://<localhost>:<port>/eureka/.
5. RUNNING THE EUREKA AND ZUUL SERVERS AND THE DEMO SERVICE
To keep this post simple let’s just run a single instance of the Eureka server using its standaloneSpring profile.
Tip: Read on if you are interested in learning how to create and run the Eureka server including using both, the standalone and peerAware modes.
Now lets start a single instance of the Zuul server:
And lastly lets start the Proxied-Demo service:
Lets’s now send request to the zuulDemo1 route (via zuul1 path) which proxies the request to the Demo service:
Here are some of the logs from the Zuul server while processing such request:
demo-zuul-api1 is the Demo application service id registered with the Eureka server and mapped to the zuulDemo1 route in Zuul server’s application.yml. In case there would be more than one instance of the Demo service registered with the Eureka server, Zuul would load-balance the requests.
Tip: The /routes actuator endpoint, automatically configured by the Zuul server will return the configured routes when sending a GET request, and will refresh the routes configuration when sending a POST request.
6. ADDING SUPPORT TO DYNAMICALLY UPDATE THE ZUUL ROUTES
One problem of the approach described so far is that adding, updating or removing Zuul routes requires the Zuul server to be restarted.
This section takes the Refreshable Configuration using Spring Cloud Config Server, Spring Cloud Bus, RabbitMQ and Git approach and configures the Zuul routes in an external properties file, backed by Git and retrieved via the Spring CloudConfig server so that when Zuul routes are updated, the Zuul server which would be subscribed to a RabbitMQ exchange will be notified about the changes and will refresh its routes without the need for it to be bounced.
Routing traffic using Spring Cloud Netflix Zuul
Next are explained the files changes to be included to accomplish such behavior:
pom.xml:
spring-cloud-starter-config dependency implements reading properties from a SpringCloud Config server backed by a Git backend, in this case, the routes are going to be configured remotely. spring-cloud-starter-bus-amqp includes the dependencies implementing subscribing to a RabbitMQ exchange where the Config server will send messages with the updated properties.
bootstrap.yml:
The first few lines turn RabbitMQ exchange subscription and SpringCloud Config client configuration off, so that the Zuul server works standalone, with routes configured locally as it did before.
The second set of lines defines a Spring profile configuring the Zuul server to read properties from a remote Config server and to subscribe to an exchange to receive notifications when those properties are updated.
Note: Notice that Zuul-Server-refreshable.yml matches the application name Zuul-Server and Spring profile refreshable. This is the criteria the Config server uses to locate this file.
ZuulServerApplication.java
In the Zuul server main class, RabbitAutoConfiguration is left out so that this server works standalone, without any need to connect to a RabbitMQ exchange.
AppConfig.java (New file):
This @Configuration-annotated class is going to instantiate beans and auto-configure a RabbitMQ exchange only when spring.cloud.bus.enabled is true, which happens when using the refreshableSpring profile.
The zuulConfigProperties bean provides Zuul properties read from the Config server and the @RefreshScope annotation indicates this bean to be re-created and injected in components using it.
The reason the Config server is listening on port 8101 is because the Bitbucket has already configured a Webhook to POST to and my router has a port forward entry allowing it.
6.4. RUNNING THE ZUUL SERVER
Notice once starting the Zuul server using the refreshableSpring profile it retrieves configuration properties from the Config server and subscribes to the springCloudBus exchange.
6.5. RUNNING THE PROXIED-DEMO API
6.6. SENDING API REQUESTS VIA ZUUL, UPDATING REMOTE PROPERTIES AND RETRY
It can be seen the request is sent to /zuul2 which is now mapped to the zuulDemo1 route which is specified in the Git-backed file Zuul-Server-refreshable.yml. New requests to path /zuul1 should now fail with HTTP status 404.
In order for the next section to work, the Git repo including the Zuul server configuration properties file (Zuul-Server-refreshable.yml) where the Zuul routes are configured need to be setup with a Webhook to POST to the Config server’s /monitor endpoint when changes are pushed.
Let’s now update the zuulDemo1 route from /zuul2/** to /zuul3/** and push the change:
Zuul-Server-refreshable.yml:
The Config server logs now shows:
The Zuul server logs now shows a Refresh event has been received:
And sending a new request to the Proxied-Demo service results in:
Requests to the previous path /zuul2 should now returns in 404.
And that’s the end of this post.
Thanks for reading and as always, feedback is very much appreciated. If you found this post helpful and would like to receive updates when content like this gets published, sign up to the newsletter.
7. SOURCE CODE
Accompanying source code for this blog post can be found at:
Orlando L Otero is a Sr Software Engineer Consultant focusing on integration, modernization, cloud adoption and migration, microservices, API design and implementation, and agile delivery.