전체 글

총 653개의 글

반응형

Fluent Bit과 Loki로 쿠버네티스 로그 모으기: kind 실습과 Grafana 조회, AWS 확장 예시

로컬 쿠버네티스 실습 5편Fluent Bit → Loki → Grafana파드 로그를 한곳에서 보기수집기와 저장소를 나눠 설치하고, 실제로 겪은 오류 4개를 고친 과정Fluent BitLokiLogQLGrafanaFluent Bit과 Loki는 경쟁 제품이 아니라 역할이 다릅니다. Fluent Bit은 노드마다 떠서 컨테이너 로그를 읽고 쿠버네티스 정보를 붙여 보내는 수집기이고, Loki는 그 로그를 받아 저장하고 LogQL로 찾게 해 주는 저장소입니다. kind 클러스터에 Loki를 단일 구성으로, Fluent Bit을 DaemonSet으로 설치해 연결했고, web 파드에 보낸 요청 5개가 Grafana에서 5줄 모두 검색되는 것까지 확인했습니다. 설치 중에는 차트 스키마 오류, 약 11GiB를 요청하..

kube-prometheus-stack 설치와 Grafana 접속: kind 클러스터 모니터링과 target down 해결

로컬 쿠버네티스 실습 4편 kube-prometheus-stack으로Prometheus·Grafana 띄우기 helm 한 번으로 설치하고, Gateway로 Grafana를 열고, down 6개를 0개로 만든 기록 kube-prometheus-stackGrafanaPrometheusGateway APIkube-prometheus-stack helm 차트를 쓰면 Prometheus, Grafana, Alertmanager, node-exporter, kube-state-metrics가 한 번에 설치됩니다. 실습 PC의 kind 클러스터에서는 81초 만에 모든 파드가 Running이 됐고 메모리는 약 1.4GB를 더 썼습니다. Grafana는 3편의 Gateway에 grafana.localhost 호스트..

NGINX Ingress 대신 Gateway API: kind 클러스터 앱을 Windows localhost로 접속하기

로컬 쿠버네티스 실습 3편NGINX Ingress 대신 Gateway APIkind 앱을 localhost:8080으로Envoy Gateway를 설치하고, 로드밸런서 없는 kind에서 Windows 브라우저까지 연결한 과정Gateway APIEnvoy GatewayHTTPRoutekindkind 클러스터에서 Gateway API를 쓰려면 Gateway API 리소스 정의와 구현체가 필요한데, Envoy Gateway helm 차트 하나로 둘 다 설치됩니다. 남는 문제는 kind에 로드밸런서가 없다는 점입니다. 이 글에서는 Envoy 프록시를 8080 포트가 연결된 제어 플레인 노드에 띄우고 노드의 80번 포트를 직접 받게(hostPort) 설정해서, Windows 브라우저의 localhost:8080이 ..

Headlamp 설치와 사용법: 로컬 쿠버네티스를 화면으로 보고 파드 배포하기

로컬 쿠버네티스 실습 2편 Headlamp 설치와 사용법클릭으로 보고, YAML로 배포하기 WSL2 안의 kind 클러스터를 Windows 앱에서 보고 파드까지 배포해 본 기록 Headlamp쿠버네티스 GUIkindWSL2Headlamp는 쿠버네티스 클러스터를 화면으로 보고 관리하는 데스크톱 앱입니다. Windows에서는 winget으로 설치하고, 사용자 폴더의 .kube\config에 클러스터 접속 정보를 넣으면 별도 설정 없이 클러스터가 목록에 나옵니다. 보기 전용이 아니라 왼쪽 아래 '+ 생성' 버튼에 YAML을 붙여 넣고 '적용'을 누르면 Deployment를 바로 만들 수 있고, 실제로 만든 파드 2개가 Running이 되는 것까지 확인했습니다.1편에서 WSL2 Ubuntu 24.04 안에..

Windows에서 무료 쿠버네티스 실습 환경 만들기: WSL2 + Docker Engine + kind

로컬 쿠버네티스 실습 1편Windows에서 무료 쿠버네티스 실습 환경WSL2 + Docker Engine + kind클라우드 비용 없이 내 PC에 노드 3개짜리 클러스터를 만든 과정과 실제로 겪은 문제WSL2Docker EnginekindkubectlWindows 11에서는 WSL2에 Ubuntu를 설치하고, 그 안에 Docker Engine과 kind를 올리면 클라우드 비용 없이 제어 플레인 1개와 워커 2개로 된 쿠버네티스 클러스터를 만들 수 있습니다. 실제로 해 보니 클러스터 생성은 52초면 끝났고, 시간이 걸린 부분은 WSL 설치 뒤 재부팅과 배포판 설치였습니다. Docker Desktop 없이 Docker 공식 apt 저장소로 설치하고, kubectl·kind·helm은 공식 배포처 파일을 SH..

Bedrock Knowledge Bases 검색 설정과 재정렬: 필요한 문서를 먼저 찾는 방법 | Bedrock 28편

Amazon Bedrock 완전 가이드 · 28편 Knowledge Bases 검색 설정과 재정렬 필요한 문서를 먼저 찾는 방법 Amazon Bedrock Knowledge Bases는 질문할 때마다 검색 설정을 바꿀 수 있습니다. 가져올 청크 수는 numberOfResults(기본 5개, 1~100)로, 검색 범위는 메타데이터 필터로, 결과 순서는 재정렬 모델로, 여러 주제가 섞인 질문은 질의 분해로 조정합니다. 설정을 바꾸기 전에 Retrieve로 검색 결과의 원문·위치·메타데이터·점수를 먼저 보고, 문제가 수집·검색·생성 중 어디서 생겼는지 가른 뒤 한 번에 하나씩 바꿉니다.고객 관리형 Knowledge Base의 비정형 데이터 소스를 기준으로 Retrieve·RetrieveAndGenerat..

Bedrock Knowledge Bases 임베딩과 벡터 저장소: 차원·인덱스·유사도 이해하기 | Bedrock 27편

Amazon Bedrock 완전 가이드 · 27편 Knowledge Bases 임베딩과 벡터 저장소 차원·인덱스·유사도 이해하기 Amazon Bedrock Knowledge Bases에서 임베딩 모델은 청크를 정해진 길이(차원)의 숫자 벡터로 바꾸고, 벡터 저장소는 이 벡터를 인덱스에 저장해 질문 벡터와 가까운 것을 찾습니다. 벡터 인덱스의 차원은 임베딩 모델의 출력 차원과 같아야 하며, 하이브리드 검색은 일부 저장소에서만 쓸 수 있습니다. 임베딩 모델과 저장소는 만든 뒤 바꾸기 어려우므로, 지원 리전·검색 방식·비용 구조를 함께 보고 고릅니다.고객 관리형 Knowledge Base를 기준으로 임베딩 모델과 차원, 벡터 인덱스와 필드 매핑, 지원 벡터 저장소와 S3 Vectors 선택 기준, 의미 ..

Bedrock Knowledge Bases 문서 파싱과 청킹: 검색 품질을 결정하는 전처리 | Bedrock 26편

Amazon Bedrock 완전 가이드 · 26편 Knowledge Bases 문서 파싱과 청킹 검색 품질을 결정하는 전처리 Amazon Bedrock Knowledge Bases는 문서를 수집할 때 먼저 파싱(내용 추출)하고, 그 결과를 청크(chunk)라는 조각으로 나눠 임베딩합니다. 표나 이미지가 중요한 문서라면 텍스트만 읽는 기본 파서 대신 Data Automation이나 모델 기반 파서를 검토하고, 청크는 문서 구조와 질문 유형에 맞는 분할 방식을 고릅니다. 분할 설정은 데이터 소스를 만든 뒤에는 바꿀 수 없으므로, 처음에 실제 문서와 질문으로 시험해 보고 정해야 합니다.고객 관리형 Knowledge Base를 기준으로 파서 세 가지의 차이, 기본·고정 크기·계층적·의미 기반·분할 없음의 ..

데이터 소스와 수집: 문서는 어떻게 검색 대상이 되는가 | Bedrock 25편

Amazon Bedrock 완전 가이드 · 25편 데이터 소스와 수집 문서는 어떻게 검색 대상이 되는가 Amazon Bedrock Knowledge Bases에서 문서는 데이터 소스를 동기화(수집 작업)해야 검색 대상이 됩니다. 동기화는 증분 방식이라 지난 동기화 이후 추가·수정·삭제된 문서만 처리하므로, 원본을 고친 뒤 동기화하지 않으면 검색 결과는 예전 상태로 남습니다. 수집이 끝나면 그 내용은 원본 시스템의 문서 권한과 관계없이 검색 권한(bedrock:Retrieve)을 가진 누구나 조회할 수 있으므로, 사용자별 접근 제어는 별도로 설계해야 합니다.가상의 사내 안내 도우미를 예로 데이터 소스 유형, 동기화와 직접 수집의 차이, 문서 삭제가 검색 결과에 반영되는 방식과 삭제 정책, 수집 작업 ..

Knowledge Bases 구성 선택: 관리형 저장소와 직접 선택한 저장소 | Bedrock 24편

Amazon Bedrock 완전 가이드 · 24편 Knowledge Bases 구성 선택 관리형 저장소와 직접 선택한 저장소 Amazon Bedrock Knowledge Bases는 두 방식으로 구성합니다. 관리형(Bedrock Managed) Knowledge Base는 저장소·임베딩·재정렬을 Bedrock이 맡고, 고객 관리형(Customer-managed) Knowledge Base는 사용자가 고른 벡터 저장소를 직접 만들고 운영합니다. AWS는 사용 편의성·정확도·비용의 균형을 이유로 관리형을 권장합니다. 벡터 저장소를 직접 조정해야 하거나 기존 저장소를 그대로 써야 한다면 고객 관리형을 선택합니다.가상의 사내 안내 도우미를 예로 Knowledge Base와 데이터 소스의 관계, 관리형과 고..
반응형

Fluent Bit과 Loki로 쿠버네티스 로그 모으기: kind 실습과 Grafana 조회, AWS 확장 예시

반응형
로컬 쿠버네티스 실습 5편
Fluent Bit → Loki → Grafana
파드 로그를 한곳에서 보기

수집기와 저장소를 나눠 설치하고, 실제로 겪은 오류 4개를 고친 과정

Fluent BitLokiLogQLGrafana

Fluent Bit과 Loki는 경쟁 제품이 아니라 역할이 다릅니다. Fluent Bit은 노드마다 떠서 컨테이너 로그를 읽고 쿠버네티스 정보를 붙여 보내는 수집기이고, Loki는 그 로그를 받아 저장하고 LogQL로 찾게 해 주는 저장소입니다. kind 클러스터에 Loki를 단일 구성으로, Fluent Bit을 DaemonSet으로 설치해 연결했고, web 파드에 보낸 요청 5개가 Grafana에서 5줄 모두 검색되는 것까지 확인했습니다. 설치 중에는 차트 스키마 오류, 약 11GiB를 요청하는 기본 캐시, inotify 한도 부족 같은 문제를 차례로 만났고 그 해결 방법도 함께 정리했습니다.

1~4편의 kind 클러스터(노드 3개, Gateway API, kube-prometheus-stack의 Grafana)를 이어서 씁니다. Loki 차트 grafana-community/loki 21.0.0(Loki 3.7.9), Fluent Bit 차트 fluent/fluent-bit 0.58.3(Fluent Bit 5.1.3) 기준입니다. Loki 확장 구성, 보관 기간 설정, 알림은 다루지 않고, AWS로 보내는 부분은 구성 예시로만 소개합니다.

01

Fluent Bit과 Loki는 무엇이 다른가

로그 시스템은 보통 두 부분으로 나뉩니다. 로그를 모아서 보내는 수집기와, 받아서 저장하고 검색하게 해 주는 저장소입니다. Fluent Bit은 앞쪽, Loki는 뒤쪽이라 함께 쓰는 경우가 많습니다.

Fluent Bit과 Loki의 역할 비교
구분 Fluent Bit Loki
역할 로그 수집·가공·전달(에이전트) 로그 저장·검색(백엔드)
설치 위치 노드마다 하나씩(DaemonSet) 클러스터에 한 곳(필요하면 여러 개로 확장)
주로 하는 일 컨테이너 로그 파일을 읽고 파드 정보를 붙여 목적지로 보냄 받은 로그를 저장하고 LogQL로 검색
연결 대상 Loki, Elasticsearch·OpenSearch, CloudWatch Logs, S3 등 Fluent Bit, Grafana Alloy 같은 수집기에서 받음
화면 없음 Grafana에서 조회

Loki는 로그 본문 전체가 아니라 namespace, pod 같은 라벨에만 색인을 만듭니다. 그래서 저장 부담이 적고, Prometheus와 같은 라벨 방식이라 Grafana에서 메트릭과 로그를 같은 라벨로 오가기 쉽습니다. 대신 본문 검색은 라벨로 범위를 먼저 좁혀야 빠릅니다. Fluent Bit이 왜 필요한지와 필터 구조는 아래 관련 글에서 더 자세히 다룹니다.

02

Loki 설치: 차트 위치부터 바뀌었다

Loki 공식 설치 문서는 이제 grafana-community 저장소의 차트를 안내합니다. 실습 시점에 grafana-community/loki는 21.0.0(Loki 3.7.9)이었고, 예전 위치인 grafana/loki는 7.5.0(Loki 3.6.16)에 설명이 Grafana Enterprise Logs용으로 바뀌어 있었습니다. 예전 글을 따라 할 때는 저장소 주소부터 확인합니다. Loki 단일 구성 설치 문서

차트 기본값은 인증이 켜져 있고(auth_enabled: true) 저장소가 S3입니다. 실습용으로는 인증을 끄고 노드 디스크(filesystem)에 저장하도록 바꿨습니다. 다른 배포 모드의 복제 수는 공식 예시처럼 모두 0으로 둡니다(아래는 그중 일부).

deploymentMode: Monolithic
loki:
auth_enabled: false
commonConfig:
replication_factor: 1
storage:
type: filesystem
schemaConfig:
configs:
- from: "2024-04-01"
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: loki_index_
period: 24h
singleBinary:
replicas: 1
persistence:
size: 5Gi
read:
replicas: 0
write:
replicas: 0
backend:
replicas: 0

처음에는 공식 문서 예시에 있던 minio: enabled: false도 넣었는데 설치가 바로 거부됐습니다. 차트 21.0.0의 설정 검사(스키마)에 minio 항목이 없기 때문입니다. 문서와 차트 버전이 어긋날 수 있으니, 오류 메시지에 나온 키를 지우면 됩니다.

Error: INSTALLATION FAILED: values don't meet the specifications of the schema(s) in the following chart(s):
loki:
- at '': additional properties 'minio' not allowed
helm repo add grafana-community https://grafana-community.github.io/helm-charts
helm install loki grafana-community/loki --version 21.0.0 -n loki --create-namespace -f loki-values.yaml --wait
03

기본 캐시가 메모리 11GiB를 요청한다

설치는 61초 만에 끝났지만 파드 목록을 보면 Loki 본체 외에 게이트웨이, loki-canary(노드마다), 캐시 두 개가 함께 떠 있었습니다. 캐시의 메모리 요청량을 확인하니 로컬 실습에는 너무 컸습니다.

기본으로 켜지는 캐시의 메모리 요청
파드 메모리 요청 근거
loki-chunks-cache-0 9830Mi chunksCache.allocatedMemory 기본값 8192(MB)의 1.2배
loki-results-cache-0 1229Mi resultsCache.allocatedMemory 기본값 1024(MB)의 1.2배

합치면 약 11GiB입니다. 실습 PC는 WSL에 메모리가 23GiB 잡혀 있어서 떴지만, 메모리가 적은 PC라면 이 파드 때문에 다른 파드가 자리를 못 잡을 수 있습니다. 로컬에서는 캐시와 canary를 끕니다.

chunksCache:
enabled: false
resultsCache:
enabled: false
lokiCanary:
enabled: false
test:
enabled: false

test.enabled: false도 필요합니다. canary만 끄고 업그레이드하면 Helm test requires the Loki Canary to be enabled라는 오류로 거부되는데, 차트의 helm 테스트가 canary를 쓰기 때문입니다. 네 값을 모두 넣고 helm upgrade하자 파드가 loki-0과 게이트웨이 두 개로 줄었습니다.

04

Fluent Bit 설치: 출력만 Loki로 바꾸기

Fluent Bit 차트의 기본 설정은 컨테이너 로그를 읽는 tail 입력, kubelet 로그를 읽는 systemd 입력, 쿠버네티스 정보를 붙이는 kubernetes 필터, 그리고 Elasticsearch(elasticsearch-master)로 보내는 출력으로 되어 있습니다. 출력을 Loki로 바꾸고, kind 노드에는 필요 없는 systemd 입력을 뺐습니다. Fluent Bit Loki 출력

tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
config:
outputs: |
[OUTPUT]
Name loki
Match kube.*
Host loki.loki.svc.cluster.local
Port 3100
Labels job=fluent-bit, namespace=$kubernetes['namespace_name'], pod=$kubernetes['pod_name'], container=$kubernetes['container_name']
Remove_Keys kubernetes
Line_Format json

Labels의 $kubernetes['…']는 kubernetes 필터가 붙인 값을 꺼내 Loki 라벨로 쓰는 표기입니다. 라벨은 검색 범위를 좁히는 기준이므로 namespace, pod, container처럼 값의 종류가 많지 않은 것만 씁니다. Remove_Keys kubernetes는 라벨로 쓴 정보를 로그 본문에서 빼서 저장 공간을 줄입니다. tolerations가 없으면 Fluent Bit이 제어 플레인 노드에 뜨지 않아, 3편의 Envoy 프록시처럼 그 노드에 있는 파드의 로그를 놓칩니다.

