KHP 모니터링과 알림 – 2부

앞선 1부에서는 KHP 시스템이 모니터링 체계의 데이터 기반이 되는 메트릭과 로그를 어떻게 수집 및 저장하고 이를 가공하여 제공하는지 알아보았습니다.

이로써 카카오에서 운영 중인 클러스터들의 현황을 메트릭 기반의 대시보드와 로그 검색 기능을 사용하여 파악하는 과정을 소개해 드렸습니다. 그러나 개발자들이 항상 모니터링 화면을 들여다봐야 한다면 비효율적일 것입니다. 그래서 운영 중인 클러스터의 문제 상황을 자동으로 파악하고 이를 관련자에게 알려줄 수 있는 알림 체계를 만들 필요가 있었습니다. 2부에서는 이 내용에 대해 말씀드리도록 하겠습니다.

요구 사항

모니터링 및 알림에 대한 요구사항은 운영 조직의 업무 특성에 따라 매우 다양합니다. 따라서 범용성에 초점을 두고 개발된 상용 솔루션으로는 이러한 요건들을 충분히 만족하기 어려울 수 있습니다. 실제로 카카오의 하둡 운영 조직은 KHP를 개발하여 운영하기 전에는 외부의 상용 솔루션을 사용했는데요, 실제 운영에서 필요했던 모니터링 기능을 자체적으로 추가할 수 없었고, 알림 정책이 운영 상황과 맞지 않는 단점들이 존재했습니다. 이런 여러 단점을 보완하기 위해서 개발진은 요구 사항을 만족하는 여러 기능을 별도의 툴로 추가 개발하고 관리해야 했습니다. 

KHP 모니터링 및 알람 기능은 어떤 요구 사항을 바탕으로 개발되었는지 이야기해 보겠습니다.

룰(Rule) 기반의 모니터링 체계

KHP 시스템은 전통적인 룰(rule) 기반의 모니터링 방식을 채택하고 있습니다. KHP 운영 조직은 하둡(Hadoop)에이치베이스(HBase), 드루이드(Druid) 등 여러 빅데이터 시스템에 대한 다년간의 운영 경험과 노하우를 가지고 있습니다. 이러한 경험과 노하우를 잘 정의된 룰로 만드는 것만으로도 충분히 안정적인 시스템 운영 체계를 구축할 수 있다는 자신이 있었습니다. 따라서 손쉽고 빠르게 룰을 정의할 수 있는 시스템을 구현하여, 모두가 자신의 노하우와 경험을 최대한 이끌어내고 체계적인 룰을 정의할 수 있도록 독려하는 것이 중요했습니다.

알림 관리의 필요성

소수의 인원이 100개가 넘는 클러스터를 운영하려면 알림 관리 기능이 반드시 필요합니다. 이러한 운영 환경을 고려하였을 때, 다음과 같은 조건을 만족하는 기능이 필요했습니다.

  • 중요한 알림을 놓치는 ‘양치기 소년 효과‘나 과도한 알림에서 오는 피로도를 줄일 수 있도록 모니터링 임계치와 알림 발송 빈도를 유연하게 조정할 수 있고, 알림을 지연하고 묶음 발송할 수 있어야 합니다.
  • 클러스터와 서비스마다 장애 민감도 및 SLA(Service Level Agreement)가 다르므로 알림 방식과 알림 규칙을 유연하게 설정할 수 있어야 합니다.
  • 이미 인지하고 있는 일시적인 이슈 또는 계획된 서버 작업 등으로 인해 발생하는 알림을 제어할 수 있어야 합니다.
  • 부적절한 모니터링 룰 또는 임계치 때문에 불필요한 알림들이 발생하거나, 특정 클러스터에 미묘한 문제가 있는 것은 아닌지 이력과 통계를 통해 파악할 수 있어야 합니다.
  • 알림 채널을 다각화할 수 있어야 합니다.

단순하게 클러스터에 배포된 크론잡(Cron Job) 형태의 모니터링 및 알림 방식으로는 이러한 다채로운 요구 사항을 만족할 수 없었습니다. KHP 시스템의 중앙 관리 서버인 KHP Server는 이러한 요구 조건들을 염두에 두고 개발되었습니다.

KHP Server: 클러스터 관리 중앙 웹서버

