Configure a Port Range with the (Yet Another) Docker plugin

Article ID:360001723531
4 minute readKnowledge base

Issue

  • I want to restrict the port range on which SSH docker agents are listening to

  • My Docker cloud sometimes does not provision agents. The Jenkins logs show a stacktrace like the following:

2018-01-22 01:45:45.017+0000 [id=1263]	SEVERE	c.g.d.c.a.ResultCallbackTemplate#onError: Error during callback
com.github.dockerjava.api.exception.InternalServerErrorException: {"message":"driver failed programming external connectivity on endpoint unruffled_euclid (XXXX): Bind for <IP>:<PORT> failed: port is already allocated"}

or

Jan 22, 2018 12:50:17 PM com.github.kostyasha.yad_docker_java.com.github.dockerjava.core.async.ResultCallbackTemplate onError
SEVERE: Error during callback
com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.exception.InternalServerErrorException: {"message":"driver failed programming external connectivity on endpoint confident_wing (XXXX): all ports are allocated"}

Background

You cannot have two applications listening on the same port - for example you cannot have 2 sshd server running on port 20001. Starting a process that listens on a port already used would cause an startup error reported by the OS. That is somewhat what happens when you try to map the same port on 2 different ssh agent containers on the same docker host.

  • If you experience the error Bind for <IP>:<PORT> failed: port is already allocated when provisioning docker agents from Jenkins, that is because the PORT being mapped to the container is already in use.

  • If you experience the error all ports are allocated when provisioning docker agents from Jenkins, that is because all ports of the port range binding specified are already in use

The configuration of Docker clouds makes it possible to specify the port binding to avoid these issues.

Resolution

By default, the Docker Plugin and the Yet Another Docker plugin have a mechanism that automatically picks up a port within the ephemeral port range and bind it to the SSH port (the port specified in the Docker SSH Launcher). This is how it can runs several agents simultaneously. But the ephemeral port range is large: 32768 to 61000.

For security reasons, it is recommended and really common to restrict access to some ports or a port range. In such cases, the default configuration of a docker template is not appropriate. The Jenkins Docker templates must be configured accordingly.

In Jenkins and the configuration of the docker templates, you can bind the container SSH port to a range of host ports - like for example 20001-20005:22 or 0.0.0.0:20001-20005:22. That way, when Jenkins requests an agent for a specific docker template, the docker host starts a container and pick up an available port in the range specified to map to the container SSH port. The docker template instance capacity ensure that not more than the defined number of containers are running simultaneously and therefore should prevent that the same port be requested twice.

Configure Port Binding

Go to the configuration of the Shared Cloud / Docker cloud, then depending on the solution being used:

  • Yet Another Docker Plugin: In the configuration of the docker template, click on the button "Create Container settings…​" to expand the configuration. Set the port range binding in the field Port bindings.

  • Docker Plugin / Operations Center Docker Cloud Plugin: In the configuration of the docker template, click on the button "Container settings…​" to expand the configuration. Set the port range binding in the field Port bindings.

Examples

As an example, I set an instance capacity of 5 for my docker template and set the options 20001-20005:22 in the port binding field. With this configuration, when Jenkins requests an agent for that docker template, the docker host starts a container and pick up an available port in the range 20001-20005 to map to the container SSH port. The docker template instance capacity ensure that no more than 5 containers are running simultaneously and therefore prevents that the same port be requested twice.

Note that this can be extended for the docker cloud configuration. Instead of defining a "port range per docker template", another use case could be to define a "port range per docker cloud". For example, if I have a docker cloud with a capacity of 100 and 5 templates each with a capacity of 20. I can either set each template with the same property for port binding 20001-20100:22 or I can isolate each template with a different port range: template1 on 20001-20020:22, template2 on 20021-20040:22, …​

What matters is that the container capacity - number of containers that can be started by the cloud/template - is inferior or equal to the number of ports in the port range specified. Otherwise you may experience one of the issues with port allocation Bind for <IP>:<PORT> failed: port is already allocated or all ports are allocated.

Tested product/plugin versions [mandatory, when it applies]