EKSアドオン運用で本番が止まった|CoreDNS・VPC CNI・kube-proxy トラブル対策

本番環境でDNS障害を経験したEKSアドオン管理の実装知見。バージョン互換性・DNS遅延・ネットワークエラーの原因と対策を、実装パターン付きで解説します。

EKSアドオン管理で本番が止まった日

先日、本番環境でDNS解決がぶっ壊れた。朝7時に急いで対応したら、原因はEKSアドオンのバージョン互換性問題だった。その時点で初めて気づいたんだけど、うちのチームってEKSアドオンをちゃんと管理できてなかったんですよね。

AWS管理コンソールからたまに「アドオン更新があります」って通知が来るんだけど、「後で」をずっと押してた。CoreDNS・VPC CNI・kube-proxy、この3つがどう関係してるのか、正直よくわかってなかったし。本番環境で実装してみてやっと見えてきた部分を、ここに記録として残しておこうと思う。

EKSアドオンって何が難しいのか

正直最初、「アドオン = AWS側で管理してくれる便利なやつ」くらいの認識だった。でもそれ大間違い。アドオンのバージョン・K8sクラスタのバージョン・Node AMIのバージョン、この3つの組み合わせが全部マッチしてないと、細かいバグが積み重なるんですよ。

うちが経験したのは、こんな感じ。

  • CoreDNS:DNS TTL切れのタイミングでたまにタイムアウト
  • VPC CNI:ENI割り当てで瞬間的なネットワークエラー
  • kube-proxy:Iptables ルール更新の遅延でパケットロス

個別には些細な問題に見えるんだけど、本番環境で複数同時に発生すると、トラブルの原因特定が地獄になる。深夜のPagerDuty通知との闘いはほんとに勘弁してほしい。

実装した監視設定

うちが実装した監視構成を図で説明すると、こんな感じだ:

flowchart TB
    subgraph "EKS Cluster"
        CoreDNS["CoreDNS Pod"]
        VPCCNIPlugin["VPC CNI Plugin"]
        KubeProxy["kube-proxy DaemonSet"]
        Monitoring["Prometheus + CloudWatch"]
    end
    
    subgraph "CloudWatch"
        Metrics["カスタムメトリクス"]
        Logs["コンテナログ"]
    end
    
    subgraph "Alert"
        SNS["SNS Notification"]
        PagerDuty["PagerDuty"]
    end
    
    CoreDNS -->|DNS Query Latency| Metrics
    VPCCNIPlugin -->|ENI Allocation Time| Metrics
    KubeProxy -->|Iptables Rules Count| Metrics
    Monitoring -->|Emit| Logs
    Metrics -->|Threshold Exceeded| SNS
    SNS -->|Alert| PagerDuty

この構成で、アドオンのヘルスを可視化できるようになった。地味に便利なんだけど、セットアップはちょっと手間がかかる。

CoreDNS:DNS遅延のトラブルシューティング

最初に火がついたのがこれ。本番環境で「たまに遅い」ってバグ報告が上がってきた。「たまに」ってのが最悪で、再現性がなくて半日潰れたんですよ。

原因を特定するために、こんなメトリクス収集を実装した:

apiVersion: v1
kind: ConfigMap
metadata:
  namespace: kube-system
  name: coredns
data:
  Corefile: |
    . {
        errors
        health :8080
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
          pods insecure
          fallthrough in-addr.arpa ip6.arpa
          ttl 30
        }
        prometheus :9153
        forward . /etc/resolv.conf {
          max_concurrent 1000
          policy round_robin
        }
        cache 30
        loop
        reload
        loadbalance round_robin
    }

ポイントは prometheus :9153 で、CoreDNS自体がPrometheusメトリクスを吐き出してくれるんですよね。うちはこれをCloudWatch Containerinsightsで吸い上げて、coredns_dns_request_duration_seconds を監視してる。

実装した監視コマンドを実行してみたら、結果として以下がわかった。

  • DNS クエリが10ms以下だったのに、タイムアウトエラーが週1-2回発生
  • クエリ自体は成功してるのに、クライアント側で timeout になってる
  • 原因:Pod内のresolv.conf設定が、kubelet更新時に一瞬リセットされてた
# 実際に運用してる監視コマンド
kubectl top pod -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=100 | grep -i error

# Prometheus Query で DNS latency追跡
rate(coredns_dns_request_duration_seconds_sum[5m]) / rate(coredns_dns_request_duration_seconds_count[5m])

