如何获取特定 AWS ECS 任务的 IP 地址?

Jak*_*sel 8 amazon-web-services docker amazon-ecs amazon-lambda

我正在尝试在 ECS 中构建我自己的服务发现版本,因为我希望向上和向下扩展的服务不是 HTTP 服务器,因此无法由 ELB 管理。此外,ECS 尚不支持 docker 的用户定义网络功能,这将是进行服务发现的另一种方式。正如该问题讨论中提到的:

目前服务发现是一个巨大的痛苦,需要另一个服务(它本身通常是基于集群的,自我发现然后侦听其他服务)。这是一个凌乱的解决方案,更不用说实施和维护更令人讨厌的 Lambda“解决方案”了。

因此,我将采用令人讨厌的 Lambda“解决方案”路线来代替其他选择。我需要构建这个 hack 服务发现的主要内容是在我的 EC2 主机上运行的每个 docker 容器的 IP 地址。

通过 SSH 连接到充当我的 ECS 容器实例之一的 EC2 服务器,我可以运行docker ps以获取每个正在运行的 docker 容器的容器 ID。对于任何给定的 containerId,我可以运行docker inspect ${containerId}它返回 JSON,包括有关该容器的许多详细信息,特别是NetworkSettings.IPAddress与该容器的绑定(我的发现实现所需的主要内容)。

我正在尝试使用 Lambda 中的 AWS 开发工具包来获取此值。到目前为止,这是我的 Lambda 函数(您也应该能够运行它——这里没有特定于我的设置):

exports.handler = (event, context, callback) => {
    var AWS = require('aws-sdk'),
        ecs = new AWS.ECS({"apiVersion": '2014-11-13'});

    ecs.listClusters({}, (err, data) => {
        data.clusterArns.map((clusterArn) => {
            ecs.listTasks({
                cluster: clusterArn
            }, (err, data) => {
                ecs.describeTasks({
                    cluster: clusterArn,
                    tasks: data.taskArns
                }, (err, data) => {
                   if (err) console.log(err, err.stack); 
                   else     console.log(JSON.stringify(data, null, 4));
                })
            });
        })
    })
};
Run Code Online (Sandbox Code Playgroud)

describeTasks调用的输出非常无用。它没有docker inspect调用产生的详细信息,特别是它不包括运行任务的 docker 容器的 IP 地址。

我还尝试通过describeContainerInstances调用找到我需要的数据,但正如预期的那样,它没有返回任何特定于任务的详细信息。

我愿意尝试docker inspect直接在 EC2 主机上运行,如果 Lambda 可以这样做的话。我不确定是否可以通过 SDK 在容器上运行命令;可能不是。因此,我必须构建一个在特制版本的 ECS 容器映像上运行的自定义服务,这听起来很糟糕。

我该如何使用 AWS 开发工具包获取这些容器 IP 地址?或者关于如何解决 ECS 中服务发现的一般问题的一些更好的想法?

Jak*_*sel 5

事实证明,我最初的前提(需要知道任务容器自己的内部 IP 地址以进行服务发现)非常有缺陷 - IP 地址只能在单个 EC2 容器实例中使用。如果您有多个容器实例(您可能应该有),那么这些任务容器 IP 基本上是无用的。

我提出的替代解决方案是遵循为运行 HTTP / HTTPS 的应用程序负载均衡器建议的模式 - 使用 0 作为主机端口的端口映射,指向我需要使用的 docker 实例内的端口。通过这样做,Docker 将分配一个随机主机端口,然后我可以使用 AWS SDK 找到该端口 - 特别是 ECS 模块上可用的“describeTasks”函数。有关详细信息,请参阅此处:http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/ECS.html#describeTasks-property

这是我的自行式服务发现机制的基本基础——以完整的方式完成此工作还需要许多其他细节。我使用 Lambda 函数调用 AWS SDK 以及 PostgreSQL 数据库,以使我的主机容器列表保持最新(有点像动态 DNS 注册表)。部分技巧是您需要知道每个容器的 IP 和端口,但 describeTasks 仅返回端口。这是我编写的一个方便的 No​​deJS 函数,它采用容器名称并查找在集群中找到的具有该名称的容器的所有 IP 地址和端口:

