Назад | Перейти на главную страницу

Как получить IP-адрес для конкретной задачи AWS ECS?

Я пытаюсь создать свою собственную версию обнаружения сервисов в ECS, поскольку сервисы, которые я хочу увеличивать или уменьшать, не являются HTTP-серверами и не могут управляться с помощью ELB. Также, ECS еще не поддерживает функцию пользовательских сетей в докере что было бы другим способом обнаружения сервисов. Как упоминалось в этом обсуждении проблемы:

В настоящее время обнаружение служб - это огромная проблема, требующая еще одной службы (которая сама обычно основана на кластере и автоматически обнаруживает, а затем прослушивает другие службы). Это запутанное решение, не говоря уже о «решениях» Lambda, которые еще более неприятны в реализации и поддержке.

Поэтому я иду неприятным путем "решения" Lambda вместо других вариантов. Главное, что мне нужно для создания этого обнаружения службы взлома, - это IP-адрес каждого из контейнеров докеров, работающих на моих хостах EC2.

Подключившись по SSH к серверу EC2, действующему как один из моих экземпляров контейнера ECS, я могу запустить docker ps чтобы получить идентификаторы контейнера для каждого запущенного контейнера докеров. Для любого данного containerId я могу запустить docker inspect ${containerId} который возвращает JSON, включая множество деталей об этом контейнере, в частности NetworkSettings.IPAddress привязанный к этому контейнеру (главное, что мне нужно для моей реализации обнаружения).

Я пытаюсь использовать AWS SDK из Lambda, чтобы получить это значение. Вот моя функция 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));
                })
            });
        })
    })
};

Выход из describeTasks звонок довольно бесполезен. В нем не так много деталей, как в docker inspect вызывает, в частности, он не включает IP-адрес контейнера докеров, выполняющего задачу.

Я также попытался найти нужные мне данные через describeContainerInstances вызов, но, как и ожидалось, не вернул никаких сведений о конкретной задаче.

Я бы хотел попробовать бежать docker inspect непосредственно на хосте EC2, если это можно было сделать с помощью Lambda. Я не уверен, можно ли запускать команды в контейнере через SDK; возможно нет. Поэтому мне пришлось бы создать собственный сервис, работающий на специально созданной версии образа контейнера ECS, что звучит ужасно.

Как я могу получить эти IP-адреса контейнеров с помощью AWS SDK? Или лучшее представление о том, как решить общую проблему обнаружения сервисов в ECS?

Оказывается, моя первоначальная предпосылка (необходимость знать собственный внутренний IP-адрес контейнера задач для обнаружения службы) очень ошибочна - этот IP-адрес можно использовать только в пределах одного экземпляра контейнера EC2. Если у вас есть несколько экземпляров контейнера (которые, вероятно, должны быть), то эти IP-адреса контейнера задач в основном бесполезны.

Альтернативное решение, которое я придумал, состоит в том, чтобы следовать шаблону, предложенному для балансировщиков нагрузки приложений, работающих по протоколу HTTP / HTTPS - иметь сопоставление портов с 0 в качестве порта хоста, указывающим на порт в экземпляре докера, который мне нужно использовать. Таким образом, Docker назначит случайный порт хоста, который я затем могу найти с помощью AWS SDK, в частности, с помощью функции «describeTasks», доступной в модуле ECS. Подробнее см. Здесь: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/ECS.html#describeTasks-property

Это фундаментальная основа для моего механизма обнаружения собственных сервисов - есть много других деталей, необходимых для того, чтобы сделать это в полной мере. Я использовал функции Lambda, обращающиеся к AWS SDK, а также к базе данных PostgreSQL, чтобы поддерживать мой список контейнеров хоста в актуальном состоянии (что-то вроде динамического реестра DNS). Частично уловка заключается в том, что вам нужно знать IP и порт для каждого из контейнеров, но descriptionTasks возвращает только порт. Вот написанная мной удобная функция NodeJS, которая принимает имя контейнера и ищет все 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
            })
        )
    );
};

Обратите внимание, что здесь используется библиотека обещаний Q - вам нужно объявить это как зависимость в вашем package.json.

Остальную часть моего пользовательского решения для обработки обнаружения службы ECS с использованием лямбда-функций можно найти здесь: https://github.com/jakefeasel/sqlfiddle3#setting-up-in-amazon-web-services

Вы можете связать Classic Elastic Load Balancer со службой ECS, даже если ваши службы не являются HTTP. Убедитесь, что вы создали прослушиватель TCP (не HTTP или HTTP / SSL) на ELB и указываете на открытый порт вашего контейнера. Недостатком использования Classic ELB по сравнению с Application ELB является то, что вам придется иметь отдельный ELB для каждой службы ECS (за дополнительную плату).

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