helm repo add fluent https://fluent.github.io/helm-charts
helm install fluent-bit fluent/fluent-bit --version 0.58.3 -n logging --create-namespace -f fluent-bit-values.yaml
05

CrashLoopBackOff: Too many open files 해결

설치 직후 Fluent Bit 파드 3개가 모두 CrashLoopBackOff가 됐고 재시작이 5번까지 쌓였습니다. 로그에는 tail 입력이 시작하자마자 실패한 기록이 있었습니다.

[error] [/src/fluent-bit/plugins/in_tail/tail_fs_inotify.c:425 errno=24] Too many open files
[error] failed initialize input tail.0
[error] [engine] input initialization failed

파일 개수 문제가 아니라 inotify 한도 문제입니다. Fluent Bit은 로그 파일 변화를 inotify로 감시하는데, kind 노드 3개와 그 안의 파드가 모두 WSL 커널 하나를 같이 써서 fs.inotify.max_user_instances 기본값 128이 모자랐습니다. kind 문서의 알려진 문제에 나오는 권장값으로 WSL에서 올리고, 재부팅 후에도 유지되게 파일로 남겼습니다. kind 알려진 문제

sysctl -w fs.inotify.max_user_instances=512 fs.inotify.max_user_watches=524288
printf 'fs.inotify.max_user_instances = 512\nfs.inotify.max_user_watches = 524288\n' > /etc/sysctl.d/99-kind-inotify.conf
kubectl -n logging rollout restart ds/fluent-bit

kind 노드는 커널을 공유하므로 노드 컨테이너 안에서도 바로 512로 보였고, 다시 시작한 Fluent Bit 3개가 제어 플레인을 포함해 모두 Running이 됐습니다. 로그에는 [output:loki:loki.0] configured, hostname=loki.loki.svc.cluster.local:3100이 찍혔습니다.

06

Grafana에서 로그 보기: 데이터 소스와 LogQL

4편의 Grafana에 Loki를 연결합니다. kube-prometheus-stack의 Grafana는 grafana_datasource: "1" 라벨이 붙은 ConfigMap을 사이드카가 읽어 데이터 소스로 등록하므로, Grafana를 다시 시작할 필요가 없습니다.

apiVersion: v1
kind: ConfigMap
metadata:
name: loki-datasource
namespace: monitoring
labels:
grafana_datasource: "1"
data:
loki-datasource.yaml: |
apiVersion: 1
datasources:
- name: Loki
type: loki
uid: loki
access: proxy
url: http://loki.loki.svc.cluster.local:3100

적용하고 20초 안에 데이터 소스 목록에 Loki가 생겼고, 상태 점검에서 Data source successfully connected.가 나왔습니다. 다만 그 전에 열어 둔 Grafana 화면의 Explore 데이터 소스 목록에는 Loki가 보이지 않았습니다. 웹 화면이 페이지를 열 때 받아 둔 목록을 쓰기 때문이라, 브라우저를 새로고침하면 나타납니다. 확인용으로 Windows에서 http://localhost:8080/?from=loki-test-1부터 -5까지 요청을 보낸 뒤 Grafana의 Explore에서 Loki를 고르고 검색했습니다.

{namespace="default", container="nginx"} |= "loki-test"

중괄호 안이 라벨로 범위를 좁히는 부분이고, |=는 그 안에서 본문에 문자열이 들어간 줄을 거릅니다. 요청 5개가 nginx 접근 로그 5줄로 모두 검색됐고, 파드별로는 3줄과 2줄이었습니다. sum by (pod) (count_over_time({…} |= "loki-test" [15m]))처럼 바꾸면 줄 수를 숫자로 세어 메트릭처럼 그래프로 그릴 수도 있습니다.

브라우저로 http://localhost:8080/?from=loki-test-6, -7을 열고 같은 쿼리를 Grafana Explore에서 실행하면 아래처럼 보입니다. 브라우저 요청은 캐시 때문에 상태 코드가 200 대신 304로 찍히기도 합니다. Logs volume 그래프에는 unknown 10건과 error 1건이 나왔는데, error는 브라우저가 자동으로 요청한 favicon.ico를 nginx가 찾지 못해 남긴 오류 로그였습니다. 그 줄의 referrer에 loki-test가 들어 있어 함께 검색된 것이고, nginx 오류 로그는 [error]가 찍혀 stderr로 나오므로 error로 분류됩니다. 접근 로그에는 수준 표시가 없어 unknown으로 나옵니다.

로그를 모으는 네임스페이스는 default, envoy-gateway-system, kube-system, logging, loki, monitoring 여섯 곳이었습니다. Loki는 Fluent Bit이 붙인 라벨 외에 service_name과 stream 라벨을 스스로 추가합니다.

쿼리를 직접 쓰지 않아도 됩니다. Explore의 Builder 모드에서 Label filters에 namespace = monitoring을 고르면 아래에 {namespace="monitoring"} 식이 만들어지고, 실습 화면에서는 Grafana 자신의 로그가 214줄 나왔습니다. 위쪽 Logs volume 그래프는 시간대별 로그 양을 error·info·warning으로 나눠 보여 줬는데(이때 error 9, info 138, warning 30), 이 수준은 Fluent Bit 설정에 넣은 값이 아니라 로그 본문의 level=info 같은 표시를 Grafana와 Loki가 읽어 detected_level 필드로 나눈 것입니다. 에러가 몰린 시간대를 그래프에서 먼저 찾고 그 구간의 로그만 펼쳐 보는 식으로 쓰면 편합니다.

07

Fluent Bit의 확장성: AWS로 함께 보내는 구성 예시

Fluent Bit은 출력을 여러 개 둘 수 있어서, 같은 로그를 Loki와 다른 저장소로 동시에 보낼 수 있습니다. 예를 들어 운영 환경에서는 실시간 조회는 Loki, 장애 대응용 보관은 CloudWatch Logs, 장기 보관은 S3로 나누는 구성을 생각할 수 있습니다. 아래는 이 실습 클러스터에 적용하지 않은 구성 예시로, 설정 이름은 Fluent Bit 공식 문서 기준이고 로그 그룹·버킷 이름은 가상의 값입니다. Fluent Bit CloudWatch 출력

[OUTPUT]
Name cloudwatch_logs
Match kube.*
Region ap-northeast-2
Log_Group_Name /lab/kubernetes/containers
Log_Stream_Prefix from-fluent-bit-
Auto_Create_Group On
Log_Retention_Days 7

[OUTPUT]
Name s3
Match kube.*
Region ap-northeast-2
Bucket example-lab-log-archive
Total_File_Size 50M
Upload_Timeout 10m
Compression gzip
S3_Key_Format /kubernetes/%Y/%m/%d/%H/$UUID.gz

같은 Match kube.*를 가진 출력이 여러 개면 Fluent Bit은 같은 로그를 각각에 보냅니다. CloudWatch Logs 출력은 Auto_Create_Group On이면 로그 그룹이 없을 때 만들고, S3 출력은 Total_File_Size나 Upload_Timeout 중 먼저 닿는 조건에서 파일을 올립니다. 공식 문서에 적힌 필요 권한은 아래와 같습니다.

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", "logs:PutRetentionPolicy"],
"Resource": "LOG_GROUP_ARN_PATTERN"
},
{
"Effect": "Allow",
"Action": ["s3:PutObject"],
"Resource": "LOG_BUCKET_OBJECTS_ARN"
}
]
}

