EventBridgeを介してDatadogとIncident Managerを連携させてみる

背景

Datadogで発生したアラートをもとにオンコールしたい場合、PagerDutyを使うのが一番簡単ですが、Incident Managerを使うともっと安価にオンコールが実現できるのではということで、連携してみました。

連携の概要

Datadog → EventBridge → Incident Manager → オンコール

事前準備

DatadogでEventBridgeのIntegrationを有効化します。

https://app.datadoghq.com/account/settings#integrations/amazon-event-bridge をConfigurationの部分を読むか以下のドキュメントを参考にします。 (Datadog IAM Roleに events:CreateEventBus権限を付与するのを忘れないように) docs.datadoghq.com

設定

まずにIncident Managerから連絡先、エスカレーションプラン、対応プランの設定をします。
ここは特に迷うところはないと思います。

次にEventBridgeの設定をします。ここがちょっと分かりづらい。
EventBridgeのルールの画面にいき、イベントバスにaws.partner/datadog.com/datadogを選択、ルールの作成ボタンを押す。
ルールの作成画面では、 イベント一致パターンサービスごとの事前定義パターンを選び、サービスプロバイダーにサービスパートナーを選び、サービス名にDatadogを選ぶ。
カスタムイベントバスまたはパートナーイベントバスにはaws.partner/datadog.com/datadogを選ぶ。
ターゲットを選択では、Incident Manager レスポンスプランを選び、レスポンスプランに作成済みのプランを選択する。
で、作成ボタン押してルールの作成が完了。

最後にDatadogのアラートの設定をします。
通知文に、 @awseventbridge-datadog のように@awseventbridge-<event bus name>が指定できるのでします。

注意点

Datadogからアラートを発生させたのに、Incident Managerのインシデントがオープンにならない場合、CloudTrailをみると原因が分かるケースがあります。
CloudTrailのイベント履歴画面に行き、イベント名にStartIncidentと入力します。以下のように一致が見つかりませんと出ますが嘘なのでそのままEnterキーを押すと検索結果がでます。
f:id:shepherdMaster:20211218130548p:plain イベント履歴の詳細をみると、'triggerDetails.rawData' failed to satisfy constraint: Member must have length less than or equal to 4000"のようなエラー文が出てるのが分かります。どうやらDatadogから送られてくるデータが多すぎるようです。
その場合は、EventBridgeのターゲットを選択入力の設定で、Incident Managerに送る情報量を減らすとよいです。
この入力の設定は分かりづらいのですが、以下のブログなどを参考にすると分かりやすいかと思います。
EventBridgeの入力トランスフォーマーでSNSのメール通知内容を整形してみた

感想

fujiwaraさんのツイートにもあるのですが、インシデントが開いていると同じインシデントにまとまってしまうのがネックですね。 またIncident Managerの画面からどういったアラートなのかぱっとみ分かりづらいというのもあります。
個人的には素直にPagerDutyを使ったほうがいいかと思いました。

Locustでリクエストfail時のログ(日時とか)を出力する方法

負荷試験ツールのLocustでリクエストがfailするとFailuresタブから情報が見れるのですが、いつfailしたかが分かりません。

f:id:shepherdMaster:20210320140435p:plain

locustにはevent hook機能があり、以下のように書けばどんなリクエストがいつfailしたか標準出力されます。

from locust import events

@events.request_failure.add_listener
def request_failure_handler(request_type, name, response_time, exception, **kwargs):
    print("Request Failed! time:%s,  name:%s, exception:%s" % (datetime.datetime.now(), name, exception))

標準出力

Request Failed! time:2021-03-20 14:04:11.602999,  name:/hello, exception:404 Client Error: Not Found for url: http://www.example.com/hello
Request Failed! time:2021-03-20 14:04:11.761363,  name:/world, exception:404 Client Error: Not Found for url: http://www.example.com/world

EKS Cluster Autoscalerとログ保存用Daemonsetの組み合わせでスケールイン時にログが失われる問題

EKS Cluster Autoscalerとログ保存用Daemonsetの組み合わせでNodeスケールイン時にログが失われる問題に遭遇した。

前提

現象

Cluster AutoscalerがNodeをスケールインさせるとき、一部のアプリケーションログがS3に保存されていないことをに気がついた。
まずスケールイン処理が始まるとアプリケーションPodはスケールインするNodeから他のNodeに退避がされる(正確には他のNodeに先にPodを作成しそれからスケールインするNodeにのっているPodはターミネート)。
しかしDaemonSetはターミネート処理がされる前にEC2インスタンスの終了が始まりDaemonSetも強制終了されているようにみえる。

調査

該当するissueがあった
Why not to drain daemonset pods? · Issue #1578 · kubernetes/autoscaler

