web-dev-qa-db-ja.com

特定のAWS ECSタスクのIPアドレスを取得するにはどうすればよいですか?

ECS内で独自のバージョンのサービスディスカバリを構築しようとしています。スケールアップおよびスケールダウンするサービスはHTTPサーバーではないため、ELBで管理できません。また、 ECSはまだdockerのユーザー定義ネットワーク機能をサポートしていません これは、サービス検出を行う別の方法です。その問題の議論で述べたように:

現在、サービスディスカバリは、さらに別のサービス(通常、それ自体がクラスタベースであり、自己発見して他のサービスをリッスンする)を必要とする大きな問題です。これは厄介なソリューションであり、実装と保守がさらに厄介なLambdaの「ソリューション」は言うまでもありません。

だから私は他のオプションの代わりに不愉快なラムダの「解決策」の道を行くつもりです。このハックサービスディスカバリを構築するために必要な主なことは、EC2ホストで実行されている各DockerコンテナーのIPアドレスです。

ECSコンテナーインスタンスの1つとして機能するEC2サーバーにSSHで接続することにより、docker psを実行して、実行中の各DockerコンテナーのコンテナーIDを取得できます。任意の特定のcontainerIdに対して、docker inspect ${containerId}を実行できます。これにより、そのコンテナーに関する多くの詳細を含むJSONが返されます。特に、そのコンテナーにバインドされているNetworkSettings.IPAddressです(検出の実装に必要な主なもの)。

Lambda内からAWS SDKを使用してこの値を取得しようとしています。ここに私の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呼び出しが生成するほど詳細はありません。特に、タスクを実行しているDockerコンテナーのIPアドレスは含まれていません。

また、describeContainerInstances呼び出しを介して必要なデータを見つけようとしましたが、予想通り、タスク固有の詳細は返されませんでした。

Lambdaから実行する方法があった場合は、EC2ホストでdocker inspectを直接実行してみてもかまいません。 SDKを介してコンテナーでコマンドを実行できるかどうかはわかりません。おそらく違います。そのため、ECSコンテナーイメージの特別に作成されたバージョンで実行されるカスタムサービスを構築する必要があります。

AWS SDKを使用してこれらのコンテナーIPアドレスを取得するにはどうすればよいですか?または、ECSでのサービス検出の一般的な問題を解決する方法についてのより良いアイデアはありますか?

8
Jake Feasel

私の最初の前提(サービス検出のためにタスクコンテナー自体の内部IPアドレスを知る必要がある)は非常に欠陥があることがわかりました-そのIPアドレスは単一のEC2コンテナーインスタンス内でのみ使用できます。複数のコンテナーインスタンスがある場合(おそらく持っているはずです)、それらのタスクコンテナーIPは基本的に役に立ちません。

私が思いついた別の解決策は、HTTP/HTTPSを実行しているApplication Load Balancerに提案されているパターンに従うことです。使用する必要があるDockerインスタンス内のポートを指す、ホストポートとして0のポートマッピングがあります。これにより、Dockerはランダムなホストポートを割り当てます。これは、AWS SDKを使用して特定できます。特に、ECSモジュールで使用可能な「describeTasks」関数を使用します。詳細はこちらをご覧ください: http://docs.aws.Amazon.com/AWSJavaScriptSDK/latest/AWS/ECS.html#describeTasks-property

これは私の独自のサービスディスカバリメカニズムの基本的な基盤です。これを完全な方法で実行するには、他にも多くの詳細が必要です。ホストコンテナーのリストを最新に保つために、AWS SDKとPostgreSQLデータベースを呼び出すLambda関数を使用しました(動的DNSレジストリのようなものです)。トリックの一部は、各コンテナーのIPとポートを知る必要があることですが、describeTasksはポートのみを返します。これが私が書いた便利な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で依存関係として宣言する必要があります。

Lambda関数を使用してECSサービス検出を処理するための残りのカスタムソリューションは、次の場所にあります。 https://github.com/jakefeasel/sqlfiddle3#setting-up-in-Amazon-web-services

4
Jake Feasel

サービスがHTTPでない場合でも、Classic Elastic Load BalancerをECSサービスに関連付けることができます。 ELBでTCPリスナー(HTTPまたはHTTPs/SSLではなく))を作成し、コンテナの公開されたポートを指すようにします。クラシックELBとアプリケーションELBを使用する場合の欠点は、 ECSサービスごとに個別のELBがあります(追加料金)。

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

1
Andrey