현대 CI 플랫폼이 강력해졌지만 그 대가로 복잡성이 커졌고, 충분히 복잡해진 CI는 빌드 시스템과 구분되지 않으며 CI 제품들이 잘못된 추상화 계층을 겨냥하고 있다는 주장.
현대 CI 플랫폼의 상태는 불과 몇 년 전과 비교해 훨씬 더 강력해졌다. 전반적으로 이는 좋은 일이다. 강력한 CI 플랫폼에 접근할 수 있으면 소프트웨어 개발자와 기업은 더 신뢰할 수 있는 소프트웨어를 더 자주 출시할 수 있고, 이는 사용자/고객에게 이익이 된다. GitHub Actions, GitLab Pipelines, Bitbucket 같은 중앙화된 CI 플랫폼은 규모의 이점을 제공한다. 인터넷이 이들을 사용하는 방법에 대한 집단적 지식 저장소 역할을 하기 때문이다. _CI 플랫폼 Y에서 X를 하는 방법_을 검색하면 대개 복사해 붙여넣을 수 있는 코드를 찾을 수 있다. 결국 아무도 CI 설정과 씨름하느라 고생하고 싶지 않다. 그저 출시하고 싶을 뿐이다.
CI 플랫폼의 발전은 대가를 치렀다. 바로 복잡성의 증가다. 그리고 더 생각해 볼수록, 현대 CI 시스템은 너무 복잡하다는 믿음 쪽으로 점점 기울고 있다. 설명해 보겠다.
본질적으로 CI 플랫폼은 특수한 형태의 _서비스형 원격 코드 실행_이다(이건 기능이지 CVE가 아니다!). 여기서 실행되는 코드는 소프트웨어를 빌드하고 테스트하고 배포하는 목적을 가진다(물론 암호화폐 채굴에 악용하지 않는 한). 그래서 CI 플랫폼은 소프트웨어를 더 쉽게 출시할 수 있도록 다양한 부가가치 기능을 함께 제공하는 경우가 많다. 이 영역에는 접근 방식과 비즈니스 모델이 매우 다양하다. (커뮤니티가 유지보수하는 _액션_을 통해 네트워크 효과를 활용하는 GitHub Actions에는 경의를 표하지 않을 수 없다. 이는 GitHub 입장에서 많은 _액션_을 직접 유지보수할 필요가 없어 TCO를 낮추고, 사용자가 플랫폼 고유의 _액션_에 의존하게 만들어 벤더 종속을 만들며, 동시에 최종 사용자에게 플랫폼의 가치를 높인다. 보기 드문 제품 삼박자다.) CI 플랫폼의 흔한 부가가치 요소는 구성 파일(대개 YAML)이며, 여기에는 버전 관리 체크아웃을 설정하고 실행할 명령을 지정하는 등 공통 기능이 담긴다. 문제는 여기서부터 시작된다.
(여기서는 GitHub Actions에 집중하겠다. 최악이라서가 아니라(전혀 그렇지 않다), 가장 대중적이고 독자가 더 쉽게 공감할 수 있기 때문이다. 하지만 내 논평은 GitLab 같은 다른 플랫폼에도 적용된다.)
현대 CI 플랫폼의 YAML 구성은… 강력하다. GitHub Actions 워크플로 YAML에 있는 기능을 보면 다음과 같다.
범위를 GitHub가 유지보수하는 액션까지 약간 넓히면 다음과 같은 스텝/액션 기능도 있다.
그리고 당연히 서드파티 액션도 있다. 엄청 많다!
여기엔 많은 기능이 있고, 그중 상당수는 필요하다고 주장할 수 있다. 줄일 기능을 꼽기가 어렵다. (YAML을 프로그래밍 언어처럼 쓰는 건 마음에 들지 않지만, YAML을 생성하기 위해 코드를 쓰거나 동일한 API 호출을 강제하는 것과 비교하면 타협점으로서 그 사용을 인정할 수는 있다.) 충분히 강력한 CI를 제공하려면 이런 기능들이 필요해 보인다. 결국 누구도 즉시 쓸 수 있는(turnkey) 기능이 없다면 그 서비스를 쓰지 않을 테니까.
그렇다면 내 불만은 무엇일까?
나는 충분히 복잡한 CI 시스템은 빌드 시스템과 구별되지 않게 된다고 주장한다. 도전해 보라. GitHub Actions, GitLab CI 같은 CI 시스템이 빌드 시스템이 아니라는 점을 나(혹은 당신 자신)에게 설득해 보라. 기본 원시(primitives)는 다 있다. 잡으로 구성된 워크플로, 스텝으로 구성된 잡은 규칙으로 구성된 Makefile, 명령으로 구성된 규칙과 크게 다르지 않다. 의존성이 모든 것을 엮어준다. 주요 차이는 _형태(form factor)_와 실행 모델(빌드 시스템은 전통적으로 로컬 단일 머신에서 실행되지만, CI 시스템은 원격/분산이라는 점)이다.
그와 유사한 추론도 가능하다. 충분히 복잡한 빌드 시스템은 CI 시스템과 구별되지 않게 된다. 앞에서 CI 시스템을 _서비스형 원격 코드 실행_이라고 했다. 반면 빌드 시스템은 역사적으로 로컬에서 실행되는 것(따라서 _서비스_가 아님)이었지만, Bazel(또는 Buck, Gradle) 같은 현대 빌드 시스템은 완전히 다른 동물이다. 예를 들어 Bazel은 원격 실행과 원격 캐싱을 내장 기능으로 제공한다. 이런 것들은 현대 CI 시스템의 내장 기능이기도 하다! 그럼 이런 사고실험을 해 보자. Bazel로 빌드 시스템을 정의하고, 서버 측 Git push 훅을 정의해 원격 서버가 Bazel을 트리거하여 빌드하고 테스트를 돌린 뒤 결과를 어딘가에 게시하도록 만들면, 그것은 CI 시스템인가? 나는 그렇다고 생각한다! 조잡하긴 하지만, CI 시스템 자격은 있다고 본다.
눈을 가늘게 뜨고 보면, 충분히 복잡한 CI 시스템과 충분히 복잡한 빌드 시스템은 내게 거의 같은 것으로 보이기 시작한다. 아주 높은 수준에서 보면 둘 다 서버 풀을 제공해 일반적인 연산/실행 기능을 제공하면서도, 태스크 간 아티팩트 교환, 캐싱, 의존성, 모든 동작을 정의하는 프런트엔드 언어 같은 소프트웨어 빌드/출시에 특화된 기능을 제공한다.
(아주 더 눈을 가늘게 뜨면 더 일반적인 컴퓨트 스케줄링으로서 Kubernetes의 가치 제안까지 보이기 시작하지만, 이 글에서는 거기까지 가지 않겠다. 주장하기 훨씬 어렵고, 나도 꼭 믿는 건 아니기 때문이다. 다만 흥미로운 사고실험이라 언급은 하겠다. 대신 더 쉬운 도약은 데이터 웨어하우스에서 흔히 보이는 배치 잡 실행을 빌드/CI 시스템과 같은 범주로 넣는 것이다. 배치 잡 실행도 의존성과 잡 간 아티팩트 교환이 있고, CI 시스템(따라서 빌드 시스템)처럼 보일 때가 많다.)
현대 CI 시스템에서 나를 괴롭히는 건, 결국 빌드 시스템을 재발명하고 빌드 시스템 로직을 파편화하는 기분이 든다는 점이다. CI 구성은 실행 시간을 줄이고 신뢰성을 확보하기 위한 온갖 캐시와 의존성 최적화가 들어간 복잡한 YAML 더미로 필연적으로 퇴화한다. 마치 빌드 시스템처럼. 프로젝트의 빌드 시스템을 CI 맥락에 맞추려고 꼬아야 하고, 그 반대도 마찬가지다. 결국 하나가 아니라 두 개의 복잡한 DAG와 플랫폼/시스템을 관리하게 된다.
빌드 시스템이 CI 시스템보다 더 범용적이기 때문에(충분히 발전한 빌드 시스템은 충분히 복잡한 CI 시스템이 할 수 있는 일의 상위집합을 할 수 있다고 생각한다), 충분히 발전한 빌드 시스템이 있는 상황에서 CI 시스템은 중복이다. 그래서 제목을 넘어 더 말하자면, CI 시스템은 너무 복잡한 게 아니라: 존재할 필요가 없다. CI 기능은 빌드 시스템의 확장이어야 한다.
중복성 논증 외에도, 통합된 시스템이 더 사용자 친화적이라고 생각한다. CI 시스템을 빌드 시스템에 통합하면(정의상 로컬 개발 워크플로의 일부로 구동 가능), 개발자에게 CI의 모든 힘을 더 쉽게 노출할 수 있다. 변경 사항을 원격 서버에 먼저 푸시하지 않고도 임시(ad-hoc) CI 잡을 돌릴 수 있다고 생각해 보라. 로컬 빌드나 테스트처럼 말이다. 이는 사용성 측면에서 엄청난 이점이고, (대개 변경/테스트가 취약한) 이런 시스템의 변경 사이클 타임을 크게 줄일 수 있다.
오해는 하지 말라. 빌드 시스템에 전통적으로 없던 CI의 측면(예: 중앙화된 결과 리포팅, 잡을 (재)트리거하기 위한 UI/API)은 반드시 필요하다. 대신 완전히 중복되는 것은 원격 컴퓨트와 작업 정의(work definition) 측면이다.
이제 빌드와 CI 시스템이 본질적으로 비슷하다는 관점이 어떤 함의를 갖는지 살펴보자.
빌드와 CI 시스템이 (될 수 있고/실제로) 비슷한 것이라고 가정하면, GitHub Actions, GitLab CI 등 많은 현대 CI 제품이 잘못된 추상화 계층을 겨냥하고 있다는 결론이 따른다. 즉, CI 시스템을 실행하기 위한 도메인 특화 플랫폼으로 정의되어 있지만, 사실 한 발 물러서 빌드 시스템(그리고 어쩌면 데이터 웨어하우스/파이프라인에서 흔히 보는 배치 잡 실행)에도 필요한 더 넓은 범주의 일반 컴퓨트 플랫폼을 겨냥해야 한다.
각 CI 제품은 이 스펙트럼에서 위치가 다르다. 나는 GitHub Actions가 _플랫폼_이라기보다 _CI 제품_에 가깝다고까지 주장하고 싶다. 설명해 보겠다.
내가 이상적으로 생각하는 _CI 플랫폼_에서는, 임시로 구성한 태스크 그래프를 그 플랫폼에 스케줄링할 수 있어야 한다. 즉, 내가 원하는 태스크 정의를 API로 전달하면 플랫폼이 이를 받아 실행하고, 어딘가에 아티팩트를 업로드하며, 태스크 결과를 보고해 의존 태스크가 실행될 수 있게 하는 식이다.
GitHub Actions API가 있어 서비스와 상호작용할 수는 있다. 하지만 결정적으로 없는 기능이 있다. 임시 작업 단위(ad-hoc units of work)를 정의하는 기능, 즉 진짜 _서비스형 원격 실행_이다. 대신 작업 단위를 정의하는 유일한 방법은 저장소에 체크인된 워크플로 YAML 파일뿐이다. 너무 제약적이다!
GitLab Pipelines는 훨씬 낫다. GitLab Pipelines는 부모-자식 파이프라인(서로 다른 파이프라인 간 의존성), 멀티 프로젝트 파이프라인(서로 다른 프로젝트/저장소 간 의존성), 동적 자식 파이프라인(파이프라인 잡에서 새로운 파이프라인을 정의하는 YAML을 생성) 같은 기능을 지원한다. (GitHub Actions는 이런 기능을 지원하지 않는다고 알고 있다.) 동적 자식 파이프라인은 체크인된 YAML 구성과 서비스형 원격 실행 기능을 상당 부분 분리해 주기 때문에 중요하다. 여기서 주요하게 빠진 것은, 부모 파이프라인/YAML을 거치지 않고도 이 기능을 달성할 수 있는 일반 API다. 그런 API가 있다면, GitLab의 의도된 사용 방식과 의견이 강한 YAML 구성 파일이 강제하는 제약을 덜 받으면서 GitLab Pipelines 위에 나만의 빌드/CI/배치 실행 시스템을 구축할 수 있을 것이다. (일반적으로 잘 설계된 플랫폼/도구의 리트머스 테스트는, 누군가 의도치 않은 방식으로 그것을 사용했을 때 제작자가 놀라는 경우라고 생각한다. 물론 이 칼은 양날이라, 때로는 암호화폐 채굴 같은 바람직하지 않은 일을 하기도 한다.)
GitHub Actions, GitLab Pipelines 같은 CI 제품은 이론적으로는 범용적인 서비스형 원격 실행 위에 의견이 강한 구성 메커니즘(YAML 파일)과 웹 UI(및 그에 대응하는 API)를 단단히 결합해 놓았기 때문에 플랫폼이라기보다 제품에 가깝다. 내가 이들을 플랫폼이라고 부르려면, 공식적으로 기본 제공되는 YAML에 제약받지 않고 API로 임의의 컴퓨트를 스케줄링할 수 있는 능력이 커져야 한다. GitLab은 거의 그 지점에 와 있다(결정적으로 빠진 고리는 _인라인으로 정의된 파이프라인을 스케줄_하는 API다). GitHub가 그 방향을 추구하고 있는지(혹은 관심이 있는지)는 알 수 없다. (이건 뒤에서 더 이야기하겠다.)
GitHub, GitLab 등이 추구하는 CI 제품에 대한 반례로 Taskcluster를 잠깐 언급만 하려 했다. 하지만 칭찬을 쏟아내게 되어, 결국 Taskcluster에 대한 섹션이 생겼다. 이 내용은 글 전체에서 결정적으로 중요하진 않으니 건너뛰어도 된다. 하지만 엔지니어를 위해 만들어진 CI 플랫폼이 어떤 모습인지 알고 싶거나, CI 플랫폼 개발자라서 훔쳐갈 만한 아이디어를 읽고 싶다면 계속 읽어라.
Mozilla의 Taskcluster는 Firefox를 위해 처음 만들어진 범용 CI 플랫폼이다. 2014~2015년 무렵 구상되고 초기 개발이 이뤄졌을 때, 이와 비슷한 것은 다른 곳에 없었다. 그리고 지금도 그 순수한 역량을 따라올 수 있는 것을 나는 알지 못한다. 기업 내부에 독점 솔루션이 있을 수도 있다. 하지만 오픈 소스 영역에서는 가까운 것조차 없다. 내가 아는 독점 CI 플랫폼들조차 Taskcluster의 기능 목록에 못 미치는 경우가 많다.
내가 알기로 Taskcluster는 공개적으로 사용할 수 있는, 초대형 프로젝트 규모의, 진정한 _CI 플랫폼_으로 존재하는 유일한 사례다.
이 글과 관련해 내가 Taskcluster에서 사랑하는 점은 실행 단위를 정의하는 핵심 원시(primitives)다. Taskcluster의 핵심 실행 원시는 태스크(task)다. 태스크들은 DAG를 이루도록 서로 연결된다. (빌드 시스템이 동작하는 방식과 다르지 않다.)
_Task_는 _큐 서비스(queue service)_에 API 요청을 보내 생성한다. 그 API 요청은 본질적으로 _이 작업 단위를 스케줄해라_라고 말하는 것이다.
태스크는 상당히 일반적으로 정의되며, 임의의 컴퓨트 단위와 함께 태스크 의존성, 그 태스크가 가진 권한/스코프 같은 메타데이터를 포함한다. 그 작업 단위에는 GitHub Actions, GitLab Pipelines 사용자에게 익숙한 많은 원시가 있다. 실행할 명령 목록, 실행할 Docker 이미지, 아티팩트를 구성하는 파일 경로, 재시도 설정 등이다.
Taskcluster는 오늘날 GitHub, GitLab 등이 제공하는 것보다 훨씬 더 많은 기능을 제공한다.
예를 들어 Taskcluster는 접근 제어를 조정하는 IAM 유사 스코프(scopes) 기능을 제공한다. 스코프는 수행할 수 있는 작업, 접근 가능한 서비스, 사용할 수 있는 러너 기능(예: ptrace 사용 가능 여부), 접근 가능한 시크릿 등을 제어한다. 구체적으로 Firefox의 Taskcluster 설정에서는 Firefox 빌드를 서명하는 데 쓰이는 암호화 키/시크릿이 신뢰되지 않은 태스크(예: PR로 시작된 태스크에 해당하는 것, Mozilla 용어로는 Try Server)에서는 접근 불가능하다. CI 플랫폼이 사실상 거대한 서비스형 원격 코드 실행 위험이라는 점(보안/리스크 팀의 잠을 설치게 만들 수 있고, 또 그래야만 하는 점)을 완화할 만한 충분한 보호 장치를 갖춘 CI 플랫폼은 내가 아는 한 Taskcluster가 유일하다. Taskcluster의 보안 모델과 비교하면 GitHub Actions, GitLab Pipelines 같은 흔한 CI 서비스는 데이터 유출과 소프트웨어 공급망 취약점 공장처럼 보일 정도다.
Taskcluster도 저장소에 YAML 파일을 추가해 태스크를 정의하는 것을 지원한다. 하지만 일반적인 스케줄링 API가 있기 때문에, 꼭 그것을 쓸 필요가 없고 그 기능에 제약받지도 않는다. 태스크를 정의하기 위한 나만의 구성/프런트엔드를 만들 수도 있다. Taskcluster는 진정한 _플랫폼_이기 때문에 상관하지 않는다. 실제로 Firefox는 이 Taskcluster YAML을 대체로 피하고, 대신 태스크 정의 기능을 자체적으로 구축했다. Firefox 저장소에는 실행하면 Firefox의 빌드 및 릴리스 DAG를 구성하는 수천 개의 개별 태스크를 도출하고, 적절한 서브그래프를 Taskcluster 태스크로 등록하는 코드 더미가 체크인되어 있다. (이 또한 YAML 더미이긴 하다. 하지만 프로그래밍 원시와 제어 흐름이 YAML 파일에 대체로 없어서, 예컨대 GitHub/GitLab CI YAML이 진화한 _YAML DSL_보다는 좀 더 깔끔하다.) 이 기능은 Taskcluster 플랫폼을 실행/평가 메커니즘으로 사용하는 일종의 미니 빌드 시스템이다.
Taskcluster의 모델과 역량은 오늘날 GitHub Actions나 GitLab Pipelines가 제공하는 것과 비교할 수 없을 정도로 앞서 있다. 베껴갈 만한 좋은 아이디어가 아주 많다.
불행히도 Taskcluster는 전형적인 파워 유저용 CI 제품이다. GitHub나 GitLab처럼 누구나 쓸 수 있는 중앙 인스턴스가 없다. 학습 곡선도 가파르다. 모든 힘에는 복잡성이라는 비용이 따른다. 나는 선의로 캐주얼 사용자에게 Taskcluster를 추천할 수는 없다. 하지만 자체 CI 플랫폼을 호스팅하고 싶고, 다른 CI 제품들이 요구를 충족하지 못하며, CI 플랫폼을 지속적으로 지원할 사람 몇 명을 감당할 수 있다면(즉 사람과 머신을 포함한 CI 운영 총비용이 연간 100만 달러를 넘는다면), Taskcluster는 고려할 가치가 있다.
이제 본론으로 돌아가자.
내가 이상적으로 생각하는 세계에는, 근실시간 실행과 배치/지연 실행을 모두 서비스할 수 있도록 목적에 맞게 설계된 단일 서비스형 원격 코드 실행 플랫폼이 존재한다. 아마도 소프트웨어 개발을 지원하도록 맞춤화되어 있을 것이다. 그런 도메인 특화 기능이 Kubernetes, Lambda 같은 범용 컴퓨트 서비스와 구별해 주기 때문이다. 하지만 더 범용적인 무언가도 잠재적으로는 가능할지 모른다.
DAG 개념이 실행 모델에 강하게 내장되어, 실행 단위를 그래프로 정의하고 의존성을 포착할 수 있다. 물론 고립된 임시 작업 단위도 정의할 수 있다. 하지만 여러 작업 단위를 정의하고 싶다면, 빌드 시스템이 보통 그렇듯 완료까지 실행을 조율하기 위해 상주 에이전트를 돌릴 필요 없이 할 수 있어야 한다. (이를 _실행 서비스에 DAG를 업로드하는 것_이라고 생각하라.)
내가 이상적으로 생각하는 세계에서는 빌드, 테스트, 릴리스 태스크를 모두 지배하는 단 하나의 DAG가 있다. 빌드, CI, 기타 배치 실행 경계에서 DAG가 파편화되지 않는다. 모든 것이 통합되어 있기 때문에 N+1개의 시스템이나 구성을 관리하거나 추가 플랫폼을 유지보수할 필요가 없다. 통합을 통해 규모의 경제가 적용되고, 전반적인 효율이 향상된다.
플랫폼은 작업을 수행할 수 있는 에이전트를 실행하는 워커 풀들로 구성된다. 근실시간/동기 RPC 스타일 호출용 풀과, 스케줄/지연/비동기 실행용 풀이 따로 있을 것이다. 사용자는 자신의 워커 풀을 정의하고, 자신의 워커를 가져올 수 있다. 고급 고객은 아마도 매우 일시적인(ephemeral) 워커(예: EC2 스팟 인스턴스)로 구성된 오토스케일링 그룹을 이 풀에 붙여, 수요에 맞춰 비교적 저렴하게 용량을 확장하고, 더 이상 필요 없을 때 워커와 머신을 종료해 비용을 절감할 것이다(Firefox의 Taskcluster 인스턴스가 최소 6년 이상 해온 방식이다).
최종 사용자 관점에서 _로컬 빌드_는 필요한 빌드 아티팩트를 만들기 위해 전체 태스크 그래프 중 필요한 부분집합을 구동하거나 스케줄링하는 것이다. _CI 빌드/테스트_는 그것을 달성하기 위한 태스크 그래프의 부분집합이다(아마 로컬 빌드 그래프의 상위집합일 것이다). 릴리스도 마찬가지다.
구성 프런트엔드와 실행 단위를 정의하는 방법 측면에서, 이 플랫폼이 제공해야 하는 것은 딱 하나다. 작업을 스케줄/실행할 수 있는 API. 다만 사용자 친화적이려면 오늘날 CI 시스템처럼 YAML 구성 파일 같은 것을 제공하는 게 좋다. 괜찮다. 많은(대부분의?) 사용자는 단순화된 YAML 인터페이스에 머물 것이다. 중요한 것은 파워 유저에게 탈출구/확장 벡터가 있어, 저수준 스케줄/실행 API를 통해 자신만의 드라이버를 작성할 수 있어야 한다는 점이다. 사람들은 이 플랫폼과 통합하는 빌드 시스템 플러그인을 작성할 것이다. 누군가는 Bazel, Buck, Gradle 같은 확장 가능한 빌드 시스템의 빌드 그래프 노드를 이 플랫폼의 컴퓨트 태스크로 강제로 변환하게 만들 것이다. 그러면 빌드와 CI 시스템의 통합(그리고 어쩌면 데이터 파이프라인 같은 것들까지)이 가능해진다.
마지막으로, 소프트웨어 개발에 맞춤화된 특수 시스템을 이야기하고 있으니 강력한 결과/리포팅 API와 인터페이스가 필요하다. 아무도 무엇을 하는지 볼 수 없다면, 이 멋진 분산 원격 컴퓨트가 무슨 소용인가? 이는 아마도 가장 도메인 특화된 서비스일 것이다. 결과 추적 방식은 예외적으로 도메인 특화적이기 때문이다. 파워 유저는 자신만의 결과 추적 서비스를 만들고 싶어할 수도 있으니 이를 염두에 두어야 한다. 하지만 플랫폼은 GitHub Actions, GitLab Pipelines가 오늘날 제공하는 것 같은 범용 서비스를 제공해야 한다. 엄청난 부가가치이고, 그런 기능 없이는 제품을 쓰는 사람이 거의 없을 것이기 때문이다.
빠르게 짚고 넘어가면, 내가 제안한 통합 세계가 앞에서 제기한 CI 복잡성 우려를 완전히 해소하진 못한다. 충분히 큰 빌드/CI 시스템은 언제나 내재적 복잡성을 갖고, 유지보수를 위해 전문가가 필요할 수도 있다. 하지만 복잡한 CI 시스템은 거의 항상 복잡한 빌드 시스템에 붙어 있으므로, 빌드와 CI를 통합하면 복잡성의 표면적을 줄인다(빌드/CI 상호운용을 그만큼 덜 걱정해도 된다). 파편화가 줄면 전반적 복잡성도 줄어든다. 이는 새로운 승리다. (모노레포를 정당화할 때도 비슷한 논리가 적용된다.)
내 비전의 구성 요소는 오늘날 모두 어떤 형태로든 존재한다. Bazel, Gradle Enterprise 같은 현대 빌드 시스템은 원격 실행 및/또는 캐싱을 위한 RPC를 갖고 있다. 게다가 확장 가능해서 빌드 시스템이 실행되는 핵심 동작을(물론 정도의 차이는 있지만) 플러그인으로 바꿀 수도 있다. Taskcluster, GitLab Pipelines 같은 CI 제품은 DAG 형태의 태스크 스케줄링을 지원한다(원하는 최종 상태에는 Taskcluster의 지원이 훨씬 더 적합하다). Airflow 같은 배치 잡 실행 프레임워크도 있는데, 이는 도메인 특화된 Taskcluster의 변종처럼 보일 정도다. 우리가 갖지 못한 것은 이 모든 기능을 하나의 응집력 있는 제품/서비스로 묶어 제공하는 단일한 오퍼링이다.
내가 원하는 것을 만드는 일은 _할 수 있느냐_의 문제가 아니라, _해야 하느냐_와 _누가 하느냐_의 문제라고 확신한다.
그리고 여기서 문제가 생길 것이다. 솔직히 말해, 이것이 몇몇 기업의 벽 밖에서 가까운 시일 내에 널리 사용 가능한 서비스로 존재하게 될 것이라곤 회의적이다. 이유는 총 адрес 가능한 시장(TAM) 때문이다.
내 비전의 가치는 분리된 시스템(빌드, CI, 그리고 어쩌면 데이터 파이프라인 같은 원오프)을 통합하는 데서 나오는데, 그런 통합을 비즈니스/효율 관점에서 하고 싶어질 만큼 그 시스템들이 충분히 복잡해야 한다. 결국 복잡하거나 비효율적이지 않다면 더 단순/빠르게 만들 필요를 느끼지 못할 것이다. 바로 여기서 시장의 90% 이상이 걸러진다고 본다. 그들의 시스템은 이런 게 중요할 만큼 복잡하지 않기 때문이다.
이 비전은 원격 실행을 구동하는 통합 DAG의 두뇌 역할을 할 수 있을 만큼 충분히 발전한 빌드 시스템의 채택을 요구한다. 일부 기업과 프로젝트는 자원, 기술적 노하우, 효율상의 동기가 있기 때문에 Bazel 같은 호환되는 고급 빌드 시스템을 채택할 것이다. 하지만 많은 곳은 그렇지 않을 것이다. 더 고급 빌드 시스템이 단순한 시스템에 비해 주는 이점은 종종 미미하다. 게다가 많은 회사는 빌드/CI 지원을 제품 개발의 오버헤드이자 최소화해야 할 가상 비용 센터로 인식한다. 과도한 고통 없이 훨씬 적은 비용으로 충분히 좋은 덜 고급 빌드 시스템으로 버틸 수 있다면, 그 길을 택하는 회사와 프로젝트가 많을 것이다. 다시 말해, 사람과 회사는 대체로 빌드와 CI를 씨름하는 데 관심이 없다. 그저 출시하고 싶을 뿐이다.
이 아이디어의 TAM은 너무 작아 보여서, 이를 구현해 서비스로 제공할 기술력을 가진 주요 플레이어가 향후 몇 년 내에 나올 것 같지 않다. 게다가 내가 제안하는 것(빌드와 CI 통합)이 좋은 아이디어라는 공감대조차 아직 넘지 못했다. 이 분야에서 10년 일하면서 Taskcluster 모델의 가능성을 목격했고, 전/현/잠재적 고용주들이 서로 다른 정도로 고생하는 것도 봤기 때문에, 이 아이디어가 일부에게는 매우 가치 있다는 것을 안다. (어떤 회사는 유사한 시스템을 유지보수하는 중복 인력을 제거하고, 머신 유휴/실행 비용을 줄이며, 핵심 개발 루프의 턴어라운드 타임을 개선함으로써 연간 수백만 달러를 절약할 수도 있다.) 일부에게 중요하더라도, 내 직감으로는 그들이 TAM의 너무 작은 조각을 차지하기 때문에, GitHub나 GitLab 같은 기존 CI 사업자가 지금 당장 이 파이를 신경 쓸 만큼 크지 않다. 더 수익성 높은 기회가 많다. (예: 보안 스캐닝. 법/규제/소송이 마침내 소프트웨어 산업을 따라잡아 회사들이 보안과 프라이버시를 더 진지하게 받아들이도록 강제하고 있고, 이는 보안 서비스에 돈을 쓰는 것으로 이어진다. 이것이 지난 1~2년 사이 GitHub와 GitLab이 서로 앞다퉈 새 보안 기능을 발표해 온 이유다.)
이 분야에서 스타트업을 하는 것도 좋은 생각이 아니라고 본다. 고객 확보가 너무 어렵다. 그리고 핵심 기술의 상당 부분이 기존 도구에 이미 존재하기 때문에, 깊은 자본을 가진 모방자를 막아줄 독점 IP 해자가 크지 않다. 최선의 엑싯은 아마 Microsoft/GitHub, GitLab, 혹은 Amazon/AWS 같은 이 영역의 플레이어 지망생에게 조기 인수되는 것일 가능성이 크다.
오히려 내 비전이 현실이 될 최선의 희망은, (비공개든 공개든) 기존 대형 CI 플랫폼의 운영자가 자신들도 큰 빌드 시스템 또는 임시 배치 실행 문제를 겪고 있어서 이를 구현한 뒤, 오픈 소스든 서비스든 세상에 내놓는 것이다. GitHub, GitLab 같은 코드 호스팅 제공자는 커뮤니티 효과로 업계 채택을 이끌 수 있으니 이상적인 후보들이다. 하지만 나는 평판 좋은 회사의 고품질 오퍼링이라면 거의 무엇이든 기꺼이 받아들일 것이다!
언제가 될지는 모르지만, 내 돈은 GitHub/Microsoft가 이 비전을 먼저 실행할 것에 걸겠다. 이들은 더 넓은 시장/제품 연계(예: Visual Studio나 GitHub Workspaces[엔터프라이즈용]에서의 빌드+CI 통합)라는 더 강한 유인을 갖고 있다. 더구나 내부에서부터 요구가 생길 것이다. Microsoft는 아주 거대한 빌드 시스템과 CI 문제(특히 Windows)를 가지고 있다. Microsoft의 일부 조직이 GitHub에서, 심지어 공개적으로 개발하고 있다는 것도 분명하다(이 시점에서 Satya Nadella의 Microsoft는 지옥의 여러 층을 얼려버려서 단테의 고전이 새로 개정돼야 할 정도다). Microsoft 엔지니어들은 분리된 빌드/CI 시스템의 고통과 한계를 체감할 것이다. 결국 GitHub에서 최소한 빌드 시스템용 원격 실행 서비스/오퍼링에 대한 요구가 나올 것이다. (이는 더 많은 소프트웨어 개발 라이프사이클을 포획하려는 GitHub의 기존 시장 전략과도 자연스럽게 맞아떨어진다.) 내 바람은 GitHub(혹은 누군가)이 이를 분리된 서비스가 아니라 통합된 플랫폼/서비스/제품으로 구현하는 것이다. 내가 주장했듯 두 문제는 사실상 같은 문제이기 때문이다. 하지만 통합 오퍼링은 저항이 가장 작은 길은 아니니, 무슨 일이 벌어질지는 모르겠다.
만약 손가락을 튕겨 업계의 분리된 빌드, CI, 그리고 어쩌면 배치 실행(예: 데이터 파이프라인)을 10년 앞당길 수 있다면, 나는 다음을 할 것이다.
이 꿈이 가까운 시일 내에 현실이 될까? 아마 아닐 것이다. 하지만 꿈꿀 수는 있다. 그리고 어쩌면 독자 한 명쯤은 이를 추구해 보도록 설득했을지도 모른다.