mirror of
https://github.com/docker/compose.git
synced 2025-07-30 00:54:19 +02:00
commit
ed31673069
@ -10,6 +10,7 @@ from operator import attrgetter
|
|||||||
import six
|
import six
|
||||||
from docker.errors import APIError
|
from docker.errors import APIError
|
||||||
from docker.utils import create_host_config, LogConfig
|
from docker.utils import create_host_config, LogConfig
|
||||||
|
from docker.utils.ports import build_port_bindings, split_port
|
||||||
|
|
||||||
from . import __version__
|
from . import __version__
|
||||||
from .config import DOCKER_CONFIG_KEYS, merge_environment
|
from .config import DOCKER_CONFIG_KEYS, merge_environment
|
||||||
@ -595,13 +596,13 @@ class Service(object):
|
|||||||
if 'ports' in container_options or 'expose' in self.options:
|
if 'ports' in container_options or 'expose' in self.options:
|
||||||
ports = []
|
ports = []
|
||||||
all_ports = container_options.get('ports', []) + self.options.get('expose', [])
|
all_ports = container_options.get('ports', []) + self.options.get('expose', [])
|
||||||
for port in all_ports:
|
for port_range in all_ports:
|
||||||
port = str(port)
|
internal_range, _ = split_port(port_range)
|
||||||
if ':' in port:
|
for port in internal_range:
|
||||||
port = port.split(':')[-1]
|
port = str(port)
|
||||||
if '/' in port:
|
if '/' in port:
|
||||||
port = tuple(port.split('/'))
|
port = tuple(port.split('/'))
|
||||||
ports.append(port)
|
ports.append(port)
|
||||||
container_options['ports'] = ports
|
container_options['ports'] = ports
|
||||||
|
|
||||||
override_options['binds'] = merge_volume_bindings(
|
override_options['binds'] = merge_volume_bindings(
|
||||||
@ -857,38 +858,6 @@ def parse_volume_spec(volume_config):
|
|||||||
|
|
||||||
return VolumeSpec(external, internal, mode)
|
return VolumeSpec(external, internal, mode)
|
||||||
|
|
||||||
|
|
||||||
# Ports
|
|
||||||
|
|
||||||
|
|
||||||
def build_port_bindings(ports):
|
|
||||||
port_bindings = {}
|
|
||||||
for port in ports:
|
|
||||||
internal_port, external = split_port(port)
|
|
||||||
if internal_port in port_bindings:
|
|
||||||
port_bindings[internal_port].append(external)
|
|
||||||
else:
|
|
||||||
port_bindings[internal_port] = [external]
|
|
||||||
return port_bindings
|
|
||||||
|
|
||||||
|
|
||||||
def split_port(port):
|
|
||||||
parts = str(port).split(':')
|
|
||||||
if not 1 <= len(parts) <= 3:
|
|
||||||
raise ConfigError('Invalid port "%s", should be '
|
|
||||||
'[[remote_ip:]remote_port:]port[/protocol]' % port)
|
|
||||||
|
|
||||||
if len(parts) == 1:
|
|
||||||
internal_port, = parts
|
|
||||||
return internal_port, None
|
|
||||||
if len(parts) == 2:
|
|
||||||
external_port, internal_port = parts
|
|
||||||
return internal_port, external_port
|
|
||||||
|
|
||||||
external_ip, external_port, internal_port = parts
|
|
||||||
return internal_port, (external_ip, external_port or None)
|
|
||||||
|
|
||||||
|
|
||||||
# Labels
|
# Labels
|
||||||
|
|
||||||
|
|
||||||
|
35
docs/yml.md
35
docs/yml.md
@ -105,19 +105,41 @@ An entry with the ip address and hostname will be created in `/etc/hosts` inside
|
|||||||
|
|
||||||
### ports
|
### ports
|
||||||
|
|
||||||
Expose ports. Either specify both ports (`HOST:CONTAINER`), or just the container
|
Makes an exposed port accessible on a host and the port is available to
|
||||||
port (a random host port will be chosen).
|
any client that can reach that host. Docker binds the exposed port to a random
|
||||||
|
port on the host within an *ephemeral port range* defined by
|
||||||
|
`/proc/sys/net/ipv4/ip_local_port_range`. You can also map to a specific port or range of ports.
|
||||||
|
|
||||||
> **Note:** When mapping ports in the `HOST:CONTAINER` format, you may experience
|
Acceptable formats for the `ports` value are:
|
||||||
> erroneous results when using a container port lower than 60, because YAML will
|
|
||||||
> parse numbers in the format `xx:yy` as sexagesimal (base 60). For this reason,
|
* `containerPort`
|
||||||
> we recommend always explicitly specifying your port mappings as strings.
|
* `ip:hostPort:containerPort`
|
||||||
|
* `ip::containerPort`
|
||||||
|
* `hostPort:containerPort`
|
||||||
|
|
||||||
|
You can specify a range for both the `hostPort` and the `containerPort` values.
|
||||||
|
When specifying ranges, the container port values in the range must match the
|
||||||
|
number of host port values in the range, for example,
|
||||||
|
`1234-1236:1234-1236/tcp`. Once a host is running, use the 'docker-compose port' command
|
||||||
|
to see the actual mapping.
|
||||||
|
|
||||||
|
The following configuration shows examples of the port formats in use:
|
||||||
|
|
||||||
ports:
|
ports:
|
||||||
- "3000"
|
- "3000"
|
||||||
|
- "3000-3005"
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
|
- "9090-9091:8080-8081"
|
||||||
- "49100:22"
|
- "49100:22"
|
||||||
- "127.0.0.1:8001:8001"
|
- "127.0.0.1:8001:8001"
|
||||||
|
- "127.0.0.1:5000-5010:5000-5010"
|
||||||
|
|
||||||
|
|
||||||
|
When mapping ports, in the `hostPort:containerPort` format, you may
|
||||||
|
experience erroneous results when using a container port lower than 60. This
|
||||||
|
happens because YAML parses numbers in the format `xx:yy` as sexagesimal (base
|
||||||
|
60). To avoid this problem, always explicitly specify your port
|
||||||
|
mappings as strings.
|
||||||
|
|
||||||
### expose
|
### expose
|
||||||
|
|
||||||
@ -411,3 +433,4 @@ dollar sign (`$$`).
|
|||||||
- [Command line reference](/reference)
|
- [Command line reference](/reference)
|
||||||
- [Compose environment variables](env.md)
|
- [Compose environment variables](env.md)
|
||||||
- [Compose command line completion](completion.md)
|
- [Compose command line completion](completion.md)
|
||||||
|
|
||||||
|
@ -5,3 +5,4 @@ simple:
|
|||||||
ports:
|
ports:
|
||||||
- '3000'
|
- '3000'
|
||||||
- '49152:3001'
|
- '49152:3001'
|
||||||
|
- '49153-49154:3002-3003'
|
||||||
|
@ -334,6 +334,7 @@ class CLITestCase(DockerClientTestCase):
|
|||||||
# get port information
|
# get port information
|
||||||
port_random = container.get_local_port(3000)
|
port_random = container.get_local_port(3000)
|
||||||
port_assigned = container.get_local_port(3001)
|
port_assigned = container.get_local_port(3001)
|
||||||
|
port_range = container.get_local_port(3002), container.get_local_port(3003)
|
||||||
|
|
||||||
# close all one off containers we just created
|
# close all one off containers we just created
|
||||||
container.stop()
|
container.stop()
|
||||||
@ -342,6 +343,8 @@ class CLITestCase(DockerClientTestCase):
|
|||||||
self.assertNotEqual(port_random, None)
|
self.assertNotEqual(port_random, None)
|
||||||
self.assertIn("0.0.0.0", port_random)
|
self.assertIn("0.0.0.0", port_random)
|
||||||
self.assertEqual(port_assigned, "0.0.0.0:49152")
|
self.assertEqual(port_assigned, "0.0.0.0:49152")
|
||||||
|
self.assertEqual(port_range[0], "0.0.0.0:49153")
|
||||||
|
self.assertEqual(port_range[1], "0.0.0.0:49154")
|
||||||
|
|
||||||
def test_rm(self):
|
def test_rm(self):
|
||||||
service = self.project.get_service('simple')
|
service = self.project.get_service('simple')
|
||||||
@ -456,7 +459,7 @@ class CLITestCase(DockerClientTestCase):
|
|||||||
|
|
||||||
self.assertEqual(get_port(3000), container.get_local_port(3000))
|
self.assertEqual(get_port(3000), container.get_local_port(3000))
|
||||||
self.assertEqual(get_port(3001), "0.0.0.0:49152")
|
self.assertEqual(get_port(3001), "0.0.0.0:49152")
|
||||||
self.assertEqual(get_port(3002), "")
|
self.assertEqual(get_port(3002), "0.0.0.0:49153")
|
||||||
|
|
||||||
def test_port_with_scale(self):
|
def test_port_with_scale(self):
|
||||||
|
|
||||||
|
@ -14,13 +14,11 @@ from compose.service import (
|
|||||||
ConfigError,
|
ConfigError,
|
||||||
NeedsBuildError,
|
NeedsBuildError,
|
||||||
NoSuchImageError,
|
NoSuchImageError,
|
||||||
build_port_bindings,
|
|
||||||
build_volume_binding,
|
build_volume_binding,
|
||||||
get_container_data_volumes,
|
get_container_data_volumes,
|
||||||
merge_volume_bindings,
|
merge_volume_bindings,
|
||||||
parse_repository_tag,
|
parse_repository_tag,
|
||||||
parse_volume_spec,
|
parse_volume_spec,
|
||||||
split_port,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -100,48 +98,6 @@ class ServiceTest(unittest.TestCase):
|
|||||||
self.assertEqual(service._get_volumes_from(), [container_id])
|
self.assertEqual(service._get_volumes_from(), [container_id])
|
||||||
from_service.create_container.assert_called_once_with()
|
from_service.create_container.assert_called_once_with()
|
||||||
|
|
||||||
def test_split_port_with_host_ip(self):
|
|
||||||
internal_port, external_port = split_port("127.0.0.1:1000:2000")
|
|
||||||
self.assertEqual(internal_port, "2000")
|
|
||||||
self.assertEqual(external_port, ("127.0.0.1", "1000"))
|
|
||||||
|
|
||||||
def test_split_port_with_protocol(self):
|
|
||||||
internal_port, external_port = split_port("127.0.0.1:1000:2000/udp")
|
|
||||||
self.assertEqual(internal_port, "2000/udp")
|
|
||||||
self.assertEqual(external_port, ("127.0.0.1", "1000"))
|
|
||||||
|
|
||||||
def test_split_port_with_host_ip_no_port(self):
|
|
||||||
internal_port, external_port = split_port("127.0.0.1::2000")
|
|
||||||
self.assertEqual(internal_port, "2000")
|
|
||||||
self.assertEqual(external_port, ("127.0.0.1", None))
|
|
||||||
|
|
||||||
def test_split_port_with_host_port(self):
|
|
||||||
internal_port, external_port = split_port("1000:2000")
|
|
||||||
self.assertEqual(internal_port, "2000")
|
|
||||||
self.assertEqual(external_port, "1000")
|
|
||||||
|
|
||||||
def test_split_port_no_host_port(self):
|
|
||||||
internal_port, external_port = split_port("2000")
|
|
||||||
self.assertEqual(internal_port, "2000")
|
|
||||||
self.assertEqual(external_port, None)
|
|
||||||
|
|
||||||
def test_split_port_invalid(self):
|
|
||||||
with self.assertRaises(ConfigError):
|
|
||||||
split_port("0.0.0.0:1000:2000:tcp")
|
|
||||||
|
|
||||||
def test_build_port_bindings_with_one_port(self):
|
|
||||||
port_bindings = build_port_bindings(["127.0.0.1:1000:1000"])
|
|
||||||
self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000")])
|
|
||||||
|
|
||||||
def test_build_port_bindings_with_matching_internal_ports(self):
|
|
||||||
port_bindings = build_port_bindings(["127.0.0.1:1000:1000", "127.0.0.1:2000:1000"])
|
|
||||||
self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000"), ("127.0.0.1", "2000")])
|
|
||||||
|
|
||||||
def test_build_port_bindings_with_nonmatching_internal_ports(self):
|
|
||||||
port_bindings = build_port_bindings(["127.0.0.1:1000:1000", "127.0.0.1:2000:2000"])
|
|
||||||
self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000")])
|
|
||||||
self.assertEqual(port_bindings["2000"], [("127.0.0.1", "2000")])
|
|
||||||
|
|
||||||
def test_split_domainname_none(self):
|
def test_split_domainname_none(self):
|
||||||
service = Service('foo', image='foo', hostname='name', client=self.mock_client)
|
service = Service('foo', image='foo', hostname='name', client=self.mock_client)
|
||||||
self.mock_client.containers.return_value = []
|
self.mock_client.containers.return_value = []
|
||||||
|
Loading…
x
Reference in New Issue
Block a user