Fluent Bit 파드가 AWS에 접근하려면 이 권한을 가진 자격 증명이 필요합니다. 로컬 kind에서는 자격 증명을 따로 넣어야 하고, 키를 매니페스트에 직접 쓰지 않도록 주의합니다. LOG_GROUP_ARN_PATTERN은 리전과 계정 ID, /lab/kubernetes/로 시작하는 로그 그룹 이름을 담은 CloudWatch Logs 로그 그룹 ARN으로, LOG_BUCKET_OBJECTS_ARN은 버킷 example-lab-log-archive 안의 모든 객체를 가리키는 S3 ARN(버킷 ARN 뒤에 /*)으로 바꿉니다. 공식 문서의 예시 정책은 리소스를 *로 두지만, 이렇게 로그 그룹과 버킷 이름으로 좁히는 편이 안전합니다.

09

자주 묻는 질문

Promtail이나 Grafana Alloy 대신 Fluent Bit을 써도 되나요?

됩니다. Loki는 수집기를 가리지 않고 푸시 API로 로그를 받습니다. Grafana 문서와 그대로 맞추고 싶다면 Grafana Alloy가 편하고, 같은 로그를 Loki 말고 CloudWatch Logs·S3·Elasticsearch로도 보내야 한다면 Fluent Bit이 맞습니다.

Loki 게이트웨이는 꼭 필요한가요?

이 실습에서는 Fluent Bit과 Grafana 모두 Loki 서비스(3100)에 직접 연결했기 때문에 게이트웨이를 거치지 않습니다. 게이트웨이는 여러 구성 요소로 나눈 배포에서 요청을 나눠 주거나 인증을 앞에서 받을 때 쓰입니다.

작성 방식과 다음 편

이 글의 명령, 오류 메시지, 결과는 1편의 실습 PC에서 직접 실행한 것이며, 설치와 설정은 Loki·Fluent Bit·kind 공식 문서를 기준으로 했습니다. AWS 출력 부분만 실행하지 않은 구성 예시입니다. 실행 기록 정리와 글 작성에 AI 도구를 함께 사용했습니다. 다음 편에서는 Keycloak을 설치하고 Grafana 로그인을 SSO로 바꿉니다.

반응형

kube-prometheus-stack 설치와 Grafana 접속: kind 클러스터 모니터링과 target down 해결

반응형
로컬 쿠버네티스 실습 4편
kube-prometheus-stack으로
Prometheus·Grafana 띄우기

helm 한 번으로 설치하고, Gateway로 Grafana를 열고, down 6개를 0개로 만든 기록

kube-prometheus-stackGrafanaPrometheusGateway API

kube-prometheus-stack helm 차트를 쓰면 Prometheus, Grafana, Alertmanager, node-exporter, kube-state-metrics가 한 번에 설치됩니다. 실습 PC의 kind 클러스터에서는 81초 만에 모든 파드가 Running이 됐고 메모리는 약 1.4GB를 더 썼습니다. Grafana는 3편의 Gateway에 grafana.localhost 호스트 이름으로 HTTPRoute를 붙여 Windows 브라우저에서 열었습니다. 설치 직후 Prometheus 수집 대상 28개 중 6개가 down이었는데, kind가 제어 플레인 구성 요소의 메트릭 포트를 127.0.0.1에만 열기 때문이었고 바인딩 주소를 바꾸자 28개 모두 up이 됐습니다.

1~3편에서 만든 kind 클러스터(노드 3개, Envoy Gateway의 lab-gateway가 Windows localhost:8080을 받음)를 이어서 씁니다. 차트는 kube-prometheus-stack 93.1.0, Grafana 13.3.0 기준입니다. 알림 설정, 장기 보관, 운영 환경 보안 설정은 다루지 않습니다.

01

kube-prometheus-stack에 들어 있는 것

kube-prometheus-stack은 쿠버네티스 모니터링에 필요한 구성 요소를 묶은 helm 차트입니다. Prometheus를 직접 설정 파일로 운영하지 않고, Prometheus Operator가 ServiceMonitor 같은 리소스를 읽어 수집 대상을 관리합니다. kube-prometheus-stack 차트

설치된 주요 구성 요소
구성 요소하는 일실습에서 뜬 파드
Prometheus메트릭을 주기적으로 긁어 저장prometheus-…-0 (2/2)
Prometheus OperatorServiceMonitor 등을 읽어 Prometheus 설정 관리…-operator (1/1)
Grafana대시보드로 메트릭 시각화kps-grafana (3/3)
Alertmanager경고를 모아 전달alertmanager-…-0 (2/2)
node-exporter노드 CPU·메모리·디스크 수집노드마다 1개씩 3개
kube-state-metrics디플로이먼트·파드 같은 리소스 상태를 메트릭으로1개
02

helm으로 설치하기

prometheus-community 저장소를 추가하고 설치합니다. --wait를 붙이면 모든 파드가 준비될 때까지 기다린 뒤 끝납니다.

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update prometheus-community
helm install kps prometheus-community/kube-prometheus-stack -n monitoring --create-namespace --wait --timeout 10m

실습 시점의 차트 버전은 93.1.0이었고 설치는 81초가 걸렸습니다. 모든 서비스는 ClusterIP로 만들어지며, Grafana 서비스 이름은 릴리스 이름을 붙인 kps-grafana(포트 80)입니다. WSL 메모리 사용량은 설치 전 1.9GiB에서 설치 후 3.3GiB가 됐습니다. 참고로 kind에는 metrics-server가 없어서 kubectl top nodes는 Metrics API not available을 냅니다. 이 글의 메트릭은 모두 Prometheus가 따로 수집합니다.

03

Gateway에 Grafana 붙이기: 다른 네임스페이스 라우트 허용

3편의 Gateway(lab-gateway)는 default 네임스페이스에 있고 Grafana는 monitoring 네임스페이스에 있습니다. Grafana용 HTTPRoute를 monitoring에 만들고, grafana.localhost라는 호스트 이름으로 web 앱과 구분합니다.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: grafana
namespace: monitoring
spec:
parentRefs:
- name: lab-gateway
namespace: default
hostnames:
- grafana.localhost
rules:
- backendRefs:
- name: kps-grafana
port: 80

이 라우트만 먼저 적용해 보면 붙지 않습니다. HTTPRoute 상태에 아래처럼 나오고, grafana.localhost로 보낸 요청은 404가 돌아왔습니다. Gateway 리스너는 기본적으로 같은 네임스페이스의 라우트만 받기 때문입니다. Gateway 리소스 설명

Accepted=False (NotAllowedByListeners) No listeners included by this parent ref allowed this attachment.

Gateway 리스너에 allowedRoutes를 추가해 다른 네임스페이스의 라우트도 받게 합니다. 실습 환경이라 from: All로 열었고, 실제 운영에서는 from: Selector로 라벨이 붙은 네임스페이스만 허용하는 편이 안전합니다.

listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All

적용 후 HTTPRoute는 Accepted=True가 됐고, Gateway의 attachedRoutes는 2(web, grafana)가 됐습니다. 호스트 이름이 없는 web 라우트는 그대로 나머지 요청을 받습니다.

04

Windows에서 grafana.localhost로 접속

Windows는 localhost로 끝나는 이름을 내 PC 주소로 풀어 줍니다. 실습 PC에서 Resolve-DnsName grafana.localhost를 실행하면 ::1이 나왔고, hosts 파일을 고치지 않아도 바로 접속됐습니다.

PS> curl.exe -s http://grafana.localhost:8080/api/health
{
"database": "ok",
"version": "13.3.0",
...
}
PS> curl.exe -s -o NUL -w "HTTP %{http_code}" http://grafana.localhost:8080/login
HTTP 200

브라우저에서 http://grafana.localhost:8080을 열면 Grafana 13.3.0 로그인 화면이 나옵니다. 사용자 이름은 admin이고, 비밀번호는 차트가 만든 시크릿에 들어 있습니다.

kubectl get secret -n monitoring kps-grafana -o jsonpath='{.data.admin-password}' | base64 -d; echo

로그인하면 차트가 미리 넣어 둔 대시보드가 있습니다. 실습 클러스터에는 대시보드용 ConfigMap이 29개 있었고, Dashboards 메뉴를 열면 Alertmanager, CoreDNS, etcd와 Kubernetes / Compute Resources / …, Kubernetes / Networking / …, Node Exporter / … 같은 대시보드가 태그와 함께 나옵니다. 따로 만들지 않아도 노드·네임스페이스·파드 단위로 바로 볼 수 있습니다.

예를 들어 Kubernetes / Compute Resources / Pod 대시보드에서 namespace를 default, pod를 2편에서 만든 web-… 파드로 고르면 그 파드의 CPU와 메모리가 그래프로 나옵니다. 실습 화면에서 nginx 컨테이너는 CPU를 거의 쓰지 않았고(0에 가까움), 메모리는 Working Set 기준 12.0MiB, RSS 8.37MiB였습니다. 요청이 거의 없는 nginx라 값이 작지만, 같은 화면에서 리소스 요청·제한(Quota)과 실제 사용량을 나란히 볼 수 있어 파드 크기를 정할 때 쓸 수 있습니다. 실습 PC에서 어떤 파드가 자원을 많이 쓰는지 찾을 때도 이 대시보드부터 보면 편합니다.

05

target down 6개: kind에서 생기는 이유와 해결

설치 직후 Prometheus 수집 대상을 확인하면 28개 중 22개만 up이었습니다. Prometheus 화면의 Status → Targets에서 볼 수도 있고, API로 세어 볼 수도 있습니다.

설치 직후 down이었던 수집 대상
대상포트오류
kube-controller-manager10257connection refused
kube-scheduler10259connection refused
kube-etcd2381connection refused
kube-proxy (노드 3개)10249connection refused

모두 connection refused라는 점이 실마리입니다. Prometheus는 노드 주소로 접속하는데, kind가 만든 이 구성 요소들은 메트릭 포트를 노드 안의 127.0.0.1에서만 엽니다. 실제로 제어 플레인 노드 안의 정적 파드 매니페스트에는 --bind-address=127.0.0.1이, etcd에는 --listen-metrics-urls=http://127.0.0.1:2381이 들어 있었고, kube-proxy 설정의 metricsBindAddress는 빈 값(기본값 127.0.0.1:10249)이었습니다.

실습 클러스터이므로 바인딩 주소를 0.0.0.0으로 바꿨습니다. kube-proxy는 ConfigMap을 고친 뒤 데몬셋을 다시 시작하고, 나머지 셋은 제어 플레인 노드 컨테이너 안의 매니페스트를 고치면 kubelet이 파드를 다시 만듭니다.

kubectl -n kube-system get cm kube-proxy -o yaml \
| sed 's/metricsBindAddress: .*/metricsBindAddress: 0.0.0.0:10249/' \
| kubectl apply -f -
kubectl -n kube-system rollout restart ds/kube-proxy
docker exec lab-control-plane sh -c "
sed -i 's/--bind-address=127.0.0.1/--bind-address=0.0.0.0/' /etc/kubernetes/manifests/kube-controller-manager.yaml /etc/kubernetes/manifests/kube-scheduler.yaml
sed -i 's#--listen-metrics-urls=http://127.0.0.1:2381#--listen-metrics-urls=http://0.0.0.0:2381#' /etc/kubernetes/manifests/etcd.yaml"

약 40초 뒤 네 구성 요소가 다시 Running이 됐고, 그다음 수집 주기에 28개 모두 up으로 바뀌었습니다. 이때 켜져 있던 경고는 Watchdog 하나뿐이었는데, 경고 전달 경로가 살아 있는지 확인하려고 항상 켜 두는 정상 경고입니다.

백업 파일 위치 주의

매니페스트를 고치기 전에 백업을 같은 /etc/kubernetes/manifests 폴더에 두면 kubelet이 그 파일까지 파드로 읽어 같은 구성 요소가 두 번 뜰 수 있습니다. 이 실습에서는 백업을 폴더 밖(/root/manifest-backup)에 두었습니다. 또 이 수정은 kind 클러스터를 다시 만들면 사라지므로, 다시 만들 때 같은 작업을 반복해야 합니다.

07

자주 묻는 질문

운영 클러스터에서도 bind-address를 0.0.0.0으로 바꿔도 되나요?

그대로 따라 하면 안 됩니다. 메트릭 포트가 모든 네트워크에 열리므로 운영 환경에서는 노드 방화벽이나 네트워크 정책으로 Prometheus만 접근하게 막아야 합니다. EKS 같은 관리형 클러스터는 제어 플레인 구성 요소를 사용자가 직접 바꿀 수 없으므로 수집 방식이 다릅니다.

Grafana를 localhost:8080/grafana 같은 경로로 열 수는 없나요?

가능하지만 Grafana에 하위 경로 설정(root_url과 serve_from_sub_path)을 따로 해야 합니다. 이 글은 설정을 바꾸지 않아도 되는 호스트 이름 방식(grafana.localhost)을 썼습니다.

작성 방식과 다음 편

이 글의 명령과 출력은 1편의 실습 PC에서 직접 실행한 결과이며, 설치와 설정은 kube-prometheus-stack 차트와 Gateway API 공식 문서를 기준으로 했습니다. 실행 기록 정리와 글 작성에 AI 도구를 함께 사용했습니다. 다음 편에서는 Loki를 설치해 파드 로그를 모으고, 같은 Grafana 화면에서 메트릭과 로그를 함께 봅니다.

반응형

NGINX Ingress 대신 Gateway API: kind 클러스터 앱을 Windows localhost로 접속하기

반응형
로컬 쿠버네티스 실습 3편
NGINX Ingress 대신 Gateway API
kind 앱을 localhost:8080으로

Envoy Gateway를 설치하고, 로드밸런서 없는 kind에서 Windows 브라우저까지 연결한 과정

Gateway APIEnvoy GatewayHTTPRoutekind

kind 클러스터에서 Gateway API를 쓰려면 Gateway API 리소스 정의와 구현체가 필요한데, Envoy Gateway helm 차트 하나로 둘 다 설치됩니다. 남는 문제는 kind에 로드밸런서가 없다는 점입니다. 이 글에서는 Envoy 프록시를 8080 포트가 연결된 제어 플레인 노드에 띄우고 노드의 80번 포트를 직접 받게(hostPort) 설정해서, Windows 브라우저의 localhost:8080이 Gateway → HTTPRoute → web 파드로 이어지게 만들었습니다. 적용 후 약 20초 만에 Gateway가 준비됐고, 요청 6번이 파드 2개에 3건씩 나뉘어 들어가는 것까지 확인했습니다.

1편에서 만든 kind 클러스터(노드 3개, Windows 8080 → 제어 플레인 80 포트 연결)와 2편에서 만든 web Deployment(nginx 파드 2개)를 그대로 씁니다. Envoy Gateway v1.10.0, Kubernetes v1.37.0 기준이며 HTTPS와 여러 도메인 라우팅은 다루지 않습니다.

01

왜 Ingress가 아니라 Gateway API인가

쿠버네티스에서 외부 요청을 서비스로 보내는 방법은 오랫동안 Ingress였고, 그 구현체로 NGINX Ingress Controller가 가장 많이 쓰였습니다. GitHub에서 확인하면 kubernetes/ingress-nginx 저장소는 2026년 3월 이후 새 커밋이 없습니다. 새로 실습 환경을 만든다면 Ingress의 후속 표준인 Gateway API로 시작하는 편이 낫습니다. Gateway API 공식 문서

Gateway API는 역할을 리소스 세 단계로 나눕니다. 이 구분을 알면 아래 매니페스트가 왜 여러 개인지 이해하기 쉽습니다.

Gateway API의 주요 리소스
리소스 정하는 것 이 글에서 만든 것
GatewayClass 어떤 구현체(컨트롤러)를 쓸지 eg (Envoy Gateway)
Gateway 어느 포트·프로토콜로 요청을 받을지 lab-gateway (HTTP 80)
HTTPRoute 받은 요청을 어느 서비스로 보낼지 web (/ → web 서비스 80)
02

Envoy Gateway 설치: CRD까지 helm 한 번에

kind는 Gateway API 리소스를 기본으로 넣지 않습니다. 설치 전에 kubectl api-resources --api-group=gateway.networking.k8s.io를 실행하면 아무것도 나오지 않습니다. Envoy Gateway의 helm 차트가 리소스 정의(CRD)와 컨트롤러를 함께 설치합니다. Envoy Gateway 빠른 시작

helm install eg oci://docker.io/envoyproxy/gateway-helm --version v1.10.0 -n envoy-gateway-system --create-namespace
kubectl wait --timeout=5m -n envoy-gateway-system deployment/envoy-gateway --for=condition=Available

컨트롤러가 준비되기까지 18초가 걸렸습니다. 설치 중에 인증서를 만드는 eg-gateway-helm-certgen 잡이 한 번 실행되고 Completed로 끝납니다. 설치된 리소스 종류는 GatewayClass, Gateway, HTTPRoute, GRPCRoute, TLSRoute 등 10가지였습니다.

확인해 볼 점: 함께 깔리는 Gateway API 버전

같은 날 Gateway API 최신 릴리스는 v1.7.0이었지만, Envoy Gateway v1.10.0 차트가 설치한 CRD에는 v1.6.1 experimental 채널이 적혀 있었습니다. 구현체가 지원하는 버전이 따로 있으므로, Gateway API CRD를 직접 최신으로 올리기 전에 구현체의 지원 버전을 먼저 확인합니다. 확인 명령: kubectl get crd gateways.gateway.networking.k8s.io -o jsonpath='{.metadata.annotations}'

03

로드밸런서 없는 kind에서 Windows까지 잇는 방법

Envoy Gateway는 Gateway를 만들면 Envoy 프록시 파드와 LoadBalancer 서비스를 만듭니다. 클라우드라면 이 서비스에 외부 주소가 붙지만 kind에는 로드밸런서가 없습니다. 선택지는 세 가지입니다.

kind에서 Gateway에 접속하는 방법
방법 장점 단점
kubectl port-forward 설정 없이 바로 확인 가능(공식 빠른 시작의 방법) 명령을 켜 둔 동안만 접속, 매번 다시 실행
MetalLB 같은 로드밸런서 구현 클라우드와 비슷하게 주소가 붙음 붙는 주소가 WSL 안 Docker 네트워크 대역이라 Windows 브라우저에서 바로 쓰려면 추가 설정이 필요할 수 있음
노드 포트 직접 받기(hostPort) 1편의 8080 연결을 그대로 사용, 항상 접속 가능 Envoy 파드를 특정 노드에 고정해야 함

이 글은 세 번째 방법을 썼습니다. 1편에서 Windows 8080을 제어 플레인 노드 컨테이너의 80번 포트에 연결해 두었으므로, Envoy가 그 노드의 80번 포트를 직접 받으면 됩니다. Envoy 프록시의 모양은 EnvoyProxy라는 Envoy Gateway 전용 리소스로 바꿉니다. EnvoyProxy 설정 안내

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyProxy
metadata:
name: kind-hostport
namespace: envoy-gateway-system
spec:
provider:
type: Kubernetes
kubernetes:
envoyService:
type: ClusterIP
envoyDeployment:
replicas: 1
pod:
nodeSelector:
ingress-ready: "true"
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
patch:
type: StrategicMerge
value:
spec:
template:
spec:
containers:
- name: envoy
ports:
- containerPort: 10080
hostPort: 80
protocol: TCP

설정마다 이유가 있습니다. envoyService.type: ClusterIP는 붙지 않을 외부 주소를 기다리지 않게 합니다. nodeSelector의 ingress-ready: "true"는 1편에서 제어 플레인에 붙여 둔 라벨이고, 제어 플레인에는 일반 파드가 올라가지 못하게 막는 테인트가 있어서 tolerations로 허용합니다. 마지막 patch가 핵심인데, 노드의 80번 포트를 Envoy 컨테이너의 10080번 포트로 연결합니다.

80이 아니라 10080인 이유는 Envoy Gateway가 1024보다 작은 특권 포트를 컨테이너 안에서 높은 번호로 바꿔 열기 때문입니다. Gateway 리스너를 80으로 만들면 Envoy는 실제로 10080에서 기다립니다. 처음에는 이 점을 모르면 hostPort를 80 → 80으로 잡기 쉽습니다.

04

GatewayClass·Gateway·HTTPRoute 만들기

GatewayClass는 위의 EnvoyProxy를 parametersRef로 가리켜서, 이 클래스로 만드는 모든 Gateway가 그 설정을 따르게 합니다. web Deployment에는 아직 서비스가 없으므로 서비스도 함께 만듭니다.

apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: eg
spec:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
parametersRef:
group: gateway.envoyproxy.io
kind: EnvoyProxy
name: kind-hostport
namespace: envoy-gateway-system
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: lab-gateway
spec:
gatewayClassName: eg
listeners:
- name: http
protocol: HTTP
port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: web
spec:
parentRefs:
- name: lab-gateway
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: web
port: 80

HTTPRoute의 parentRefs가 어느 Gateway에 붙을지, backendRefs가 요청을 받을 서비스를 정합니다. 서비스(web, 포트 80 → 파드 80)까지 하나의 파일에 넣어 kubectl apply -f로 적용했고, 상태가 준비될 때까지 기다렸습니다.

$ kubectl wait --for=condition=Programmed gateway/lab-gateway --timeout=180s
gateway.gateway.networking.k8s.io/lab-gateway condition met
$ kubectl get gatewayclass,gateway
NAME CONTROLLER ACCEPTED
eg gateway.envoyproxy.io/gatewayclass-controller True
NAME CLASS PROGRAMMED
lab-gateway eg True

적용부터 Programmed까지 약 20초가 걸렸습니다. HTTPRoute 상태에도 Accepted=True와 ResolvedRefs=True가 나와야 합니다. ResolvedRefs가 False라면 backendRefs의 서비스 이름이나 포트가 틀린 경우가 많습니다. Envoy 프록시 파드는 의도대로 lab-control-plane 노드에 떴고, envoy 컨테이너의 10080 포트에 hostPort 80이 붙어 있었습니다.

05

Windows 브라우저로 접속하고 부하 분산 확인

이제 Windows에서 접속합니다. 요청 경로는 Windows localhost:8080 → kind 제어 플레인 노드 80 → Envoy 10080 → web 서비스 → nginx 파드입니다.

PS> curl.exe -s -o NUL -w "HTTP %{http_code}" http://localhost:8080/
HTTP 200
PS> curl.exe -s http://localhost:8080/ | Select-String "<title>"
<title>Welcome to nginx!</title>

Windows 브라우저에서 http://localhost:8080을 열어도 nginx 환영 페이지가 나왔습니다. 마지막으로 요청이 파드 2개로 나뉘는지 봤습니다. Windows에서 /?n=1부터 /?n=6까지 6번 요청한 뒤 각 파드의 nginx 접근 로그를 세면 됩니다.

for p in $(kubectl get pods -l app=web -o name); do
echo "$p $(kubectl logs "$p" --since=5m | grep -c 'GET /?n=')건"
done

결과는 web-…-5gdxz 3건, web-…-k5682 3건이었습니다. Gateway가 서비스 뒤의 파드들로 요청을 나눠 보낸다는 것을 로그로 확인할 수 있습니다.

PowerShell에서 bash 명령을 넘길 때

위 반복문을 PowerShell에서 wsl -- bash -c "..."로 한 줄에 넘기면 $(...) 부분을 PowerShell이 먼저 해석해서 bash 문법 오류가 납니다. 실습 중 실제로 겪은 문제로, 스크립트 파일로 저장한 뒤 wsl -- bash 파일로 실행하면 해결됩니다.

07

자주 묻는 질문

hostPort 대신 port-forward만 써도 되나요?

확인만 할 때는 됩니다. Envoy Gateway 빠른 시작도 로드밸런서가 없으면 kubectl port-forward로 Envoy 서비스에 접속하는 방법을 안내합니다. 다만 명령을 켜 둔 동안에만 접속되므로, 다음 편부터 여러 앱을 계속 띄워 둘 실습 환경에는 hostPort 방식이 편합니다.

Gateway의 ADDRESS에 나오는 주소로 접속하면 되나요?

이 구성에서는 Envoy 서비스를 ClusterIP로 바꿨기 때문에 ADDRESS에는 클러스터 안에서만 쓰는 주소가 나옵니다. Windows에서는 그 주소가 아니라 localhost:8080으로 접속합니다.

작성 방식과 다음 편

이 글의 명령과 출력은 1편의 실습 PC에서 직접 실행한 결과이며, 설치와 설정은 Gateway API와 Envoy Gateway 공식 문서를 기준으로 했습니다. 실행 기록 정리와 글 작성에 AI 도구를 함께 사용했습니다. 다음 편에서는 kube-prometheus-stack으로 Prometheus와 Grafana를 설치하고, Grafana도 이 Gateway를 통해 Windows 브라우저에서 엽니다.

반응형

Headlamp 설치와 사용법: 로컬 쿠버네티스를 화면으로 보고 파드 배포하기

반응형
로컬 쿠버네티스 실습 2편
Headlamp 설치와 사용법
클릭으로 보고, YAML로 배포하기

WSL2 안의 kind 클러스터를 Windows 앱에서 보고 파드까지 배포해 본 기록

Headlamp쿠버네티스 GUIkindWSL2

Headlamp는 쿠버네티스 클러스터를 화면으로 보고 관리하는 데스크톱 앱입니다. Windows에서는 winget으로 설치하고, 사용자 폴더의 .kube\config에 클러스터 접속 정보를 넣으면 별도 설정 없이 클러스터가 목록에 나옵니다. 보기 전용이 아니라 왼쪽 아래 '+ 생성' 버튼에 YAML을 붙여 넣고 '적용'을 누르면 Deployment를 바로 만들 수 있고, 실제로 만든 파드 2개가 Running이 되는 것까지 확인했습니다.

1편에서 WSL2 Ubuntu 24.04 안에 만든 kind 클러스터(kind-lab, 노드 3개)를 Windows 11에 설치한 Headlamp 0.46.0으로 연결한 결과입니다. 화면 이름은 Headlamp 한국어 화면 기준이며, 로그 보기·파드 터미널·삭제는 이 글에서 다루지 않습니다.

01

Headlamp란: Kubernetes Dashboard 대신 쓰는 GUI

Headlamp는 쿠버네티스 리소스를 목록과 상세 화면으로 보여 주는 오픈소스 GUI입니다. 데스크톱 앱으로 설치할 수도 있고 클러스터 안에 웹 앱으로 올릴 수도 있습니다. 이 글은 Windows 데스크톱 앱을 씁니다. Headlamp 저장소

쿠버네티스 GUI를 고를 때 예전에는 Kubernetes Dashboard나 Lens를 먼저 떠올렸습니다. 2026년 11월 기준 GitHub에서 확인하면 Kubernetes Dashboard 저장소는 kubernetes-retired 조직으로 옮겨져 보관(archived) 상태이고, Lens의 공개 저장소는 2025년 2월 이후 갱신이 없습니다. Headlamp는 쿠버네티스 공식 하위 프로젝트 조직(kubernetes-sigs)에서 개발되고 있어 새로 시작한다면 먼저 검토할 만합니다.

쿠버네티스 클러스터를 보는 방법 비교
방법형태이런 경우에 맞음
kubectl명령어모든 작업의 기본. 자동화와 스크립트
k9s터미널 대시보드터미널에서 키보드로 빠르게 둘러볼 때
Headlamp데스크톱·웹 앱구조를 화면으로 익히거나 클릭으로 확인할 때
VS Code Kubernetes 확장편집기 사이드바YAML을 편집하면서 클러스터를 같이 볼 때
02

Windows에 Headlamp 설치(winget)

Windows 11에는 패키지 관리자 winget이 기본으로 들어 있습니다. 먼저 패키지 이름을 확인하고 설치합니다.

winget search --id Headlamp --source winget
winget install --id Headlamp.Headlamp -e --source winget

실제로 설치된 버전은 0.46.0이었고, 설치 파일은 Headlamp의 GitHub 릴리스에서 내려받았습니다. winget이 설치 파일 해시를 확인한 뒤 설치합니다.

찾음 Headlamp [Headlamp.Headlamp] 버전 0.46.0
다운로드 중 https://github.com/kubernetes-sigs/headlamp/releases/download/v0.46.0/Headlamp-0.46.0-win-x64.exe
설치 관리자 해시를 확인했습니다.
설치 성공

설치가 끝나면 시작 메뉴에서 Headlamp를 실행할 수 있습니다.

03

WSL2 kind 클러스터를 Headlamp에 연결하기

Headlamp는 Windows 사용자 폴더의 .kube\config 파일을 읽어 클러스터 목록을 만듭니다. kind 클러스터의 접속 정보는 WSL 안에 있으므로 kind get kubeconfig로 꺼내 Windows 쪽에 저장합니다. 이미 .kube\config가 있다면 덮어쓰지 말고 백업부터 합니다.

New-Item -ItemType Directory -Force "$env:USERPROFILE\.kube" | Out-Null
$cfg = (wsl -d Ubuntu-24.04 -u root -- kind get kubeconfig --name lab) -join "`n"
[IO.File]::WriteAllText("$env:USERPROFILE\.kube\config", $cfg + "`n")

[IO.File]::WriteAllText를 쓴 이유는 Windows PowerShell 5.1의 > 리디렉션이 UTF-16으로 저장하기 때문입니다. 저장된 파일의 server 값은 https://127.0.0.1:포트였습니다. kind는 API 서버를 WSL 안의 127.0.0.1에 열어 두는데, WSL2는 이 주소를 Windows에서도 그대로 쓸 수 있게 해 줍니다. Headlamp를 열기 전에 Windows에서 실제로 닿는지 확인할 수 있습니다.

PS> curl.exe -sk -o NUL -w "livez HTTP %{http_code}" https://127.0.0.1:포트/livez
livez HTTP 200

200이 나오면 Headlamp를 엽니다. 실습 PC에서는 클러스터 추가 설정 없이 첫 화면에 kind-lab이 바로 나왔습니다. 클러스터 상세 화면에는 Kubeconfig: 항목에 파일 경로가 표시되는데, 경로에 Windows 계정명이 들어가므로 화면을 캡처해 공유할 때는 가립니다.

04

화면 둘러보기: 노드·파드는 어디서 보나

kind-lab에 들어가면 왼쪽 메뉴가 쿠버네티스 리소스 종류별로 나뉘어 있습니다. 한국어 화면에서 본 메뉴는 아래와 같습니다.

Headlamp 왼쪽 메뉴(한국어 화면)
메뉴볼 수 있는 것
Clusters연결된 클러스터 목록과 노드
맵리소스 사이의 연결 관계
워크로드파드, 디플로이먼트, 스테이트풀 셋, 데몬셋, 레플리카 셋, 잡, 크론잡 등
스토리지 / 네트워크볼륨, 서비스 등
게이트웨이(베타)Gateway API 리소스(다음 편부터 사용)
보안 / 구성서비스 어카운트·역할, ConfigMap·Secret 등
커스텀 리소스CRD로 추가한 리소스

파드 목록은 워크로드 → 파드에서 봅니다. 시스템 파드까지 보려면 위쪽 네임스페이스 선택을 모든 네임스페이스로 바꿉니다. 1편에서 만든 클러스터라면 kube-system의 CoreDNS, kindnet, kube-proxy와 local-path-storage의 프로비저너까지 파드 13개가 Running으로 보입니다.

05

'+ 생성' 버튼으로 파드 배포하기

Headlamp는 보기 전용이 아닙니다. 왼쪽 아래 + 생성 버튼을 누르면 '생성 / 적용' 편집 창이 열립니다. 창에는 에디터·Form·문서·Review Changes 탭과 Upload File/URL 버튼이 있고, 아래쪽에 닫기·Dry Run·적용 버튼이 있습니다. 에디터 탭에 아래 YAML을 붙여 넣었습니다.

apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80

nginx 컨테이너 하나를 가진 파드를 2개 유지하라는 Deployment입니다. selector.matchLabels와 template.metadata.labels의 app: web이 같아야 Deployment가 자기 파드를 찾습니다. 적용을 누른 뒤 WSL 셸에서 kubectl로 결과를 확인했습니다.

$ kubectl get deploy web
NAME READY UP-TO-DATE AVAILABLE AGE
web 2/2 2 2 20s
$ kubectl get pods -l app=web -o custom-columns=NAME:.metadata.name,STATUS:.status.phase,NODE:.spec.nodeName
NAME STATUS NODE
web-75d8d959fb-5gdxz Running lab-worker
web-75d8d959fb-k5682 Running lab-worker2

Headlamp가 만든 리소스라는 기록도 클러스터에 남습니다. Deployment의 metadata.managedFields를 보면 이 리소스를 바꾼 주체가 Headlamp 0.46.0 (windows ...)로, 그다음이 kube-controller-manager로 적혀 있습니다. Headlamp가 kubectl처럼 API 서버에 직접 요청을 보낸다는 뜻이므로, Headlamp에서 한 작업도 kubectl로 한 작업과 똑같이 클러스터에 반영됩니다.

Dry Run 먼저

적용 옆의 Dry Run은 실제로 만들지 않고 API 서버가 요청을 받아들이는지만 확인하는 버튼입니다. YAML을 처음 써 볼 때는 Dry Run으로 오류가 없는지 본 뒤 적용하면 안전합니다.

06

클러스터가 Unavailable로 나올 때

한동안 쓰지 않다가 Headlamp를 보니 kind-lab 아래에 Unavailable이 표시됐습니다. Windows에서 /livez를 다시 호출하자 응답이 없었고(HTTP 000), wsl -l -v로 보니 Ubuntu-24.04가 Stopped였습니다. 열린 WSL 셸이나 실행 중인 명령이 없으면 WSL이 스스로 꺼지고, 그 안의 Docker와 kind 노드도 함께 멈춥니다.

PS> wsl -l -v
NAME STATE VERSION
* Ubuntu-24.04 Stopped 2
PS> wsl -d Ubuntu-24.04 -u root

WSL을 다시 켜는 것만으로 해결됐습니다. 켜자마자 Docker가 시작되고 kind 노드 컨테이너 3개가 자동으로 다시 떴습니다. 다만 켜진 직후 몇십 초 동안은 kubectl get nodes가 Forbidden 오류를 냈고, 잠시 뒤 다시 실행하자 노드 3개가 모두 Ready였습니다. Headlamp를 쓰는 동안 Windows 터미널에 Ubuntu 탭을 하나 열어 두면 이 문제를 피할 수 있습니다.

이렇게 WSL이 꺼졌다 켜지는 일이 반복되면 Headlamp 파드 목록의 '재시작' 열에 숫자가 쌓입니다. 실습 PC에서는 약 30분 사이에 kindnet·kube-proxy·CoreDNS 같은 시스템 파드가 9회, local-path-provisioner가 12회로 표시됐습니다. kubectl로 직전 종료 이유를 보면 대부분 Unknown, 종료 코드 255였는데, 컨테이너가 스스로 죽은 것이 아니라 WSL과 함께 갑자기 멈췄다는 뜻입니다. local-path-provisioner는 API 서버가 준비되기 전에 먼저 떠서 Error(종료 코드 1)로 몇 번 더 재시작했습니다. 모두 Running으로 돌아왔다면 이 환경에서는 걱정하지 않아도 되는 숫자입니다.

kubectl get pods -A -o custom-columns='NAME:.metadata.name,RESTARTS:.status.containerStatuses[0].restartCount,REASON:.status.containerStatuses[0].lastState.terminated.reason,EXIT:.status.containerStatuses[0].lastState.terminated.exitCode'
08

자주 묻는 질문

Headlamp는 보기 전용인가요?

아닙니다. 이 글처럼 YAML로 리소스를 만들 수 있고, Headlamp 저장소의 README는 생성·수정·삭제, 로그, 터미널 접속을 기능으로 소개합니다. 버튼은 접속한 사용자의 권한에 맞춰 보이므로, kind 기본 kubeconfig처럼 관리자 권한이면 모든 작업 버튼이 나옵니다.

kind 클러스터를 다시 만들면 Headlamp 설정도 바꿔야 하나요?

kind는 클러스터를 만들 때마다 API 서버 포트를 새로 정합니다. 클러스터를 지우고 다시 만들었다면 kind get kubeconfig로 Windows의 .kube\config를 다시 저장해야 합니다.

작성 방식과 다음 편

이 글의 명령과 출력, 화면 이름은 1편의 실습 PC에서 직접 확인한 결과이며, 파드 배포는 Headlamp 화면에서 직접 적용했습니다. 실행 기록 정리와 글 작성에 AI 도구를 함께 사용했습니다. 다음 편에서는 이 클러스터에 Gateway API를 올려 Windows 브라우저에서 localhost:8080으로 web 파드에 접속합니다.

반응형

Windows에서 무료 쿠버네티스 실습 환경 만들기: WSL2 + Docker Engine + kind

반응형
로컬 쿠버네티스 실습 1편
Windows에서 무료 쿠버네티스 실습 환경
WSL2 + Docker Engine + kind

클라우드 비용 없이 내 PC에 노드 3개짜리 클러스터를 만든 과정과 실제로 겪은 문제

WSL2Docker Enginekindkubectl

Windows 11에서는 WSL2에 Ubuntu를 설치하고, 그 안에 Docker Engine과 kind를 올리면 클라우드 비용 없이 제어 플레인 1개와 워커 2개로 된 쿠버네티스 클러스터를 만들 수 있습니다. 실제로 해 보니 클러스터 생성은 52초면 끝났고, 시간이 걸린 부분은 WSL 설치 뒤 재부팅과 배포판 설치였습니다. Docker Desktop 없이 Docker 공식 apt 저장소로 설치하고, kubectl·kind·helm은 공식 배포처 파일을 SHA256으로 확인한 뒤 설치합니다.

Windows 11 Pro(빌드 26200), WSL 3.0.3, Ubuntu 24.04 LTS 기준입니다. 설치한 버전은 Docker 29.9.3, kubectl v1.37.2, kind v0.33.0, helm v4.3.1이며 클러스터 노드는 kindest/node:v1.37.0입니다. 회사 PC처럼 관리자 권한이나 가상화 설정이 막힌 환경은 다루지 않습니다.

01

왜 WSL2 + Docker Engine + kind인가

클라우드에서 쿠버네티스를 실습하면 관리형 컨트롤 플레인, 노드, 로드밸런서 비용이 시간 단위로 나옵니다. 로컬 PC에서는 이 비용이 없고, 클러스터를 지웠다가 1분 안에 다시 만들 수 있어서 설정을 망가뜨려 보는 실습에도 부담이 없습니다.

kind(Kubernetes IN Docker)는 쿠버네티스 노드 하나를 Docker 컨테이너 하나로 띄우는 도구입니다. 컨테이너만 늘리면 노드가 늘어나므로 제어 플레인과 워커를 나눈 다중 노드 구성을 PC 한 대에서 만들 수 있습니다. kind 빠른 시작

이 글에서 쓰는 구성 요소
구성 요소 역할 설치 위치
WSL2 + Ubuntu 24.04 리눅스 커널과 systemd가 있는 실행 환경 Windows
Docker Engine kind 노드 컨테이너를 실행 Ubuntu 안
kind Docker 컨테이너로 쿠버네티스 노드를 생성 Ubuntu 안
kubectl 클러스터에 명령을 보내는 CLI Ubuntu 안
helm 다음 편부터 쓸 오픈소스 패키지 설치 도구 Ubuntu 안

Docker Desktop을 쓰지 않은 이유는 두 가지입니다. WSL 안에 Docker Engine만 두면 구성 요소가 적어 문제가 생겨도 원인을 좁히기 쉽고, Docker Desktop은 회사 규모에 따라 유료 구독이 필요할 수 있어서 개인 실습 환경을 설명할 때 조건이 하나 더 붙기 때문입니다.

02

1단계: 가상화 확인과 WSL 설치(재부팅 필수)

먼저 CPU 가상화가 BIOS에서 켜져 있는지 확인합니다. 일반 권한 PowerShell에서 아래 명령을 실행해 둘 다 True면 다음 단계로 갑니다.

$p = Get-CimInstance Win32_Processor | Select-Object -First 1
$p.VirtualizationFirmwareEnabled
$p.SecondLevelAddressTranslationExtensions

실습 PC(Ryzen 5 3600X)에서는 둘 다 True였습니다. 이 PC에는 wsl.exe가 있었지만 wsl --status를 실행하면 WSL이 설치되어 있지 않다는 안내가 나왔습니다. wsl.exe는 Windows에 기본으로 들어 있는 진입점일 뿐이므로, 파일이 있다고 WSL이 설치된 것은 아닙니다.

WSL 설치는 Windows 기능을 켜는 작업이라 관리자 권한 PowerShell에서 실행합니다. WSL 설치 안내

wsl --install -d Ubuntu-24.04

여기서 실제로 막힌 부분이 있습니다. 설치 직후 재부팅하지 않은 상태에서는 wsl --version은 정상으로 나오는데 wsl -l -v는 설치된 배포가 없다고 나왔습니다. 배포판 설치를 다시 시도해도 "시스템을 다시 시작하면 변경 사항이 적용됩니다"라는 메시지만 나왔고, 이때 HypervisorPresent 값은 False였습니다. 가상 머신 플랫폼은 재부팅해야 켜지므로 WSL 설치 뒤에는 반드시 재부팅합니다.

재부팅 뒤에도 배포판이 없다면

재부팅 후 HypervisorPresent는 True가 됐지만 Ubuntu는 자동으로 설치되지 않았습니다. 이때는 일반 권한 PowerShell에서 wsl --install -d Ubuntu-24.04를 한 번 더 실행하면 다운로드와 설치가 바로 진행됩니다. 관리자 권한은 필요 없었습니다.

03

2단계: Ubuntu 안에 Docker Engine 설치

Ubuntu 셸에서 Docker 공식 apt 저장소를 등록하고 설치합니다. Ubuntu 기본 저장소의 docker.io 패키지가 아니라 Docker가 배포하는 패키지를 쓰는 방식입니다. Docker Engine Ubuntu 설치 안내

이 글의 명령은 wsl -d Ubuntu-24.04 -u root로 연 root 셸에서 실행했습니다. 일반 사용자 셸이라면 각 명령 앞에 sudo를 붙입니다.

apt-get update
apt-get install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
. /etc/os-release
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu ${UBUNTU_CODENAME:-$VERSION_CODENAME} stable" > /etc/apt/sources.list.d/docker.list
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

signed-by는 이 저장소의 패키지를 Docker 서명 키로만 검증하도록 묶는 설정입니다. Ubuntu 24.04의 코드명은 noble이라 실제로 등록된 줄은 아래와 같았습니다.

deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu noble stable

WSL의 Ubuntu 24.04는 PID 1이 systemd라서 Docker 서비스가 일반 리눅스처럼 바로 실행됐습니다. 설치 후 확인 결과는 아래와 같습니다.

$ docker version --format 'Client {{.Client.Version}} / Server {{.Server.Version}}'
Client 29.9.3 / Server 29.9.3
$ systemctl is-active docker
active
$ docker run --rm hello-world
Hello from Docker!
04

3단계: kubectl·kind·helm 설치와 체크섬 확인

세 도구는 모두 실행 파일 하나라서 공식 배포처에서 받아 /usr/local/bin에 넣으면 됩니다. 받은 파일이 배포된 파일과 같은지 SHA256 체크섬을 먼저 확인합니다. 아래는 kubectl 예시입니다. kubectl 설치 안내

KV=$(curl -fsSL https://dl.k8s.io/release/stable.txt)
curl -fsSLO "https://dl.k8s.io/release/$KV/bin/linux/amd64/kubectl"
curl -fsSLO "https://dl.k8s.io/release/$KV/bin/linux/amd64/kubectl.sha256"
echo "$(cat kubectl.sha256) kubectl" | sha256sum --check
install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl

kind는 https://kind.sigs.k8s.io/dl/버전/kind-linux-amd64와 같은 위치의 .sha256sum 파일로, helm은 https://get.helm.sh/helm-버전-linux-amd64.tar.gz와 .sha256sum 파일로 같은 방식으로 확인했습니다. 세 파일 모두 OK가 나온 뒤 설치했고, 설치된 버전은 아래와 같습니다.

Client Version: v1.37.2
Kustomize Version: v5.8.1
kind v0.33.0 go1.26.7 linux/amd64
v4.3.1+gc0d08d0
실제로 겪은 오류: curl (23)

최신 버전 번호를 curl ... | grep -m1 tag_name 식으로 뽑았더니 curl: (23) Failure writing output to destination이 나왔습니다. grep -m1이 첫 줄을 찾자마자 파이프를 닫아 curl이 쓰기 오류를 내고, set -o pipefail을 켠 스크립트가 그 자리에서 멈춘 것입니다. 응답을 변수에 먼저 담은 뒤 grep하도록 바꾸자 해결됐습니다.

05

4단계: 노드 3개짜리 kind 클러스터 만들기

노드 구성은 설정 파일로 정합니다. 제어 플레인 1개와 워커 2개를 두고, 다음 편에서 Gateway API를 시험할 수 있게 제어 플레인의 80·443 포트를 Windows 쪽 8080·8443으로 연결했습니다.

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: lab
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraPortMappings:
- containerPort: 80
hostPort: 8080
protocol: TCP
- containerPort: 443
hostPort: 8443
protocol: TCP
- role: worker
- role: worker

extraPortMappings는 노드 컨테이너의 포트를 호스트 포트로 내보내는 설정이고, ingress-ready=true 라벨은 이후 게이트웨이 컨트롤러를 이 노드에 두기 위한 표시입니다. 80·443을 그대로 쓰지 않은 이유는 Windows에서 이미 쓰는 포트와 겹치지 않게 하기 위해서입니다.

kind create cluster --config kind-lab.yaml --wait 180s

노드 이미지(kindest/node:v1.37.0) 다운로드를 포함해 52초가 걸렸습니다. 출력 마지막에 Ready after 8s가 나오고 kubectl 컨텍스트가 kind-lab으로 바뀝니다.

06

워커가 NotReady로 나올 때: 확인과 동작 테스트

생성 직후 바로 kubectl get nodes를 실행하면 제어 플레인만 Ready이고 워커 2개는 NotReady였습니다. 워커가 붙은 지 10초밖에 지나지 않은 시점이었고, kind 출력에도 Waiting ≤ 3m0s for control-plane = Ready라고 나오듯 --wait는 제어 플레인만 기다립니다. 모든 노드를 기다리려면 아래 명령을 씁니다.

$ kubectl wait --for=condition=Ready nodes --all --timeout=180s
node/lab-control-plane condition met
node/lab-worker condition met
node/lab-worker2 condition met
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
lab-control-plane Ready control-plane 33s v1.37.0
lab-worker Ready <none> 21s v1.37.0
lab-worker2 Ready <none> 21s v1.37.0

노드 목록만으로는 워커가 실제로 파드를 받는지 알 수 없으므로 nginx 파드 2개를 띄워 봤습니다. 약 6초 만에 둘 다 Running이 됐고 lab-worker와 lab-worker2에 하나씩 배치됐습니다. 확인한 뒤 바로 지웁니다.

kubectl create deployment hello --image=nginx:alpine --replicas=2
kubectl rollout status deploy/hello --timeout=120s
kubectl get pods -l app=hello -o wide
kubectl delete deployment hello
만들어진 클러스터 구성
항목 값
노드 lab-control-plane, lab-worker, lab-worker2 (모두 Docker 컨테이너)
노드 OS·런타임 Debian 13, containerd 2.3.4, 커널은 WSL2 커널 공유
네트워크(CNI) kindnet
스토리지 local-path-provisioner (기본 StorageClass)
WSL에 잡힌 자원 CPU 12개, 메모리 약 23GB (PC 48GB의 절반)
08

자주 묻는 질문

WSL 설치 후 재부팅을 꼭 해야 하나요?

네. 실습 PC에서는 재부팅 전까지 하이퍼바이저가 꺼져 있어(HypervisorPresent False) 배포판이 설치되지 않았습니다. 재부팅 후에도 배포판이 없으면 일반 권한으로 wsl --install -d Ubuntu-24.04를 다시 실행합니다.

일반 사용자로 docker와 kubectl을 쓰려면 어떻게 하나요?

사용자를 docker 그룹에 넣고(sudo usermod -aG docker 사용자명) WSL 셸을 다시 연 뒤, kind export kubeconfig --name lab으로 그 사용자의 kubeconfig에 클러스터 접속 정보를 씁니다. 이 글은 root 셸에서 진행했습니다.

작성 방식과 다음 편

이 글의 명령과 출력은 위에 적은 PC에서 직접 실행한 결과이며, 설치 순서는 Microsoft·Docker·Kubernetes·kind·Helm 공식 문서를 기준으로 했습니다. 실행 기록 정리와 글 작성에 AI 도구를 함께 사용했습니다. 다음 편에서는 이 클러스터를 Windows 앱 Headlamp로 연결해 화면으로 보고, WSL이 꺼졌다 켜졌을 때 클러스터가 다시 살아나는지도 확인합니다.

반응형

Bedrock Knowledge Bases 검색 설정과 재정렬: 필요한 문서를 먼저 찾는 방법 | Bedrock 28편

반응형
Amazon Bedrock 완전 가이드 · 28편
Knowledge Bases 검색 설정과 재정렬

필요한 문서를 먼저 찾는 방법

Amazon Bedrock Knowledge Bases는 질문할 때마다 검색 설정을 바꿀 수 있습니다. 가져올 청크 수는 numberOfResults(기본 5개, 1~100)로, 검색 범위는 메타데이터 필터로, 결과 순서는 재정렬 모델로, 여러 주제가 섞인 질문은 질의 분해로 조정합니다. 설정을 바꾸기 전에 Retrieve로 검색 결과의 원문·위치·메타데이터·점수를 먼저 보고, 문제가 수집·검색·생성 중 어디서 생겼는지 가른 뒤 한 번에 하나씩 바꿉니다.

고객 관리형 Knowledge Base의 비정형 데이터 소스를 기준으로 Retrieve·RetrieveAndGenerate 요청의 검색 설정(검색 개수, 메타데이터 필터, 재정렬, 질의 분해)과 검색 문제의 진단 순서를 설명합니다. 관리형 Knowledge Base와 다른 점은 해당 위치에 따로 표시합니다. 두 API의 응답 처리와 생성 프롬프트는 29편, 구조화 데이터 검색은 30편에서 다룹니다.

01

검색 개수와 검색 범위

Knowledge Base에 질문하면 Bedrock은 질문과 가까운 청크를 찾아 기본적으로 최대 5개를 돌려줍니다. 이 개수는 Retrieve나 RetrieveAndGenerate 요청의 numberOfResults로 1부터 100까지 정할 수 있으며, 값을 바꿔도 Knowledge Base 자체 설정은 바뀌지 않고 그 요청에만 적용됩니다. 검색 설정 안내

아래는 가상의 사내 서비스 안내 도우미가 Retrieve를 호출할 때의 요청 본문 구성 예시입니다. Knowledge Base ID는 본문이 아니라 요청 경로(/knowledgebases/KNOWLEDGE_BASE_ID/retrieve)에 들어갑니다.

{
"retrievalQuery": {"text": "회의실 예약은 며칠 전까지 할 수 있나요?"},
"retrievalConfiguration": {
"vectorSearchConfiguration": {
"numberOfResults": 10
}
}
}

numberOfResults는 상한입니다. 질문과 관련된 청크가 적거나 필터로 후보가 줄면 더 적게 돌아옵니다. 26편에서 다룬 계층적 분할을 쓰면 이 값은 자식 청크 수에 해당하고, 같은 부모를 가진 자식 청크는 부모 청크 하나로 바뀌어 반환되므로 결과 수가 요청보다 적을 수 있습니다.

개수를 늘리면 필요한 청크가 결과에 들어올 가능성은 커지지만, RetrieveAndGenerate에서는 모든 결과가 생성 프롬프트에 들어가므로 입력 토큰과 비용이 늘고 질문과 상관없는 내용도 함께 섞입니다. 프롬프트가 글자 수 한도를 넘는다는 오류가 나면 공식 문서도 검색 개수를 줄이거나 더 작은 청크로 다시 수집하라고 안내합니다. 기본값 5개에서 시작하고, 답에 필요한 청크가 결과 밖으로 밀려나는 것을 확인했을 때 늘리는 편이 안전합니다.

검색 범위는 기본적으로 요청한 Knowledge Base에 연결된 모든 데이터 소스입니다. 범위를 좁히는 방법은 세 가지입니다.

  • Knowledge Base 분리: 공개 범위나 업무 영역이 크게 다르면 Knowledge Base를 따로 만들고, 애플리케이션이 호출할 Knowledge Base를 고릅니다(25편).
  • 데이터 소스 지정: Bedrock이 모든 청크에 붙이는 기본 메타데이터 x-amz-bedrock-kb-data-source-id로 필터를 걸면 특정 데이터 소스에서만 찾습니다.
  • 사용자 정의 메타데이터: 부서·연도·문서 유형처럼 문서에 붙인 값으로 필터를 겁니다(다음 섹션).

관리형 Knowledge Base는 vectorSearchConfiguration 대신 managedSearchConfiguration에 같은 이름의 numberOfResults와 filter를 넣습니다. 관리형은 항상 하이브리드 검색을 쓰며 의미 검색만 따로 고를 수 없습니다.

02

명시적·암시적 메타데이터 필터

메타데이터 필터는 결과를 돌려주기 전에 메타데이터 조건에 맞지 않는 청크를 빼는 기능입니다. 애플리케이션이 조건을 직접 넣는 방식을 명시적(수동) 필터, 모델이 질문을 읽고 조건을 만들어 적용하는 방식을 암시적 필터라고 합니다. 두 방식 모두 25편에서 다룬 .metadata.json 파일이나 26편의 변환 Lambda로 문서에 메타데이터가 붙어 있어야 동작합니다. RetrievalFilter API

아래는 시설팀 문서 중 2026년 이후에 작성된 것만 찾는 명시적 필터의 구성 예시입니다. department와 doc_year는 가상의 메타데이터 이름이며, doc_year는 숫자로 저장했다고 가정합니다.

"vectorSearchConfiguration": {
"numberOfResults": 5,
"filter": {
"andAll": [
{"equals": {"key": "department", "value": "facility"}},
{"greaterThanOrEquals": {"key": "doc_year", "value": 2026}}
]
}
}

andAll은 모든 조건을, orAll은 하나 이상의 조건을 만족하는 청크를 남깁니다. 두 연산자는 조건을 2개 이상 5개 이하로 묶으며, 묶음 안에 묶음을 한 단계까지 넣을 수 있습니다. 조건이 하나뿐이면 andAll 없이 equals 같은 연산자를 filter 바로 아래에 둡니다.

주요 필터 연산자와 값 형식
연산자값 형식저장소 조건
equals·notEquals문자열·숫자·불리언공통 연산자
greaterThan·lessThan 등 범위 비교숫자공통 연산자
in·notIn문자열 목록OpenSearch Serverless와 Neptune Analytics에서 가장 잘 지원
startsWith문자열OpenSearch Serverless만 지원
stringContains·listContains문자열OpenSearch Serverless에서 가장 잘 지원, S3 Vectors는 stringContains 불가

필터를 쓰려면 저장소 쪽 조건도 맞아야 합니다. OpenSearch Serverless는 벡터 인덱스가 faiss 엔진이어야 하며, nmslib 엔진으로 만든 인덱스라면 새 인덱스를 만들고 Knowledge Base도 새로 만들어야 합니다. S3 Vectors와 관리형 Knowledge Base는 startsWith와 stringContains를 쓸 수 없습니다. 기존 Aurora 인덱스에 메타데이터를 추가할 때는 메타데이터를 한 열에 모아 저장하도록 사용자 정의 메타데이터 열을 지정하고 그 열에 인덱스를 만드는 방법이 권장됩니다.

암시적 필터는 vectorSearchConfiguration의 implicitFilterConfiguration에 메타데이터 설명과 필터를 만들 모델을 지정합니다. 공식 문서 기준으로 필터 생성은 Anthropic Claude 모델이 지원합니다.

"implicitFilterConfiguration": {
"modelArn": "FILTER_MODEL_ARN",
"metadataAttributes": [
{"key": "department", "type": "STRING", "description": "문서를 관리하는 부서. 예: facility, hr"},
{"key": "doc_year", "type": "NUMBER", "description": "문서를 작성한 연도. 예: 2026"}
]
}

FILTER_MODEL_ARN은 필터를 만들 Claude 모델의 ARN으로 바꿉니다. metadataAttributes에는 1~25개의 속성을 넣고, type은 STRING·NUMBER·BOOLEAN·STRING_LIST 중 하나입니다. 모델은 description을 보고 "올해 시설팀 안내에서 주차 등록 방법 알려줘" 같은 질문에서 부서와 연도 조건을 뽑아내므로, 설명에 값의 예시와 형식을 구체적으로 적을수록 필터가 정확해집니다.

암시적 필터를 쓰면 검색 전에 모델 호출이 한 번 더 일어나므로 응답이 늦어지고 모델 사용 비용이 늘 수 있습니다. 또 모델이 만든 조건이므로 질문 표현에 따라 달라질 수 있습니다. 따라서 사용자가 볼 수 있는 문서를 제한하는 권한 조건은 암시적 필터에 맡기지 않고, 애플리케이션이 인증된 사용자 정보로 만든 명시적 필터나 Knowledge Base 분리로 통제합니다.

03

재정렬의 역할과 비용

Knowledge Base의 기본 순위는 질문 벡터와 청크 벡터가 얼마나 가까운지(하이브리드 검색이면 원문 일치도 포함)로 정해집니다. 재정렬 모델(reranker)은 이렇게 뽑힌 후보 청크를 질문과 함께 다시 읽고 관련성 점수를 새로 계산해 순서를 바꿉니다. 재정렬 결과는 Knowledge Base의 기본 순위를 대체하며, 텍스트 데이터에만 쓸 수 있습니다. 재정렬 모델 안내

재정렬은 "넓게 찾고 좁게 고르는" 구성에 쓰입니다. 아래 구성 예시는 후보 30개를 검색한 뒤 재정렬해 상위 5개만 돌려받습니다.

"vectorSearchConfiguration": {
"numberOfResults": 30,
"rerankingConfiguration": {
"type": "BEDROCK_RERANKING_MODEL",
"bedrockRerankingConfiguration": {
"modelConfiguration": {"modelArn": "RERANK_MODEL_ARN"},
"numberOfRerankedResults": 5
}
}
}

RERANK_MODEL_ARN은 아래 표의 재정렬 모델 ARN으로 바꿉니다. numberOfResults는 재정렬에 넘길 후보 수, numberOfRerankedResults는 재정렬 후 돌려받을 수입니다. numberOfRerankedResults를 numberOfResults보다 크게 주어도 최대 numberOfResults개만 반환됩니다. 예외는 다음 섹션의 질의 분해를 함께 쓸 때로, 이때는 numberOfRerankedResults를 numberOfResults의 5배까지 줄 수 있습니다. 선택 항목인 metadataConfiguration은 재정렬할 때 메타데이터를 모두 참고할지(ALL), 고른 필드만 참고할지(SELECTIVE)를 정합니다.

재정렬 모델과 단일 리전 지원
모델모델 ID지원 리전
Amazon Rerank 1.0amazon.rerank-v1:0도쿄, 캐나다(중부), 프랑크푸르트, 오레곤
Cohere Rerank 3.5cohere.rerank-v3-5:0도쿄, 캐나다(중부), 프랑크푸르트, 버지니아 북부, 오레곤

이 표에 서울 리전은 없습니다. 서울 리전의 Knowledge Base에서 재정렬을 쓰려는 경우 만들기 전에 재정렬 지원 리전을 다시 확인하고, 지원하지 않으면 재정렬 없이 검색 개수·필터·하이브리드 검색으로 조정하거나 지원 리전에 Knowledge Base를 두는 방안을 비교합니다. 다른 리전에 둘 때는 데이터가 저장·처리되는 위치가 바뀌므로 데이터 위치 요건도 함께 검토합니다.

권한은 어느 API로 재정렬하느냐에 따라 필요한 주체가 다릅니다.

재정렬 사용 방식별 필요 권한
사용 방식호출하는 사용자·역할Knowledge Base 서비스 역할
Retrieve + 재정렬bedrock:Retrievebedrock:Rerank, 재정렬 모델에 대한 bedrock:InvokeModel
RetrieveAndGenerate + 재정렬bedrock:RetrieveAndGenerate, bedrock:Rerank, 재정렬 모델과 생성 모델에 대한 bedrock:InvokeModel추가 권한 없음
Rerank API 단독 호출bedrock:Rerank, 재정렬 모델에 대한 bedrock:InvokeModel해당 없음

Cohere Rerank처럼 외부 제공사 모델을 쓰려면 사용하는 IAM 역할에 aws-marketplace:ViewSubscriptions와 aws-marketplace:Subscribe 권한도 필요합니다. 콘솔에서 재정렬 모델을 고를 때 서비스 역할 권한을 자동으로 고칠 수 있지만, 직접 만든 사용자 정의 역할은 Bedrock이 고칠 수 없으므로 정책을 직접 추가합니다. 재정렬 권한

재정렬 요금은 쿼리 단위로 매겨지며, 쿼리 하나는 재정렬 모델을 한 번 호출하는 것으로 최대 100개의 청크를 담을 수 있습니다. 처리된 쿼리 수는 CloudWatch의 SearchUnits 지표로 확인합니다. 재정렬은 질문마다 호출이 하나 더 늘어 비용과 지연이 더해지지만, 생성 모델에 넘기는 청크를 줄이면 생성 단계의 입력 토큰은 줄어듭니다. 단가는 Bedrock 요금 페이지에서 확인합니다.

관리형 Knowledge Base는 서비스가 관리하는 재정렬이 기본으로 켜져 있습니다. managedSearchConfiguration의 rerankingModelType을 CUSTOM으로 주면 직접 고른 재정렬 모델을, NONE으로 주면 재정렬 없이 검색합니다. 서비스 관리형 재정렬(MANAGED)은 관리형 임베딩을 쓰는 Knowledge Base에서만 쓸 수 있습니다.

04

질의 변환·분해와 검색 전략

질의 분해(query decomposition)는 여러 주제가 섞인 질문을 작은 하위 질문으로 나눠 각각 검색하는 기능입니다. 예를 들어 "3층 회의실 예약 방법과 방문객 주차 등록 방법을 알려줘"라는 질문을 회의실 예약과 주차 등록에 관한 두 하위 질문으로 나누면, 한 번의 벡터 검색으로는 한쪽 주제의 청크가 밀려날 수 있는 문제를 줄일 수 있습니다. QueryTransformationConfiguration API

질의 분해는 RetrieveAndGenerate 요청의 orchestrationConfiguration에서 켭니다. 현재 지정할 수 있는 변환 유형은 QUERY_DECOMPOSITION 하나입니다.

"retrieveAndGenerateConfiguration": {
"type": "KNOWLEDGE_BASE",
"knowledgeBaseConfiguration": {
"knowledgeBaseId": "KNOWLEDGE_BASE_ID",
"modelArn": "GENERATION_MODEL_ARN",
"orchestrationConfiguration": {
"queryTransformationConfiguration": {"type": "QUERY_DECOMPOSITION"}
}
}
}

KNOWLEDGE_BASE_ID는 Knowledge Base ID로, GENERATION_MODEL_ARN은 답변을 생성할 모델의 ARN으로 바꿉니다. 이 옵션을 켜면 질문 하나에 Knowledge Base 검색이 여러 번 실행될 수 있어 지연이 늘어납니다. 단순한 질문이 대부분이라면 끄고, 여러 조건이나 비교가 들어간 질문이 많을 때 켜서 결과를 비교합니다.

Retrieve에는 질의 분해 옵션이 없습니다. Retrieve로 검색 결과를 직접 처리하는 구성(29편)에서 같은 효과가 필요하면, 애플리케이션이 질문을 나누고 하위 질문마다 Retrieve를 호출한 뒤 결과를 합치는 방식으로 설계해야 합니다. RetrieveAndGenerate의 orchestration 프롬프트도 질문을 검색어로 바꾸는 단계에 쓰이며, 프롬프트 구성은 29편에서 다룹니다.

검색 설정은 질문 유형에 따라 고릅니다. 아래 표는 증상과 먼저 시도할 설정을 짝지은 것입니다.

질문 유형별 검색 전략
질문·증상먼저 시도할 설정관련 편
오류 코드·제품 번호처럼 글자가 정확해야 하는 질문하이브리드 검색(overrideSearchType)27편
부서·연도·문서 유형 같은 조건이 붙은 질문명시적 또는 암시적 메타데이터 필터이 편 2섹션
맞는 청크가 후보에는 있지만 순위가 낮음검색 개수를 늘리고 재정렬이 편 3섹션
여러 주제가 한 질문에 섞임질의 분해이 편 4섹션
답에 필요한 내용이 여러 청크에 흩어짐검색 개수 조정, 계층적 분할 검토26편

설정은 한 번에 하나씩 바꿉니다. 여러 설정을 동시에 바꾸면 결과가 좋아지거나 나빠진 원인을 알 수 없기 때문입니다. 실제 사용자 질문과 비슷한 질문 20~30개와 각 질문에 맞는 정답 문서를 정해 두고, 설정을 바꿀 때마다 같은 질문으로 정답 문서가 몇 위에 오는지 비교하면 변경 효과를 판단할 수 있습니다.

05

검색 누락과 무관한 결과의 진단

답변이 틀렸을 때 가장 먼저 할 일은 검색과 생성을 분리해 보는 것입니다. Retrieve를 호출하거나 콘솔의 Knowledge Base 테스트 창에서 응답 생성을 끄면, 모델이 답을 만들기 전의 검색 결과를 그대로 볼 수 있습니다. 결과마다 청크 원문(content), 원본 문서 위치(location), 메타데이터(metadata), 관련성 점수(score)가 돌아옵니다. Retrieve로 검색 결과 확인

아래는 AWS SDK for Python(Boto3)으로 검색 결과의 순위·점수·위치·메타데이터만 출력하는 진단용 예시입니다. KNOWLEDGE_BASE_ID를 바꾸고, 실행하는 자격 증명에 해당 Knowledge Base에 대한 bedrock:Retrieve 권한이 있어야 합니다.

import boto3

client = boto3.client("bedrock-agent-runtime", region_name="REGION")
response = client.retrieve(
knowledgeBaseId="KNOWLEDGE_BASE_ID",
retrievalQuery={"text": "방문객 주차 등록은 어떻게 하나요?"},
retrievalConfiguration={"vectorSearchConfiguration": {"numberOfResults": 20}},
)
for rank, result in enumerate(response["retrievalResults"], start=1):
print(rank, round(result.get("score", 0), 4), result.get("location"))
print(" ", result.get("metadata"))

REGION은 Knowledge Base가 있는 리전 코드로 바꿉니다. 평소보다 큰 20개를 가져오는 이유는 정답 문서가 기본 5개 밖에 있는지 확인하기 위해서입니다. 점수는 같은 설정 안에서 결과의 순서를 비교하는 데 쓰고, 재정렬이나 검색 방식을 바꾼 결과끼리 점수 값을 직접 비교하지 않습니다. 점수로 결과를 잘라 내는 기준을 정하려면 정해 둔 질문 세트로 점수 분포를 먼저 확인합니다.

검색 결과를 본 뒤에는 증상에 따라 원인을 좁힙니다.

검색 증상별 점검 순서
증상먼저 확인할 것조치
결과가 0건필터의 키 이름(대소문자 포함)과 값 형식(숫자를 문자열로 저장했는지), OpenSearch Serverless 인덱스 엔진필터 없이 같은 질문을 다시 검색해 필터 문제인지 가르기
정답 문서가 20개 안에도 없음문서가 수집되었는지(수집 작업 상태와 실패 원인), 파싱 결과에 내용이 남았는지25편의 수집 점검, 26편의 파서·분할 점검
정답 문서가 6위 이하질문과 문서의 표현 차이, 코드·고유 명사 포함 여부검색 개수 조정, 하이브리드 검색, 재정렬
무관한 문서가 상위오래된 버전·다른 부서 문서가 섞였는지, 청크가 너무 큰지메타데이터 필터, 재정렬, 분할 크기 조정
결과가 요청한 수보다 적음계층적 분할의 부모 청크 치환, numberOfRerankedResults 상한정상 동작인지 확인 후 개수 조정
검색 결과는 맞는데 답변이 틀림생성 프롬프트와 모델 설정29편의 생성 단계 점검

필터를 걸었는데 결과가 0건이라면, 메타데이터 파일을 추가하거나 고친 뒤 데이터 소스를 다시 동기화했는지도 확인합니다. .metadata.json을 고쳐도 동기화 전에는 저장소의 메타데이터가 바뀌지 않습니다. 반대로 필터를 걸었는데도 다른 부서 문서가 섞인다면, 그 문서에 메타데이터가 아예 없거나 다른 값으로 붙어 있는지 Retrieve 결과의 metadata에서 확인합니다.

반응형

Bedrock Knowledge Bases 임베딩과 벡터 저장소: 차원·인덱스·유사도 이해하기 | Bedrock 27편

반응형
Amazon Bedrock 완전 가이드 · 27편
Knowledge Bases 임베딩과 벡터 저장소

차원·인덱스·유사도 이해하기

Amazon Bedrock Knowledge Bases에서 임베딩 모델은 청크를 정해진 길이(차원)의 숫자 벡터로 바꾸고, 벡터 저장소는 이 벡터를 인덱스에 저장해 질문 벡터와 가까운 것을 찾습니다. 벡터 인덱스의 차원은 임베딩 모델의 출력 차원과 같아야 하며, 하이브리드 검색은 일부 저장소에서만 쓸 수 있습니다. 임베딩 모델과 저장소는 만든 뒤 바꾸기 어려우므로, 지원 리전·검색 방식·비용 구조를 함께 보고 고릅니다.

고객 관리형 Knowledge Base를 기준으로 임베딩 모델과 차원, 벡터 인덱스와 필드 매핑, 지원 벡터 저장소와 S3 Vectors 선택 기준, 의미 검색과 하이브리드 검색, 저장소 변경과 비용을 설명합니다. 관리형 Knowledge Base와 다른 점은 해당 위치에 따로 표시합니다.

01

임베딩 모델과 벡터 차원

임베딩 모델은 텍스트를 숫자 목록(벡터)으로 바꿉니다. 이 목록의 길이를 차원이라고 하며, 의미가 비슷한 문장일수록 벡터끼리 가깝게 놓이도록 학습되어 있습니다. 검색은 질문을 같은 모델로 벡터로 바꾼 뒤, 저장된 청크 벡터 중 가까운 것을 찾는 방식으로 이루어집니다. Knowledge Bases 지원 모델

고객 관리형 Knowledge Base의 주요 텍스트 임베딩 모델
모델벡터 형식차원서울 리전 단일 리전 지원
Amazon Titan Text Embeddings V2부동소수점, 바이너리256·512·1024 중 선택지원
Cohere Embed Multilingual v3부동소수점, 바이너리1024표에 없음
Cohere Embed English v3부동소수점, 바이너리1024표에 없음
Amazon Titan Embeddings G1 - Text부동소수점1536표에 없음

한국어 문서가 중심이라면 다국어를 지원하는 모델을 고릅니다. Titan Text Embeddings V2는 100개 이상의 언어를, Cohere Embed Multilingual v3는 이름 그대로 다국어를 지원합니다. 서울 리전에서 Knowledge Base를 만든다면, 단일 리전 지원 표 기준으로는 Titan Text Embeddings V2를 쓸 수 있고 Cohere v3 모델은 표에 없습니다. 이 표는 바뀔 수 있으므로 만들기 전에 다시 확인합니다.

차원이 클수록 의미를 더 세밀하게 담을 수 있지만, 벡터 하나가 차지하는 저장 공간과 검색 계산량도 늘어납니다. 바이너리 벡터는 차원마다 1비트만 써서 부동소수점(차원당 32비트)보다 저장 비용이 훨씬 작지만, 표현이 덜 정밀합니다. 바이너리 벡터를 쓰려면 임베딩 모델과 벡터 저장소가 모두 바이너리를 지원해야 합니다.

관리형(Bedrock Managed) Knowledge Base는 기본적으로 내장 임베딩 모델을 쓰고, 직접 고를 때는 float32 형식의 1,024차원 모델만 지정할 수 있습니다.

02

저장소·인덱스·필드 매핑

고객 관리형 Knowledge Base는 벡터 저장소 안의 인덱스(검색용 데이터 구조)에 청크를 저장합니다. OpenSearch Serverless는 콘솔에서 Knowledge Base를 만들 때 Bedrock이 저장소와 인덱스를 자동으로 만들어 주는 방법도 있습니다. 직접 만든다면 인덱스를 미리 만들고, Bedrock이 어떤 필드에 무엇을 쓸지 연결(필드 매핑)해야 합니다.

아래는 OpenSearch Serverless에 1,024차원 벡터용 인덱스를 만드는 매핑의 구성 예시입니다. 필드 이름은 가상의 예시입니다.

PUT guide-kb-index
{
"settings": {"index": {"knn": true}},
"mappings": {
"properties": {
"guide_vector": {
"type": "knn_vector",
"dimension": 1024,
"method": {"name": "hnsw", "engine": "faiss", "space_type": "l2"}
},
"guide_text": {"type": "text", "index": true},
"guide_metadata": {"type": "text", "index": false}
}
}
}

dimension은 임베딩 모델의 출력 차원과 같아야 합니다. Titan Text Embeddings V2를 512차원으로 쓰기로 했다면 여기도 512여야 하며, 차원이 맞지 않으면 벡터를 인덱스에 저장할 수 없습니다. space_type은 벡터 사이의 거리를 재는 방식(유사도 기준)입니다. Knowledge Base를 만들 때는 세 필드를 다음처럼 연결합니다.

  • 벡터 필드(vectorField): 임베딩 벡터를 저장할 필드. 예시의 guide_vector
  • 텍스트 필드(textField): 청크의 원문을 저장할 필드. 예시의 guide_text
  • 메타데이터 필드(metadataField): Bedrock이 관리하는 메타데이터를 저장할 필드. 예시의 guide_metadata

텍스트 필드를 검색 가능한(filterable) 텍스트로 만들어 두어야 4섹션의 하이브리드 검색을 쓸 수 있습니다. 예시에서 텍스트 필드에 "index": true를 준 이유입니다.

03

지원 저장소와 S3 Vectors 등의 선택 기준

고객 관리형 Knowledge Base가 연결할 수 있는 저장소는 AWS 서비스와 외부 서비스로 나뉩니다. 이미 운영 중인 저장소가 있다면 그것을 연결할 수 있는지부터 확인합니다.

벡터 저장소별 특징
저장소특징맞는 경우
Amazon OpenSearch Serverless콘솔에서 자동 생성 가능, 하이브리드 검색 지원가장 일반적인 시작점
Amazon OpenSearch Service 관리형 클러스터기존 클러스터 재사용이미 OpenSearch 클러스터를 운영 중
Amazon Aurora PostgreSQL(pgvector)SQL 데이터베이스 안에 벡터 저장, 하이브리드 검색 지원관계형 데이터와 함께 관리하고 싶을 때
Amazon S3 VectorsS3 기반 벡터 저장, 사용량 기반 비용검색량 대비 문서가 많고 비용이 중요할 때
Amazon Neptune Analytics그래프 기반 검색(GraphRAG)문서 간 관계를 따라가는 질문(30편)
Pinecone·Redis Enterprise Cloud·MongoDB Atlas외부 서비스, MongoDB는 하이브리드 검색 지원이미 해당 서비스를 쓰고 있을 때

S3 Vectors는 벡터를 S3 방식으로 저장하는 서비스로, AWS는 비용이 낮은 저장소로 소개합니다. 대신 Knowledge Base 저장소로 쓸 때 다음 제약을 알아 둬야 합니다. Knowledge Bases와 S3 Vectors

  • 메타데이터 크기: 벡터당 메타데이터는 최대 40KB이고, 이 중 필터에 쓸 수 있는 메타데이터는 2KB까지입니다.
  • 원문 저장 위치: 벡터 인덱스를 만들 때 청크 원문(AMAZON_BEDROCK_TEXT)과 Bedrock이 관리하는 메타데이터(AMAZON_BEDROCK_METADATA)를 필터 불가 메타데이터 키로 지정해야 큰 청크와 필터용 메타데이터를 함께 담을 수 있습니다. 표가 있는 문서에서 붙는 표 전체·페이지 전체 맥락(26편)도 메타데이터 크기에 포함됩니다. 이 키 목록은 인덱스를 만든 뒤 바꿀 수 없습니다.
  • 분할 방식: 메타데이터 크기 한도 때문에 계층적 분할은 권장되지 않습니다(26편).
  • 검색 방식: 하이브리드 검색 지원 저장소에 포함되지 않아 의미 검색만 쓸 수 있습니다.
04

의미 검색과 하이브리드 검색

의미 검색(semantic search)은 벡터만 비교합니다. "휴게실 쓸 수 있는 시간"으로 물어도 "운영 시간"이 적힌 문서를 찾을 수 있다는 장점이 있지만, 오류 코드나 제품 번호처럼 글자가 정확히 같아야 하는 검색에는 약할 수 있습니다. 하이브리드 검색은 벡터 비교에 원문 텍스트 검색을 더해 두 장점을 함께 노립니다. 예를 들어 "출입증 오류 E-1024"처럼 코드가 들어간 질문에서 해당 코드가 적힌 안내문을 찾는 데 도움이 됩니다.

하이브리드 검색은 Amazon RDS(Aurora PostgreSQL), OpenSearch Serverless, MongoDB 저장소 중 검색 가능한 텍스트 필드가 있는 경우에만 쓸 수 있습니다. 조건이 맞지 않으면 요청해도 의미 검색으로 동작합니다. 검색 방식 설정

검색 방식은 Retrieve나 RetrieveAndGenerate 요청의 검색 설정에서 정합니다. 값을 지정하지 않으면 Bedrock이 저장소 구성에 맞는 방식을 고릅니다.

"retrievalConfiguration": {
"vectorSearchConfiguration": {
"overrideSearchType": "HYBRID"
}
}

HYBRID는 벡터와 원문을 함께, SEMANTIC은 벡터만 검색합니다. 어느 쪽이 나은지는 질문 유형에 달려 있으므로, 코드나 고유 명사가 들어간 질문과 일반 문장 질문을 섞어 두 방식을 비교해 봅니다. 검색 개수와 필터 등 나머지 검색 설정은 28편에서 다룹니다.

05

저장소 변경·재수집·비용 고려사항

임베딩 모델이나 벡터 저장소를 바꾸는 일은 사실상 Knowledge Base를 새로 만드는 일입니다. 모델이 바뀌면 기존 벡터와 새 질문 벡터를 비교할 수 없고, 저장소가 바뀌면 벡터를 옮길 곳이 달라지므로 모든 문서를 다시 수집해야 합니다. 관리형 Knowledge Base도 내장 임베딩과 직접 지정한 임베딩 사이를 바꾸려면 새로 만들어야 합니다.

비용은 세 부분으로 나눠 봅니다.

임베딩과 저장소 관련 비용
비용 항목발생 시점줄이는 방법
임베딩 모델 호출수집할 때(청크마다), 검색할 때(질문마다)증분 동기화 유지, 불필요한 재수집 피하기
저장소 저장 비용보관하는 동안 계속차원 축소나 바이너리 벡터 검토, 오래된 문서 정리
저장소 검색·운영 비용검색 요청과 저장소 가동검색량에 맞는 저장소 선택

저장소마다 과금 구조가 다르다는 점이 중요합니다. 검색이 적고 문서가 많다면 사용량에 따라 내는 저장소가 유리할 수 있고, 검색이 많고 하이브리드 검색이 필요하다면 그에 맞는 저장소가 필요합니다. 고르기 전에 예상 문서 수, 하루 질문 수, 하이브리드 검색 필요 여부를 정리하고 각 저장소의 요금 페이지로 계산해 봅니다.

Knowledge Base를 삭제해도 직접 만든 벡터 저장소는 남아 비용이 계속 나갈 수 있습니다(25편). 시험용으로 만든 저장소는 시험이 끝나면 따로 정리합니다.

반응형

Bedrock Knowledge Bases 문서 파싱과 청킹: 검색 품질을 결정하는 전처리 | Bedrock 26편

반응형
Amazon Bedrock 완전 가이드 · 26편
Knowledge Bases 문서 파싱과 청킹

검색 품질을 결정하는 전처리

Amazon Bedrock Knowledge Bases는 문서를 수집할 때 먼저 파싱(내용 추출)하고, 그 결과를 청크(chunk)라는 조각으로 나눠 임베딩합니다. 표나 이미지가 중요한 문서라면 텍스트만 읽는 기본 파서 대신 Data Automation이나 모델 기반 파서를 검토하고, 청크는 문서 구조와 질문 유형에 맞는 분할 방식을 고릅니다. 분할 설정은 데이터 소스를 만든 뒤에는 바꿀 수 없으므로, 처음에 실제 문서와 질문으로 시험해 보고 정해야 합니다.

고객 관리형 Knowledge Base를 기준으로 파서 세 가지의 차이, 기본·고정 크기·계층적·의미 기반·분할 없음의 동작, 표와 이미지 처리, 사용자 정의 변환 Lambda와 메타데이터, 분할 방식 변경 시 영향을 설명합니다. 관리형 Knowledge Base와 다른 점은 해당 위치에 따로 표시합니다.

01

기본 파서·모델 기반 파서·Data Automation

파싱은 원본 파일에서 검색에 쓸 내용을 뽑아내는 단계입니다. 고객 관리형 Knowledge Base는 데이터 소스마다 세 가지 파서 중 하나를 고릅니다. 관리형(Bedrock Managed) Knowledge Base는 멀티모달 파일용 내장 파서를 쓰므로 이 선택이 없습니다. 파싱 옵션

고객 관리형 Knowledge Base의 파서 비교
파서추출하는 내용비용맞는 문서
기본 파서텍스트만(.txt·.md·.html·.docx·.xlsx·.pdf 등)사용 요금 없음본문 글이 대부분인 안내문
Bedrock Data Automation텍스트와 표·차트·그림·이미지처리한 페이지·이미지 수에 따라 과금별도 지시 없이 멀티모달 문서를 처리
모델 기반 파서텍스트와 표·차트·그림·이미지파싱 모델의 입력·출력 토큰에 따라 과금추출 지시(프롬프트)를 직접 조정하고 싶을 때

Data Automation이나 모델 기반 파서는 문서 안의 그림·표·이미지를 따로 추출해, Knowledge Base를 만들 때 지정한 S3 위치에 파일로 저장할 수 있습니다. 이렇게 저장된 파일은 검색 응답이나 출처 표시에 함께 나올 수 있습니다. 다만 Knowledge Bases의 Data Automation 파서는 현재 미국 서부(오리건) 리전에서 미리보기로 제공되므로, 다른 리전에서는 모델 기반 파서를 검토합니다. 모델 기반 파서로는 Claude·Nova·Llama 4의 비전 모델 계열을 쓸 수 있습니다.

비용에서 주의할 점이 있습니다. 두 고급 파서 중 하나를 고르면, 그 데이터 소스의 .pdf 파일은 텍스트만 있더라도 모두 고급 파서로 처리되고 요금이 발생합니다. 그래서 표가 있는 .pdf 파일과 텍스트뿐인 .pdf 파일이 섞여 있다면, 표가 있는 파일만 별도 데이터 소스로 나눠 고급 파서를 적용하는 구성을 검토합니다.

02

고정 크기·계층적·의미 기반 분할

파싱한 내용은 청크로 나뉘어 각각 임베딩됩니다. 검색은 청크 단위로 이루어지므로, 어떻게 나누느냐가 어떤 근거가 검색되는지를 정합니다. 청킹 동작 방식

분할 방식별 동작
방식나누는 기준주요 설정주의할 점
기본약 300토큰, 문장 경계 유지없음가장 무난한 시작점
고정 크기지정한 최대 토큰 수maxTokens, overlapPercentage고급 파서 결과는 페이지·섹션 경계를 넘어 합치지 않음
계층적큰 부모 청크와 작은 자식 청크부모·자식 최대 토큰, overlapTokens검색 결과 수가 요청보다 적을 수 있음
의미 기반문장 사이의 의미 차이maxTokens, bufferSize, breakpointPercentileThreshold분할에 모델을 쓰므로 추가 비용
분할 없음문서 하나를 청크 하나로없음인용에 쪽 번호가 표시되지 않음

계층적 분할은 정확한 검색과 넉넉한 맥락을 함께 노립니다. 검색은 작은 자식 청크로 하고, 모델에 넘길 때는 그 자식이 속한 큰 부모 청크로 바꿔 줍니다. 아래는 데이터 소스를 만들 때 넣는 계층적 분할 설정의 구성 예시입니다.

{
"vectorIngestionConfiguration": {
"chunkingConfiguration": {
"chunkingStrategy": "HIERARCHICAL",
"hierarchicalChunkingConfiguration": {
"levelConfigurations": [
{"maxTokens": 1500},
{"maxTokens": 300}
],
"overlapTokens": 60
}
}
}
}

levelConfigurations의 첫 항목은 부모 청크, 두 번째 항목은 자식 청크의 최대 토큰 수입니다. 자식 청크만 임베딩되므로 자식 크기는 임베딩 모델의 입력 한도 안이어야 합니다. overlapTokens는 이웃한 청크끼리 겹치는 토큰 수로, 문장이 경계에서 잘려 뜻을 잃는 것을 줄입니다. 값은 시작점 예시이며 실제 문서로 시험해 조정합니다. 벡터 저장소로 S3 Vectors를 쓴다면 계층적 분할은 권장되지 않습니다. 부모·자식 토큰 합이 크면 벡터당 메타데이터 크기 한도에 걸릴 수 있기 때문입니다.

관리형 Knowledge Base는 기본 분할, 고정 크기 분할, 분할 없음 중에서만 고를 수 있으며 계층적·의미 기반 분할은 쓸 수 없습니다.

03

표·이미지·문서 구조 처리

기본 파서는 텍스트만 꺼내므로, 표가 있으면 행과 열의 관계가 사라진 글자 나열이 될 수 있습니다. 예를 들어 층별 휴게실 운영 시간 표가 "3층 09:00 18:00 5층 08:00 20:00"처럼 이어지면, 모델이 어느 시간이 어느 층인지 헷갈릴 수 있습니다. 고급 파서는 이런 표를 구조를 유지한 형식(마크다운)으로 추출합니다.

고급 파서로 처리했거나 HTML·마크다운 문서처럼 구조가 있는 내용은, 최대 토큰을 정하는 분할 방식(분할 없음을 제외한 모든 방식)에서 다음과 같이 다뤄집니다.

  • 청크는 페이지나 섹션 같은 논리적 경계를 넘어 합쳐지지 않습니다. 최대 토큰을 늘려도 경계를 넘는 큰 청크는 만들어지지 않습니다.
  • 표나 그림이 있는 페이지의 청크가 검색되면, 그 청크 대신 페이지 전체나 표 전체가 결과로 돌아올 수 있습니다. 그래서 표의 일부만 검색돼도 같은 표의 다른 행을 함께 받을 수 있습니다.
  • 같은 페이지·표에서 나온 청크는 하나의 결과로 합쳐지므로, 결과 수가 요청한 개수(numberOfResults)보다 적을 수 있습니다.
  • 페이지나 표 전체가 1만 자를 넘으면 이 확장이 적용되지 않고 일치한 청크만 돌아옵니다.

표를 행 단위처럼 잘게 나눠 검색하고 싶다면, 분할 없음을 고르고 다음 섹션의 사용자 정의 변환 Lambda에서 직접 나누는 방법이 안내되어 있습니다.

이미지·음성·영상 같은 멀티모달 파일은 텍스트 분할 방식이 적용되지 않습니다. Nova 멀티모달 임베딩을 쓰면 임베딩 모델 단계에서 나뉘고(음성·영상은 1~30초 단위, 기본 5초), Data Automation 파서를 쓰면 먼저 텍스트(음성 전사 내용·장면 요약)로 바뀐 뒤 텍스트 분할 방식이 적용됩니다.

04

사용자 정의 변환과 메타데이터

기본 제공 분할로 부족할 때는 분할이 끝난 뒤(POST_CHUNKING) Lambda 함수를 실행하도록 설정할 수 있습니다. Bedrock은 분할된 문서를 지정한 S3 중간 저장 위치에 저장하고 Lambda를 호출하며, Lambda는 청크를 다시 나누거나 청크마다 메타데이터를 붙여 돌려줍니다.

{
"vectorIngestionConfiguration": {
"customTransformationConfiguration": {
"intermediateStorage": {
"s3Location": {"uri": "INTERMEDIATE_S3_URI"}
},
"transformations": [
{
"stepToApply": "POST_CHUNKING",
"transformationFunction": {
"transformationLambdaConfiguration": {"lambdaArn": "TRANSFORM_LAMBDA_ARN"}
}
}
]
}
}
}

INTERMEDIATE_S3_URI는 Lambda가 읽고 쓸 S3 경로로, TRANSFORM_LAMBDA_ARN은 변환 Lambda의 ARN으로 바꿉니다. Knowledge Base가 이 Lambda와 중간 저장 위치를 쓸 수 있도록 권한도 함께 구성해야 합니다.

Lambda로 할 수 있는 대표적인 작업은 다음과 같습니다.

  • 청크마다 메타데이터 추가: 청크가 속한 장 제목이나 문서 유형을 붙여 검색 필터에 씁니다.
  • 불필요한 내용 제거: 모든 쪽에 반복되는 머리글·바닥글이나 저작권 문구를 지워 검색 잡음을 줄입니다.
  • 직접 분할: 표를 행 단위로 나누는 것처럼 기본 방식으로 안 되는 분할을 구현합니다.

메타데이터는 두 수준에서 붙일 수 있습니다. 문서 전체에 같은 값을 붙일 때는 25편에서 다룬 .metadata.json 파일을 쓰고, 청크마다 다른 값이 필요할 때 Lambda를 씁니다. Lambda는 수집할 때마다 실행되므로, 처리 시간과 호출 비용이 동기화 시간에 더해집니다.

05

분할 방식을 바꾸면 달라지는 결과

분할 설정(chunkingConfiguration)은 데이터 소스를 만든 뒤에는 바꿀 수 없습니다. 다른 분할 방식을 시험하려면 새 데이터 소스를 만들고 모든 문서를 다시 수집해야 합니다. 그때 임베딩 비용이 다시 발생하고, 고급 파서나 의미 기반 분할을 쓴다면 그 비용도 함께 듭니다. CreateDataSource API

분할 방식이 바뀌면 같은 질문에도 검색되는 근거가 달라집니다.

청크 크기와 방식이 결과에 주는 영향
변경좋아지는 점나빠질 수 있는 점
청크를 작게질문과 정확히 맞는 부분을 찾기 쉬움앞뒤 맥락이 잘려 근거가 부족한 답변
청크를 크게한 청크에 맥락이 충분함무관한 내용이 섞이고 입력 토큰 증가
계층적 분할작은 단위로 찾고 큰 단위로 답함결과 수 감소, 부모 청크만큼 입력 증가
의미 기반 분할주제가 바뀌는 지점에서 나뉨분할 비용 추가, 결과 예측이 어려움
분할 없음문서 단위로 그대로 전달쪽 번호 인용·필터 불가, 긴 문서는 비효율

어느 방식이 나은지는 문서와 질문에 달려 있으므로 직접 비교해야 합니다. 실제 사용자가 할 질문 20~30개를 정하고, 분할 방식별로 같은 질문을 검색해 필요한 근거가 상위 결과에 들어오는지 기록합니다. 사내 안내문처럼 짧은 항목이 많은 문서는 기본 분할로 시작하고, 긴 규정 문서에서 근거가 잘린다면 계층적 분할을 비교해 보는 순서를 권합니다. 검색 결과와 답변을 체계적으로 평가하는 방법은 38편에서 다룹니다.

반응형

데이터 소스와 수집: 문서는 어떻게 검색 대상이 되는가 | Bedrock 25편

반응형
Amazon Bedrock 완전 가이드 · 25편
데이터 소스와 수집

문서는 어떻게 검색 대상이 되는가

Amazon Bedrock Knowledge Bases에서 문서는 데이터 소스를 동기화(수집 작업)해야 검색 대상이 됩니다. 동기화는 증분 방식이라 지난 동기화 이후 추가·수정·삭제된 문서만 처리하므로, 원본을 고친 뒤 동기화하지 않으면 검색 결과는 예전 상태로 남습니다. 수집이 끝나면 그 내용은 원본 시스템의 문서 권한과 관계없이 검색 권한(bedrock:Retrieve)을 가진 누구나 조회할 수 있으므로, 사용자별 접근 제어는 별도로 설계해야 합니다.

가상의 사내 안내 도우미를 예로 데이터 소스 유형, 동기화와 직접 수집의 차이, 문서 삭제가 검색 결과에 반영되는 방식과 삭제 정책, 수집 작업 상태 해석과 실패 원인, 원본 권한과 검색 권한의 차이를 설명합니다.

01

S3·외부 서비스·웹·사용자 정의 연결

데이터 소스는 Knowledge Base가 문서를 가져오는 연결입니다. 연결할 수 있는 유형은 Knowledge Base 구성 방식에 따라 다릅니다. 관리형(Bedrock Managed)은 S3, SharePoint, Confluence, 웹 크롤러, Google Drive, OneDrive, 사용자 정의 커넥터를 지원하고, 고객 관리형(Customer-managed)은 S3와 사용자 정의 커넥터를 지원합니다.

데이터 소스 유형별 수집 방식
유형문서를 가져오는 방식맞는 경우
S3버킷의 지정 위치를 동기화할 때 읽음문서를 파일로 모아 두고 정기적으로 반영
외부 서비스(SharePoint·Confluence 등)커넥터가 서비스에 접속해 크롤링협업 도구의 문서를 옮기지 않고 연결(관리형)
웹 크롤러지정한 웹 페이지를 크롤링공개 안내 페이지를 검색 대상으로 삼음(관리형)
사용자 정의애플리케이션이 API로 문서를 직접 넣음사용자가 올린 파일처럼 실행 중에 생기는 문서

Confluence 같은 외부 서비스 커넥터는 각 서비스가 제공하는 변경 추적 기능을 이용해 바뀐 내용만 가져올 수 있습니다. 첫 동기화에서는 기본적으로 전체 내용을 크롤링합니다. 서비스별 인증 정보와 크롤링 범위 설정은 커넥터마다 다르므로 해당 커넥터 문서를 따릅니다.

02

전체 수집과 변경 반영

S3나 외부 서비스 데이터 소스는 문서를 추가·수정·삭제할 때마다 동기화해야 Knowledge Base에 반영됩니다. 동기화는 증분 방식이라 지난 동기화 이후 바뀐 문서만 처리합니다. 바뀐 문서는 파싱·분할·임베딩·인덱싱을 처음부터 다시 거칩니다. 데이터 소스 동기화

동기화할 때 문서별 처리
원본 상태동기화 결과
바뀌지 않음건너뜀
내용이나 메타데이터 변경다시 파싱·분할·임베딩·인덱싱
새 문서 추가새 문서만 수집
문서 삭제벡터 저장소에서 제거

동기화는 콘솔의 Sync 버튼이나 StartIngestionJob API로 시작합니다. 아래 Python 구성 예시는 수집 작업을 시작하고 작업 ID를 받아 둡니다. KB_ID와 DATA_SOURCE_ID는 사용할 Knowledge Base와 데이터 소스의 ID로, SOURCE_REGION은 Knowledge Base가 있는 리전으로 바꿉니다. 실행하려면 bedrock:StartIngestionJob 권한이 필요하고, 결과 조회에는 bedrock:GetIngestionJob 권한이 필요합니다.

import boto3

agent = boto3.client("bedrock-agent", region_name="SOURCE_REGION")
job = agent.start_ingestion_job(
knowledgeBaseId="KB_ID",
dataSourceId="DATA_SOURCE_ID",
)["ingestionJob"]
print(job["ingestionJobId"], job["status"])

수집 작업은 비동기로 실행되므로 이 호출이 끝났다고 문서가 검색되는 것은 아닙니다. 작업이 완료될 때까지 기다린 뒤 결과를 확인해야 하며, 이는 4섹션에서 다룹니다. 원본이 자주 바뀐다면 문서 변경 이벤트나 정해진 일정에 맞춰 이 API를 호출하는 작업을 직접 구성합니다. 고객 관리형에서는 다시 처리한 문서만큼 임베딩 모델 호출 비용이 생기므로, 증분 동기화라도 불필요하게 자주 실행하지 않습니다.

사용자 정의 데이터 소스는 동기화 대신 IngestKnowledgeBaseDocuments API로 문서를 바로 넣습니다. 문서 ID를 애플리케이션이 직접 정하므로, 같은 ID로 다시 넣으면 새 문서가 추가되지 않고 기존 문서가 갱신됩니다. 일부만 고치는 방식은 없고 수정할 때마다 문서 전체를 다시 수집합니다. 사용자가 실행 중에 올리는 파일처럼 정기 동기화로 다루기 어려운 문서에 맞습니다.

03

문서 삭제와 검색 결과의 관계

원본에서 문서를 지워도 검색 결과에서 바로 사라지지는 않습니다. S3나 외부 서비스 데이터 소스는 다음 동기화 때 삭제가 반영되어 벡터 저장소에서 제거됩니다. 예를 들어 폐지된 휴게실 안내문을 S3에서 지운 뒤 동기화하지 않으면, 도우미는 계속 그 안내문을 근거로 답할 수 있습니다. 사용자 정의 데이터 소스로 직접 넣은 문서는 동기화로 지워지지 않으므로, 삭제 API로 명시적으로 지울 때까지 남습니다.

데이터 소스나 Knowledge Base 자체를 삭제할 때는 데이터 삭제 정책(data deletion policy)이 적용됩니다. 이 정책은 그 데이터 소스에서 만들어진 벡터 데이터를 지울지 남길지 정합니다. CreateDataSource API

데이터 삭제 정책
정책데이터 소스·Knowledge Base 삭제 시주의할 점
DELETE그 데이터 소스에서 만든 벡터 데이터를 삭제벡터 저장소 자체는 삭제되지 않음
RETAIN벡터 데이터를 벡터 저장소에 남김남은 데이터는 직접 정리해야 함

관리형 Knowledge Base는 DELETE만 지원하며 기본값도 DELETE입니다. 고객 관리형에서 RETAIN을 고르면, Bedrock 리소스를 지운 뒤에도 벡터 저장소에 문서 조각이 남습니다. 민감한 문서를 다뤘다면 남은 데이터를 어떻게 정리할지 미리 정해 둡니다. 어느 정책이든 벡터 저장소를 직접 만들었다면 저장소 자체는 따로 삭제해야 비용이 멈춥니다.

04

수집 작업 상태와 실패 원인

수집 작업의 결과는 GetIngestionJob API나 콘솔의 동기화 기록에서 확인합니다. 응답에는 작업 상태(status), 문서별 처리 통계(statistics), 실패 사유 목록(failureReasons)이 있습니다. GetIngestionJob API

앞에서 시작한 작업의 결과를 조회하는 예시입니다. job은 2섹션 예시에서 받은 값입니다.

result = agent.get_ingestion_job(
knowledgeBaseId="KB_ID",
dataSourceId="DATA_SOURCE_ID",
ingestionJobId=job["ingestionJobId"],
)["ingestionJob"]
stats = result.get("statistics", {})
print(result["status"], stats.get("numberOfDocumentsScanned"), stats.get("numberOfDocumentsFailed"))
for reason in result.get("failureReasons", []):
print(reason)

작업 상태가 완료(COMPLETE)로 나와도 모든 문서가 들어갔다는 뜻은 아닙니다. 상태는 작업 전체의 진행 결과이고, 개별 문서의 실패는 통계에 따로 집계됩니다. 그래서 상태와 함께 다음 통계를 봅니다.

  • numberOfDocumentsScanned: 이번 작업에서 확인한 원본 문서 수
  • numberOfNewDocumentsIndexed·numberOfModifiedDocumentsIndexed: 새로 들어가거나 갱신된 문서 수
  • numberOfDocumentsSkipped: 바뀌지 않아 건너뛴 문서 수
  • numberOfDocumentsDeleted: 원본 삭제가 반영된 문서 수
  • numberOfDocumentsFailed: 수집에 실패한 문서 수

예를 들어 시설 안내 문서 10개를 올렸는데 작업이 실패하거나 확인한 문서 수가 0이라면, 실패 사유와 함께 서비스 역할의 S3 읽기 권한과 데이터 소스의 경로 설정을 먼저 확인합니다. 확인한 문서는 10개인데 실패가 3개라면 그 문서들의 파일 형식이나 크기, 파싱 문제를 실패 사유에서 찾습니다. 수집은 됐는데 원하는 내용이 검색되지 않는다면 수집이 아니라 분할이나 검색 설정의 문제일 수 있으며, 이는 26·28편에서 다룹니다.

05

원본 접근 권한과 검색 권한의 차이

가장 오해하기 쉬운 부분은 권한입니다. 원본 시스템에서 문서마다 열람 권한을 걸어 두었더라도, 동기화로 수집된 내용은 검색 권한(bedrock:Retrieve)을 가진 누구나 조회할 수 있습니다. 공식 문서도 원본에서 권한이 제한된 데이터까지 여기에 포함될 수 있다고 안내합니다. 원본 권한은 서비스 역할이 문서를 읽을 수 있는지를 정할 뿐, 검색하는 사용자가 누구인지는 따지지 않습니다. 커넥터 권한 안내(Confluence 예)

예를 들어 인사팀만 볼 수 있는 급여 규정을 일반 안내 문서와 같은 Knowledge Base에 수집하면, 사내 안내 도우미를 쓰는 모든 직원의 질문에 급여 규정이 근거로 검색될 수 있습니다. 이를 막는 방법은 구성 방식에 따라 다릅니다.

  • 공개 범위별로 Knowledge Base 분리: 전 직원용과 인사팀용을 따로 만들고, 애플리케이션이 사용자에 따라 호출할 Knowledge Base를 고릅니다. 가장 단순하고 경계가 분명합니다.
  • 메타데이터 필터: 문서마다 공개 범위 같은 메타데이터를 붙이고, 애플리케이션이 로그인한 사용자에 맞는 필터를 검색 요청에 넣습니다. S3 데이터 소스에서는 문서 옆에 같은 이름의 .metadata.json 파일을 둡니다.
  • 관리형 KB의 ACL 인식 검색: 지원 커넥터에서 ACL 인식을 켜면 원본의 사용자·그룹 권한을 문서와 함께 수집하고, 검색할 때 전달한 사용자 정보에 맞는 문서만 돌려줍니다. ACL 설정은 데이터 소스를 만들 때 정해야 하며 나중에 켜거나 끌 수 없습니다.

아래는 S3에 둔 시설안내.docx 옆에 함께 올리는 메타데이터 파일의 구성 예시입니다. 파일 이름은 원본 파일 이름 뒤에 .metadata.json을 붙인 형태이며, 속성 이름과 값은 가상의 예시입니다.

{
"metadataAttributes": {
"audience": "all",
"department": "facility"
}
}

어느 방법이든 사용자를 확인하는 책임은 애플리케이션에 있습니다. 메타데이터 필터는 애플리케이션이 넣은 조건대로만 걸러지고, 관리형 KB의 ACL 인식 검색도 전달받은 사용자 정보가 진짜인지 확인하지 않습니다. 공식 문서는 ACL 인식 검색이 보안 경계가 아니므로, 앞단의 사용자 인증 없이 이것만으로 접근을 통제하지 말라고 안내합니다. 즉 사용자 인증은 애플리케이션이 하고, Knowledge Base는 인증된 사용자 정보로 결과를 거르는 역할만 맡습니다.

반응형

Knowledge Bases 구성 선택: 관리형 저장소와 직접 선택한 저장소 | Bedrock 24편

반응형
Amazon Bedrock 완전 가이드 · 24편
Knowledge Bases 구성 선택

관리형 저장소와 직접 선택한 저장소

Amazon Bedrock Knowledge Bases는 두 방식으로 구성합니다. 관리형(Bedrock Managed) Knowledge Base는 저장소·임베딩·재정렬을 Bedrock이 맡고, 고객 관리형(Customer-managed) Knowledge Base는 사용자가 고른 벡터 저장소를 직접 만들고 운영합니다. AWS는 사용 편의성·정확도·비용의 균형을 이유로 관리형을 권장합니다. 벡터 저장소를 직접 조정해야 하거나 기존 저장소를 그대로 써야 한다면 고객 관리형을 선택합니다.

가상의 사내 안내 도우미를 예로 Knowledge Base와 데이터 소스의 관계, 관리형과 고객 관리형의 차이, 임베딩 모델과 저장소의 연결, 서비스 역할의 데이터 접근 범위, 직접 구현한 RAG와의 선택 기준을 설명합니다.

01

Knowledge Base와 데이터 소스의 관계

Knowledge Base는 검색의 단위이고, 데이터 소스는 문서를 가져오는 위치입니다. 애플리케이션은 Knowledge Base를 대상으로 검색하고, Knowledge Base는 연결된 데이터 소스에서 문서를 수집해 검색할 수 있는 형태로 저장합니다. 하나의 Knowledge Base에 여러 데이터 소스를 연결할 수 있습니다.

가상의 사내 안내 도우미라면 다음처럼 구성할 수 있습니다.

  • Knowledge Base: 사내 안내 KB(검색 대상, 임베딩 설정, 저장소를 가짐)
  • 데이터 소스 1: S3 버킷의 시설 안내 문서
  • 데이터 소스 2: 사내 협업 도구의 인사 공지(관리형이라면 기본 커넥터로 연결)

수집(동기화)은 데이터 소스 단위로 실행합니다. 시설 안내 문서만 바뀌었다면 해당 데이터 소스만 다시 동기화하면 됩니다. 반면 검색은 Knowledge Base 단위라서, 한 번의 검색으로 두 데이터 소스의 문서가 함께 결과에 나올 수 있습니다. 특정 데이터 소스의 문서만 찾으려면 메타데이터 필터로 범위를 좁히며, 이는 28편에서 다룹니다.

어떤 데이터 소스를 연결할 수 있는지는 구성 방식에 따라 다릅니다. 관리형 Knowledge Base는 S3, SharePoint, Confluence, 웹 크롤러, Google Drive, OneDrive, 사용자 정의 커넥터를 지원하고, 고객 관리형은 S3와 사용자 정의 커넥터를 지원합니다. 데이터 소스별 수집 방식은 25편에서 다룹니다.

02

관리 범위에 따른 구성 방식

두 방식의 가장 큰 차이는 저장소를 누가 운영하느냐입니다. 관리형은 임베딩·텍스트·메타데이터·원본 파일을 담는 저장소를 Bedrock이 만들고 확장합니다. 고객 관리형은 사용자가 지원되는 벡터 저장소를 골라 직접 만들고, 용량과 업데이트를 관리합니다. 대신 저장소에 직접 접근할 수 있습니다. 관리형과 고객 관리형 비교

구성 방식별 주요 차이(공식 비교 요약)
항목관리형(Bedrock Managed)고객 관리형(Customer-managed)
저장소Bedrock이 만들고 자동 확장사용자가 선택·생성·운영
임베딩 모델내장 관리형 모델 기본 제공, Bedrock 임베딩 모델로 변경 가능Bedrock 임베딩 모델 중 선택
재정렬내장 관리형 재정렬 기본 제공필요하면 Bedrock 재정렬 모델 선택
검색 방식에이전트형 검색과 의미·하이브리드 검색사용자가 검색 전략 선택
문서 파싱멀티모달 파일용 내장 파서기본 파서·모델 기반 파서·Data Automation 중 선택
AgentCore Gateway 연동지원지원하지 않음

관리형에 포함된 임베딩 모델과 재정렬 모델은 추가 비용 없이 제공됩니다. 고객 관리형에서는 벡터 저장소 운영 비용과 재정렬 모델 사용 비용을 따로 계산해야 합니다. 또 관리형 Knowledge Base는 지원 리전이 따로 정해져 있으므로, 사용할 리전에서 쓸 수 있는지 먼저 확인합니다.

Bedrock에는 이 두 방식 외에, Amazon Redshift의 정형 데이터를 자연어로 조회하는 정형 데이터용 Knowledge Base도 있습니다. 문서가 아니라 표 형태의 업무 데이터를 묻는 경우에 쓰며, 30편에서 다룹니다.

03

임베딩 모델과 저장소의 연결

임베딩 모델은 문서 조각과 질문을 숫자 벡터로 바꾸는 모델입니다. 수집할 때와 검색할 때 같은 모델을 써야 두 벡터를 비교할 수 있습니다. 그래서 임베딩 모델은 Knowledge Base를 만들 때 정하고, 저장소에는 그 모델이 만든 벡터가 쌓입니다.

관리형에서는 내장 임베딩 모델을 그대로 쓰는 것이 기본입니다. 다른 모델을 쓰려면 Knowledge Base를 만들 때 지정해야 하며, float32 형식의 1,024차원 벡터를 만드는 Bedrock 임베딩 모델만 고를 수 있습니다. 고객 관리형에서는 Bedrock 임베딩 모델 중 자유롭게 고를 수 있지만, 직접 만든 벡터 인덱스의 차원과 형식을 그 모델의 출력에 맞춰야 합니다.

임베딩 모델을 나중에 바꾸는 일은 가볍지 않습니다. 모델이 다르면 같은 문장도 다른 벡터가 되므로, 이미 저장된 벡터와 새 질문의 벡터를 비교할 수 없습니다. 모델을 바꾸려면 모든 문서의 벡터를 새로 만들어야 합니다. 따라서 처음에 한국어 문서로 검색 품질을 시험해 보고 모델을 정하는 것이 좋습니다. 차원·인덱스·유사도 설정은 27편에서 자세히 다룹니다.

04

서비스 역할과 데이터 접근

고객 관리형 Knowledge Base에는 두 종류의 권한 주체가 있습니다. 하나는 Knowledge Base가 수집과 검색을 위해 맡는 서비스 역할(service role)이고, 다른 하나는 Knowledge Base를 만들거나 검색을 호출하는 사용자·애플리케이션의 권한입니다. 애플리케이션은 데이터 소스에 직접 접근하지 않고, bedrock:Retrieve 같은 검색 권한만 있으면 됩니다. 서비스 역할은 데이터 소스 읽기, 임베딩 모델 호출, 벡터 저장소 쓰기·조회 권한을 가집니다. 저장소나 데이터를 KMS 키로 암호화했다면 복호화 권한도 필요합니다. Knowledge Bases 서비스 역할

콘솔에서 만들면 Bedrock이 서비스 역할을 자동으로 만들 수 있습니다. 직접 만든 역할을 쓴다면 Bedrock만 그 역할을 맡을 수 있도록 신뢰 정책을 작성합니다. 아래는 신뢰 정책 구성 예시입니다. ACCOUNT_ID는 Knowledge Base를 만들 계정 ID로, KNOWLEDGE_BASE_ARN_PATTERN은 해당 계정·리전의 Knowledge Base ARN 형식(끝이 knowledge-base/*)으로 바꿉니다.

{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "bedrock.amazonaws.com"},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": "ACCOUNT_ID"},
"ArnLike": {"aws:SourceArn": "KNOWLEDGE_BASE_ARN_PATTERN"}
}
}]
}

aws:SourceAccount와 aws:SourceArn 조건은 다른 계정의 리소스가 이 역할을 대신 쓰는 상황(confused deputy)을 막습니다. Knowledge Base를 만든 뒤에는 ARN 끝의 knowledge-base/*를 실제 Knowledge Base ID로 좁힙니다.

운영에서 특히 주의할 점이 있습니다. 데이터 소스를 만들거나 수정하고 수집을 시작하는 작업은 새 역할을 전달하지 않고, Knowledge Base에 이미 연결된 서비스 역할로 실행됩니다. 따라서 데이터 소스 관리 권한과 검색 권한을 가진 사람은 자신에게 S3 권한이 없어도, 서비스 역할이 읽을 수 있는 위치라면 어디든 수집해 Retrieve로 내용을 볼 수 있습니다. 서비스 역할의 S3 권한은 실제로 필요한 버킷과 접두사로만 제한하고, 데이터 소스 관련 작업 권한은 관리자 권한처럼 다룹니다.

05

직접 구현한 RAG와의 선택 기준

Knowledge Bases를 쓰지 않고 RAG를 직접 구현할 수도 있습니다. 애플리케이션이 문서를 나누고, 임베딩 모델을 호출하고, 벡터 데이터베이스에 저장·검색한 뒤, Converse API로 답변을 만드는 방식입니다. 모든 단계를 직접 제어할 수 있지만 그만큼 만들고 운영할 부분이 늘어납니다. AWS 벡터 데이터베이스 선택 가이드

세 가지 구성의 선택 기준
상황관리형 KB고객 관리형 KB직접 구현
운영 부담을 최소화하고 싶다적합저장소 운영 필요모든 단계 운영 필요
S3 외 협업 도구 문서를 연결한다기본 커넥터 지원문서를 직접 넣는 사용자 정의 데이터 소스로 구현직접 구현
벡터 저장소 설정을 직접 조정해야 한다불가(Bedrock이 저장소 관리)적합적합
이미 운영 중인 벡터 DB를 써야 한다불가(Bedrock이 저장소 관리)지원 저장소라면 적합적합
분할·검색 로직을 완전히 바꿔야 한다제한적제한적(사용자 정의 변환 가능)적합

사내 안내 도우미처럼 문서를 모아 검색하는 것이 목적이고 저장소를 직접 다룰 이유가 없다면 관리형부터 검토합니다. 기존 벡터 저장소를 재사용해야 하거나 인덱스 설정을 직접 조정해야 할 때 고객 관리형을 고르고, Knowledge Bases가 제공하지 않는 분할·검색 방식이 꼭 필요할 때 직접 구현을 검토합니다.

반응형