Amazon Web Services ブログ

Amazon ECSコンテナにCloud Native Networkingが登場

この記事はECSのSr. Software Dev EngineerのAnirudh Aithalの寄稿です。

2017年11月14日に、AWSはAmazon ECSのTask Networkingを発表しました。これによって、Elastic Network Interfaceを使ったAmazon EC2のネットワーク機能をタスクに持ち込むことができるようになります。

Elastic Network InterfaceはVPC内のインスタンスにアタッチすることができる仮想的なネットワークインタフェースです。EC2の仮想マシンを起動する時には、インスタンスにネットワークの機能を提供するために自動的に1つのElastic Network Interfaceがプロビジョンされます。

タスクは実行されるコンテナの論理的なグループです。これまでは、Amazon ECSで実行されるタスクはそれが動くEC2ホストのElastic Network Interfaceを共有していました。これからは、新しいawsvpcというネットワークモードを使うことで、Elastic Network Interfaceが直接タスクにアタッチされます。

これによってネットワークの設定を簡略化することができ、VPCが持っているネットワークの全機能、隔離性、そしてセキュリティの制御を各コンテナにEC2インスタンスと同様のレベルで利用することができます。

この記事では、awsvpcモードがどのように動作し、ECSのタスクでどのようにElastic Network Interfaceを使い始めることができるかをご紹介します。

背景: EC2のElastic Network Interface

VPC内にEC2インスタンスを起動する時には、各インスタンスが互いに通信できるようにするために、追加のオーバーレイネットワークを設定する必要はありません。標準で、VPCのルーティングテーブルがインスタンスや他のエンドポイント間での通信をシームレスに実現してくれます。これは、Elastic Network Interfaceと呼ばれるVPC内の仮想的なネットワーク・インタフェースによって可能となっています。全ての起動されるEC2インスタンスは自動的に1つのElastic Network Interface(プライマリネットワークインタフェース)がアサインされます。サブネット、セキュリティグループといった全てのネットワークパラメータは、このプライマリネットワークインタフェースの属性として扱われます。

さらに、各Elastic Network Interfaceは作成時にVPCによってIPv4アドレス(プライマリIPv4アドレス)が割当られます。このプライマリアドレスはユニークでVPC内でルーティング可能なものです。これによって、VPCは実際はフラットなネットワークとなり、ネットワークトポロジを簡潔なものとしてくれます。

Elastic Network InterfaceはVPC上で多様なエンドポイントとの接続を実現するための基本的なビルディングブロックとみなすことができ、そのうえでより高レベルな抽象レイヤを構築することができます。これによってElastic Network Interfacdeは以下の機能を利用することが可能となっています:

  • VPCネイティブなIPv4アドレスとルーティング (VPC上でのインスタンス間や他のエンドポイントとの間)
  • ネットワークトラフィックの隔離
  • ACLとファイアウォールルール(セキュリティグループ)を使ったネットワークポリシーの強制
  • (サブネットのCIDRを通じた)IPv4アドレスレンジの強制

なぜawsvpcを使うのか?

以前はECSはDockerが提供する標準のネットワークの挙動が提供するネットワーク機能に依存した形でコンテナ向けのネットワークスタックを構成していました。デフォルトのBridgeネットワークモードでは、インスタンス上のコンテナ達はdocker0ブリッジを使って互いにつながっています。インスタンス外のエンドポイントと通信する時には、コンテナはこのブリッジを利用し、それが実行されているインスタンスのプライマリネットワークインタフェースを使います。コンテナは、ファイアウォールルール(セキュリティグループ)やIPアドレスは、そのプライマリElastic Network Interfaceのネットワーク属性を共有しまた依存しています。

これは、Dockerによって割当られたIPアドレス(ローカルスコープのアドレスプールから割当られます)を使ってもこれらのコンテナに到達できないことと、細かいNetwork ACLやファイアウォールルールを強制できないことを意味します。代わりに、VPCからはインスタンスのプライマリElastic Network InterfaceのIPアドレスと、マップされたホスト側のポート番号(静的か動的なポートマッピング)の組み合わせによって、コンテナに到達することができます。また、1つのElastic Network Interfaceが複数のコンテナで共有されているので、各コンテナに最適なネットワークポリシーを簡単に作成することが難しくなっています。

