[DevOps] ArgoCD Notification 설정

안녕하세요? 정리하는 개발자 워니즈 입니다. 이번시간에는 ArgoCD Notification 설정에 대해서 정리를 해보도록 하겠습니다. ArgoCD는 이전 시간에 정리를 한적이 있었습니다.

첫 번째 포스팅은 설치 및 설저에 관련한 내용이고, 두번 째 포스팅은 gitOps 프로세스와 Notification 설정에 관련한 내용을 정리했었습니다.
보시다시피 두번째 포스팅에서 Slack Notification에 대해서 정리를 했었습니다.

하지만, 필자가 이직한 사내에는 전사 공용 Slack을 사용하고 있는데요. Slack App관련해서 생성을 허가하고 있지 않습니다. 따라서 다른 방안을 생각했어야 했는데, 다행히도(?) ArgoCD Notification관련해서는 다양한 서비스( Email, Github, Slack, Webhook, Telegram 등등 )를 통해서 알림을 발송할 수있도록 제공하고 있었습니다.

1. Trigger, Template & Subscription

적용 가이드는 아래에서 확인을 할 수 있습니다.

ArgoCD 알림은 ArgoCD 의 app들을 지속적으로 모니터링하고 사용자에게 app들의 중요한 상태 변경을 알리기 위한 유연한 방법을 제공합니다. 여기서 중요한 3가지 내용이 있습니다.

트리거는 알림을 보내야 하는 조건을 정의합니다. 이는 argocd-notifications-cm의 ConfigMap에 구성이 됩니다. 아래의 예시는 어플리케이션의 동기화 상태가 Unknown일 경우 알람을 발송하는 예제입니다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-notifications-cm
data:
  trigger.on-sync-status-unknown: |
    - when: app.status.sync.status == 'Unknown'     # trigger condition
      send: [app-sync-status, github-commit-status] # template names

알림 템플릿은 어떤 형태의 포맷대로 알림을 발송 할 것인지를 정의합니다. 이는 argocd-notifications-cm의 ConfigMap에 구성이 됩니다. 템플릿은 한번 만들어 놓으면 재사용이 가능하며 여러개의 트리거에서 동시에 참조할 수 있습니다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-notifications-cm
data:
  template.my-custom-template-slack-template: |
    message: |
      Application {{.app.metadata.name}} sync is {{.app.status.sync.status}}.
      Application details: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}.

ArgoCD 어플리케이션 이벤트에 대한 구독은 notifications.argoproj.io/subscribe.<trigger>.<service>: <recipient>주석을 사용하여 정의할 수 있습니다.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  annotations:
    notifications.argoproj.io/subscribe.on-sync-succeeded.slack: my-channel1;my-channel2

예를들어 위와 같이 구독을 설정할 수 있습니다.

  • on-sync-succeeded– 트리거 이름
  • slack– 알림 서비스 이름
  • my-channel1;my-channel2– 세미콜론으로 구분된 수신자 목록

2. 설치 가이드

설치는 굉장히 심플합니다. 다만 필자가 한가지 짚고 넘어가고 싶은것은 현재 ArgoCD Document가 최신화가 안되어있습니다. 최신본으로 다운 받기 위해서 필자는 1.2버전의 Latest 버전을 받기로 했습니다. 왜그런지는 다음 섹션에서 정리하도록 하겠습니다.

$ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-notifications/release-1.2/manifests/install.yaml
# 설치 되는 Object
# role 관련 설정
serviceaccount/argocd-notifications-controller created
role.rbac.authorization.k8s.io/argocd-notifications-controller created
rolebinding.rbac.authorization.k8s.io/argocd-notifications-controller created

# ConfigMap & Secret
configmap/argocd-notifications-cm created
secret/argocd-notifications-secret created

# ArgoCD Notification Application
service/argocd-notifications-controller-metrics created
deployment.apps/argocd-notifications-controller created

구조는 위와 같이 나뉘어져있습니다. 중요한 것이 ArgoCD deployment이고, 실제로 동작하기 위한 설정은 ConfigMap에 설정이 되고 있습니다. 이전에 Slack Notification 관련해서 정리를 할 때는 Secret도 활용해서 Slack App의 Token과 같은 정보를 넣었었는데 이번에 필자는 Webhook을 활용 할것이라 필요 없었습니다.

2. 트러블 슈팅

사내의 Slack Webhook을 호출하기 위해서 아래의 내용을 참고 했습니다.

