Wie erhalte ich die IP-Adresse für eine bestimmte AWS ECS-Aufgabe?

8

Ich versuche, eine eigene Version der Diensterkennung in ECS zu erstellen, da die Dienste, die ich vergrößern und verkleinern möchte, keine HTTP-Server sind und daher nicht von ELB verwaltet werden können. Außerdem unterstützt ECS die benutzerdefinierte Netzwerkfunktion von Docker noch nicht. Dies wäre eine weitere Möglichkeit, die Diensterkennung durchzuführen. Wie in dieser Diskussion erwähnt:

Derzeit ist die Serviceerkennung ein großer Schmerz, der einen weiteren Service erfordert (der normalerweise clusterbasiert ist und sich selbst erkennt und dann auf andere Services wartet). Es ist eine chaotische Lösung, ganz zu schweigen von den Lambda- "Lösungen", deren Implementierung und Wartung noch unangenehmer sind.

Also gehe ich den abscheulichen Weg der Lambda-Lösung anstelle anderer Optionen. Die Hauptsache, die ich zum Erstellen dieser Hack-Service-Erkennung benötige, ist die IP-Adresse jedes Docker-Containers, der auf meinen EC2-Hosts ausgeführt wird.

Durch SSH in den EC2-Server, der als eine meiner ECS-Containerinstanzen fungiert, kann ich ausgeführt werden docker ps, um die Container-IDs für jeden ausgeführten Docker-Container abzurufen. Für jede gegebene containerId kann ich ausführen, docker inspect ${containerId}die JSON zurückgibt, einschließlich vieler Details zu diesem Container, insbesondere der NetworkSettings.IPAddressBindung an diesen Container (die Hauptsache, die ich für meine Discovery-Implementierung benötige).

Ich versuche, das AWS SDK aus Lambda heraus zu verwenden, um diesen Wert zu erhalten. Hier ist meine Lambda-Funktion (Sie sollten dies auch ausführen können - nichts Spezielles für mein Setup hier):

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

Die Ausgabe des describeTasksAnrufs ist ziemlich nutzlos. Es enthält nicht annähernd so viele Details wie der docker inspectAufruf, insbesondere nicht die IP-Adresse des Docker-Containers, in dem die Aufgabe ausgeführt wird.

Ich habe auch versucht, die benötigten Daten über den describeContainerInstancesAnruf zu finden, aber wie erwartet wurden keine aufgabenspezifischen Details zurückgegeben.

Ich wäre bereit, docker inspectdirekt auf dem EC2-Host zu laufen , wenn Lambda dies möglich machen würde. Ich bin nicht sicher, ob es möglich ist, Befehle über das SDK für den Container auszuführen. wahrscheinlich nicht. Daher müsste ich einen benutzerdefinierten Dienst erstellen, der auf einer speziell erstellten Version des ECS-Container-Images ausgeführt wird, was schrecklich klingt.

Wie kann ich diese Container-IP-Adressen mithilfe des AWS SDK abrufen? Oder eine bessere Idee, wie das allgemeine Problem der Serviceerkennung in ECS gelöst werden kann?

Jake Feasel
quelle

Antworten:

4

Es stellt sich heraus, dass meine ursprüngliche Prämisse (die interne IP-Adresse des Task-Containers für die Serviceerkennung kennen muss) sehr fehlerhaft ist - diese IP-Adresse kann nur innerhalb einer einzelnen EC2-Container-Instanz verwendet werden. Wenn Sie mehrere Containerinstanzen haben (die Sie wahrscheinlich haben sollten), sind diese Task-Container-IPs im Grunde genommen nutzlos.

Die alternative Lösung, die ich mir ausgedacht habe, besteht darin, dem für Application Load Balancers mit HTTP / HTTPS vorgeschlagenen Muster zu folgen. Verwenden Sie eine Portzuordnung mit 0 als Host-Port, die auf den Port in der Docker-Instanz verweist, den ich verwenden muss. Auf diese Weise weist Docker einen zufälligen Host-Port zu, den ich dann mithilfe des AWS SDK finden kann - insbesondere die auf dem ECS-Modul verfügbare Funktion "descriptionTasks". Weitere Informationen finden Sie hier: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/ECS.html#describeTasks-property

Dies ist die grundlegende Grundlage für meinen Roll-Your-Own-Service-Erkennungsmechanismus. Es sind viele weitere Details erforderlich, um dies vollständig zu tun. Ich habe Lambda-Funktionen verwendet, die das AWS SDK aufrufen, sowie eine PostgreSQL-Datenbank, um meine Liste der Host-Container auf dem neuesten Stand zu halten (so etwas wie eine dynamische DNS-Registrierung). Ein Teil des Tricks besteht darin, dass Sie die IP und den Port für jeden der Container kennen müssen. DescriptTasks gibt jedoch nur den Port zurück. Hier ist eine praktische NodeJS-Funktion, die ich geschrieben habe. Sie verwendet einen Containernamen und sucht nach allen IP-Adressen und Ports im Cluster für Container mit diesem Namen:

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

Beachten Sie, dass hierfür die 'Q'-Versprechensbibliothek verwendet wird. Sie müssen dies als Abhängigkeit in Ihrer package.json deklarieren.

Den Rest meiner benutzerdefinierten Lösung für die Übergabe der ECS-Serviceerkennung mithilfe von Lambda-Funktionen finden Sie hier: https://github.com/jakefeasel/sqlfiddle3#setting-up-in-amazon-web-services

Jake Feasel
quelle
1

Sie können Classic EC Elastic Load Balancer mit dem ECS-Dienst verknüpfen, auch wenn Ihre Dienste nicht HTTP sind. Stellen Sie sicher, dass Sie einen TCP-Listener (nicht HTTP oder HTTPs / SSL) auf der ELB erstellen und auf den exponierten Port Ihres Containers zeigen. Der Nachteil der Verwendung von Classic ELB gegenüber Application ELB besteht darin, dass Sie für jeden ECS-Dienst eine separate ELB benötigen (zusätzliche Kosten).

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

Andrey
quelle
Vielen Dank für die Idee, ich bin einen anderen Weg gegangen (detailliert in meiner eigenen Antwort). Ich mache mir Sorgen, dass eine ELB als Vermittler für meine Clients und die Server fungiert. Könnte sie wirklich jede TCP-Socket-Verbindung ohne Störung verarbeiten? Ich mag auch nicht die Idee, für jeden Dienst extra zu bezahlen - ich plane, mehrere verschiedene unabhängige Dienste zu nutzen. Jedenfalls war das eine interessante Idee, ich bin froh darüber zu wissen.
Jake Feasel