폐쇄망 데이터센터에서 CI/CD를 만든다는 것 — 현장 노트

첫 온프레 고객사에서 부서진 클라우드-CI 가정들의 목록. 이론이 아니라 실제로 어디서, 어떻게 무너졌는지를 순서대로.

백재민
백재민
CollabOps 창업자
폐쇄망 데이터센터에서 CI/CD를 만든다는 것 — 현장 노트

처음 우리 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 가 있는 환경 — 은행, 공공기관, 방위 산업, 큰 엔터프라이즈 — 으로 클라우드 네이티브 소프트웨어를 들고 들어가려는 사람에게. 시작하기 전에 한 번 읽고, 첫 고객을 라이브로 띄운 뒤에 한 번 더 읽으면 된다. 첫 번째 읽기는 이론처럼 느껴질 거고, 두 번째 읽기는 너무 당연한 얘기네 라고 느껴질 거다.

그 환경에 안 들어가는 사람에게는 이 글이 편집증처럼 읽힐 거다. 편집증 맞다. 그리고 정확하다.

태그#cicd#on-prem#air-gapped#infrastructure#devops