Networking

This document describes the networking API released as of Marathon 1.5.

While Marathon continues to consume the legacy ports API that was shipped in versions 1.4.x and prior, all new applications should be declared using the new, non-deprecated networking API fields that are documented here. Applications using the old networking API fields will be automatically migrated to the new networking API in Marathon 1.5.x. As of version 1.5, Marathon will only produce responses in the new 1.5 networking API format.

See the Migrating to the 1.5 Networking API for more information on changes you may need to make to your applications.

VIPs

If you are running Marathon within a DC/OS cluster, you can use virtual addresses (VIPs) to make ports management easier. VIPs simplify inter-app communication and implement a reliable service-oriented architecture. VIPs map traffic from a single virtual address to multiple IP addresses and ports.

Configuration

Several command-line flags determine Marathon’s behavior with respect to networking.

  • default_network_name is injected as the name of a container mode network when left blank by an application.
  • local_port_min and local_port_max define a port range from which Marathon automatically allocates *service-port*s.

Networking Modes

Marathon apps and pods declare networks the same way. Three networking modes are supported:

host Networking

In host networking, an application shares the network namespace of the Mesos agent process, typically the host network namespace.

container Networking

An application should be allocated its own network namespace and IP address; Mesos network isolators are responsible for providing backend support for this. When using the Docker containerizer, this translates to a Docker “user” network. Container networks are named, either explicitly by an application or else via --default_network_name. Unnamed container networks will fail to validate if --default_network_name is not specified by the operator.

container/bridge Networking

Similar to container, an application should be allocated its own network namespace and IP address; Mesos CNI provides a special mesos-bridge that application containers are attached to. When using the Docker containerizer, this translates to the Docker “default bridge” network.

Notes:

  • All network modes are supported for both the Mesos and Docker containerizers.
  • The Network.name parameter is only supported with container networking.

Usage:

  • An application can join one or more container mode networks, with caveats:
    • The Docker containerizer does not support multiple container mode networks (this limitation is imposed by Mesos).
    • When joining multiple container networks, additional restrictions are imposed on port-mapping entries (see Port Mappings for details).
  • An application can only join one host mode network. This is the default if an app definition does not declare a networks field.
  • An application can only join one container/bridge network.
  • An application cannot mix networking modes: you must either specify a single host network, a single container/bridge network, or one or more container networks.

Ports for Marathon Apps

Marathon apps declare ports differently than pods. The following section is only relevant to Marathon apps.

Terminology

Port Types

container-port:

Specifies a port within a container. containerPort is specified as a field of a port-mapping or endpoint when using container or container/bridge mode networking.

host-port:

Specifies a port to allocate from the resources offered by a Mesos agent. hostPort is specified as a field of a port-mapping or endpoint when using container or container/bridge mode networking. port is specified as a field of a port-definition when using host mode networking.

Note: Only host ports are made available to a task through environment variables.

service-port:

When you create a new application in Marathon (either through the REST API or the front end), you may assign one or more service ports to it. You can specify all valid port numbers as service ports, or you can use 0 to indicate that Marathon should allocate free service ports to the app automatically. Marathon allocates service ports from the range defined by the --local_port_min and --local_port_max command line flags. If you choose your own service port, you must ensure that it is unique across all of your applications.

See the port definition section for more information.

Note: Pods (endpoints) do not support service ports.

Ports Use Cases

The following use cases explore how you can configure port mapping to meet your needs.

You can configure 3 defined ports: containerPort, hostPort, and servicePort, as well as the label for VIP_0 in your port mapping.

All applications on Mesos are hosted in containers. Containers can run with a network configuration in bridge or host mode.

Let us assume an application called “ACME,” which opens port 8080.

Host Mode Use Case

When in host mode, the container will have the host network interface defined in the container namespace. The container will not do any port mapping and the application will open up port 8080 on the host network interface. In this case, there is no container port and the hostPort is 8080.

Bridge Mode Use Case

If the container is run in bridge mode, it gets its own network interface and the containerPort is 8080. In bridge mode, the container will open a pseudo random port on the host and bridge/NAT communication to the container port.