awsvpcネットワークモードでは、タスク毎にElastic Network Interfaceをプロビジョンすることによって、これらの課題を解決しています。従って、コンテナ達はこれらのリソースを共有もせず干渉することももはやありません。これによって、以下のことが可能となります:

  • 複数のコンテナのコピーを同じインスタンス上で同じコンテナポートを使っていても、ポートマッピングの様なことをせずに実行することができ、アプリケーションの構成を簡素化できます。
  • 共有ブリッジで帯域が干渉することがないので、アプリケーションのネットワークパフォーマンスを向上させられます。
  • 各Amazon ECS タスクにセキュリティグループのルールを紐付けることで、コンテナ化したアプリケーションに緻密なアクセス制御を強制することができるので、アプリケーションのセキュリティを向上できます。

タスク内のコンテナにセキュリティグループを紐付けることで、送信元のポートとIPアドレスで受け付けるネットワークトラフィックを制限することができます。例えば、インスタンスにはSSHアクセスを許可するが、コンテナにはそれを許可しないということが可能です。あるいは、HTTPのトラフィックを80番ポートでコンテナは受付可能とするポリシーを強制するが、インスタンスではブロックするといったことができます。このようなセキュリティグループのルールを強制することによって、インスタンスもコンテナも攻撃にさらされる範囲を限りなく狭めることができます。

ECSはタスクのためにプロビジョンされるElastic Network Interfaceのライフサイクルを管理しており、オンデマンドで作成し、タスクが停止したら削除します。EC2インスタンスを起動する時と全く同じプロパティをタスクに指定することができます。こうしたタスクの中にあるコンテナは以下のような特徴を持ちます:

  • Elastic Network InterfaceのIPアドレスとDNS名で到達可能
  • Application Load BalancerとNetwork Load BalancerにIPターゲットとして登録可能
  • VPC Flow Logで観測可能
  • セキュリティグループでアクセス制御可能

これはまた、全く同じタスク定義から複数のタスクを同じインスタンス上で実行するときに、ポートの衝突を心配する必要をなくしてくれます。Bridgeネットワークモードの時にはあった、共有のdocker0ブリッジでのポートの変換や帯域の干渉が不要となるので、より高いパフォーマンスを得ることもできます。

始めてみよう

まだECSクラスタを持っていなければ、クラスタ作成ウィザードを使って1つ作成することができます。この記事ではawsvpc-demoをクラスタ名として使います。また、コマンドラインの手順を行うのであれば、最新バージョンのAWS CLISDKをインストールしていることを確認してください。

タスク定義を登録する

タスクネットワークの為にタスク定義で変更する必要がある唯一のものは、ネットワークモードのパラメータをawsvpcにするだけです。ECSコンソールでは、この値はNetwork Modeに入力します。

もしこのタスク定義のコンテナをECSサービスへ登録しようと思っている場合には、コンテナポートをタスク定義で指定します。この例ではNGINXコンテナは80番ポートを開放するように指定しています:

これでnginx-awsvpcという名前のタスク定義が作成され、ネットワークモードがawsvpcに設定されています。以下のコマンド群は、コマンドラインでタスク定義を登録する方法を示しています:

$ cat nginx-awsvpc.json
{
        "family": "nginx-awsvpc",
        "networkMode": "awsvpc",
        "containerDefinitions": [
            {
                "name": "nginx",
                "image": "nginx:latest",
                "cpu": 100,
                "memory": 512,
                "essential": true,
                "portMappings": [
                  {
                    "containerPort": 80,
                    "protocol": "tcp"
                  }
                ]
            }
        ]
}

$ aws ecs register-task-definition --cli-input-json file://./nginx-awsvpc.json

タスクを実行する

