EMD Blog

GCP Pub/Sub + Cloud Functions로 Google Chat 알림 채널 구성 본문

Public Cloud/GCP

GCP Pub/Sub + Cloud Functions로 Google Chat 알림 채널 구성

EmaDam 2022. 9. 3. 10:32
반응형

모니터링 알림을 받기 위해서는 알림 채널을 설정해 알림을 전달 받을 수 있다. 주로 사용되는 Gmail이나 Slack 같은 경우는 쉽게 가공된 알림 데이터를 전달 받을 수 있지만 Google Chat은 알림 채널로 지원하고 있지 않아 Pub/Sub을 알림 채널로 사용해 Pub/Sub -> Cloud Functions → Webhook → Google Chat 흐름을 직접 구성해야한다.

큰 순서는 다음과 같다.

  1. 모니터링 알림 채널로 사용할 Pub/Sub 주제를 생성
  2. 모니터링 알림 채널로 방금 생성한 주제 지정
  3. 생성한 주제를 트리거로 하는 Cloud Functions를 생성 or Cloud Functions를 생성하고 인증된 호출을 하도록 구독을 수동으로 생성
  4. 알림을 받을 Google Chat Space를 생성하고 Webhook URL 추가
  5. Webhook으로 알림 데이터를 보낼 수 있게 Cloud Functions 코드 작성
  6. 테스트

하나씩 살펴보면

  • 모니터링 알림 채널로 사용할 Pub/Sub 주제를 생성

Pub/Sub으로 모니터링 알림을 전달할 것이므로 주제를 제일 먼저 생성해야한다. 주제를 생성하면 다양한 GCP Cloud에서 이 주제에 메세지를 게시할 수 있게 된다. 자세한 내용은 아래 문서 참고

What is Pub/Sub?  |  Cloud Pub/Sub Documentation  |  Google Cloud

Pub/Sub의 주제를 생성하려면 아래 명령어를 사용한다.

gcloud pubsub topics create  |  Google Cloud CLI Documentation

  • 모니터링 알림 채널로 방금 생성한 주제 지정

모니터링에서 발생하는 알림은 알림 채널로 전송되며, 현재는 아래와 같은 알림 채널을 지원하고 있다. (문서 참고)

Manage notification channels  |  Cloud Monitoring  |  Google Cloud

이 알림의 경우 해당 채널 서비스에 해당하는 REST API 호출하는 방식이기 때문에 데이터가 가공되어 전달될 수도 있다. 예를 들어 알림 채널을 Pub/Sub으로 지정하면 Pub/Sub의 REST API는 데이터를 Base64로 인코딩된 데이터만 받기 때문에 알림 데이터 전달 시 인코딩된 데이터가 전달 된다.

알림 채널을 추가하는 방법은 알림 채널 페이지로 이동해 주제 이름을 입력해주면 된다. 주제 이름은 아래와 같은 형식으로 작성해야 한다.

projects/[PROJECT_ID]/topics/[TOPIC]

이 알림 채널을 설정하는 것은 알림 메세지를 주제에 게시하겠다는 말이기 때문에 서비스 계정에 주제 게시자 역할을 부여해주어야 한다. 역할 부여는 아래 명령어를 사용한다.

gcloud pubsub topics add-iam-policy-binding  |  Google Cloud CLI Documentation

gcloud pubsub topics add-iam-policy-binding \\
projects/<project_id>/topics/<topic> --role=roles/pubsub.publisher \\
--member=serviceAccount:service-<project_numnber>@gcp-sa-monitoring-notification.iam.gserviceaccount.com

참고로 서비스 계정은 service-<PROJECT_NUMBER>@gcp-sa-monitoring-notification.iam.gserviceaccount.com 이다.

  • 생성한 주제를 트리거로 하는 Cloud Functions를 생성 or Cloud Functions를 생성하고 인증된 호출을 하도록 구독을 수동으로 생성

알림 메세지를 Pub/Sub에 게시할 수 있게 되었으니 이제 이 메세지를 Cloud Functions로 보내 가공해야 한다. Cloud Functions로 메세지를 보내는 방법은 두 가지가 존재한다.

[1] 주제에 메세지가 게시될 때마다 이벤트 트리거로 호출되는 Cloud Functions를 생성.

[2] HTTP 트리거 방식의 Cloud Functions를 생성하고 엔드포인트로 메세지를 Push하는 구독 생성.

[1] 방식의 경우 구독이 자동으로 생성되기 때문에 네이밍 규칙을 준수할 수 없게 된다. 그래서 시간이 더 걸리더라도 [2] 방식으로 진행한다. 만약에 네이밍이 상관 없다면 [1] 방식 추천.

Cloud Functions 배포는 아래의 명령어를 사용한다.

gcloud functions deploy  |  Google Cloud CLI Documentation