対策として、CoreDNS Pod再起動時の DNS クエリ再試行ロジックをクライアント側に入れた。本当は根本修正すべきだったけど、タイムボックス決まってたので応急処置で対応。実装してから半年経つけど、今のところ DNS 絡みのアラートは出てない。

VPC CNI:ENI割り当ての遅延問題

これが一番やっかいだった。EKS立ち上げ初期は意識してなかったけど、Pod数が増えると ENI(Elastic Network Interface)の割り当てが追いつかなくなるんですよ。

VPC CNI プラグインって、事前に ENI を確保して、Pod起動時にそこから IP アドレス割り当てる仕組みなんですよ。だから Node あたりの ENI 数が上限に来ると、新しい Pod が Pending のまま止まる。

実装した Terraform コードはこんな感じ:

resource "helm_release" "vpc_cni" {
  name             = "aws-vpc-cni"
  repository       = "https://aws.github.io/eks-charts"
  chart            = "aws-vpc-cni"
  namespace        = "kube-system"
  version          = "1.14.1"

  values = [
    yamlencode({
      env = {
        # ENI をプリウォーム(事前確保)する設定
        WARM_ENI_TARGET      = "1"      # 常に1つ予備を用意
        WARM_IP_TARGET       = "10"     # IP アドレスで管理
        MINIMUM_IP_TARGET    = "5"      # 最低限5個は確保
        MAX_ENI              = "5"      # インスタンスタイプに応じて調整
        ENI_CONFIG_LABEL_DEF = "failure-domain.beta.kubernetes.io/zone"
      }
      # Prometheus メトリクス有効化
      enableWindowsIpam = false
      metrics = {
        enabled = true
        port    = "61678"
      }
    })
  ]
}

# CloudWatch アラーム:Available IP アドレス数が危険水準
resource "aws_cloudwatch_metric_alarm" "vpc_cni_ip_shortage" {
  alarm_name          = "eks-vpc-cni-available-ips"
  comparison_operator = "LessThanThreshold"
  evaluation_periods  = "2"
  metric_name         = "vpc_cni_available_ip_count"
  namespace           = "AWS/EKS"
  period              = "300"
  statistic           = "Average"
  threshold           = "5"
  alarm_actions       = [aws_sns_topic.alerts.arn]
}

WARM_IP_TARGET の設定が肝で、これを低くしすぎるとPod起動が遅くなるし、高くしすぎると無駄な IP が貯まってコスト増えるんですよね。うちは実測で「10」に落ち着いた。

実際に経験した問題はこうだった。t3.medium(IP数 11)に 20 Pod 以上走らせようとしたら、Pod が Pending のまま 10 分放置されてた。CloudWatch に「available IPs: 0」って警告出たけど見てなかったんですよ。本当に危ない。

これを解決するために、Karpenter の autoscaling 設定も見直した。VPC CNI の制約を考慮して、Node 追加のタイミングを早めるようにした。

kube-proxy:Iptables ルール爆発

これは本当に気づきにくいバグだった。kube-proxy が管理する iptables ルールが、あるタイミングで一気に増えることがある。

原因は Service 数の増加や Pod の頻繁な再起動なんですよ。kube-proxy が古いルールをちゃんと削除せずに、新しいルールを追加し続けることがある。バージョンによって挙動が違うのがまた厄介。

うちで実装した監視はこんな感じ:

# Node 内でルール数をカウント
sudo iptables -L -n | grep KUBE | wc -l

# これを metrics で吐き出す
curl -s http://localhost:10249/metrics | grep kubeproxy_iptables_rules_total

Prometheus で追跡することで、「このバージョンだと危ない」ってパターンが見えてきた。個人的には、kube-proxy のバージョン間での動作差が大きすぎるんじゃないかと思うんですよね。

2026年時点では、kube-proxy は IPVS モードも選べるようになってる。うちはまだ iptables を使ってるけど、次の更新では IPVS に移行検討中だ。

apiVersion: v1
kind: ConfigMap
metadata:
  namespace: kube-system
  name: kube-proxy
data:
  config.conf: |
    apiVersion: kubeproxy.config.k8s.io/v1alpha1
    kind: KubeProxyConfiguration
    mode: "ipvs"  # または "iptables"
    ipvs:
      syncPeriod: 30s
      minSyncPeriod: 5s
      scheduler: "rr"

バージョン互換性マトリックスの実装

本番が安定した大きな理由が、バージョン互換性を明確にしたことなんですよ。うちは 3ヶ月ごとにアドオンバージョンを更新するサイクルにした。

このマトリックスを Notion で管理してる:

K8s VerCoreDNSVPC CNIkube-proxy検証済み本番投入日
1.281.10.11.14.11.28.12026-02-15
1.291.10.41.14.11.29.02026-04-20
1.301.10.61.15.01.30.0TBD