var Q = require('q');
/**
 * @param {String} - cluster - name of the cluster to query, e.g. "sqlfiddle3"
 * @param {String} - containerType - name of the container to search for within the cluster
 * @returns {Promise} - promise resolved with a list of ip/port combinations found for this container name, like so:
    [
      {
        "connection_meta": "{\"type\":\"ecs\",\"taskArn\":\"arn:aws:ecs:u..\"}",
        "port": 32769,
        "ip": "10.0.1.49"
      }
    ]
 *
 */
exports.getAllHostsForContainerType = (cluster, containerType) => {
    var AWS = require('aws-sdk'),
        ecs = new AWS.ECS({"apiVersion": '2014-11-13'}),
        ec2 = new AWS.EC2({"apiVersion": '2016-11-15'});

    return ecs.listTasks({ cluster }).promise()
    .then((taskList) => ecs.describeTasks({ cluster, tasks: taskList.taskArns }).promise())
    .then((taskDetails) => {
        var containersForName = taskDetails.tasks
            .filter((taskDetail) =>
                taskDetail.containers.filter(
                    (container) => container.name === containerType
                ).length > 0
            )
            .map((taskDetail) =>
                taskDetail.containers.map((container) => {
                    container.containerInstanceArn = taskDetail.containerInstanceArn;
                    return container;
                })
            )
            .reduce((final, containers) =>
                final.concat(containers)
            , []);

        return containersForName.length ? (ecs.describeContainerInstances({ cluster,
            containerInstances: containersForName.map(
                (containerDetails) => containerDetails.containerInstanceArn
            )
        }).promise()
        .then((containerInstanceList) => {

            containersForName.forEach((containerDetails) => {
                containerDetails.containerInstanceDetails = containerInstanceList.containerInstances.filter((instance) =>
                    instance.containerInstanceArn === containerDetails.containerInstanceArn
                )[0];
            });

            return ec2.describeInstances({
                InstanceIds: containerInstanceList.containerInstances.map((instance) =>
                    instance.ec2InstanceId
                )
            }).promise();
        })
        .then((instanceDetails) => {
            var instanceList = instanceDetails.Reservations.reduce(
                (final, res) => final.concat(res.Instances), []
            );

            containersForName.forEach((containerDetails) => {
                if (containerDetails.containerInstanceDetails) {
                    containerDetails.containerInstanceDetails.ec2Instance = instanceList.filter(
                        (instance) => instance.InstanceId === containerDetails.containerInstanceDetails.ec2InstanceId
                    )[0];
                }
            });
            return containersForName;
        })) : [];
    })
    .then(
        (containersForName) => containersForName.map(
            (container) => ({
                connection_meta: JSON.stringify({
                    type: "ecs",
                    taskArn: container.taskArn
                }),
                // assumes that this container has exactly one network binding
                port: container.networkBindings[0].hostPort,
                ip: container.containerInstanceDetails.ec2Instance.PrivateIpAddress
            })
        )
    );
};
Run Code Online (Sandbox Code Playgroud)

请注意,这使用了“Q”承诺库 - 您需要将其声明为 package.json 中的依赖项。

我使用 Lambda 函数处理 ECS 服务发现的自定义解决方案的其余部分可以在这里找到: https: //github.com/jakefeasel/sqlfiddle3#setting-up-in-amazon-web-services


小智 1

即使您的服务不是 HTTP,您也可以将经典弹性负载均衡器与 ECS 服务关联。确保您在 ELB 上创建 TCP 侦听器(不是 HTTP 或 HTTPs/SSL)并指向容器的公开端口。与应用程序 ELB 相比,使用经典 ELB 的缺点是您必须为每个 ECS 服务拥有单独的 ELB(额外成本)。

http://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-listener-config.html