배포 시 주의할 점은

  • -allow-unauthenticated 옵션을 사용하면 Open API가 되어버리니 주의
  • -service-account의 경우 Cloud Functions가 다른 GCP 리소스에 접근할 일이 없으므로 별다른 역할이 필요하지 않음.
  • -ingress-settings 옵션은 all 로 설정해야한다. 여기서 internal은 같은 VPC를 뜻하기 때문에 Pub/Sub에서 접근이 안된다.

Cloud Functions를 생성했으면 Function에 메세지를 전달할 구독을 생성해야 한다. 하지만 그 전에 방금 생성한 Function을 호출할 수 있는 권한을 가진 서비스 계정부터 생성해야 한다. 서비스 계정은 아래 명령어로 생성한다.

gcloud iam service-accounts create  |  Google Cloud CLI Documentation

서비스 계정 생성 후 방금 생성한 Functions에 대한 호출 권한을 부여한다. 호출 권한을 부여하려면 서비스 계정에 roles/cloudfunctions.invoker 역할을 부여해야 한다.

gcloud functions add-iam-policy-binding  |  Google Cloud CLI Documentation

역할까지 부여했다면 구독을 생성한다. 구독 생성은 아래 명령어를 사용한다.

gcloud pubsub subscriptions create  |  Google Cloud CLI Documentation

  • -push-auth-service-account에 방금 생성한 서비스 계정을 지정하면 된다.
  • 알림을 받을 Google Chat Space를 생성하고 Webhook URL 추가
  1. Google Chat으로 이동해 스페이스 탭 우측의 + 버튼 클릭 후 스페이스 만들기 클릭
  2. 스페이스 명 입력 후 만들기
  3. 방금 생성한 스페이스의 대화창 상단의 스페이스 명 클릭 → 웹훅 관리
  4. 웹훅 명 입력 후 만들기 → URL 복사 (아바타 URL을 지정하면 웹훅 URL 왼쪽 이미지가 변경됨)
  • Webhook으로 알림 데이터를 보낼 수 있게 Cloud Functions 코드 작성

Pub/Sub은 REST API로 데이터를 전달 받을 때 base64로 인코딩된 데이터만을 받을 수 있다. 데이터 구조는 아래 문서를 참고하면 된다.

PubsubMessage  |  Cloud Pub/Sub Documentation  |  Google Cloud

base64로 인코딩된 데이터를 디코딩 했을 때 데이터 구조는 아래와 같다.

  • JSON 예시
{
  "incident": {
    "incident_id": "0.opqiw61fsv7p",
    "scoping_project_id": "internal-project",
    "scoping_project_number": 12345,
    "url": "<https://console.cloud.google.com/monitoring/alerting/incidents/0.lxfiw61fsv7p?project=internal-project>",
    "started_at": 1577840461,
    "ended_at": 1577877071,
    "state": "closed",
    "resource_id": "11223344",
    "resource_name": "internal-project gke-cluster-1-default-pool-e2df4cbd-dgp3",
    "resource_display_name": "gke-cluster-1-default-pool-e2df4cbd-dgp3",
    "resource_type_display_name": "VM Instance",
    "resource": {
      "type": "gce_instance",
      "labels": {
        "instance_id": "11223344",
        "project_id": "internal-project",
        "zone": "us-central1-c"
      }
    },
    "metric": {
      "type": "compute.googleapis.com/instance/cpu/utilization",
      "displayName": "CPU utilization",
      "labels": {
        "instance_name": "the name of the VM instance"
      }
    },
    "metadata": {
      "system_labels": { "labelkey": "labelvalue" },
      "user_labels": { "labelkey": "labelvalue" }
    },
    "policy_name": "Monitor-Project-Cluster",
    "policy_user_labels" : {
        "user-label-1" : "important label",
        "user-label-2" : "another label"
    },
    "condition_name": "VM Instance - CPU utilization [MAX]",
    "threshold_value": "0.9",
    "observed_value": "0.835",
    "condition": {
      "name": "projects/internal-project/alertPolicies/1234567890123456789/conditions/1234567890123456789",
      "displayName": "VM Instance - CPU utilization [MAX]",
      "conditionThreshold": {
        "filter": "metric.type=\\\\"compute.googleapis.com/instance/cpu/utilization\\\\" resource.type=\\\\"gce_instance\\\\" metadata.system_labels.\\\\"state\\\\"=\\\\"ACTIVE\\\\"",
        "aggregations": [
          {
            "alignmentPeriod": "120s",
            "perSeriesAligner": "ALIGN_MEAN"
          }
        ],
        "comparison": "COMPARISON_GT",
        "thresholdValue": 0.9,
        "duration": "0s",
        "trigger": {
          "count": 1
        }
      }
    },
    "documentation": {
      "content": "TEST ALERT\\n\\npolicy.name=projects/internal-project/alertPolicies/1234567890123456789\\n\\npolicy.display_name=Monitored-Project-NO-GROUPBY\\n\\ncondition.name=projects/nternal-project/alertPolicies/1234567890123456789/conditions/1234567890123456789\\n\\ncondition.display_name=VM Instance - CPU utilization [MAX]\\n\\nproject=internal-project\\n\\nresrouce.project=internal-project \\n\\nDONE\\n",
      "mime_type": "text/markdown"
    },
    "summary": "CPU utilization for internal-project gke-cluster-1-16-default-pool-e2df4cbd-dgp3 with metric labels {instance_name=gke-cluster-1-default-pool-e2df4cbd-dgp3} and system labels {state=ACTIVE} returned to normal with a value of 0.835."
  },
  "version": "1.2"
}