これを terraform の data source と組み合わせて、自動バージョン選定できるようにした:

locals {
  addon_versions = {
    "1.28" = {
      coredns    = "v1.10.1-eksbuild.2"
      vpc_cni    = "v1.14.1-eksbuild.1"
      kube_proxy = "v1.28.1-eksbuild.1"
    }
    "1.29" = {
      coredns    = "v1.10.4-eksbuild.1"
      vpc_cni    = "v1.14.1-eksbuild.1"
      kube_proxy = "v1.29.0-eksbuild.1"
    }
  }
  
  cluster_version = aws_eks_cluster.main.version
  selected_versions = local.addon_versions[local.cluster_version]
}

resource "aws_eks_addon" "coredns" {
  cluster_name             = aws_eks_cluster.main.name
  addon_name               = "coredns"
  addon_version            = local.selected_versions.coredns
  resolve_conflicts_on_create = "OVERWRITE"
  resolve_conflicts_on_update = "OVERWRITE"
}

AWS構成図:本番環境でのアドオン管理

graph TB
    subgraph "EKS Cluster (1.30)"
        subgraph "Control Plane"
            CP["EKS Control Plane<br/>K8s 1.30"]
        end
        
        subgraph "Addons Management"
            ADDON1["CoreDNS<br/>v1.10.6-eksbuild.1"]
            ADDON2["VPC CNI<br/>v1.15.0-eksbuild.2"]
            ADDON3["kube-proxy<br/>v1.30.0-eksbuild.1"]
        end
        
        subgraph "Worker Nodes"
            NODE1["Node 1<br/>t3.large<br/>IP: 10.0.1.10"]
            NODE2["Node 2<br/>t3.large<br/>IP: 10.0.2.10"]
        end
        
        subgraph "Monitoring"
            PROM["Prometheus<br/>Scrape :9153"]
            CW["CloudWatch<br/>Container Insights"]
        end
    end
    
    subgraph "VPC Network"
        ENI1["Primary ENI<br/>eth0"]
        ENI2["Secondary ENI<br/>eth1"]
    end
    
    subgraph "External Services"
        EXT_DNS["Route 53<br/>External DNS"]
        LOGS["CloudWatch Logs<br/>Addon Events"]
    end
    
    CP -->|manages| ADDON1
    CP -->|manages| ADDON2
    CP -->|manages| ADDON3
    
    ADDON1 -->|DNS queries| EXT_DNS
    ADDON2 -->|IP allocation| ENI1
    ADDON2 -->|IP allocation| ENI2
    ADDON3 -->|route traffic| NODE1
    ADDON3 -->|route traffic| NODE2
    
    NODE1 -->|publish metrics| PROM
    ADDON1 -->|publish metrics| PROM
    ADDON2 -->|publish metrics| PROM
    
    PROM -->|scrape| CW
    ADDON1 -->|logs| LOGS
    ADDON2 -->|logs| LOGS
    ADDON3 -->|logs| LOGS

CloudWatch メトリクス収集の実装

アドオンの健全性を可視化するために、カスタム CloudWatch メトリクスを送信するようにした:

#!/usr/bin/env python3
import boto3
import subprocess
import json
from datetime import datetime

cloudwatch = boto3.client('cloudwatch')

def get_addon_metrics():
    metrics = []
    
    # CoreDNS メトリクス
    try:
        dns_query = subprocess.run([
            'kubectl', 'exec', '-n', 'kube-system',
            '-c', 'coredns',
            'coredns-xxxxx',
            'curl', '-s', 'localhost:9153/metrics'
        ], capture_output=True, text=True, timeout=5)
        
        for line in dns_query.stdout.split('\n'):
            if 'coredns_dns_request_duration_seconds_bucket' in line:
                metrics.append(parse_prometheus_metric(line))
    except Exception as e:
        print(f"CoreDNS metrics error: {e}")
    
    # VPC CNI メトリクス
    try:
        cni_metrics = subprocess.run([
            'kubectl', 'exec', '-n', 'kube-system',
            '-l', 'k8s-app=aws-node',
            'curl', '-s', 'localhost:61678/metrics'
        ], capture_output=True, text=True, timeout=5)
        
        available_ips = extract_metric(cni_metrics.stdout, 'awscni_available_ip_count')
        metrics.append({
            'MetricName': 'vpc_cni_available_ips',
            'Value': available_ips,
            'Unit': 'Count'
        })
    except Exception as e:
        print(f"VPC CNI metrics error: {e}")
    
    return metrics

