DNS 리졸버 쿼리 로그를 Data Query 서비스를 통해 조회
Object Storage에 저장된 DNS 리졸버 쿼리 로그를 Data Catalog와 Data Query를 활용해 SQL 기반으로 조회하는 방법을 안내합니다.
- 예상 소요 시간: 30분
- 사전 준비 사항
- DNS 리졸버 쿼리 로그를 저장할 Object Storage 버킷 생성
시나리오 소개
이 튜토리얼에서는 VPC 내 DNS 리졸버의 쿼리 로그를 Object Storage에 저장한 뒤, Data Catalog와 Data Query를 활용해 SQL 기반으로 로그를 조회하는 방법을 단계별로 안내합니다.
주요 내용은 다음과 같습니다.
- DNS 리졸버 쿼리 로그를 Object Storage 버킷에 저장 설정
- Data Catalog에서 로그 버킷과 연결된 데이터베이스 생성
- Data Query에서 테이블 생성, 파티션 동기화 및 로그 데이터 조회
- (부록) 활용 쿼리 예시
시작하기 전에
DNS 리졸버 쿼리 로그를 Data Query로 조회하려면 Object Storage 버킷이 먼저 생성되어 있어야 합니다. Object Storage 버킷 생성 가이드를 참고하여 버킷을 준비한 후 아래 단계를 진행하세요.
시작하기
Step 1. DNS 리졸버 쿼리 로그 저장 활성화
DNS 리졸버 쿼리 로깅 설정 가이드를 참고하여 VPC 내 DNS 리졸버에 쿼리 로깅을 설정합니다.
Step 2. Data Catalog 설정
Data Catalog 서비스에서 카탈로그와, 로그가 저장되고 있는 버킷과 연결된 데이터베이스를 생성합니다.
-
카카오클라우드 콘솔 > Analytics > Data Catalog 메뉴로 이동합니다.
-
카탈로그 메뉴에서 [카탈로그 생성] 버튼을 클릭합니다.
-
카탈로그 생성 팝업창에서 아래 정보를 입력한 후, [생성] 버튼을 클릭합니다.
구분 설정값 유형 Standard 카탈로그 이름 카탈로그 이름 (예: catalog)VPC 설정 카탈로그에 해당하는 네트워크 선택 (VPC-서브넷당 1개만 생성 가능) -
생성한 카탈로그(
catalog)를 클릭하여 데이터베이스 목록에서 [데이터베이스 생성] 버튼을 클릭합니다. -
데이터베이스 생성 팝업창에서 아래 정보를 입력한 후, [생성] 버튼을 클릭합니다.
구분 설정값 유형 Standard 데이터베이스 카탈로그 위에서 생성한 catalog선택이름 데이터베이스 이름 (예: dns_query)경로 s3 연결: 체크 / 버킷 이름: Object Storage 버킷 이름 입력 / 디렉터리: KCLogs/kr-central-2속성 (선택) - 설명 (선택) - 참고Object Storage에 DNS 리졸버 쿼리 로그는 지정된 버킷에 아래와 같은 형식으로 적재됩니다.
{bucket-name}/KCLogs/{region-name}/{year=yyyy/month=mm/day=dd}/{az-name}_{project-id}_{vpc-id}_{logger-id}_{start-time}_{end-time}.log.gz
Step 3. Data Query 조회
Data Catalog에서 생성한 데이터베이스를 통해 테이블을 만들고 파티션을 동기화한 뒤, 로그 데이터를 조회합니다.
-
카카오클라우드 콘솔 > Analytics > Data Query > 쿼리 편집기 메뉴로 이동합니다.
-
아래와 같이 데이터 원본과 데이터베이스를 선택합니다.
구분 설정값 데이터 원본 Data Catalog에서 생성한 카탈로그 catalog선택데이터베이스 Data Catalog에서 생성한 데이터베이스 dns_query선택 -
우측의 쿼리 편집기에서 테이블을 생성합니다.
테이블 생성CREATE TABLE ${CATALOG_NAME}.${DATABASE_NAME}.${TABLE_NAME} (
availability_zone varchar,
project_id varchar,
query_class varchar,
query_name varchar,
record_type varchar,
region varchar,
response_code varchar,
response_count varchar,
response_protocol varchar,
response_size varchar,
response_time varchar,
timestamp varchar,
version varchar,
vpc_id varchar,
year varchar,
month varchar,
day varchar
)
WITH (
format = 'JSON',
external_location = 's3a://${BUCKET_NAME}/KCLogs/kr-central-2', -- 현재 kr-central-2 리전만 지원
partitioned_by = ARRAY['year', 'month', 'day']
);환경변수 설명 CATALOG_NAME🖌︎ Data Catalog에서 생성한 카탈로그 이름 DATABASE_NAME🖌︎ Data Catalog에서 생성한 데이터베이스 이름 TABLE_NAME🖌︎ 테이블 이름 BUCKET_NAME🖌︎ 버킷 이름 -
새로운 쿼리 탭을 열어 파티션 동기화 쿼리를 실행합니다. 테이블 생성 쿼리와 같은 탭에서 실행하면 파티션이 정상적으로 인식되지 않을 수 있습니다.
파티션 동기화CALL system.sync_partition_metadata(
schema_name => '${DATABASE_NAME}',
table_name => '${TABLE_NAME}',
mode => 'FULL',
case_sensitive => true
);환경변수 설명 DATABASE_NAME🖌︎ Data Catalog에서 생성한 데이터베이스 이름 TABLE_NAME🖌︎ 테이블 이름 -
파티션 동기화 성공 후, 파티션 조회를 진행합니다.
파티션 조회SELECT *
FROM ${CATALOG_NAME}.${DATABASE_NAME}."${TABLE_NAME}$partitions";환경변수 설명 CATALOG_NAME🖌︎ Data Catalog에서 생성한 카탈로그 이름 DATABASE_NAME🖌︎ Data Catalog에서 생성한 데이터베이스 이름 TABLE_NAME🖌︎ 테이블 이름 쿼리 결과로 year, month, day별 파티션이 조회되면 정상입니다.
부록
부록 1. 쿼리 예시
SELECT *
FROM ${CATALOG_NAME}.${DATABASE_NAME}.${TABLE_NAME}
WHERE year = '${YEAR}' AND month = '${MONTH}' AND day = '${DAY}';
| 환경변수 | 설명 |
|---|---|
| CATALOG_NAME🖌︎ | Data Catalog에서 생성한 카탈로그 이름 |
| DATABASE_NAME🖌︎ | Data Catalog에서 생성한 데이터베이스 이름 |
| TABLE_NAME🖌︎ | 테이블 이름 |
| YEAR🖌︎ | 연도, 예: 2026 |
| MONTH🖌︎ | 월, 예: 04 |
| DAY🖌︎ | 일, 예: 30 |
SELECT query_name, COUNT(*) AS query_count
FROM ${CATALOG_NAME}.${DATABASE_NAME}.${TABLE_NAME}
WHERE year = '${YEAR}' AND month = '${MONTH}' AND day = '${DAY}'
GROUP BY query_name
ORDER BY query_count DESC
LIMIT 10;
| 환경변수 | 설명 |
|---|---|
| CATALOG_NAME🖌︎ | Data Catalog에서 생성한 카탈로그 이름 |
| DATABASE_NAME🖌︎ | Data Catalog에서 생성한 데이터베이스 이름 |
| TABLE_NAME🖌︎ | 테이블 이름 |
| YEAR🖌︎ | 연도, 예: 2026 |
| MONTH🖌︎ | 월, 예: 04 |
| DAY🖌︎ | 일, 예: 30 |
SELECT
SUBSTR("timestamp", 12, 2) AS hour,
COUNT(*) AS failed_query_count
FROM ${CATALOG_NAME}.${DATABASE_NAME}.${TABLE_NAME}
WHERE year = '${YEAR}'
AND month = '${MONTH}'
AND day = '${DAY}'
AND response_code != 'NOERROR'
GROUP BY SUBSTR("timestamp", 12, 2)
ORDER BY failed_query_count DESC
LIMIT 10;
| 환경변수 | 설명 |
|---|---|
| CATALOG_NAME🖌︎ | Data Catalog에서 생성한 카탈로그 이름 |
| DATABASE_NAME🖌︎ | Data Catalog에서 생성한 데이터베이스 이름 |
| TABLE_NAME🖌︎ | 테이블 이름 |
| YEAR🖌︎ | 연도, 예: 2026 |
| MONTH🖌︎ | 월, 예: 04 |
| DAY🖌︎ | 일, 예: 30 |
SELECT
query_name,
COUNT(*) AS failed_count
FROM ${CATALOG_NAME}.${DATABASE_NAME}.${TABLE_NAME}
WHERE year = '${YEAR}'
AND month = '${MONTH}'
AND day = '${DAY}'
AND response_code != 'NOERROR'
GROUP BY query_name
ORDER BY failed_count DESC
LIMIT 5;
| 환경변수 | 설명 |
|---|---|
| CATALOG_NAME🖌︎ | Data Catalog에서 생성한 카탈로그 이름 |
| DATABASE_NAME🖌︎ | Data Catalog에서 생성한 데이터베이스 이름 |
| TABLE_NAME🖌︎ | 테이블 이름 |
| YEAR🖌︎ | 연도, 예: 2026 |
| MONTH🖌︎ | 월, 예: 04 |
| DAY🖌︎ | 일, 예: 30 |
SELECT
query_name,
response_time,
response_code,
response_size,
response_protocol
FROM ${CATALOG_NAME}.${DATABASE_NAME}.${TABLE_NAME}
WHERE year = '${YEAR}'
AND month = '${MONTH}'
AND day = '${DAY}'
AND regexp_like(response_time, '^[0-9]+(\.[0-9]+)?$')
ORDER BY CAST(response_time AS DOUBLE) DESC
LIMIT 10;
| 환경변수 | 설명 |
|---|---|
| CATALOG_NAME🖌︎ | Data Catalog에서 생성한 카탈로그 이름 |
| DATABASE_NAME🖌︎ | Data Catalog에서 생성한 데이터베이스 이름 |
| TABLE_NAME🖌︎ | 테이블 이름 |
| YEAR🖌︎ | 연도, 예: 2026 |
| MONTH🖌︎ | 월, 예: 04 |
| DAY🖌︎ | 일, 예: 30 |
-- HAVING COUNT(*) > 1000은 기준값이므로 환경에 맞게 조정 필요
SELECT
vpc_id,
query_name,
COUNT(*) AS query_count
FROM ${CATALOG_NAME}.${DATABASE_NAME}.${TABLE_NAME}
WHERE year = '${YEAR}'
AND month = '${MONTH}'
AND day = '${DAY}'
GROUP BY vpc_id, query_name
HAVING COUNT(*) > 1000
ORDER BY query_count DESC
LIMIT 10;
| 환경변수 | 설명 |
|---|---|
| CATALOG_NAME🖌︎ | Data Catalog에서 생성한 카탈로그 이름 |
| DATABASE_NAME🖌︎ | Data Catalog에서 생성한 데이터베이스 이름 |
| TABLE_NAME🖌︎ | 테이블 이름 |
| YEAR🖌︎ | 연도, 예: 2026 |
| MONTH🖌︎ | 월, 예: 04 |
| DAY🖌︎ | 일, 예: 30 |
SELECT
SUBSTR("timestamp", 12, 2) AS hour,
COUNT(*) AS query_count
FROM ${CATALOG_NAME}.${DATABASE_NAME}.${TABLE_NAME}
WHERE year = '${YEAR}'
AND month = '${MONTH}'
AND day = '${DAY}'
GROUP BY SUBSTR("timestamp", 12, 2)
ORDER BY hour;
| 환경변수 | 설명 |
|---|---|
| CATALOG_NAME🖌︎ | Data Catalog에서 생성한 카탈로그 이름 |
| DATABASE_NAME🖌︎ | Data Catalog에서 생성한 데이터베이스 이름 |
| TABLE_NAME🖌︎ | 테이블 이름 |
| YEAR🖌︎ | 연도, 예: 2026 |
| MONTH🖌︎ | 월, 예: 04 |
| DAY🖌︎ | 일, 예: 30 |
SELECT
day,
COUNT(*) AS query_count
FROM ${CATALOG_NAME}.${DATABASE_NAME}.${TABLE_NAME}
WHERE year = '${YEAR}'
AND month = '${MONTH}'
GROUP BY day
ORDER BY day;
| 환경변수 | 설명 |
|---|---|
| CATALOG_NAME🖌︎ | Data Catalog에서 생성한 카탈로그 이름 |
| DATABASE_NAME🖌︎ | Data Catalog에서 생성한 데이터베이스 이름 |
| TABLE_NAME🖌︎ | 테이블 이름 |
| YEAR🖌︎ | 연도, 예: 2026 |
| MONTH🖌︎ | 월, 예: 04 |
SELECT
response_code,
COUNT(*) AS request_count
FROM ${CATALOG_NAME}.${DATABASE_NAME}.${TABLE_NAME}
WHERE year = '${YEAR}'
AND month = '${MONTH}'
AND day = '${DAY}'
GROUP BY response_code
ORDER BY request_count DESC;
| 환경변수 | 설명 |
|---|---|
| CATALOG_NAME🖌︎ | Data Catalog에서 생성한 카탈로그 이름 |
| DATABASE_NAME🖌︎ | Data Catalog에서 생성한 데이터베이스 이름 |
| TABLE_NAME🖌︎ | 테이블 이름 |
| YEAR🖌︎ | 연도, 예: 2026 |
| MONTH🖌︎ | 월, 예: 04 |
| DAY🖌︎ | 일, 예: 30 |
-- query_name에 DROP, DELETE, UNION 키워드가 포함된 의심 쿼리 집계
SELECT
query_name,
COUNT(*) AS occurrence
FROM ${CATALOG_NAME}.${DATABASE_NAME}.${TABLE_NAME}
WHERE year = '${YEAR}'
AND month = '${MONTH}'
AND day = '${DAY}'
AND (query_name LIKE '%DROP%' OR query_name LIKE '%DELETE%' OR query_name LIKE '%UNION%')
GROUP BY query_name
ORDER BY occurrence DESC;
-- record_type이 TXT이고 비정상적으로 긴 query_name 확인 (기준값 100자는 환경에 맞게 조정 필요)
SELECT
query_name,
response_size,
response_code,
LENGTH(query_name) AS query_length
FROM ${CATALOG_NAME}.${DATABASE_NAME}.${TABLE_NAME}
WHERE year = '${YEAR}'
AND month = '${MONTH}'
AND day = '${DAY}'
AND record_type = 'TXT'
AND LENGTH(query_name) > 100
ORDER BY query_length DESC
LIMIT 20;
| 환경변수 | 설명 |
|---|---|
| CATALOG_NAME🖌︎ | Data Catalog에서 생성한 카탈로그 이름 |
| DATABASE_NAME🖌︎ | Data Catalog에서 생성한 데이터베이스 이름 |
| TABLE_NAME🖌︎ | 테이블 이름 |
| YEAR🖌︎ | 연도, 예: 2026 |
| MONTH🖌︎ | 월, 예: 04 |
| DAY🖌︎ | 일, 예: 30 |
SELECT
record_type,
COUNT(*) AS query_count
FROM ${CATALOG_NAME}.${DATABASE_NAME}.${TABLE_NAME}
WHERE year = '${YEAR}'
AND month = '${MONTH}'
AND day = '${DAY}'
GROUP BY record_type
ORDER BY query_count DESC;
| 환경변수 | 설명 |
|---|---|
| CATALOG_NAME🖌︎ | Data Catalog에서 생성한 카탈로그 이름 |
| DATABASE_NAME🖌︎ | Data Catalog에서 생성한 데이터베이스 이름 |
| TABLE_NAME🖌︎ | 테이블 이름 |
| YEAR🖌︎ | 연도, 예: 2026 |
| MONTH🖌︎ | 월, 예: 04 |
| DAY🖌︎ | 일, 예: 30 |