For example, lets say the host port is 31000. Now, the containerPort is 8080, but the hostPort is 31000. Clients connecting to this service will open port 31000 on the host the service is running on. The container will route traffic to the internal port in the container, 8080.

Multiple App Instances Use Case

Now, let’s say you have 3 instances of app ACME: 10.0.0.2:31000, 10.0.0.2:31001, and 10.0.0.3:31000. There is a common need to have a port that will route with an algorithm to each of those instances. The algorithm used is determined by the configuration of the load balancer. This “port” is metadata specific to this application. All instances of the application will be hosted behind this port.

In Marathon, this is referred to as the service port. The service port is specified in the servicePort parameter of the ACME app definition. Let us assume servicePort is specified as 8080.

The service port is metadata; Marathon does not do anything with this information except track it and provide it to a load balancer. The DevOps team setting up this service is expected to create a script or provide a means to read the servicePort and configure the load balancer to route calls to that port (port 8080 in this case) to each of the instances of the application. All metadata is queryable from Marathon.

The Marathon-LB service, when configured, does exactly this. Marathon-LB will register all instances of an app and route to its configured servicePort. Marathon-LB is an HAProxy service with scripts that will register all instances of an app and route to its configured servicePort. In the configuration in this example, a client will connect to a load balancer at port 8080 (servicePort), which will route (with an algorithm) to 10.0.0.2:31000 (hostPort), which will in turn route to 8080 (containerPort) of the internal application.

The VIP_0 Label Use Case

VIP_0, defined in portDefinitions as a label, is like servicePort in that it is informational and implementation-dependent. However, when used with DC/OS, services internal to DC/OS will make available to the cluster that DNS name and port as a route to services. An example configuration:

    app_def['portDefinitions'] = [{
        "port": 0,
        "protocol": "tcp",
        "name": "acme",
        "labels": {
            "VIP_0": "/acme:10000"
        }
    }]

This configuration will create a fully qualified domain name (FQDN) according to the following schema: <vip-name>.marathon.l4lb.thisdcos.directory:<vip-port>, which will load balance all instances of the application. Here, the FQDN would be acme.marathon.l4lb.thisdcos.directory:10000.

Marathon itself does nothing with this configuration. Marathon manages it as metadata for the application. A client in DC/OS could open a connection to acme.marathon.l4lb.thisdcos.directory at port 10000, which would route to 10.0.0.2:31000, which will in turn route to the containerPort of 8080.

Declaring ports in an application

endpoint:

Endpoints are declared only by the containers of a Pod. See the documentation for pods.

port-definition:

Port-definitions are used only with host mode networking. A port-definition (specifically its port field) is interpreted through the lens of the requirePorts app field:

  • When requirePorts is false (default), a port-definition’s port is considered the service-port and a host-port is dynamically chosen by Marathon.
  • When requirePorts is true, a port-definition’s port is considered both a host-port and service-port, otherwise;
  • The special port value of 0 tells Marathon to select any host-port from a Mesos resource offer and any service-port from the configured service port range.
port-mapping:

A port-mapping declares a container-port for an application, possibly linking that container-port to a host-port and service-port. Marathon communicates container-port/host-port links (aka “mappings”) to Mesos when launching instances of the application. Port-mappings are used with both container and container/bridge networking. Marathon ignores the value of requirePorts when interpreting a port-mapping.

  • The special containerPort value of 0 tells Marathon to internally assign the (eventually) allocated host-port to containerPort.
  • The special hostPort value of 0 tells Marathon to select any host-port from a Mesos resource offer.
  • The special servicePort value of 0 tells Marathon to select any service-port from the configured service-port range.

Port Definitions

Summary

  • Review port-definition, host-port, and service-port in Terminology.
  • Location in app definition: { "portDefinitions": [ <port-definition>... ], "requirePorts": <bool>, ... }
  • Used in conjunction with host mode networking.
  • requirePorts applies to portDefinitions.
  • If no portDefinitions are defined (or defined as null) at create-time, default to { "portDefinitions": [ { "port": 0, "name": "default" } ], ... }
    • Specify an empty array ([]) to indicate NO ports are used by the app; no default is injected in this case.
  • Ignored when used in conjunction with other networking modes.
    • NOTE: Future versions of Marathon may fail to validate apps that declare portDefinitions with network modes other than host.