そして1.20.0で改善が入っていた
Release Cluster Autoscaler 1.20.0 · kubernetes/autoscaler · GitHub

Add best-effort eviction for DaemonSet pods while scaling down non-empty nodes

このPR
add daemonset eviction for non-empty nodes by yaroslava-serdiuk · Pull Request #3701 · kubernetes/autoscaler · GitHub

さっそくCluster Autoscaler 1.20を試す。

すると、スケールイン時にDaemonsetがターミネートされるようになったが、 アプリケーションPodのターミネート処理と同時にDaemonsetのターミネート処理が行われる。

通常、WebアプリケーションPodはpreStop内で新規リクエストが来なくなるまでsleepを入れることが多い。そのためアプリケーションPodの終了処理の前にFluentd Daemonsetが終了してしまうとその間(Fluentd Daemonsetが終了してからアプリケーションPodがリクエスト処理終了するまで)のログが保存されないということが発生する。

じゃあDaemonsetのpreStop内でアプリケーションPodが終了するまでsleepをしてそれからDaemonsetのターミネート処理(fleuntdならバッファのflush)をすればいいのではと考えたがそれもうまくいかない。 なぜなら Cluster AutoscalerはDaemonset以外のPodがすべてターミネートするとNodeの終了処理(EC2インスタンスの終了)を初めてしまうからだ。 そうするとDaemonsetのターミネート処理(fleuntdならバッファのflush)がされなくて結局ログが正常に保存されない。

これらのことから以下の条件を満たす必要がある。

  • アプリケーションpodのターミネート処理よりfluentd Daemonsetのターミネート処理は早く終わる必要がある
  • アプリケーションpodのリクエスト処理が終わってからfluentd のflush処理を行う

add DaemonSet eviction option for empty nodes by yaroslava-serdiuk · Pull Request #3824 · kubernetes/autoscaler できれいに解決される可能性があるがまだこの修正が含まれたリリースはされていない。リリースされたら再度検証したい。

追記

解決編を書きました

shepherdmaster.hateblo.jp

定期的にPodを再作成する方法

背景

簡単に定期的にPodを再作成する方法を紹介します。

なんで定期的にPodを再作成したくなるかというと、例えば以下のような理由が思いつきます。

  • メモリリークしていてずっと稼働させるのはまずい
  • DBを負荷に応じてオートスケールさせていて、スケールアウトした新しいDBをアプリケーションに認識させたい

自分は2番目の理由でした。

どうやってやるか

kubectl rollout restart deployment名というコマンドがあり、これを使うと指定したdeploymentのpodを再作成してくれます。 kubernetes.io

このkubectl rollout restartコマンドをCronJobを使って定期的に実行してあげればよさそうです。 そのためにはkubectlコマンドを実行できるimageが必要になります。 自分で作成してもいいのですが、bitnamiがちょうどkubectlというimageを提供してくれているのでこれを使います。
https://hub.docker.com/r/bitnami/kubectl/

またPod内からkubectlを使ってKubernetes APIを叩くにはRBACの設定が必要になります。

マニフェストファイル例

service-account.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-rollout-restart

cronjob.yaml

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: my-rollout-restart-cronjob
spec:
  schedule: "30 12 * * *"
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: my-rollout-restart
          containers:
            - name: my-rollout-restart-job
              image: bitnami/kubectl:1.20
              command: ['kubectl', 'rollout' ,'restart', 'deployment/my-api-deployment']
          restartPolicy: OnFailure

role.yaml

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: my-rollout-restart-role
rules:
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get", "patch"]

rolebinding.yaml

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: my-rollout-restart-role-binding
subjects:
  - kind: ServiceAccount
    name: my-rollout-restart
roleRef:
  kind: Role
  name: my-rollout-restart-role
  apiGroup: rbac.authorization.k8s.io

KubernetesのNodeのディスク使用率がどんどん上がっていく件

背景

Kubernetes(EKS)のNodeのディスク使用率がどんどん上がっていっているので原因を調べた

調査

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
devtmpfs        1.9G     0  1.9G   0% /dev
tmpfs           1.9G     0  1.9G   0% /dev/shm
tmpfs           1.9G  1.3M  1.9G   1% /run
tmpfs           1.9G     0  1.9G   0% /sys/fs/cgroup
/dev/nvme0n1p1   50G   32G   19G  63% /
tmpfs           389M     0  389M   0% /run/user/1000

どのディレクトリが容量を食っているか調べると /var/lib/docker/overlay2 だった。

