손호영
Platform Engineer2026.07 – 현재

Noraneko Platform

배포 플랫폼(PaaS)을 밑바닥부터 직접 구축했습니다. 2대의 OCI VM(노라네코·맛챠) 위에서 push-to-deploy CI/CD, digest 고정 배포, 관측성(메트릭·로그·경보·상태 페이지), 한글 Telegram ChatOps, 암호화 백업까지 — 운영 중인 모든 서비스가 이 플랫폼 위에서 돌아갑니다.

83%
CLI 커버리지
5.8k
Go 코드
18
경보 규칙
10
대시보드
3.2k줄
Ansible
3
운영 호스트
Noraneko Platform 아키텍처 다이어그램
시스템 아키텍처 — 클릭하면 원본으로 열립니다

01설계 결정 — 왜 이렇게 만들었나

채택한 것보다 버린 것이 설계를 더 잘 설명합니다.

왜 Kubernetes가 아니라 Docker Compose + Ansible인가

VM 2대·서비스 수가 한 자릿수인 규모에서 k8s를 올리면 컨트롤 플레인 자체가 가장 무거운 워크로드가 됩니다. 제가 필요했던 속성은 네 가지 — 선언적 정의, 반복 가능한 배포, 즉시 롤백, 서비스 추가 시 관측성 자동 반영 — 였고, 이건 JSON Schema로 검증되는 서비스 매니페스트 + 멱등 Ansible 플레이북 + digest 고정 + platformctl 렌더링으로 전부 충족됩니다. 대신 매니페스트를 처음부터 선언적으로 설계해서, 규모가 커지면 같은 정의를 k8s 매니페스트로 컴파일하는 이전 경로를 열어뒀습니다.

왜 완전 자동 배포가 아니라 승인 게이트인가

운영자가 1명이면 잘못된 커밋의 blast radius가 곧 서비스 전체입니다. 그래서 CI는 '검증된 아티팩트(digest)를 준비'하는 데서 멈추고, 프로덕션 반영은 Telegram 승인 카드 + Jenkins input의 2중 게이트를 거치는 명시적 의사결정으로 분리했습니다. 완전 자동화가 목표가 아니라, 자동화할 것(빌드·검증·설정 생성)과 사람이 결정할 것(언제 무엇을 내보낼지)의 경계를 긋는 게 목표였습니다.

왜 Jenkins인가 — GitHub Actions을 두고

셀프호스팅 전제에서 비교했습니다. Actions self-hosted runner는 편하지만 러너가 곧 신뢰 경계라 포크 PR·크리덴셜 격리를 세밀하게 긋기 어렵습니다. Jenkins는 낡았다는 인상과 달리 JCasC + Job DSL로 컨트롤러 전체(보안 정책·크리덴셜·잡)를 Git에 넣을 수 있고, executor 0 컨트롤러 + Docker 소켓 없는 에이전트 분리로 '신뢰할 수 없는 코드가 닿는 곳'을 명시적으로 제한할 수 있었습니다. 도구의 유행이 아니라 경계를 코드로 그릴 수 있는가로 선택했습니다.

02플랫폼 아키텍처

왜 PaaS를 직접 만들었고 어떻게 설계했는지 정리했습니다.

Push-to-Deploy 전체 흐름

GitHub push → 공개 443의 Go HMAC 릴레이(서명 검증·레포 allowlist·중복 제거) → 내부 Jenkins 멀티브랜치 파이프라인 → 격리 컨테이너 테스트 → Buildx 멀티아치 빌드 → Trivy 이중 스캔 → GHCR digest 고정 발행까지 자동으로 진행됩니다. 여기서 파이프라인은 멈추고, 배포는 Jenkins input + Telegram 승인 카드를 거쳐 platformctl이 Ansible로 대상 호스트에 sha256 digest를 배포합니다.

관제 플레인과 독립 워치독의 분리

노라네코(arm64)는 Jenkins·Prometheus·Grafana·Loki 등 15개 컨테이너의 관제 플레인을, 맛챠(x86_64)는 의도적으로 의존성이 없는 Gatus 워치독만 돌립니다. 관제 플레인이 통째로 죽어도 맛챠가 외부에서 DNS·TLS·헬스를 계속 감시하고 Telegram으로 직접 알립니다. 모니터링 시스템 자신이 단일 장애점이 되는 문제를 토폴로지로 풀었습니다.

Tailscale 전용 관리 플레인 + DNS-01 내부 TLS

모든 관리 UI는 테일넷 안에서만 접근 가능하고, Caddy가 Cloudflare DNS-01 챌린지로 공개 노출 없이 정식 인증서를 발급합니다. 자체 서명 인증서 없이, 공개 포트 없이 어디서든 HTTPS로 안전하게 운영합니다.

03공급망 보안과 배포 안전성

혼자 운영해도 회사 수준의 안전장치를 갖추는 데 집중했습니다.

digest 고정 배포와 이중 검증

빌드 시 이미지 digest를 release.json으로 아카이브하고, 승격 시점에 읽기 전용 크리덴셜로 GHCR digest를 재대조 + git merge-base ancestry를 검증합니다. 배포되는 것은 태그가 아니라 sha256 digest — 태그가 나중에 다른 이미지로 옮겨져도 영향이 없습니다.

신뢰할 수 없는 코드의 격리

