폐쇄망 데이터센터에서 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 가 있는 환경 — 은행, 공공기관, 방위 산업, 큰 엔터프라이즈 — 으로 클라우드 네이티브 소프트웨어를 들고 들어가려는 사람에게. 시작하기 전에 한 번 읽고, 첫 고객을 라이브로 띄운 뒤에 한 번 더 읽으면 된다. 첫 번째 읽기는 이론처럼 느껴질 거고, 두 번째 읽기는 너무 당연한 얘기네 라고 느껴질 거다.
그 환경에 안 들어가는 사람에게는 이 글이 편집증처럼 읽힐 거다. 편집증 맞다. 그리고 정확하다.
비슷한 글
에이전틱 DevOps 12개월 후 — 첫 가설 중 무엇이 *맞았고* 무엇이 *틀렸나*
12개월 전 다음 10년의 DevOps는 에이전틱이다 의 가설들. 12개월의 데이터로 어느 가설이 맞고 어느 게 틀렸는지의 정직한 평가.
백재민
3 pillars 그 후 — 4 추가 신호의 *6개월 후* 운영 노트
3 pillars 가 더 이상 충분하지 않은 이유 발행 후 6개월. 4 추가 신호 (events / user journeys / deploy correlation / similarity) 가 운영에서 어떻게 작동했는지의 후속.
백재민
GitHub Actions vs 자체 호스팅 — *진짜 비용* 비교 (12개월 데이터)
GitHub Actions 가 *비싸 보임* 은 표면. 12개월 자체 호스팅 vs SaaS 비교 — 단순 *분당 비용* 이 아니라 *총 운영 비용* 으로.
백재민