본문 바로가기
Cloud

[ArgoCD] Teams 연동기

by JunHeeLim 2024. 2. 9.

구축 계기

ArgoCD의 각 Application의 Health Check하여 teams로 알림을 발송하여 접속하여 확인하기 전까지 알 수 없는 불편함을 해결하고 배포 상황을 파악할 수 있도록 구성하게 되었다.

 

Teams 컨넥터 설정

1. 알림을 보내고자 하는 채널에서 컨넥터 선택

 

2. Incoming Webhook 구성 선택

 

3. Webhook의 이름과 보여질 이미지를 업로드 한다.

4. 만들기를 누르면 아래와 같이 Webhook 링크가 생성된다.

추후에 K8S에 배포 되어 있는 ArgoCD Secert에 넣어야 함으로 해당 링크를 잘 복사해 둔다.

 

ArgoCD Config Map 설정

Webhook URL 의 암호화를 위해 argocd-notification-secret 을 통해 주입을 받게 설정합니다.

kind: Secret
apiVersion: v1
metadata:
  name: argocd-notifications-secret
  namespace: argocd
  labels:
    app.kubernetes.io/component: notifications-controller
    app.kubernetes.io/name: argocd-notifications-controller
    app.kubernetes.io/part-of: argocd
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: >
      {"apiVersion":"v1","kind":"Secret","metadata":{"annotations":{},"labels":{"app.kubernetes.io/component":"notifications-controller","app.kubernetes.io/name":"argocd-notifications-controller","app.kubernetes.io/part-of":"argocd"},"name":"argocd-notifications-secret","namespace":"argocd"},"type":"Opaque"}
stringData:
  channel-teams-url: |
    **여기에 위에서 복사한 컨넥터 웹훅 링크를 붙여 넣습니다.**
type: Opaque

다음으로 argocd-notifications-cm 을 통해 trigger와 template을 선언합니다.

 


Templates

아래는 샘플을 custom 한 내용이며 teams의 경우 facts, sections 두 가지를 이용하여 template 선언할 수 있습니다.

아래 명령어를 통해 sample template 을 적용할 수 있습니다.

kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/notifications_catalog/install.yaml

설정에서 중요한 부분은 data.service.teams.recipientUrls 입니다.

$ + “secret에 설정한 key” 형식으로 value를 설정하며 자유로운 key이름을 설정하시면 됩니다.

예시) channelName: $channel-teams-url

 

참고 문서

Connector Message Layout
https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using?tabs=cURL
Function : Go Lang
https://argo-cd.readthedocs.io/en/stable/operator-manual/notifications/functions/
kind: ConfigMap
apiVersion: v1
metadata:
  name: argocd-notifications-cm
  namespace: argocd