참고로 필자가 보고있는 Document는 1.0 버전의 Stable 버전이였고, 1.0 버전의 ArgoCD Notification을 설치했었습니다. 지금 이 포스팅을 작성하면서 트러블 슈팅이 완료되어 손쉽게 작성하지만, Document가 최신화가 안되어있어, 이부분 찾는데 정말 많은 노력과 시간을 쏟았습니다.

우선 가이드 상에 나와있는 대로 form을 호출해도 도저히 아무 반응이 없었습니다 .

2-1. jenkins remote call

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-notifications-cm
data:
  service.webhook.jenkins: |
    url: https://xxxxx.jenkins.com//view/xxxx/job/test_job/build?token=test
    headers:
      - name: Content-Type
        value: application/x-www-form-urlencoded
    basicAuth:
      username: 'xxxxxx'
      password: 'xxxxxx'

  template.all-good: |
    webhook:
      jenkins:
        method: POST
        body: "token=test"

사내에서 Slack을 제공해주지 않았기 때문에 Jenkins에 Slack을 호출하는 Job을 만들어두고, 해당 job을 webhook 방식으로 호출하고자 했습니다.
적용을 완료했고, ArgoCD Notification app을 한번 재시작 해줬습니다. 추가적으로 샘플 app의 annotaion 설정은 아래와 같이 진행했습니다.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  annotations:
    notifications.argoproj.io/subscribe.sync-operation-change.ikameshi: ""

그런데, 로그상으로는 어떤 액션도 없었습니다.

2-2. github issue확인 후, 차용하여 적용

별다른 방안이 없었습니다. Document를 따라서 진행해도 도저히 적용이 되질 않았고, ArgoCD Notification의 Issue 사항을 하나씪 확인하기 시작했습니다. 그러다가 뭔가 다른 이슈긴 하지만, 적용된 코드를 차용할 수 있었고, 그대로 적용해보기로 했습니다.

apiVersion: v1
kind: Secret
metadata:
  name: argocd-notifications-secret
stringData:
  notifiers.yaml: |
    webhook:
    - name: slackwebhook
      url: http://slack.webook.sample.com/notice
      headers:
      - name: Content-Type
        value: application/json
type: Opaque
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-notifications-cm
data:
  config.yaml: |
    triggers:
      - name: sync-running
        condition: app.status.operationState.phase in ['Running']
        template: slackwebhook-notification-template
        enabled: true
      - name: sync-failed
        condition: app.status.operationState.phase in ['Error', 'Failed']
        template: slackwebhook-notification-template
        enabled: true
      - name: sync-succeeded
        condition: app.status.operationState.phase in ['Succeeded']
        template: slackwebhook-notification-template
        enabled: true
    subscriptions:
    - recipients:
      - webhook:slackwebhook
      trigger: slackwebhook
    templates:
      - name: slackwebhook-notification-template
        webhook:
          slackwebhook:
            method: POST
            body: |
              {
                "channel": "test_channel"
                "message": "test_message"
              }

위의 내용을 보면, Secret에는 실제 호출하는 Webhook을 구성했고, ConfigMap에는 트리거, 템플릿 그리고 구독설정이 모두 되어있습니다. (보시면 아시겠지만 이전 방식입니다.)

추가적으로 어플리케이션에도 구독 설정을 해보았습니다.

recipients.argocd-notifications.argoproj.io: webhook:ikameshi     

적용을 하고 Notification의 로그를 확인했더니 아래와 같이 로그가 출력이 되었습니다.

time="2022-02-21T09:56:39Z" level=warning msg="Key 'notifiers.yaml' in Secret is deprecated, please migrate to new settings"
time="2022-02-21T09:56:39Z" level=warning msg="Key 'config.yaml' in ConfigMap is deprecated, please migrate to new settings"

구버전의 방식이였고, 사용할 수 없는 코드였습니다.

3. 최종 코드

