Services Added to the Catalog Quietly Disappear
한국어 원문으로 표시합니다.
목표
VM 안에 진짜 카탈로그 백엔드를 띄우고, 엔티티가 카탈로그에 들어오지 않는 네 가지 원인을 직접 만들어 어디에 흔적이 남는지 찾습니다. 정적 location 자동 수집, API 로 등록하는 location, 고아 엔티티 정리, location 삭제까지 실제 API 응답으로 확인합니다.
왜 중요한가
"catalog-info.yaml 을 올렸는데 포털에 안 보여요" 는 Backstage 운영에서 가장 흔한 문의입니다. 검증 라이브러리로 YAML 한 개를 확인하는 것만으로는 부족합니다. 돌아가는 카탈로그에서는 실패가 API 응답에도, 기본 로그에도 드러나지 않는 경우가 있고, 한 엔티티의 문제가 같은 파일 전체를 막기도 합니다. 등록 API 가 201 을 돌려줘도 처리 단계에서 실패할 수 있습니다. 어디를 봐야 하는지 알아야 고칠 수 있습니다.
처음 VM 이 뜨는 데 4분쯤 걸리고, 1단계 설치는 인터넷에서 약 30초 걸립니다. GitHub 같은 외부 시스템을 훑는 탐색(discovery) 공급자는 외부 계정이 필요해 다루지 않습니다. 여기서 "자동 수집" 은 설정에 적은 location 을 카탈로그가 주기적으로 다시 읽는 것입니다.
단계
/usr/local/cba-ingest에 카탈로그 백엔드 패키지를 고정 버전으로 설치합니다.- 설정에 적은 파일 location 이 자동으로 수집되는 것을 확인합니다.
- owner 가 빠진 엔티티가 흔적 없이 빠지는 것을 기록합니다.
- 로그 모듈을 붙여 처리 오류를 warn 로그로 끌어냅니다.
- 허용되지 않은 kind 가 파일 전체를 막는 것을 보고 location 별 규칙으로 풉니다.
- API 로 url location 을 등록하고, 읽기 허용 목록 문제를 고칩니다.
- 파일에서 뺀 엔티티가 고아가 됐다가 지워지는 것을 봅니다.
- location 을 지우고 수집 장애 보고서를 씁니다.
참고
- 카탈로그 API 호출:
curl -s -H 'Authorization: Bearer ingest-lab-token-7f3a9c' http://127.0.0.1:7007/api/catalog/entities | jq -r '.[].metadata.name' - 백엔드 다시 띄우기:
cd /usr/local/cba-ingest && pkill -f 'node index.js'; setsid nohup node index.js >> backend.log 2>&1 < /dev/null & - 로그는 이어 씁니다(
>>). 기동할 때마다Loading config from줄로 시작하니, 지금 프로세스의 로그는 그 마지막 줄 뒤를 보세요. - 준비 확인:
curl -s http://127.0.0.1:7007/.backstage/health/v1/readiness - 로그의 색 문자 벗기기:
sed 's/\x1b\[[0-9;]*m//g' backend.log - DB 가
:memory:라서 재시작하면 API 로 등록한 location 은 사라집니다(설정 파일의 location 은 다시 읽힙니다). - 카탈로그 설정(rules·orphanStrategy·processingInterval·오류 로그 모듈): https://backstage.io/docs/features/software-catalog/configuration
- 엔티티의 일생(처리·고아·삭제): https://backstage.io/docs/features/software-catalog/life-of-an-entity
- 카탈로그 API(locations·entities): https://backstage.io/docs/features/software-catalog/software-catalog-api
- URL Reader 와 backend.reading.allow: https://backstage.io/docs/backend-system/core-services/url-reader
- 외부 호출용 정적 토큰(externalAccess): https://backstage.io/docs/auth/service-to-service-auth
- 엔티티 형식(Component 필수 필드): https://backstage.io/docs/features/software-catalog/descriptor-format
돌아가는 카탈로그의 재료를 고정 버전으로 받는다
/usr/local/cba-ingest 에 npm 프로젝트를 만들고 네 패키지를 범위 기호 없이 정확한 버전으로 설치하세요(package.json 에도 ^ 없이). npm 캐시는 npm_config_cache=/usr/local/cba-ingest/.npmcache 로 둡니다. @backstage/backend-defaults@0.17.8, @backstage/plugin-catalog-backend@3.9.1, @backstage/plugin-catalog-backend-module-logs@0.1.25, better-sqlite3@12.4.1.
npm install --save-exact 를 쓰세요. 로그 모듈은 설치만 해 두고 4단계에서 붙입니다. better-sqlite3 13.x 는 이 Node 20 에 미리 빌드한 바이너리가 없어 컴파일을 시도하다 실패합니다.
설정에 적은 파일이 자동으로 수집된다
/usr/local/cba-ingest/app-config.yaml 에 다음을 두고 카탈로그 플러그인만 든 백엔드(index.js)를 7007 포트에 띄우세요(출력은 /usr/local/cba-ingest/backend.log). backend.baseUrl: http://localhost:7007, backend.listen.port: 7007, DB better-sqlite3·':memory:', backend.auth.externalAccess 에 type static·token ingest-lab-token-7f3a9c·subject ingest-cli, catalog.processingInterval: { seconds: 5 }, catalog.rules: [{allow: [Component, Group, Location]}], catalog.locations 에 type: file·target: ./catalog/team.yaml. catalog/team.yaml 에는 Group team-search 와 그 팀 소유의 Component search-api·search-indexer 를 넣습니다. Authorization: Bearer ingest-lab-token-7f3a9c 로 /api/catalog/entities 를 불러 세 엔티티가 보이면 됩니다.
카탈로그 API 는 기본 인증 정책 뒤에 있어 토큰 없이 부르면 401 입니다. 정적 토큰은 운영에서는 ${환경변수} 로 넣어야 하지만 이 실습에서는 흐름을 보려고 파일에 적습니다. 들어온 엔티티의 backstage.io/managed-by-location 애노테이션이 어느 파일을 가리키는지 확인하세요.
owner 를 빠뜨린 엔티티는 흔적 없이 빠진다
team.yaml 끝에 spec.owner 가 없는 Component search-ui(type website, lifecycle production)를 덧붙이세요. 재시작하지 않고 몇 초 기다린 뒤 카탈로그와 로그를 보고 /root/cba-ingest/silent.txt 에 세 줄을 쓰세요 — search_ui_in_catalog=(yes/no), search_api_in_catalog=(yes/no), log_lines_mentioning_search_ui=(그 순간 backend.log 에서 search-ui 가 나오는 줄 수). search-ui 는 이 실습 끝까지 고치지 말고 둡니다(뒤 단계와 채점이 이 상태를 씁니다).
처리 주기를 5초로 줄였으니 파일을 고치면 곧 다시 읽힙니다. 엔티티 하나가 검증에 실패했을 때 같은 파일의 나머지가 어떻게 되는지, 그리고 그 실패가 어디에 남는지(혹은 안 남는지) 보세요. grep -c search-ui backend.log 로 셉니다.
처리 오류를 로그로 끌어낸다
index.js 에 @backstage/plugin-catalog-backend-module-logs 를 add 하고 백엔드를 재시작하세요. backend.log 에 component:default/search-ui 에 대한 warn 줄이 찍히면, 색 제어 문자를 벗긴 그 줄 하나를 /root/cba-ingest/owner-error.txt 에 저장하세요. team.yaml 은 고치지 않습니다.
카탈로그는 처리 오류를 이벤트로 내보낼 뿐이고, 그것을 로그로 적는 것은 별도 모듈입니다. 모듈을 붙인 뒤에도 처리 주기가 한 번 돌아야 줄이 생깁니다. sed 's/\x1b\[[0-9;]*m//g' backend.log | grep search-ui 로 찾으세요. events backend not found 경고는 events 플러그인이 없어서 나는 것이고 이 과제와 무관합니다.
API 하나 때문에 파일 전체가 안 들어온다
/usr/local/cba-ingest/catalog/billing.yaml 에 Component billing-api(owner team-search, providesApis [billing-openapi])와 API billing-openapi(type openapi, owner team-search)를 넣고, catalog.locations 에 type: file·target: ./catalog/billing.yaml 을 추가한 뒤 재시작하세요. 두 엔티티가 모두 안 들어오는 것을 확인하고, api:default/billing-openapi 에 대한 warn 줄 하나를 /root/cba-ingest/kind-error.txt 에 저장하세요. 그다음 전역 catalog.rules 는 그대로 두고 billing.yaml location 에만 rules: [{allow: [API]}] 를 달아 재시작해 billing-api 와 billing-openapi 가 모두 들어오게 하세요.
허용되지 않은 kind 는 그 엔티티 하나만 버려지는 게 아니라 그 location 의 처리 결과 전체를 실패시킵니다 — owner 누락과 비교해 보세요. 전역 규칙에 API 를 넣으면 어느 파일에서든 API 가 들어오게 됩니다. 문서의 location 별 rules 를 보세요.
등록은 201 인데 아무것도 들어오지 않는다
/usr/local/cba-ingest/incoming/data.yaml 에 Component etl-runner 와 etl-scheduler(둘 다 owner team-search)를 두고, 그 디렉터리를 python3 -m http.server 8088 --bind 127.0.0.1 로 서비스하세요(백그라운드). ① 카탈로그 API 로 type: file·target /usr/local/cba-ingest/incoming/data.yaml 등록을 시도해 응답(400)을 보고, ② type: url·target http://localhost:8088/data.yaml 로 POST /api/catalog/locations 를 보내 201 이지만 엔티티가 안 들어오는 것을 확인한 뒤, 그 url 에 대한 warn 줄 하나를 /root/cba-ingest/reading-error.txt 에 저장하세요. ③ backend.reading.allow 에 host: localhost:8088 을 넣고 재시작한 다음, 같은 url 을 다시 등록해 두 엔티티가 들어오게 하세요. 마지막 등록의 POST 응답 JSON 을 /root/cba-ingest/location.json 에 저장합니다.
API 로 등록하는 location 은 url 형식만 받습니다. url 을 읽는 것은 UrlReader 이고, 통합(integration)이 없는 호스트는 허용 목록에 있어야 읽습니다 — 이 검사는 등록 시점이 아니라 처리 시점에 일어나서 등록 응답은 성공입니다. :memory: DB 라 재시작하면 앞서 등록한 location 이 사라지니 GET /api/catalog/locations 로 확인하세요. http.server 도 셸이 끝나도 살도록 setsid nohup ... > 로그 2>&1 < /dev/null & 로 띄우세요.
파일에서 뺀 엔티티는 고아가 됐다가 지워진다
incoming/data.yaml 에서 etl-runner 문서를 지우세요(etl-scheduler 는 남깁니다). 카탈로그에서 etl-runner 에 backstage.io/orphan: "true" 애노테이션이 붙는 순간의 엔티티 JSON(GET /api/catalog/entities/by-name/component/default/etl-runner 응답)을 /root/cba-ingest/orphan.json 에 저장하세요. 그런 다음 기다려서 그 엔티티가 카탈로그에서 지워지는 것(by-name 404)과 로그의 Deleted ... orphaned entities 줄을 확인하세요.
엔티티를 내보내던 location 이 더는 그 엔티티를 내보내지 않으면 고아가 됩니다. 고아를 남길지 지울지는 catalog.orphanStrategy 로 정하고, 기본값에서는 정리 작업(로그의 catalog_orphan_cleanup, 30초 주기)이 지웁니다. 애노테이션은 잠깐만 보이니 1초 간격으로 폴링해 붙는 순간을 잡으세요.
location 을 지우고 수집 장애 보고서를 쓴다
location.json 의 id 로 DELETE /api/catalog/locations/<id> 를 보내 등록을 해제하고, etl-scheduler 가 카탈로그에서 사라지는 것을 확인하세요. 그다음 /root/cba-ingest/report.md 첫 다섯 줄에 지금 잰 값을 적으세요 — registered_location_status=(GET /api/catalog/locations/), etl_scheduler_status=(by-name 조회), search_ui_status=(by-name 조회), api_location_count=(GET /api/catalog/locations 결과 개수 — 정적 location 두 개가 여기 나오는지), file_register_status=(type file 등록 시도 응답). 그 아래에 이 실습에서 수집이 실패한 네 가지 원인과 각각 어디에 흔적이 남았는지 정리하세요.
모든 값은 토큰을 붙인 curl 로 지금 잰 것입니다. 정적 location 은 설정 파일이 관리하므로 API 로 지울 수 없고 목록에도 나오지 않습니다. 원인 네 가지는 owner 누락, 허용되지 않은 kind, 읽기 허용 목록, 파일에서 빠진 엔티티(고아)입니다.