Search

Wiring Up Continuous Integration

Introduction to CI
간단히 설명하면 현재 작업하고 있는 Local Repository에서의 변경 사항들을 모두 Central Repository로 보내는 과정을 CI (Continuous Integration)이라고 한다.
CI의 사전적 정의는 위와 같은데, 결국에는 하나의 Central Repository로 코드들을 합치는데 의의가 있다. 보통은 큰 프로젝트에서 사용되거나, 한 프로젝트에 여러 Engineer가 작업하는 경우 사용된다. 이렇게 일반적이지 않은 경우에도, 개인적인 프로젝트에 CI를 이용하는 경우도 있다. CI를 쓰는데 어떤 장점이 있길래, 프로젝트에서는 CI를 사용하는 걸까?
우선 CI와 가장 많이 헷갈리는 CI Server를 짚고 넘어가자. CI Server는 Local Repository에서 Master Repository로부터 바뀐 점들을 선별하고, 바뀐 점들을 모든 코드들이 모여 있는 Master Repository로 통합해주는 역할을 수행한다. Mater Repository로 통합하기 전에 반복적으로 확인을 한다. 여기서 말하는 반복적인 확인은 Test를 말한다.
여기서 해볼 것은 CI를 수행해주는 CI Server에 대한 전반적인 Setting 및 구축을 해보는 것이다. CI Server를 만들기 전에 CI에 대한 플로우를 살펴보자.
CI Server runs all tests에서 병렬성 보장 및 Integration Test는 jest, Discrete Unit Test는 mocha를 쓰면 되겠다.
최근에는 CI를 제공해주는 정말 많은 Provider들이 있다. 아래와 같은 Providers 중에 우리는 Github와 잘 연동되는 Provider를 이용할 것이다. CI Provider들은 CI를 할 수 있는 Server를 제공해주며, 이 Server는 Cloud Service로 제공되는 Virtual Machine이다. 위에서 CI Server의 역할에서 밝혔듯이, 각 Provider가 Cloud Service로 제공하는 Virtual Machine들은 자동으로 코드를 Clone하고, 코드가 문제가 있는지 Test를 해준다.
**Travis와 Circle은 태생적으로 굉장히 유사하다. Travis를 쓸 줄 알면 어지간하면 Circle은 쓸 수 있을 것이다. 태성적으로 유사하기 때문에 동작하는 것도 비슷하다.
** Codebuild는 다른 AWS Service와 묶여 있기 때문에 조금 Advanced한 느낌이 있다.
** 결론은 서로 다른 CI Provider들이지만, CI를 하는 플로우는 전반적으로 비슷하다.
Github를 통해서 CI를 쓰는 경우 훌륭한 CI를 제공해주는 Travis라는 Provider가 있다. 우선 Travis를 통한 CI를 하기 위해선, 아래와 같은 준비가 필요하다.
위에서 CI의 플로우를 밝혔지만, Travis를 이용할 떄의 CI 플로우를 다시 정리해보면 아래와 같다.
위 과정을 보면 알겠지만... Github에 푸시를 했을 때 코드 변경을 알아채는 것도, 코드를 Clone하는 것도, Test 이후 문제가 없으면 간단하게 Notification을 보내는 것도 모두 Travis가 해주는 것을 알 수 있다. 즉, 가장 중요한 것은 어떻게 Test할지 Test 옵션들을 설정하는 것이다.
The Basics of YAML Files & Setup
YAML File이 무엇이고, YAML File 내에 작성해야 하는 것들을 살펴보자. YAML File은 평범한 JSON 파일이다. Key & Value 쌍으로 이뤄져있고, Numbers와 Strings를 받을 수 있다. 여기 작성한 설정 값을 토대로 Travis가 작업을 수행하게 된다.
** YAML to JSON Converter 역할을 해주는 Web Site에 들어가게 되면 해당 기능을 수행할 수 있는데, 특이한 점은 Object 혹은 Array로 표현할 때는 Indent가 필요하다는 점이다. YAML에서의 Array, Object의 차이는 - 표시가 붙냐 안 붙냐이다. -가 붙은 것은 Array라고 보면 된다.
YAML File을 통해 Travis에 대한 전반적인 작업을 설정하고 싶다면, docs.travis-ci.com의 Official Document를 통해 도움을 얻을 수 있다.
YAML File은 프로젝트 내에서 최상단 경로에 .travis.yml 파일로 생성한다.
1.
프로젝트 내에서 사용하는 언어에 대해서 명시한다.
2.
어떤 버전의 언어를 사용하고 싶은지 명시한다. (Array로 명시한다.)
3.
Distribution에 대해 명시한다. (생성될 Virtual Machine의 Base Image(OS)이다.)
** trusty는 Linux의 작은 Version 중에 하나로써 작은 Virtual Machine에 적합하다.
4.
프로젝트 내에 생성되길 바라는 Service를 명시한다. (프로젝트 내에 연결되어 있는 Service들을 말한다. MongoDB, Redis와 같은 것들이 해당 된다. 이미 Running 중인 Service들을 연결할 수도 있지만, 그것보다는 CI Server 내에 자체적으로 사용하는 것이 더 빠르다. Array로 명시한다.)
여기까지가 보편적인 세팅 방법이라고 한다면, 이 이후에는 프로젝트에 특화된 세팅들을 명시하게 된다.
1.
환경 변수 값들을 명시해줘야 한다. CI 환경 내에서 사용할 환경 변수를 줘야하기 때문에, .travis.yml 내에 환경 변수 값을 명시해야 한다. .travis.yml의 환경 변수 값은 Array로 준다. 모든 환경 변수들을 기입하는 것보다, 실행 환경에 대한 환경 변수만 넣어두고 나머지는 .env 파일에 유지하여 사용하는 것이 바람직하다. (여기서 실습하는 대로 하게 되면 .env를 사용하지 않고 config 내에 ci.js를 두므로, ci.js에 설정하여 사용한다.) 또한, 여기에 명시되는 환경 변수 설정 값은 1줄에 모두 기입해야 한다. (이유는 아래와 같다.)
** 환경 변수를 설정할 때, 환경 변수 값이 여러 라인이라면 각각에 대해서 build된다. 예를 들어서 NODE_ENV=ci와 PORT=3000이 각 라인으로 주어졌을 때, 두 환경 변수 값이 하나의 build에 들어가는 것이 아니라 두 개로 나뉘어서 들어간다. 따라서 한 build는 NODE_ENV=ci, 나머지 한 build는 PORT=3000을 갖게 된다. 따라서 NODE_ENV=ci와 PORT=3000을 한 줄에 두어 하나의 build를 유지한 채로 만든다.
2.
cache에 대한 값을 명시한다. 일종의 node_modules 가 Commit되지 않도록 만들어 준다. 이는 변경된 코드들을 Clone하게 되었을 때, cache된 node_modules를 그대로 불러오게 된다. 그렇다면 사용해야 하는 Dependency가 바뀌면 위 node_modules는 어떻게 될까? 애초에 새로운 코드가 Clone되면, npm install을 하게 된다. (아래 설정 값으로 npm install을 하게 만들 것이다.) 만일 변경 사항이 없다면 기존 node_modules를 이용하면서 빠르게 처리될 것이고, 변경 사항이 있다면 문제 없이 바뀐 node_modules를 이용하게 되면서 바뀐 node_modules는 cahce 처리 된다.
3.
실행하고 싶은 Command-Line을 명시한다. (Virtual Machine에서 실행될 수 있도록만들어준다.)
4.
script에 대해서 작성한다. 이는 3번에 적은 Command-Line을 명시하는 것과 크게 다르진 않다. 다만 Travis 내의 설정들을 실행할 때, 3번에 대한 Command-Line을 모두 실행하고 난 뒤 해야할 작업들에 대해서 명시한다. (nohup npm start &와 같은 명령어를 기재한다. 다만 Server 실행에 요구되는 시간이 조금 있다보니, sleep 3와 같이 3초간 잠시 대기하는 명령어를 같이 기재한다.)
위 8가지에 대한 .travis.yml의 내용은 다음과 같다.
language: node_js node_js: - '8' dist: trusty services: - mongodb - redis-server env: - NODE_ENV=ci PORT=3000 cache: directories: - node_modules - client/node_modules install: - npm install - npm run build script: - nohup npm run start & - sleep 3 - npm run test
YAML
복사
** install 부분에 들어가야하는 Command-Line 중 npm run build에 대한 것은 아래와 같다. dev 부분과 prod 부분의 react (client) 실행이 서로 다르다. prod에는 react app이 일종의 final output으로써, build되어 사용된다. 따라서 react app내의 스크립트를 빌드하는 과정이 필요하다. (npm run build를 실행하면, package.json에 기입된 명령어를 실행하면서 react app을 만들게 된다.)
** react app에 대해 조금 더 자세히 언급하면, dev에서는 Local Machine에서 돌아가는 React Server에 의해 제공된다. 반면에 ci혹은 prod에서는 Local Machine의 React Server에서 이를 제공하는 것이 아니라, build된 파일들을 통해 제공 받게 된다.
** dev모드와 ci모드에서의 Backend 구조는 다음과 같다.
More Server Configuration
.travis.yml을 통해서 CI에 대한 전반적인 Setting을 끝냈다면, MongoDB와 Redis Connection을 위한 Setting을 해야 한다. (위의 그림과 같은 구도가 나오도록 URL과 포트를 CI 모드의 환경 변수로 나타내야 한다.) Official Document를 살펴보면, 어떤 식으로 .travis.yml에 설정 값을 줘야하는지 나타나 있다.
** MongoDB는 기본적으로 127.0.0.1에서 돌도록 되어 있다. 이에 맞춰서 IP를 설정한다.
** Redis는 MongoDB보다 더 설명이 없다.
조금 더 구체적인 나머지 Configuration은 아래와 같다. 사실 장황한 것 같지만 별 거 없다. ci.js 파일만 추가하면 된다.
새롭게 추가한 ci.js는 아래와 같다.
module.exports = { googleClientID: '70265989829-0t7m7ce5crs6scqd3t0t6g7pv83ncaii.apps.googleusercontent.com', googleClientSecret: '8mkniDQOqacXtlRD3gA4n2az', mongoURI: 'mongodb://127.0.0.1:27017/blog_ci', cookieKey: '123123123', redisUrl: 'redis://127.0.0.1:6379', };
JavaScript
복사
cache.js는 다음과 같은 코드가 수정되었다.
// hard coded url replaced // const redisUrl = 'redis://127.0.0.1:6379'; const keys = require('../config/keys'); const redisUrl = keys.redisUrl;
JavaScript
복사
index.js에서의 코드 중, prod에서 static 파일을 제공하는 코드는 다음과 같이 수정되었다.
if (['production', 'ci'].includes(process.env.NODE_ENV)) { app.use(express.static('client/build')); const path = require('path'); app.get('*', (req, res) => { res.sendFile(path.resolve('client', 'build', 'index.html')); }); }
JavaScript
복사
A Touch More Configuration
.travis.yml의 설정 값들을 정하고 Server 사용에 대한 설정 값들을 정했다면, CI 모드에서 Integration을 성공했을 때 실행될 Test에 대해서 조그마한 설정이 필요하다.
실제로 dev 모드에서 Test를 진행하거나 Test에 대해서 설계를 할 때는 Chromium이 Headless인 경우 많은 어려움을 겪기 때문에, Puppeteer를 통해 생성한 Browser의 headless 옵션 값은 false이다.
하지만 ci모드에서는 Test 진행 시 우리가 보지 않아도 되기 때문에, headless 값을 true로 만들어준다. 또한 추가 인자로 args라는 Key 값이 들어가게 되는데, 이에 대한 Value 값으로는 Array가 들어간다. 그 값은 ['—no-sandbox']가 되겠다. 이를 주는 이유는 Test에 대한 수행 시간을 획기적으로 줄일 수 있기 때문이다. (sandbox는 보안 관련 옵션이다. Test 환경이다보니 Chromium Instance에서 보안을 따질 필요가 없기 때문에 이를 끄고 진행하면 꽤나 많은 시간을 단축하여 Test할 수 있다.)
수정된 CustomPage 클래스의 build 함수는 아래와 같다.
static async build() { const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'], }); const page = await browser.newPage(); const customPage = new CustomPage(page, browser); return new Proxy(customPage, { get: function (target, property) { return target[property] || page[property] || browser[property]; }, }); }
JavaScript
복사
마지막으로 가장 중요한 것이 있는데, 접속 IP Address이다. ci 모드에서 Server 구동은 포트 3000번이고, Test를 수행할 때 접근하는 포트도 3000번인데 무슨 문제가 있냐 싶겠지만 매우 큰 차이가 있다. dev 모드에서 수행한 Test에서 3000번에 대한 접근은 React Server에 대한 접근이었다. (React Server 접근을 통해서, 뒤에 돌고 있는 5000번 포트의 Server로 요청을 보내는 것이었다.) React Server가 돌고 있었기 때문에 localhost:3000으로 요청을 보내도 무방하였다. 하지만 ci에서는 react app이 build 되어 static파일로 Server에 제공되고, 이 Server가 3000번으로 돌아가고 있는 상황인데, React Server가 동작하고 있지 않은 상태이다 보니 localhost:3000으로 주면 오류가 발생한다. 따라서 IP Address 앞에 http://를 붙이도록 한다.
Git Repo Setup & Travis CI Setup
1.
Github에 Repository를 생성하여 프로젝트를 올린다. (아래 그림과 같이 기존 Github과 연동되어 있다면 remote 설정을 통해 Github 연동 정보를 바꾼다.)
remote의 origin 보기
git remote -v
remote의 origin 지우기
git remote remove origin
remote의 origin 추가하기
git remote add origin $repoUrl
2.
Travis가 현재 프로젝트가 담겨 있는 Repository를 볼 수 있게끔 설정한다. (travis-ci.org 사이트에 접속한다. 사이트에 로그인하면 Github OAuth를 통해 로그인하게 된다. 이 때 좌측을 보면, Travis를 사용하고 있는 Repository 리스트들을 볼 수 있다. + 버튼을 누르면 각 Repository에 대해 Travis가 Github Repository를 볼 수 있도록 Activate 시킬 것인지 체크할 수 있는 것을 볼 수 있다. 이를 on 하기만 하면 된다.)
3.
.travis.yml 파일이 추가 된 상태로 프로젝트를 Repository에 Push한다.
4.
Travis를 사용하도록 Activate도 되어 있고 .travis.yml도 추가 된 상태로 Push를 했으니, Travis는 Push를 감지하고 CI를 실행하여 프로젝트를 .travis.yml의 설정 값에 맞춰 build하게 된다. (Travis 웹 사이트에서 Detail을 보면 build 된 것을 볼 수 있다.)
5.
script에 기재한 Command-Line들을 모두 무사히 수행했다면, passing build를 띄우게 된다. 만일 하나라도 실패하면 build에 fail 했다는 것을 볼 수 있다.
6.
passing build의 값을 받았다면, Travis의 기본 동작 상 Github에 연동된 email로 수행 결과를 보내게 된다.
느낀 점?
이런 Travis를 이용하게 해주는 .travis.yml 파일의 설정 값들과 변동 사항들은 프로젝트마다 달라지니 위 설정을 참고 정도만 하고, CI의 개념에 대해서 알아두는 것이 좋아 보인다. 또한 TDD를 지향하는 경우 TDD의 실행 환경을 CI로 두면서 문제가 있는지 확인하며 작업을 할 수 있어 굉장히 유용해 보인다. (npm run test를 일일이 하지 않아도, git push만 하면 변동 사항 Detecting에 따라 CI를 하게 되고 이 때 Test를 알아서 수행하게 할 수 있으니 간편해 보인다.)