명령·정책·보존
중앙에서 장비로 명령을 보내고, 정책을 바꾸고, 오프라인 동안 쌓인 데이터가 동기화되는 방식
필요 권한: 운영OPERATOR · ADMIN
명령
중앙은 공장 NAT 뒤의 장비에 먼저 접속할 수 없으므로, 장비가 commands_poll_s(기본 25초) 동안 기다리는 롱폴로 명령을 가져갑니다. 장비는 처리한 결과를 반드시 보고(ack)하므로, 화면에서 명령이 대기 중 → 전달됨 → 완료/실패 로 바뀌는 것을 볼 수 있습니다. 명령을 보내려면 운영OPERATOR · ADMIN 권한이 필요합니다.
- 디바이스 상세의 명령 탭을 엽니다. ① 명령 종류를 고르고 ② 명령 보내기 를 누릅니다.
명령 탭: ① 명령 종류 ② 명령 보내기 ③ 실패한 명령은 장비가 보고한 오류와 함께 남는다 - 장비가 다음 폴링에서 가져가 처리하고, 결과가 결과 열에 남습니다. ③ 실패한 명령은 장비가 보고한 오류가 그대로 보입니다(이 예에서는 아티팩트가 없는 모델 버전을 받으려다 404).
| 명령 | 화면 이름 | 장비가 하는 일 | 결과 예 |
|---|---|---|---|
ping | 핑 | 아무것도 바꾸지 않고 답함 | {"pong": true, "device_id": "edge-bench-01"} |
restart | 재시작 | 에이전트를 종료 코드 3 으로 끝냄. 감시자가 다시 띄움 | {"restarting": true} |
resync | 재동기화 | failed 가 된 큐 항목을 다시 대기 상태로 돌리고 바로 전송 | {"requeued": 0} |
pull_model | 모델 내려받기 | 인자 {name, version, activate} 의 모델을 받아 활성화 | {"name": …, "activated": true} |
set_policy | (정책 저장 시 자동) | 새 정책을 적용 | {"applied": true} |
명령은 기본 24시간 안에 가져가지 않으면 만료 됩니다. 에이전트가 도는 동안 같은 명령이 다시 전달돼도 한 번만 실행합니다.
재시작은 종료 코드 3
에이전트는 스스로 다시 실행하지 않습니다. 문제가 있다고 본 프로세스가 스스로를 고치게 두지 않으려는 것입니다. restart 를 받으면 하던 전송을 정리하고 종료 코드 3 으로 끝나며, systemd(Restart=always)나 컨테이너 런타임(restart: unless-stopped)이 깨끗한 새 프로세스를 띄웁니다. 디바이스 상세 오른쪽 위 재시작 버튼도 같은 명령을 보냅니다.
실제로 확인한 결과:
WARNING geo_mlops_sdk.edge.runtime: restart requested: command 1a1d2559-…
$ echo $?
3
| 종료 코드 | 뜻 |
|---|---|
0 | 요청받아 정상 종료(SIGTERM·Ctrl-C) |
3 | 재시작 요청. 감시자가 다시 띄워야 함 |
4 | 로컬 API 를 열지 못함(포트 사용 중 등). 다시 띄워도 소용없는 설정 문제 |
Offline 차단
디바이스 상세의 Offline 버튼은 명령이 아니라 서버 쪽 스위치입니다. 켜 두는 동안 그 장비의 토큰 호출은 모두 503(Retry-After: 60)으로 거절됩니다. 장비 입장에서는 중앙이 잠시 사라진 것과 같습니다. 보내지 못한 데이터를 큐에 그대로 두고 기다리다가, 차단을 풀면 쌓인 데이터를 올려 보냅니다. 토큰 폐기와 달리 화면에서 바로 되돌릴 수 있습니다.
정책
정책은 하트비트 주기, 명령 폴링 주기, 보존 한도, 전송 조절(retention·sync)을 중앙에서 장비별로 정하는 것입니다. 바꾸려면 설정ADMIN 권한이 필요합니다.
- 디바이스 상세의 정책 탭을 엽니다. ① 현재 리비전이 보입니다. 값을 고치고 ② 정책 저장 을 누릅니다.
정책 탭: ① 현재 리비전 ② 정책 저장 (저장하면 리비전이 오르고 장비가 다음 하트비트에서 가져간다) - 저장하면 리비전이 1 오르고, 즉시 반영되도록
set_policy명령도 함께 큐에 들어갑니다. 장비는 하트비트 응답의policy_revision이 바뀐 것을 보고도 새 정책을 가져갑니다.
| 화면 항목 | 설정 키 |
|---|---|
| 하트비트 주기 (초) | heartbeat_interval_s |
| 보존 기간 (일) | retention.max_age_days |
| 보존 용량 (GiB) | retention.max_bytes |
| 전송 속도 상한 (B/s) | sync.max_bytes_per_s |
| CPU 일시정지 임계 (%) | sync.cpu_pause_percent |
| 전송 허용 시간대 | sync.windows |
하트비트 주기는 서버의 오프라인 판정 기준(기본 180초)보다 충분히 짧아야 합니다. 길면 정상 장비가 비정상 과 정상을 오갑니다.
보존: 끊겨도 버리지 않는다
엣지 장비는 케이블이 뽑히고 전원이 나가고 며칠씩 오프라인인 것이 정상입니다. 에이전트는 이 전제로 움직입니다.
- 수집은 멈추지 않습니다. 수집기는 링크 상태와 상관없이 로컬 큐(SQLite + 파일 스풀)에 씁니다.
- 한도 안에서 쌓습니다.
retention의 세 한도(max_bytes50 GiB,max_age_days30일,free_disk_min_bytes5 GiB) 중 먼저 걸리는 것을 지키며, 넘치면 우선순위가 낮고 오래된 것부터 버립니다. 버린 건수는 하트비트의evicted_24h로 올라가 플릿 목록 대기(적체 건수) 열에 빨간−N으로 보입니다. 보내는 속도보다 쌓는 속도가 빠르다는 신호입니다. - 링크가 돌아오면 바로 보냅니다. 연속 두 번 연결 확인(
link.online_after_ok)이 되면 등록 → 즉시 하트비트 → 큐 비우기 순으로 움직입니다. 보낼 때는 우선순위가 높은 것부터입니다. - 두 번 저장되지 않습니다. 레코드 id 가 멱등 키(같은 것을 여러 번 보내도 한 번만 저장되게 하는 키)라서, 응답을 못 받고 같은 묶음을 다시 보내도 중앙은
duplicates로 세고 한 번만 저장합니다. 파일은 청크 단위로 이어 올립니다.
실제로 CPU 가 바쁜 장비에서 cpu_pause_percent(기본 85) 때문에 전송이 멈춰 레코드 7건이 쌓였다가, 한도를 풀자 곧바로 모두 올라간 것을 확인했습니다.
"sync": { "state": "paused", "last_error": "paused: cpu above threshold" },
"backlog": { "count": 7, "bytes": 1150, "by_kind": { "blob": 1, "http": 3, "robot": 3 } }
전송을 조절하는 값은 설정 레퍼런스의 sync 표에 있습니다. 실패(failed) 상태가 된 항목은 geo-mlops-edge queue --state failed 로 확인하고, 재동기화 명령이나 POST /api/v1/sync:retry-failed 로 다시 보냅니다.