このタスク定義でタスクを実行するために、Amazon ECSコンソールのクラスタ画面に行き、Run new taskを選択します。nginx-awsvpcのタスク定義を指定します。次に、このタスクを実行するサブネットの集合を指定します。これらサブネットの少なくとも1つにはECSに登録されたインスタンスが必要です。そうしないと、ECSはElatic Network Interfaceをアタッチする候補を見つけることができません。

コンソールを使うと、Cluster VPCの値を選ぶことで、サブネットの候補を絞り込むことができます:

次に、タスクのセキュリティグループを選択します。この例では、新しいセキュリティグループを作成し、80番ポートの内向きのみを許可します。もしくは、既に作成済のセキュリティグループを選択することもできます。

次に、Run Taskをクリックしてタスクを実行します。

これで実行中のタスクができました。タスクの詳細画面をみると、Elastic Network Interfaceが割り当てられていることと、そのElatic Network Interfaceに紐付いたIPアドレスを確認することができます:

コマンドラインを使って同じことができます:

$ aws ecs run-task --cluster awsvpc-ecs-demo --network-configuration "awsvpcConfiguration={subnets=["subnet-c070009b"],securityGroups=["sg-9effe8e4"]}" nginx-awsvpc $ aws ecs describe-tasks --cluster awsvpc-ecs-demo --task $ECS_TASK_ARN --query tasks[0]
{
    "taskArn": "arn:aws:ecs:us-west-2:xx..x:task/f5xx-...",
    "group": "family:nginx-awsvpc",
    "attachments": [
        {
            "status": "ATTACHED",
            "type": "ElasticNetworkInterface",
            "id": "xx..",
            "details": [
                {
                    "name": "subnetId",
                    "value": "subnet-c070009b"
                },
                {
                    "name": "networkInterfaceId",
                    "value": "eni-b0aaa4b2"
                },
                {
                    "name": "macAddress",
                    "value": "0a:47:e4:7a:2b:02"
                },
                {
                    "name": "privateIPv4Address",
                    "value": "10.0.0.35"
                }
            ]
        }
    ],
    ...
    "desiredStatus": "RUNNING",
    "taskDefinitionArn": "arn:aws:ecs:us-west-2:xx..x:task-definition/nginx-awsvpc:2",
    "containers": [
        {
            "containerArn": "arn:aws:ecs:us-west-2:xx..x:container/62xx-...",
            "taskArn": "arn:aws:ecs:us-west-2:xx..x:task/f5x-...",
            "name": "nginx",
            "networkBindings": [],
            "lastStatus": "RUNNING",
            "networkInterfaces": [
                {
                    "privateIpv4Address": "10.0.0.35",
                    "attachmentId": "xx.."
                }
            ]
        }
    ]
}

awsvpcのタスクの詳細を見ると、Elastic Network Interfaceの詳細についてはattachmentsオブジェクトとして値が返ってきます。containersオブジェクトの中でもこの情報を見ることができます。例:

$ aws ecs describe-tasks --cluster awsvpc-ecs-demo --task $ECS_TASK_ARN --query tasks[0].containers[0].networkInterfaces[0].privateIpv4Address
"10.0.0.35"

まとめ

nginxコンテナはVPC上で10.0.0.35というIPv4アドレスでアクセスすることが可能です。80番ポートでリクエストを受け付けるために、インスタンスのセキュリティグループを編集する必要はありませんので、インスタンスのセキュリティを向上できます。また、アプリケーションの改修を全くせずに、80番ポート以外の全てのポートがブロックされていることも保証できるので、ネットワーク上でタスクの管理が簡単になります。Elastic Network InterfaceのAPI操作はECSが全て面倒を見てくれるので、何もする必要がありません。

タスクネットワークの機能に関するより詳しい情報はECSのドキュメントをご覧下さい。この新しいネットワークモードがインスタンス上でどのように実装されているかの詳細は、詳説: Amazon ECSのタスクネットワークをご覧下さい。

原文: Introducing Cloud Native Networking for Amazon ECS Containers (翻訳: SA岩永)