English {#english}
Ansible Frappe bench rc:127 Resolution Plan
Purpose: Document the structural cause of repeated rc: 127 (No such file or directory) when running bench new-site, and plan short-, mid-, and long-term fixes.
References: ansible-implementation-plan.md, ansible-clean-and-reinstall.md, pulumi-ansible-step1-step2-plan.md §2.5 (image-based provisioning).
1. Core error
- Symptom:
bash: line 1: /home/frappe/frappe-bench/env/bin/bench: No such file or directory→ rc: 127 (binary not found). - Implication: The failure is not
bench new-sitelogic; the bench binary path is wrong or the file does not exist (structural).
Four structural issues: (1) bench CLI location vs workspace separation (CLI at ~/.local/bin/bench, workspace frappe-bench/ empty). (2) Docker volume mount vs bench init: mounting an empty host dir over frappe-bench overlays (hides) the image’s bench tree. (3) Init + new-site at runtime is not idempotent; failure leaves unclear state. (4) Ansible trusts “init changed” without verifying that the workspace was actually populated.
2–5. Log analysis, root cause, bench layers, essence
- Logs:
BENCH_DIR=/home/frappe/frappe-benchis empty (only./..);env/bin/benchmissing;which bench=/home/frappe/.local/bin/bench(user-local pip). So CLI exists, workspace does not. - Root cause: Path mismatch (Ansible assumes
env/bin/bench; image has user-local bench). Virtualenv/workspace not created or host volume overlay hid image workspace. Most likely: empty host dir mounted over/home/frappe/frappe-benchso image’s bench tree is hidden. - Bench layers: CLI layer (
~/.local/bin/bench) ✅; workspace layer (frappe-bench/with apps, sites, config, env) ❌ empty. - Conclusion: Not a simple path typo; it’s Docker volume + runtime bench init design conflict. Fix: resolve path usage and fix volume/init strategy.
6–8. Gaps, solution strategy, structural options
- Current plan gaps: Short-term PATH/which bench removes 127 but can lead to “Not a bench directory”. Init check should validate full workspace (sites, apps, Procfile). Golden Image alone is insufficient if the whole workspace is volume-mounted; only sites should be on a volume.
- Short-term: Use existing bench (PATH or
which bench), single-string command. Mid-term: Post-init verification task (sites, apps, Procfile); fail playbook if missing. Long-term: Complete bench in image (Dockerfile) or Golden Image; Ansible does new-site + orchestration only. - Structural (recommended): (A) Volume only
sites, not whole workspace. (B) Bench init in Dockerfile; Ansible runs new-site only. (C) Image = fixed workspace; volume = sites only. (D) Idempotent site creation (check before new-site).
9–12. SRE checklist, priority, conclusion, production structure
- SRE: DB connection vars to new-site; directory ownership (frappe vs root); idempotency and cleanup of partial DB on retry.
- Priority: (1) Immediately inspect
docker inspectMounts; confirm whether/home/frappe/frappe-benchis a host bind (likely root cause). (2) Stabilize: bench init in Dockerfile; sites-only volume. (3) Production: versioned app image; Ansible orchestration only. - Conclusion: Fast fix = use existing bench + fix volume (no whole-workspace mount; sites only). Final: build = bench init in image; run = sites-only volume; provision = new-site only.
- Production Docker (§12): No runtime bench init; image contains complete workspace; persist only sites + logs + assets; never bind-mount over workspace.
Full tables (§1.1, §2.1–§2.2, §3.3–§3.5, §4, §6, §7.1–§7.3, §8, §10.1–§10.3) and exact wording are in the Korean section below.
한국어 {#korean}
Ansible Frappe bench rc:127 해결 기획서
목적: bench new-site 실행 시 반복되는 rc: 127 (No such file or directory) 의 구조적 원인을 정리하고, 단기·중기·장기 해결안을 기획한다.
참조: ansible-implementation-plan.md, ansible-clean-and-reinstall.md, pulumi-ansible-step1-step2-plan.md §2.5(이미지 기반 프로비저닝).
1. 핵심 에러 요약
반복적으로 발생하는 치명적 에러는 다음 한 줄이다.
bash: line 1: /home/frappe/frappe-bench/env/bin/bench: No such file or directoryrc: 127- Exit code 127 = 실행하려 한 바이너리를 찾을 수 없음.
- 즉,
bench new-site로직 자체의 실패가 아니라 bench 바이너리 경로가 잘못되었거나 해당 경로에 파일이 없다는 구조적 문제이다.
1.1 문제 재정의 (단순 경로 오류가 아님)
현재 문제는 “env/bin/bench 경로 오류” 하나가 아니다.
Docker + Frappe Bench 실제 동작을 고려하면 구조적 오해와 설계 리스크가 있으며, 단순 “경로 수정”을 넘어 벤치 초기화 모델 자체가 불안정한 구조이다.
실제 구조적 문제 4가지:
| # | 문제 | 설명 |
|---|---|---|
| 1 | bench 실행 위치와 bench workspace 분리 | CLI(~/.local/bin/bench)는 있는데, workspace(frappe-bench/)가 비어 있음. |
| 2 | Docker volume 마운트와 bench init 충돌 | host 빈 디렉터리를 frappe-bench에 마운트하면, 이미지 내부 bench 결과가 가려짐(overlay). |
| 3 | 벤치 초기화와 사이트 생성의 책임 분리 실패 | runtime에 init + new-site를 연달아 실행하는 설계는 idempotent하지 않고, 실패 시 상태 불명확. |
| 4 | Ansible이 상태를 신뢰하지만 검증하지 않음 | init “changed”만 보고 성공으로 간주하고, workspace가 실제로 채워졌는지 검증하지 않음. |
2. 로그 기반 구조 분석
2.1 디버그 결과 (실제 로그)
[frappe_site] PWD=/home/frappe[frappe_site] BENCH_DIR=/home/frappe/frappe-benchtotal 12drwxr-xr-x 2 root root 4096 Feb 21 15:30 .drwxr-xr-x 1 frappe frappe 4096 Feb 21 15:30 ..[frappe_site] BENCH_BINls: cannot access '/home/frappe/frappe-bench/env/bin/bench': No such file or directory[frappe_site] which bench=/home/frappe/.local/bin/bench[frappe_site] env/bin/bench exists? no2.2 의미 정리
| 항목 | 상태 | 의미 |
|---|---|---|
/home/frappe/frappe-bench | 비어 있음 (. / .. 만 존재) | bench init 결과물이 이 경로에 없음. sites/, config/, env/ 미존재. |
/home/frappe/frappe-bench/env/bin/bench | ❌ 없음 | virtualenv가 이 경로에 생성되지 않았거나, 디렉터리 자체가 없음. |
which bench | /home/frappe/.local/bin/bench | 이미지에서 bench는 user-local pip 설치 형태로 존재. |
| bench init 태스크 | changed | init은 실행되었으나, 마운트된 frappe-bench 디렉터리는 비어 있음. |
3. 구조적 원인 (근본 원인)
3.1 경로 불일치 (Path Mismatch)
- Ansible이 가정한 경로:
/home/frappe/frappe-bench/env/bin/bench(virtualenv 내부 bench). - 실제 사용 가능한 경로:
/home/frappe/.local/bin/bench(전역·user-local bench). - 이 이미지/실행 환경에서는 bench가 virtualenv 기반이 아니라 user-local pip 방식으로 설치되어 있다.
3.2 virtualenv 미생성 / 디렉터리 비어 있음
bench init은 실행되었으나, 마운트된/home/frappe/frappe-bench에는env/,sites/,config/등이 생성되지 않음.- 따라서
env/bin/bench는 “경로만 잘못된 것”이 아니라 해당 디렉터리·파일이 아예 없는 상태이다.
3.3 원인 가능성 (우선순위)
| 가능성 | 설명 |
|---|---|
| 1 (가장 유력) | bench init 시 --skip-redis-config-generation 또는 Python/venv 관련 옵션·환경 문제로 virtualenv 생성 단계가 실패했으나, Ansible은 exit code 0 등으로 성공으로 간주. |
| 2 | bench init 이 다른 경로에 결과를 썼고, 마운트된 /home/frappe/frappe-bench 는 비어 있음 (마운트 포인트가 이미 존재해 init이 별도 경로에 생성했을 수 있음). |
| 3 | 이미지 내부: Python venv 생성 권한 문제, python3-venv 미설치, root로 init 실행 등으로 venv가 생성되지 않음. |
| 4 | pip install --user frappe-bench 형태로만 설치된 환경이라, venv를 만들지 않는 이미지 설계. |
3.4 Docker 관점: Volume이 bench 결과를 덮어씀 (가장 유력)
로그에서 /home/frappe/frappe-bench에 . / .. 만 존재하는 이유로 가장 가능성 높은 시나리오:
- Docker 이미지 안에서 이미 bench init이 완료된 상태일 수 있음.
- 컨테이너 실행 시
-v host_dir:/home/frappe/frappe-bench로 빈 host_dir을 마운트. - 빈 host_dir이 마운트되며 이미지 내부 bench 결과가 가려짐(overlay).
즉, init은 이미지에서 되었지만, 실행 시 host volume이 덮어써서 bench 구조가 사라진다.
이는 Docker에서 흔히 발생하는 실수이며, 가장 먼저 확인할 것은 docker inspect <container> 의 Mounts 항목에서 /home/frappe/frappe-bench가 host bind mount인지 여부이다. 이게 root cause일 가능성이 매우 높다.
3.5 기타 Docker/설계 관점 문제
| 문제 | 설명 |
|---|---|
| Runtime bench init | 컨테이너 기동 후 docker exec bench init → bench new-site 방식은 idempotent하지 않고, race·실패 시 상태 불명확. Frappe bench workspace는 “build artifact”에 가깝고, runtime에서 생성하는 것은 불안정. |
| root vs frappe 권한 | 로그에서 frappe-bench가 root:root 소유. bench는 보통 frappe 유저 기반. 권한 불일치 시 venv/sites 생성 실패·silent failure 후 exit 0 가능. |
| virtualenv 전제 가정 | “env/bin/bench가 항상 존재해야 한다”는 가정 자체가 위험. 최근 Frappe/bench는 global bench + workspace env, poetry, custom image 등 다양한 구조 존재. |
4. Frappe Bench 실제 동작 원리
bench는 두 레이어로 구분된다.
| 레이어 | 경로/역할 | 현재 상태 |
|---|---|---|
| CLI Layer | ~/.local/bin/bench — CLI 실행 파일 | ✅ 존재 |
| Workspace Layer | frappe-bench/ 아래 apps/, sites/, config/, env/ | ❌ 비어 있음 |
즉, CLI만 있고 project workspace는 없는 상태이다.
Workspace가 비어 있으면 PATH 수정으로 127은 제거할 수 있으나, bench new-site 실행 시 “Not a bench directory” 또는 “sites directory not found” 등 다음 에러가 발생할 수 있다.
5. 반복 에러의 본질
- 에러 패턴은 항상 동일: rc: 127, No such file or directory.
- 정리하면 다음 두 가지가 동시에 존재한다.
- 경로 하드코딩: 존재하지 않는
env/bin/bench를 호출. - venv/디렉터리 미생성 또는 Volume overlay:
bench init이 기대한 구조를 해당 경로에 만들지 못했거나, host volume 마운트가 이미지 내 workspace를 덮어썼음.
- 경로 하드코딩: 존재하지 않는
결론: 현재 문제는 단순 “env/bin/bench 경로 오류”가 아니라 Docker volume + runtime bench init 설계 충돌 문제이다.
6. 현재 기획서의 개선 필요 부분
| # | 기획서 내용 | 한계 |
|---|---|---|
| 1 | 단기: PATH/which bench | Workspace가 비어 있으면 127 제거는 가능하지만, 이어서 “Not a bench directory” / “sites directory not found” 발생. 근본 해결 아님. |
| 2 | init 검증: test -f env/bin/bench | env만 보면 불완전. 더 정확한 검증은 test -d sites, test -d apps, test -f Procfile 등 workspace 구조 전체. |
| 3 | Golden Image | Golden Image만으로는 부족. Volume을 그대로 전체 workspace에 마운트하면 동일 문제 반복. sites만 volume화해야 함. |
7. 해결 전략 (단계별)
7.1 단기: 즉시 적용 (경로 수정)
목표: 존재하는 bench 실행 파일을 사용하여 rc:127 제거.
| 방안 | 내용 | 장점 | 비고 |
|---|---|---|---|
| A. 전역 bench + PATH | export PATH=$PATH:/home/frappe/.local/bin, cd /home/frappe/frappe-bench, bench new-site ... | 구현 간단, 127 제거 가능 | frappe-bench 가 비어 있으면 new-site 단계에서 “bench 디렉터리 아님” 등 다른 에러 가능. |
| B. 동적 경로 | 실행 전 which bench(또는 docker exec ... which bench)로 경로를 구하고, 그 경로로 bench 호출. | 경로 하드코딩 제거, 이미지/환경 차이에 강함. | 한 번만 조회해 변수로 재사용하면 됨. |
| C. 단일 문자열 명령 | bench new-site ... 를 bench 로만 호출 (PATH 보정 후). Ansible에서 command 는 문자열 하나로 전달해 exec: "[bash," 같은 오류 방지. | 이전에 발생한 docker exec 리스트 파싱 문제 회피. | 현재 역할에서 이미 문자열 command 사용 중이면 유지. |
권장 (단기): B + C — 컨테이너에서 which bench 로 경로를 한 번 구하고, 해당 경로(또는 PATH 보정 후 bench)로 문자열 command 실행.
이때 옵션은 현재 스펙 유지: DB 분리형이므로 --db-host, --db-port, --db-root-password, --no-mariadb-socket 및 site_name/canonical_hostname.
7.2 중기: bench init 검증 및 venv 보장
목표: init이 실패했거나 venv를 만들지 않았을 때, new-site 전에 감지하고 실패 처리.
| 방안 | 내용 |
|---|---|
| init 옵션 명시 | bench init frappe-bench --python python3 등으로 virtualenv 생성 유도 (이미지·bench 버전에서 지원 시). |
| 검증 태스크 | init 직후 docker exec ... test -f /home/frappe/frappe-bench/env/bin/bench 또는 test -d /home/frappe/frappe-bench/sites 실행. 실패 시 playbook 실패 처리. |
| init 실패 시 로그 | init 태스크의 stdout/stderr를 등록하고, 실패 시 “[frappe_bench] bench init failed: …” 형태로 출력해 원인 파악 가능하게 함. |
권장 (중기): init 직후 검증 태스크 추가. env/bin/bench 뿐 아니라 sites/, apps/, Procfile 등 workspace 구조 전체 확인. 없으면 실패로 두고, init 옵션·이미지·Volume 마운트를 함께 점검.
7.3 장기: 아키텍처 개선 (안정성·멀티테넌트)
목표: runtime bench init 의존도를 낮추고, 경로·환경을 단일하게 만든다.
| 방안 | 내용 | 효과 |
|---|---|---|
| 이미지에서 bench 완성 | Dockerfile 또는 별도 빌드에서 RUN bench init ... 로 bench 완성 상태까지 이미지에 포함. Ansible은 사이트 생성(new-site)·API 키만 수행. | runtime init 제거, 경로·venv 항상 동일. |
| Golden Image | 첫 성공 설치 후 App 서버를 Hetzner 스냅샷/이미지로 저장하고, 이후 프로비저닝 시 그 이미지에서 기동. pulumi-ansible-step1-step2-plan.md §2.5 참고. | 설치 시간 단축, init 실패 가능성 감소. |
| 경로 동적 탐지 | Ansible에서 bench 경로를 하드코딩하지 않고, 컨테이너 내 which bench 또는 env/bin/bench 존재 시 그 경로 사용. | 이미지/버전이 바뀌어도 동일 playbook 유지. |
권장 (장기): 이미지에서 bench 완성 또는 Golden Image 중 하나를 도입하고, Ansible은 orchestration(사이트 생성·API 키·아티팩트) 만 담당하도록 정리.
8. 구조적 개선안 (권장 아키텍처)
| 안 | 내용 | 효과 |
|---|---|---|
| A. Workspace를 Volume 전체가 아니라 sites만 | 현재(추정): -v host_dir:/home/frappe/frappe-bench. 권장: bench workspace는 이미지 내부에 두고, sites만 별도 volume — -v sites_data:/home/frappe/frappe-bench/sites. apps/config/venv는 고정, sites만 영속. | Volume overlay로 workspace가 가려지는 문제 제거. |
| B. Bench init은 Dockerfile에서 수행 | Dockerfile에서 RUN bench init frappe-bench --frappe-branch version-15, WORKDIR /home/frappe/frappe-bench. Ansible에서는 new-site만 실행, init 제거. | runtime init 제거, 가장 안정적. |
| C. 컨테이너는 immutable, Site는 mutable | Image = bench workspace 고정. Container = 실행 환경. Volume = sites만. Ansible = new-site orchestration만. | 책임 분리, drift 제거. |
| D. Idempotent site 생성 | new-site 전에 bench --site <site> list-apps 또는 MariaDB SHOW DATABASES LIKE 'site_db' 로 검증 후 생성. 실패 시 부분 생성(DB만 등) cleanup 옵션. | 재시도·멱등성 보장. |
9. SRE 관점 체크리스트 (현재 적용 가능)
- DB 연결:
--db-host,--db-port,--db-root-password가 인벤토리·play 변수에서 new-site까지 정확히 전달되는지 확인. - 권한:
/home/frappe/frappe-bench가 root 소유이면 frappe 유저로 쓰기 시 실패할 수 있음. init/new-site 실행 사용자와 디렉터리 소유권 일치 확인. - 멱등성: 실패한 new-site 시도로 MariaDB에 DB만 생성됐을 수 있음. 재시도 전 해당 DB 존재 여부 확인 후 필요 시 drop 하고 재실행.
10. 구현 우선순위 요약
10.1 기존 우선순위 (참고)
| 순서 | 구분 | 작업 | 산출물 |
|---|---|---|---|
| 1 | 단기 | frappe_site: 동적 경로(which bench) 또는 PATH 보정 + bench, command 문자열 유지. | roles/frappe_site/tasks/main.yml |
| 2 | 단기 | (선택) frappe_bench: init 직후 sites/apps/Procfile 등 검증, 실패 시 명시적 실패. | roles/frappe_bench/tasks/main.yml |
| 3 | 중기 | bench init 옵션·이미지·Volume 마운트 점검. | 문서화, defaults |
| 4 | 장기 | Dockerfile에서 bench 완성 또는 Golden Image, Ansible은 orchestration만. | 별도 기획 |
10.2 개선된 우선순위 제안
| 단계 | 내용 |
|---|---|
| 1단계 (즉시) | Volume 마운트 구조 점검. docker inspect <container> 의 Mounts에서 /home/frappe/frappe-bench가 host bind mount인지 확인. bench init 결과가 실제로 어디에 생성되는지 확인. init 직후 ls -la 전체 출력 로그 남김. |
| 2단계 (구조 안정화) | Dockerfile에서 bench init. Ansible에서 init 제거. sites만 volume화 (workspace 전체 마운트 제거). |
| 3단계 (Production) | App Image versioned build. DB external(이미 분리형 OK). Site provisioning pipeline 분리. Ansible은 orchestration only. |
10.3 가장 먼저 확인할 것
docker inspect <container>에서 Mounts 항목 확인. /home/frappe/frappe-bench가 host bind mount이면 root cause일 가능성 매우 높음.
11. 결론 및 최종 권장 방향
-
현재 장애의 정확한 원인:
/home/frappe/frappe-bench/env/bin/bench경로가 존재하지 않는 상태. 원인은 (1) 경로 하드코딩 + (2) virtualenv/workspace 미생성 또는 Docker volume이 이미지 내 workspace를 덮어쓴 것.
즉, “env/bin/bench 경로 오류”가 아니라 Docker volume + runtime bench init 설계 충돌 문제. -
가장 빠른 수정 (임시):
Ansible에서 존재하는 bench 사용(PATH 보정 또는 which bench). 동시에 Volume 마운트를 점검하고, 가능하면 workspace 전체 마운트 제거·sites만 volume으로 전환. -
최종 권장 구조:
- Build 단계: bench init 포함한 App Image 생성 (Dockerfile에서 수행).
- Run 단계: sites만 volume (workspace 전체 마운트 금지).
- Provision 단계: bench new-site만 실행 (init 제거).
- Ansible: orchestration 전용. 테넌트 생성은 Provisioner Job으로 분리 권장.
-
Production에서는 bench 경로:
which bench는 예비 수단. 이미지 내부 고정 경로(예:/opt/bench/env/bin/bench)가 더 안전.
12. 완전 안정형 Production Docker 구조 (권장)
12.1 목표
- 컨테이너 기동 시점에 bench init 금지 (runtime init 제거).
- 이미지에 완성된 bench workspace 포함 (apps/, env/, config 고정).
- 영속 데이터는 sites + logs + assets 만 분리.
- “한 번 빌드 → 어디서나 동일 실행” (drift 제거).
12.2 권장 디렉터리/볼륨 설계
- 이미지 내부(Immutable):
/opt/bench(workspace 전체). - 영속 볼륨(Mutable):
/var/lib/frappe/sites,/var/log/frappe,/var/lib/frappe/assets(옵션).
절대 금지:
/opt/bench(또는 workspace 전체)를 bind mount로 덮어쓰기.
빈 디렉터리 마운트가 workspace를 “가려서” 현재와 동일한 장애를 재발시킴.
12.3 컨테이너 역할 분리 (최소)
backend(gunicorn / frappe)nginx(reverse proxy + static)queue-short,queue-long,scheduler(worker)socketio(옵션)job-provisioner(원샷 job: new-site / migrate / install-app 등)
flowchart TB
subgraph Image[Immutable App Image]
W[bench workspace /opt/bench\napps, env, config fixed]
end
subgraph Volumes[Persistent Volumes]
S[sites volume\n/var/lib/frappe/sites]
L[logs volume\n/var/log/frappe]
A[assets volume optional\n/var/lib/frappe/assets]
end
subgraph Runtime[Containers]
N[nginx]
B[backend]
QS[queue-short]
QL[queue-long]
SC[scheduler]
P[job-provisioner one-shot]
end
N --> B
B --> S
QS --> S
QL --> S
SC --> S
P --> S
B --> L
QS --> L
QL --> L
SC --> L
N --> A
12.4 운영 안정성 체크포인트
- read-only rootfs(가능하면): backend/worker는 workspace에 쓰지 못하게.
- 컨테이너는
frappe유저로 실행, sites/logs 볼륨만 write. - healthcheck는 “포트 오픈”이 아니라 “site 접근/DB 연결” 기준.
13. 멀티테넌트 SaaS용 Bench 아키텍처
13.1 핵심 원칙
- 벤치(workspace)는 앱 버전 단위로 고정:
bench@vX.Y. - 테넌트는 site 단위로만 증가:
sites/<tenant-domain>. - 신규 테넌트 생성은 별도 프로비저닝 파이프라인이 수행 (backend 컨테이너가 하지 않음).
- 테넌트 메타데이터는 Control Plane DB에서 관리 (site 존재/상태/버전/리전).
13.2 데이터/메타 모델 (권장)
tenant_id,site_fqdn,db_name,db_host,db_user(권장: per-tenant user),bench_image_tag,status(provisioning|active|failed|migrating|disabled),region,node_pool,created_at.
13.3 요청 흐름 (테넌트 생성)
- User/Signup → Control Plane API → Provision Queue → Provisioner Job → MariaDB(db+user) + Sites Volume → bench new-site + install-apps + migrate → Control Plane status=active. Runtime는 Host 헤더 기반으로 해당 site 서빙.
13.4 프로비저닝 안정화 규칙 (필수)
- Idempotent: 동일 tenant_id 재시도 시 안전하게 재실행.
- 락: tenant 단위 분산락(Redis lock / DB row lock).
- 사전검증: sites에 이미 디렉터리 있으면 상태 확인. DB에 db_name 존재하면 소유/권한 확인.
- 실패정리: 실패 시
status=failed+ 원인 로그. “DB만 생기고 site 없음” 같은 부분 실패 감지 후 cleanup 옵션.
13.5 bench 경로 정책
- 프로비저너는 이미지 내부 고정 경로만 사용 (예:
/opt/bench/env/bin/bench). Production에서는 고정 경로가 더 안전.
14. Hetzner + Pulumi + Ansible 정합 설계
14.1 계층별 책임 분리
- Pulumi (Infra as Code): 네트워크/서버/볼륨/방화벽/로드밸런서/DNS/서브넷/서버그룹 생성.
- Ansible (Config + Orchestration): OS baseline, Docker/Compose 설치, secrets 배치, systemd, 런타임 배포.
- Provisioning Job (앱 레벨):
bench new-site,install-app,migrate,set-config등 테넌트 생성/변경.
Ansible이 테넌트를 직접 만들지 말고, Provisioner Job 실행을 “트리거”하는 쪽이 안정적.
14.2 권장 인프라 구성 (Hetzner)
- DB 노드: 1~3대 (초기 1, 성장 시 Galera/Primary-Replica).
- App 노드: 2대 이상 (롤링/무중단).
- Shared storage: 단순은 각 App 노드 로컬 볼륨 + 테넌트 pinning; 권장은 S3 호환 Object Storage(백업/첨부) + sites 노드별 분산.
- LB: Hetzner Load Balancer + healthcheck. DNS: wildcard
*.example.com→ LB.
14.3 실전 충돌 방지 규칙
- Pulumi가 만든 리소스 식별자(IP/볼륨ID)를 Ansible 인벤토리에 자동 주입 (Pulumi output → inventory.yml 또는 dynamic inventory).
- Ansible은 볼륨 마운트까지 책임, 마운트 지점 표준화:
/var/lib/frappe/sites,/var/log/frappe. - Ansible은 앱 이미지 태그를 변수로 고정 (
app_image_tag=erpnext-v15.XX). 배포/롤백은 태그 교체로만. - Provisioner Job도 동일 이미지 사용 (runtime와 provisioner bench 버전 drift 방지).
14.4 Ansible Role 분해 (권장)
common_base,docker_engine,mount_volumes(sites/logs),app_runtime(compose up),app_provisioner(job 실행 트리거),observability.
14.5 정합 아키텍처 (계층 관계)
flowchart LR
subgraph Pulumi
VPC[Network/Subnet]
FW[Firewall]
LB[Load Balancer]
APP[App Servers xN]
DB[(DB Server/Cluster)]
VOL[Volumes sites/logs]
DNS[DNS Wildcard]
end
subgraph Ansible
BASE[OS baseline users/ssh/ufw]
DOCKER[Docker/Compose]
RUNTIME[Deploy runtime stack]
SECRETS[Secrets + env]
MON[Monitoring/Log shipping]
end
subgraph AppOps
CP[Control Plane API]
Q[Queue]
PROV[Provisioner Job]
end
DNS --> LB --> APP
APP --> DB
APP --> VOL
APP --> BASE --> DOCKER --> RUNTIME
RUNTIME --> SECRETS
CP --> Q --> PROV
PROV --> DB
PROV --> VOL
14.6 배포/프로비저닝 운영 플로우
- Step 1 (Pulumi): infra up → outputs 생성.
- Step 2 (Ansible): 앱 런타임 up (테넌트 없음 상태도 정상).
- Step 3 (Control Plane): 테넌트 생성 요청 → Provisioner Job 실행.
- Step 4 (검증):
/api/method/ping+ site별 헬스체크 + DB/권한 확인. - Step 5 (백업/DR): DB daily logical + binlog; sites는 rsync 스냅샷 또는 object storage sync.
15. 즉시 적용 우선순위 (rc:127 재발 방지에 직결)
- workspace 전체 마운트 제거:
/home/frappe/frappe-bench같은 전체 덮어쓰기 금지. - 이미지 내부 고정 경로로 bench 실행 (프로비저너): bench init 없는 상태에서 new-site만 수행하도록 설계 전환.
- sites만 persistent volume화:
/var/lib/frappe/sites. - Provisioner Job 분리: runtime 컨테이너에서 테넌트 생성 수행 금지.
16. 참조 문서
| 문서 | 용도 |
|---|---|
| ansible-implementation-plan.md | Frappe/bench 설치 방식 및 role 구조 |
| ansible-clean-and-reinstall.md | 초기화 후 재설치 절차 |
| pulumi-ansible-step1-step2-plan.md | §2.5 이미지 기반 프로비저닝, Runbook 리소스 |
| provision-tenant-pipeline.md | Ansible 실행 순서 및 변수 |
이 기획서에 따른 구체적 수정안(role별 task 단위)은 별도 이슈 또는 구현 단계에서 정리한다.