Random Server Ports and Spring Cloud Service Discovery with Netflix Eureka
Recently, I've had an issue were I want to be able to run multiple spring boot services locally for testing and development purposes. Unfortunately they all run on the same port so they fail to start!
Fixing this is simple enough, in our Spring Boot application, we can just configure a server port in our application.yml
like so:
server:
port: 0
We can choose 0
for our port to be randomly chosen at startup. If we want to specify another port, we can manually set it to 8081
or 8082
, etc... However, manually specifying ports is painful, especially if you have many services you'd like to run.
Another downside to specifying ports manually is deployments. When you deploy your application to a server (on-prem or in the cloud), then you have to make sure that there are no port collisions on the server the application is being deployed to. This is a tedious and time consuming effort for the team responsible for deploying services, and annoying to the developer for documenting the port(s) the application requires. To complicate matters further, imagine you are deploying in a docker container and using a container orchestration framework. Likely you will not know ahead of time which node the application will actually be deployed on, if it's successfully deployed at all. In this instance your orchestrator could run into port conflicts, or fail to provision your application due to a lack of nodes with your container's port available.
Setting a random port comes with downsides as well. It is difficult to access your service locally since it is always on a different port after restarting. This can be addressed by using a gateway, like Spring Cloud Gateway and configuring auto-discovery using Spring Cloud Discovery with Netflix Eureka to direct traffic to your application through a common gateway port. Again, we will run into another problem, your application during initialization will report port 0
(random) to Eureka as the port it's running on, resulting in an unreachable service.
This is not what we want, so we need a way to customize initialization of the application to choose a random port for us and allow Eureka / Service Discovery to configure itself with that new random port. We can achieve this in Spring Boot 2 (Spring Framework 5+) by extending the WebServerFactoryCustomizer
class like so:
@Configuration
public class WebServerFacotryCustomizerConfiguration implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Value("${port.number.min:8080}")
private Integer minPort;
@Value("${port.number.max:8090}")
private Integer maxPort;
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
int port = SocketUtils.findAvailableTcpPort(minPort, maxPort);
factory.setPort(port);
System.getProperties().put("server.port", port);
}
}
Here we'll use Spring's SocketUtils to find a port that is available in our range, set that port on the servlet web server factory configuration, and also set it as a system property. Doing this will ensure that our application will get an available port that doesn't collide with a port already in use and it will also allow our service discovery to initialize itself properly!
On a side note, I have not tested this in a production environment or deployed in any manner, so it's possible there will be some issues. This solution may also be difficult to use in your deployment process which may require knowing the port of the application ahead of time. However if your services all depend on Eureka for service discovery amongst themselves, Eureka will report the correct application port and IP address for your service to be reachable. As long as your services do not require additional port forwarding for your port(s) be be reachable outside of the host this should work just fine.