mirror of https://github.com/docker/compose.git
Merge pull request #3011 from mdaue/2804
Fix #2804: Add ipv4 and ipv6 static addressing
This commit is contained in:
commit
99d68be815
|
@ -152,7 +152,9 @@
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"aliases": {"$ref": "#/definitions/list_of_strings"}
|
"aliases": {"$ref": "#/definitions/list_of_strings"},
|
||||||
|
"ipv4_address": {"type": "string"},
|
||||||
|
"ipv6_address": {"type": "string"}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
|
|
@ -159,26 +159,26 @@ class ProjectNetworks(object):
|
||||||
network.ensure()
|
network.ensure()
|
||||||
|
|
||||||
|
|
||||||
def get_network_aliases_for_service(service_dict):
|
def get_network_defs_for_service(service_dict):
|
||||||
if 'network_mode' in service_dict:
|
if 'network_mode' in service_dict:
|
||||||
return {}
|
return {}
|
||||||
networks = service_dict.get('networks', {'default': None})
|
networks = service_dict.get('networks', {'default': None})
|
||||||
return dict(
|
return dict(
|
||||||
(net, (config or {}).get('aliases', []))
|
(net, (config or {}))
|
||||||
for net, config in networks.items()
|
for net, config in networks.items()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_network_names_for_service(service_dict):
|
def get_network_names_for_service(service_dict):
|
||||||
return get_network_aliases_for_service(service_dict).keys()
|
return get_network_defs_for_service(service_dict).keys()
|
||||||
|
|
||||||
|
|
||||||
def get_networks(service_dict, network_definitions):
|
def get_networks(service_dict, network_definitions):
|
||||||
networks = {}
|
networks = {}
|
||||||
for name, aliases in get_network_aliases_for_service(service_dict).items():
|
for name, netdef in get_network_defs_for_service(service_dict).items():
|
||||||
network = network_definitions.get(name)
|
network = network_definitions.get(name)
|
||||||
if network:
|
if network:
|
||||||
networks[network.full_name] = aliases
|
networks[network.full_name] = netdef
|
||||||
else:
|
else:
|
||||||
raise ConfigurationError(
|
raise ConfigurationError(
|
||||||
'Service "{}" uses an undefined network "{}"'
|
'Service "{}" uses an undefined network "{}"'
|
||||||
|
|
|
@ -451,7 +451,10 @@ class Service(object):
|
||||||
def connect_container_to_networks(self, container):
|
def connect_container_to_networks(self, container):
|
||||||
connected_networks = container.get('NetworkSettings.Networks')
|
connected_networks = container.get('NetworkSettings.Networks')
|
||||||
|
|
||||||
for network, aliases in self.networks.items():
|
for network, netdefs in self.networks.items():
|
||||||
|
aliases = netdefs.get('aliases', [])
|
||||||
|
ipv4_address = netdefs.get('ipv4_address', None)
|
||||||
|
ipv6_address = netdefs.get('ipv6_address', None)
|
||||||
if network in connected_networks:
|
if network in connected_networks:
|
||||||
self.client.disconnect_container_from_network(
|
self.client.disconnect_container_from_network(
|
||||||
container.id, network)
|
container.id, network)
|
||||||
|
@ -459,7 +462,9 @@ class Service(object):
|
||||||
self.client.connect_container_to_network(
|
self.client.connect_container_to_network(
|
||||||
container.id, network,
|
container.id, network,
|
||||||
aliases=list(self._get_aliases(container).union(aliases)),
|
aliases=list(self._get_aliases(container).union(aliases)),
|
||||||
links=self._get_links(False),
|
ipv4_address=ipv4_address,
|
||||||
|
ipv6_address=ipv6_address,
|
||||||
|
links=self._get_links(False)
|
||||||
)
|
)
|
||||||
|
|
||||||
def remove_duplicate_containers(self, timeout=DEFAULT_TIMEOUT):
|
def remove_duplicate_containers(self, timeout=DEFAULT_TIMEOUT):
|
||||||
|
|
|
@ -116,6 +116,30 @@ Here's an example Compose file defining two custom networks. The `proxy` service
|
||||||
foo: "1"
|
foo: "1"
|
||||||
bar: "2"
|
bar: "2"
|
||||||
|
|
||||||
|
Networks can be configured with static IP addresses by setting the ipv4_address and/or ipv6_address for each attached network. The corresponding `network` section must have an `ipam` config entry with subnet and gateway configurations for each static address. If IPv6 addressing is desired, the `com.docker.network.enable_ipv6` driver option must be set to `true`. An example:
|
||||||
|
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
networks:
|
||||||
|
app_net:
|
||||||
|
ipv4_address: 172.16.238.10
|
||||||
|
ipv6_address: 2001:3984:3989::10
|
||||||
|
|
||||||
|
networks:
|
||||||
|
app_net:
|
||||||
|
driver: bridge
|
||||||
|
driver_opts:
|
||||||
|
com.docker.network.enable_ipv6: "true"
|
||||||
|
ipam:
|
||||||
|
driver: default
|
||||||
|
config:
|
||||||
|
- subnet: 172.16.238.0/24
|
||||||
|
gateway: 172.16.238.1
|
||||||
|
- subnet: 2001:3984:3989::/64
|
||||||
|
gateway: 2001:3984:3989::1
|
||||||
|
|
||||||
For full details of the network configuration options available, see the following references:
|
For full details of the network configuration options available, see the following references:
|
||||||
|
|
||||||
- [Top-level `networks` key](compose-file.md#network-configuration-reference)
|
- [Top-level `networks` key](compose-file.md#network-configuration-reference)
|
||||||
|
|
|
@ -3,7 +3,7 @@ cached-property==1.2.0
|
||||||
dockerpty==0.4.1
|
dockerpty==0.4.1
|
||||||
docopt==0.6.1
|
docopt==0.6.1
|
||||||
enum34==1.0.4
|
enum34==1.0.4
|
||||||
git+https://github.com/docker/docker-py.git@81d8caaf36159bf1accd86eab2e157bf8dd071a9#egg=docker-py
|
git+https://github.com/docker/docker-py.git@d8be3e0fce60fbe25be088b64bccbcee83effdb1#egg=docker-py
|
||||||
jsonschema==2.5.1
|
jsonschema==2.5.1
|
||||||
requests==2.7.0
|
requests==2.7.0
|
||||||
six==1.7.3
|
six==1.7.3
|
||||||
|
|
|
@ -475,6 +475,30 @@ class CLITestCase(DockerClientTestCase):
|
||||||
assert 'forward_facing' in front_aliases
|
assert 'forward_facing' in front_aliases
|
||||||
assert 'ahead' in front_aliases
|
assert 'ahead' in front_aliases
|
||||||
|
|
||||||
|
@v2_only()
|
||||||
|
def test_up_with_network_static_addresses(self):
|
||||||
|
filename = 'network-static-addresses.yml'
|
||||||
|
ipv4_address = '172.16.100.100'
|
||||||
|
ipv6_address = 'fe80::1001:100'
|
||||||
|
self.base_dir = 'tests/fixtures/networks'
|
||||||
|
self.dispatch(['-f', filename, 'up', '-d'], None)
|
||||||
|
static_net = '{}_static_test'.format(self.project.name)
|
||||||
|
|
||||||
|
networks = [
|
||||||
|
n for n in self.client.networks()
|
||||||
|
if n['Name'].startswith('{}_'.format(self.project.name))
|
||||||
|
]
|
||||||
|
|
||||||
|
# One networks was created: front
|
||||||
|
assert sorted(n['Name'] for n in networks) == [static_net]
|
||||||
|
web_container = self.project.get_service('web').containers()[0]
|
||||||
|
|
||||||
|
ipam_config = web_container.get(
|
||||||
|
'NetworkSettings.Networks.{}.IPAMConfig'.format(static_net)
|
||||||
|
)
|
||||||
|
assert ipv4_address in ipam_config.values()
|
||||||
|
assert ipv6_address in ipam_config.values()
|
||||||
|
|
||||||
@v2_only()
|
@v2_only()
|
||||||
def test_up_with_networks(self):
|
def test_up_with_networks(self):
|
||||||
self.base_dir = 'tests/fixtures/networks'
|
self.base_dir = 'tests/fixtures/networks'
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
version: "2"
|
||||||
|
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
image: busybox
|
||||||
|
command: top
|
||||||
|
networks:
|
||||||
|
static_test:
|
||||||
|
ipv4_address: 172.16.100.100
|
||||||
|
ipv6_address: fe80::1001:100
|
||||||
|
|
||||||
|
networks:
|
||||||
|
static_test:
|
||||||
|
driver: bridge
|
||||||
|
driver_opts:
|
||||||
|
com.docker.network.enable_ipv6: "true"
|
||||||
|
ipam:
|
||||||
|
driver: default
|
||||||
|
config:
|
||||||
|
- subnet: 172.16.100.0/24
|
||||||
|
gateway: 172.16.100.1
|
||||||
|
- subnet: fe80::/64
|
||||||
|
gateway: fe80::1001:1
|
|
@ -5,6 +5,7 @@ import random
|
||||||
|
|
||||||
import py
|
import py
|
||||||
import pytest
|
import pytest
|
||||||
|
from docker.errors import APIError
|
||||||
from docker.errors import NotFound
|
from docker.errors import NotFound
|
||||||
|
|
||||||
from ..helpers import build_config
|
from ..helpers import build_config
|
||||||
|
@ -650,6 +651,96 @@ class ProjectTest(DockerClientTestCase):
|
||||||
}],
|
}],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@v2_only()
|
||||||
|
def test_up_with_network_static_addresses(self):
|
||||||
|
config_data = config.Config(
|
||||||
|
version=V2_0,
|
||||||
|
services=[{
|
||||||
|
'name': 'web',
|
||||||
|
'image': 'busybox:latest',
|
||||||
|
'networks': {
|
||||||
|
'static_test': {
|
||||||
|
'ipv4_address': '172.16.100.100',
|
||||||
|
'ipv6_address': 'fe80::1001:102'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
volumes={},
|
||||||
|
networks={
|
||||||
|
'static_test': {
|
||||||
|
'driver': 'bridge',
|
||||||
|
'driver_opts': {
|
||||||
|
"com.docker.network.enable_ipv6": "true",
|
||||||
|
},
|
||||||
|
'ipam': {
|
||||||
|
'driver': 'default',
|
||||||
|
'config': [
|
||||||
|
{"subnet": "172.16.100.0/24",
|
||||||
|
"gateway": "172.16.100.1"},
|
||||||
|
{"subnet": "fe80::/64",
|
||||||
|
"gateway": "fe80::1001:1"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
project = Project.from_config(
|
||||||
|
client=self.client,
|
||||||
|
name='composetest',
|
||||||
|
config_data=config_data,
|
||||||
|
)
|
||||||
|
project.up()
|
||||||
|
|
||||||
|
network = self.client.networks(names=['static_test'])[0]
|
||||||
|
service_container = project.get_service('web').containers()[0]
|
||||||
|
|
||||||
|
assert network['Options'] == {
|
||||||
|
"com.docker.network.enable_ipv6": "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAMConfig = (service_container.inspect().get('NetworkSettings', {}).
|
||||||
|
get('Networks', {}).get('composetest_static_test', {}).
|
||||||
|
get('IPAMConfig', {}))
|
||||||
|
assert IPAMConfig.get('IPv4Address') == '172.16.100.100'
|
||||||
|
assert IPAMConfig.get('IPv6Address') == 'fe80::1001:102'
|
||||||
|
|
||||||
|
@v2_only()
|
||||||
|
def test_up_with_network_static_addresses_missing_subnet(self):
|
||||||
|
config_data = config.Config(
|
||||||
|
version=V2_0,
|
||||||
|
services=[{
|
||||||
|
'name': 'web',
|
||||||
|
'image': 'busybox:latest',
|
||||||
|
'networks': {
|
||||||
|
'static_test': {
|
||||||
|
'ipv4_address': '172.16.100.100',
|
||||||
|
'ipv6_address': 'fe80::1001:101'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
volumes={},
|
||||||
|
networks={
|
||||||
|
'static_test': {
|
||||||
|
'driver': 'bridge',
|
||||||
|
'driver_opts': {
|
||||||
|
"com.docker.network.enable_ipv6": "true",
|
||||||
|
},
|
||||||
|
'ipam': {
|
||||||
|
'driver': 'default',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
project = Project.from_config(
|
||||||
|
client=self.client,
|
||||||
|
name='composetest',
|
||||||
|
config_data=config_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
with self.assertRaises(APIError):
|
||||||
|
project.up()
|
||||||
|
|
||||||
@v2_only()
|
@v2_only()
|
||||||
def test_project_up_volumes(self):
|
def test_project_up_volumes(self):
|
||||||
vol_name = '{0:x}'.format(random.getrandbits(32))
|
vol_name = '{0:x}'.format(random.getrandbits(32))
|
||||||
|
|
Loading…
Reference in New Issue