19.6 제한된 에지 환경으로의 지속적 배포(CD) 관리

19.6 제한된 에지 환경으로의 지속적 배포(CD) 관리

클라우드(Cloud) 환경 배포가 무한한 자원 위에서 펼쳐지는 정규군(K8s)의 전투라면, 에지(Edge) 로봇이나 센서 디바이스로 코드를 배포하는 과정은 자원 기아에 시달리며 무전(Network)이 언제 끊길지 모르는 산악 지대의 비정규 게릴라전과 같다.

10대의 로봇 모두가 초고속 LTE를 물고 배송지에 컨테이너 이미지가 완벽하게 다운로드되기를 기도하는 것은 어리석다. 로봇은 지하 터널을 지나다 다운로드 중 패킷이 날아갈 수도 있고, CPU 코어가 2개밖에 없어서 앱을 업데이트하는 도중에 카메라 센서 프로세스가 멈춰 로봇이 벽에 부딪힐 수도 있다.

이 단원에서는 클라우드의 풍요로운 배포 템플릿(YAML)을 어떻게 극한의 제약을 지닌 에지 디바이스로 구겨 넣을지 다룬다. 그 핵심 병기로 초경량 쿠버네티스(K3s, MicroK8s), 자원 스케줄링(Resource Constraint), 그리고 오프라인 동기화, 궁극의 OTA(Over-The-Air) 파이프라인의 조립을 단계별로 심층 해부한다.

1. 에지 배포 지형의 한계와 돌파구

graph TD
    subgraph "에지 디바이스 (제한된 자원)"
        EdgeCPU[2-Core CPU & 2GB RAM]
        EdgeDisk[16GB eMMC Storage]
        EdgeNet[불안정한 Wi-Fi/LTE]
    end

    subgraph "에지 배포 전략 (Edge CD Deployment Strategy)"
        LightK8s[1. 초경량 오케스트레이션<br/>K3s / MicroK8s]
        Limit[2. 자원 제한과 QOS<br/>CPU/Mem Requests & Limits]
        OTA[3. 자율적 멱등성 병기<br/>OTA 및 오프라인 Sync]
    end

    EdgeCPU -.->|메모리/CPU 탈진 방지| Limit
    EdgeDisk -.->|저용량 바이너리| LightK8s
    EdgeNet -.->|네트워크 단절 극복| OTA

    LightK8s -->|통제| ZRouter[Zenoh Edge Router]
    LightK8s -->|통제| ZClient[Zenoh ROS/Python App]

우리는 에지 기기 안에 무거운 순정 쿠버네티스를 설치하는 끔찍한 실수를 저지르지 않으면서도, 클라우드 기술(선언적 배포, 재시작, 로깅)의 찬란한 장점만을 빼먹는 영리한 런북(Runbook)을 작성해야 한다.
스마트 팩토리의 구석에 처박힌 라즈베리 파이 단말 하나하나가 작지만 완벽한 하나의 독립된 데이터센터 단위(Micro-DataCenter)로 진화하게 될 것이다.

2. 경량 쿠버네티스(K3s, MicroK8s) 구성 및 에지 클러스터 설계

“로봇당 셸 스크립트 기반 배포(Bash scripts)“는 빠르고 쉽다. 하지만 시스템의 대수가 100대를 넘어가면 어느 로봇에서 에러가 났는지, 어떤 로봇의 Zenoh 컨테이너가 꺼져있는지 중앙에서 관제할 통제력을 단번에 상실한다. 클라우드의 무결점을 제공하는 K8s가 에지에도 절실하지만, 순정 쿠버네티스(비대한 etcd, kube-apiserver 등)는 로봇의 작은 메모리를 켜자마자 고갈시켜 버린다.

이 모순을 타파하기 위해, 에지 전용으로 내장을 모두 파내어(Strip-down) 500MB 미만의 메모리만 소모하도록 만들어진 극한의 다이어트 쿠버네티스인 K3s (Rancher)MicroK8s (Canonical) 가 투입된다.

