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 Ver | CoreDNS | VPC CNI | kube-proxy | 検証済み | 本番投入日 |
|---|---|---|---|---|---|
| 1.28 | 1.10.1 | 1.14.1 | 1.28.1 | ✓ | 2026-02-15 |
| 1.29 | 1.10.4 | 1.14.1 | 1.29.0 | ✓ | 2026-04-20 |
| 1.30 | 1.10.6 | 1.15.0 | 1.30.0 | △ | TBD |
これを 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 モードへの移行検討と、アドオン更新を全自動化する仕組みを整える予定だ。現在の手動対応は十分安定してるけど、スケーリングするなら自動化必須だと思う。
実務で困ってることあれば、ぜひ声かけてもらえると幸いです。