在Marathon中,对应用程序端口的配置可能会造成混淆,并且有一个悬而未决的问题,需要重新设计的端口API。本文尝试更清楚的解释Marathon的端口是如何工作的。
如果你是在DC/OS集群中运行Marathon,你可以使用虚拟地址(VIP:Virtual addresses) ,使端口的管理更容易。VIP简化应用间的通讯,并实现可靠的面向服务的架构。VIP从单个虚拟地址向多个IP地址和端口映射的流量。
containerPort:用来指定容器内部的一个端口。当使用BRIDGE或USER网络模式连接Docker容器时,必须将这个属性设置为port mapping的一部分。
hostPort:用来指定绑定到主机上的一个端口。当使用BRIDGE或USER网络模式,你可以指定一个port mapping将一个主机端口映射到容器端口。在HOST网络模式下,默认的请求端口就是主机的端口。请注意,主机端口只可以通过环境变量提供给一个任务。
BRIDGE网络模式:指定Docker应用程序使用BRIDGE网络模式。在这种模式下,容器端口(容器内部的端口)被映射到主机端口(主机上的端口)。在这种模式下,应用程序被绑定到容器内的指定端口,容器的端口被绑定到主机上的指定端口。
USER网络模式:指定Docker应用程序使用USER网络模式。在这种模式下,容器端口(容器内部的端口)被映射到主机端口(主机上的端口)。在这种模式下,应用程序被绑定到容器内的指定端口,容器的端口被绑定到主机上的指定端口。在与“用户自定义”Docker网络集成时,USER网络模式将会非常有用。在Mesos世界,这种网络通常是通过使用与Mesos CNI网络隔离的 CNI 插件访问。
HOST网络模式:该种模式在Marathon应用为非容器化而其它应用为容器化的情况下使用。在这种模式下,应用程序直接绑定到主机上的一个或多个端口。
portMapping:在Docker BRIDGE模式下,在容器外部可被访问到的端口都需要做端口映射。端口映射是一个包含host port, container port, service port和协议的元组。可以为Marathon应用指定多个端口映射; 未指定hostPort,则其默认值为0(意味着Marathon将随机分配一个)。在Docker USER模式下,hostPort的语义为稍有点变化:USER模式不需要指定hostPort,如果未指定Marathon不会自动分配一个随机的。这允许在USER网络模式下部署容器,包括containerPort和发现信息,但不暴露主机网络上的这些端口(意味着将不消耗主机端口资源)。
port:该端口数组被用来定义在HOST网络模式下应被视为资源提供的一部分的端口。在没有指定端口映射的情况下这个属性是必须的。一个应用程序只需要定义端口或portDefinitions两者其中之一。
portDefinitions:portDefinitions数组用于定义应被认为是资源提供一部分的端口。在使用HOST网络模式和没有指定端口映射的情况下,需要定义portDefinitions数组。这个数组是为了替换port数组,并可以为其指定一个端口名,协议和标签。一个应用程序只需要定义端口或portDefinitions两者其中之一。
protocol:protocol用于指定端口使用的因特网协议(例如tcp,udp或者udp,tcp)。这只需要在Docker容器环境下使用BRIDGE或USER网络模式时,在port mapping属性下设置。
requirePorts:requirePorts是指定Marathon是否应该在收到资源提供请求时,专门寻找指定端口的一个属性。这确保了这些端口是可用的,可以绑定到上Mesos Agent上的。这不支持BRIDGE或USER网络模式。
servicePort:当您在Marathon上(不管是通过REST API或界面)创建一个新的应用程序,你可以指定一个或多个服务端口给它。可以指定所有有效的端口号为服务端口,也可以用0表示Marathon应该自动分配的可用服务端口给应用程序使用。如果你选择自己的服务端口,你必须自己确保,这个端口在所有应用程序中是唯一的。
随机端口分配
使用值0,表示你想让Marathon随机分配一个端口。但是,如果在portMapping属性内部,将containerPort的值设置为0,意味着使用与hostPort相同的端口。
每个host port值是通过 PORT0∗∗,∗∗ PORT1等等的环境变量暴露给正在运行的应用程序实例。每个Marathon应用默认被分配一个端口,因此 PORT0∗∗始终可用。这些变量也可以在通过Marathon运行的Docker容器内部使用。此外,如果该端口被命名为∗∗NAME∗∗,它也将通过环境变量∗∗ PORT_NAME访问。
当使用BRIDGE或USER模式的网络,确保你的应用程序绑定到你在portMapping中声明的containerPort的端口上。但是,如果设置containerPort的值为0,那么这个端口将与hostPort的端口一样,你可以使用$PORT环境变量。
Host模式网络是Docker容器默认的组网方式,是非Docker应用程序的唯一网络模式。注意,没有必要在Dockerfile文件中EXPOSE这个端口。
Host模式默认情况下在Docker中是启用状态。如果你想明确的指定,你也可以手工在network属性上指定:
"container": {
"type": "DOCKER",
"docker": {
"image": "my-image:1.0",
"network": "HOST"
}
},
对于非Docker应用程序,你不需要指定任何东西。
您可以通过ports数组指定端口:
"ports": [
0, 0, 0
],
或通过portDefinitions数组:
"portDefinitions": [
{"port": 0}, {"port": 0}, {"port": 0}
],
在这个例子中,我们指定三个随机分配主机端口,这可以通过环境变量 PORT0∗∗,∗∗ PORT1和$PORT2提供给我们的命令行使用。Marathon也会在这三个主机端口之外,随机分配三个服务端口(service port)。
您也可以指定特定的服务端口(service port):
"ports": [
2001, 2002, 3000
],
或者:
"portDefinitions": [
{"port": 2001}, {"port": 2002}, {"port": 3000}
],
在这种情况下,主机端口 PORT0∗∗,∗∗ PORT1和$PORT3保持随机分配。但是,这个应用程序的三个服务端口现在是2001,2002和3000。
在本例中,像上面前一个,有必要使用一个服务发现解决方案,如HAProxy,代理从服务端口到主机端口的请求。
如果你想要的应用程序服务端口等于其主机端口,可以设置requirePorts为true(默认情况下requirePorts是false)。这将告诉Marathon在Agent上只调度具有这些可用端口的应用程序:
"ports": [
2001, 2002, 3000
],
"requirePorts" : true
服务端口和主机端口(包括环境变量 PORT0∗∗,∗∗ PORT1和$PORT2),现在都是2001,2002和3000。
如果你不使用服务发现解决方案,代理从服务端口主机端口的请求,这个属性很有用。
定义portDefinitions数组允许你为每个端口指定一个协议,一个名字和标签。当开始新的任务,Marathon将传递这些元数据给Mesos。Mesos会在任务的discovery域中暴露这些信息。自定义网络发现解决方案可以使用这个值。
下例的端口定义中,请求一个名字是http、标签VIP_0的为10.0.0.1:80的 动态tcp端口:
"portDefinitions": [
{
"port": 0,
"protocol": "tcp",
"name": "http",
"labels": {"VIP_0": "10.0.0.1:80"}
}
],
该port字段是强制性的。protocol,name和labels字段是可选的。其中有port字段被设置为等于ports数组。
注意,只有ports数组和portDefinitions数组不应该一起使用,除非他们所有的元素是一样的。
您可以易用为我们虚构的应用程序在Dockerfile中定义的端口:
CMD ./my-app --http-port=$PORT0 --https-port=$PORT1 --monitoring-port=$PORT2
另外,如果你不使用Docker或在马拉松应用程序中指定了一个cmd,运行效果一样:
"cmd": "./my-app --http-port=$PORT0 --https-port=$PORT1 --monitoring-port=$PORT2"
桥接模式网络允许你将主机端口映射到容器内的端口,并且只适用于Docker容器。如果想为容器镜像分配一个固定的端口,这个办法将会非常有用。注意,没有必要在Dockerfile文件中EXPOSE这个端口。
你需要通过network属性指定桥接模式:
"container": {
"type": "DOCKER",
"docker": {
"image": "my-image:1.0",
"network": "BRIDGE"
}
},
你需要通过network属性指定用户模式:
"container": {
"type": "DOCKER",
"docker": {
"image": "my-image:1.0",
"network": "USER"
}
},
"ipAddress": {
"networkName": "someUserNetwork"
}
端口映射可以简单地在命令行中通过-p参数传递给Docker,并在容器内端口和主机端口之间创建关联关系。在这种情况下,用portMappings数组来替换在主机模式下使用的ports或portDefinitions数组。
端口映射在container对象中的portMappings对象中指定:
"container": {
"type": "DOCKER",
"docker": {
"image": "my-image:1.0",
"network": "BRIDGE",
"portMappings": [
{ "containerPort": 0, "hostPort": 0 },
{ "containerPort": 0, "hostPort": 0 },
{ "containerPort": 0, "hostPort": 0 }
]
}
},
在这个例子中,我们指定了3个映射。0值让Marathon给hostPort随机分配一个值。在这种情况下,设置containerPort为0将让它有和hostPort相同的值。这些值作在容器内分别通过 PORT0, PORT1并$PORT2访问。
另外,如果我们的应用在容器中使用固定端口,我们可以像下面这样的做:
"container": {
"type": "DOCKER",
"docker": {
"image": "my-image:1.0",
"network": "BRIDGE",
"portMappings": [
{ "containerPort": 80, "hostPort": 0 },
{ "containerPort": 443, "hostPort": 0 },
{ "containerPort": 4000, "hostPort": 0 }
]
}
},
在这种情况下,Marathon随机分配主机端口并将这些端口分别映射到80,443和4000。值得注意的是,这是很重要, PORT变量引用的是主机端口。在这种情况下,∗∗ PORT0**将被设置为hostPort的第一个值,其它的顺序设置。
您也可以指定这些端口映射的协议。默认值是tcp:
"container": {
"type": "DOCKER",
"docker": {
"image": "my-image:1.0",
"network": "BRIDGE",
"portMappings": [
{ "containerPort": 80, "hostPort": 0, "protocol": "tcp" },
{ "containerPort": 443, "hostPort": 0, "protocol": "tcp" },
{ "containerPort": 4000, "hostPort": 0, "protocol": "udp" }
]
}
},
默认情况下,Marathon会为每个端口创建服务端口,并赋予它们随机值。服务端口用于服务发现解决方案,这是通常设置这些值为众所周知的值。你可以通过为每一个映射设置一个servicePort做到这一点:
"container": {
"type": "DOCKER",
"docker": {
"image": "my-image:1.0",
"network": "BRIDGE",
"portMappings": [
{ "containerPort": 80, "hostPort": 0, "protocol": "tcp", "servicePort": 2000 },
{ "containerPort": 443, "hostPort": 0, "protocol": "tcp", "servicePort": 2001 },
{ "containerPort": 4000, "hostPort": 0, "protocol": "udp", "servicePort": 3000}
]
}
},
在这个例子中,主机端口 PORT0∗∗,∗∗ PORT1和$PORT3保持随机分配。但是,这个应用程序的服务端口现在2001,2002和3000。外部代理,如HAProxy,应配置为从服务端口到主机端口路由。
如果设置containerPort为0,那么你应该在Dockerfile文件中为我们虚构的应用程序指定的端口,像下面这样:
CMD ./my-app --http-port=$PORT0 --https-port=$PORT1 --monitoring-port=$PORT2
但是,如果指定了containerPort的值,只需使用在Dockerfile相同的值代替:
CMD ./my-app --http-port=80 --https-port=443 --monitoring-port=4000
另外,也可以指定在Marathon应用程序中指定一个cmd,它的会和上面一样以相同的工作方式运行:
"cmd": "./my-app --http-port=$PORT0 --https-port=$PORT1 --monitoring-port=$PORT2"
或者,如果你使用固定的值:
"cmd": "./my-app --http-port=80 --https-port=443 --monitoring-port=4000"
参考资料:
[1]: http://mesosphere.github.io/marathon/docs/ports.html