🔥스파르타 TIL (MSA)
Grafana Alert Manager와 Kakao Open API 나에게 보내기 연동해보기 (feat. 좋지 않은 결과)
승승장규
2025. 4. 22. 14:38
문득 이메일, slack, discord 등등 다양하게 Grafana에서 제공해주는 기능들을 보다보니 카카오톡 메시지로 알람을 받으면 좋을 것 같다는 생각을 하게 되었다. (하고 싶은 건 다 해보는 스타일...) 사실 좋은 방법은 아니지만 실제로 카카오톡 메시지를 받으면 신기할 것 같아서 진행해보았다.
우선 Discord, Slack에서 Webhook을 사용하듯이 직접 Grafana에서 사용할 Webhook용 Controller를 만들어야 한다.
{
"client": "Grafana",
"client_url": "http://grafana.local/alerting",
"event_type": "alerting",
"incident_key": "some-unique-key",
"service_key": "key-for-integration",
"description": "CPU usage too high",
"contexts": [
{
"type": "link",
"href": "http://grafana.local/d/abcd1234",
"text": "View dashboard"
}
],
"details": {
"firing": "2",
"resolved": "1",
"numFiring": "2",
"numResolved": "1",
"severity": "critical"
}
}
위와 같은 Grafana에서 보내는 JSON 형식을 참고해서 DTO를 구현한 뒤
public class GrafanaAlertManagerDto {
private String client;
private String clientUrl;
private List<Context> contexts;
private String description;
private String eventType;
private String incidentKey;
private String serviceKey;
private Details details;
public static class Context {
private String href;
private String text;
private String type;
}
public static class Details {
private String firing;
private String lol;
private String numFiring;
private String numResolved;
private String resolved;
private String severity;
}
받은 알람 메시지를 사용해서 kakao 나에게 보내기 기능을 사용해보자
@RestController
@RequestMapping("/api/v1/users/webhook")
public class AlertWebhookController {
private final KaKaoService kaKaoService;
public AlertWebhookController(KaKaoService kaKaoService) {
this.kaKaoService = kaKaoService;
}
@PostMapping("/grafana")
public ResponseEntity<String> receiveAlert(@RequestBody GrafanaAlertManagerDto alertDTO) {
try {
// 알림 메시지 구성
String message = buildAlertMessage(alertDTO);
// 웹 URL (Grafana 대시보드 URL)
String webUrl = alertDTO.getClientUrl();
// 카카오톡으로 메시지 전송
boolean result = kaKaoService.sendMessage(message, webUrl);
if (result) {
return ResponseEntity.ok("알림이 성공적으로 처리되었습니다");
} else {
return ResponseEntity.internalServerError().body("카카오톡 메시지 전송 실패");
}
} catch (Exception e) {
return ResponseEntity.internalServerError().body("알림 처리 중 오류 발생: " + e.getMessage());
}
}
private String buildAlertMessage(GrafanaAlertManagerDto alertDTO) {
StringBuilder messageBuilder = new StringBuilder();
messageBuilder.append("[Grafana 알림]\n");
messageBuilder.append("이벤트: ").append(alertDTO.getEventType()).append("\n");
messageBuilder.append("설명: ").append(alertDTO.getDescription()).append("\n");
if (alertDTO.getDetails() != null) {
messageBuilder.append("심각도: ").append(alertDTO.getDetails().getSeverity()).append("\n");
messageBuilder.append("발생 수: ").append(alertDTO.getDetails().getNumFiring()).append("\n");
if (alertDTO.getDetails().getFiring() != null && !alertDTO.getDetails().getFiring().isEmpty()) {
String firing = alertDTO.getDetails().getFiring();
String[] lines = firing.split("\n");
for (String line : lines) {
if (line.trim().startsWith("Labels:") ||
line.trim().startsWith("- alertname") ||
line.trim().startsWith("Annotations:")) {
messageBuilder.append(line.trim()).append("\n");
}
}
}
}
messageBuilder.append("\n자세한 내용은 알림을 터치하여 확인하세요.");
return messageBuilder.toString();
}
}
public class KaKaoService {
private final RestTemplate restTemplate;
@Value("${kakao.api.token}")
private String accessToken;
@Value("${kakao.api.url:https://kapi.kakao.com/v2/api/talk/memo/default/send}")
private String kakaoApiUrl;
public boolean sendMessage(String message, String webUrl) {
try {
// HTTP 헤더 설정
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.set("Authorization", "Bearer " + accessToken);
// 카카오톡 메시지 템플릿 생성
String templateObject = String.format(
"{\"object_type\":\"text\",\"text\":\"%s\",\"link\":{\"web_url\":\"%s\",\"mobile_web_url\":\"%s\"}}",
message.replace("\n", "\\n").replace("\"", "\\\""),
webUrl, webUrl);
// HTTP 요청 파라미터 설정
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.add("template_object", templateObject);
// HTTP 요청 객체 생성
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(parameters, headers);
// API 호출
ResponseEntity<String> response = restTemplate.exchange(
kakaoApiUrl,
HttpMethod.POST,
requestEntity,
String.class);
if (response.getStatusCode().is2xxSuccessful()) {
return true;
} else {
return false;
}
} catch (Exception e) {
return false;
}
}
}
이제 Grafana에 새로운 Contact point를 생성해주면 되는데 Controller에 설정한 url을 입력해주면 된다. 지금 Grafana가 docker로 띄워져있기 때문에 host.docker.internal로 작성하였다.
이후 Test를 진행하면 정상적으로 잘 전송되는 것을 확인할 수 있다.
하지만 이 방식을 구현하다 느낀 것은 별로 좋은 방식은 아니라는 것이다.
문제 상황을 빠르게 대응하기 위해 알람 시스템을 적용하는게 목적이지만, 카카오 메시지 API를 사용하려면 우선 로그인 절차가 이루어지고 발급받은 access_token이 존재해야 하기 때문에 Grafana에서 제공하는 slack, discord, jira 등등 사용하는게 좋을 것 같다!