KHP Server는 전체 KHP 클러스터 운영 현황을 보여주는 대시보드를 제공하고 모니터링과 알림 관리를 담당합니다. 주기적으로 일련의 모니터링 테스트들을 실행하여, 클러스터의 문제 상황들을 검출하고 지정된 룰에 따라 운영자에게 알림을 발송합니다.

구성과 흐름

다음은 KHP Server의 모니터링/알림 부분만을 간략화해 나타낸 구성도입니다.

  • KHP 클러스터 호스트의 프로세스들은 슈퍼바이저(Supervisor)에 의해 관리됩니다. KHP Server는 Supervisor API를 통해 각 프로세스의 상태 정보를 수집합니다.
  • 각 프로세스(Processes)의 메트릭은 khp-agent가 카프카(Kafka)로 전송합니다. 카프카 토픽의 메트릭은 드루이드(Druid)에 전달되고 인덱싱됩니다.
  • 각 프로세스의 로그는 파일비트(Filebeat)가 Kafka로 전송합니다. Kafka 토픽의 로그는 로그스태시(Logstash)에 의해 엘라스틱서치(Elasticsearch)로 전달 후 인덱싱됩니다.
  • KHP Server의 백엔드 프로세스가 주기적으로 모니터링 코드들을 실행합니다.
    • 모니터링 코드는 Supervisor API를 통해 수집된 프로세스 상태 정보, 드루이드에 저장된 메트릭, 엘라스틱서치에 저장된 로그 데이터를 통해 문제 상황을 파악합니다.
  • 파악된 문제는 특정 규칙에 따라 카카오톡, 카카오 워크 메신저로 전송됩니다.
    • 발송 채널을 간단히 플러그인 형태로 구현하여 추가할 수 있습니다.
  • 파악된 문제, 발송 여부, 시각 등은 엘라스틱서치에 저장되어 통계 분석이 가능합니다.
  • 운영자는 KHP Server의 대시보드 화면으로 문제 상황을 확인할 수 있으며, 모니터링 규칙, 발송 규칙들을 조정할 수 있습니다.

KHP Server는 2022년 12월 기준, 100개 이상의 클러스터 및 수천 대의 호스트에서 실행 중인 수만 개의 프로세스 정보를 취합하고 있으며, 70개 이상의 모니터링 룰을 주기적으로 실행 중입니다. 전체 정보의 수집과 판별 작업에 소요되는 시간은 2분 미만입니다.

이와 관련해서, 지금쯤 독자분들이 다음과 같은 궁금증을 가지시고 계실 수도 있을 것 같습니다.

메트릭과 로그를 드루이드(Druid)나 엘라스틱서치(Elasticsearch)에 저장하고, 그걸 조회하는 형태로 구현하는 대신, 스트리밍 처리를 하여 판별까지의 지연 시간을 더 줄여볼 수도 있습니다. 하지만 이는 실제 운영 환경에서는 불필요한 일입니다. 하둡(Hadoop) 에코 시스템의 컴포넌트들은 고가용성(H/A, High Availability) 구성이 되어 있어 문제가 발생하더라도 빠르게 자가치유(Self-healing) 되는 경우가 대부분으로, 오히려 자가치유가 완료되기 전에 도달하는 빠른 알림은 운영의 피로도를 높이는 소음이 됩니다.

때문에 KHP Server는 기본적으로 특정 문제가 수 분 이상 지속될 때만 알림이 발송되도록 구현됐습니다. 이런 상황에서 스트리밍 방식으로 모니터링을 구현할 때 추가되는 제약과 복잡도를 감안하면 스트리밍 방식의 실익이 없다고 판단했습니다.

엘라스틱서치는 본래 전문(full-text) 검색엔진으로 시작되었지만, 현재는 메트릭 데이터의 저장과 조회 용도로도 많이 사용되고 있습니다. 그래서 엘라스틱서치만 사용해도 원하는 기능을 모두 구현할 수 있었을 것입니다. 다만, 드루이드에서 사용하는 방식이 메트릭을 처리할 때는 성능상 장점이 뚜렷하기 때문에, 두 방식을 모두 사용하고 있습니다. 더 자세한 내용은 아래 문서를 참고하시면 됩니다.

알림 관리

