지속적 배포(CD), 검사를 통과해야 나가는 파이프라인
코드를 올리면 빌드와 테스트가 자동으로 돌고, 통과한 결과만 운영 서버로 나간다.
CI와 CD가 각각 무엇을 가리키는지, 산출물을 왜 한 번만 만드는지, 검사에 걸리면 무엇이 멈추는지 정리한다.
이 글의 목차코드는 어떻게 서버까지 가는가9
GitHub에서 저장소를 둘러보면 커밋 옆에 초록색 체크나 빨간 X가 붙어 있는 경우가 많다.
처음에는 무심코 넘기기 쉽지만, 이 표시 뒤에서는 꽤 많은 일이 일어난다.
GitHub에서는 이 표시를 상태 검사(status check)라고 부르고, 대개 저장소 바깥의 시스템이 만든다.
그 시스템이 올라온 코드를 받아 빌드하고 테스트를 돌린 뒤 결과를 커밋에 붙인다.
이 자동화가 어디까지 이어지느냐에 따라 CI라고도 하고 CD라고도 부른다.
코드는 어떻게 서버까지 가는가
웹 서비스의 소스 코드는 서버에 올리기 전에 손볼 것이 많다.
타입스크립트를 자바스크립트로 바꾸고, 필요한 패키지를 설치하고, 파일을 묶어야 한다.
이렇게 실행할 수 있는 형태로 만드는 일을 빌드라고 한다.
빌드가 끝나면 결과물이 남는다.
압축 파일일 수도 있고 컨테이너 이미지일 수도 있다.
이 결과물을 산출물(artifact)이라고 부른다.
산출물을 사용자가 실제로 접속하는 서버에 올려 실행하는 일이 배포(deploy)다.
그 서버가 돌아가는 환경은 운영 환경, 영어로는 프로덕션(production)이라고 한다.
빌드부터 검사와 배포까지를 변경이 생길 때마다 같은 순서로 자동 실행하는 장치가 파이프라인이다.
빌드를 여러 단계로 나눠 두고, 단계를 하나씩 지날 때마다 이 변경을 내보내도 된다는 확신을 더해 간다.
첫 단계에서는 보통 컴파일을 하고, 다음 단계들이 쓸 산출물을 만든다.
운영 배포는 대개 마지막 단계다.
CI: 자주 합치고, 합칠 때마다 검사한다
여러 사람이 한 저장소에서 일하면 각자 고친 코드를 언젠가 합쳐야 한다.
몇 주씩 따로 작업하다 한꺼번에 합치면 충돌이 쏟아지고, 어디서 무엇이 깨졌는지 찾기도 어렵다.
해법은 합치는 일을 미루지 않는 데 있다.
자주 합치되, 합칠 때마다 기계가 자동으로 빌드하고 테스트하게 한다.
이것을 지속적 통합(Continuous Integration), 줄여서 CI라고 한다.
여기서 말하는 테스트는 코드가 기대한 대로 동작하는지 확인하려고 미리 짜 둔 코드다.
사람이 화면을 하나씩 눌러 보는 대신, 변경이 올라올 때마다 기계가 똑같이 실행한다.
이 방식은 1990년대에 Kent Beck이 익스트림 프로그래밍이라는 개발 방법의 하나로 만들었다.
요즘은 거의 모든 팀이 이 일을 대신 돌려 주는 CI 서비스를 쓴다.
GitHub Actions도 빌드, 테스트, 배포 파이프라인을 자동화하는 CI/CD 플랫폼이다.
저장소의 .github/workflows 폴더에 적어 둔 설정대로 움직인다.
CD는 두 가지 뜻으로 쓰인다
CI 다음에 붙는 CD는 처음에 헷갈리기 쉽다.
지속적 전달(Continuous Delivery)도, 지속적 배포(Continuous Deployment)도 줄이면 CD가 된다.
지속적 전달은 소프트웨어를 언제든 운영에 내보낼 수 있는 상태로 유지하는 일이다.
실제로 내보낼지는 사람이 정하되, 버튼 한 번이면 나갈 수 있어야 한다.
지속적 배포는 한 걸음 더 나간다.
파이프라인을 통과한 변경이 사람 손을 거치지 않고 자동으로 운영에 올라간다.
그래서 지속적 배포를 하려면 지속적 전달이 먼저 갖춰져 있어야 한다.
둘이 기대는 규칙은 같다.
검사를 통과하지 못한 변경은 다음 단계로 넘어가지 못한다.
검사에 걸리면 무엇이 멈추는가
검사가 실패하면 파이프라인은 그 자리에서 멈춘다.
새 산출물은 운영 서버에 닿지 않고, 사용자는 직전 버전을 그대로 쓴다.
고칠 곳은 저장소의 코드뿐이다.
멈추는 것이 번거롭게 느껴질 수 있다.
그래도 운영에 나간 뒤 수습하는 문제와 달리, 나가기 전에 멈춘 실패는 사용자에게 아무 영향도 주지 않는다.
합치는 단계에서부터 막을 수도 있다.
변경을 합쳐 달라는 요청을 풀 리퀘스트(pull request)라고 하는데, GitHub에서 main처럼 중요한 브랜치를 보호 브랜치로 정하고 필수 상태 검사를 걸어 두면 검사가 모두 통과하기 전에는 풀 리퀘스트를 합칠 수 없다.
main 브랜치에 합쳐 달라는 풀 리퀘스트가 멈추는 자리는 검사 결과와 main의 보호 규칙에 따라 달라진다.
- 풀 리퀘스트 검사실패 · 빨간 X
- 합치기막지 않음 · 합침
- main 브랜치실패한 변경 반영 · 빨간 X
- 운영 서버직전 버전 그대로
- 풀 리퀘스트 검사통과 · 초록 체크
- 합치기막지 않음 · 합침
- main 브랜치새 변경 반영
- 운영 서버새 버전 배포
- 풀 리퀘스트 검사실패 · 빨간 X
- 합치기막힘 · 필수 검사 실패
- main 브랜치그대로
- 운영 서버직전 버전 그대로
- 풀 리퀘스트 검사통과 · 초록 체크
- 합치기막지 않음 · 합침
- main 브랜치새 변경 반영
- 운영 서버새 버전 배포
보호 브랜치에 필수 상태 검사를 걸어 두면 검사가 실패한 풀 리퀘스트는 합칠 수 없고 main도 그대로다. 필수 검사가 없으면 실패한 변경도 main에 들어가고, 배포 전 검사에서 다시 걸려 운영만 직전 버전에 머문다.
필수 검사가 없으면 검사가 실패해도 합치기를 막지 않는다. 합치고 나면 main에 빨간 X가 붙고, 배포 전 검사에서 다시 걸려 운영은 직전 버전에 머문다.
일반 협업자가 합치는 경우다. 저장소 관리자는 기본 설정에서 보호 규칙을 비켜 갈 수 있다. main에 합친 변경도 같은 검사를 거쳐야 배포되는 파이프라인을 가정했다.
사람이 하던 순서를 기계에 맡기는 이유
파이프라인이 없으면 이 순서를 사람이 지켜야 한다.
빌드하고, 테스트를 돌리고, 파일을 서버에 복사하고, 서비스를 다시 시작하는 식이다.
평소에는 잘 지키던 순서도 급한 날에는 짧아진다.
테스트를 건너뛰거나, 다른 사람 컴퓨터에서 만든 파일을 그대로 올리게 된다.
“내 컴퓨터에서는 되는데”라는 말이 흔히 이런 데서 나온다.
산출물은 한 번만 만든다
파이프라인을 처음 짤 때 흔히 하는 실수가 있다.
테스트용으로 한 번 빌드해 놓고, 운영에 내보낼 때 같은 소스로 다시 빌드하는 경우다.
소스가 같아도 결과가 같다는 보장은 없다.
그사이 의존성 패키지의 새 버전이 나왔을 수도 있고, 빌드 도구나 환경 설정이 달랐을 수도 있다.
그러면 운영에 올라간 것은 검사를 받지 않은 새 파일이다.
그래서 산출물은 한 번만 만들고, 검사를 통과한 그 파일을 그대로 다음 환경으로 옮긴다.
이것을 승격(promotion)이라고 한다.
개발 서버에서 확인한 파일이 그대로 운영 서버에서 돈다.
설치할 때마다 패키지 버전이 달라지는 문제는 lockfile과 npm ci를 다룬 글에서 따로 다룬다.
환경마다 다른 값은 실행할 때 넣는다
그렇다면 환경마다 달라지는 값은 어디에 둘까.
데이터베이스 주소나 외부 API 키처럼 개발과 운영에서 다른 값은 산출물에 넣지 않는다.
웹 서비스를 만드는 원칙 열두 가지를 정리한 Twelve-Factor App은 이런 값을 설정(config)이라 부르고, 코드와 엄격하게 분리하라고 한다.
산출물은 그대로 두고, 실행하는 환경에서 그 환경의 값을 넣어 준다.
이 원칙은 빌드와 릴리스, 실행도 서로 다른 단계로 나누라고 한다.
빌드 결과에 환경 설정을 합친 것이 릴리스이고, 릴리스마다 고유한 번호를 붙여 한번 만든 뒤에는 고치지 않는다.
통과했는데 문제가 생기면 되돌린다
검사는 미리 생각해 둔 문제만 잡는다.
실제 트래픽이나 운영 데이터에서만 드러나는 문제는 검사를 통과하고도 나타난다.
이때는 되돌리기(rollback)가 필요하다.
직전 릴리스를 다시 실행하면 되는데, 산출물을 한 번만 만들어 보관하는 구조라면 그 파일이 이미 남아 있다.
다시 빌드하지 않아도 되니 빠르다.
배포 도구는 대개 이전 릴리스로 되돌리는 기능을 갖추고 있다.
검사 단계마다 잡아내는 문제도, 걸렸을 때 일어나는 일도 다르다.
| 검사 | 잡아내는 문제 | 걸리면 |
|---|---|---|
| 빌드와 타입 검사 | 형태가 깨진 코드 | 산출물이 만들어지지 않는다 |
| 테스트와 규칙 검사 | 동작이 틀리거나 팀 규칙을 어긴 코드 | 산출물은 있어도 배포되지 않는다 |
| 배포 후 상태 확인 | 운영 환경에서만 드러나는 문제 | 직전 릴리스로 되돌린다 |
처음 파이프라인을 만든다면
처음부터 모든 단계를 갖추지 않아도 된다.
변경이 올라올 때마다 빌드와 테스트가 돌고, 실패하면 합치지 못하게 막는 데서 시작하면 된다.
그다음은 배포를 파이프라인 안으로 옮기는 일이다.
운영 서버에 직접 접속해 파일을 올리는 대신, 검사를 통과한 산출물만 정해진 경로로 나가게 한다.
산출물에는 어느 커밋에서 만들었는지 알 수 있는 이름을 붙여 보관한다.
그러면 운영에서 지금 어떤 코드가 도는지 바로 알 수 있고, 직전 것으로 돌아가기도 쉽다.
운영 서버가 여러 대라면 새 버전을 한꺼번에 바꿀지 몇 대씩 나눠 바꿀지도 정해야 한다.
그 이야기는 ECS 서비스 배포 글에서 이어진다.