2.1 K3s 기반 단일 노드(Single-Node) 에지 클러스터 런북

가장 보편적인 자율주행 로봇 아키텍처는 로봇 1대(Host OS)를 K3s 의 1개 클러스터(Control Plane + Worker 융합)로 취급하는 것이다.

  • 설치 사상 (SQLite 결합): K3s는 무거운 메모리 분산 데이터베이스인 etcd를 도려내고, 이를 아주 가벼운 파일 베이스의 SQLite3로 대체했다. 이로써 메모리 2GB짜리 초소형 단말기 위에서도 거대한 시스템 오케스트레이션이 가능하다.
## 로봇/에지 단말기 내부에서 단 한 줄로 K3s 런타임 설치 및 실행 (서버겸 에이전트로 융합)
curl -sfL https://get.k3s.io | sh -s - server \
  --disable traefik \       # 불필요한 L7 라우터(traefik) 제거
  --disable servicelb \     # 퍼블릭 네트워크를 쓰지 않으므로 L4 LB 제거
  --write-kubeconfig-mode 644

이렇게 설치된 K3s 위로 우리는 클라우드에서 만들어 두었던 것과 100% 문법이 일치하는 평범한 쿠버네티스 YAML 파일을 그대로 로봇에 적용(kubectl apply -f)할 수 있다. 이는 K8s 라는 단일 문법(Single Control Plane Grammar) 하나로 클라우드와 에지 장비를 모두 통치한다는 어마어마한 혜택을 가져다준다.

2.2 공장 내 자율 클러스터링 (안개 계층, Fog Computing)

단일 로봇이 혼자서 클러스터가 되는 것이 아니라, 스마트 팩토리 구역 내의 라즈베리 파이 센서 노드 10대를 묶어 하나의 “군단 클러스터(Fog)“로 편성할 수도 있다.

이때 구역 내에서 성능이 가장 좋은 산업용 PC(IPC) 한 대를 K3s 마스터(Master) 노드로 지정하고, 성능이 약한 9대의 센서 노드들을 쿠버네티스 워커(Worker) 노드로 K3s 클러스터 토큰을 기반으로 결합(Join)시킨다.

## 1번 마스터 노드의 토큰을 획득
cat /var/lib/rancher/k3s/server/node-token

## 2~10번 센서 노드(Worker)에서 마스터를 향해 편입(Join)을 맺음
curl -sfL https://get.k3s.io | K3S_URL=https://[마스터_IP]:6443 \
  K3S_TOKEN=[위에서발견한토큰] sh -

이 안개(Fog) 클러스터 망이 완성되면, 이 구역 내의 노드 1개가 물리적으로 부서져 꺼지더라도, K3s 마스터 노드는 즉각적으로 다른 기기로 Zenoh 센서 처리 파드(Pod)를 스케줄링하여 이동(Eviction)시키는 자가 수복 능력을 얻게 된다. 기계 고장에 인간 엔지니어가 곧바로 뛰어나갈 필요가 없어진 것이다.

3. 리소스 제약(CPU/Memory) 환경에서의 Pod 스케줄링 및 QoS(서비스 품질)

클라우드의 파드(Pod) 배포는 자원이 부족하면 AWS 요금을 더 지출해서 서버 용량(Instance Type)을 자동으로 늘리면 끝난다.
하지만 에지 디바이스에서는 로봇의 물리적 하드웨어 칩(CPU)을 무선으로 교체할 방법이 없다. 배포된 Zenoh C 플러그인과 비디오 스트리밍 파이프라인이 갑자기 메모리를 1GB 넘게 집어삼킨다면, 로봇의 하단 모터 제어를 담당하는 리얼타임(Real-time) OS 단의 쓰레드마저 자원 기아(Starvation) 상태에 빠져 벽에 부딪히는 치명적인 사고로 직결된다.