인하우스(In-house) 시스템의 가장 큰 장점은 명확하고 구체적인 요구 사항을 바탕으로 시스템을 구현할 수 있다는 점입니다. 카카오에서도, 수년간 하둡(Hadoop) 시스템들을 운영한 경험을 바탕으로 운영에 꼭 필요한 기능들을 담고자 했습니다. KHP Server가 효율적인 운영을 돕기 위해 제공하는 다양한 알림 관리 기능에는 어떤 것이 있는지 가볍게 훑어보도록 하겠습니다.

알림 발송 규칙

KHP Server가 검출하는 문제점은 경고(warning)심각(critical)의 두 가지 단계의 중요도로 구분되며, 중요도에 따라 각기 다른 카카오톡 및 카카오 워크 채팅방으로 알림이 발송됩니다. 긴급 대응이 필요하지 않은 warning 채팅방의 경우 평소에 모바일 알림을 끄고 critical 채팅방의 경우만 실시간으로 알림을 받고 있습니다. 앞서 언급한 것처럼, 시스템을 운영하면서 생기는 피로를 줄이기 위해, 문제가 발생하면 알림을 바로 발송하지 않고 일정 시간 이상 지속된 경우에 발송하고 있습니다. 그리고 대부분의 문제는 그 기간 안에 해소가 됩니다.

 

위의 기간 동안 전체 파악된 문제 중 warning 채팅방으로 알림 발송된 비율은 12%, critical 채팅방으로 알림 발송된 비율은 0.89%에 불과합니다.

다만, 어떠한 경우에도 발생한 문제에 대한 사후 파악이 필요하기 때문에 발생/해소 이력은 엘라스틱서치로 인덱싱하며, 이에 대한 통계를 필요할 때 확인할 수 있습니다. 이 통계를 이용해 어떤 클러스터에서 어떤 문제가 발생하는지 확인 및 조치를 하고, 모니터링 임계치를 조정하기도 합니다.

클러스터 설정

  • 클러스터 별로 알림 발송 채널을 다르게 설정할 수 있습니다.
  • 전역으로 설정된 기본값과 다른, 클러스터에 별개로 설정된 임계치 값의 목록을 볼 수 있으며 조정할 수 있습니다.
  • 알림을 완전히 끄는 유지 관리 모드(maintenance mode) 설정이 가능합니다. 유지 관리 모드를 설정한 후 해제하는 걸 잊어버려 알림을 받지 못하는 경우를 방지하기 위해서 반드시 모드가 종료되는 날짜를 입력받도록 되어 있습니다.

문제점 목록

클러스터에 검출된 문제점은 아래와 같이 표시됩니다.

  • 개별 문제점의 알림 방식을 임시 조정할 수 있습니다. Critical 레벨 이상인 경우에만 발송하도록 하거나 (CRI), 아예 무시할 수도 있습니다 (OFF).
  • 해당 문제에 대한 설명을 볼 수 있고, 모니터링 코드의 구현을 확인할 수 있는 깃허브(GitHub) 링크가 제공됩니다. 검출에 사용된 임계치 값을 확인하고 여기서 조정할 수도 있습니다.

카카오톡 알림

아래의 이미지는 카카오톡을 통해 받은 알림을 보여주는 예시입니다. 카카오 워크에도 동일한 형태의 알림이 동시에 발송됩니다.

카카오톡 봇

일시적이거나 무시해도 되는 문제를 알림받는 경우, 굳이 랩탑을 꺼내고 VPN을 연결하기 번거로울 수 있죠. 이러한 불편을 해소하고자 카카오톡 봇을 통해서 유지 관리 모드를 설정하거나 개별 문제점의 알림을 비활성화하는 기능을 구현하였습니다.

모니터링 룰의 구현

도입부에서 말씀드린 것처럼, 조직원 모두가 모니터링 룰을 손쉽게 구현할 수 있게 하는 것이 KHP Server의 개발 과정에서 중요한 목표였습니다. 일반적인 메트릭 기반의 모니터링 기능은 메트릭 값이 특정 임계치를 넘는지 확인하는 형태로 구현되어 있습니다. 먼저, 손쉬운 모니터링 룰을 만들고자 다음과 같이 1.메트릭 명, 2.비교 연산자, 3.임계치 값을 나열하고 임계치를 확인하는 방식을 생각해 볼 수 있습니다.

메트릭명비교 연산자임계치레벨
cpu.user>80warning
cpu.user>90critical
disk.space<20warning
disk.space<10critical
............

