189 lines
12 KiB
Markdown
189 lines
12 KiB
Markdown
# 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-ops/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는 분기된 설치 방식을 만들지 않는다. 공식 경로는 `iop-edge setup`과 `iop-node setup` 하나로 고정하고, 검토나 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:
|
|
iop-node setup -> systemd unit + config/data directories [구현 완료]
|
|
systemd -> iop-node serve --config /etc/iop/node.yaml
|
|
|
|
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 의존성 금지
|