iop/docs/deploy-dev.md

12 KiB

Dev Field Deployment

이 문서는 dev 필드 테스트 기준의 배포 단위를 정리한다. Control Plane과 Web은 compose 서비스로 묶고, Edge와 Node는 호스트에서 직접 실행하는 단일 바이너리로 배포한다.

배포 단위

control host
  docker compose
    - postgres
    - redis
    - control-plane
    - web

edge host
  iop-edge binary
  edge.yaml

node host
  iop-node binary
  node.yaml

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를 모두 만든다.

bin/build/field-binaries.sh

Jenkins에서 Linux amd64/arm64를 함께 만들 때는 다음처럼 호출한다.

TARGETS="linux/amd64 linux/arm64" \
VERSION="${BUILD_TAG}" \
COMMIT="${GIT_COMMIT}" \
bin/build/field-binaries.sh

산출물은 dist/field/<version>/<goos>-<goarch>/ 아래에 생성된다.

iop-edge
iop-edge.sha256
iop-node
iop-node.sha256
SHA256SUMS
manifest.env

Control Plane/Web Compose

루트의 .env.example을 참고해 필요한 값만 .env로 복사해 조정한다.

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-postgresiop-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의 로컬 기본값을 덮어쓴다.

헬스체크는 다음 명령으로 확인한다.

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을 지정해 직접 실행한다.

./iop-edge serve --config /etc/iop/edge.yaml

콘솔 기반 현장 smoke가 필요하면 다음처럼 실행한다.

./iop-edge console --config /etc/iop/edge.yaml

dev 초기 단계에서는 repo의 configs/edge.yaml을 기준으로 포트와 node token을 맞춘다. 이후 Control Plane enrollment/config sync가 붙으면 edge 설정 파일은 bootstrap 정보만 남기는 방향으로 줄인다.

Node 실행

Jenkins 산출물의 iop-node를 node host에 배포한 뒤, 초기에는 edge 주소와 token이 들어 있는 node.yaml로 직접 실행한다.

./iop-node serve --config /etc/iop/node.yaml

repo root에서 수동으로 검증할 때는 기존 helper를 사용할 수 있다.

IOP_EDGE_ADDR=<edge-host>:9090 ./bin/node.sh

공통 Ollama 필드 테스트 환경

OpenAI-compatible Ollama E2E 서빙 검증은 아래 필드 환경을 공통 기준으로 사용한다. 이 환경은 특정 마일스톤에만 두지 않고, 이후 모델 서빙, routing, fallback, client 통합 검증에서도 재사용한다. scripts/e2e-openai-ollama.sh 같은 fake Ollama smoke는 보조 확인이다. 완료 판정은 사용자가 직접 테스트하는 방식과 동일하게 bin/edge.sh, bin/node.sh, 실제 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에서 실행하거나 터널을 구성한다.

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

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 실행:

IOP_EDGE_CONFIG=/tmp/iop-edge-toki-labs.yaml ./bin/edge.sh

node host에 접속한 뒤 node 임시 config를 만든다.

ssh toki@toki-labs.com
transport:
  edge_addr: "<edge-host>:9090"
  token: "CHANGE_ME_FIELD_TOKEN"

logging:
  level: "info"
  pretty: true

metrics:
  port: 9091

node 실행:

IOP_NODE_CONFIG=/tmp/iop-node-toki-labs.yaml ./bin/node.sh

edge host에서 OpenAI-compatible API를 호출해 검증한다.

curl -fsS http://127.0.0.1:8080/v1/models
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 검증:

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-ops/roadmap/milestones/agent-bootstrap-oto-enrollment.md에서 관리한다. Agent 설치가 어렵거나 임시 제어만 필요한 대상의 remote terminal bridge 계획은 agent-ops/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에서 iop-node serve --config /etc/iop/node.yaml을 실행한다.
  4. edge console에서 /nodes로 node 등록을 확인한다.
  5. 메시지 2회를 보내고 각 요청의 start, message, complete event가 edge console에 표시되는지 확인한다.
  6. /capabilities, /transport, /sessions를 실행해 command 응답을 확인한다.

보조 자동 smoke는 repo root에서 다음 명령으로 실행한다.

make test-e2e

이 명령은 보조 확인이며, 필드 배포 완료 기준은 실제 edge/node 바이너리 실행 흐름 확인이다.

Edge setup CLI

Edge 바이너리는 호스트 환경 준비를 setup 명령으로 일원화한다. 공식 운영 경로는 하나다.

sudo iop-edge setup --enable --start

배포 전에 결과를 검토하려면 --dry-run을 쓴다.

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 print/check)의 root persistent --config 기본값(configs/edge.yaml)과는 다르다.

별도 render, service install, service status 명령은 초기 범위에 넣지 않는다. 검토와 CI 확인은 setup --dry-run으로 흡수하고, 상태/로그/재시작은 systemctljournalctl을 기준 운영 도구로 둔다.

현재 구현된 edge CLI 표면은 다음과 같다.

iop-edge serve
iop-edge console
iop-edge setup
iop-edge config print
iop-edge config check
iop-edge version

setup은 여러 번 실행해도 같은 결과로 수렴한다. 기존 설정 파일은 기본적으로 덮어쓰지 않고, 강제 갱신이 필요하면 --overwrite-config를 지정한다.

Node setup CLI

Node 바이너리는 호스트 환경 준비를 setup 명령으로 일원화한다. 공식 운영 경로는 하나다.

sudo iop-node setup --enable --start

배포 전에 결과를 검토하려면 --dry-run을 쓴다.

sudo iop-node setup --dry-run --binary /usr/local/bin/iop-node

setup은 다음을 담당한다.

  • 실행 user/group(iop) 준비
  • /etc/iop/var/lib/iop/node 디렉터리 준비
  • 설정 파일이 없을 때만 기본 템플릿 생성 (--overwrite-config로 강제 갱신)
  • systemd unit 생성 또는 갱신 (/etc/systemd/system/iop-node.service)
  • systemctl daemon-reload
  • 옵션에 따른 --enable, --start, --restart

setup--config 기본값은 /etc/iop/node.yaml이다. dev 단계 명령(serve, config print/check)의 root persistent --config 기본값(configs/node.yaml)과는 다르다.

별도 render, service install, service status 명령은 초기 범위에 넣지 않는다. 검토와 CI 확인은 setup --dry-run으로 흡수하고, 상태/로그/재시작은 systemctljournalctl을 기준 운영 도구로 둔다.

현재 구현된 node CLI 표면은 다음과 같다.

iop-node serve
iop-node setup
iop-node config print
iop-node config check
iop-node version

setup은 여러 번 실행해도 같은 결과로 수렴한다. 기존 설정 파일은 기본적으로 덮어쓰지 않고, 강제 갱신이 필요하면 --overwrite-config를 지정한다.