따라서 우리는 모든 파드의 명세(Manifest)에 자원의 상한선과 하한선을 구속복처럼 강제로 채워야만 한다. 리소스의 한계를 정의하지 않은(Unlimited) 무자비한 매니페스트는 에지 생태계의 테러리스트다.

3.1 [인스펙션] 리소스 Request 와 Limit 의 쌍검술 설계

쿠버네티스(K3s) 스케줄러에게 이 컨테이너가 생존하기 위한 “최소량(Requests)“과, 절대로 넘어서는 안 될 “최대폭주 방어선(Limits)“을 선언하라.

## zenoh-telemetry-client.yaml (로봇 배포 매니페스트 내부)
spec:
  containers:
  - name: zenoh-agent
    image: company/zenoh-agent:arm64-v1.2
    resources:
      requests: # 1. 생존 최소 방어선 (이만큼의 자원이 없는 노드에는 아예 배포되지 않음)
        memory: "64Mi"  # 64메가바이트 보장 요구
        cpu: "100m"     # 0.1 코어 보장 요구
      limits:   # 2. 폭주 억제선 (이를 초과하려 들면 컨테이너가 OOM Killed 됨)
        memory: "128Mi" # 아무리 트래픽이 몰려도 128MB를 넘으면 강제 참수!
        cpu: "250m"     # 0.25 코어 이상을 점유(Throttling)하지 못하게 방해!

이 Limits 제약은 엄청난 위력을 발휘한다. 개발자의 실수로 무한 루프(Infinite Loop)를 도는 버그가 로봇에 배포되었다 하더라도, 커널 차원(cgroups)에서 물리적인 블록을 걸어 로봇의 핵심적인 센서 구동과 모터 제어부(나머지 75%의 CPU)에는 털끝 하나 손상을 미치지 못하도록 기계적으로 억제한다.

3.2 QoS (Quality of Service) 등급표 계급의 탄생

K3s 클러스터 내에서 메모리가 부족해지면(OOM 상황 돌입), 스케줄러는 시스템 붕괴를 막기 위해 파드들을 차례차례 쏴 죽이기(Kill) 시작한다. 이때 과연 어떤 파드를 먼저 쏴 죽일 것인가를 정하는 계급 사회가 바로 QoS 클래스다.

  1. Guaranteed (귀족 계급):
    requestslimits의 메모리, CPU 수치를 동일(Exactly the same) 하게 맞추면 이 등급을 부여받는다. 로봇의 뇌 역할을 하거나, 생명과 직결된 브레이크 제어 데몬 등 절대 죽어선 안 되는 컨테이너 파드에 이 설정을 때려 넣어야 한다. K3s가 메모리 부족을 겪어도 이 컨테이너는 가장 마지막까지 결사 수호된다.
  2. Burstable (평민 계급):
    requests보다 limits 설정값을 더 높게 잡아두면 이 등급이 매겨진다. 평소에는 CPU를 적게 쓰다가, 갑자기 연산이 몰릴 때 남는 자원을 끌어다 쓰는 Zenoh-Flow 데이터 파이프라인 같은 모델에 적합하다.
  3. BestEffort (천민 계급 - 절대로 쓰지 말 것):
    리소스 제약 자체를 아예 적지 않으면 이 등급이 된다. 메모리가 포화 상태가 되면 1순위로 즉각 목이 날아간다. 개발용(Dev) 단말기가 아닌, 실제 생산용 현장 로봇에 이 등급의 매니페스트를 던지는 것은 시스템을 사지로 내모는 것과 같다.

에지 단말(Edge Node)에 배포되는 파트는 단순히 코드의 정확성(Logic)보다, 주어진 배급 식량(Resource) 안에서 어떻게든 이빨을 깨물고 버티는 마이크로 생존공학이 가장 최우선적으로 존중되어야 한다.

4. 에지 디바이스 리소스 제약(CPU, Memory)을 고려한 파드(Pod) 스케줄링

