Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions compose/cli/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ def project_from_options(base_dir, options):
base_dir,
get_config_path(options.get('--file')),
project_name=options.get('--project-name'),
verbose=options.get('--verbose'))
verbose=options.get('--verbose'),
use_networking=options.get('--x-networking'),
network_driver=options.get('--x-network-driver'),
)


def get_config_path(file_option):
Expand All @@ -76,14 +79,18 @@ def get_client(verbose=False):
return client


def get_project(base_dir, config_path=None, project_name=None, verbose=False):
def get_project(base_dir, config_path=None, project_name=None, verbose=False,
use_networking=False, network_driver=None):
config_details = config.find(base_dir, config_path)

try:
return Project.from_dicts(
get_project_name(config_details.working_dir, project_name),
config.load(config_details),
get_client(verbose=verbose))
get_client(verbose=verbose),
use_networking=use_networking,
network_driver=network_driver,
)
except ConfigError as e:
raise errors.UserError(six.text_type(e))

Expand Down
4 changes: 4 additions & 0 deletions compose/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ class TopLevelCommand(DocoptCommand):
Options:
-f, --file FILE Specify an alternate compose file (default: docker-compose.yml)
-p, --project-name NAME Specify an alternate project name (default: directory name)
--x-networking (EXPERIMENTAL) Use new Docker networking functionality.
Requires Docker 1.9 or later.
--x-network-driver DRIVER (EXPERIMENTAL) Specify a network driver (default: "bridge").
Requires Docker 1.9 or later.
--verbose Show more output
-v, --version Print version and exit

Expand Down
58 changes: 55 additions & 3 deletions compose/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,12 @@ class Project(object):
"""
A collection of services.
"""
def __init__(self, name, services, client):
def __init__(self, name, services, client, use_networking=False, network_driver=None):
self.name = name
self.services = services
self.client = client
self.use_networking = use_networking
self.network_driver = network_driver or 'bridge'

def labels(self, one_off=False):
return [
Expand All @@ -89,11 +91,15 @@ def labels(self, one_off=False):
]

@classmethod
def from_dicts(cls, name, service_dicts, client):
def from_dicts(cls, name, service_dicts, client, use_networking=False, network_driver=None):
"""
Construct a ServiceCollection from a list of dicts representing services.
"""
project = cls(name, [], client)
project = cls(name, [], client, use_networking=use_networking, network_driver=network_driver)

if use_networking:
remove_links(service_dicts)