쉽고 간단한 방법이라 생각할 수 있겠으나 이 방식이 가지는 한계는 다음과 같이 명확합니다.

  • 문제 상황을 단 하나의 메트릭만으로는 판단할 수 없는 경우가 많습니다. 간단한 예로, CPU 사용량을 볼 때도 user timesystem time을 합해서 보아야 하지만, 위와 같이 모든 메트릭이 분리되어 있다면 이를 활용하기 어렵습니다. 메트릭 수집 단계에서 최종 참조 형태를 고려하여 미리 가공할 수도 있겠지만 이는 번거로운 작업이며 그렇게 할 경우 모니터링 룰의 유연한 수정이 어렵습니다.
  • 메트릭이 아닌 다른 내용을 고려한 판단이 필요할 수 있습니다. 클러스터 구성 노드 수, 호스트를 구성하고 있는 프로세스의 조합 등, 단순 메트릭 만으로는 표현이 되지 않는 조건들에 대한 분기가 필요합니다.
  • 메트릭들을 서로 비교해 문제를 파악해야 할 수도 있습니다.
  • 클러스터 별로 중요도와 장애 민감도가 다르므로 임계치를 다르게 설정하고 싶을 수 있습니다. 위의 테이블 형태를 확장하여 클러스터 별 임계치를 입력하게 할 수도 있겠으나 항목이 많아질수록 관리가 용이하지 않고 직관적으로 파악하기 어려워집니다.

이런 사항들을 고려할 때, 결국 코드로 모니터링을 구현하는 것이 가장 유연한 방식이라 판단하게 되었습니다. 다만, 단순하게 임계치 비교를 통해 모니터링이 가능한 항목인 경우, 위의 테이블 방식과 비교해서도 어렵지 않다고 느낄 정도로 사용하기 쉬운 API를 제공하는 것을 목표로 했습니다.

모니터링 API

다음 코드는 실제 동작하는 온전한 모니터링 구현체 예제입니다.해당 코드에서는 보일러플레이트 코드(Boilerplate Code)가 최소화되었고, 드루이드 조회 및 클러스터 별 임계치 비교와 같은 부분들이 모두 추상화되어 있습니다. 결과적으로 해당 API를 모르는 사람이 보아도 어떠한 동작을 수행하는 코드인지 쉽게 파악할 수 있도록 구현하였습니다.

				
					# <코드 예제 A>
class KuduMonitor < KHP::Monitor::Base
  def run
    runnings = all('kudu/tserver/server.tablets_num_running')
    emit('Too many tablets',
         warning: runnings > w(:kudu_tablets_num_running),
         critical: runnings > c(:kudu_tablets_num_running),
         description: '태블릿 수가 권장치를 초과')

    failures = all('kudu/tserver/server.tablets_num_failed')
    emit('Failed tablets',
         warning: failures > w(:kudu_tablets_num_failed),
         critical: failures > c(:kudu_tablets_num_failed),
         description: '비정상 상태의 태블릿 발견')
  end
end

				
			

다른 예제를 한번 볼까요? 이 부분에서는 메트릭을 산술 연산할 수 있다는 것을 알 수 있습니다.

				
					# <코드 예제 B>
class DruidMonitor < KHP::Monitor::Base
  def run
    slot_used_percent =
      max('druid/overlord/taskSlot.used.count') /
      max('druid/overlord/taskSlot.total.count') * 100

    emit('Task slot usage',
         warning: slot_used_percent > w(:druid_slot_used_percent),
         critical: slot_used_percent > c(:druid_slot_used_percent),
         description: 'Task slot 사용률 임계치 초과')
  end
end
				
			

메트릭 기반 모니터링

KHP Server는 모니터링 룰을 구현할 코드의 개발을 돕기 위해, 모니터링 API를 실행하고 그 결과를 바로 볼 수 있는 irb 기반의 REPL(Read-Eval-Print Loop)을 제공하고 있습니다. 이에 대한 예제 코드는 하단의 <코드 예제 C>에서 확인할 수 있습니다.

				
					// <코드 예제 C>