$ du -sch /var/lib/docker/*
16K /var/lib/docker/builder
56K /var/lib/docker/buildkit
502M    /var/lib/docker/containers
102M    /var/lib/docker/image
72K /var/lib/docker/network
33G /var/lib/docker/overlay2
0   /var/lib/docker/plugins
0   /var/lib/docker/runtimes
0   /var/lib/docker/swarm
0   /var/lib/docker/tmp
0   /var/lib/docker/trust
31M /var/lib/docker/volumes
34G total

/var/lib/docker/overlay2とは?

docker system dfで見てみると、過去のimageで容量を食っていることが分かる。24.76GBが削除可能な過去のimage

$ docker system df
TYPE                TOTAL               ACTIVE              SIZE                RECLAIMABLE
Images              94                  12                  27.07GB             24.76GB (91%)
Containers          30                  26                  13.67MB             233.8kB (1%)
Local Volumes       20                  20                  30.99MB             0B (0%)
Build Cache         0                   0                   0B                  0B

Garbage collection for container images | Kubernetes を読むと、 デフォルトでは、ディスク使用率が85%に達するとディスク使用率が80%になるまで不要なimageが削除される。

なので、放っておいてもディスク使用率が100%になることはない。

EKSを使っていて、85%というしきい値(--image-gc-high-threshold)を変えたい場合、以下の方法があるが面倒そう
https://aws.amazon.com/jp/premiumsupport/knowledge-center/eks-worker-nodes-image-cache/

EKSコンソールからnodeやworkload情報を見れるようにする

AWSコンソールのEKS画面で

Your current user or role does not have access to Kubernetes objects on this EKS cluster
This may be due to the current user or role not having Kubernetes RBAC permissions to describe cluster resources or not having an entry in the cluster’s auth config map

と表示される場合は、権限設定が必要です。

例えばAWSコンソールにログインしているIAM Userが my-user なら以下のように aws-auth ConfigMapの mapUsers に追加します。

apiVersion: v1
kind: ConfigMap
data:
  mapRoles: |
    - rolearn: arn:aws:iam::1234567890:role/my-role
      username: system:node:{{EC2PrivateDNSName}}
      groups:
        - system:bootstrappers
        - system:nodes
  mapUsers: |
    - userarn: arn:aws:iam::1234567890:user/my-user
      username: my-user
      groups:
        - system:masters

権限を絞る

system:mastersだと 全てのリソースに対して全ての操作を許可してしまうので、権限を絞りたいというニーズが出てくるかもしれません。 その場合は、 https://docs.aws.amazon.com/eks/latest/userguide/add-user-role.html の 5 にある All Regions other than Beijing and Ningxia China のリンクからダウンロードできるyamlを使います。 そのyamlは以下のようになってます(2021/01/08時点)

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: eks-console-dashboard-full-access-clusterrole
rules:
- apiGroups:
  - ""
  resources:
  - nodes
  - namespaces
  - pods
  verbs:
  - get
  - list
- apiGroups:
  - apps
  resources:
  - deployments
  - daemonsets
  - statefulsets
  - replicasets
  verbs:
  - get
  - list
- apiGroups:
  - batch
  resources:
  - jobs
  verbs:
  - get
  - list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: eks-console-dashboard-full-access-binding
subjects:
- kind: Group
  name: eks-console-dashboard-full-access-group
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: eks-console-dashboard-full-access-clusterrole
  apiGroup: rbac.authorization.k8s.io

で、このyamlをapplyし、以下のようにgroupsにeks-console-dashboard-full-access-groupを指定します。

group
  mapUsers: |
    - userarn: arn:aws:iam::1234567890:user/my-user
      username: my-user
      groups:
        - eks-console-dashboard-full-access-group

これでEKSコンソールからnodeやworkload情報を見るための最低限の権限に絞られます。

その他注意点

注意点としては、 usernameに system:node:{{EC2PrivateDNSName}}を指定してしまうと、以下のようなメッセージがでてnodeやworkload情報が見れません。

Your current user or role does not have access to Kubernetes objects on this EKS cluster
This may be due to the current user or role not having Kubernetes RBAC permissions to describe cluster resources or not having an entry in the cluster’s auth config map

また、以下のように

      groups:
        - system:bootstrappers
        - system:nodes

groupsにsystem:bootstrapperssystem:nodesを指定してしまうと、以下のようなメッセージがでてnodeやworkload情報が見れません。

namespaces is forbidden: User "my-user" cannot list resource "namespaces" in API group "" at the cluster scope

エンジニアキャリアラダーを作成する際に参考になるサイト

海外の公開されているキャリアラダー集 www.swyx.io

これが参考になった。

まずゼロからキャリアラダーを作成するのは非常に難易度が高い。どの等級にどんな役割を期待するのかを定義しそれを言語化していく作業は並大抵のことではない。 そこですでに公開されている他社の素晴らしいキャリアラダーを参考にするとかなり楽になる。 もちろんそのままコピペするのではキャリアラダーとして機能しないので、会社の組織が各等級に期待する役割に合わせて役割や表現を変えていく必要がある。

上記のサイトの中で参考にした会社のキャリアラダー