Port Mappings

Summary:

  • Review port-mapping, container-port, and host-port in Terminology.
  • Location in app definition: { "container": { "portMappings": [ <port-mapping>... ], ... }, ... }
  • Used in conjunction with container and container/bridge mode networking.
  • When using container/bridge mode networking, an unspecified (null) value for hostPort is translated to "hostPort": 0.
  • requirePorts does not apply to portMappings.
  • If unspecified (null) at create-time, defaults to { "portMappings": [ { "containerPort": 0, "name": "default" } ], ... }
    • Specify an empty array ([]) to indicate NO ports are used by the app; no default is injected in this case.
    • NOTE: When using container/bridge mode, the default port-mapping also sets "hostPort: 0".
  • Ignored when used in conjunction with other networking modes.
    • NOTE: Future versions of Marathon may fail to validate apps that declare container.portMappings with network modes other than container or container/bridge.
  • When used in conjunction with multiple container networks, each mapping entry that specifies a hostPort must also declare a networkNames value with a single item, identifying the network for which the mapping applies (a single hostPort may be mapped to only one container network, and networkNames defaults to all container networks for a pod or app).

Downward API

Per-Task Environment Variables

If a port is named NAME, it will be accessible via the environment variable $PORT_NAME. Every host-port value is also exposed to the running application instance via environment variables $PORT0, $PORT1, etc. Each Marathon application is given a single port by default, so $PORT0 will normally be available, except for apps that specifically declare “no ports”. Variables are generated for all apps, regardless of whether the app uses the Mesos or Docker containerizer. It is highly recommended to name the ports of an app to provide clarity with respect to the app configuration and intended use of each port.

When using container or container/bridge mode networking, be sure to bind your application to the containerPorts you have specified in your portMappings. If you have set containerPort to 0, this will be the same as hostPort and you can use the $PORTxxx environment variables.

Additional per-task enviroment variables are also provided.

Discovery Via Mesos

DiscoveryInfo and port labels:

  • labels may be defined for items of portDefinitions as well as for items of portMappings. These labels are sent to Mesos via DiscoveryInfo protobufs at instance-launch time.
  • Given a mapping or endpoint, we generate a port DiscoveryInfo for every combination of specified protocols and associated networks. (IE, if an Endpoint specifies 2 protocols and is associated with 3 container networks, then a total of 6 DiscoveryInfo protobufs would be generated for that single Endpoint).
  • Marathon injects a network-scope label into the port DiscoveryInfo to disambiguate between a host-port and container-port.
    • A scope value of host is used for host-port discovery.
    • A scope value of container is used for container-port discovery.
  • For container-port discovery, Marathon also injects a network-name label into the respective port DiscoveryInfo.

Virtual addresses

See the DC/OS documentation for virtual addresses (VIPs).

Examples

host Mode

host mode networking is the default networking mode for all apps. If your app uses Docker containers, it not necessary to EXPOSE ports in your Dockerfile.

Using host Mode

Host mode is enabled by default for all apps and all container types. If you wish to be explicit, you can also specify it manually through the networks property:

  "networks": [ { "mode": "host" } ],
  "container": {
    "type": "MESOS",
    "docker": {
      "image": "my-image:1.0"
    }
  },

Specifying Ports

You can specify the ports that are available through the portDefinitions array:

    "portDefinitions": [
      {"port": 0, "name": "http"}, {"port": 0, "name": "https"}, {"port": 0, "name": "mon"}
    ],

In this example, we specify three dynamically assigned host ports, which would then be available to our command via the environment variables $PORT_HTTP, $PORT_HTTPS and $PORT_MON. Marathon will also associate three dynamically selected service ports to these three host ports.

You can also specify specific service ports:

    "portDefinitions": [
        {"port": 2001, "name": "http"}, {"port": 2002, "name": "https"}, {"port": 3000, "name": "mon"}
    ],

In this case, host ports $PORT_HTTP, $PORT_HTTPS and $PORT_MON remain dynamically assigned. However, the three service ports for this application are now 2001, 2002 and 3000.

In this example, as with the previous one, it is necessary to use a service discovery solution such as HAProxy to proxy requests from service ports to host ports.