클라우드에서는 파드가 메모리를 펑펑 써도 되지만, 로봇 안에서는 Zenoh 데몬이 카메라 프로세스의 메모리를 뺏어 먹어 로봇이 눈먼 깡통이 되는 사태를 막아야 한다.

4.0.1 [Runbook] 리소스 할당의 철권통치(Hard Quota) 런북

1. Requests 와 Limits 의 파쇼(Fascism) 적 통제
모든 에지용 배포 YAML 에는 리소스 제한 명세가 강제되어야 한다.

resources:
  requests:
    cpu: "100m"      # 최소 0.1코어는 보장해줘
    memory: "64Mi"   # 최소 64MB 보장
  limits:
    cpu: "500m"      # 절대 0.5코어 이상 먹지 마
    memory: "256Mi"  # 256MB 넘기면 즉각적으로 죽여버림(OOM-Killed)

이 룰을 적어두면, K3s 는 Zenoh 컨테이너가 256MB 이상 메모리를 점유하려는 순간 인정사정없이 프로세스를 죽이고 다시 켠다. 로봇의 다른 생존 엔진(OS, 모터 드라이버) 이 죽는 것보다는 로컬 Zenoh 가 잠깐 죽는 것이 100배 낫기 때문이다.

2. 우선순위 (PriorityClass) 계급 부여
로봇 자원이 고갈 직전일 때, K3s 는 누군가를 죽여서(Evict) 메모리를 확보한다.
AI 로그 수집기 파드에는 낮은 우선순위를, 메인 Zenoh 라우터 파드에는 최상위(system-node-critical) 우선순위를 부여하여 메모리 가뭄 현상에도 통신 심장부만큼은 끝까지 살아남도록 계급 사회를 구성하라.

5. 네트워크 단절 상황에서의 배포 대응 및 오프라인 동기화(Synchronization) 전략

“배포 버튼을 눌렀는데 네트워크가 끊겼다. 배포가 절반만 된 상태로 로봇 프로그램이 파괴되었다.”
이는 수백억 원의 가치를 가진 산업용 드론 군집이나 스마트 팩토리 물류 라인을 일순간에 고철 덩어리로 전락시킬 수 있는 끔찍한 네트워크 단절(Intermittent Connectivity)의 공포다.

에지(Edge) 네트워크는 완벽하지 않다. 끊어지는 것이 에러가 아니라 ‘기본 상태(Default State)’ 라고 가정하는 철학적 전환이 오프라인 배포 런북의 핵심이다.

5.1 [Runbook] 불완전성을 극복하는 다운로드 이분법(A/B) 전략

에지에 배포되는 컨테이너 이미지는 작게는 수 MB 에서, 크게는 AI 파이토치 모델을 포함한 수 GB 에 달한다. 다운로드(Pulling) 도중에 라인(Line)이 끊기면, 기존 프로그램이 날아가고 컨테이너 이미지만 남는 사태를 완벽하게 차단해야 한다.

5.1.1 단계: OCI 블록 청크(Chunk) 해시 무결성 조각 다운로드

K3s 나 Docker 엔진이 레이어(Layer) 이미지를 다운로드 받아 기기에 적재할 때, K8s는 절대로 기존 운영 컨테이너(Running 상태)를 정지(Kill)시키지 않는다.

Kubelet 데몬백엔드 엔진은 전체 이미지의 각 조각(Blob)별 SHA256 해시 주소를 검사하며 디스크의 Temp 영역에 파일을 조금씩 이어받기(Resumable) 시도한다. 만약 98% 다운로드 상태에서 엘리베이터(시멘트 벽면)를 타서 로봇의 와이파이가 30분 동안 끊겼다 하더라도, 로봇은 메모리에 올라가 있는 구버전(v1.0)으로 기존의 임무를 흔들림 없이 수행한다. 다운로드 에러는 단순 경고(Warning) 백오프(Back-off) 큐에 들어갈 뿐 시스템 파괴로 직결되지 않는다.

