| name | debug_Tech |
|---|---|
| description | m2slide의 Kroki 다이어그램(slide 31~44) 미표시 문제 진단·수정 기록 — 외부 kroki.io 의존 제거 + 빌드 타임 SVG 캐시 + Self-hosted Docker 전환 |
| date | 2026-05-09 |
Projects/MarkdownGraph/slide/index.html(현 MermaidExample)의 #/31 ~ #/44 슬라이드 14장에서 본문이 빈 상태로 표시됨. 제목(<h2>)만 보이고 다이어그램 영역이 백지.
해당 슬라이드 본문은 모두 Kroki 계열 다이어그램:
blockdiag/seqdiag/actdiag/nwdiag/ditaadot(Graphviz) /vega/vegaliteplantuml(Class / Component / Activity / Use Case 4종)
Mermaid 슬라이드(#/1 ~ #/30)와 일반 텍스트 슬라이드(#/45~)는 정상.
lib/markdown.js의 코드 블록 처리에서 kroki 언어 감지 시 zlib.deflateSync로 path-based URL을 만드는 로직이 있었으나 zlib이 require되지 않음.
// 문제 코드 (수정 전)
try {
const deflated = zlib.deflateSync(Buffer.from(source, 'utf-8'));
// ... → ReferenceError: zlib is not defined
} catch (e) {
// fallback: data-type 속성 + raw text
html.push(`<div class="media-container kroki" data-type="${lang}">`);
html.push(source);
html.push('</div>');
}→ 매 빌드마다 catch 블록으로 분기되어 fallback HTML(미렌더 컨테이너)이 생성됨.
lib/html-builder.js의 Reveal.on('ready') 핸들러가 fallback 컨테이너를 발견하면 다음 URL로 <img>를 만들어 부착:
const imgUrl = 'https://kroki.io/' + diagramType + '/svg?source=' + encodeURIComponent(diagramSource);?source= 쿼리 형식은 kroki.io 표준이 아님. kroki.io 표준 GET 형식은 path-based deflate(raw) + base64url:
GET https://kroki.io/{type}/{format}/{base64url(deflate(source))}
?source=로 보내면 kroki.io는 504/timeout으로 응답.
위 두 결함을 고쳐도 현재 ICN 리전에서 kroki.io의 렌더링 엔드포인트가 30초+ 응답 없음(홈페이지만 200). 즉 코드를 정상화해도 외부 서비스 자체가 도달 불가 상태.
| 엔드포인트 | 결과 |
|---|---|
https://kroki.io/ (홈) |
HTTP 200 ✓ |
GET /{type}/svg/{encoded} (path-based, 표준) |
30s timeout ✗ |
GET /{type}/svg?source=... (비표준, m2slide 기존 fallback) |
504 ✗ |
POST /{type}/svg (body) |
20s timeout ✗ |
| 슬라이드 범위 | 렌더링 경로 | 결과 |
|---|---|---|
| #/1 ~ #/30 (Mermaid) | 클라이언트 사이드 mermaid.js |
✓ 정상 |
| #/31 ~ #/44 (Kroki 계열) | 외부 kroki.io GET 이미지 |
✗ 빈 화면 |
| #/45 ~ (텍스트·코드) | 정적 HTML | ✓ 정상 |
빈 슬라이드 위치가 정확히 kroki 의존 슬라이드와 일치 → 결함 위치 확정.
H: 빌드 타임 결함으로 fallback HTML이 생성되고, 런타임 fallback URL은 비표준이라 kroki.io 응답 불가. 추가로 kroki.io 자체가 ICN에서 미도달.
검증:
lib/markdown.js상단에require부재 확인 → 결함 ① 확정curl로?source=URL → 504, 표준 path URL → 타임아웃 → 결함 ② + 외부 장애 확정- 같은 페이지 Mermaid는 정상 → 클라이언트 사이드 렌더는 영향 없음
zlib require 추가 + path-based URL 생성 + <img onerror> 안내문 부착. 그러나 ICN에서 kroki.io 자체가 응답 안 해 화면은 여전히 빈 상태(또는 30초 후 onerror로 안내문). 사용자 결정으로 외부 kroki.io 의존을 완전히 제거하기로 함.
[빌드 타임]
*.md ──parse──> kroki source 추출
│
▼
sha256(lang+source) 해시
│
├── 캐시 hit ── slide/kroki/{lang}_{hash}.svg
│
└── 캐시 miss ── curl ${kroki_server}/{lang}/svg/{deflate+base64url}
│
├── 성공: SVG 저장 + img src 로컬 경로
└── 실패: img src 외부 URL + onerror 안내
[런타임]
reveal.js → <img src="kroki/{lang}_{hash}.svg"> ── 외부 호출 0
zlib/fs/path/crypto/child_processrequire 추가setKrokiCacheDir(absDir, relPath, server)모듈 setter 추가krokiPathUrl(lang, source, server)— deflate + base64url로 path-based URL 생성fetchKrokiSvgCached(lang, source, opts)— 캐시 우선, miss 시curl로 동기 fetch (timeout 8s), 성공 시 캐시 저장 + 상대경로 반환, 실패 시null- kroki 코드 블록 핸들러를 위 캐시 로직 + path-based fallback URL +
onerror안내문으로 재작성 module.exports에setKrokiCacheDir추가
setKrokiCacheDirimportgenerateHTML()진입 시점에setKrokiCacheDir(outputDir/kroki, 'kroki', _cfg.krokiServer || 'https://kroki.io')호출 — 캐시 디렉토리 + 서버 endpoint 주입- runtime fallback (Reveal.on 'ready')는 정규식 없는 단순 안내문으로 단순화 (캐시 채워진 환경에서는 dead code)
- 주의: 본 핸들러는
generateHTML이 반환하는 template literal 내부라 정규식 escape에 민감. 1차 시도에서\+,\/가 출력 HTML에서+,/로 변환되어/+/g(Invalid regex) →Reveal.initialize스크립트 전체가 SyntaxError → reveal.js 미초기화로 모든 슬라이드 백지화. 정규식 자체를 제거해 안전하게 처리
- 주의: 본 핸들러는
cfg.krokiServer = 'https://kroki.io'기본값_config.yml의kroki_server:YAML 키 매핑 추가
kroki_server: http://localhost:8000/tmp/kroki/docker-compose.yml:
services:
core:
image: yuzutech/kroki
container_name: m2-kroki-core
environment:
- KROKI_BLOCKDIAG_HOST=blockdiag
ports:
- "8000:8000"
depends_on:
- blockdiag
blockdiag:
image: yuzutech/kroki-blockdiag
container_name: m2-kroki-blockdiag
expose:
- "8001"기동:
cd /tmp/kroki && docker compose up -dblockdiag companion 컨테이너는 blockdiag/seqdiag/actdiag/nwdiag/packetdiag/rackdiag 처리. core 컨테이너는 plantuml/graphviz/ditaa/vega/vegalite 등 자체 처리.
Found 1 markdown file(s) to process
Processing: MermaidExample.md
→ Generated: .../slide/index.html
✅ All files processed!
[kroki] fetch 실패 메시지 0건 (이전 12건).
| 항목 | 결과 |
|---|---|
slide/kroki/*.svg |
12개 (1.6KB ~ 8KB, 모두 유효 XML) |
HTML src="kroki/..." 참조 |
12건 |
HTML src="https://kroki.io/..." 참조 |
0건 |
inline <script> 4종 syntax |
모두 OK (1차 시도에선 49KB script가 SyntaxError) |
| Chrome에서 슬라이드 #/31~#/44 렌더링 | ✓ 모든 다이어그램 표시 |
slide/kroki/*.svg는 영구 자산. Docker 종료해도 빌드는 캐시 hit으로 작동 → CI/CD에서도 한 번만 컨테이너 띄우면 됨. 캐시는 git에 commit해 다른 환경에서도 재사용 가능 (단 .gitignore로 slide/ 자체가 제외돼 있을 수 있어 별도 정책 검토 필요).
_config.yml에 한 줄만 추가:
kroki_server: http://localhost:8000미지정 시 자동으로 https://kroki.io fallback (외부 도달 가능한 환경에서 동작).
cd /tmp/kroki && docker compose up -d # 기동
cd /tmp/kroki && docker compose ps # 상태 확인
cd /tmp/kroki && docker compose down # 정지
cd /tmp/kroki && docker compose down -v # 정지 + 볼륨 삭제 (있을 시)- 캐시 디렉토리를
Projects/{name}/.kroki-cache/로 이동(slide/와 분리, deploy 산출물 클린) m2slide.sh에--start-kroki옵션 추가 — 빌드 전 자동으로 컨테이너 기동- 캐시 hit ratio·miss 시 fetch 시간 telemetry 출력
- mermaid도
mmdc로 빌드 타임 SVG 캐시 (오프라인 환경 대응 + 로딩 속도 개선)
- template literal 내부의 backslash escape는 출력 HTML에서 한 단계 더 소실됨
lib/html-builder.js의generateHTML이 backtick string으로 HTML 전체를 조립- 그 안의
<script>코드에서replace(/\+/g, '-')라 적으면 출력에는replace(/+/g, '-')로 새겨져 invalid regex - 해결: 정규식을 사용하지 않거나,
\\+(이중 escape)로 작성
- 외부 인프라 의존은 코드 결함을 가린다: kroki.io 살아있을 때는 비표준
?source=URL이라도 일부 작동했을 수 있어 결함 ②가 발견되지 않았을 가능성 - catch 블록으로 자동 fallback되는 코드는 silent failure 위험: 결함 ①의 catch는
zlib is not defined를 묵살하고 fallback HTML을 만들어 빌드는 성공 처리. 빌드 단계에서 kroki source가 path-based URL로 변환됐는지 검증하는 후크가 있었다면 더 일찍 발견됐을 것
- lib/markdown.js — kroki 캐시 로직
- lib/html-builder.js —
setKrokiCacheDir호출 + runtime fallback - lib/config.js —
kroki_server매핑 - Projects/MermaidExample/_config.yml — endpoint 지정
- /tmp/kroki/docker-compose.yml — 컨테이너 정의