If you want the application’s service ports to be equal to its host ports, you can set requirePorts to true (requirePorts is false by default). This will tell Marathon to only schedule this application on agents that have these ports available:

    "portDefinitions": [
        {"port": 2001, "name": "http"}, {"port": 2002, "name": "https"}, {"port": 3000, "name": "mon"}
    ],
    "requirePorts" : true

The service and host ports (including the environment variables $PORT_HTTP, $PORT_HTTPS, and $PORT_MON), are both now 2001, 2002 and 3000.

This property is useful if you don’t use a service discovery solution to proxy requests from service ports to host ports.

Each port-definition in a portDefinitions array allows you to specify a protocol, a name and labels for each definition. When starting new tasks, Marathon will pass this metadata to Mesos. Mesos will expose this information in the discovery field of the task. Custom network discovery solutions can consume this field.

Example port-definition requesting a dynamic tcp port named http with the label VIP_0 set to 10.0.0.1:80:

    "portDefinitions": [
        {
            "port": 0,
            "protocol": "tcp",
            "name": "http",
            "labels": {"VIP_0": "10.0.0.1:80"}
        }
    ],

The port field is mandatory. The protocol, name and labels fields are optional.

Referencing Ports

You can reference the host-ports in the Dockerfile for our fictitious app as follows:

CMD ./my-app --http-port=$PORT_HTTP --https-port=$PORT_HTTPS --monitoring-port=$PORT_MON

Alternatively, if you are not using Docker, or had specified a cmd in your Marathon application definition, it works in the same way:

    "cmd": "./my-app --http-port=$PORT_HTTP --https-port=$PORT_HTTPS --monitoring-port=$PORT_MON"

container and container/bridge Mode

Bridge mode networking allows you to map host ports to ports inside your containers. It is particularly useful if you are using a container image with fixed port assignments that you can’t modify.

Note: It is not necessary to EXPOSE ports in your Dockerfile when using Docker container images.

Enabling container/bridge Mode

Specify container/bridge mode through the networks property:

  "networks": [ { "mode": "container/bridge" } ],
  "container": {
    "type": "MESOS",
    "docker": {
      "image": "my-image:1.0"
    }
  },
Installing the mesos-bridge CNI plugin

If you are not using DC/OS and want to enable container/bridge mode with the Universal Container Runtime (UCR), several more steps are necessary to install and use the mesos-bridge CNI plugin.

Prerequisites

  • Mesos version 1.2.0 or higher.
  • Marathon version 1.5 or higher.
  1. Clone the CNI repository from https://github.com/containernetworking/cni and build according to their instructions.

  2. Navigate to or create a /var/lib/mesos/cni directory on each of your agent nodes as well as config and plugins subdirectories.

  3. Copy the contents of the bin folder created in the previous step to /var/lib/mesos/cni/plugins on each agent node.

  4. Create a file called mesos-bridge.json, copy the following configuration into it, and add it to /var/lib/mesos/cni/config.

   {
     "name": "mesos-bridge",
     "type": "mesos-cni-port-mapper",
     "excludeDevices": ["mesos-bridge"],
     "chain": "MESOS-BRIDGE-PORT-MAPPER",
     "delegate": {
       "type": "bridge",
       "bridge": "mesos-bridge",
       "isGateway": true,
       "ipMasq": true,
       "ipam": {
         "type": "host-local",
         "subnet": "10.1.1.0/24",
         "routes": [{
           "dst": "0.0.0.0/0"
         }]
       }
     }
   }
  1. Soft-link the mesos-cni-port-mapper plugin to your plugins directory using the following command
    $ sudo ln -sf /usr/libexec/mesos/mesos-cni-port-mapper /var/lib/mesos/cni/plugins/mesos-cni-port-mapper
    

Enabling container Mode

Specify container mode through the network property:

  "networks": [ { "mode": "container", "name": "someUserNetwork" } ],
  "container": {
    "type": "MESOS",
    "docker": {
      "image": "my-image:1.0"
    }
  }

If there is a --default_network_name configured for Marathon, then specifying a network name for the container network is optional: container networks with an unspecified (null) name will inherit the value of the --default_network_name flag.

