update docs and roadmap files
This commit is contained in:
parent
eb74d041b7
commit
0ac1217730
12 changed files with 18 additions and 875 deletions
|
|
@ -238,6 +238,10 @@ NomadCode
|
|||
|
||||
Portal의 장기 UI 기준은 Flutter 앱이며, 필요한 웹 표면은 Flutter Web 산출물로 제공한다. `apps/control-plane`은 Go 기반 운영 제어 서버다. 주요 통신은 edge-node에서 사용 중인 proto-socket을 IOP Wire Protocol 기준으로 Portal-Control Plane, Control Plane-Edge, Edge-Node 방향으로 확장한다. Portal-Control Plane은 앱/브라우저 경계를 고려해 proto-socket WebSocket/WSS를 우선하고, `net/http`는 health/readiness/bootstrap 같은 보조 endpoint 용도로 유지한다.
|
||||
|
||||
## Guide
|
||||
|
||||
사람이 읽는 최신 실행 가이드는 [Edge-local Dev Guide](docs/edge-local-dev-guide.md) 하나로 유지한다.
|
||||
|
||||
## 로드맵
|
||||
|
||||
제품 방향, 단계, 마일스톤, 우선순위의 단일 기준 문서는 `agent-roadmap/ROADMAP.md`다.
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
- `bin/` — 개발 보조 shell entrypoint와 field binary build entrypoint이다. Edge/Node 운영 UX의 공식 표면은 `iop-edge`/`iop-node` 바이너리 command로 모은다.
|
||||
- `scripts/` — 보조 E2E smoke와 입력 표면 검증 스크립트이다.
|
||||
- `Makefile` — 빌드와 테스트 진입점을 정의한다.
|
||||
- `docs/` — 아키텍처 및 운영 방향 문서이다. field 테스트 환경, 외부 테스트 포트, one-line bootstrap 기준은 `docs/deploy-dev.md`를 우선 진입점으로 본다.
|
||||
- `docs/` — 사람용 최신 가이드만 둔다. 환경별 값은 `agent-ops/rules/private/`에 두고, `rules.md`는 라우터로만 쓴다.
|
||||
|
||||
## 기술 스택
|
||||
|
||||
|
|
@ -57,9 +57,8 @@
|
|||
- 앱 설정 구조 변경 시 `packages/config`의 struct/default와 `configs/*.yaml` 예시를 함께 확인한다.
|
||||
- 테스트는 변경 범위에 맞춰 `go test ./...` 또는 대상 패키지 테스트를 실행한다.
|
||||
- 사용자 실행 파이프라인에 닿는 작업을 한 경우, 작업 완료 후 `agent-ops/rules/project/domain/testing/rules.md`의 검증 기준을 따른다.
|
||||
- field 테스트 환경 또는 one-line bootstrap 작업은 `docs/deploy-dev.md`의 Field 테스트 환경 라우팅과 `docs/field-bootstrap-work-guide.md`를 먼저 확인한다.
|
||||
- field/bootstrap 작업은 `testing` domain rule을 따르고, 실제 환경값이 필요하면 private rule 라우팅을 따른다.
|
||||
- Node, OTO, specialized agent, Control Plane enrollment 등 사용자가 대상 host에서 실행하는 bootstrap/install command 작업은 `agent-ops/rules/project/domain/testing/rules.md`의 one-line bootstrap UX 기준을 따른다.
|
||||
- code-server 기반 field 테스트 포트는 원격 `ssh toki@toki-labs.com`의 `~/docker/services/code-server/compose/docker-compose.yml`에서 관리한다. 기준 host port는 web/dev `13000-13099`, artifact/bootstrap HTTP `18080`, OpenAI-compatible HTTP `18081`, Edge-Node transport `19090`, Edge metrics `19092`, OTO/specialized agent transport `19190`이다.
|
||||
- 상세 DB schema, event schema, permission/policy/audit model, federation, mTLS 구현 세부, Portal UI 세부 기획은 각 작업에서 별도로 결정한다.
|
||||
|
||||
## 도메인 룰 로딩
|
||||
|
|
@ -90,6 +89,6 @@
|
|||
## 스킬 라우팅
|
||||
|
||||
- 사용자 실행 파이프라인 검증, repo 내부 edge-node 진단, 메시지 2회 왕복, edge command 응답, 보조 E2E smoke, full-cycle 실제 구동, `bin/edge.sh`/`bin/node.sh` 진단 테스트: `agent-ops/skills/project/e2e-smoke/SKILL.md`
|
||||
- field 테스트 포트, artifact/bootstrap HTTP, one-line Node bootstrap, code-server compose 기반 외부 테스트 환경: `docs/deploy-dev.md`와 `docs/field-bootstrap-work-guide.md`
|
||||
- bootstrap/install UX, Agent Bootstrap, OTO 등록, Control Plane enrollment처럼 사용자가 대상 host에서 복사해 실행하는 명령을 설계하거나 바꿀 때: `agent-ops/rules/project/domain/testing/rules.md`의 one-line bootstrap UX 기준과 `docs/deploy-dev.md`의 field bootstrap 기준을 확인한다.
|
||||
- field 테스트 포트, artifact/bootstrap HTTP, 외부 테스트 환경: private rule 라우팅을 따른다.
|
||||
- bootstrap/install UX, Agent Bootstrap, OTO 등록, Control Plane enrollment: `testing` domain rule과 private rule 라우팅을 따른다.
|
||||
- 반복 작업이 확인되면 `agent-ops/skills/project/<skill-name>/SKILL.md`를 생성하고 이 표에 등록한다.
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ OTO는 build/deploy domain agent의 대표 후보로 둔다.
|
|||
|
||||
## 작업 컨텍스트
|
||||
|
||||
- 관련 경로: `apps/edge`, `docs/deploy-dev.md`, `docs/architecture.md`, `../oto/agent-roadmap/phase/edge-direct-oto-agent/milestones/oto-agent-registration.md`, `../oto/agent-roadmap/phase/message-based-build-agent/milestones/agent-message-protocol.md`, `../oto/assets/script/shell/oto_agent_bootstrap.sh`
|
||||
- 관련 경로: `apps/edge`, `README.md`, `agent-ops/rules/private/rules.md`, `../oto/agent-roadmap/phase/edge-direct-oto-agent/milestones/oto-agent-registration.md`, `../oto/agent-roadmap/phase/message-based-build-agent/milestones/agent-message-protocol.md`, `../oto/assets/script/shell/oto_agent_bootstrap.sh`
|
||||
- 전환 상태: Ollama 실테스트와 후속 안정화를 우선 열어두는 동안 이 마일스톤은 계획 후보로 대기한다. 구현 시작 전 `enrollment_token` 범위와 HTTP local/insecure bootstrap 정책 결정을 먼저 해소한다.
|
||||
- 표준선(선택): OTO agent는 `iop-node` 하위 프로세스가 아니라 Edge에 직접 outbound 연결하는 specialized domain agent로 둔다.
|
||||
- 표준선(선택): Edge는 `oto-agent` record를 만들고, agent id/alias/enrollment token/release base URL/bootstrap script URL을 포함한 one-line command를 발급한다.
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ Edge는 terminal session broker가 되고, 대상에 도달 가능한 Node가 SS
|
|||
|
||||
## 작업 컨텍스트
|
||||
|
||||
- 관련 경로: `apps/edge`, `apps/node`, `docs/architecture.md`, `docs/deploy-dev.md`
|
||||
- 관련 경로: `apps/edge`, `apps/node`, `README.md`, `agent-ops/rules/private/rules.md`
|
||||
- 표준선(선택): Control Plane/Portal/운영 CLI는 Edge에 terminal session을 요청하고, Edge가 Node terminal transport로 중계한다. OpenAI-compatible/A2A payload에 terminal 제어를 싣지 않는다.
|
||||
- 선행 작업: Edge-Node 실행 스켈레톤, CLI Automation Runtime 안정화
|
||||
- 후속 작업: 정책, 이력, 감사; Control Plane과 Portal의 terminal session 운영 표면
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ Client는 NomadCode 전용 화면이 아니라 IOP가 독립적으로 운영되
|
|||
- [x] [server-bridge] 브라우저 직접 TCP가 어려운 구간은 Control Plane server-side bridge를 둔다.
|
||||
- [x] [portal-wire-hello] Client-Control Plane proto-socket WebSocket hello handshake scaffold가 구현되고 테스트되어 있다. 검증: Control Plane과 client 테스트에서 hello handshake가 통과한다.
|
||||
- [x] [http-boundary] `net/http`는 health/readiness/bootstrap 같은 보조 endpoint 용도로 유지한다.
|
||||
- [x] [deploy-docs] Control Plane과 Flutter client의 배포/검증 절차가 `docs/deploy-dev.md`와 일치한다.
|
||||
- [x] [deploy-docs] Control Plane과 Flutter client의 배포/검증 절차가 `README.md`의 운영 경계와 private local testing rule의 환경 기준에 맞게 정리되어 있다.
|
||||
|
||||
## 완료 리뷰
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ Client는 NomadCode 전용 화면이 아니라 IOP가 독립적으로 운영되
|
|||
|
||||
## 작업 컨텍스트
|
||||
|
||||
- 관련 경로: `apps/control-plane`, `apps/portal`, `apps/control-plane/README.md`, `apps/portal/README.md`, `docs/deploy-dev.md`, `docker-compose.yml`, `bin/web.sh`
|
||||
- 관련 경로: `apps/control-plane`, `apps/portal`, `apps/control-plane/README.md`, `apps/portal/README.md`, `agent-ops/rules/private/rules.md`, `docker-compose.yml`, `bin/web.sh`
|
||||
- 표준선(선택): Control Plane은 Node를 직접 스케줄링하지 않고 Edge를 통해 관찰/제어한다.
|
||||
- 표준선(선택): Client-Control Plane의 첫 wire surface는 기존 `/portal` WebSocket hello handshake를 유지할 수 있으며, Edge 운영 조회와 명령 전달은 후속 IOP Wire Protocol 확장에서 붙인다.
|
||||
- 선행 작업: Flutter-first Portal 마이그레이션, Client Integration Clone Adoption, Agent Bootstrap과 OTO 등록, 정책/이력/감사 일부 기준
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ Multi-edge scheduling과 fleet-wide state ownership은 Control Plane과 IOP nati
|
|||
|
||||
## 작업 컨텍스트
|
||||
|
||||
- 관련 경로: `apps/control-plane`, `apps/portal`, `apps/edge`, `docs/architecture.md`
|
||||
- 관련 경로: `apps/control-plane`, `apps/portal`, `apps/edge`, `README.md`
|
||||
- 표준선(선택): Edge는 로컬 런타임 상태와 registry 소유권을 유지하고, Control Plane은 관찰/집계 계층으로 시작한다.
|
||||
- 선행 작업: Control Plane과 Client, 정책/이력/감사
|
||||
- 후속 작업: 없음
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ Ollama 실테스트에서 안정화한 모델 조회, non-streaming/streaming ch
|
|||
|
||||
## 작업 컨텍스트
|
||||
|
||||
- 관련 경로: `apps/edge`, `apps/node`, `packages/config`, `proto/iop`, `configs`, `bin/edge.sh`, `bin/node.sh`, `docs/deploy-dev.md`
|
||||
- 관련 경로: `apps/edge`, `apps/node`, `packages/config`, `proto/iop`, `configs`, `bin/edge.sh`, `bin/node.sh`, `agent-ops/rules/private/rules.md`
|
||||
- 표준선(선택): 내부 실행 계약은 `adapter + target`을 유지하고, OpenAI-compatible API는 외부 호환 입력 표면으로 둔다.
|
||||
- 표준선(선택): Ollama provider에서 안정화한 field smoke 기준을 SGLang provider에도 그대로 적용한다.
|
||||
- 선행 작업: Ollama 실테스트와 후속 안정화
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ NomadCode는 이 계층의 소비자 중 하나이며, 이 Milestone은 특정 A
|
|||
|
||||
## 작업 컨텍스트
|
||||
|
||||
- 관련 경로: `apps/edge`, `apps/node`, `apps/control-plane`, `packages/policy`, `packages/observability`, `proto/iop`, `docs/architecture.md`, `agent-roadmap/archive/phase/serving-routing-optimization/milestones/model-serving-load-routing.md`
|
||||
- 관련 경로: `apps/edge`, `apps/node`, `apps/control-plane`, `packages/policy`, `packages/observability`, `proto/iop`, `README.md`, `agent-roadmap/archive/phase/serving-routing-optimization/milestones/model-serving-load-routing.md`
|
||||
- 표준선(선택): 기본 모델 serving/load routing이 먼저 안정화된 뒤 최적화 계층을 붙인다.
|
||||
- 선행 작업: Ollama 서빙 안정화, Control Plane과 Client 운영
|
||||
- 후속 작업: 정책/이력/감사와 품질 기반 routing/fallback 고도화
|
||||
|
|
|
|||
|
|
@ -1,189 +0,0 @@
|
|||
# IOP Architecture
|
||||
|
||||
## 개요
|
||||
|
||||
IOP(Inference Operations Platform)는 모델 서빙과 CLI Agent/Automation 실행을 함께 다루는 실행 오케스트레이션 플랫폼을 지향한다. 단순한 OpenAI-compatible proxy나 모델 라우터가 아니라, Control Plane - Edge - Node 계층으로 로컬 실행 그룹을 관리하는 구조를 목표로 한다.
|
||||
|
||||
현재 구현은 Edge-Node 실행 스켈레톤을 검증하는 단계다. Control Plane과 worker는 placeholder이며, 세부 계약은 후속 작업에서 정리한다.
|
||||
제품 Phase와 Milestone 상세는 `agent-roadmap/ROADMAP.md`에서 관리하고, 이 문서는 상대적으로 덜 변하는 구조 원칙과 책임 경계를 설명한다.
|
||||
|
||||
## 계층 구조
|
||||
|
||||
```text
|
||||
Control Plane
|
||||
├─ Edge Group A
|
||||
│ ├─ Node 1
|
||||
│ │ ├─ adapter: cli
|
||||
│ │ └─ adapter: ollama
|
||||
│ ├─ Node 2
|
||||
│ │ └─ adapter: vllm
|
||||
│ └─ OTO Agent
|
||||
│ └─ domain: build/deploy
|
||||
└─ Edge Group B
|
||||
└─ Node 3
|
||||
└─ adapter: cli
|
||||
```
|
||||
|
||||
Control Plane은 Node에 직접 연결하지 않는다. Control Plane은 Edge 연결을 관리하고, Edge 상태를 조회하고, Edge 설정을 조정하고, Edge에 명령을 전달하는 중앙 관리 계층이다.
|
||||
|
||||
Edge는 여러 Node를 묶는 실행 그룹 컨트롤러다. Edge는 Node registry, adapter/profile configuration, routing, stream relay, session handling, event aggregation을 담당한다.
|
||||
|
||||
Node는 실제 실행자다. Node는 Edge에 연결되어 adapter execution을 수행하고, RuntimeEvent를 Edge로 돌려준다. 연결 생명주기와 이후 제어/상태성 이벤트는 RunEvent와 분리된 EdgeNodeEvent envelope로 다룬다.
|
||||
|
||||
Edge는 Node만 관리하는 컨트롤러로 고정하지 않는다. OTO처럼 독립 바이너리와 독립 실행 도메인을 가진 도구는 specialized domain agent로 Edge에 직접 연결할 수 있다. 이 경우 OTO agent는 `iop-node`의 하위 실행물이 아니라 Edge가 별도 agent type으로 등록하고 제어하는 build/deploy 실행자다.
|
||||
|
||||
Agent를 설치하기 어려운 대상은 remote terminal bridge로 다룬다. Edge는 terminal session broker가 되고, 대상에 도달 가능한 Node가 local shell, SSH, WinRM, serial 같은 transport를 열어 stdin/stdout/stderr, resize, signal, session lifecycle을 relay한다. 이 경로는 target device에 IOP agent를 설치하지 않는 운영 방식이지만, 네트워크나 credential 같은 접근 경로 없이 대상을 제어하는 완전 agentless 모델은 아니다.
|
||||
|
||||
## 현재 Edge-Node 흐름
|
||||
|
||||
현재 검증 중인 기본 흐름은 다음과 같다.
|
||||
|
||||
```text
|
||||
Edge
|
||||
└─ accepts Node connection
|
||||
└─ validates token
|
||||
└─ sends NodeConfigPayload
|
||||
└─ relays RunRequest / NodeCommandRequest
|
||||
|
||||
Node
|
||||
└─ receives adapter/runtime config
|
||||
└─ builds adapter registry
|
||||
└─ resolves adapter + target
|
||||
└─ executes adapter
|
||||
└─ emits RuntimeEvent / EdgeNodeEvent
|
||||
|
||||
OTO Agent
|
||||
└─ installed by Edge bootstrap command
|
||||
└─ registers as build/deploy domain agent
|
||||
└─ receives run/cancel/status/capability messages
|
||||
└─ executes YAML pipeline
|
||||
└─ emits step/log/artifact/result events
|
||||
```
|
||||
|
||||
현재 실행 이력의 최소 영속 저장은 Node local SQLite store가 맡는다. 저장 범위는 node가 직접 수행한 run의 상태와 완료/error 정보이며, Edge event aggregation의 durable backend가 아니다. Edge는 `internal/events.Bus`로 `RunEvent`와 `EdgeNodeEvent`를 in-process fanout하고 bounded replay를 제공한다. 이 replay는 운영 표면의 최근 이벤트 관찰용이며 전역 이력/audit 저장소는 후속 Control Plane/Portal 경계에서 별도 설계한다.
|
||||
|
||||
edge-local ops console의 `/` 명령은 ops console의 수동 테스트 표면이다. ops console은 `edge/internal/service`를 호출하는 얇은 어댑터로 유지하고, 이후 HTTP/API 표면(central/remote management surface)도 같은 service와 `edge/internal/events` bus를 재사용한다.
|
||||
|
||||
ops console과 HTTP/API를 구분한다: ops console은 edge-local diagnostic surface이고, HTTP/API는 central/remote management surface다.
|
||||
|
||||
## 내부 실행 모델
|
||||
|
||||
내부 실행은 `adapter + target`으로 표현한다.
|
||||
|
||||
- `adapter`: 실행 방식. 예: `mock`, `ollama`, `vllm`, `cli`
|
||||
- `target`: adapter 안의 실행 대상. 예: 모델 이름, CLI profile, agent/toolchain
|
||||
- `execution`: 특정 adapter와 target으로 수행되는 단일 실행
|
||||
|
||||
외부 OpenAI-compatible API에서는 호환성을 위해 `model` 필드가 남을 수 있다. 그러나 내부 실행 계약에서는 `target`을 우선한다.
|
||||
|
||||
## 외부 API 표면
|
||||
|
||||
Edge의 외부 입력 표면은 두 방식을 병행한다.
|
||||
|
||||
- **OpenAI-compatible HTTP API**: 외부 모델 클라이언트, Cline류 도구, 단순 chat completion/inference 호환을 위한 입력이다. 이 경계에서는 `model`을 받을 수 있지만 Edge 내부에서는 `adapter + target`으로 변환한다.
|
||||
- **A2A JSON-RPC HTTP API**: NomadCode Core나 외부 agent가 작업을 위임하고 `Task` 상태, artifact, cancel/polling을 공유해야 할 때 사용하는 agent delegation 입력이다. 이 경계도 Edge 내부에서는 `adapter + target`으로 변환한다.
|
||||
|
||||
두 입력 방식은 Edge inbound/input surface로 함께 관리하고, 내부 실행은 기존 `RunRequest`/`RunEvent` 흐름으로 수렴시킨다.
|
||||
|
||||
IOP native protocol은 Control Plane, Portal, 운영 CLI, 고급 자동화 클라이언트를 위한 IOP 고유 wire protocol이다. 기존 protobuf-socket 흐름을 기준으로 `RunRequest`, `RunEvent`, `NodeCommandRequest`, `EdgeNodeEvent` 계열 계약을 확장한다. OpenAI-compatible API와 A2A API는 외부 호환 입력으로 유지하지만, node 선택, logical session, background run, terminate-session, capabilities/status/session/transport command, node lifecycle event 같은 운영 기능을 억지로 `model`이나 vendor extension에 싣지 않는다. 이런 기능은 IOP native protocol에서 명시적인 필드와 이벤트로 다룬다.
|
||||
|
||||
## apps/node 내부 구조
|
||||
|
||||
```text
|
||||
Hexagonal Architecture
|
||||
|
||||
┌───────────────────────────────┐
|
||||
│ node.Node │ application core
|
||||
│ │
|
||||
TCP │ transport.Handler interface │
|
||||
─────────► OnRunRequest() │
|
||||
edge │ OnCancel() │
|
||||
│ OnCommandRequest() │
|
||||
└───────┬───────────────────────┘
|
||||
│
|
||||
router.Resolve() -> ExecutionSpec
|
||||
│
|
||||
adapter.Execute() -> RuntimeEvent stream
|
||||
│
|
||||
store.CompleteRun() -> SQLite
|
||||
```
|
||||
|
||||
### 어댑터 패턴
|
||||
|
||||
```go
|
||||
type Adapter interface {
|
||||
Name() string
|
||||
Capabilities(ctx) (Capabilities, error)
|
||||
Execute(ctx, spec ExecutionSpec, sink EventSink) error
|
||||
}
|
||||
```
|
||||
|
||||
새 어댑터를 추가할 때는 `runtime.Adapter`를 구현하고 registry에 등록한다. CLI Agent, 모델 런타임, 도구 실행은 모두 같은 adapter execution 경로로 다루는 방향을 유지한다.
|
||||
|
||||
## 통신 방향
|
||||
|
||||
- Edge-Node 내부 통신은 TCP + protobuf 기반 소켓 흐름을 우선한다.
|
||||
- Node는 Edge에 연결한다.
|
||||
- OTO 같은 domain agent도 Edge에 직접 outbound 연결한다.
|
||||
- Remote terminal bridge는 Control Plane이 Node에 직접 붙는 구조가 아니라, Control Plane/Portal/운영 CLI가 Edge에 terminal session을 요청하고 Edge가 적절한 Node terminal transport로 중계하는 구조로 둔다.
|
||||
- Control Plane은 향후 Edge와 소켓 기반 연결을 맺는다.
|
||||
- Control Plane이 Node에 직접 연결하거나 매 요청마다 Node를 직접 스케줄링하는 구조는 목표가 아니다.
|
||||
- OTO agent는 Control Plane이나 `iop-node`에 직접 종속되지 않고 Edge의 agent registry와 bootstrap/enrollment 계약을 따른다.
|
||||
- Portal-Control Plane, Control Plane-Edge, Edge-Node의 장기 통신 기준은 IOP Wire Protocol(proto-socket)이다. Edge-Node와 Control Plane-Edge의 기본 transport는 TCP 기반 proto-socket으로 유지하고, Portal-Control Plane은 Flutter mobile/desktop과 Flutter Web을 고려해 proto-socket WebSocket/WSS를 우선한다. Flutter Web 브라우저 타깃은 raw TCP가 아니라 WebSocket binary frame 기반 transport를 사용한다.
|
||||
- OpenAI-compatible HTTP API와 A2A JSON-RPC HTTP API는 Edge 외부 입력 표면으로 유지하며, Control Plane 운영 제어 프로토콜의 기본값으로 삼지 않는다.
|
||||
|
||||
## 배포 철학
|
||||
|
||||
Control Plane과 Portal은 중앙 운영면으로 묶어 compose 기반 서비스로 배포한다. Portal의 장기 UI 기준은 Flutter-first 앱이며, compose `web` 서비스는 `apps/portal/Dockerfile` 다단계 빌드로 Flutter Web 산출물을 빌드해 nginx로 정적 서빙한다. dart `proto-socket` path dependency 접근을 위해 build context는 repo parent(`..`)로 두고 dockerfile은 `iop/apps/portal/Dockerfile`로 지정한다. dev 필드 테스트 compose에는 Control Plane DB와 Redis 후보를 함께 포함해 이후 schema, audit, event, queue 작업을 같은 배포 단위 안에서 진행한다.
|
||||
|
||||
Edge와 Node는 Docker 이미지로 만들지 않고 호스트 단일 바이너리로 배포한다. Jenkins는 Edge/Node 바이너리만 빌드하고, 각 호스트는 배포된 바이너리의 `setup` 명령을 통해 systemd 실행 환경을 준비한다. OTO는 별도 프로젝트의 단일 바이너리 build/deploy agent로 두고, 장기적으로는 Edge가 OTO agent 생성과 bootstrap command 발급을 담당한다.
|
||||
|
||||
Edge bootstrap UX는 Jenkins node 연결 방식과 유사하게 둔다. Edge에서 agent를 먼저 만들고 one-time token이 포함된 bootstrap command를 발급하면, 대상 머신이 이 command를 실행해 agent 바이너리 설치, 설정 생성, service 등록, Edge 등록을 완료한다. 폐쇄망과 개발망을 고려해 HTTPS를 권장하되 강제하지 않고, HTTP local/insecure 모드는 one-time token, Edge fingerprint 또는 public key pinning, checksum/signature 검증을 함께 요구한다.
|
||||
|
||||
Remote terminal bridge UX는 bootstrap과 분리한다. 대상에 agent를 설치할 수 있으면 bootstrap/enrollment를 우선하고, 설치가 어렵거나 일회성 유지보수 대상이면 Edge가 사전에 허용된 Node terminal profile을 통해 대상 terminal을 연다. 이 기능은 target allowlist, credential 관리, timeout, 동시 실행 제한, session audit를 전제로 한다.
|
||||
|
||||
운영 CLI는 분기된 설치 방식을 만들지 않는다. Edge 운영 표면은 `iop-edge` command에 모으고, Node 사용자 연결은 Edge가 제시한 bootstrap 명령 하나로 끝나게 한다. 검토나 CI 확인은 별도 `render` 명령이 아니라 `--dry-run` 옵션으로 흡수한다.
|
||||
|
||||
```text
|
||||
build:
|
||||
Jenkins -> bin/build/field-binaries.sh -> iop-edge, iop-node
|
||||
|
||||
control host:
|
||||
docker compose -> postgres, redis, control-plane, web
|
||||
|
||||
edge host:
|
||||
iop-edge setup -> systemd unit + config/data directories [구현 완료]
|
||||
systemd -> iop-edge serve --config /etc/iop/edge.yaml
|
||||
|
||||
node host:
|
||||
edge bootstrap command -> iop-node binary + runtime state
|
||||
foreground/system service -> bootstrap-managed node execution
|
||||
|
||||
oto build host:
|
||||
edge bootstrap command -> oto binary + agent config/service
|
||||
systemd -> oto agent --config /etc/oto/agent.yaml
|
||||
```
|
||||
|
||||
`setup`은 idempotent해야 한다. 기존 설정 파일은 기본적으로 덮어쓰지 않고, 필요한 디렉터리와 unit만 생성/갱신하며, `--enable`, `--start`, `--restart`, `--dry-run` 같은 옵션으로 운영 차이를 표현한다. `status`, `logs`, `start`, `stop`, `restart` 같은 명령은 초기 CLI에 넣지 않고 `systemctl`과 `journalctl`을 기준 운영 도구로 둔다.
|
||||
|
||||
## 라이브러리 선택
|
||||
|
||||
| 라이브러리 | 역할 |
|
||||
|------------|------|
|
||||
| `go.uber.org/fx` | 의존성 주입 |
|
||||
| `go.uber.org/zap` | 구조화 로깅 |
|
||||
| `github.com/spf13/cobra` | CLI |
|
||||
| `github.com/spf13/viper` | 설정 파일 |
|
||||
| `google.golang.org/protobuf` | Protobuf |
|
||||
| `github.com/prometheus/client_golang` | 메트릭 |
|
||||
| `modernc.org/sqlite` | 현재 Node local 실행 이력 저장 |
|
||||
|
||||
## 금지 사항
|
||||
|
||||
- gRPC (`grpc-go`) 사용 금지
|
||||
- Edge-Node 기본 transport를 WebSocket으로 전환 금지. Portal-Control Plane의 proto-socket WebSocket/WSS 사용은 예외로 허용
|
||||
- Actor framework 사용 금지
|
||||
- External worker pool 사용 금지
|
||||
- FSM framework 사용 금지
|
||||
- Plugin framework 사용 금지
|
||||
- Kubernetes 의존성 금지
|
||||
|
|
@ -1,580 +0,0 @@
|
|||
# Dev Field Deployment
|
||||
|
||||
이 문서는 dev 필드 테스트 기준의 배포 단위를 정리한다. Control Plane과 Web은 compose 서비스로 묶고, Edge와 Node는 호스트에서 직접 실행하는 단일 바이너리로 배포한다.
|
||||
|
||||
## 배포 단위
|
||||
|
||||
```text
|
||||
control host
|
||||
docker compose
|
||||
- postgres
|
||||
- redis
|
||||
- control-plane
|
||||
- web
|
||||
|
||||
edge host
|
||||
iop-edge binary
|
||||
edge.yaml
|
||||
|
||||
node host
|
||||
iop-node binary
|
||||
bootstrap-managed runtime state
|
||||
|
||||
oto build host
|
||||
oto binary
|
||||
agent.yaml
|
||||
```
|
||||
|
||||
Edge와 Node는 Docker 이미지로 만들지 않는다. Jenkins는 repo의 shell entrypoint를 호출해 Edge/Node 바이너리 산출물을 만든다.
|
||||
OTO는 별도 프로젝트의 단일 바이너리 산출물로 두고, IOP Edge는 장기적으로 OTO agent를 설치/등록시키는 bootstrap provider 역할을 가진다.
|
||||
Agent 설치가 어렵거나 일회성 유지보수 대상인 host/device는 별도 배포 단위로 보지 않고, 대상에 접근 가능한 Node의 terminal profile을 통해 Edge가 remote terminal bridge session을 중계하는 방향으로 둔다.
|
||||
|
||||
## Edge/Node 바이너리 빌드
|
||||
|
||||
기본 빌드는 현재 호스트의 `GOOS/GOARCH` 대상으로 `edge`, `node`를 모두 만든다.
|
||||
|
||||
```bash
|
||||
bin/build/field-binaries.sh
|
||||
```
|
||||
|
||||
Jenkins에서 Linux amd64/arm64를 함께 만들 때는 다음처럼 호출한다.
|
||||
|
||||
```bash
|
||||
TARGETS="linux/amd64 linux/arm64" \
|
||||
VERSION="${BUILD_TAG}" \
|
||||
COMMIT="${GIT_COMMIT}" \
|
||||
bin/build/field-binaries.sh
|
||||
```
|
||||
|
||||
산출물은 `dist/field/<version>/<goos>-<goarch>/` 아래에 생성된다.
|
||||
|
||||
```text
|
||||
iop-edge
|
||||
iop-edge.sha256
|
||||
iop-node
|
||||
iop-node.sha256
|
||||
SHA256SUMS
|
||||
manifest.env
|
||||
```
|
||||
|
||||
## Control Plane/Web Compose
|
||||
|
||||
루트의 `.env.example`을 참고해 필요한 값만 `.env`로 복사해 조정한다.
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
서비스 포트 기본값은 다음과 같다.
|
||||
|
||||
| Service | Port | Purpose |
|
||||
|---|---:|---|
|
||||
| web | 3000 | Web Portal |
|
||||
| control-plane | 9080 | HTTP health/bootstrap |
|
||||
| control-plane wire | 19080 | IOP wire endpoint |
|
||||
| postgres | 5432 | Control Plane DB |
|
||||
| redis | 6379 | Control Plane cache/queue 후보 |
|
||||
|
||||
현재 Control Plane 구현은 DB/Redis를 아직 사용하지 않지만, dev compose에는 먼저 포함해 이후 schema, queue, audit, event 처리 작업을 같은 배포 단위 안에서 진행할 수 있게 둔다.
|
||||
로컬에서 Control Plane 바이너리를 직접 실행하는 테스트는 외부 `code-server-postgres`의 `iop-control-plane-local` DB와 외부 `code-server-redis`의 Redis DB 1, `iop:control-plane:local:` key prefix를 사용한다. dev compose 환경은 Postgres `iop-control-plane-dev` DB와 Redis DB 2, `iop:control-plane:dev:` key prefix를 사용한다. compose의 `control-plane` 서비스는 `IOP_DATABASE_URL`, `IOP_REDIS_URL`, `IOP_REDIS_KEY_PREFIX` 환경변수로 dev 값을 지정해 `configs/control-plane.yaml`의 로컬 기본값을 덮어쓴다.
|
||||
|
||||
헬스체크는 다음 명령으로 확인한다.
|
||||
|
||||
```bash
|
||||
docker compose ps
|
||||
curl -fsS http://localhost:${IOP_CONTROL_PLANE_HTTP_PORT:-9080}/healthz
|
||||
curl -fsS http://localhost:${IOP_CONTROL_PLANE_HTTP_PORT:-9080}/readyz
|
||||
```
|
||||
|
||||
## Edge 실행
|
||||
|
||||
Jenkins 산출물의 `iop-edge`를 edge host에 배포한 뒤, 초기에는 dev 환경의 `edge.yaml`을 지정해 직접 실행한다.
|
||||
|
||||
```bash
|
||||
./iop-edge serve --config /etc/iop/edge.yaml
|
||||
```
|
||||
|
||||
콘솔 기반 현장 smoke가 필요하면 다음처럼 실행한다.
|
||||
|
||||
```bash
|
||||
./iop-edge console --config /etc/iop/edge.yaml
|
||||
```
|
||||
|
||||
dev 초기 단계에서는 `iop-edge config init` 명령을 실행해 생성한 bundle-local `edge.yaml`을 기준으로 포트와 node token을 맞추며, 이 bundle-local `edge.yaml`이 개발/테스트의 primary source of truth다. repo의 `configs/edge.yaml`은 직접 편집하거나 참조하는 공식 기본 경로가 아니며 단지 로컬 개발 환경용 fallback 또는 예시 템플릿일 뿐이다. 이후 Control Plane enrollment/config sync가 붙으면 edge 설정 파일은 bootstrap 정보만 남기는 방향으로 줄인다.
|
||||
|
||||
## Node 실행
|
||||
|
||||
사용자/field 기본 경로에서 Node는 직접 설정 파일을 만들거나 `iop-node serve --config ...`로 실행하지 않는다.
|
||||
Edge가 제시한 bootstrap 명령을 대상 host에서 실행하면 다운로드, 검증, 연결, 실행까지 이어져야 한다.
|
||||
|
||||
## Field 테스트 환경 라우팅
|
||||
|
||||
field 테스트의 1차 기준 환경은 `ssh toki@toki-labs.com`의 code-server 컨테이너다.
|
||||
컨테이너 compose 원본은 원격 `~/docker/services/code-server/compose/docker-compose.yml`이며, 재기동 타이밍은 운영자가 정한다.
|
||||
이 저장소의 작업자는 compose를 수정할 수 있지만, 사용자가 명시하지 않으면 code-server 컨테이너를 재시작하지 않는다.
|
||||
|
||||
| Host port | Container port | Purpose |
|
||||
|---:|---:|---|
|
||||
| `13000-13099` | `3000-3099` | web/dev app preview range |
|
||||
| `18080` | `18080` | IOP field artifact/bootstrap HTTP |
|
||||
| `18081` | `8080` | Edge OpenAI-compatible HTTP |
|
||||
| `19080` | `19080` | Portal/Control Plane wire test endpoint |
|
||||
| `19090` | `9090` | Edge-Node TCP transport |
|
||||
| `19092` | `9092` | Edge metrics |
|
||||
| `19190` | `9190` | OTO/specialized agent field transport reserve |
|
||||
|
||||
field bootstrap UX는 Jenkins agent 연결처럼 Edge가 command를 제시하고 대상 host가 실행하는 형태를 목표로 한다.
|
||||
현재 field 기준 artifact/bootstrap 외부 주소는 `http://toki-labs.com:18080`이며, Edge-Node 런타임 주소는 `toki-labs.com:19090`이다.
|
||||
초기 field 단계의 Node bootstrap은 다음 요구를 만족해야 한다.
|
||||
|
||||
- Edge 또는 field artifact server가 `dist/field/<version>/<goos>-<goarch>/iop-node`를 정해진 URL로 노출한다.
|
||||
- Apple Silicon Mac은 `darwin/arm64` 산출물을 사용한다. `linux/arm64`는 Mac 실행 대상이 아니다.
|
||||
- 사용자가 실행하는 명령은 한 줄 수준으로 유지하고, 내부에서 다운로드, 실행 권한 부여, 필요한 내부 상태 준비, `iop-node` 실행까지 이어진다.
|
||||
- 사용자가 `node.yaml`을 만들거나 확인하거나 편집하는 흐름은 기본 경로에 두지 않는다.
|
||||
- 직접 포트가 아직 적용되지 않았거나 외부망에서 막히면 SSH tunnel 또는 reverse tunnel은 fallback으로만 사용한다.
|
||||
|
||||
Apple Silicon Node용 field artifact는 다음처럼 만든다. Node token은 artifact에 굽지 않고 사용자 명령의 첫 번째 인자로 전달한다.
|
||||
|
||||
```bash
|
||||
VERSION=<field-version> \
|
||||
APPS=node \
|
||||
TARGETS="darwin/arm64" \
|
||||
ARTIFACT_BASE_URL="http://toki-labs.com:18080" \
|
||||
BOOTSTRAP_EDGE_ADDR="toki-labs.com:19090" \
|
||||
DIST_DIR=dist/field \
|
||||
./bin/build/field-binaries.sh
|
||||
```
|
||||
|
||||
산출물 layout과 artifact URL 규칙은 다음을 기준으로 한다.
|
||||
|
||||
```text
|
||||
dist/field/<field-version>/darwin-arm64/iop-node
|
||||
dist/field/<field-version>/darwin-arm64/SHA256SUMS
|
||||
dist/field/<field-version>/bootstrap/node-darwin-arm64.sh
|
||||
|
||||
http://toki-labs.com:18080/darwin-arm64/iop-node
|
||||
http://toki-labs.com:18080/darwin-arm64/SHA256SUMS
|
||||
http://toki-labs.com:18080/bootstrap/node-darwin-arm64.sh
|
||||
```
|
||||
|
||||
정적 artifact/bootstrap HTTP는 version 디렉터리를 document root로 열어 둔다.
|
||||
|
||||
```bash
|
||||
python3 -m http.server 18080 --bind 0.0.0.0 -d dist/field/<field-version>
|
||||
```
|
||||
|
||||
작업자는 artifact URL을 완성한 뒤 사용자가 token 값 하나만 바꾸는 한 줄 명령을 전달한다.
|
||||
사용자에게 전달하는 명령에는 `<artifact-base-url>` placeholder를 남기지 않는다.
|
||||
|
||||
```bash
|
||||
curl -fsSL http://toki-labs.com:18080/bootstrap/node-darwin-arm64.sh | bash -s CHANGE_ME_FIELD_TOKEN
|
||||
```
|
||||
|
||||
bootstrap 스크립트는 `~/iop-field`를 만들고, `darwin-arm64/iop-node`와 `SHA256SUMS`를 받아 checksum을 확인한 뒤 필요한 내부 상태를 준비하고 `iop-node`를 실행한다.
|
||||
내부 구현이 임시 설정 파일을 쓰더라도 사용자에게 그 파일을 만들거나 관리하게 하지 않는다.
|
||||
첫 번째 인자 `CHANGE_ME_FIELD_TOKEN`은 Edge config의 `nodes[].token`과 같은 값이어야 한다.
|
||||
|
||||
## Field Bootstrap 작업자 테스트 Runbook
|
||||
|
||||
이 절차는 LLM/작업자가 사용자 직접 테스트를 준비하고 실패 지점을 분리할 때 기준으로 삼는다.
|
||||
사용자에게 전달할 짧은 가이드는 `docs/field-bootstrap-user-test.md`를 사용한다.
|
||||
다음 작업 재개와 포트 의미 정리는 `docs/field-bootstrap-work-guide.md`를 먼저 확인한다.
|
||||
토큰과 version 값은 테스트 시점에 맞춰 바꾸고, 기본 `configs/*.yaml`은 수정하지 않는다.
|
||||
|
||||
### 1. code-server 포트 확인
|
||||
|
||||
원격 host에서 code-server 컨테이너의 외부 포트가 실제로 열렸는지 먼저 확인한다.
|
||||
|
||||
```bash
|
||||
ssh toki@toki-labs.com '/usr/local/bin/docker ps --format "{{.Names}} {{.Ports}}" | grep "^code-server "'
|
||||
```
|
||||
|
||||
기대 포트:
|
||||
|
||||
```text
|
||||
18080 artifact/bootstrap HTTP
|
||||
18081 Edge OpenAI-compatible HTTP
|
||||
19090 Edge-Node TCP transport
|
||||
19092 Edge metrics
|
||||
19190 OTO/specialized agent reserve
|
||||
```
|
||||
|
||||
`docker ps`에 포트가 보이지 않으면 compose 파일은 갱신됐지만 컨테이너 recreate가 아직 안 된 상태일 수 있다.
|
||||
컨테이너 recreate 타이밍은 운영자가 정한다.
|
||||
|
||||
### 2. Edge field config 작성
|
||||
|
||||
edge host에서 임시 설정 파일을 만든다.
|
||||
Apple Silicon Mac에서 Ollama를 실행하고 Node도 같은 Mac에서 실행할 경우, Edge가 Node에 내려줄 Ollama base URL은 Node 관점의 `http://127.0.0.1:11434`다.
|
||||
|
||||
```bash
|
||||
cat > /tmp/iop-edge-field-bootstrap.yaml <<'YAML'
|
||||
edge:
|
||||
id: "edge-field-bootstrap"
|
||||
name: "Field Bootstrap Edge"
|
||||
|
||||
server:
|
||||
listen: "0.0.0.0:9090"
|
||||
|
||||
openai:
|
||||
enabled: true
|
||||
listen: "0.0.0.0:8080"
|
||||
node: "node-silicon-ollama"
|
||||
adapter: "ollama"
|
||||
target: ""
|
||||
models:
|
||||
- "gemma4:26b"
|
||||
session_id: "openai-field"
|
||||
timeout_sec: 300
|
||||
strict_output: true
|
||||
strict_stream_buffer: false
|
||||
|
||||
metrics:
|
||||
port: 9092
|
||||
|
||||
nodes:
|
||||
- id: "node-silicon-ollama"
|
||||
alias: "silicon-ollama"
|
||||
token: "CHANGE_ME_FIELD_TOKEN"
|
||||
adapters:
|
||||
ollama:
|
||||
enabled: true
|
||||
base_url: "http://127.0.0.1:11434"
|
||||
context_size: 262144
|
||||
YAML
|
||||
```
|
||||
|
||||
config가 로드되는지 확인한다.
|
||||
|
||||
```bash
|
||||
go run ./apps/edge/cmd/edge config check --config /tmp/iop-edge-field-bootstrap.yaml
|
||||
```
|
||||
|
||||
### 3. Apple Silicon artifact 생성
|
||||
|
||||
repo root에서 Apple Silicon Node artifact와 bootstrap script를 만든다.
|
||||
|
||||
```bash
|
||||
VERSION=field-bootstrap-manual \
|
||||
APPS=node \
|
||||
TARGETS="darwin/arm64" \
|
||||
ARTIFACT_BASE_URL="http://toki-labs.com:18080" \
|
||||
BOOTSTRAP_EDGE_ADDR="toki-labs.com:19090" \
|
||||
DIST_DIR=dist/field \
|
||||
./bin/build/field-binaries.sh
|
||||
```
|
||||
|
||||
기대 파일:
|
||||
|
||||
```bash
|
||||
test -x dist/field/field-bootstrap-manual/darwin-arm64/iop-node
|
||||
test -f dist/field/field-bootstrap-manual/darwin-arm64/SHA256SUMS
|
||||
test -x dist/field/field-bootstrap-manual/bootstrap/node-darwin-arm64.sh
|
||||
```
|
||||
|
||||
### 4. artifact/bootstrap HTTP 시작
|
||||
|
||||
version 디렉터리를 document root로 열어 둔다.
|
||||
|
||||
```bash
|
||||
python3 -m http.server 18080 --bind 0.0.0.0 -d dist/field/field-bootstrap-manual
|
||||
```
|
||||
|
||||
다른 터미널에서 URL이 보이는지 확인한다.
|
||||
|
||||
```bash
|
||||
curl -fsS http://127.0.0.1:18080/darwin-arm64/SHA256SUMS
|
||||
curl -fsS http://127.0.0.1:18080/bootstrap/node-darwin-arm64.sh | sed -n '1,40p'
|
||||
```
|
||||
|
||||
기준 field 환경에서는 Apple Silicon Mac에서도 같은 URL이 보여야 한다.
|
||||
`18080`을 사용할 수 없는 별도 환경에서만 내부망, front/control-plane, CI artifact, 임시 로컬 HTTP 같은 artifact 채널을 사용한다.
|
||||
|
||||
```bash
|
||||
curl -fsS http://toki-labs.com:18080/darwin-arm64/SHA256SUMS
|
||||
```
|
||||
|
||||
### 5. Edge serve 시작
|
||||
|
||||
Edge process를 field config로 실행한다.
|
||||
|
||||
```bash
|
||||
go run ./apps/edge/cmd/edge serve --config /tmp/iop-edge-field-bootstrap.yaml
|
||||
```
|
||||
|
||||
다른 터미널에서 listener를 확인한다.
|
||||
|
||||
```bash
|
||||
curl -fsS http://127.0.0.1:8080/healthz
|
||||
curl -fsS http://127.0.0.1:8080/v1/models
|
||||
```
|
||||
|
||||
`/v1/models`는 Node가 아직 연결되지 않아도 configured model 목록을 반환해야 한다.
|
||||
|
||||
### 6. Apple Silicon Mac에서 one-line bootstrap 실행
|
||||
|
||||
Mac에서 Ollama가 떠 있고 `gemma4:26b`를 사용할 수 있는지 먼저 확인한다.
|
||||
|
||||
```bash
|
||||
ollama list | grep 'gemma4:26b'
|
||||
```
|
||||
|
||||
그 다음 one-line bootstrap을 실행한다.
|
||||
사용자에게 전달할 때는 완성된 URL을 포함하고, token 값만 마지막 positional value로 바꾸게 한다.
|
||||
|
||||
```bash
|
||||
curl -fsSL http://toki-labs.com:18080/bootstrap/node-darwin-arm64.sh | bash -s CHANGE_ME_FIELD_TOKEN
|
||||
```
|
||||
|
||||
성공하면 Mac에 다음 파일이 생기고, Node process가 foreground로 실행된다.
|
||||
|
||||
```text
|
||||
~/iop-field/iop-node
|
||||
```
|
||||
|
||||
Edge 로그에는 `node registered`와 `node-silicon-ollama`가 보여야 한다.
|
||||
|
||||
### 7. Edge에서 모델 호출 확인
|
||||
|
||||
Edge host에서 Node 등록 후 OpenAI-compatible API를 호출한다.
|
||||
|
||||
```bash
|
||||
curl -fsS http://127.0.0.1:8080/v1/models
|
||||
```
|
||||
|
||||
non-streaming:
|
||||
|
||||
```bash
|
||||
curl -fsS http://127.0.0.1:8080/v1/chat/completions \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"model":"gemma4:26b","messages":[{"role":"user","content":"Reply with IOP_FIELD_BOOTSTRAP_OK only."}]}'
|
||||
```
|
||||
|
||||
streaming:
|
||||
|
||||
```bash
|
||||
curl -fsS -N http://127.0.0.1:8080/v1/chat/completions \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"model":"gemma4:26b","stream":true,"messages":[{"role":"user","content":"Reply with IOP_FIELD_STREAM_OK only."}]}'
|
||||
```
|
||||
|
||||
성공 기준:
|
||||
|
||||
- Mac의 bootstrap 명령이 foreground Node process 실행까지 도달한다.
|
||||
- Edge 로그에 `node registered`가 나온다.
|
||||
- `/v1/models`가 `gemma4:26b`를 포함한다.
|
||||
- non-streaming 응답에서 content가 비어 있지 않다.
|
||||
- streaming 응답에서 SSE chunk와 `data: [DONE]`이 보인다.
|
||||
|
||||
### 8. 실패 시 확인 순서
|
||||
|
||||
다운로드 실패:
|
||||
|
||||
```bash
|
||||
curl -v http://toki-labs.com:18080/bootstrap/node-darwin-arm64.sh
|
||||
curl -v http://toki-labs.com:18080/darwin-arm64/iop-node
|
||||
```
|
||||
|
||||
Node가 Edge에 연결하지 못함:
|
||||
|
||||
```bash
|
||||
nc -vz toki-labs.com 19090
|
||||
```
|
||||
|
||||
token 불일치:
|
||||
|
||||
- Edge config의 `nodes[].token` 값과 bootstrap 명령 마지막의 token 인자 값을 맞춘다.
|
||||
|
||||
Ollama 호출 실패:
|
||||
|
||||
```bash
|
||||
curl -fsS http://127.0.0.1:11434/api/tags
|
||||
```
|
||||
|
||||
Apple Silicon Mac에서 Node와 Ollama를 함께 실행하는 경우 Edge config의 `nodes[].adapters.ollama.base_url`은 `http://127.0.0.1:11434`여야 한다.
|
||||
|
||||
### 9. 정리
|
||||
|
||||
Mac의 foreground Node process는 `Ctrl-C`로 종료한다.
|
||||
재실행은 같은 one-line bootstrap command를 다시 실행한다.
|
||||
|
||||
관련 완료 Milestone 기록은 `agent-roadmap/archive/phase/serving-routing-optimization/milestones/field-bootstrap-test-port-readiness.md`에 보존되어 있다.
|
||||
이후 field bootstrap 관련 구현, 문서, 테스트 포트 변경은 새 활성 Milestone 또는 후속 Phase 범위로 판단한다.
|
||||
|
||||
## 공통 Ollama 필드 테스트 환경
|
||||
|
||||
OpenAI-compatible Ollama E2E 서빙 검증은 아래 필드 환경을 공통 기준으로 사용한다.
|
||||
이 환경은 특정 마일스톤에만 두지 않고, 이후 모델 서빙, routing, fallback, client 통합 검증에서도 재사용한다.
|
||||
`scripts/e2e-openai-ollama.sh` 같은 fake Ollama smoke는 보조 확인이다.
|
||||
완료 판정은 `iop-edge` 실행, Edge가 제시한 Node bootstrap 명령, 실제 Ollama endpoint를 연결한 full-cycle 흐름으로 한다.
|
||||
`gemma4:26b`는 thinking을 먼저 stream할 수 있으므로, 기본 검증은 `think:false`로 우회하지 않는다.
|
||||
Edge 응답에서 thinking/reasoning stream과 최종 content stream이 분리되어 보이는지 확인한다.
|
||||
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| Node host | `ssh toki@toki-labs.com` |
|
||||
| Ollama base URL | `http://192.168.0.97:11434` |
|
||||
| 기준 model | `gemma4:26b` |
|
||||
| 권장 node id | `node-toki-labs-ollama` |
|
||||
| 권장 node alias | `toki-labs-ollama` |
|
||||
|
||||
기본 `configs/*.yaml`은 필드 테스트 값으로 덮어쓰지 않는다.
|
||||
edge와 node는 임시 config 또는 `/etc/iop/*.yaml` 필드 설정을 사용한다.
|
||||
Node가 사용할 Ollama endpoint는 node config가 아니라 edge config의 `nodes[].adapters.ollama.base_url`에 둔다.
|
||||
Node는 registration 이후 Edge가 내려주는 adapter config로 Ollama를 호출한다.
|
||||
|
||||
edge host에서 임시 config를 만들 때의 기준은 다음과 같다.
|
||||
`CHANGE_ME_FIELD_TOKEN`과 `<edge-host>`는 테스트 시점의 값으로 바꾼다.
|
||||
`openai.target`은 비워 두면 요청의 `model`을 내부 target으로 사용하고, `gemma4:26b`로 고정하면 외부 요청 model과 무관하게 해당 model로만 호출한다.
|
||||
Node는 Edge로 outbound 연결하므로 `ssh toki@toki-labs.com` 환경에서 `<edge-host>:9090`에 접근할 수 있어야 한다.
|
||||
로컬 개발 머신이 원격 host에서 직접 보이지 않으면 Edge를 접근 가능한 host에서 실행하거나 터널을 구성한다.
|
||||
|
||||
```yaml
|
||||
edge:
|
||||
id: "edge-toki-labs-field"
|
||||
name: "Toki Labs Field Edge"
|
||||
|
||||
server:
|
||||
listen: "0.0.0.0:9090"
|
||||
|
||||
openai:
|
||||
enabled: true
|
||||
listen: "0.0.0.0:8080"
|
||||
node: "node-toki-labs-ollama"
|
||||
adapter: "ollama"
|
||||
target: ""
|
||||
models:
|
||||
- "gemma4:26b"
|
||||
session_id: "openai-field"
|
||||
timeout_sec: 300
|
||||
strict_output: true
|
||||
strict_stream_buffer: false
|
||||
|
||||
nodes:
|
||||
- id: "node-toki-labs-ollama"
|
||||
alias: "toki-labs-ollama"
|
||||
token: "CHANGE_ME_FIELD_TOKEN"
|
||||
adapters:
|
||||
ollama:
|
||||
enabled: true
|
||||
base_url: "http://192.168.0.97:11434"
|
||||
context_size: 262144
|
||||
```
|
||||
|
||||
edge 실행:
|
||||
|
||||
```bash
|
||||
iop-edge serve --config /tmp/iop-edge-toki-labs.yaml
|
||||
```
|
||||
|
||||
node host에서는 별도 Node config를 만들지 않는다. Edge가 제시한 bootstrap 명령만 실행한다.
|
||||
|
||||
```bash
|
||||
ssh toki@toki-labs.com
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -fsSL http://toki-labs.com:18080/bootstrap/node-darwin-arm64.sh | bash -s CHANGE_ME_FIELD_TOKEN
|
||||
```
|
||||
|
||||
edge host에서 OpenAI-compatible API를 호출해 검증한다.
|
||||
|
||||
```bash
|
||||
curl -fsS http://127.0.0.1:8080/v1/models
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -fsS http://127.0.0.1:8080/v1/chat/completions \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"model":"gemma4:26b","messages":[{"role":"user","content":"Reply with IOP_OLLAMA_E2E_OK only."}]}'
|
||||
```
|
||||
|
||||
streaming 검증:
|
||||
|
||||
```bash
|
||||
curl -fsS -N http://127.0.0.1:8080/v1/chat/completions \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"model":"gemma4:26b","stream":true,"messages":[{"role":"user","content":"Reply with IOP_OLLAMA_STREAM_OK only."}]}'
|
||||
```
|
||||
|
||||
완료 기준은 사용자가 재현하는 실제 절차에서 node registration, `/v1/models` 응답, non-streaming chat completion, streaming SSE chunk와 `data: [DONE]` 확인이다.
|
||||
`gemma4:26b`가 thinking을 내보내는 경우 thinking/reasoning chunk가 content와 섞이지 않고, content가 시작된 뒤 답변 stream이 이어지는지도 확인한다.
|
||||
Edge/Node 뒤의 소켓 relay가 사용자 UX에 드러나지 않아야 하며, 실패 시 edge와 node 양쪽 로그를 함께 확인한다.
|
||||
|
||||
## Agent Bootstrap / Bridge References
|
||||
|
||||
OTO 같은 specialized domain agent의 bootstrap/enrollment 상세 계획은 `agent-roadmap/milestones/agent-bootstrap-oto-enrollment.md`에서 관리한다.
|
||||
Agent 설치가 어렵거나 임시 제어만 필요한 대상의 remote terminal bridge 계획은 `agent-roadmap/milestones/remote-terminal-bridge-poc.md`에서 관리한다.
|
||||
|
||||
이 배포 문서에서는 현재 dev field 기준의 배포 단위와 smoke 절차만 유지한다.
|
||||
|
||||
## Field Smoke
|
||||
|
||||
최소 확인 순서는 다음을 기준으로 한다.
|
||||
|
||||
1. `docker compose ps`에서 `postgres`, `redis`, `control-plane`, `web`이 healthy/running인지 확인한다.
|
||||
2. edge host에서 `iop-edge console --config /etc/iop/edge.yaml`을 실행한다.
|
||||
3. node host에서 Edge가 제시한 bootstrap 명령을 실행한다.
|
||||
4. edge console에서 `/nodes`로 node 등록을 확인한다.
|
||||
5. 메시지 2회를 보내고 각 요청의 start, message, complete event가 edge console에 표시되는지 확인한다.
|
||||
6. `/capabilities`, `/transport`, `/sessions`를 실행해 command 응답을 확인한다.
|
||||
|
||||
보조 자동 smoke는 repo root에서 다음 명령으로 실행한다.
|
||||
|
||||
```bash
|
||||
make test-e2e
|
||||
```
|
||||
|
||||
이 명령은 보조 확인이며, 필드 배포 완료 기준은 실제 edge/node 바이너리 실행 흐름 확인이다.
|
||||
|
||||
## Edge setup CLI
|
||||
|
||||
Edge 바이너리는 호스트 환경 준비를 `setup` 명령으로 일원화한다. 공식 운영 경로는 하나다.
|
||||
|
||||
```bash
|
||||
sudo iop-edge setup --enable --start
|
||||
```
|
||||
|
||||
배포 전에 결과를 검토하려면 `--dry-run`을 쓴다.
|
||||
|
||||
```bash
|
||||
sudo iop-edge setup --dry-run --binary /usr/local/bin/iop-edge
|
||||
```
|
||||
|
||||
`setup`은 다음을 담당한다.
|
||||
|
||||
- 실행 user/group(`iop`) 준비
|
||||
- `/etc/iop`와 `/var/lib/iop/edge` 디렉터리 준비
|
||||
- 설정 파일이 없을 때만 기본 템플릿 생성 (`--overwrite-config`로 강제 갱신)
|
||||
- systemd unit 생성 또는 갱신 (`/etc/systemd/system/iop-edge.service`)
|
||||
- `systemctl daemon-reload`
|
||||
- 옵션에 따른 `--enable`, `--start`, `--restart`
|
||||
|
||||
`setup`의 `--config` 기본값은 `/etc/iop/edge.yaml`이다. dev 단계 명령(`serve`, `console`, `config init/print/check`, `env`, `node register`)의 root persistent `--config` 기본값은 bundle-local `edge.yaml` (없을 시 executable-adjacent `edge.yaml`, 그 다음 repo dev fallback `configs/edge.yaml`)로 확인된다.
|
||||
|
||||
별도 `render`, `service install`, `service status` 명령은 초기 범위에 넣지 않는다. 검토와 CI 확인은 `setup --dry-run`으로 흡수하고, 상태/로그/재시작은 `systemctl`과 `journalctl`을 기준 운영 도구로 둔다.
|
||||
|
||||
현재 구현된 edge CLI 표면은 다음과 같다.
|
||||
|
||||
```text
|
||||
iop-edge serve
|
||||
iop-edge console
|
||||
iop-edge setup
|
||||
iop-edge env
|
||||
iop-edge config init
|
||||
iop-edge config print
|
||||
iop-edge config check
|
||||
iop-edge node register
|
||||
iop-edge nodes list
|
||||
iop-edge smoke openai
|
||||
iop-edge version
|
||||
```
|
||||
|
||||
`setup`은 여러 번 실행해도 같은 결과로 수렴한다. 기존 설정 파일은 기본적으로 덮어쓰지 않고, 강제 갱신이 필요하면 `--overwrite-config`를 지정한다.
|
||||
|
||||
## Node host setup note
|
||||
|
||||
Node setup/config CLI는 현재 구현에 남아 있는 저수준 보조 표면이다.
|
||||
field 사용자 기본 경로와 다음 개발 방향에서는 Node setup/config를 안내하지 않는다.
|
||||
Control Plane 없는 개발 단계에서도 Node별 실행 설정은 Edge config의 `nodes[]`에서 관리하고, Node host에서는 Edge가 제시한 bootstrap 명령만 실행한다.
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# Edge-local Dev 테스트 세팅
|
||||
# Edge-local Dev Guide
|
||||
|
||||
이 문서는 Control Plane 없이 로컬 또는 개발 서버에서 Edge/Node bundle을 빠르게 세팅하고 확인하는 사용자용 흐름이다.
|
||||
사용자는 `iop-edge`를 중심으로 진행한다.
|
||||
|
|
@ -93,7 +93,7 @@ Ollama Node로 등록할 때:
|
|||
출력 예:
|
||||
|
||||
```bash
|
||||
curl -fsSL http://toki-labs.com:18080/bootstrap/node-darwin-arm64.sh | bash -s <generated-token>
|
||||
curl -fsSL <bootstrap-url> | bash -s <generated-token>
|
||||
```
|
||||
|
||||
사용자는 이 한 줄만 Node host에서 실행한다.
|
||||
|
|
@ -132,7 +132,7 @@ tail -f logs/edge.log
|
|||
`iop-edge node register ...`가 출력한 한 줄을 Node host에서 그대로 실행한다.
|
||||
|
||||
```bash
|
||||
curl -fsSL http://toki-labs.com:18080/bootstrap/node-darwin-arm64.sh | bash -s <generated-token>
|
||||
curl -fsSL <bootstrap-url> | bash -s <generated-token>
|
||||
```
|
||||
|
||||
Node host 사용자는 `node.yaml`을 만들거나 편집하지 않는다.
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
# Field Bootstrap 작업 가이드
|
||||
|
||||
이 문서는 작업자/LLM이 field bootstrap 테스트를 이어갈 때 읽는 재개 가이드다.
|
||||
사용자에게 그대로 전달하는 문서는 `docs/field-bootstrap-user-test.md`를 사용하고, 상세 runbook은 `docs/deploy-dev.md`를 확인한다.
|
||||
|
||||
## 핵심 규칙
|
||||
|
||||
- Edge-Node 런타임 연결에 외부 공개가 필요한 포트는 `19090` 하나다.
|
||||
- `18080`은 bootstrap shell script, `iop-node` binary, checksum을 내려받는 artifact HTTP 포트다. Edge-Node 런타임 포트가 아니다.
|
||||
- 현재 field 기준 bootstrap/artifact 외부 주소는 `http://toki-labs.com:18080`이다.
|
||||
- bootstrap이 사용하는 Edge runtime 주소는 `toki-labs.com:19090`으로 맞춘다.
|
||||
- Cline/OpenAI-compatible 외부 주소는 `http://toki-labs.com:18081/v1`이다.
|
||||
- 사용자에게 보이는 bootstrap 명령은 완성된 URL과 token positional value 하나만 포함한다. `IOP_*=` named parameter를 사용자 기본 경로에 두지 않는다.
|
||||
- 사용자에게 `node.yaml` 생성, 확인, 편집, `iop-node serve --config ...` 실행을 요구하지 않는다. bootstrap 내부 상태 파일은 구현 세부로만 취급한다.
|
||||
|
||||
## 완료 테스트 스냅샷
|
||||
|
||||
- live version: `field-live-20260527-19ddd9bd38b7`
|
||||
- node id: `node-silicon-ollama`
|
||||
- model: `gemma4:26b`
|
||||
- edge config 후보: `/tmp/iop-edge-field-bootstrap.yaml`
|
||||
|
||||
위 값은 완료된 field 테스트의 임시 값이다. token은 테스트마다 새로 발급하고 문서값을 재사용하지 않는다.
|
||||
|
||||
## 재개 전 확인
|
||||
|
||||
stale process가 남아 있는지 먼저 본다.
|
||||
|
||||
```bash
|
||||
ps -ef | rg 'python3 -m http.server 18080|go run ./apps/edge/cmd/edge|/tmp/go-build.*/edge serve|iop-edge serve'
|
||||
netstat -ltnp 2>/dev/null | rg ':(18080|8080|9090)\b|Proto' || true
|
||||
```
|
||||
|
||||
이전 테스트 프로세스가 남아 있으면 정리한 뒤 새 설정으로 띄운다.
|
||||
|
||||
## 권장 테스트 흐름
|
||||
|
||||
Apple Silicon bootstrap artifact를 만들 때 artifact URL과 edge runtime 주소를 분리한다.
|
||||
|
||||
```bash
|
||||
VERSION=field-live-20260527-19ddd9bd38b7 \
|
||||
APPS=node \
|
||||
TARGETS="darwin/arm64" \
|
||||
ARTIFACT_BASE_URL="http://toki-labs.com:18080" \
|
||||
BOOTSTRAP_EDGE_ADDR="toki-labs.com:19090" \
|
||||
DIST_DIR=dist/field \
|
||||
./bin/build/field-binaries.sh
|
||||
```
|
||||
|
||||
artifact HTTP가 필요하면 `18080`으로 연다.
|
||||
|
||||
```bash
|
||||
python3 -m http.server 18080 --bind 0.0.0.0 -d dist/field/field-live-20260527-19ddd9bd38b7
|
||||
```
|
||||
|
||||
Edge는 field config로 실행한다.
|
||||
|
||||
```bash
|
||||
go run ./apps/edge/cmd/edge serve --config /tmp/iop-edge-field-bootstrap.yaml
|
||||
```
|
||||
|
||||
기본 확인:
|
||||
|
||||
```bash
|
||||
curl -fsS http://127.0.0.1:8080/healthz
|
||||
curl -fsS http://127.0.0.1:8080/v1/models
|
||||
nc -vz toki-labs.com 19090
|
||||
```
|
||||
|
||||
사용자에게 전달하는 bootstrap 명령의 URL은 artifact 전달 채널에 맞춰 작업자가 완성한다.
|
||||
사용자 명령은 token 값 하나만 인자로 받는다. 사용자에게 `<artifact-base-url>` 같은 placeholder를 채우게 하지 않는다.
|
||||
bootstrap script 안의 기본 Edge runtime 주소는 `19090`으로 들어가야 한다.
|
||||
|
||||
```bash
|
||||
curl -fsSL http://toki-labs.com:18080/bootstrap/node-darwin-arm64.sh | bash -s <token>
|
||||
```
|
||||
|
||||
## 성공 기준
|
||||
|
||||
- Node host에서 `toki-labs.com:19090`에 연결할 수 있다.
|
||||
- Edge 로그에 `node registered`가 나온다.
|
||||
- `/v1/models`가 `gemma4:26b`를 반환한다.
|
||||
- OpenAI-compatible 호출에서 비어 있지 않은 응답 또는 SSE delta가 나온다.
|
||||
|
||||
## 주의
|
||||
|
||||
- field 기본 경로에서는 `curl http://toki-labs.com:18080/... | bash -s <token>` 형태를 사용한다.
|
||||
- `18080`을 사용할 수 없는 별도 환경에서만 내부망, front/control-plane, CI artifact, 임시 로컬 HTTP 같은 artifact 전달 채널로 바꾼다.
|
||||
- Cline 전용 감지나 provider별 분기는 만들지 않는다. OpenAI-compatible protocol 차원에서 안정화한다.
|
||||
- 기본 `configs/*.yaml`에 field token이나 임시 host 값을 넣지 않는다.
|
||||
- 테스트 후에는 임시 edge와 artifact server를 내려둔다.
|
||||
Loading…
Reference in a new issue