Use system's ephemeral port range for port allocation
Read `/proc/sys/net/ipv4/ip_local_port_range` kernel parameter to obtain ephemeral port range that now sets the boundaries of port allocator which finds free host ports for those exported by containers. Signed-off-by: Michal Minar <miminar@redhat.com>
This commit is contained in:
parent
1ff904e045
commit
0eb3544c43
|
@ -3,8 +3,22 @@ package portallocator
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultPortRangeStart = 49153
|
||||||
|
DefaultPortRangeEnd = 65535
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
beginPortRange = DefaultPortRangeStart
|
||||||
|
endPortRange = DefaultPortRangeEnd
|
||||||
)
|
)
|
||||||
|
|
||||||
type portMap struct {
|
type portMap struct {
|
||||||
|
@ -15,7 +29,7 @@ type portMap struct {
|
||||||
func newPortMap() *portMap {
|
func newPortMap() *portMap {
|
||||||
return &portMap{
|
return &portMap{
|
||||||
p: map[int]struct{}{},
|
p: map[int]struct{}{},
|
||||||
last: EndPortRange,
|
last: endPortRange,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,11 +44,6 @@ func newProtoMap() protoMap {
|
||||||
|
|
||||||
type ipMapping map[string]protoMap
|
type ipMapping map[string]protoMap
|
||||||
|
|
||||||
const (
|
|
||||||
BeginPortRange = 49153
|
|
||||||
EndPortRange = 65535
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrAllPortsAllocated = errors.New("all ports are allocated")
|
ErrAllPortsAllocated = errors.New("all ports are allocated")
|
||||||
ErrUnknownProtocol = errors.New("unknown protocol")
|
ErrUnknownProtocol = errors.New("unknown protocol")
|
||||||
|
@ -59,6 +68,29 @@ func NewErrPortAlreadyAllocated(ip string, port int) ErrPortAlreadyAllocated {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
const param = "/proc/sys/net/ipv4/ip_local_port_range"
|
||||||
|
|
||||||
|
if line, err := ioutil.ReadFile(param); err != nil {
|
||||||
|
log.Errorf("Failed to read %s kernel parameter: %s", param, err.Error())
|
||||||
|
} else {
|
||||||
|
var start, end int
|
||||||
|
if n, err := fmt.Fscanf(strings.NewReader(string(line)), "%d\t%d", &start, &end); n != 2 || err != nil {
|
||||||
|
if err == nil {
|
||||||
|
err = fmt.Errorf("unexpected count of parsed numbers (%d)", n)
|
||||||
|
}
|
||||||
|
log.Errorf("Failed to parse port range from %s: %v", param, err)
|
||||||
|
} else {
|
||||||
|
beginPortRange = start
|
||||||
|
endPortRange = end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPortRange() (int, int) {
|
||||||
|
return beginPortRange, endPortRange
|
||||||
|
}
|
||||||
|
|
||||||
func (e ErrPortAlreadyAllocated) IP() string {
|
func (e ErrPortAlreadyAllocated) IP() string {
|
||||||
return e.ip
|
return e.ip
|
||||||
}
|
}
|
||||||
|
@ -137,10 +169,11 @@ func ReleaseAll() error {
|
||||||
|
|
||||||
func (pm *portMap) findPort() (int, error) {
|
func (pm *portMap) findPort() (int, error) {
|
||||||
port := pm.last
|
port := pm.last
|
||||||
for i := 0; i <= EndPortRange-BeginPortRange; i++ {
|
start, end := GetPortRange()
|
||||||
|
for i := 0; i <= end-start; i++ {
|
||||||
port++
|
port++
|
||||||
if port > EndPortRange {
|
if port > end {
|
||||||
port = BeginPortRange
|
port = start
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := pm.p[port]; !ok {
|
if _, ok := pm.p[port]; !ok {
|
||||||
|
|
|
@ -5,6 +5,11 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
beginPortRange = DefaultPortRangeStart
|
||||||
|
endPortRange = DefaultPortRangeStart + 500
|
||||||
|
}
|
||||||
|
|
||||||
func reset() {
|
func reset() {
|
||||||
ReleaseAll()
|
ReleaseAll()
|
||||||
}
|
}
|
||||||
|
@ -17,7 +22,7 @@ func TestRequestNewPort(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if expected := BeginPortRange; port != expected {
|
if expected := beginPortRange; port != expected {
|
||||||
t.Fatalf("Expected port %d got %d", expected, port)
|
t.Fatalf("Expected port %d got %d", expected, port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,13 +107,13 @@ func TestUnknowProtocol(t *testing.T) {
|
||||||
func TestAllocateAllPorts(t *testing.T) {
|
func TestAllocateAllPorts(t *testing.T) {
|
||||||
defer reset()
|
defer reset()
|
||||||
|
|
||||||
for i := 0; i <= EndPortRange-BeginPortRange; i++ {
|
for i := 0; i <= endPortRange-beginPortRange; i++ {
|
||||||
port, err := RequestPort(defaultIP, "tcp", 0)
|
port, err := RequestPort(defaultIP, "tcp", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if expected := BeginPortRange + i; port != expected {
|
if expected := beginPortRange + i; port != expected {
|
||||||
t.Fatalf("Expected port %d got %d", expected, port)
|
t.Fatalf("Expected port %d got %d", expected, port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,7 +128,7 @@ func TestAllocateAllPorts(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// release a port in the middle and ensure we get another tcp port
|
// release a port in the middle and ensure we get another tcp port
|
||||||
port := BeginPortRange + 5
|
port := beginPortRange + 5
|
||||||
if err := ReleasePort(defaultIP, "tcp", port); err != nil {
|
if err := ReleasePort(defaultIP, "tcp", port); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -153,13 +158,13 @@ func BenchmarkAllocatePorts(b *testing.B) {
|
||||||
defer reset()
|
defer reset()
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
for i := 0; i <= EndPortRange-BeginPortRange; i++ {
|
for i := 0; i <= endPortRange-beginPortRange; i++ {
|
||||||
port, err := RequestPort(defaultIP, "tcp", 0)
|
port, err := RequestPort(defaultIP, "tcp", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if expected := BeginPortRange + i; port != expected {
|
if expected := beginPortRange + i; port != expected {
|
||||||
b.Fatalf("Expected port %d got %d", expected, port)
|
b.Fatalf("Expected port %d got %d", expected, port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,15 +236,15 @@ func TestPortAllocation(t *testing.T) {
|
||||||
func TestNoDuplicateBPR(t *testing.T) {
|
func TestNoDuplicateBPR(t *testing.T) {
|
||||||
defer reset()
|
defer reset()
|
||||||
|
|
||||||
if port, err := RequestPort(defaultIP, "tcp", BeginPortRange); err != nil {
|
if port, err := RequestPort(defaultIP, "tcp", beginPortRange); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else if port != BeginPortRange {
|
} else if port != beginPortRange {
|
||||||
t.Fatalf("Expected port %d got %d", BeginPortRange, port)
|
t.Fatalf("Expected port %d got %d", beginPortRange, port)
|
||||||
}
|
}
|
||||||
|
|
||||||
if port, err := RequestPort(defaultIP, "tcp", 0); err != nil {
|
if port, err := RequestPort(defaultIP, "tcp", 0); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else if port == BeginPortRange {
|
} else if port == beginPortRange {
|
||||||
t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
|
t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,7 +129,8 @@ func TestMapAllPortsSingleInterface(t *testing.T) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
for i := portallocator.BeginPortRange; i < portallocator.EndPortRange; i++ {
|
start, end := portallocator.GetPortRange()
|
||||||
|
for i := start; i < end; i++ {
|
||||||
if host, err = Map(srcAddr1, dstIp1, 0); err != nil {
|
if host, err = Map(srcAddr1, dstIp1, 0); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -137,8 +138,8 @@ func TestMapAllPortsSingleInterface(t *testing.T) {
|
||||||
hosts = append(hosts, host)
|
hosts = append(hosts, host)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := Map(srcAddr1, dstIp1, portallocator.BeginPortRange); err == nil {
|
if _, err := Map(srcAddr1, dstIp1, start); err == nil {
|
||||||
t.Fatalf("Port %d should be bound but is not", portallocator.BeginPortRange)
|
t.Fatalf("Port %d should be bound but is not", start)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, val := range hosts {
|
for _, val := range hosts {
|
||||||
|
|
|
@ -244,9 +244,10 @@ and foreground Docker containers.
|
||||||
When set to true publish all exposed ports to the host interfaces. The
|
When set to true publish all exposed ports to the host interfaces. The
|
||||||
default is false. If the operator uses -P (or -p) then Docker will make the
|
default is false. If the operator uses -P (or -p) then Docker will make the
|
||||||
exposed port accessible on the host and the ports will be available to any
|
exposed port accessible on the host and the ports will be available to any
|
||||||
client that can reach the host. When using -P, Docker will bind the exposed
|
client that can reach the host. When using -P, Docker will bind any exposed
|
||||||
ports to a random port on the host between 49153 and 65535. To find the
|
port to a random port on the host within an *ephemeral port range* defined by
|
||||||
mapping between the host ports and the exposed ports, use **docker port**.
|
`/proc/sys/net/ipv4/ip_local_port_range`. To find the mapping between the host
|
||||||
|
ports and the exposed ports, use `docker port`.
|
||||||
|
|
||||||
**-p**, **--publish**=[]
|
**-p**, **--publish**=[]
|
||||||
Publish a container's port, or range of ports, to the host.
|
Publish a container's port, or range of ports, to the host.
|
||||||
|
|
|
@ -370,17 +370,18 @@ to provide special options when invoking `docker run`. These options
|
||||||
are covered in more detail in the [Docker User Guide](/userguide/dockerlinks)
|
are covered in more detail in the [Docker User Guide](/userguide/dockerlinks)
|
||||||
page. There are two approaches.
|
page. There are two approaches.
|
||||||
|
|
||||||
First, you can supply `-P` or `--publish-all=true|false` to `docker run`
|
First, you can supply `-P` or `--publish-all=true|false` to `docker run` which
|
||||||
which is a blanket operation that identifies every port with an `EXPOSE`
|
is a blanket operation that identifies every port with an `EXPOSE` line in the
|
||||||
line in the image's `Dockerfile` and maps it to a host port somewhere in
|
image's `Dockerfile` or `--expose <port>` commandline flag and maps it to a
|
||||||
the range 49153–65535. This tends to be a bit inconvenient, since you
|
host port somewhere within an *ephemeral port range*. The `docker port` command
|
||||||
then have to run other `docker` sub-commands to learn which external
|
then needs to be used to inspect created mapping. The *ephemeral port range* is
|
||||||
port a given service was mapped to.
|
configured by `/proc/sys/net/ipv4/ip_local_port_range` kernel parameter,
|
||||||
|
typically ranging from 32768 to 61000.
|
||||||
|
|
||||||
More convenient is the `-p SPEC` or `--publish=SPEC` option which lets
|
Mapping can be specified explicitly using `-p SPEC` or `--publish=SPEC` option.
|
||||||
you be explicit about exactly which external port on the Docker server —
|
It allows you to particularize which port on docker server - which can be any
|
||||||
which can be any port at all, not just those in the 49153-65535 block —
|
port at all, not just one within the *ephemeral port range* — you want mapped
|
||||||
you want mapped to which port in the container.
|
to which port in the container.
|
||||||
|
|
||||||
Either way, you should be able to peek at what Docker has accomplished
|
Either way, you should be able to peek at what Docker has accomplished
|
||||||
in your network stack by examining your NAT tables.
|
in your network stack by examining your NAT tables.
|
||||||
|
|
|
@ -635,10 +635,11 @@ developer, the operator has three choices: start the server container
|
||||||
with `-P` or `-p,` or start the client container with `--link`.
|
with `-P` or `-p,` or start the client container with `--link`.
|
||||||
|
|
||||||
If the operator uses `-P` or `-p` then Docker will make the exposed port
|
If the operator uses `-P` or `-p` then Docker will make the exposed port
|
||||||
accessible on the host and the ports will be available to any client
|
accessible on the host and the ports will be available to any client that can
|
||||||
that can reach the host. When using `-P`, Docker will bind the exposed
|
reach the host. When using `-P`, Docker will bind the exposed port to a random
|
||||||
ports to a random port on the host between 49153 and 65535. To find the
|
port on the host within an *ephemeral port range* defined by
|
||||||
mapping between the host ports and the exposed ports, use `docker port`.
|
`/proc/sys/net/ipv4/ip_local_port_range`. To find the mapping between the host
|
||||||
|
ports and the exposed ports, use `docker port`.
|
||||||
|
|
||||||
If the operator uses `--link` when starting the new client container,
|
If the operator uses `--link` when starting the new client container,
|
||||||
then the client container can access the exposed port via a private
|
then the client container can access the exposed port via a private
|
||||||
|
|
|
@ -25,10 +25,10 @@ container that ran a Python Flask application:
|
||||||
> Docker can have a variety of network configurations. You can see more
|
> Docker can have a variety of network configurations. You can see more
|
||||||
> information on Docker networking [here](/articles/networking/).
|
> information on Docker networking [here](/articles/networking/).
|
||||||
|
|
||||||
When that container was created, the `-P` flag was used to automatically map any
|
When that container was created, the `-P` flag was used to automatically map
|
||||||
network ports inside it to a random high port from the range 49153
|
any network port inside it to a random high port within an *ephemeral port
|
||||||
to 65535 on our Docker host. Next, when `docker ps` was run, you saw that
|
range* on your Docker host. Next, when `docker ps` was run, you saw that port
|
||||||
port 5000 in the container was bound to port 49155 on the host.
|
5000 in the container was bound to port 49155 on the host.
|
||||||
|
|
||||||
$ sudo docker ps nostalgic_morse
|
$ sudo docker ps nostalgic_morse
|
||||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||||
|
|
|
@ -154,11 +154,11 @@ ports exposed in our image to our host.
|
||||||
In this case Docker has exposed port 5000 (the default Python Flask
|
In this case Docker has exposed port 5000 (the default Python Flask
|
||||||
port) on port 49155.
|
port) on port 49155.
|
||||||
|
|
||||||
Network port bindings are very configurable in Docker. In our last
|
Network port bindings are very configurable in Docker. In our last example the
|
||||||
example the `-P` flag is a shortcut for `-p 5000` that maps port 5000
|
`-P` flag is a shortcut for `-p 5000` that maps port 5000 inside the container
|
||||||
inside the container to a high port (from the range 49153 to 65535) on
|
to a high port (from *ephemeral port range* which typically ranges from 32768
|
||||||
the local Docker host. We can also bind Docker containers to specific
|
to 61000) on the local Docker host. We can also bind Docker containers to
|
||||||
ports using the `-p` flag, for example:
|
specific ports using the `-p` flag, for example:
|
||||||
|
|
||||||
$ sudo docker run -d -p 5000:5000 training/webapp python app.py
|
$ sudo docker run -d -p 5000:5000 training/webapp python app.py
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue