🐳

ECS FargateでDNSキャッシュを利用するためのワークアラウンド

2024/04/06に公開

要約

  • ECS Fargate ではタスク定義で DNS 設定を行うことができない
  • 名前解決結果をキャッシュしたいコンテナの起動時に /etc/resolv.conf を上書きするワークアラウンドがある

やりたいこと

やりたいことは ECS Fargate タスクのコンテナでの名前解決結果をキャッシュすることです。
動機としては以下の2つです。

Fargate での問題

ECS タスク定義には dnsServers というパラメータがありますが、Fargate では利用することができません。
その理由は、Fargate では awsvpc ネットワークモードしか利用できないためです。
dnsServers パラメータは下記に記載しているように awsvpc ネットワークモードではサポートされていません。

注記
awsvpc ネットワークモードを使用するタスクもしくは Windows コンテナでは、このパラメータはサポートされません。

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task_definition_parameters.html

この件については AWS の containers-roadmap にも issue が挙げられていますが2024年現在は対応されていません。
https://github.com/aws/containers-roadmap/issues/1123

ワークアラウンド

上記の問題を回避するためのワークアラウンドとして、コンテナの起動時に /etc/resolv.conf を書き換える方法があります。
(というかそれしか無いかもしれません)

DNS キャッシュサーバーをサイドカーコンテナとして起動しておき、メインのコンテナでは起動時に /etc/resolv.conf を以下のような内容で上書きします。

nameserver 127.0.0.1
nameserver 169.254.169.253

search ap-northeast-1.compute.internal

1行目の nameserver 127.0.0.1 によってデフォルトの DNS サーバーを Fargate タスク内で動いている DNS キャッシュサーバーコンテナに設定します。

DNS キャッシュサーバーコンテナが動作していない場合に備えて2行目で AWS Route 53 Resolver (169.254.169.253) にフォールバックするようにしています。

4行目の search ap-northeast-1.compute.internal は ap-northeast-1 リージョンで Fargate タスクを立ち上げた際にデフォルトで設定されている内容です。
リージョンごとにドメインが変わります。
もし不要であれば削除して問題ありません。

具体例

DNS キャッシュサーバーとして CoreDNS を使った具体例を示します。

CoreDNS イメージ定義

Corefile で AWS Route 53 Resolver (169.254.169.253) への forward 設定と cache の有効化をおこないます。
ここでは IP が共通でやりやすいため 169.254.169.253 を使っていますが、VPC+2 のアドレスでも問題ありません。

Corefile
. {
    bind 127.0.0.1
    forward . 169.254.169.253
    cache
}
Dockerfile
FROM coredns/coredns:1.11.1

COPY ./Corefile /etc/coredns/Corefile

CMD [ "-conf", "/etc/coredns/Corefile" ]

アプリイメージ定義

テスト用のアプリとして、1秒ごとに www.google.com の名前解決をし続けるイメージを作成します。

entrypoint.sh
#!/bin/bash

cat /resolv.conf | tee /etc/resolv.conf

exec "$@"
main.sh
#!/bin/bash

while true; do
    dig www.google.com
    sleep 1
done
resolv.conf
nameserver 127.0.0.1
nameserver 169.254.169.253

search ap-northeast-1.compute.internal
Dockerfile
FROM debian:bullseye-slim

RUN apt -y update && apt -y install dnsutils

COPY ./entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

COPY ./resolv.conf /resolv.conf

COPY ./main.sh /main.sh
RUN chmod +x /main.sh

ENTRYPOINT [ "/entrypoint.sh" ]

CMD [ "/main.sh" ]

ポイントとしては、前述のように entrypoint.sh/etc/resolv.conf を上書きしている点です。

タスク定義

CoreDNS コンテナとアプリコンテナを同じタスクに入れます。
CoreDNS コンテナが起動する前にアプリコンテナが起動してしまわないよう dependsOn で起動順序を指定しています。

{
    "containerDefinitions": [ 
       {
          "essential": true,
          "image": "012345678901.dkr.ecr.ap-northeast-1.amazonaws.com/app:latest",
          "logConfiguration": { 
             "logDriver": "awslogs",
             "options": { 
                "awslogs-group" : "/ecs/app",
                "awslogs-create-group": "true",
                "awslogs-region": "ap-northeast-1",
                "awslogs-stream-prefix": "app"
             }
          },
          "name": "app",
          "dependsOn": [
            {
               "containerName": "coredns",
               "condition": "START"
            }
          ]
       },
       {
          "essential": true,
          "image": "012345678901.dkr.ecr.ap-northeast-1.amazonaws.com/coredns:latest",
          "logConfiguration": { 
             "logDriver": "awslogs",
             "options": { 
                "awslogs-group" : "/ecs/app",
                "awslogs-create-group": "true",
                "awslogs-region": "ap-northeast-1",
                "awslogs-stream-prefix": "coredns"
             }
          },
          "name": "coredns"
       }
    ],
    "cpu": "256",
    "executionRoleArn": "arn:aws:iam::012345678901:role/ecsTaskExecutionRole",
    "family": "app",
    "memory": "512",
    "networkMode": "awsvpc",
    "requiresCompatibilities": [ 
        "FARGATE" 
     ]
 }

動作

上記のタスクを Fargate タスクとして実行すると、アプリコンテナのログとして1秒おきに CloudWatch Logs に以下のような dig の実行結果が出力されます。

; <<>> DiG 9.16.48-Debian <<>> www.google.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 22181
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: e861b1440ec42525 (echoed)
;; QUESTION SECTION:
;www.google.com. IN A
;; ANSWER SECTION:
www.google.com. 249 IN A 142.251.42.164
;; Query time: 52 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Fri Apr 05 15:44:29 UTC 2024
;; MSG SIZE rcvd: 85

このログから、アプリコンテナの DNS サーバーが 127.0.0.1:53 (CoreDNSコンテナ)に設定されており、正しく名前解決できていることがわかります。
(本当にキャッシュされているかどうかはコンテナの中に入って tcpdump とかで確認する必要があると思いますが、面倒なので今回は CoreDNS へ DNS クエリが送られていることのみ確認)

おわりに

今回紹介した方法はあくまでワークアラウンドであり、公式で動作が保証されたものではありません。
実際に利用される場合は十分に検証を行ったうえでご利用ください。

Discussion