for service_dict in sort_service_dicts(service_dicts):
links = project.get_links(service_dict)
volumes_from = project.get_volumes_from(service_dict)
Expand All @@ -103,6 +109,7 @@ def from_dicts(cls, name, service_dicts, client):
Service(
client=client,
project=name,
use_networking=use_networking,
links=links,
net=net,
volumes_from=volumes_from,
Expand Down Expand Up @@ -207,6 +214,8 @@ def get_volumes_from(self, service_dict):
def get_net(self, service_dict):
net = service_dict.pop('net', None)
if not net:
if self.use_networking:
return Net(self.name)
return Net(None)

net_name = get_service_name_from_net(net)
Expand Down Expand Up @@ -289,6 +298,9 @@ def up(self,

plans = self._get_convergence_plans(services, strategy)

if self.use_networking:
self.ensure_network_exists()

return [
container
for service in services
Expand Down Expand Up @@ -350,6 +362,26 @@ def matches_service_names(container):

return [c for c in containers if matches_service_names(c)]

def get_network(self):
networks = self.client.networks(names=[self.name])
if networks:
return networks[0]
return None

def ensure_network_exists(self):
# TODO: recreate network if driver has changed?
if self.get_network() is None:
log.info(
'Creating network "{}" with driver "{}"'
.format(self.name, self.network_driver)
)
self.client.create_network(self.name, driver=self.network_driver)

def remove_network(self):
network = self.get_network()
if network:
self.client.remove_network(network['id'])

def _inject_deps(self, acc, service):
dep_names = service.get_dependency_names()

Expand All @@ -365,6 +397,26 @@ def _inject_deps(self, acc, service):
return acc + dep_services


def remove_links(service_dicts):
services_with_links = [s for s in service_dicts if 'links' in s]
if not services_with_links:
return

if len(services_with_links) == 1:
prefix = '"{}" defines'.format(services_with_links[0]['name'])
else:
prefix = 'Some services ({}) define'.format(
", ".join('"{}"'.format(s['name']) for s in services_with_links))

log.warn(
'\n{} links, which are not compatible with Docker networking and will be ignored.\n'
'Future versions of Docker will not support links - you should remove them for '
'forwards-compatibility.\n'.format(prefix))

for s in services_with_links:
del s['links']


class NoSuchService(Exception):
def __init__(self, name):
self.name = name
Expand Down
5 changes: 5 additions & 0 deletions compose/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ def __init__(
name,
client=None,
project='default',
use_networking=False,
links=None,
volumes_from=None,
net=None,
Expand All @@ -123,6 +124,7 @@ def __init__(
self.name = name
self.client = client
self.project = project
self.use_networking = use_networking
self.links = links or []
self.volumes_from = volumes_from or []
self.net = net or Net(None)
Expand Down Expand Up @@ -601,6 +603,9 @@ def _get_container_create_options(
container_options['hostname'] = parts[0]
container_options['domainname'] = parts[2]

if 'hostname' not in container_options and self.use_networking:
container_options['hostname'] = self.name

if 'ports' in container_options or 'expose' in self.options:
ports = []
all_ports = container_options.get('ports', []) + self.options.get('expose', [])
Expand Down
7 changes: 2 additions & 5 deletions docs/django.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,8 @@ and a `docker-compose.yml` file.

The `docker-compose.yml` file describes the services that make your app. In
this example those services are a web server and database. The compose file
also describes which Docker images these services use, how they link
together, any volumes they might need mounted inside the containers.
Finally, the `docker-compose.yml` file describes which ports these services
also describes which Docker images these services use, any volumes they might
need mounted inside the containers, and any ports they might
expose. See the [`docker-compose.yml` reference](yml.md) for more
information on how this file works.

Expand All @@ -81,8 +80,6 @@ and a `docker-compose.yml` file.
- .:/code
ports:
- "8000:8000"
links:
- db

This file defines two services: The `db` service and the `web` service.

Expand Down
3 changes: 0 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,6 @@ Next, define a set of services using `docker-compose.yml`:
- "5000:5000"
volumes:
- .:/code
links:
- redis
redis:
image: redis

Expand All @@ -138,7 +136,6 @@ This template defines two services, `web` and `redis`. The `web` service:
* Builds from the `Dockerfile` in the current directory.
* Forwards the exposed port 5000 on the container to port 5000 on the host machine.
* Mounts the current directory on the host to `/code` inside the container allowing you to modify the code without having to rebuild the image.
* Links the web container to the Redis service.

The `redis` service uses the latest public [Redis](https://registry.hub.docker.com/_/redis/) image pulled from the Docker Hub registry.

Expand Down
84 changes: 84 additions & 0 deletions docs/networking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<!--[metadata]>
+++
title = "Networking in Compose"
description = "How Compose sets up networking between containers"
keywords = ["documentation, docs, docker, compose, orchestration, containers, networking"]
[menu.main]
parent="smn_workw_compose"
weight=6
+++
<![end-metadata]-->


# Networking in Compose

> **Note:** Compose’s networking support is experimental, and must be explicitly enabled with the `docker-compose --x-networking` flag.

Compose sets up a single default [network](http://TODO/docker-networking-docs) for your app. Each container for a service joins the default network and is both *reachable* by other containers on that network, and *discoverable* by them at a hostname identical to the service's name.

> **Note:** Your app's network is given the same name as the "project name", which is based on the name of the directory it lives in. See the [CLI docs](cli.md#p-project-name-name) for how to override it.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has there been any discussion about namespacing these like com.docker.compose.<project_name> or a little less verbose composition.<project_name> ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I would be in favour of namespacing - to most Compose users, the network name is an implementation detail that they won't spend any time looking at (unlike container names, which appear in docker ps and generally all over the place), so verbosity isn't much of a concern. com.docker.compose.* sounds good to me.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that we need to be slightly careful of how we namespace network names since there is a reasonable chance they may end up featuring in service discovery namespacing at some point in the future. e.g. myapp.mynetwork.mycompany.com as a DNS entry (with the DNS search path including .mynetwork.mycompany.com by default for myapp's containers, so a simple app doesn't need to worry about this namespacing).


For example, suppose your app is in a directory called `myapp`, and your `docker-compose.yml` looks like this:

web:
build: .
ports:
- "8000:8000"
db:
image: postgres

When you run `docker-compose --x-networking up`, the following happens:

1. A network called `myapp` is created.
2. A container is created using `web`'s configuration. It joins the network `myapp` under the name `web`.
3. A container is created using `db`'s configuration. It joins the network `myapp` under the name `db`.

Each container can now look up the hostname `web` or `db` and get back the appropriate container's IP address. For example, `web`'s application code could connect to the URL `postgres://db:5432` and start using the Postgres database.

Because `web` explicitly maps a port, it's also accessible from the outside world via port 8000 on your Docker host's network interface.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 1.8 timeframe is it likely that ports will be supported by libnetwork or the plugin multi-host network drivers will support this? So is this really just an option when using the bridge network? If so then in the short term I think it would be worth tweaking the wording to finesse this. e.g. "When testing on a single host system using the bridge network you can..." or similar.

Longer term I think ports could map sensibly to plug-in drivers where NAT is required at the boundary of the network. The drivers could implement this in a variety of ways depending on their architecture. e.g. either via specific NAT device, or via fully distributed NAT similar to what K8s does today, or any other way that fits with the driver. (This would need some careful consideration though. Is it a network driver issue? Or when we have service discovery drivers is it a service discovery driver issue? Or will there be separate load balancer drivers? I suggest this is all for discussion later way beyond the scope of this first version of this document.)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(cc @dave-tucker)

Does this mean that the web container needs to be both on the bridge network and the app-specific network? If so, will Compose need to specify that, or will everything join bridge by default?

Also, given the "container in multiple networks" problem won't be solved generically for the first release, would this need to be a specially-handled case on the Engine side?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dave-tucker might be best placed to comment on what will be implemented in 1.8 timeframe.

My best guess is:

  • Plugin network drivers will not support ports. (They are not currently passed over the libnetwork API, and adding this in the "right way" will need a reasonable amount of thought and community consensus building.)
  • The bridge driver will continue to support ports.
  • Plugin network drivers will continue to support their own mechanisms for providing NAT style function if it features in their solution. For example, Weave have weave export amongst other things.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That all makes sense. I just want to ensure the dev use case still works: web needs to be able to talk to db (so it needs to be on the app's network), but it also needs to talk to the outside world (so it needs to be on the bridge network).

My understanding is that those are two separate networks, and so it needs to be able to join both.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll let @dave-tucker comment, but having a container join two networks was one of the scenarios we were trying to avoid to 1.8 (because it needs a decent amount of thought to get right across the broad range of plugin network drivers). So I was expecting the ports directive would only do anything if the network you've specified is the bridge network (either because that's what is set up as the default network, or because you explicitly specified that). For the plugin drivers (via libnetwork) the ports directive would be ignored and it's up to the user to additionally issue whatever commands required (if any) to make web accessible from the outside world (e.g. weave expose or similar in the case of Weave).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification. Since this document doesn't go as far as to mention network drivers (or multi-host) at all, I think it's fair to leave this sentence as is. Perhaps there should be another section about "Running an app on multiple hosts", which both explains the driver architecture and points out its limitations re: ports.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A design for handling -p has been discussed in significant detail. It does not require changing the driver API.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dave-tucker Let me double check. (I based my comments about the port option not being passed to remote drivers on what someone had told me. I didn't check the code myself.)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dave-tucker It looks like it is in the API now. (It wasn't when we first started writing the Calico driver, but was added some time later, probably pre DockerCon but hard to say.) I think that means in theory remote drivers could implement the port mapping. I don't have a feel for how many have or how people may have interpreted any requirement to do so. There seems to be little documentation around what libnetwork is expecting drivers to do with the parameter (and no comments in the code either) so your guess is as good as mine. I think the fact you say it doesn't currently work in multi-host and are proposing to silently attach to the bridge also is an indication greater clarity in this area would be helpful. (I'm slightly hesitant about endorsing the silently connecting to the bridge workaround without understanding more about exactly how this is intended to work. e.g. How is the container's namespace configured to ensure traffic goes down the right interface etc.) Perhaps the discussion @shykes refers to is somewhere public and will help clarify for those of us who aren't already familiar with it?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should probably reiterate, that I do see the port mapping option as being important for a range of drivers and deployment scenarios. (For example, I quite like Kubernetes approach of allowing every host to act as a port mapping redirect. It makes a lot of sense to me in public cloud deployments given the current limitations of cloud APIs.) I'm just trying to clarify what is supported today vs what is longer term, and how the feature is intended to behave longer term.


## Updating containers

If you make a configuration change to a service and run `docker-compose up` to update it, the old container will be removed and the new one will join the network under a different IP address but the same name. Running containers will be able to look up that name and connect to the new address, but the old address will stop working.

If any containers have connections open to the old container, they will be closed. It is a container's responsibility to detect this condition, look up the name again and reconnect.

## Configure how services are published

By default, containers for each service are published on the network with the same name as the service. If you want to change the name, or stop containers from being discoverable at all, you can use the `hostname` option:

web:
build: .
hostname: "my-web-application"

This will also change the hostname inside the container, so that the `hostname` command will return `my-web-application`.

## Scaling services

If you create multiple containers for a service with `docker-compose scale`, each container will join the network with the same name. For example, if you run `docker-compose scale web=3`, then 3 containers will join the network under the name `web`. Inside any container on the network, looking up the name `web` will return the IP address of one of them, but Docker and Compose do not provide any guarantees about which one.

This limitation will be addressed in a future version of Compose, where a load balancer will join under the service name and balance traffic between the service's containers in a configurable manner.

## Links

Docker links are a one-way, single-host communication system. They should now be considered deprecated, and you should update your app to use networking instead. In the majority of cases, this will simply involve removing the `links` sections from your `docker-compose.yml`.

## Specifying the network driver

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this section should focus on providing the flexibility to run multiple apps in a single network. For example, docker compose up -d --net=<network> (and any equivalents of that which might feature directly within the Compose file if we want to support specifying networks in the Compose file).

I don't think support for containers connecting to multiple networks in 1.8 timeframe is realistic. I think there are broad range of libnetwork, network driver and service discovery interaction and implementation issues that need to be resolved to make that work sensibly. I suggest restricting each container to a single network for 1.8.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. Everything works neatly in the example given, but it's easy to construct a scenario which is not so clear; for instance if there are containers published as foo in both networks, what answer should web get if it asks for foo? (You could answer "just don't do that" I suppose, but it would be better to come up with a model that avoids such an ambiguity).

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I'm just going to remove this section for now.

By default, Compose uses the `bridge` driver when creating the app’s network. The Docker Engine provides one other driver out-of-the-box: `overlay`, which implements secure communication between containers on different hosts (see the next section for how to set up and use the `overlay` driver). Docker also allows you to install [custom network drivers](http://TODO/custom-driver-docs).

You can specify which one to use with the `--x-network-driver` flag:

$ docker-compose --x-networking --x-network-driver=overlay up

## Multi-host networking

(TODO: talk about Swarm and the overlay driver)

## Custom container network modes

Compose allows you to specify a custom network mode for a service with the `net` option - for example, `net: "host"` specifies that its containers should use the same network namespace as the Docker host, and `net: "none"` specifies that they should have no networking capabilities.

If a service specifies the `net` option, its containers will *not* join the app’s network and will not be able to communicate with other services in the app.

If *all* services in an app specify the `net` option, a network will not be created at all.
4 changes: 1 addition & 3 deletions docs/rails.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Next, create a bootstrap `Gemfile` which just loads Rails. It'll be overwritten
source 'https://rubygems.org'
gem 'rails', '4.2.0'

Finally, `docker-compose.yml` is where the magic happens. This file describes the services that comprise your app (a database and a web app), how to get each one's Docker image (the database just runs on a pre-made PostgreSQL image, and the web app is built from the current directory), and the configuration needed to link them together and expose the web app's port.
Finally, `docker-compose.yml` is where the magic happens. This file describes the services that comprise your app (a database and a web app), how to get each one's Docker image (the database just runs on a pre-made PostgreSQL image, and the web app is built from the current directory), and the configuration needed to expose the web app's port.

db:
image: postgres
Expand All @@ -48,8 +48,6 @@ Finally, `docker-compose.yml` is where the magic happens. This file describes th
- .:/myapp
ports:
- "3000:3000"
links:
- db

### Build the project

Expand Down
2 changes: 0 additions & 2 deletions docs/wordpress.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ and a separate MySQL instance:
command: php -S 0.0.0.0:8000 -t /code
ports:
- "8000:8000"
links:
- db
volumes:
- .:/code
db:
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
PyYAML==3.10
docker-py==1.4.0
dockerpty==0.3.4
docopt==0.6.1
enum34==1.0.4
git+https://github.com/aanand/docker-py.git@1df3226c9bd9d2e8ff79d5929741c1e8f1a92b0d#egg=docker-py
jsonschema==2.5.1
requests==2.7.0
six==1.7.3
Expand Down
7 changes: 3 additions & 4 deletions script/build-windows.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,14 @@ Get-ChildItem -Recurse -Include *.pyc | foreach ($_) { Remove-Item $_.FullName }
virtualenv .\venv

# Install dependencies
.\venv\Scripts\pip install pypiwin32==219
.\venv\Scripts\pip install -r requirements.txt
.\venv\Scripts\pip install --no-deps .

# TODO: pip warns when installing from a git sha, so we need to set ErrorAction to
# 'Continue'. See
# https://github.com/pypa/pip/blob/fbc4b7ae5fee00f95bce9ba4b887b22681327bb1/pip/vcs/git.py#L77
# This can be removed once pyinstaller 3.x is released and we upgrade
$ErrorActionPreference = "Continue"
.\venv\Scripts\pip install pypiwin32==219
.\venv\Scripts\pip install -r requirements.txt
.\venv\Scripts\pip install --no-deps .
.\venv\Scripts\pip install --allow-external pyinstaller -r requirements-build.txt

# Build binary
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def find_version(*file_paths):
'requests >= 2.6.1, < 2.8',
'texttable >= 0.8.1, < 0.9',
'websocket-client >= 0.32.0, < 1.0',
'docker-py >= 1.4.0, < 2',
'docker-py >= 1.5.0-dev, < 2',
'dockerpty >= 0.3.4, < 0.4',
'six >= 1.3.0, < 2',
'jsonschema >= 2.5.1, < 3',
Expand Down
Loading