[k8s] Prometheus: AlertManager로 Slack 경보 구성

[원본 링크]

서비스 운영에서 가장 중요한 것중 하나는, 서버에서 장애가 발생했을때 빠르게 알아차리는 것이다.

Pod가 OOM으로 죽거나, Node에 문제가 생기거나, 리소스 사용량이 급격이 증가한다면, 그걸 빠르게 알아야 장애로 인한 위험을 최소화하고 조치를 할 수 있기 때문이다. 서비스의 품질에 매우 중요한 영향을 끼치는 부분이다.

AWS 같은 클라우드에서는 Cloudwatch를 비롯한 전용 시스템으로 Alert를 지원하지만, k8s 스택에서는 당연히도 직접 다 구성해줘야 한다.
여기서는 거의 표준으로 여겨지는 Prometheus의 AlertManager를 쓴다고 가정하겠다.

AlertManager를는 Prometheus의 세부 스택 중 하나이고, Prometheus 팀에서 관리한다.

이걸 쓰는 가장 간단한 방법은 kube-prometheus-stack 패키지를 깔아서 설치하는 것이다.
안에 AlertManager가 내장되어있다.
https://blog.naver.com/sssang97/224176325746




목표

대부분의 개발 조직에서는 아마 Slack을 메인 소통 채널로 사용할 것이다.
따라서 여기서는 서버가 죽거나 node가 offline상태가 된 경우, 혹은 node에 taint가 붙은 경우에 Slack으로 전송할 수 있도록 구성해보겠다.




Slack App 준비

Slack 서비스에 들어가서 Bot App을 만들고, 그 앱의 토큰과 알림을 전송할 채널의 ID를 준비한다.

자세한 방법은 별도 포스트를 참조한다.
https://blog.naver.com/sssang97/223176628812




AlertManager 환경설정 (with helm)

helm으로 설치한 경우가 아니라면 세팅이 좀 불편하다. helm을 쓴다고 가정하겠다.

먼저 helm으로 설치된 prometheus stack의 이름과 네임스페이스를 알아낸다.

그리고 다음과 같이 Slack App의 토큰과 전송할 채널 등을 helm values로 정의한다.

alertmanager:
  config:
    global:
      slack_api_url: "https://slack.com/api/chat.postMessage"

    route:
      group_by: ["alertname", "cluster", "namespace"]
      group_wait: 10s # 첫 알림 대기
      group_interval: 10s # 그룹에 새 알림 추가 대기
      repeat_interval: 12h # 같은 알림 반복 간격
      receiver: "slack-critical"

      routes:
        # Critical 알림
        - match:
            severity: critical
          receiver: "slack-critical"
          continue: true

        # Warning 알림
        - match:
            severity: warning
          receiver: "slack-warnings"

    receivers:
      # Critical 알림 (즉시)
      - name: "slack-critical"
        slack_configs:
          - channel: "채널 ID"
            title: '{{ if eq .Status "firing" }}🔴 Critical Alert{{ else }}✅ Resolved: Critical Alert{{ end }}'
            text: |-
              <!here>
              {{ if eq .Status "resolved" }}*Status:* RESOLVED{{ end }}
              *Alert:* {{ .GroupLabels.alertname }}
              *Severity:* {{ .CommonLabels.severity }}
              *Summary:* {{ .CommonAnnotations.summary }}
              *Description:* {{ .CommonAnnotations.description }}
            send_resolved: true
            http_config:
              authorization:
                credentials: "SLACK 토큰"

      # Warning 알림
      - name: "slack-warnings"
        slack_configs:
          - channel: "채널 ID"
            title: '{{ if eq .Status "firing" }}⚠️ Warning{{ else }}✅ Resolved: Warning{{ end }}'
            text: |-
              {{ if eq .Status "resolved" }}*Status:* RESOLVED{{ end }}
              *Alert:* {{ .GroupLabels.alertname }}
              *Namespace:* {{ .CommonLabels.namespace }}
              *Pod:* {{ .CommonLabels.pod }}
              *Description:* {{ .CommonAnnotations.description }}
            send_resolved: true
            http_config:
              authorization:
                credentials: "SLACK 토큰"

그리고 저걸 가져다가 helm으로 update를 적용한다.

helm upgrade prometheus prometheus-community/kube-prometheus-stack \
  --reuse-values \
  -f alertmanager-values.yaml \
  --namespace default

오류 없이 적용된다면 대충 된 것이다.




Rule 정의

위에서 helm으로 채널과 인증 정보를 정의했다면, 실제 Alert 동작과 조건을 정의하는 것은 Rule을 통해서 이루어진다. PrometheusRule라는 CRD를 통해서 리소스를 정의할 수 있다.

이런 식으로, 프로메테우스에 기록되는 메트릭 정보를 기반으로 해서 Alert를 날리는 것이다.
아까 정의했던 severity에 따라서 실제 채널에 전송이 된다.

예제 코드는 좀 커서 Github을 통해 첨부한다.
https://github.com/myyrakle/infrastructures/blob/master/k8s/setups/alert-manager/rules-example.yaml

아무튼 잘 작성해서 생성하고

혹시나 모르니 Reload를 한번 해주자. 이게 실시간으로 잘 적용되는건 아니더라.

kubectl exec -n default prometheus-prometheus-kube-prometheus-prometheus-0 -c prometheus -- wget --post-data="" -qO- 'http://localhost:9090/-/reload' 2>/dev/null && echo "Reload triggered" && sleep 5



Alert 실험해보기 (OOM)

이제 Alert가 동작하는지를 확인해보자.
메모리 먹고 OOM으로 자살하는 Pod를 하나 정의해봤다.

apiVersion: v1
kind: Pod
metadata:
  name: oom-test
  namespace: default
  labels:
    app: oom-test
spec:
  containers:
  - name: memory-eater
    image: python:3.9-slim
    command:
    - python3
    - -c
    - |
      import time
      print("Starting memory allocation...")
      data = []
      try:
        while True:
          # 10MB씩 메모리 할당
          data.append(' ' * (10 * 1024 * 1024))
          print(f"Allocated: {len(data) * 10}MB")
          time.sleep(0.5)
      except Exception as e:
        print(f"Error: {e}")
    resources:
      limits:
        memory: "100Mi"  # 100MB 제한
      requests:
        memory: "50Mi"
  restartPolicy: Always  # OOM 후 계속 재시작 (CrashLoopBackOff)

그리고 실행해보면, 당연히 OOM으로 죽을 것이고

Slack 알림도 날라올 것이다.

위험이 해소되는 것도 확인해보자.
OOM이 나지 않도록 변경하고

재부팅해준다.


오류가 없는 상태로 좀 지속이 되면


그때는 이제 resolved 메시지가 날라올 것이다.



참조
https://prometheus.io/docs/alerting/latest/alertmanager/
https://medium.com/@hyukjuner/kubernetes%EC%97%90-%EB%B0%B0%ED%8F%AC%EB%90%9C-prometheus-alertmanager%EC%9D%98-%EA%B5%AC%EC%84%B1-%EC%9D%B4%ED%95%B4-0fda19076ab9