필자가 정말 이러 저러한 시도 끝에, 포기하는 심정으로 Document의 Latest 버전을 선택했고 설정이 조금 다르다는 것을 확인했습니다. 그리고 따라서 진행해보기로했습니다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-notifications-cm
data:
  service.webhook.slackwebhook: |
    url: http://slack.webook.sample.com/notice
    headers: #optional headers
    - name: X-SENDER
      value: slack
    - name: Content-Type
      value: application/x-www-form-urlencoded
  template.slackwebhook-success: |
    webhook:
      ikameshi:
        method: POST
        body: |
              channel=test_channel&color=green&nickname=Deploy Notification Bot&icon_emoji=:argocd:&message=
              Application {{.app.metadata.name}} sync is {{.app.status.sync.status}}.
              :dot: Phase : `alpha`
              :dot: Status : `{{.app.status.sync.status}}`
              :dot: health : `{{.app.status.health.status}}`
              :dot: name : `{{.app.metadata.name}}`
              :dot: version : `{{.app.status.sync.revision}}`
              :dot: deployer : `{{.app.status.operationState.operation.initiatedBy.username}}`


  template.slackwebhook-warning: |
    webhook:
      ikameshi:
        method: POST
        body: |
              channel=test_channel&color=yellow&nickname=Deploy Notification Bot&icon_emoji=:argocd:&message=
              Application {{.app.metadata.name}} sync is {{.app.status.sync.status}}.
              :dot: Phase : `alpha`
              :dot: Status : `{{.app.status.sync.status}}`
              :dot: health : `{{.app.status.health.status}}`
              :dot: name : `{{.app.metadata.name}}`
              :dot: version : `{{.app.status.sync.revision}}`
              :dot: deployer : `{{.app.status.operationState.operation.initiatedBy.username}}`


  template.slackwebhook-failed: |
    webhook:
      ikameshi:
        method: POST
        body: |
              channel=test_channel&color=red&nickname=Deploy Notification Bot&icon_emoji=:argocd:&message=
              Application {{.app.metadata.name}} sync is {{.app.status.sync.status}}.
              :dot: Phase : `alpha`
              :dot: Status : `{{.app.status.sync.status}}`
              :dot: health : `{{.app.status.health.status}}`
              :dot: name : `{{.app.metadata.name}}`
              :dot: version : `{{.app.status.sync.revision}}`
              :dot: app :  `{{.app.status.operationState.operation.initiatedBy.username}}`


  trigger.sync-operation-change: |
    - when : app.status.operationState.phase in ['Succeeded'] and app.status.health.status == 'Healthy' and         app.status.sync.status == "Synced"
      #oncePer: app.status.sync.revision
      send: [slackwebhook-success]
    - when: app.status.health.status == 'Progressing' or app.status.sync.status == 'OutOfSync'
      oncePer: app.status.sync.revision
      send: [slackwebhook-warning]
    - when: app.status.health.status == 'Degraded'
      oncePer: app.status.sync.revision
      send: [slackwebhook-failed]

  #subscriptions: |
  #  # subscription for on-sync-status-unknown trigger notifications
  #  - recipients:
  #    - slackwebhook
  #    triggers:
  #    - on-deployed

내용이 확연히 다르다는 것을 알 수 있습니다. 사실 버전업된 부분에서는 Trigger영역이 추가적으로 들어가있었습니다.

  trigger.sync-operation-change: |
    - when : app.status.operationState.phase in ['Succeeded'] and app.status.health.status == 'Healthy' and         app.status.sync.status == "Synced"
      #oncePer: app.status.sync.revision
      send: [slackwebhook-success]
    - when: app.status.health.status == 'Progressing' or app.status.sync.status == 'OutOfSync'
      oncePer: app.status.sync.revision
      send: [slackwebhook-warning]
    - when: app.status.health.status == 'Degraded'
      oncePer: app.status.sync.revision
      send: [slackwebhook-failed]

필자는 다양한 조건을 확인 후, 알람 발송하기 위해서 위와 같이 sync-operation-change 인경우에 템플릿을 통해서 알람을 발송하도록 처리했습니다.
구독 -> 트리거 -> 템플릿 -> 서비스(웹훅) 순서로 호출이 이뤄집니다. 정상적으로 호출이 되면, 웹훅을 통한 요청이 정상적으로 전달이 되는 것을 확인 할 수 있었습니다.

4. 마치며..

한가지 아쉬운 부분이 ArgoCD Notification관련해서 블로깅을 해보면, 대부분이 Slack App을 통한 연계가 상당히 많아 보입니다. 하지만 웹훅에 관련해서는 내용이 다소 부실하게 되어있습니다. 심지어 Document도 그대로 따라하더라도 동작을 안하는 아이러니함을 발견할 수 있었습니다.

이번기회에 오픈소스에 이슈사항을 등록해보는 도전을 해보려고 합니다. 이틀간 정말 많은 시도와 도전끝에 원인을 알았지만, 조금만 Document가 친절했더라면 이런일이 없었을거 같은 생각을 해보았습니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다