Como obter o endereço IP para uma tarefa específica do AWS ECS?

5

Estou tentando criar minha própria versão de descoberta de serviço no ECS, pois os serviços que desejo aumentar ou diminuir não são servidores HTTP e, portanto, não podem ser gerenciados pelo ELB. Além disso, o ECS ainda não suporta o recurso de redes definidas pelo usuário do docker , que seria outro maneira de fazer a descoberta de serviços. Como mencionado na discussão do assunto:

Currently Service Discovery is a huge pain requiring yet another service (which itself is usually cluster-based and self-discovers and then listens for other services). It's a messy solution, not to mention the Lambda "solutions" that are even more obnoxious to implement and maintain.

Então, eu vou pela irritante rota "solução" Lambda em vez de outras opções. A principal coisa que preciso para criar essa descoberta de serviço de invasão é o endereço IP de cada contêiner do docker em execução nos hosts do EC2.

Por meio de SSH no servidor do EC2 agindo como uma das instâncias do contêiner do ECS, posso executar docker ps para obter os IDs do contêiner para cada contêiner do Docker em execução. Para qualquer containerId fornecido, posso executar docker inspect ${containerId} , que retorna JSON, incluindo muitos detalhes sobre esse contêiner, em particular o NetworkSettings.IPAddress associado a esse contêiner (a principal coisa que preciso para minha implementação de descoberta).

Estou tentando usar o AWS SDK de dentro do Lambda para obter esse valor. Aqui está a minha função Lambda até agora (você deve ser capaz de executar isso também - nada específico para a minha configuração aqui):

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));
                })
            });
        })
    })
};

A saída da chamada describeTasks é bastante inútil. Ele não tem quase tantos detalhes quanto a chamada docker inspect produz, em particular, não inclui o endereço IP do contêiner docker que está executando a tarefa.

Também tentei localizar os dados de que preciso por meio da chamada describeContainerInstances , mas, como esperado, não retornou nenhum detalhe específico da tarefa.

Eu estaria disposto a tentar executar docker inspect diretamente no host do EC2, se houvesse alguma maneira de fazer isso do Lambda. Não tenho certeza se é possível executar comandos no contêiner por meio do SDK; provavelmente não. Portanto, eu teria que criar um serviço personalizado em execução em uma versão especialmente criada da imagem do contêiner do ECS, o que parece terrível.

Como posso obter esses endereços IP de contêiner usando o AWS SDK? Ou alguma ideia melhor sobre como resolver o problema geral da descoberta de serviços no ECS?

    
por Jake Feasel 07.05.2017 / 20:27

2 respostas

4

Acontece que minha premissa original (a necessidade de saber o endereço IP interno do recipiente da tarefa para a descoberta de serviço) é muito falha - esse endereço IP só pode ser usado em uma única Instância de Contêiner do EC2. Se você tiver várias instâncias de contêiner (o que provavelmente deve ter), esses IPs do contêiner de tarefas são basicamente inúteis.

A solução alternativa que criei é seguir o padrão sugerido para balanceadores de carga de aplicativos que executam HTTP / HTTPS - ter um mapeamento de porta com 0 como a porta do host, apontando para a porta na instância do docker que preciso usar . Ao fazer isso, o Docker atribuirá uma porta de host aleatória, que eu posso encontrar usando o SDK da AWS - em particular, a função "describeTasks" disponível no módulo do ECS. Veja aqui para mais detalhes: link

Esta é a base fundamental para o meu mecanismo de descoberta de serviço de rolagem - há muitos outros detalhes necessários para fazer isso de maneira completa. Eu usei as funções do Lambda chamando o AWS SDK, bem como um banco de dados PostgreSQL, para manter atualizada a minha lista de recipientes de host (algo parecido com um registro de DNS dinâmico). Parte do truque é que você precisa saber o IP e a porta para cada um dos contêineres, mas describeTasks apenas retorna a porta. Aqui está uma função útil do NodeJS que escrevi, que pega um nome de contêiner e procura todos os endereços IP e portas encontrados dentro do cluster para contêineres com esse nome:

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
            })
        )
    );
};

Observe que isso usa a biblioteca de promessas 'Q' - você precisará declarar isso como uma dependência em seu pacote.json.

O restante da minha solução personalizada para a entrega da descoberta de serviço do ECS usando as funções do Lambda pode ser encontrada aqui: link

    
por 30.05.2017 / 07:53
1

Você pode associar o Classic Elastic Load Balancer ao serviço do ECS, mesmo que seus serviços não sejam HTTP. Certifique-se de criar o listener TCP (não HTTP ou HTTPs / SSL) no ELB e apontar para a porta exposta do seu contêiner. A desvantagem de usar o ELB Classic vs o ELB de Aplicativos é que você terá que ter um ELB separado para cada serviço do ECS (custo adicional).

link

    
por 09.05.2017 / 21:35