Specifying Ports

Port mappings are similar to passing -p into the Docker command line and specify a relationship between a port on the host machine and a port inside the container. In this case, the portMappings array is used instead of the portDefinitions array used in host mode.

Port mappings are specified inside a container object:

  "networks": [ { "mode": "container/bridge" } ],
  "container": {
    "type": "MESOS",
    "docker": {
      "image": "my-image:1.0"
    },
    "portMappings": [
      { "containerPort": 0, "hostPort": 0, "name": "http" },
      { "containerPort": 0, "hostPort": 0, "name": "https" },
      { "containerPort": 0, "hostPort": 0, "name": "mon" }
    ]
  }

In this example, we specify 3 mappings. A value of 0 will ask Marathon to dynamically assign a value for hostPort. In this case, setting containerPort to 0 will cause it to have the same value as hostPort. These values are available inside the container as $PORT_HTTP, $PORT_HTTPS and $PORT_MON respectively.

Alternatively, if our process running in the container had fixed ports, we might do something like the following:

  "networks": [ { "mode": "container/bridge" } ],
  "container": {
    "type": "MESOS",
    "docker": {
      "image": "my-image:1.0"
    },
    "portMappings": [
      { "containerPort": 80, "hostPort": 0, "name": "http" },
      { "containerPort": 443, "hostPort": 0, "name": "https" },
      { "containerPort": 4000, "hostPort": 0, "name": "mon" }
    ]
  }

In this case, Marathon will randomly allocate host ports and map these to ports 80, 443 and 4000 respectively. The $PORT_xxx variables refer to the host ports. In this case, $PORT_HTTP will be set to the value of hostPort for the first mapping, and so on.

Specifying Protocol

You can also specify the protocol for these port mappings. The default is tcp:

  "networks": [ { "mode": "container/bridge" } ],
  "container": {
    "type": "MESOS",
    "docker": {
      "image": "my-image:1.0"
    },
    "portMappings": [
      { "containerPort": 80, "hostPort": 0, "name": "http", "protocol": "tcp" },
      { "containerPort": 443, "hostPort": 0, "name": "https", "protocol": "tcp" },
      { "containerPort": 4000, "hostPort": 0, "name": "mon", "protocol": "udp" }
    ]
  }

It is possible to specify multiple protocols by using a comma as a separator: udp,tcp (note the lack of spaces).

Specifying Service Ports

By default, Marathon will create associated service ports for each of these declared mappings and dynamically assign them values. Service ports are used by service discovery solutions and it is often desirable to set these to well known values. You can assign well-known service-port values by defining a servicePort for each mapping:

  "networks": [ { "mode": "container/bridge" } ],
  "container": {
    "type": "MESOS",
    "docker": {
      "image": "my-image:1.0"
    },
    "portMappings": [
      { "containerPort": 80, "hostPort": 0, "name": "http", "protocol": "tcp", "servicePort": 2000 },
      { "containerPort": 443, "hostPort": 0, "name": "https", "protocol": "tcp", "servicePort": 2001 },
      { "containerPort": 4000, "hostPort": 0, "name": "mon", "protocol": "udp", "servicePort": 3000 }
    ]
  },

In this example, the host ports $PORT_HTTP, $PORT_HTTPS and $PORT_MON remain dynamically assigned. However, the service ports for this application are now 2001, 2002 and 3000. An external proxy, like HAProxy, may be configured to route from the service ports to the host ports.

Referencing Ports

If you set containerPort to 0, then you should specify ports in the Dockerfile for our fictitious app as follows:

CMD ./my-app --http-port=$PORT_HTTP --https-port=$PORT_HTTPS --monitoring-port=$PORT_MON

However, if you have defined non-zero containerPort values, simply use the same values in the Dockerfile:

CMD ./my-app --http-port=80 --https-port=443 --monitoring-port=4000

Alternatively, you can specify a cmd in your Marathon application definition, it works in the same way as before:

"cmd": "./my-app --http-port=$PORT_HTTP --https-port=$PORT_HTTPS --monitoring-port=$PORT_MON"

Or, if you’ve used fixed values:

"cmd": "./my-app --http-port=80 --https-port=443 --monitoring-port=4000"