def publish_metrics(metrics):
    for metric in metrics:
        cloudwatch.put_metric_data(
            Namespace='EKS/Addons',
            MetricData=[{
                'MetricName': metric['MetricName'],
                'Value': metric['Value'],
                'Unit': metric.get('Unit', 'None'),
                'Timestamp': datetime.utcnow()
            }]
        )

if __name__ == '__main__':
    metrics = get_addon_metrics()
    publish_metrics(metrics)
    print(f"Published {len(metrics)} metrics")

これを CloudWatch Event + Lambda で 5 分ごとに実行してる。本番の信頼性がぐっと上がった感じがする。

Karpenter との相性

EKS アドオンと Karpenter の相性も重要なポイントなんですよ。特に VPC CNI との組み合わせで、Node 追加のタイミングが工夫必要になる。

設定例を示すと:

apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
  name: default
spec:
  requirements:
    - key: karpenter.sh/capacity-type
      operator: In
      values: ["on-demand", "spot"]
    - key: node.kubernetes.io/instance-type
      operator: In
      values: ["t3.large", "t3.xlarge", "m5.large"]
  limits:
    resources:
      cpu: 1000
      memory: 1000Gi
  
  # VPC CNI の IP 枯渇を防ぐため、容量を多めに確保
  ttlSecondsAfterEmpty: 30
  ttlSecondsUntilExpired: 2592000
  providerRef:
    name: default

Karpenter の autoscaling が VPC CNI の IP 枯渇より遅れると、Pod Pending 地獄になるんですよね。なので Karpenter の ttlSecondsAfterEmpty は短めに(30秒)設定した。

バージョン更新の手順化

毎回手探りでやってると危ないので、更新手順を Runbook 化した。

1. 事前検証(ステージング環境)

# アドオン新バージョンを試す
aws eks update-addon --cluster-name staging-eks \
  --addon-name coredns --addon-version v1.10.6-eksbuild.1

# ヘルスチェック
kubectl wait --for=condition=Active pod \
  -l k8s-app=kube-dns -n kube-system --timeout=300s

# メトリクス監視(10分)
watch 'kubectl top pod -n kube-system -l k8s-app=kube-dns'

2. 本番更新(メンテナンスウィンドウ)

# 更新前ロールバック準備
CURRENT_VERSION=$(aws eks describe-addon --cluster-name prod-eks \
  --addon-name coredns --query 'addon.addonVersion' --output text)

# 更新実行
aws eks update-addon --cluster-name prod-eks \
  --addon-name coredns --addon-version v1.10.6-eksbuild.1

# ロールバック関数
rollback_addon() {
    aws eks update-addon --cluster-name prod-eks \
      --addon-name coredns --addon-version $CURRENT_VERSION
}

3. 事後監視(30分)

更新後は以下を確認してる。

  • CloudWatch アラーム:すべて Green
  • Pod 再起動数:0 増加
  • API レスポンスタイム:p99 < 100ms

実装してみてわかったことなんだけど、この手順化がほんとに大事だ。毎回違うやり方でやってると、いずれ失敗する。

まとめ

EKS アドオン管理、実装してみてわかったことをまとめるとこんな感じだ。

バージョン互換性は絶対に外せない

K8s・アドオン・Node AMI の 3 つの組み合わせを記録して、Terraform で自動選定できるようにする。これだけで本番のトラブル率が劇的に下がる。

監視メトリクスを先に決める

CoreDNS は DNS query latency・request rate、VPC CNI は available IPs・ENI allocation time、kube-proxy は iptables rule count。これらを CloudWatch で常時監視することで、問題を早期発見できる。

更新は絶対に段階的に

ステージング環境で 1-2 週間検証して、本番は大型連休前後を避ける。ロールバック手順も事前テストしておかないと、本当に詰む。

Karpenter と組み合わせるなら VPC CNI の制約を意識する

VPC CNI の IP 枯渇を想定して Node autoscaling を早める。Node capacity を多めに確保することで、Pod Pending を防げる。

次のアクションとしては、IPVS モードへの移行検討と、アドオン更新を全自動化する仕組みを整える予定だ。現在の手動対応は十分安定してるけど、スケーリングするなら自動化必須だと思う。

実務で困ってることあれば、ぜひ声かけてもらえると幸いです。

U

Untanbaby

ソフトウェアエンジニア|AWS / クラウドアーキテクチャ / DevOps

10年以上のIT実務経験をもとに、現場で使える技術情報を発信しています。 記事の誤りや改善点があればお問い合わせからお気軽にご連絡ください。

関連記事