컨트롤러는 executor 0개로 코드를 직접 실행하지 않고 Docker 소켓도 없습니다. 테스트·빌드는 non-root, 메모리 1.8G/CPU 0.75/PID 512 제한 컨테이너에서 격리 실행되며, 포크 PR과 비메인 브랜치에는 배포·Vault 크리덴셜이 절대 주입되지 않습니다.

시크릿 관리 강제

ansible-vault 암호문만 커밋되고 평문은 리포지토리에 존재하지 않습니다. platformctl validate가 PASSWORD·TOKEN·SECRET 등 시크릿 모양의 환경변수를 정규식으로 거부하고 Vault 기반 secretRef 사용을 강제해, 실수로 시크릿이 커밋되는 경로 자체를 차단합니다.

04운영 자동화와 관측성

설정 드리프트 없이 반복 가능한 운영을 목표로 했습니다.

GitOps 렌더링 — 매니페스트 1개, 산출물 6종

서비스 정의 YAML 하나를 platformctl render가 Prometheus 타깃/호스트 JSON, 경보 규칙, 내부·공개 Gatus 설정, 워치독 env, Ansible 변수로 컴파일합니다. 생성물은 전부 generated/ 아래 '수정 금지' 마커와 함께 커밋되고 CI에서 검증되므로, 서비스 추가 시 수작업 설정 변경이 0건입니다.

한글 ChatOps와 바운디드 자동 복구

장애 경보·배포 승인 카드·파이프라인 상태가 전부 한글 Telegram으로 옵니다. 자동 복구는 의도적으로 범위를 제한했습니다 — 컨테이너 재시작은 systemd 워치독이 처리하되, 호스트 장애·데이터 손상 같은 3중 신호는 반드시 사람에게 에스컬레이션합니다.

풀어낸 케이스

마주친 문제 · 고민하고 적용한 접근 · 실제 결과를 한 세트로 묶어 정리했습니다.

01CASE
securitygonetwork
문제

GitHub 웹훅을 받으려면 Jenkins를 공개 인터넷에 노출해야 했지만, CI 서버는 가장 민감한 공격 표면입니다.

고민·접근

Go로 얇은 릴레이를 직접 작성해 공개 443에 두고, X-Hub-Signature-256을 상수시간 비교로 검증, 레포 allowlist는 ^…$ 앵커 강제 + 와일드카드 금지, delivery ID로 중복 제거 후 내부 Jenkins URL로만 전달하도록 했습니다.

결과

공개 표면이 웹훅 등 4개 경로로 줄었고 관리 UI는 단 하나도 인터넷에 노출되지 않습니다.

공개 관리 UI 0개
02CASE
supply-chainci/cd
문제

태그 기반 배포는 레지스트리에서 태그가 다른 이미지로 옮겨지면 검증 없이 그대로 배포되는 공급망 리스크가 있습니다.

고민·접근

빌드가 digest를 release.json으로 아카이브하고, 승격 시 읽기 전용 크리덴셜로 GHCR digest 재대조 + git ancestry 검증을 거쳐 sha256 digest 자체를 배포하도록 파이프라인을 설계했습니다.

결과

커밋(준비)과 배포(승격)가 분리되고, 태그 변조가 일어나도 배포물은 바뀌지 않습니다.

digest-pinned 배포
03CASE
reliabilityarchitecture
문제

모니터링이 관제 서버에 같이 있으면, 그 서버가 죽는 순간 장애 사실 자체를 알 수 없게 됩니다.

고민·접근

별도 아키텍처(x86_64)의 맛챠를 Jenkins·DB·Prometheus 없이 Gatus만 돌아가는 무의존 워치독으로 설계하고, 경보는 관제 플레인을 거치지 않고 Telegram으로 직접 발송하게 했습니다.

결과

노라네코 전면 장애 상황에서도 외부 감시와 경보가 유지됩니다.

감시 SPOF 제거
04CASE
gitopsautomation
문제

서비스를 하나 추가할 때마다 Prometheus·Gatus·Alertmanager·워치독 설정을 손으로 고치면 반드시 드리프트와 누락이 생깁니다.

고민·접근

platformctl render가 서비스 매니페스트 1개에서 6종 설정 산출물을 결정적으로 생성하고, promtool 검증·throwaway 컨테이너 검증을 거쳐 반영되도록 만들었습니다.

결과

신규 서비스 온보딩 시 수작업 설정 변경이 사라졌고, 산출물은 전부 Git으로 추적됩니다.

매니페스트 1개 → 산출물 6종

프로젝트 정보

역할Platform Engineer
기간2026.07 – 현재
상태Production

기술 스택

Go 1.23Jenkins (JCasC · Shared Library)AnsibleDocker BuildxGHCRCaddy (Cloudflare DNS-01)TailscalePrometheusGrafanaLoki · AlloyAlertmanagerGatusTrivyresticansible-vaultTelegram Bot APIJSON Schema

주요 성과

  • git push부터 이미지 빌드·보안 스캔·승인·배포·관측성 반영까지 전 과정 자동화
  • 일본 철도 API(railway-server·dia-worker), Girls Band Tabi 등 실서비스를 이 플랫폼에서 운영
  • 공개 표면을 4개 경로로 최소화 — Jenkins·Grafana 등 관리 UI 전부 테일넷 전용
  • digest 고정 + 이중 검증으로 태그 변조에 면역인 공급망 안전 배포
  • Go 5,830 LOC(테스트 포함) · Ansible 3,189줄 · 플레이북 10종 · 역할 7종
  • 프라이빗 레포로 운영 중 — 면접 시 코드 워크스루·라이브 데모 가능
Noraneko Platform | Portfolio