각 항목별 설명은 아래와 같다.

  • 스키마 구조 v1.2
{
  "version": "1.2",
  "incident": {
    
    이슈에 관한 정보
    
    "incident_id": 문자열, 이 이슈에 대해 생성된 ID입니다.
    "scoping_project_id": 문자열, 측정항목 범위를 호스팅하는 프로젝트 ID입니다.
    "scoping_project_number": 숫자, 범위 지정 프로젝트의 프로젝트 번호입니다.
    "url": 문자열, 이 이슈에 대한 Google Cloud Console URL입니다
    "started_at": 숫자, 이슈가 개설된 시간(Unix epoch 초 단위)입니다.
    "ended_at": 숫자, 이슈가 종료된 시간(Unix epoch 초 단위)입니다. state가 closed일 때만 채워집니다.
    "state": 문자열, 이슈 상태(open 또는 closed) open이면 ended_at이 null입니다.
    "summary": 문자열, 이 이슈에 대해 생성된 텍스트 요약입니다.
    "apigee_url": 문자열, Apigee 리소스 유형 Environment 및 Proxy*에만 해당하는 이 이슈에 대한 Apigee URL입니다.
    "observed_value": 문자열, 조건이 만료된 경우 알림을 트리거하거나 해결한 관찰된 값이 비어 있을 수 있습니다.
    
    이슈가 개설된 모니터링 리소스에 대한 정보입니다.
    
    "resource": {
      "type": 문자열, gce_instance와 같이 알람을 제공하는 모니터링 리소스 유형의 식별자입니다. 모니터링 리소스 목록을 참조하세요.
      "labels": 객체, 모니터링 리소스와 연결된 라벨의 키-값 쌍입니다.
    },
    "resource_type_display_name": 문자열, 모니터링 리소스 유형의 표시 이름입니다.
    "resource_id": 문자열, 모니터링 리소스의 인스턴스 ID입니다. resource.labels.instance_id와 동일한 값입니다.
    "resource_display_name": 문자열, 모니터링 리소스의 표시 이름입니다.
    "resource_name": 문자열, 이 모니터링 리소스의 생성 이름입니다. 다른 필드의 값으로 구성됩니다.
    
    시계열 데이터의 측정항목 유형에 대한 정보입니다.
    
    "metric": {
      "type": 문자열, compute.googleapis.com/instance/cpu/utilization과 같이 알림을 받는 측정항목 유형의 식별자입니다.측정항목 목록을 참조하세요.
      "displayName": 문자열, 측정항목 유형의 표시 이름입니다.
      "labels": 객체, 측정항목과 연결된 라벨의 키-값 쌍입니다.
    },
    "metadata": {
      "system_labels": 객체, 시스템 메타데이터 라벨의 키-값 쌍입니다.
      "user_labels": 객체, 사용자 메타데이터 라벨의 키-값 쌍입니다.
    },
    
    이슈를 개설/해결한 알림 정책 및 조건에 대한 정보입니다.
    값은 관련 AlertPolicy 객체에서 가져옵니다.
    
    "policy_name": 문자열, 알림 정책의 표시 이름입니다.
    "policy_user_labels": 객체, 정책에 연결된 사용자 라벨의 키-값 쌍입니다.
    "documentation": 객체, Documentation 형식의 삽입된 구조입니다.
    "condition": 객체, Condition 형식의 삽입된 구조입니다.
    "condition_name": 문자열, 조건의 표시 이름, condition.displayName과 동일한 값입니다.
    "threshold_value": 문자열, 이 조건의 임곗값이며 조건이 임곗값 조건이 아닌 경우 비어 있을 수 있습니다.
  },
}

코드로 진행해야하는 부분은 다음과 같다.

  1. 데이터 디코딩
  2. 필요한 데이터 선별
  3. 보기 좋게 가공
  4. Webhook으로 전달

참고로 Webhook은 text로만 데이터를 받는다.

Cloud Functions의 코드는 인라인으로 작성할 수도 있지만 Cloud Storage 경로를 지정할 수도 있다.(CI/CD 적용 가능)

반응형

'Public Cloud > GCP' 카테고리의 다른 글

GCP 감사 로그 관련 문서 모음  (0) 2022.09.03
GCP KMS  (0) 2022.09.03
비공개 Uptime Check 구성  (0) 2022.09.03
GCP GCS 커스텀 도메인 적용  (0) 2022.09.03
GCP Cloud Functions  (0) 2022.09.03