Release 의 모든 것 (12장-13장)
Release 의 모든 것 (12,13장) 의 내용 중, 인상적이었던 부분을 발췌 및 요약합니다.
배치 작업
많은 사람들이 모여 배치를 수행하고 있을 것이다. 오래된 운영 소프트웨어일수록 그럴 가능성이 높다. 배치가 빠르고, 자주 수행될 수록 운영의 이득이 있다. 배치 시스템 설계의 안티패턴과 해법을 알아보자.
운영 환경과 소프트웨어
요즘 대부분의 운영 환경에서 소프트웨어는 가상 머신 상에서 수행된다. 가상 머신은 관리자에 의해 직접 관리되지 않고, 수명도 짧다. 관리해야 할 구성도 많아지고 구성을 관리할 도구도 더 많아진다.
시스템 점검 시간이라는 오류
운영에 한번 또는 어쩌다 몇 번이 아니라 끊임없이 수많은 배치를 하겠다고 계획해야 한다. 시스템 점검 시간의 이유는 사용자에게 중요하지 않다. 중단은 중단일 뿐이다. 이런 변경에는 시간이 걸리고, 사용자는 변경 중 상태의 시스템을 보게 된다.
자동 배치
어플리케이션이 쉽게 배치되도록 설계하는 방법을 배워보자. 빌드 파이프라인을 상상해보자.
개발자가 버전 관리 시스템에 변경 사항을 제출하면 빌드 관리 시스템에 의해서 빌드될 것이다. 빌드 파이프라인은 성장한 지속적 통합 서버(CI)라고 할 수 있다.
CI 에서는 정확한 단위 테스트, 정적 코드 분석, 컴파일 같은 개발 관련 관심사를 다룬다. CI 는 테스트 보고서와 빌드 결과를 보관하는데 그치지만 빌드 파이프라인은 운영 환경에 배치되는 일련의 단계까지 진행된다.
빌드 파이프라인을 만드는 데 주로 쓰이는 제품 중 가장 일반적인 것은 젠킨스이다. 이외에도 여러 도구가 있는데, 최고의 도구를 찾으려 하지 말고 쓸만한 도구를 선택하여 충분히 활용하라.
빌드 파이프라인의 맨 끝에서 빌드 서버는 구성 관리 도구와 상호작용한다. 구성 관리란 특정 구성을 호스트나 가상 머신에 대응시키는 것을 의미한다.
자동 역할 할당은 운영자가 머신을 선정하는 것이 아니라, 구성 관리 도구에 의해서 자동으로 머신과 서비스 인스턴스가 할당되는 것이다.
지속적 배치
배치되지 않은 코드는 미완성 재고다. 숨은 버그나 운영 중단을 유발할 수 있다. 지속적 배치(CD) 라는 개념은 이런 지연을 최대한 줄여 배치되지 않은 코드의 자원을 최소화하는 것이다. 변경 사항 제출에서 운영까지 시간이 길어질수록 배치에 더 많은 변경 사항이 쌓인다. 변경 사항이 많은 큰 배치는 분명히 더 위험하다.
이를 방지할 수 있는 유일한 방법은 지속적 배치를 통해 자주 아프는 수 밖에 없다.
배치의 여러 단계
큰 파일 하나에 내장된 5백만 줄의 자바 어플리케이션을 생각해보라. 이런 어플리케이션은 기기에 복사하는 시간도 오래 걸리고, 런타임 프로세스의 시간도 오래걸린다. 정적 사이트, PHP 와 같은 파일 단위의 배치는 가볍고 빠르며, jar, war, rpm 과 같은 압축 파일은 중간이며 기기 전체는 느리고 무겁다. 이런 배치 단위의 크기를 기기 하나에 업데이트하는데 필요한 시간과 관련지을 수 있다.
새 버전을 설치할 때는 거시적 시간 척도와 미시적 시간 척도 모두를 고려해야 한다. 거시적 시간 척도는 전체 적용 작업이고, 미시적 시간 척도는 단일 인스턴스(호스트, 가상 머신, 컨테이너)에 적용된다.
미시적인 수준에서는 4가지 기간을 이해해야 한다.
- 전환을 준비하는데 걸리는 시간(가변 인프라의 경우, 파일을 정해진 위치에 복사해서 심볼릭 링크나 디렉토리 참조를 교체하는 시간, 불변 인프라의 경우 새 이미지를 배치하는 시간)
- 신규 요청이 들어오지 못하게 막고 나서 모든 작동이 멈추는데 걸리는 시간
- 변경 사항을 서버에 적용하는데 소요되는 시간(심볼릭 링크 변경만으로 적용된다면 순식간에 끝날 것이다.)
- 특정 기기에서 새 공개 버전이 작동하면, 그 인스턴스가 부하를 받을 준비가 될 때까지 걸리는 시간(캐시에 데이터를 적재하고, JIT 를 워밍업하고, 데이터베이스를 연결하는 등)
관계형 데이터베이스 스키마
DB 변경, 그 중 특히 RDB 의 스키마 변경은 예정된 중단 시간을 일으키는 요인 중 하나다. 데이터베이스 마이그레이션 프레임워크를 사용해보자. 관리자 CLI 에 SQL 스크립트를 실행하는 대신, 프로그래밍 방식으로 스키마 버전을 롤백할 수 있는 제어 수단이 있어야 한다. 어떤 스키마의 변경은 새 버전의 코드가 적용되기 전이라도 호환성 문제를 일으키지 않는다.
- 테이블 추가
- 뷰 추가
- 테이블에 Nullable 칼럼 추가
- 새 저장 프로시저 추가
- 트리거 추가
- 새 테이블이나 컬럼에 기존 데이터 복사
이런 변경은 무언가를 추가한다. 트리거는 쉼을 생성하기 위해서 쓰인다. 쉼은 기존 버전과 새 버전의 어플리케이션을 결합하는데 도움이 되는 약간의 코드이다.
현실적인 데이터 샘플로 테스트하는 것도 잊어서는 안된다. 테스트 환경에는 QA 에 좋은 데이터만 있었기 때문에 운영 환경에서 이전하다 실패하게 되는 경우가 흔하다.
스키마 없는 데이터베이스
스키마 없는 데이터베이스에는 애플리케이션에서 해결해야 하는 문제가 존재한다. MongoDB 에서 반환되는 문서나, 그래프 노드에 일정한 구조가 있기를 어플리케이션에서는 기대한다.
가장 처음 생성되었던 문서가 새 버전의 어플리케이션에서도 작동하는지를 따져봐야 한다. 이 문제를 해결하기 위한 3가지 방법이 있다.
- 어떤 버전이 생성했든 모두 읽을 수 있도록 번역 파이프라인을 구성해라
- 이전 루틴을 작성해서 배치 도중에 전체 데이터베이스에 걸쳐 실행하게 하는 것이다. 이 작업은 데이터의 양이 많아지면 매우 긴 시간이 소요될 수 있다.
- 점진 작업 후 일괄 처리
3번째 방법은 필자가 추천하는 방법이다. 새 버전에는 조건에 따라 작동하는 코드를 추가해 문서를 최신화하고, 최신 형식으로 저장하게끔 한다. 요청마다 조금씩 지연이 발생하고, 일괄 이전 작업 시간에 소요되는 시간을 많은 요청에 나누어 분산 처리할 수 있다.
시간이 충분히 지났음에도 오랫동안 접근할 일 없는 문서가 남아있을 수 있다. 이런 문서는 일괄 이전을 수행하면 그만이다.
웹 자산 파일
버전을 신경 써야 하는 대상이 DB 뿐만은 아니다. 애플리케이션이 모종의 사용자 인터페이스를 포함하고 있다면 이미지, 시트 파일, JS 파일과 같은 다른 자산 파일도 신경써야 한다.
정적 자산 파일은 언제나 캐시 해더를 사용해 먼 미래에 만료되도록 해야 한다. 변경된 애플리케이션을 배치할 때가 되면 브라우저가 새 버전의 스크립트를 가져오도록 해야 한다. 캐시 무효화란 브라우저를 포함해 중간의 프록시와 캐시 서버가 최신 버전을 가져오도록 만드는 여러 기법을 가리킨다.
1
<link rel="stylesheet" href="/styles/app.css?v=224dsf3"/>
이런 HTML 을 다음과 같이 수정한다.
1
<link rel="stylesheet" href="/styles/app.css?v=17cq3v8"/>
버전 식별 전호로 깃 commit SHA 값을 그대로 사용하는 것도 좋은 생각이다.
적용
새 코드를 기기에 설치할 때, 환경과 구성 관리 도구의 선택에 따라서 다양한 방법의 사용이 가능하다. 수명이 긴 기기에 변경 사항을 적용하는 수렴식 인프라를 생각해보자. 한 번에 기기 몇대를 바꿀지 생각해야 한다. 무중단이 목표이기에 작업 과정 내내 요청을 처리할 수 있는 충분한 기기가 정상작동해야 한다.
보통은 기기를 여러 묶음으로 나누어 설치한다. 알파, 브라보, 찰리, 델타, 폭스트롯이라고 부른느 다섯 그룹이 있다고 가정해보자.
- 알파에게 신규 요청을 받지 말라고 지시한다.
- 알파가 하던 일을 모두 끝낼 때까지 기다린다.
- 구성 관리 도구를 실행하여 코드와 구성을 갱신한다
- 알파 그룹의 모든 기기 상태가 정상이 될 때까지 기다린다.
- 알파가 요청을 수락하도록 지시한다.
- 브라보, 찰리, 델타, 폭스트롯에서 이 과정을 반복한다.
첫 번째 그룹은 카나리 그룹으로, 이상 징후가 있는지 모니터링해야 한다. 모든 어플리케이션과 서비스는 종단 간 상태 점검 경로를 가지고 있어야 한다. 부하 분산기는 그 경로를 확인해서 인스턴스가 작업을 수락하고 있는지 확인할 수 있다.
이런 상태 점검을 사용해 애플리케이션의 단순한 상태 변경만으로도 부하 분산기에게 새 작업을 보내지 않도록 알릴 수 있다. 위 단계에서, 알파 브라보는 작업이 끝나고, 찰리 그룹이 변경될 때 기존 버전과 새 버전이 동시에 실행되고 있는 상태에 도달한다.
불변 인프라에서는 코드를 설치할 때 기존 기기를 변경하지 않는다. 새 코드는 새 기기에서 띄워진다. 새 기기가 기존 클러스터에서 시작되면, 세션 고정을 통해 기존 버전의 호출은 기존 버전으로, 새 버전의 호출은 새 버전으로 흘러가게 해야 한다. 새 기기를 새 클러스터에서 구성하면, IP 주소를 전환하는 순간에 새로운 클러스터에서 요청을 받지만, 기존 클러스터에서 처리가 끝나지 않은 요청에 영향을 줄 수 있다.
배치가 자주 일어난다면 기존 클러스터에서 새 기기를 시작하는 것이 좋다.
작업 정리
데이터베이스 확장을 적용하고, 쉼을 추가하는 등의 작업으로 성공적인 이관을 했다고 가정해보자. 작업을 잘 마무리하는 것도 중요하다.
쉼은 쉽게 제거할 수 있다. 모든 인스턴스가 새 코드로 돌아가면 쉼 용도의 트리거는 더 필요하지 않으니 삭제하기만 하면 된다. 새로운 데이터베이스 이전 작업에는 삭제도 필요하다. 축약 또는 스키마 조이기라고 불리우는 작업은 다음과 같은 내용들이 있다.
- 오래된 테이블 삭제
- 오래된 뷰 삭제
- 오래된 테이블의 열 삭제
- 더는 쓰지 않는 alias 나 동의어 삭제
- 더는 호출되지 않는 저장 프로시저 삭제
데이터베이스 이전 프레임워크를 사용하든, 스크립트를 작성하든 모든 스키마 변경은 시간순으로 정리해서 보관하는 것이 좋다.
전문가의 배치
이제 배치는 빈번하고, 물 흐르듯 원할하게 진행되어야 한다. 운영에 적합하게 소프트웨어를 설계하듯 배치하기 좋은 소프트웨어를 설계해야 한다. 빌드 파이프라인은 아키텍트, 개발자, 설계자, 테스터, DBA 의 축적된 지혜를 모두 적용할 수 있어야 한다. 빌드 중에 테스트를 수행하는 것 이상이다.
사람이 규칙을 정하고, 기계가 그 규칙을 집행하게 해라. 예를 들면 외래키 제약 조건을 정의하면서 인덱스를 누락하면 빌드가 거부되어야 한다.