EKS FargateのDatadog導入に躓いた話
はじめに
Datadog導入するにあたってインフラメトリクスを取得するのに困難したので躓いたポイントをまとめて行きたいと思います。
Datadogのアーキテクチャ
以下KubernetesにDatadogを導入した際のアーキテクチャです。
公式には以下のようなことが記述されています。
Cluster Agent は、API サーバーとノードベースの Agent 間のプロキシとして機能します。これにより、API サーバーへの直接の負荷が軽減されるだけでなく、ノードベースの Agent はノードレベルのデータの収集に集中できます。一方、Cluster Agent はマスターノードからクラスターレベルのデータを収集します。Cluster Agent は、クラスターレベルのメタデータをノードベースの Agent に送り返すため、ローカルで収集されたメトリクスを、クラスター全体で一貫したタグを設定して強化できます。また、ノードベースの Agent は API サーバーからこのデータのクエリする必要がなくなりましたので、RBAC ルールを減らしてメトリクスとメタデータのみを kubelet から読み取ることもがきます。
Ref: https://www.datadoghq.com/ja/blog/datadog-cluster-agent
何で躓いたのか?
kubeletへの権限エラー
NodeAgentはFargateの場合apiserverからノードへプロキシして、ノード内のメトリクス収集を行っています。
if kubeletProxyEnabled {
// Explicitly disable HTTP to reach APIServer
kubeletHTTPPort = 0
httpsPort, err := strconv.ParseUint(os.Getenv("KUBERNETES_SERVICE_PORT"), 10, 16)
if err != nil {
return nil, fmt.Errorf("unable to get APIServer port: %w", err)
}
kubeletHTTPSPort = int(httpsPort)
if config.Datadog.Get("kubernetes_kubelet_nodename") != "" {
kubeletPathPrefix = fmt.Sprintf("/api/v1/nodes/%s/proxy", kubeletNodeName)
apiServerHost := os.Getenv("KUBERNETES_SERVICE_HOST")
potentialHosts = &connectionInfo{
hostnames: []string{apiServerHost},
}
log.Infof("EKS on Fargate mode detected, will proxy calls to the Kubelet through the APIServer at %s:%d%s", apiServerHost, kubeletHTTPSPort, kubeletPathPrefix)
} else {
return nil, errors.New("kubelet proxy mode enabled but nodename is empty - unable to query")
}
} else {
// Building a list of potential ips/hostnames to reach Kubelet
potentialHosts = getPotentialKubeletHosts(kubeletHost)
}
// Checking HTTPS first if port available
var httpsErr error
if kubeletHTTPSPort > 0 {
httpsErr = checkKubeletConnection(ctx, "https", kubeletHTTPSPort, kubeletPathPrefix, potentialHosts, &clientConfig)
if httpsErr != nil {
log.Debug("Impossible to reach Kubelet through HTTPS")
if kubeletHTTPPort <= 0 {
return nil, httpsErr
}
} else {
return newForConfig(clientConfig, kubeletTimeout)
}
}
ですので、ClusterRoleには以下のように権限を付与します。
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: datadog-agent
rules:
- apiGroups:
- ""
resources:
- nodes
- namespaces
verbs:
- get
- list
- apiGroups:
- ""
resources:
- nodes/metrics
- nodes/spec
- nodes/stats
- nodes/proxy
- nodes/pods
- nodes/healthz
verbs:
- get
Namespace間通信
ClusterAgentは「datadog」、NodeAgentは「golang」というnamespaceで運用しているので異なるnamespace間で通信しなければなりません。その為にClusterAgentのServiceのエンドポイントが必要です。ですので、DD_CLUSTER_AGENT_URL
にて設定しました。
svc.cluster.local
としているのはデフォルトで使用するNameServerでは名前解決できないからです。
- name: DD_CLUSTER_AGENT_URL
value: "https://datadog-agent-cluster-agent.datadog.svc.cluster.local:5005"
tokenの不一致
ClusterAgentとNodeAgentで異なるtokenを設定してしまっており、403エラーが出ていました。
if len(tok) < 2 || tok[1] != GetAuthToken() {
err = fmt.Errorf("invalid session token")
http.Error(w, err.Error(), 403)
}
ClusterAgentはデフォルトでNodeAgentの両方と共有するためのSecretにランダムトークンを自動生成します。
util.CreateAndSetAuthToken()
しかし、異なるNamespaceで運用する場合は、生成されたNodeAgent側からSecretを参照できなくなります。ですので、私は手動でtokenをClusterAgent、NodeAgent側に作成し、Secretとして参照できるようにしました。(clusterAgent.tokenExistingSecretにて設定)
ClusterAgent側では以下のように設定します。(HelmとArgoCDを使用)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: datadog-agent
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://helm.datadoghq.com
targetRevision: 2.27.2
helm:
values: |
targetSystem: linux
datadog:
apiKeyExistingSecret: datadog-secrets
appKeyExistingSecret: datadog-secrets
leaderElection: true
collectEvents: true
kubeStateMetricsEnabled: false
kubeStateMetricsCore:
enabled: true
logLevel: INFO
apm:
enabled: true
processAgent:
enabled: true
processCollection: true
clusterAgent:
tokenExistingSecret: datadog-secrets
enabled: true
metricsProvider:
enabled: true
rbac:
create: true
clusterChecks:
enabled: true
clusterChecksRunner:
enabled: true
replicas: 2
rbac:
create: true
agents:
rbac:
create: true
parameters:
- name: "datadog.tags[0]"
value: "env:prd"
- name: "datadog.tags[1]"
value: "system:golang"
- name: "datadog.clusterName"
value: "golang-cluster"
chart: datadog
destination:
server: 'https://kubernetes.default.svc'
namespace: datadog
syncPolicy:
automated:
prune: true
selfHeal: true
---
apiVersion: external-secrets.io/v1alpha1
kind: SecretStore
metadata:
name: datadog
namespace: datadog
spec:
provider:
aws:
service: SecretsManager
region: ap-northeast-1
---
apiVersion: external-secrets.io/v1alpha1
kind: ExternalSecret
metadata:
name: datadog-secrets
namespace: datadog
spec:
refreshInterval: 1m
secretStoreRef:
name: datadog
kind: SecretStore
target:
name: datadog-secrets
creationPolicy: Owner
data:
- secretKey: api-key
remoteRef:
key: datadog/apikey
- secretKey: app-key
remoteRef:
key: datadog/appkey
# secretKeyはtokenにしなければなりません
- secretKey: token
remoteRef:
key: datadog/cluster-agent-token
NodeAgent側では以下のように設定します。
- name: DD_CLUSTER_AGENT_AUTH_TOKEN
valueFrom:
secretKeyRef:
name: go-secrets
key: token
apiVersion: external-secrets.io/v1alpha1
kind: SecretStore
metadata:
name: golang
namespace: golang
spec:
provider:
aws:
service: SecretsManager
region: ap-northeast-1
---
apiVersion: external-secrets.io/v1alpha1
kind: ExternalSecret
metadata:
name: golang-secrets
namespace: golang
spec:
refreshInterval: 1m
secretStoreRef:
name: golang
kind: SecretStore
target:
name: golang-secrets
creationPolicy: Owner
data:
- secretKey: datadog_api_key
remoteRef:
key: datadog/apikey
# token
- secretKey: token
remoteRef:
key: datadog/cluster-agent-token
まとめ
EKSのEC2構成ではDaemonSetで簡単に導入できますが、Fargate構成では導入にあたって考慮するべき点がたくさんありました。これに加えて、EC2とFargateのマルチ構成の場合ではセキュリティーグループのルールなども考慮しなければいけないと思い、意外と大変だなあと感じました。
その他
調査は以下のコマンドたちを用いました。
datadog-cluster-agent status
cat /var/log/datadog/datadog-cluster-agent.log
agent status
cat /var/log/datadog/agent.log
参考文献
- Amazon EKS on AWS Fargate
- Cluster Agentのドキュメント
Discussion