엘리베이터 문이 열리고 30분 뒤 네트워크가 재개(Recover)되면, 로봇은 나머지 2% 의 파일 캐시 블록을 획득하고 이미지의 완전체(Complete Image) 무결성 검증을 통과한다. 오프라인과 다운로드는 이처럼 철저히 병렬로, 뒷단에서 독립적으로 움직이도록(Background Pulling) 조율되었다.

5.1.2 단계: 최후의 일격, K8s 롤링 업데이트 융합 파티션 전환

전체 이미지가 완성되면 비로소 배포의 2단계가 발사된다.
새로운 펌웨어(파드) v2.0 이 메모리 안에서 부팅을 완료하고, API 헬스 체커(Readiness Probe)에 의해 응답이 떨어진 직후, 그 찰나의 순간에 K8s 워커 노드는 로컬 랜카드 IP 트래픽의 과녁을 옛 버전 v1.0 에서 v2.0 파드로 옮겨버린다. (A/B Partition Swapping).
이후 역할을 다한 구버전 파드는 즉시 폐기(Terminate)된다. 이로 인해 통신망의 절단과 재개가 반복되는 그 거친 현실의 지형 속에서도 로봇은 단 1밀리 초(ms)의 정지 시간(Downtime)도 없이 비즈니스 연속성을 이어나가게 된다.

5.2 플래시 메모리 보존: 영구적 오프라인 상태(Offline Sync) 타파

만약 현장이 영원히 인터넷(퍼블릭 망)이 들어오지 않는 원자력 발전소 지하 시설이나, 극비 군사 시설 내부망이라면 어찌 배포할 것인가?

인터넷에 닿을 수 없는 이들 군단을 위해서는, 사이드카 레지스트리(Sidecar local registry) 전술이 펼쳐진다.

  1. 엔지니어는 USB 드라이브에 도커 압축 이미지 파일(.tar 볼륨)을 덤프 떠서 오프라인 현장 안으로 물리적으로 진입한다.
  2. 현장에 구축된 Local Harbor 나 K3s 마스터 노드의 로컬 디스크 캐시 안에 docker load -i 로 덤프를 풀어버린다.
  3. 로봇 파드의 매니페스트(YAML) 안의 image: 주소는 외부 도커 허브(docker.io)가 아니라, 이 폐쇄망 내의 로컬 IP를 바라보게 세팅(192.168.1.100:5000/robotics/zenoh-client:v2.1) 되어 있다.
  4. 모든 배포는 완벽한 폐쇄망 오프라인 싱크(Offline Synchronization) 구조로, 인터넷 없이 내부 지휘망의 통제 속에 완벽하게 동작 완료를 뿜어낸다.

네트워크를 믿지 않는 자만이 진정한 분산 시스템 엔지니어의 자격을 획득한다.

6. 대규모 IoT 디바이스 플릿(Fleet) 관리를 위한 OTA(Over-The-Air) 파이프라인 연동

수만 대의 IoT 디바이스 군단(Fleet)을 하나의 생명체 군집처럼 능수능란하게 다루려면 K3s 와 쿠버네티스(k8s)의 명령 하달만으로는 통제력에 한계가 온다.
수십만 군단의 현황을 지도 위에 뿌려 한눈에 통제하고, “오늘은 서울 지역 기기들만 업데이트하라”, “배터리가 30% 이하인 기기들은 배포를 내일로 미뤄라” 같은 고도의 디바이스 관제 철학(MDM, Mobile Device Management)이 결합 된 궁극의 무적함대 전술이 바로 OTA (Over-The-Air) 파이프라인 연동망 이다.

6.1 [인스펙션] 중앙 관제형 플릿(Fleet) 배포 아키텍처 구축 전술