irb(#<KHP::Monitor::DruidMonitor>):001:0> max('druid/overlord/taskSlot.used.count')
=>
[{:cluster=>"░░░░░░░░░", :value=>599.0},
 {:cluster=>"▒▒▒▒▒▒▒▒▒▒▒▒▒▒", :value=>512.0},
 {:cluster=>"▓▓▓▓▓▓▓", :value=>1.0}]
irb(#<KHP::Monitor::DruidMonitor>):002:0>

				
			

이해를 돕기 위해, REPL을 실행하고 단계별로 코드를 입력하면 출력되는 결과를 차근차근 확인해 보겠습니다.

코드 따라가 보기

위의 <코드 예제 A>, <코드 예제 B>를 유심히 보셨다면, all, max 메서드가 먼저 눈에 들어오셨을 텐데요, 이 메서드들은 메트릭을 조회합니다. all(‘서비스명/롤명/메트릭명‘) 메서드를 실행하면 드루이드에 저장된 해당 메트릭을 조회하여 호스트 단위의 결과를 리턴합니다. max, sum, min, avg 등의 메서드는 호스트 단위가 아니라 클러스터 단위로 집계(aggregate)된 결과를 리턴합니다. 모니터링을 수행할 때, 호스트 단위의 판단이 필요한지 클러스터 전반적인 판단이 필요한지에 따라 적절한 메서드를 사용할 수 있습니다.

				
					# max 메서드를 사용했으므로 클러스터별 max 값이 구해진다.
max('druid/overlord/taskSlot.used.count')
# [{ cluster: '░░░░░░░░░', value: 570.0 },
#  { cluster: '▒▒▒▒▒▒▒▒▒▒▒▒▒▒', value: 523.0 },
#  { cluster: '▓▓▓▓▓▓▓', value: 1.0 }]

				
			
				
					max('druid/overlord/taskSlot.total.count')
# [{ cluster: '░░░░░░░░░', value: 1215.0 },
#  { cluster: '▒▒▒▒▒▒▒▒▒▒▒▒▒▒', value: 1200.0 },
#  { cluster: '▓▓▓▓▓▓▓', value: 75.0 }]

				
			

또, 위 코드에서는 드루이드의 슬롯 사용량과 전체 슬롯 사이즈를 나타내는 taskSlot.used.counttaskSlot.total.count 메트릭을 조회합니다. 해당 메트릭은 Druid Overlord 프로세스로부터 수집되는데, Druid Overlord는 H/A로 구성되어 있으므로 클러스터 상에 여러 인스턴스가 존재합니다. 따라서 이 경우 max 메서드로 클러스터 별로 가장 큰 값만 취하도록 합니다.

위의 메서드는 다음과 같은 형태의 Druid SQL로 변환되어 실행되는데, 실제 값이 참조될 때까지 백그라운드에서 비동기적으로 실행됩니다. 따라서 참조해야 하는 메트릭이 많아지더라도 모니터링 코드의 수행 시간이 그에 비례해서 증가하지는 않습니다.

				
					-- Cluster 단위의 aggregation
select cluster, latest("value") as "value"
from "khp-service-metrics"
where __time > time_shift(current_timestamp, 'PT10M', -1)
  and __time <= current_timestamp
  and cluster in (...)
  and service = 'druid'
  and role = 'overlord'
  and name = 'taskSlot.used.count'
group by cluster -- all 메서드의 경우 group by cluster, host
				
			

이렇게 얻은 결과들을 다음과 같이 단순 스칼라 값을 다루듯 연산할 수 있습니다.

				
					# 두 개의 메트릭을 병렬로 조회하고 cluster 필드를 기준으로 join하여 연산을 수행
slot_used_percent =
  max('druid/overlord/taskSlot.used.count') /
  max('druid/overlord/taskSlot.total.count') * 100
# [{ cluster: '░░░░░░░░░', value: 46.913 },
#  { cluster: '▒▒▒▒▒▒▒▒▒▒▒▒▒▒', value: 43.583 },
#  { cluster: '▓▓▓▓▓▓▓', value: 1.333 }]

				
			

그리고 비교 연산자를 이용해 특정 임계치를 넘는 값만 필터링할 수 있게됩니다.

				
					slot_used_percent > 45
# [{ cluster: '░░░░░░░░░', value: 46.913, compare: :>, threshold: 45 }]

				
			

그러나 위와 같이 고정 값(예: 45)을 코드에 그대로 넣게 되면, 클러스터 별 특성을 반영할 수 없습니다. 그래서 클러스터 별로 차이가 나는 임계치 값은 클러스터 설정 파일에 다음과 같이 적을 수 있도록 했습니다.

그리고 이 값을 참조하는 w, c 메서드를 제공합니다. w, c 메서드 결과에 대해 비교 연산을 실행하면, 클러스터 별 임계치를 고려하여 따라 필터링 되도록 했습니다.

				
					# 클러스터별 warning 임계치과 비교하여 필터링
slot_used_percent > w(:druid_slot_used_percent)

# 클러스터별 critical 임계치과 비교하여 필터링
slot_used_percent > c(:druid_slot_used_percent)
				
			

최종적으로 이렇게 필터링 된 데이터를 emit 메서드에 넘기는 것으로 하나의 모니터링 항목이 구현됩니다.

로그 기반 모니터링

로그 데이터는 다음과 같은 두 가지 방식으로 활용할 수 있습니다.

  1. 로그를 통해 현재 상황을 이해합니다. 이 과정에서 모르는 문제를 발견할 가능성이 있습니다.
  2. 이미 알고 있는 패턴의 로그를 찾아, 이미 알고 있는 문제가 발생하고 있는지 파악합니다.

자동화된 로그 모니터링으로 전에 모르던 문제 상황을 검출하는 것은 간단한 문제가 아닙니다. 따라서 현실적으로 우리가 구현하는 로그 모니터링은 다음과 같은 형태가 될 것입니다.

  • 특정 패턴의 로그를 찾습니다.
  • 검색된 로그가 “있다면”, 혹은 “많다면” 문제 상황으로 판단합니다.

여기서 “있다면“과 “많다면“은 각각 임계치 0 초과, N 초과의 상황으로 표현될 수 있으며, 결국 로그 기반 모니터링도 메트릭 모니터링과 그 판단 과정이 크게 다르지 않다는 것을 알 수 있습니다. 즉, 로그 패턴으로 파생 메트릭을 만들고 이를 임계치와 비교하는 방식으로 모니터링을 진행한다고 이해할 수 있습니다.

이러한 로그 기반 모니터링에 대한 아이디어를 구현하고자, 엘라스틱서치의 조회 프로세스를 다음과 같이 고수준의 API로 추상화하였고, 비교적 복잡한 과정 없이 로그 기반 모니터링을 구현할 수 있었습니다.

				
					# 지난 10분간 HBase 리젼서버의 responseTooSlow와 responseTooLarge 로그 수를 검출
log_count('hbase/regionserver/responseToo*', period: 'PT10M') > w(:hbase_response_warning_count)
				
			

물론 조회 패턴이 이것보다 더 복잡한 경우도 존재합니다. 그럴 때는 엘라스틱서치의 기본 문법을 그대로 사용해서 원하는 결과를 얻을 수 있습니다. 이처럼 로그 기반 모니터링 API는 “간단한 것은 쉽게, 하지만 복잡한 것도 가능하게”라는 기본 철학을 따릅니다.

마치며

이상으로 KHP 시스템의 모니터링과 알림에 대해 알아보았습니다. 자체 개발한 시스템이 기존 상용 솔루션에 비해 범용성이 다소 떨어질지는 모르지만, 카카오의 개발 환경과 상황에 꼭 맞게 구현되어 결과적으로 훨씬 만족스럽게 사용하고 있습니다. 또한, 필요한 모니터링 항목을 클러스터에 손쉽게 추가할 수 있게 되었고, 불필요한 알림으로 인한 운영 시의 피로도 많이 줄었습니다.

공개된 프로젝트가 아니다 보니 기술적인 내용을 구체적으로 다루지는 못했지만 우리가 어떤 고민을 가지고 어떤 문제들을 해결하고자 했는지는 충분히 독자분들께 전달되었을 것이라 믿습니다. 이 글을 통해 현재 운영하고 있는 모니터링과 알림 시스템에 적용해 볼 만한 아이디어나 영감을 얻으셨기를 바라며 글을 마칩니다.

감사합니다.

카카오톡 공유 보내기 버튼

Latest Posts

제5회 Kakao Tech Meet에 초대합니다!

Kakao Tech Meet #5 트렌드와 경험 및 노하우를 자주, 지속적으로 공유하며 개발자 여러분과 함께 성장을 도모하고 긴밀한 네트워크를 형성하고자 합니다.  다섯 번째

테크밋 다시 달릴 준비!

(TMI: 이 글의 썸네일 이미지는 ChatGPT와 DALL・E로 제작했습니다. 🙂) 안녕하세요, Kakao Tech Meet(이하 테크밋)을 함께 만들어가는 슈크림입니다. 작년 5월에 테크밋을 처음 시작하고,