디지털 사이니지 DevOps · AWS IoT OTA 배포 환경 구축
디지털 사이니지 서비스를 실제 운영 가능한 시스템으로 만들기 위해 AWS 인프라를 Pulumi로 코드화하고, GitHub Actions CI/CD와 AWS IoT Core 기반 디바이스 OTA 배포 파이프라인을 구축한 프로젝트입니다. 서버 애플리케이션 배포부터 현장 사이니지 디바이스(Android)의 원격 소프트웨어 업데이트까지 배포 전 과정을 자동화했습니다.
Architecture
동기
디지털 사이니지는 한 번 쓰고 버리는 인쇄 배너를 화면으로 대체하자는 요구에서 시작했습니다. 납품처마다 배너를 새로 찍어 보내는 대신 화면 하나를 두고 내용을 원격으로 바꾸는 것이고, 화면이 컴퓨터인 김에 방문자 수·연령대·시선·시간대 같은 것을 함께 본다는 것이 제품의 목적이었습니다.
그 그림이 성립하려면 현장에 나가지 않고 기기의 소프트웨어를 바꿀 수 있어야 합니다. 합류 시점의 배포는 APK 를 손으로 말아서 사람이 옮기는 방식이었고, 그 상태로는 납품처가 늘어나는 만큼 사람이 돌아다녀야 했습니다. 그래서 처음부터 설계하며 릴리스부터 현장 기기 설치까지를 GitHub Actions · S3 · AWS IoT Core 로 자동화했습니다.
내가 담당한 트랙
Pulumi IaC 부터 릴리스 파이프라인, S3 매니페스트 배포, Fleet Console, DevSecOps 파이프라인, 멀티환경 모니터링까지 릴리스에서 기기까지의 배포 경로 전체가 제 몫입니다. 항목별 내역은 오른쪽 「주요 업무」에 있습니다.
| 트랙 | 경계 |
|---|---|
| 디바이스 등록·프로비저닝 API 로직 | 백엔드팀 몫입니다. 그 API 가 부르는 IoT Thing Type·Policy·IAM 인프라는 제가 만들었습니다 |
| 사이니지 앱 · 고객 분석 기능 | 제품 기능이라 다른 트랙입니다 |
릴리스에서 기기까지
배포는 태그 push 한 번으로 시작합니다. GitHub Actions 가 아티팩트를 빌드해 버전별 prefix 로 S3 에 올리고 매니페스트를 발행하면, AWS IoT Job 이 그 매니페스트를 기기에 내려보냅니다. 기기는 서명된 임시 다운로드 URL 로 아티팩트를 받아 SHA256 을 확인한 뒤 설치합니다.
아티팩트는 한 번 올리면 바꾸지 않고, 해시는 발행·전달·설치 직전 세 지점에서 각각 다시 잽니다. 한 번만 재면 그때 맞았다는 것까지만 알 수 있어서, 세 번 재야 올린 것과 설치된 것이 같다는 게 보장됩니다.
파이프라인에는 AWS 접근 키를 두지 않습니다. GitHub Actions 가 실행마다 발급하는 서명된 토큰을 AWS 가 검증해 그 job 에서만 사는 임시 자격을 내주는 방식(OIDC 연동)이라, 저장소에 넣어 둘 장기 키 자체가 없습니다.
기기 인증은 Thing 단위 X.509 인증서로 mTLS 를 겁니다. 인증서가 기기마다 다르므로 한 대가 털려도 그 한 대의 권한만 사라집니다.
Fleet Console
쓸 만한 관제 화면이 없어서 직접 만들었습니다. 연결 상태·offline 알림·롤아웃 제어를 한자리에서 보려고 Go 로 짰고, 홈랩 Kubernetes 위에서 돌립니다.
여기서 신호를 둘 봅니다. AWS IoT presence 가 증명하는 것은 MQTT 세션이 살아 있다는 것까지고, 그나마도 IoT Core 는 keep-alive 의 1.5배를 기다린 뒤에야 끊긴 것으로 판정합니다. 정작 손을 대야 하는 순간은 그 배선 자체가 고장 났을 때인데, 그때 presence 는 알려주는 게 없고 알려준다 해도 들어갈 길이 없습니다.
그래서 Tailscale 을 함께 깔았습니다. MQTT 와 독립된 경로로 기기에 도달할 수 있으니 ADB 로 붙어 환경을 직접 들여다볼 수 있습니다. 두 신호가 어긋나는 지점, 도달은 되는데 IoT presence 가 없다거나 그 반대인 경우가 진단의 출발점이 됩니다. offline 은 Discord 로 알립니다.
롤아웃과 기기 쪽 방어
이 프로젝트에서 제일 무서운 건 납품처 화면의 영상이 끊기는 상황입니다. 사이니지는 사람이 지나다니는 자리에 서 있어서 장애가 곧바로 눈에 띕니다.
롤아웃은 소수 기기에 먼저 넣어 보고(canary) 이상이 없으면 단계적으로 넓히며, 실패 비율이 임계를 넘으면 자동으로 중단됩니다. 다만 중단은 아직 안 나간 실행만 취소합니다. 이미 받아서 진행 중인 기기는 그대로 갑니다. 그래서 기기 쪽에도 방어를 따로 뒀습니다. 로컬 캐시와 CloudFront 가 그 자리입니다. 새 소프트웨어가 문제를 일으켜도 화면에 이미 있는 콘텐츠로 버티고, 콘텐츠 자체는 CDN 에서 받습니다.
배포 전 검증은 실기기 E2E 까지 갑니다. 에뮬레이터에서 되는 것과 현장에 놓인 Android 기기에서 되는 것은 다릅니다.
배운 점
- 디바이스 배포는 되돌리는 방법이 서버와 다릅니다. 서버는 이전 이미지로 다시 띄우면 되지만, 기기는 되돌리려면 그 기기가 다시 명령을 받아 줄 수 있는 상태여야 합니다. 배포가 실패해도 다음 배포를 받을 수 있는 상태로 남는가가 롤백 계획의 실제 조건이었습니다.
- SonarQube·Trivy 를 파이프라인에 붙이는 일은 job 하나입니다. 어려운 쪽은 그 결과가 배포 알림 안에서 읽히게 만드는 것이었습니다. 검사 통과 여부가 배포 소식과 같은 화면에 있어야 사람이 봅니다.
- EC2 기반 dev·staging·prod 를 만들면서 어느 브랜치가 어디로 가는지를 먼저 정해야 인프라 구조가 정해졌습니다. 그 뒤로는 모니터링과 알림을 붙여 사람이 대시보드를 안 보고 있어도 되는 운영으로 옮겼습니다.