에지 기기 스스로 자의적으로 뛰노는 것에 그치지 않고, 클라우드의 관제 플랫폼이 중앙(Master)에서 디바이스 하나하나의 파편화된 상태를 수집해 전략적으로 펌웨어를 살포하는 통제 시스템을 엮어낸다.

6.1.1 단계: Mender.io 모델을 차용한 OTA 선언 모델 결합

에지 단말기들을 컨테이너 레벨이 아닌 운영체제(Host OS) 펌웨어 자체까지 갈아엎을 수 있는 대형 갱신 파이프라인을 구축한다. Eclipse hawkBit 이나 Mender.io 와 같은 플릿 매니지먼트 백엔드와 Zenoh의 상태(State)를 결합한다.

클라우드의 파이프라인(Jenkins, GitHub Action 등)은 새로운 릴리즈가 완료될 때마다 펌웨어 통합 캡슐을 생성해 Mender 서버에 연결한다.
Mender는 군단(로봇, 드론 등)을 그라파나나 AWS 콘솔과 같은 웹 대시보드 시스템에 띄우고 디바이스 태그(Device Tag) 분류를 시행한다.

6.1.2 단계: 조건부(State-Conditional) 롤아웃 타이머

드론이 지상 수백 미터 상공에서 비행 중이거나 로봇이 용접을 하는 극박한 순간에 OTA 업데이트를 강제 진행하면 펌웨어 흔적가 생성되어 기계 추락이나 화재 등 끔찍한 물리적 재난이 들이닥친다. OTA 파이프라인은 기기의 운영 컨텍스트(Context) 를 인지하고 기다려야 한다.

에지 단말(Robot) 내부에 구동 중인 데몬 셸이 OTA 서브 코드를 수신하더라도 다음 전제 조건 3가지를 체크하도록 락(Lock)을 건다.

  1. is_moving == false: 바퀴/모터가 완전 정지해있는가?
  2. battery_lvl > 50%: 배터리 충전 잔량이 업데이트 도중 방전되지 않을 만큼 넉넉한 50% 이상인가? 혹은 충전 거치대 스테이션에 물려 있는 상태인가?
  3. zenoh_status == IDLE: 현재 Zenoh 라우터를 통해 처리 중인 긴밀한 데이터 송수신 파이프라인 스트림(Traffic)이 없는 유휴(IDLE) 상태인가?

위 체크리스트를 모조리 패스(True) 했을 때에만, 에지는 관제탑을 향해 "Ack! Now Ready to Flash" 신호를 돌려주며, 부트로더(Bootloader)의 영역을 B 파티션으로 교체하고 컨테이너 재부팅 스위치를 스스로 당긴다.

6.1.3 단계: 점진적 폭발 (Phased Rollout)과 반경 격리 통제

수만 대의 기기에 동시에 배포 명령을 엔터(Enter) 한 방 쳐서 때리면, 컨테이너 다운로드 레지스트리 서버와 외부망 트래픽 터널은 수 기가바이트(GB) 대역폭의 병목 파열에 의해 단체로 마비되어 터져버린다.

배포 파이프라인은 의도적으로 거대한 군단을 몇천 대씩 잘게 쪼개어 단계적으로(Phased) 파포 시킨다.

  1. 초반 반경 1% (Canary): 사내 테스트 공장의 100대 로봇만 선제 타격 업데이트.
  2. 경과 관찰 후 10% (Ramp-up): 문제없으면, 아시아(Tokyo, Seoul) 지역 군단을 업데이트 후 밤새워 관망.
  3. 100% (Global Strike): 북미와 유럽까지 차례대로 시차를 두고 여유롭게 며칠에 걸쳐 전 대륙의 OTA 플릿 파이프라인을 완전히 동기화 완료함.

이러한 고도의 중앙 플릿 관리는 K8s 컨테이너 라이프사이클과 절묘하게 결합하여 끔찍하게 제약된 통신망 단말 군단마저 한 마리의 거대한 생명체 제국으로 완벽하게 운용하게 하는 절정의 묘미를 완성한다.