data:
  context: |
    argocdUrl: https://argocd.example.com/
  service.teams: |
    recipientUrls:
      channelName: $channel-teams-url 
  template.app-created: |
    email:
      subject: Application {{.app.metadata.name}} has been created.
    message: Application {{.app.metadata.name}} has been created.
    teams:
      title: Application {{.app.metadata.name}} has been created.
  template.app-deleted: |
    email:
      subject: Application {{.app.metadata.name}} has been deleted.
    message: Application {{.app.metadata.name}} has been deleted.
    teams:
      title: Application {{.app.metadata.name}} has been deleted.
  template.app-deployed: |
    teams:
      themeColor: "#000080"
      sections: |
        [{
          "activityTitle": "배포 완료 {{.app.metadata.name}}" ,
          "activitySubtitle": "{{.app.spec.source.path}}",
          "activityImage": "https://adaptivecards.io/content/cats/1.png",
          "facts": [{
              "name": "Result :",
              "value": "{{.app.status.operationState.phase}}"
            },
            {
              "name": "Initiated by :",
              "value": "{{.app.status.operationState.operation.initiatedBy.username}}"
            },
            { 
              "name": "Image name :",
              "value": "{{.app.status.summary.images}}"
            },
            {
              "name": "Started at :",
              "value": "{{ (call .time.Parse .app.status.operationState.startedAt).Local.Format "2006-01-02 15:04:05" }}"
            },
            {
              "name": "Finished at :",
              "value": "{{ (call .time.Parse .app.status.operationState.finishedAt).Local.Format "2006-01-02 15:04:05" }}"
            },
            {
              "name": "Sync status :",
              "value": "{{.app.status.sync.status}}"
            }],
          "markdown": false
        }]
      potentialAction: |-
        [{
          "@type":"OpenUri",
          "name":"ArgoCD 바로가기",
          "targets":[{
            "os":"default",
            "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true"
          }]
        }]
      summary: "{{.app.metadata.name}} sync succeeded"
  template.app-health-degraded: |
    teams:
      themeColor: "#FF0000"
      sections: |
        [{
          "activityTitle": "상태 확인이 요망 {{.app.metadata.name}}" ,
          "activitySubtitle": "{{.app.spec.source.path}}",
          "activityImage": "https://adaptivecards.io/content/cats/1.png",
          "facts": [{
              "name": "Result :",
              "value": "{{.app.status.operationState.phase}}"
            },
            {
              "name": "Initiated by :",
              "value": "{{.app.status.operationState.operation.initiatedBy.username}}"
            },
            { 
              "name": "Image name :",
              "value": "{{.app.status.summary.images}}"
            },
            {
              "name": "Started at :",
              "value": "{{ (call .time.Parse .app.status.operationState.startedAt).Local.Format "2006-01-02 15:04:05" }}"
            },
            {
              "name": "Finished at :",
              "value": "{{ (call .time.Parse .app.status.operationState.finishedAt).Local.Format "2006-01-02 15:04:05" }}"
            },
            {
              "name": "Sync status :",
              "value": "{{.app.status.sync.status}}"
            }],
          "markdown": false
        }]
      potentialAction: |-
        [{
          "@type":"OpenUri",
          "name":"ArgoCD 바로가기",
          "targets":[{
            "os":"default",
            "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true"
          }]
        }]
      summary: "{{.app.metadata.name}} sync failed"
  template.app-sync-failed: |
    teams:
      themeColor: "#FF0000"
      sections: |
        [{
          "activityTitle": "동기화 실패 {{.app.metadata.name}} " ,
          "activitySubtitle": "{{.app.spec.source.path}}",
          "activityImage": "https://adaptivecards.io/content/cats/1.png",
          "facts": [{
              "name": "Result :",
              "value": "{{.app.status.operationState.phase}}"
            },
            {
              "name": "Initiated by :",
              "value": "{{.app.status.operationState.operation.initiatedBy.username}}"
            },
            { 
              "name": "Image name :",
              "value": "{{.app.status.summary.images}}"
            },
            {
              "name": "Started at :",
              "value": "{{ (call .time.Parse .app.status.operationState.startedAt).Local.Format "2006-01-02 15:04:05" }}"
            },
            {
              "name": "Finished at :",
              "value": "{{ (call .time.Parse .app.status.operationState.finishedAt).Local.Format "2006-01-02 15:04:05" }}"
            },
            {
              "name": "Sync status :",
              "value": "{{.app.status.sync.status}}"
            }],
          "markdown": false
        }]
      potentialAction: |-
        [{
          "@type":"OpenUri",
          "name":"ArgoCD 바로가기",
          "targets":[{
            "os":"default",
            "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true"
          }]
        }]
      summary: "{{.app.metadata.name}} sync failed"
  template.app-sync-running: |
    email:
      subject: Start syncing application {{.app.metadata.name}}.
    message: |
      The sync operation of application {{.app.metadata.name}} has started at {{.app.status.operationState.startedAt}}.
      Sync operation details are available at: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true .
    teams:
      facts: |
        [{
          "name": "Sync Status",
          "value": "{{.app.status.sync.status}}"
        },
        {
          "name": "Started at",
          "value": "{{.app.status.operationState.startedAt}}"
        },
        {
          "name": "Repository",
          "value": "{{.app.spec.source.repoURL}}"
        }
        {{range $index, $c := .app.status.conditions}}
          {{if not $index}},{{end}}
          {{if $index}},{{end}}
          {
            "name": "{{$c.type}}",
            "value": "{{$c.message}}"
          }
        {{end}}
        ]
      potentialAction: |-
        [{
          "@type":"OpenUri",
          "name":"Open Operation",
          "targets":[{
            "os":"default",
            "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true"
          }]
        },
        {
          "@type":"OpenUri",
          "name":"Open Repository",
          "targets":[{
            "os":"default",
            "uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}"
          }]
        }]
      title: Start syncing application {{.app.metadata.name}}.
  template.app-sync-status-unknown: |
    email:
      subject: Application {{.app.metadata.name}} sync status is 'Unknown'
    message: |
      {{if eq .serviceType "slack"}}:exclamation:{{end}} Application {{.app.metadata.name}} sync is 'Unknown'.
      Application details: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}.
      {{if ne .serviceType "slack"}}
      {{range $c := .app.status.conditions}}
          * {{$c.message}}
      {{end}}
      {{end}}
    teams:
      facts: |
        [{
          "name": "Sync Status",
          "value": "{{.app.status.sync.status}}"
        },
        {
          "name": "Repository",
          "value": "{{.app.spec.source.repoURL}}"
        }
        {{range $index, $c := .app.status.conditions}}
          {{if not $index}},{{end}}
          {{if $index}},{{end}}
          {
            "name": "{{$c.type}}",
            "value": "{{$c.message}}"
          }
        {{end}}
        ]
      potentialAction: |-
        [{
          "@type":"OpenUri",
          "name":"Open Application",
          "targets":[{
            "os":"default",
            "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}"
          }]
        },
        {
          "@type":"OpenUri",
          "name":"Open Repository",
          "targets":[{
            "os":"default",
            "uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}"
          }]
        }]
      title: Application {{.app.metadata.name}} sync status is 'Unknown'
  template.app-sync-succeeded: |
    teams:
      themeColor: "#000080"
      sections: |
        [{
          "activityTitle": "동기화 성공 {{.app.metadata.name}}" ,
          "activitySubtitle": "{{.app.spec.source.path}}",
          "activityImage": "https://adaptivecards.io/content/cats/1.png",
          "facts": [{
              "name": "Result :",
              "value": "{{.app.status.operationState.phase}}"
            },
            {
              "name": "Initiated by :",
              "value": "{{.app.status.operationState.operation.initiatedBy.username}}"
            },
            { 
              "name": "Image name :",
              "value": "{{.app.status.summary.images}}"
            },
            {
              "name": "Started at :",
              "value": "{{ (call .time.Parse .app.status.operationState.startedAt).Local.Format "2006-01-02 15:04:05" }}"
            },
            {
              "name": "Finished at :",
              "value": "{{ (call .time.Parse .app.status.operationState.finishedAt).Local.Format "2006-01-02 15:04:05" }}"
            },
            {
              "name": "Sync status :",
              "value": "{{.app.status.sync.status}}"
            }],
          "markdown": false
        }]
      potentialAction: |-
        [{
          "@type":"OpenUri",
          "name":"ArgoCD 바로가기",
          "targets":[{
            "os":"default",
            "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true"
          }]
        }]
      summary: "{{.app.metadata.name}} sync succeeded"
  trigger.on-created: |
    - description: Application is created.
      oncePer: app.metadata.name
      send:
      - app-created
      when: "true"
  trigger.on-deleted: |
    - description: Application is deleted.
      oncePer: app.metadata.name
      send:
      - app-deleted
      when: app.metadata.deletionTimestamp != nil
  trigger.on-deployed: |
    - description: Application is synced and healthy. Triggered once per commit.
      oncePer: app.status.operationState?.syncResult?.revision
      send:
      - app-deployed
      when: app.status.operationState != nil and app.status.operationState.phase in ['Succeeded']
        and app.status.health.status == 'Healthy'
  trigger.on-health-degraded: |
    - description: Application has degraded
      send:
      - app-health-degraded
      when: app.status.health.status == 'Degraded'
  trigger.on-sync-failed: |
    - description: Application syncing has failed
      send:
      - app-sync-failed
      when: app.status.operationState != nil and app.status.operationState.phase in ['Error',
        'Failed']
  trigger.on-sync-running: |
    - description: Application is being synced
      send:
      - app-sync-running
      when: app.status.operationState != nil and app.status.operationState.phase in ['Running']
  trigger.on-sync-status-unknown: |
    - description: Application status is 'Unknown'
      send:
      - app-sync-status-unknown
      when: app.status.sync.status == 'Unknown'
  trigger.on-sync-succeeded: |
    - description: Application syncing has succeeded
      send:
      - app-sync-succeeded
      when: app.status.operationState != nil and app.status.operationState.phase in ['Succeeded']
Facts 를 이용할 경우 title에 html escape String 이 안되는 이슈로 sections를 이용하여 template을 작성 하였습니다.

ArgoCD에 Subscription 적용

우측에 Edit 를 통해 편집 모드로 들어갑니다.

 

notifications.argoproj.io/subscribe.<트리거>.<대상> =<service.teams.recipientURLs에 정의한 key> 형식으로 등록해 줍니다. 위 내용은 application annotation 으로 등록 됩니다.


배포 성공 / 동기화 실패 / degraded 상태일 때 template에 설정한 내용을 전송하도록 설정하였습니다.


 

결과

댓글