폐쇄망 데이터센터에서 CI/CD를 만든다는 것 — 현장 노트
첫 온프레 고객사에서 부서진 클라우드-CI 가정들의 목록. 이론이 아니라 실제로 어디서, 어떻게 무너졌는지를 순서대로.
처음 우리 CI 를 폐쇄망 고객사 데이터센터 안에 깔던 날, 빌드가 38분 동안 멈춰 있었는데 아무도 아무 말을 안 했다. 우리 셋이 고객사 NOC 에 서 있었고, 고객 측 사람들은 예의 바르게 우리를 지켜봤다.
알고 보니 npm install 이 절대 도달할 수 없는 방화벽 너머 registry.npmjs.org 를 향해 조용히 재시도를 반복하고 있었다. 에러로 떨어지지도 않았다. 그냥 재시도, 재시도, 재시도. 누군가 의심하기까지 38분이 걸린 거다.
이 글의 전부가 그 순간이다.
무너진 가정
모던 CI 도구는 — 자체 호스팅 가능한 것들조차 — 네트워크가 대략 이렇다고 가정한다.
"공개 레지스트리에 닿을 수 있을 것이다. 안 되면 깔끔한 에러로 떨어진다."
이 "닿을 수 있을 것이다" 안에 모든 게 숨어 있다. 공개 레지스트리, 베이스 이미지, OIDC 발급자, 텔레메트리 엔드포인트, 라이선스 서버. 각각이 별개의 바깥행 의존성이다. 그리고 각각이 다른 방식으로 실패한다 — 게이트웨이가 60초 동안 답을 안 주다가 소켓을 끊는 그런 종류.
첫 주에 우리는 빌드 로그에서 모든 외부 DNS lookup 을 grep 하는 법을 익혔다. 편집증처럼 들리지만 편집증이 아니다. CI 가 진짜로 뭐에 의존하는지 알아내는 유일한 방법이다.
무너진 순서대로 다시 짠 것들
3-pillar 도 아니고 maturity model 도 아니다. 우리를 문 순서대로의 목록이다.
의존성 미러. 가장 먼저, 항상 먼저. 모든 언어 생태계가 라우터 한 홉 거리에 레지스트리가 있다 고 가정한다 — npm, PyPI, Maven, Cargo, Go modules. 코드베이스가 건드리는 모든 생태계마다 내부 미러가 필요하고, 그 미러는 극히 제한된 인터넷 만 허용된 전송 영역(transit zone) 으로부터 단방향 sync 를 받는다. 우리는 npm 에 Verdaccio, Python 에 devpi, Maven/Docker 에 Nexus 를 쓴다. 1년 뒤 디스크 사용량 약 2.4 TB. 계속 증가 중.
베이스 이미지. FROM ubuntu:22.04 라는 줄은 노트북에서는 평범하고 고객사 데이터센터에서는 신학 이다. 두 달 뒤 우리는 CI 정책에서 사내 레지스트리를 가리키지 않는 모든 FROM 을 금지 했다. 이 규칙의 비용은 분명하다 — 엔지니어들이 불평한다. 하지만 1년 전 빌드를 재현해야 하는데 외부 이미지 태그가 회전해 사라진 날이 오면, 그날 동의하게 된다.
워커. 클라우드-CI 의 멘탈 모델이 가장 크게 깨지는 지점이다. 클라우드의 워커는 임시다 — 띄우고, 한 번 쓰고, 죽인다. 폐쇄망 환경의 워커는 종종 영속 이다. 하드웨어 추가 발주가 분기 단위로 걸리니까. 그러면 클라우드에선 절대 안 보이는 것들이 보이기 시작한다 — 캐시 레이어를 통한 시크릿 누출, 워커가 시간이 지나면서 과한 권한을 누적 해 가는 현상, 그리고 2월의 어느 화요일에 누군가 수동 패치를 적용했기 때문에 똑같아야 할 두 워커가 슬로우 드리프트를 시작하는 모습.
세 가지 규칙으로 풀었다. 워커는 서명된 불변 이미지로 부팅한다. 빌드는 워커 안의 컨테이너 안에서 돈다 — 그렇다, 이중 컨테이너화고, 그렇다, 비용이 든다. 그리고 모든 자격 증명은 빌드와 함께 만료되는 작업 토큰 이다. 예외 없다. "딱 이번 한 번만 재실행" 같은 요청에도 예외 없다.
시크릿. 외부 KMS 는 우리에겐 존재하지 않는 옵션이다. 작동한 패턴은 OIDC 워크로드 아이덴티티 + 내부 Vault 다. CI 가 OIDC 토큰을 들고 가면 Vault 가 그 클레임을 검증하고, 실제 대상 시스템에 단기 자격 을 발급한다. 토큰은 환경변수에도, 로그에도, 설정 파일에도 절대 들어가지 않는다. 처음 짜는 거면 일주일 잡아라.
캐시·아티팩트. 내부 S3 호환 스토리지 (Ceph, MinIO) — 괜찮다. 함정은 보존 정책 이다. 온프레의 스토리지는 무한이 아니다. 빌드 캐시가 11 TB 까지 자란 시점에서 누가 이걸 갖고 있는지 물었더니 아무도 없었다. 첫 캐시 쓰기 이후가 아니라 그 전에 보존 정책을 정해라.
누군가 나에게 이 표를 미리 줬으면 좋았을 표
| 영역 | 클라우드 가정 | 폐쇄망 답 |
|---|---|---|
| 의존성 | 공개 레지스트리 | 내부 미러 + 단방향 transit sync |
| 베이스 이미지 | 자유 선택 | 서명된 카탈로그 + 정적 FROM 검증 |
| 워커 | 임시 | 불변 이미지 + 빌드 단위 컨테이너 격리 |
| 시크릿 | 외부 KMS | 내부 Vault + OIDC 단기 토큰 |
| 캐시 | 클라우드 블롭 스토리지 | 내부 S3 호환 + 명시적 보존 정책 |
다섯 줄. 첫 고객 때는 한 줄당 분기 단위 일감, 두 번째 고객부터는 한 줄당 일주일이다.
CollabOps 에 대한 짧은 메모
이 작업이 우리가 "또 다른 CI 벤더를 만들지 말자" 가 아니라 직접 만들 수밖에 없겠다 는 결론에 닿게 한 작업이다. 우리가 평가했던 모든 기존 CI 도구는 위 가정 중 최소 하나 를 너무 깊은 곳에 배선해 두었다. 그걸 뜯어내려면 영원히 우리만 유지하는 fork 를 만들어야 했다. 우리 워크플로우 표현식 엔진과 런타임을 직접 쓴 건 원해서 가 아니라 이 목록 때문 이다. (표현식 엔진을 직접 만든 이유)
두 번 읽게 될 목록
은행, 공공기관, 방위 산업, 혹은 진짜 CISO 가 있는 큰 엔터프라이즈의 폐쇄망으로 클라우드 네이티브 소프트웨어를 들고 들어가려는 팀이라면, 위 다섯 줄짜리 표가 이 글에서 가져갈 전부다. 시작하기 전에 한 번 읽고, 첫 고객을 라이브로 띄운 뒤에 한 번 더 읽으면 된다. 첫 번째 읽기는 이론처럼 느껴질 거고, 두 번째 읽기는 너무 당연한 얘기네 라고 느껴질 거다.
그 환경에 안 들어갈 거라면 이 글은 편집증처럼 읽힐 거다. 편집증 맞다. 그리고 정확하다.
비슷한 글
기획자가 코드를 배포하면, 장애는 누가 책임질까
AI로 제품 담당자가 직접 코드를 만들고 여러 에이전트가 동시에 작업하는 조직에서, 운영에 나가는 변경을 누가 무엇으로 확인해야 하는지. NIST SSDF, SLSA provenance, Google SRE 포스트모템 문화를 근거로 작성자와 분리된 검증·승인·복구의 역할 설계를 정리했다.
백재민
에이전트 시대의 Eclipse 플러그인 — 작업 컨텍스트는 20년 전에도 문제였다
Mylyn 이 2000년대에 풀려던 문제와 지금 에이전트 컨텍스트 문제는 같습니다. 달라진 건 그 컨텍스트를 읽는 쪽에 사람만 있는 게 아니라는 점입니다.
김영상
에이전트 시대의 VS Code 확장 — 개발자는 에디터에서 무엇을 보는가
코드 작성 비용이 무너진 뒤 개발자의 시간은 판단과 승인으로 옮겨갔습니다. 그 일들이 왜 에디터 밖에 있으면 안 되는지, VS Code 확장을 만들며 내린 선택들.
이승백