2016. 6. 30.

npm, bower, gulp 를 이용한 웹 클라이언트 개발 환경 구축

서론


포스트를 작성해놓고 다른 소스들을 보다 보니 뭔가 문제가 많은 포스트네요.
포스트를 내릴까 하다가 어차피 제가 나중에 보려고 정리하는 포스트들인 것을 생각해서 일단 놔두려고 합니다.
개발 환경 관련 좀 더 나은 소스를 원하시는 분들은 https://github.com/angular/angular-seed 의 소스를 확인하시는 것이 더 현명한 선택이지 싶습니다. (2016/07/07)

그동안 Spring-cloud를 기반으로 마이크로서비스 아키텍처 구성과 관련된 포스트를 작성하다가 왜 갑자기 웹 클라이언트 개발 환경 구축인가 뜬금없다고 생각하시는 분들이 많을 것 같습니다.
아직 마이크로서비스 아키텍처도 정리할 것들이 많이 남았는데, 갑자기 개인적으로 진행하던 프로젝트에서 목업을 만들어야 할 일이 발생했습니다. 그래서 목업이지만 그냥 생각 없이 만들지 말고 새로운 환경구성을 해보자 하는 생각으로 이 포스트의 내용이 시작되었습니다. 오래 생각할 시간이 없어서 급히 이리저리 알아보니 npm, bower, gulp를 이용해서 만들면 뭔가 괜찮은 구조가 나올 것 같다는 생각을 하게 되었습니다.
뭐 제가 정리하는 포스트 대부분이 그렇지만 이번 경우에는 특히나 짧은 시간에 공부하고 구성하고 정리하다 보니 약간은 이상한 구조가 나와버렸네요. 그래도 참고가 되면 좋겠습니다.

전체 소스는 언제나 처럼 Github 에 올려놓았습니다.
Github Repository : https://github.com/roadkh/blog-npm

사전준비사항


포스트가 npm, bower, gulp를 이용해야 하니 우선 설치를 해야 합니다. 간단하게 설명을 했습니다만, 자세한 사항은 각 항목의 링크를 클릭하세요. 각 모듈의 링크에서 보시고 환경에 맞게 설치를 먼저 진행해 주세요.

  • node.js : npm, bower, gulp 모두 node.js를 기반으로 합니다. 우선 node.js가 먼저 설치가 되어있어야만 합니다. 아래 제 환경을 보시면 node.js 6.x 대를 설치했습니다만, 이 포스트를 보시고 설치하실 분들은 4.x 대를 설치하시기 바랍니다. 이 포스트를 작성하는 시점(2016-06-28)에서는 아직 node.js 6.x는 안정화 버전은 아닌 것 같네요. 전 몇 가지 해 볼 일이 있어서 이전에 6.x 대를 설치했던 것을 그대로 사용했습니다.
  • npm : node.js  설치하시고 나면  npm 은 설치가 되어있습니다. 하지만, 혹시 npm 버전이 업데이트가 있을지도 모르니 npm install npm -g 한번 해 주세요. 저도 이 포스트 정리하면서 한번 해 보니 버전이 3.9.3 에서 3.9.5 로 올라가네요. npm install 할 때 -g 옵션을 주는 경우에는 시스템에 따라 sudo 를 이용해야 할 수도 있습니다. (자세한 사항은 https://docs.npmjs.com/getting-started/installing-node 참고)
  • bower : npm install bower -g 를 이용하여 설치합니다. (https://bower.io/ 참고)
  • gulp : npm install gulp -g 를 이용하여 설치합니다. (https://github.com/gulpjs/gulp/blob/master/docs/getting-started.md 의 설명과 조금 다릅니다만 저는 위 방법을 이용해서 설치했습니다.)

bower와 gulp는 나중에 npm을 통해서 로컬에 의존성 설치를 진행하게 됩니다. Global 옵션으로 설치를 꼭 해야 하는지는 모르겠습니다. 제 로컬 환경에는 역시나 먼저 설치를 했었어서... 지우고 확인을 해야 하겠지만 귀차니즘 때문에.. 컥!! 죄송합니다. 궁금하신 분들은 위 npm 만 설치하고 아래 내용을 진행해 보시기 바랍니다. 정말 죄송합니다.

블로그 작성에 사용한 환경


역시나 제 개발 환경은 변화가 없습니다. 다만, 이번엔 자바 관련 환경은 필요가 없으니 node.js 관련 환경만 정리했습니다.

  • Ubuntu 14.04 LTS
  • Node : 6.2.1 
  • Npm : 3.9.5
  • Bower : 1.7.9
  • Gulp : CLI 3.9.1, Local 3.9.1

각 모듈의 역할


우선은 제가 구성하는 환경에서 각 모듈이 갖는 역할을 먼저 정리해 볼까 합니다.

Npm 

Npm의 역할은 주로 개발을 위한 인프라 아키텍처에 대해 관리를 하는 역할을 합니다. 설명하기 좀 힘들어서 간단히 예를 들면, 개발 중 간단히 npm start 를 통해서 웹서버 하나를 구동해서 테스트를 진행하려 합니다. 이럴 때 npm 의 의존성 관리를 이용해서 http-server 를 설치해서 사용하게 됩니다. 이 외에도 gulp 라던가 bower와 각종 플러그인 패키지들을 의존성 관리해주는 역할을 합니다. angular-seed project의 경우에는 unit test 라던가 integration test 등도 npm을 통해서 할 수 있게 되어있습니다. (구조가 아주 좋습니다. angularjs 로 프로젝트 하시려는 분들에게 좋은 참고자료입니다.)

Bower

제가 볼 때 이 구성의 핵심입니다. 실제 프로젝트에서 사용할 자바스크립트 모듈들의 의존성을 관리하는 역할을 합니다. 이 포스트에서는 angular와 angular-material을 중심으로 의존성을 추가하게 됩니다.

Gulp

뭐라고 설명을 해야 할까요? 빌드툴의 역할을 합니다. 자바스크립트 파일과 CSS 파일을 minify 한다거나 파일을 카피한다거나 다양한 역할을 진행하게 됩니다.


프로젝트 디렉터리 구조 및 파일 구성


포스트에 정리된 내용으로 진행하면 최종적으로 아래와 같은 디렉터리 구조와 파일들을 가지게 됩니다. 
위 이미지에서 상단의 붉은 박스 안의 디렉터리들은 따로 만든 디렉터리가 아니라 작업 중에 npm 과 bower, gulp 가 만들게 되는 디렉터리입니다. 일부는 git이나 intellij가 만들어낸 것들이라 혼란을 피하고자 지웠습니다. 

이제 적당한 디렉터리를 하나 만들고 그 디렉터리 내에서 작업을 시작하겠습니다.

package.json 의 생성


package.json 파일을 작성하는 방법은 두 가지 입니다. 직접 vi 등으로 작성하는 방법과 npm init 을 통해 작성하는 방법입니다. package.json 의 경우 필수 항목은 두 개라고 합니다. 그 필수 항목은 "name"과 "version" 입니다.
npm init 을 하시면 하나씩 질문이 나오면서 진행을 하게 됩니다. 
  • 첫 질문은 name입니다. 보통은 기본값으로 디렉터리명이 사용되는데 다만 이 이름에는 몇 가지 제약이 있습니다. 모두 소문자여야 하고 공백이 없는 하나의 단어여야 하며 특수문자는 dash(-)와 underscore(_)만 허용됩니다. 저는 blog-npm 으로 만들었습니다.
  • 다음은 version입니다. 기본값으로 1.0.0 이 나옵니다. 나중에 바꿀 수도 있으니 전 엔터로 진행했습니다.
  • description을 물어봅니다. 입력 안 해도 됩니다만 간단히 적어봅니다. 
  • 이 뒤로 별로 중요하지 않은 질문들이 계속됩니다. 그냥 엔터 쳐서 모두 진행했습니다.
  • license에 대한 질문을 마지막으로 Is this ok? 라고 물어봅니다. yes 치고 마무리하겠습니다.
이제 디렉터리를 보면 package.json 이 만들어졌습니다. 나중에 bower에서 사용할 자바스크립트 라이브러리 받아보시면 그곳에도 대부분 package.json 이 있는데 열어보시면 지금 만드신 내용과 크게 다르지 않을 겁니다.

여기서 npm install 이라고 쳐서 우선 package.json을 기반으로 npm 이 환경을 구성하게 하겠습니다.
node_modules 라는 디렉터리가 생기는데 현재는 비어있을 겁니다. 다음에 의존성을 추가하기 시작하면 해당 디렉터리에 관련 모듈들이 이 디렉터리에 들어가게 됩니다.

package.json 에 의존성 추가하기


package.json에 의존성을 추가하는 것도 두 가지 방법이 있습니다.
  • 직접 package.json에 수동으로 넣은 다음 npm install 을 실행.
  • npm install --save(or --save-dev) <package name> 을 통한 추가.
저는 여기서 후자를 선택하겠습니다.
우선 bower와 gulp를  의존성 추가해야겠네요.

npm install --save-dev bower 
npm install --save-dev gulp

위 명령을 보면 --save-dev를 줬는데 이 옵션은 서버용 node 프로그램을 개발하시는 분들에는 중요할지 모르겠으나 현재까지 제가 파악한 바로는 웹 개발에서는 큰 영향은 없어 보입니다. 그래도 일단 --save-dev 옵션을 주고 추가합시다. package.json 파일을 열어보면 아래와 같이 내용이 바뀌었을 것입니다.

{
  "name": "blog-npm",
  "version": "1.0.0",
  "description": "Npm + Bower + Gulp for client web development",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "road <roadkh@gmail.com>",
  "license": "ISC",
  "devDependencies": {
    "bower": "^1.7.9",
    "gulp": "^3.9.1"
  }
}

보시면 버전 표시 부분이 조금 신기하죠? Semantic Versioning(Semver)라고 하는 것입니다. 짧게 설명하는 데는 워낙 능력이 없어서 잘못하면 너무 길어질 것 같으니 [여기]를 클릭하셔서 확인해 주세요.


bower.json 파일과 .bowerrc 파일 추가


bower.json 파일을 만드는 방법도 두 가지 입니다.

  • 직접 bower.json 파일을 생성하고 내용을 입력
  • bower init 명령을 통한 생성 : 앞에서 npm init 을 통해서 했던 작업과 비슷합니다. 대부분 package.json의 내용을 기본값으로 물어봅니다. 일단 default 값을 보시고 엔터만으로도 bower.json 파일을 생성하실 수 있습니다. 그 후에 수정하시면 되니 편하게 이렇게 만드세요.
위 두 번째 방법으로 생성하시면 아래와 같은 모양이 됩니다.
bower.json의 spec은 [여기]서 확인하실 수 있습니다.

{
  "name": "blog-npm",
  "description": "Npm + Bower + Gulp for client web development",
  "main": "index.js",
  "authors": [
    "road <roadkh@gmail.com>"
  ],
  "license": "ISC",
  "homepage": "",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ]
}


.bowerrc 파일은 bower 가 실행될 때 항상 먼저 실행이 되는 기본적이 내용을 정의하게 됩니다.
리눅스 계열이나 애플의 OS X 를 사용하셨던 분들은 이름이 좀 친숙할 것 같네요. .bashrc 라던가 하는 것과 비슷한 역할을 하게 됩니다. .bowerrc 파일에 대해 궁금하신 분들은 [여기]를 클릭하시면 자세한 설명이 있습니다. .bowerrc 파일에는 단순히 bower 에 의해 관리되는 의존성 파일들을 다운로드 할 디렉터리만 설정하도록 하겠습니다. 위 링크를 보시면 .bowerrc에 설정할 수 있는 다른 정보들도 확인하실 수 있어요.

{
  "directory": "bower_components"
}

사실 위의 내용이라면 구지 설정 안하셔도 됩니다. 원래 저게 기본값입니다. (github의 소스에는 .bowerrc 파일이 없습니다)

bower를 이용해 의존성 추가하기


bower는 실제로 프로그램에서 사용할 모듈들에 대한 의존성을 관리하기 위해서 사용한다고 했습니다. 실제로 bower는 더 많은 기능을 가지고 있습니다만, 저는 그런 용도로만 사용하고자 합니다.
우선 샘플 프로젝트에서 사용할 가장 중요한 의존성은 angular-material 패키지입니다.
아래 명령어를 통해서 angular-material 패키지를 추가하도록 하겠습니다.

bower install --save angaulr-material

위 명령을 실행하면 bower_components 라는 디렉터리가 생겼을 겁니다. 안에 내용을 보시면 전 angular-material 만 추가했는데 여러 개의 angular 모듈이 함께 다운로드 되어있을 것입니다. 의존성이 있는 관련 패키지들이 함께 다운로드 됩니다.

이왕 한 것 angular-loader, angular-route 도 위 명령어를 통해 추가해 보았습니다.
여기까지 작업한 bower.json은 아래와 같은 모양이 됩니다.

{
  "name": "blog-npm",
  "description": "Npm + Bower + Gulp for client web development",
  "main": "index.js",
  "authors": [
    "road <roadkh@gmail.com>"
  ],
  "license": "ISC",
  "homepage": "",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ],
  "dependencies": {
    "angular-material": "^1.0.9",
    "angular-loader": "^1.5.7",
    "angular-route": "^1.5.7"
  }
}

gulp 설정 시작하기


이제 드디어 gulp 설정을 시작하기 전에 gulp 가 할 일을 잠시 정리해 보겠습니다.

  • bower_components 하위에 있는 모듈들을 디렉터리 구조를 유지하면서 그 안에 있는 js와 css를 build/assets/ext 디렉터리로 복사합니다. 이때 해당 모듈의 minify 된 js와 css가 있으면 해당 js와 css를 복사하고 없으면 일반 js와 css를 복사합니다.
  • src/js 하위에 있는 자바스크립트 파일을 minify하고 파일명을 .min.js 형식으로 변경한 후에 build/assets/js 에 디렉터리구조를 유지하면서 복사합니다.
  • src/css 하위에 있는 css 파일을 읽어서 지정된 브라우저 설정에 맞는 처리를 진행하고 minify 한 후에 파일명을 .min.css 형식으로 변경하여 build/assets/css 에 디렉터리 구조를 유지하면서 복사합니다.
  • src/html 에 있는 html 파일을 디렉터리 구조를 유지하면서 build/ 폴더로 복사합니다.
  • 목업 작업 중 위에 지정한 작업이 계속해서 이루어져서 일일이 명령어를 치지 않도록 합니다.
  • 마지막으로 build 디렉터리를 지우는 명령도 있었으면 합니다.
이에 따라 필요한 모듈들이 있는데 원래는 하나씩 찾아서 작업 중 추가를 하게 됩니다만, 포스트에서는 한꺼번에 설정해서 의존성을 맺도록 하겠습니다. 의존성 추가는 npm install --save-dev 명령을 이용하도록 합니다. 추가할 모듈은 아래와 같습니다.
  • gulp-load-plugins : 지금부터 설치할 모든 npm 중 gulp 관련 모듈 패키지들을 쉽게 사용.
  • bower-main : bower_components 폴더의 파일에 대한 처리를 위해 추가.
  • gulp-rename : 파일명을 바꾸기 위해 추가.
  • gulp-uglify : 자바스크립트의 minify 처리를 위해 추가.
  • gulp-autoprefixer : 지원하고자 하는 브라우저의 정보를 설정해서 적절한 처리를 자동으로 진행하게 하는 모듈 패키지. https://www.npmjs.com/package/gulp-autoprefixer 의 내용을 참고.
  • gulp-uglifycss : CSS 파일의 minify 처리를 위해 추가.
  • gulp-watch : 작업중에 작업된 내용을 실시간으로 gulp 에 설정한 내용이 실행되도록 하기 위해 추가.
  • del : build 디렉터리를 지우는 clean task 를 만들기 위해 추가.
자 이제 package.json 파일의 devDependencies 는 아래와 같은 모양이 됩니다.

  "devDependencies": {
    "bower": "^1.7.9",
    "bower-main": "^0.2.14",
    "gulp": "^3.9.1",
    "gulp-autoprefixer": "^3.1.0",
    "gulp-load-plugins": "^1.2.4",
    "gulp-rename": "^1.2.2",
    "gulp-uglify": "^1.5.4",
    "gulp-uglifycss": "^1.0.6",
    "gulp-watch": "^4.3.8"
  }

이제 gulpfile.js 를 만들고 필요한 task 들을 정의하는 일들만 남았습니다.
사실 단계로 정리를 할까 했었습니다만, 직접 소스를 보는 것이 더 빠르겠기에 소스의 주석으로 대신 정리를 할까 합니다.

/* gulp 모듈을 import 합니다. 가장 기본이고 필수입니다 */
var gulp = require('gulp');

/* 작업을 진행할 source 디렉터리와 결과물을 보관할 build 디렉터리를 지정합니다. */
var src = './src/';
var dest = './build/';

/* 
    npm 에 추가한 모듈들을 일일이 require하지 않고 사용하기 위한 처리입니다.
    예를 들어 bower-main의 경우는 plugins.bowerMain() 식으로 사용이 가능합니다. 
    gulp-로 시작하는 모듈들의 경우는 gulp-를 뺀 나머지로 사용가능합니다.
*/
var plugins = require('gulp-load-plugins')({
    pattern: ['gulp-*', 'gulp.*', 'bower-main'],
    replaceString: /\bgulp[\-.]/
});

// Minify & copy JS
/* 
    자바스크립트에 대한 처리를 진행합니다. 
    {base: ''} 옵션을 넣은 이유는 이렇게 해야 디렉터리 구조를 유지한 상태로 복사합니다.
    그렇지 않을 경우에는 build 디렉터리에 그대로 파일을 복사합니다. 
    혹시 concat모듈을 이용해서 하나의 거대 파일로 합칠 경우에는 위 옵션은 필요없습니다.
    처리의 내용은 src/js/ 하위의 모든 js 파일을 읽어서 이름을 .min.js 로 변경하고 
    minify를 진행한 후에 build/assets/js 로 복사하라는 내용입니다.
*/ 
function processJS() {
    return gulp.src(src + 'js/**/*.js', {base: src + 'js/'})
        .pipe(plugins.rename({suffix: '.min'}))
        .pipe(plugins.uglify())
        .pipe(gulp.dest(dest + 'assets/js'));
}
// gulp에 scripts라는 task로 지정합니다.
gulp.task('scripts', processJS);


// Minify & copy CSS
/*
    CSS 파일에 대한 처리를 진행합니다. 자바스크립트와 거의 동일한데 autoprefixer 부분만 특징적입니다.
    아래 설정은 모든 브라우저의 최근 2개버전을 처리하기 위한 css 처리를 하라는 내용입니다.
*/
function processCss() {
    return gulp.src(src + 'css/**/*.css', {base: src + 'css/'})
        .pipe(plugins.autoprefixer({
            // browsers: ['last 2 Chrome versions', 'last 2 Firefox versions', 'last 2 Android versions', 'last 2 ChromeAndroid versions'],
            browsers: ['last 2 versions'],
            cascade: true
        }))
        .pipe(plugins.rename({suffix: '.min'}))
        .pipe(plugins.uglifycss())
        .pipe(gulp.dest(dest + 'assets/css'));
}
// gulp에 css라는 task로 지정합니다.
gulp.task('css', processCss);


// Copy HTML
/*
    HTML 파일들을 복사합니다.
*/
function processHtml() {
    return gulp.src(src + 'html/**/*.html', {base: src + 'html/'})
        .pipe(gulp.dest(dest));
}
gulp.task('html', processHtml);


// Copy Vendor JSS & CSS from bower_components
/*
    bower_components 디렉터리에 있는 js와 css 들을 복사합니다.
    bower_main 모듈의 처리 결과는 minified, minifiedNotFound, normal 있는데 내용은
    minified : minify 처리된 파일
    normal : 일반 파일
    minifiedNotFound : minify 처리된 파일이 없는 파일들의 일반 파일
    입니다.
    각 파일들의 파일명 리스트를 모두 합쳐서 디렉터리 구조를 유지시키면서 assets/ext 로 복사합니다.
*/
function processExternal() {
    var scriptBowerMain = plugins.bowerMain('js', 'min.js');
    var cssBowerMain = plugins.bowerMain('css', 'min.css');
    var mapBowerMain = plugins.bowerMain('js', 'min.js.map');
    return gulp.src(scriptBowerMain.minified.concat(scriptBowerMain.minifiedNotFound).concat(cssBowerMain.minified).concat(cssBowerMain.minifiedNotFound).concat(mapBowerMain.minified), {base: './bower_components'})
        .pipe(gulp.dest(dest + 'assets/ext'));
}
// gulp 에 external 이라는 task 로 지정합니다.
gulp.task('external', processExternal);

// Watch for changes in files
/*
    실시간 감시를 위한 task 등록입니다.
    지정한 디렉터리를 감시하고 있다가 추가/삭제/변경 등이 발생할 경우에는 지정한 함수를 호출합니다.
    원래 gulp.watch 가 있는데 이 경우는 변경사항은 감지가 되지만 추가/삭제는 감지가 되지 않습니다.
    그래서 gulp-watch 모듈을 이용했습니다.
*/
gulp.task('watch', function () {
    plugins.watch(src + 'js/**/*.js', processJS);
    plugins.watch(src + 'css/**/*.css', processCss);
    plugins.watch(src + 'html/**/*', processHtml);
    plugins.watch('./bower_components/**/*', processExternal);
});

// Default Task 입니다.
gulp.task('default', ['scripts', 'css', 'html', 'external', 'watch']);

/**
 * clean build.
 * 원래는 default task에 넣어서 gulp가 시작될 때 깨끗하게 build 디렉터리를 비우게 만들고 싶었는데,
 * 오류가 발생해서 잘 안됩니다. 뭔가 방법이 있을텐데 쉽게 찾아지지는 않네요.
 */
gulp.task('clean', function(cb) {
    plugins.del(['build/**'], cb);
});

이제 거의 마지막까지 왔습니다.

마지막으로 npm 으로 HTTP 서버를 띄워서 작업을 쉽게 할 수 있도록 하겠습니다.

npm install --save-dev http-server

package.json의 scripts 부분에 아래와 같은 내용을 추가하겠습니다.
한 가지 더 추가를 하는데, 이것은 npm install 명령을 이용해서 최초 구성을 할 때 bower install 명령이 함께 실행되도록 하려는 내용입니다.
위 두 가지 내용을 scripts 의  postinstall 과 start 속성으로 추가합니다.

...
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "postinstall": "bower install",
    "start": "http-server -a localhost -p 8000 -c-1 ./build"
  },
...

이제 작업은 완료되었습니다.
확인을 해야 되겠지요? 현재 작업한 내용이 없으므로 package.json, bower.json, gulpfile.js 파일과 혹시 만드셨다면 .bowerrc 파일까지만 남기고 모든 내용을 삭제한 후에 npm install 을 실행합니다.
bower_componets와 node_modules 디렉터리가 만들어지고 의존성 파일들을 다운로드 받았는지 한번 확인해 보면 됩니다. 그리고 gulp 명령을 실행해서 별다른 오류 메시지 없이 대기하고 있는지 확인하면 작업 환경은 완료입니다.
여기서 일단 gulp 는 Ctrl+c 등으로 일단 종료하시고 src 디렉터리를 만드신 후에 그 하위에 js, css, html  디렉터리를 만듭니다. 다시 gulp 를 실행하시고 작업을 진행하면서 처리가 된 파일들이 build 디렉터리 안으로 적절히 이동되는지 확인하면 완료입니다.

혹시 몰라서 sample 디렉터리 안에 angular-seed 프로젝트의 샘플 내용을 조금 수정해서 넣어놨습니다. 해당 디렉터리 안의 내용을 한꺼번에 복사하시면 잘되는 경우도 있고 오류가 발생하는 경우도 있습니다. 제 경우에는 약 20~30% 정도 확률로 오류가 발생하더군요.
그리고 npm start 를 이용해서 http-server 를 실행하고 브라우저에서 http://localhost:8000 으로 접속해서 아래와 같은 화면이 뜬다면 모든 설정은 일단 완성이 된 것입니다.


제가 작업한 gulp.js 파일에는 많은 부족함이 있습니다. 해당 내용은 아래와 같습니다.

  • 디렉터리만 추가된 경우에는 동작하지 않습니다. (마치 Git 처럼 말이죠)
  • 파일이나 디렉터리가 삭제된 경우에도 build 디렉터리에는 반영되지 않습니다.
  • 동시에 여러 개의 디렉터리와 파일들을 복사하는 경우에는 오류가 발생하기도 합니다.
  • 제 우분투에서는 디렉터리 추가도 파일관리자에서 하면 오류가 발생합니다.
  • 작업 중 오류가 발생하면서 gulp 프로세스가 종료되는 경우가 있습니다. 만약 작업물이 반영되지 않는 경우가 있다면 gulp의 watch 프로세스가 종료되지 않았는지 확인해 보세요.


결론


역시나 쉬운 내용을 길게 설명하는 데는 탁월한 능력을 갖추고 있는 것 같습니다. 
마지막 부분에 적었듯이 많은 부족한 부분이 있는 내용입니다.
그냥 NPM + BOWER + GULP 를 이용하는 경우에 어떻게 구성이 되는구나! 정도의 설명으로 봐주시기 바랍니다.
이제 열심히  angular-material 을 이용해서 목업 작업을 진행해야겠네요. 휴...



2016. 6. 27.

구글의 미래 - 토마스 슐츠


출간일 : 2016년 05월 30일

376쪽 | 682g | 152*225*30mm
ISBN-13 : 9791186805268
ISBN-10 : 1186805269


어제 드디어 구글의 미래라는 책을 다 읽었습니다.

출퇴근 시간에 읽기에는 조금 무거운(500g이 넘는 책은 무겁...) 책이지만 그래도 생각보다 빨리 읽을 수 있는 책이었네요. 책 무게뿐 아니라 내용도 좀 무거운 책이지만, 전체적으로는 동어반복이 계속되는 느낌의 책이기에 적당히 무시하고 읽으면 읽기에 그렇게 힘든 책은 아닙니다.

책의 해제(책의 저자, 내용, 체재, 출판 연월일 등에 대한 간단한 설명)를 쓰신 서울대학교 장병탁 교수님께서 이 책이 어떤 질문에 대해 답을 해줄 수 있는가에 대해 간략하게 2페이지로 정리해 놓으신 내용이 있습니다. (세상에! 간략하게 소개한 내용이 2페이지라고!!!)

하지만 제 나름대로 이 책을 생각해보면 결국 디지털 시대에 사는 우리가 앞으로 기술을 어떻게 바라봐야 하는가에 대해서 구글이라는 기술기업을 통해 생각해 보고자 한 것이 이 책의 핵심이지 않을까 생각합니다.

책을 간단히 소개해볼게요.

전반부는 구글이 탄생하게 된 배경이라던가 구글의 창업자 및 주요 인물들에 대한 설명으로 시작됩니다. 중반부와 후반부에도 각 부서의 핵심인물들에 대한 설명이 나오긴 하지만, 창업자 위주의 소개는 앞부분에 집중되어있습니다.

중반부는 구글이 알파벳이라는 지주회사 체제로의 변화를 꾀하게 된 이유를 현재 진행되고 있는 수많은 사업을 기준으로 해서 설명하고 있습니다.

후반부로 가면 구글이 사악한 기업인가 아니면 이상적인 혁신주의를 실행하고 있는 기업인가? 구글은 앞으로 더 커나갈 수 있는가 아니면 점점 쇠약해질 것인가? 구글이 현재 진행하는 수많은 혁신적인 프로젝트는 사람들을 더욱 풍요롭게 하고 구글을 구원할 것인가 아니면 그 반대가 될 것인가? 등등의 앞의 해제에서 정리한 각종 물음에 대한 저자의 생각과 주변의 시선을 정리해놓고 있습니다.

여러 가지 이야기가 있지만 결국은 구글이라는 기업으로 대표되는 최근의 다양한 기술기업들을 우리가 어떻게 바라보는 것이 맞는 것인가에 대한 저자의 생각을 이야기하고 싶었던 게 아닐까 하는 생각을 해 봅니다.

저자의 말이 아니더라도 모두가 알다시피 실리콘밸리로 대표되는 기술기업들은 기존의 전통적인 기업들과는 많은 부분에서 차이가 납니다. 기존의 기업은 대부분 사악한 것으로 인식되어있고 특히 독점적 지위를 가지게 된 기업의 경우는 더욱 그렇게 인식되고 있다. 그렇다면 최근 20여 년간 새롭게 탄생한 기술기업들도 같은 시각으로 봐야 할까에 대해서 저자는 많은 부분을 할애하여 이야기합니다.

구글은 검색시장에서 독점적 지위를 가진 것이냐 아니냐에 대한 논쟁을 차치하고 구글이 대부분의 사람의 생활 속에 깊숙이 관여하고 있고 또한 독점적인 지위를 상당 부분 차지하고 있는 것은 사실입니다. 이 책에 의하면 미국에서는 일정 부분 용인되고 있지만 유럽의 경우는 그에 대한 반감이 매우 크다고 하네요. 그렇다면 우리는 어떻게 생각하고 있을까요?

이 책은 구글에 대해서 대부분 옹호하고 변호하는 태도를 보이고 있습니다.
평소 구글이 싫었던 사람이라면 논쟁을 벌이고 싶다거나 속이 뒤틀리는 이야기들도 꽤 담겨 있습니다. 중간중간 래리 페이지를 비롯한 창업자들과 주요인물들을 대단한 존재로 포장하는 일도 하고 있습니다. 이런 부분에 대해 반감을 품을 수 있는 사람이라면 이 책을 그리 권하고 싶지는 않습니다.

그렇지만 이런 현상을 그냥 외면하고만 있을 수는 없을 것 같다는 생각을 했습니다.

개발자들의 경우 개발하는 과정에서 하루에도 수십 번 구글의 검색을 이용하지 않나 생각하는데요. 구글은 모두가 알다시피 우리가 검색하는 검색어를 모두 들여다보고 있고 그것을 이용해서 검색 품질을 높이고 있습니다. 안드로이드폰이 올려주는 위치 정보를 이용해서 다양한 서비스를 해주는 대신에 그들은 제 위치 정보를 다른 부분에도 이용하고 있고요. 제가 찍은 사진을 구글 포토에 올리면 알아서 분류해주고 다양한 이미지와 스토리를 만들어주는 대신에 그들은 제 사진을 모두 들여다보고 있습니다. 이메일이 오면 제 이메일 내용을 모두 들여다보고 일정이라던가 비행 정보 등도 알려주더군요. 얼마 전에는 외국사는 친구가 돌아가길래 그 친구가 타고 갈 비행기를 검색해 봤더니 그 뒤에 구글 나우를 통해서 제게 해당 비행기가 현지 공항에 도착하자마자 도착했다고 알려도 주더군요. 너무 많은 서비스가 제공되는 대신에 그만큼 제 정보를 모조리 들여다보고 있습니다.
요즘 구글만 그런가요? 페이스 북이라던가 아마존, 심지어 얼마전에 알리 익스프레스에서 하나 샀더니 알리 익스프레스 마저도 그러고 있습니다.

제가 가끔 장난삼아서 "내 정보는 구글이 다 관리해줘"라던가 "내 개인정보는 없어 다 공공정보야"라며 이야기하곤 하는데요. 가끔 생각해보면 정말 그렇다는 생각이 들곤 합니다. 다만, 저는 악의로 하는 이야기는 아니에요. 가끔은 무섭다는 생각도 하지만 말이죠.

그 정보들이 잘 관리되기만 한다면 또 그 정보들이 기술이 발전하고 사람들의 생활을 풍요롭게 만들어줄 수 있다면.... 기본적으로 저는 괜찮지 않을까 생각합니다. 사실 제가 능력이 된다면 그런 서비스들을 만들어 보고 싶다는 생각도 하니까요. 개인이 감추고 싶은 정보에 대해서는 잘 감추고, 공유할 수 있는 정보들을 잘 공유할 수 있도록 기술을 발전시켜 나가는 것이 바람직하지 않을까 생각합니다.

얘기가 약간 삼천포로 빠져서 너무 멀리 간 느낌이네요.

정리하자면, 이 책은 구글에 관해서 설명하고 구글이 처한 각종 의혹이나 불만들에 대해서 변호하는 글들로 가득합니다. 평소 구글, 페이스북, 아마존, 애플(애플은 조금 다를지도 모르겠네요) 등의 기술 기업들에 대해서 안 좋은 시선을 가지고 있는 분이라면 권하지는 않습니다. 다만, 생각이 좀 열려있으시다면 변명 한 번 들어주는 것도 좋지 않을까요? 다른 한편으로 구글과 같은 회사를 또는 서비스를 만들고 싶은 사람들이라면 한 번 보시라고 권하고 싶네요.

이 책을 통해서 무언가 결론을 내기보다는 앞으로 디지털 세상과 기술 기업이 나아갈 방향이 무엇일지에 대해 생각해보는 계기로 삼는 것이 어떨까 생각합니다.

(제일 처음의 사진은 책 사진만 찍으려다 구글 I/O Extended에서 받은 스티커 살짝 같이 찍어봤어요. ^^)

2016. 6. 21.

Google I/O Extended Seoul

작년에 이어 올해도 구글 I/O Extended 를 다녀왔습니다.

지난 일요일인 6월 19일에 있었던 행사지요.

제가 구글 I/O Extended 를 웬만하면 꼭 가는 이유는 두 가지 입니다.

첫째는 일요일에 하기 때문에 회사의 눈치를 볼 이유가 없다는 것. 일단은 회사에 제가 갔는지 안 갔는지를 말할 필요도 없거니와 어떤 내용이 있었는지를 보고할 이유도 없어서 마음 편하다는 것.

둘째는 집에서 가까운 세종대에서 2년째 계속하고 있다는 것. 느리게 걸어도 10분 정도면 갈 수 있는 거리에서 행사가 열린다는 것은 분명 기쁜 일.

뭐 어쨌건 올해도 신청해서 다녀왔습니다.



뭐 꽤 많은 분이 오셨더군요.

제가 들었던 세션은 아래와 같습니다.

  • 안드로이드 N을 준비하는 개발자를 위한 안내서
  • Building Extraordinary Apps with Firebase Analytics
  • Google's PRPL web development pattern
  • Tensorflow 101
  • 우리는 낮에도 꿈을 꾸는 개발자들~ Daydream
열심히 준비들 하셔서 열심히 강연해주시느라 고생하셨으므로 각 세션에 대한 감상은 적지 않겠습니다. 모두 수고 많이 하셨습니다.

그래도 한 분 Building Extraordinary Apps with Firebase Analytics 를 발표하신 Bart Jarochowski 님은 외국 분이신데 한국어로 발표하시느라 정말 열심이셨습니다. 고생하셨어요.

일 년 해봐야 제가 참가하는 컨퍼런스나 세미나는 겨우 두 개 아니면 세 개 정도입니다.
그것도 정말 열심히 해야 그 정도 되고 일 년에 겨우 한 개 정도 참여하는 경우도 있습니다.
하나라도 참가를 하고자 노력을 하는 이유 즉, 제가 컨퍼런스나 세미나를 참가하고자 하는 이유는 사실 굉장히 간단합니다.

10여 명 남짓의 개발팀 그것도 한가지 서비스를 위해 달려가는 개발팀에서 벗어나 잠시 많은 개발과 관련된 분야에서 일하시는 분들 속에서 다른 공기를 마시고 싶다는 것.
최근에 어떤 부분들이 주로 관심사이고 어떤 기술들까지 익히려고 노력을 하고 있는지를 보면서 시야를 넓히고 싶은 것.
그리고 열정 가득한 개발과 관련된 분야에서 뛰시는 많은 분 사이에서 에너지를 살짝 충전해서 나태해진 나 자신을 다시 한 번 달려가게 하고 싶다는 것.

그러다 보니 컨퍼런스나 세미나의 기술 설명이 부족했다거나 내용이 좀 아니었다거나 하는 것들은 어떠해도 상관없는 것 같습니다. 그저 열정을 가지고 열심히 하시는 분들을 보는 것만으로도 매우 많은 것을 얻고 오는 것 같습니다.

이번 구글 I/O Extended Seoul 에서도 티셔츠, 스티커와 함께 좋은 기운을 얻어서 올 수 있어서 좋았습니다.

그래도 이번 컨퍼런스를 통해 새롭게 저의 관심 분야로 떠오른 부분을 정리하지 않는다면 이 포스트의 의미가 없기에 정리하겠습니다.

우선은 Polymer 에 대해서...
구글 I/O 2016 에서도 프로그레시브 웹앱이라는 부분에 대한 세션을 주로 유튜브를 통해 봤었는데요. 이 부분 매우 큰 관심 분야입니다. 앞으로 웹이 얼마나 더 발전할 지.. 그리고 웹 개발이 얼마큼 더 체계화가 될지 궁금하기에 더 관심을 끌게 되는데요. Polymer 는 계속해서 매우 조용히 발전을 하고 있는 것 같더군요. 최근에는 구글 Chrome 진영에서도 그리 큰 얘기가 나오지 않아서 궁금했었는데, 어느 정도 진전된 모습을 볼 수 있었습니다. 그래도 아직은 좀 언더그라운드 느낌이 강하더군요.

다음은 구글 I/O 2016 때도 꽤 큰 반향을 일으켰던 Firebase 입니다. 구글의 Firebase 공식사이트에 가보면 다양한 서비스들이 있어서 살짝 관심을 가졌었는데, 이번에 살짝 맛보기를 할 수 있었습니다. 앞으로 조금 더 관심을 가지고 사용할 만한 부분들을 짚어봐야겠습니다.

쉬는 날 여유로운 마음으로 즐긴 즐거운 행사였습니다.

끝나고 아끼는 동생과 함께 간만에 맥주 한잔 하는 것으로 마무리할 수 있어 더욱 즐거운 하루였습니다.

2016. 6. 18.

소프트웨어 장인 - 산드로 만쿠소


 



출간일 : 2015년 9월 25일 

328쪽 | 500g | 148*225*14mm

ISBN-13 : 9791186659489

소프트웨어 장인 : 프로페셔널리즘, 실용주의, 자부심 원제로는 The Software Craftsman-Professionalism, Pragmatism, Pride  입니다.

이 책은 로버트 C. 마틴 시리즈로 열한 살부터 코딩을 시작했고 열아홉부터 코딩으로 돈을 번 경험이 있는 브라질 출신의 산드로 만쿠소가 본인의 경험담을 통해서 소프트웨어 장인에 관해 이야기하고 있는 책입니다.

아는 동생의 가볍게 읽을 수 있는 책이라며 추천을 해줘서 읽게 된 책이죠.
평소 책을 고를 때 다른 것 보다 무게를 먼저 확인하고 나머지를 고르는 제 버릇때문인지 무게가 가볍다는 것을 먼저 이야기하면서 추천을 해 주더군요.

한동안 기술서적에 손을 못 대고 있는 상황에 있었습니다. 여러 가지 이유로 슬럼프가 좀 길게 온 탓에 기술 서적을 읽는 것은 물론 코딩 한 줄 손대는 것도 고통스러웠습니다. 회사 일이야 돈 벌어야 하니 어쩔 수 없이 하고 있지만, 집에 와서는 매일 인텔리제이를 켜놓고는 한 줄도 못 짜고 끄는 날이 대부분인 요즘이었습니다. 이럴 때 가볍게(?) 읽을 수 있는 책이라니 정말 반가웠습니다.  "그래! 이럴 때는 책을 읽으면서 뭔가 계기를 만들어야 해"라는 생각에 보게 되었습니다.

우선 빠르게 한번 읽고 난 다음에 포스트를 쓰고 있습니다. 이 책을 약간의 시간을 두고 한 번 더 읽어볼 생각인데, 아마도 그때는 다른 느낌이 있을지도 모르겠습니다.
지금부터 이 책을 읽으면서 느꼈던 점들을 지극히 주관적인 입장으로 정리를 한 번 해보려고 합니다.

이 책의 구성을 제 나름대로 보면 대충 아래와 같은 구성입니다.

1~3장은 애자일과 장인 정신에 대한 정의를 찾아가는 여정을 보여줍니다. 어떻게 해서 애자일이 나타났고 어떻게 소프트웨어의 장인정신에 대한 논의가 시작되었는지, 그리고 그것이 무엇인지에 대한 이야기를 풀어놓습니다.
정의를 마친 저자는 자연스럽게 4장부터 본격적인 장인의 마음가짐과 행동에 대한 이야기를 시작하게 됩니다. 본인의 실제 경험을 적절히 녹여서 이야기를 풀어내려 한 노력이 느껴집니다. 이런 이야기는 1부의 끝인 8장까지 계속됩니다.
2부에서는 9장에서 13장에 걸쳐서 회사를 어떻게 소프트웨어 장인들이 일하는 장소로 만들어 갈 수 있는지 등의 이야기를 주로 풀어냅니다.
그리고는 14장부터 16장까지 개발자가 어떻게 하면 소프트웨어 장인이 되기 위한 긴 여정을 바르게 갈 수 있을지에 대한 방법에 관해서 이야기하면서 책을 마무리합니다.
아! 부록에서 마지막으로 소프트웨어 장인에 대한 오해를 풀어내는 것을 잊지 않았네요.

책을 사면 서문과 추천사는 무조건 먼저 읽는 것이 버릇입니다. 이 책의 추천사는 엉클 밥(로버트 C. 마틴의 별명) 아저씨가 쓰셨습니다. 엉클 밥 시리즈의 책이니 당연하겠죠. 추천사를 읽는 중에 이런 구절이 눈에 들어오더군요.
"아픔에 대한 책이기도 하다. 당신과 나 그리고 모든 프로그래머가 겪는 아픔을 생생하게 전달한다. 수준 이하로 일을 마무리했던 경험, 전혀 프로답지 않았던 경험, 더 나아지고 싶지만 어떻게 해야 하는지 몰랐던 아픔 등에 관한 일화와 그 치유법을 담았다."
책을 읽으면 읽을수록 "아픔"이 느껴졌던 책입니다.  내 자신이 얼마나 무능력한지 그리고 얼마나 무식한지 그리고 얼마나 게으른지를 뼈저리게 그리고 생생하게 느끼게 해주는 책입니다. 그동안 얼마나 소프트웨어 장인의 길에서 멀어져 왔는지를, 그리고 현재 내 위치가 어느 정도인지를 알게 해 주는 책입니다. 맞습니다. 이 책은 "아픔"에 대한 책이네요. 다른 누구도 아니고 저의 아픔에 대한 책입니다. 추천사와 다르다면 저에겐 과거형이 아니라 현재형이라는 부분만 다를 듯합니다.

책을 읽어가는 내내 마음속으로는 변명을 계속 뱉어내고 있었습니다. 프로젝트 계약에 매여서 다른 방식의 시도 같은 건 꿈에도 꿀 수 없다고... 테스트를 짜고 있는 동안 들어오는 윗분들의 압박을 견디기는 쉽지 않다고... 그런데 그럴 때마다 어김없이 저자는 저의 변명을 무너뜨리고 있습니다. 해야 할 것을 하지 않은 것은 노력이 부족했을 뿐이라고...

사실 책 제목을 보고는 기분이 많이 안 좋았습니다. 장인이라는 호칭을 달만큼의 능력도 안 되지만 일정 부류의 개발자들이 서로에게 장인이라는 호칭을 달아주고는 으스대는 모습이 생각났거든요. 그런데 이 책은 그런 책은 아니더군요. 여기에서 장인은 프로의식과 비슷한 의미로 사용된 것이 아닐까 생각합니다. 프로의식을 가지고 더 나은 개발자가 되고자 노력하는 모습이 이 책이 이야기하고 있는 장인의 모습인 것 같습니다.

또한, 이 책에서는 대부분 내용에서 애자일 방법론에 관해서 이야기하고 있지만, 애자일 방법론이라던가 그 세부 절차인 페어프로그래밍, TDD 등을 맹목적으로 따르라고 하지는 않습니다. 단지, 제대로 된 프로그램을 만들어 내는데 애자일의 개발방법론이 어떤 이득을 줄 수 있는지를 이야기합니다. 그런 개발방법론은 개발방법론을 따르기 위해 존재하는 것이 아니라 제대로 된 소프트웨어 프로젝트를 만들기 위한 수단일 뿐이라고 계속해서 강조하면서 이야기를 하고 있습니다.

이 책이 정답이라고 이야기하고 싶지는 않습니다. 다만 스스로 제대로 된 개발자, 프로로서의 개발자는 어떤 모습으로 어떻게 일을 해야 하는가에 대해서 고민을 해 볼 기회가 되지 않을까 생각합니다.

이 책은 누구보다도 3~5년 차의 개발자들에게 읽어보기를 권합니다.
언젠가 그들이 프로젝트를 책임지는 위치가 된다면 절대 저처럼은 살지 않기를 바라는 마음에서... 그리고 미리미리 준비해서 프로의식 가득한 개발자가 되기를 바라는 마음에서...

다음은 10년이 넘도록 개발자 또는 개발자 출신의 관리자를 하는 분들께도 권합니다.
제대로 관리하고 제대로 개발을 해서 후배들에게 좋은 본보기로 그리고 좋은 선례로 남을 수 있도록 노력하자는 마음에서.. 그리고 제대로 개발하려고 하는 후배들의 걸림돌이 되지는 말자는 마음에서... 그리고 책을 읽으면서 연차가 고참 개발자를 만들어주는 것은 아니라는 것을 깨닫기를 바라면서...

다음으로 자신 있게 "난 개발자다"라고 이야기하면서 살고 싶은 개발을 사랑하는 모든 개발자에게 권합니다. 정말 제대로 된 프로개발자가 된다는 것이 어떤 것인가에 대한 고민을 해보자는 마음에서...

가벼운 마음으로 시작해서 무거운 마음으로 끝나게 된 책입니다.
이왕 정신 차린 거 TDD부터 하나씩 다시 시작해봐야겠어요. 회사가 언젠가 받아들여 주는 날까지 제 개인 프로젝트들에라도 말이죠.

마지막으로 이 책 정말 가볍습니다. 부록까지 330여 페이지에 무게는 500그램이예요.



2016. 6. 11.

Spring Cloud + Docker + Docker Compose

수정이력

  • 2016/06/12 - Docker compose restart policy 관련 정리 수정. Docker compose의 스케일 조정 관련 정리 추가

서론


지금까지 정리된 내용은 스프링의 프로젝트들(Spring-cloud, Spring-boot 등)을 이용해서 간편하게 마이크로소프트 아키텍처를 구현해 나갈 수 있는가에 대한 입문 수준의 내용을 담고 있습니다.
여기에 더해서 오늘은 Docker를 이용해서 그동안 만들었던 서비스들을 모두 Dockerizing 하고 이왕 하는 김에 Docker Compose 를 이용하도록 바꿔보겠습니다.

아래의 내용은 blog_cloud_docker 브랜치에서 전체 소스를 확인하실 수 있습니다.

또한, 본 포스트 내용은 https://github.com/sqshq/PiggyMetrics 의 내용을 참고했음을 미리 밝혀둡니다. 내용은 비슷한데 본 포스트보다 훨~~씬 잘 정리되어있으니 위 깃헙을 보시는 것도 좋을 것 같네요.

사전 준비사항


오늘은 사전 준비사항이 좀 많습니다.

블로그 작성에 사용한 환경


이번 포스트도 역시나 이전과 크게 다르지 않은 사양에서 진행되었습니다.
  • JDK 8 : 1.8.0_65
  • Intellij 2016.1.3
  • Gradle build : 2.10
  • Spring IO : 2.0.1.RELEASE
  • Spring Boot : 1.3.2
  • Spring Cloud :  Angel.SR6
  • Lombok : 1.12.6
  • IDEA Lombok plugin : 0.11.16
  • Configuration server의 git 저장소 : https://github.com/roadkh/blog-cloud-sample-config.git
  • yml 을 이용한 Property
  • Docker : 1.11.2
  • Docker Compose 1.7.0
사실 Spring Boot 나 Spring IO, Spring Cloud의 버전이 이전 블로그 작성 이후에 모두 변경된 상황입니다만, 이 포스트에서 사용한 수준에서는 크게 변동사항이 없어서(RestTemplate의  Loadbalance 관련 부분이 변경되었습니다만...) 버전 업 없이 그냥 진행하도록 하겠습니다. 업데이트된 버전에서는 테스트를 아직 진행하지 못했습니다. 혹시 업데이트 버전으로 확인을 하게 된다면 후에 해당 내용을 업데이트하겠습니다. 

Dockerfile의 작성


Dockerfile은 최소 필요한 사항으로만 구성했습니다.

Service 를 만드는 Docker 기본 이미지는 제 다른 포스트에서 만든  Ubuntu 14.04 LTS + Oracle java8 이미지를 이용했습니다. Docker hub 에도 push를 해 놓았습니다. Docker hub 에서 road/ubuntu_java8 로 검색하시거나 https://hub.docker.com/r/road/ubuntu_java8/ 에서 확인 가능합니다. 해당 이미지를 만드는데 사용한 Dockerfile 은 docker/Dockerfile-java8 이니 참고하면 될 것 같습니다.

전체 Dockerfile 에서 Configuration server, Discovery Server와 Composite API 에 대해서만 한번 보겠습니다. 전체 Dockerfile 은 blog_cloud_docker 브랜치의 docker 디렉터리에서 확인하실 수 있습니다.

프로젝트 최상위 폴더에 docker 디렉터리를 만들고 그 안에다 아래와 같이 Dockerfile 들을 만드시면 됩니다.

FROM road/ubuntu_java8

ADD ./server/configuration/build/libs/server-configuration.jar app.jar

EXPOSE 8888

ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app.jar"]

우선은 Dockerfile-config 입니다.
내용을 보면 우선 FROM road/ubuntu_java8 을 이용해서 기본 이미지를 지정했습니다. 위 이미지는 제가 일단은 Docker hub 에 public 으로 push를 해 놓았기 때문에 없다면 다운로드 후에 진행하게 됩니다. (혹시, Docker hub 에 있는 것을 이용하시지 않을 분들은 제 이전 포스트의 내용을 보시고 docker/Dockerfile-java8 을 이용해서 -tag road/ubuntu_java8 이라고 주시고 빌드를 하신 후에 진행하시면 됩니다. 물론 tag 의 이름을 변경하셨다면 Dockerfile 의 FROM을 해당 이름으로 바꾸시면 됩니다.)
Configuration server는 8888 포트로 지정해 놓았기 때문에 EXPOSE 8888 을 이용해서 포트를 외부 오픈합니다. 그리고 build 된 jar 파일을 이제 Docker container 에 복사해야 하는데 이때 상대경로를 보시면 ./ 으로 지정하게 되어있습니다. 이 부분은 이후에 Docker compose를 설명할 때 설명하도록 하겠습니다.
이후에 ENTRYPOINT를 이용해서 Container가 시동 후에 실행할 명령어를 주면 됩니다.
(위의 "-Djava.security.egd=file:/dev/./urandom" 옵션은 없어도 될 것으로 보이네요)

FROM road/ubuntu_java8

ADD ./server/discovery/build/libs/server-discovery.jar app.jar

EXPOSE 8761

ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-Dspring.profiles.active=peer1", "-jar", "/app.jar"]

다음은 Peer1 입니다. Discovery 서버는 기존에 두 개의 서버를 띄웠었습니다. peer1하고 peer2 라는 spring profile 을 이용해서 띄웠었습니다. Dockerfile-peer1과 Dockerfile-peer2 는 EXPOSE의 포트(8761, 8762)와 "-Dspring.profiles.active=peer1", "-Dspring.profiles.active=peer2" 부분을 제외하고는 모두 같습니다.

FROM road/ubuntu_java8

ADD ./api/composite/build/libs/api-composite.jar app.jar

EXPOSE 9001

ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app.jar"]

Composite API의 Dockerfile 입니다.
이젠 보시면 아시겠지요?

이렇게 전체 서비스에 대한 Dockerfile을 만드시면 됩니다.

마지막으로 하나 다른 것이 RabbitMQ의 Dockerfile 인데요. 잠깐 알아보도록 하겠습니다.

FROM rabbitmq

RUN rabbitmq-plugins enable rabbitmq_management

EXPOSE 5672 15672

RabbitMQ 는 official로 Docker hub에 등록된  rabbitmq 이미지를 사용했습니다.
"RUN rabbitmq-plugins enable rabbitmq_management" 이라고 지정한 부분은 rabbitmq plugin 중에서 rabbitmq dashboard를 이용하기 위해서 활성화해주는 명령어를 지정해 준 것입니다.
포트는 5672, 15672 두 개의 포트를 엽니다.

자, 이제 Dockerfile 은 모두 만들었습니다.

Docker compose file 작성


다음으로 docker compose를 이용할 것이므로 docker compose가 사용할 docker-compose.yml 파일을 만들도록 하겠습니다. docker-compose.yml 파일은 프로젝트 최상위 디렉터리에 만드시면 됩니다.

docker-compose.yml 파일을 작성하는 방법에 대해서는 https://docs.docker.com/compose/compose-file/ 에서 확인하실 수 있는데 역시나 저는 가장 단순한 형태로만 작성했으므로, 제가 작성한 부분까지만 설명하겠습니다.

아래는 docker-compose.yml 의 일부분입니다. 우측 부분에 # 으로 표시된 부분을 중심으로 설명하겠습니다.

version: '2'                                 # 버전
services:                                    # 서비스 항목
  rabbitmq:                                  # 서비스 이름
    build:                                   # 빌드할 Docker image 또는 Docker file
      context: .
      dockerfile: ./docker/Dockerfile-rabbitmq
    ports:                                   # expose 할 port
      - "5672:5672"
      - "15672:15672"
  config:
    build:
      context: .
      dockerfile: ./docker/Dockerfile-config
    ports:
      - "8888:8888"
    volumes:
      - /home/road/media/Dev/workspace/pilot/blog-cloud/blog-cloud-docker-config:/config
  peer1:
    build:
      context: .
      dockerfile: ./docker/Dockerfile-peer1
    ports:
      - "8761:8761"
    restart: always                          # restart policy


  • version : docker-compose가 사용할 설정 버전입니다. version은 현재 '1' 과 '2' 가 있는데 지정하지 않으면 '1' 로 인식이 됩니다. 두 버전은 크게 다른 부분은 없습니다만, 설정항목마다 약간의 상세한 설정이 가능한 것이 version '2' 라고 보시면 되겠습니다. 앞으로 여러가지 설정을 넣을 것을 생각해서 되도록 version '2' 로 만드는 것을 Document에서도 추천하고 있네요.
  • 서비스 항목 : 항목 아래로 서비스들을 지정하겠다는 설정 키값입니다.
  • 서비스 이름 : 실행할 서비스들의 이름입니다. 나중에 docker-compose를 실행하게 되면 저 이름 뒤에 _1이 붙어서 실행되는 것을 볼 수 있습니다. _1이 붙는 이유는 docker compose를 이용해서 scale up을 할 수 있는데 그때를 위해서인 것으로 보이네요.
  • build : 이 부분이 version '1'과 차이가 있습니다. version '1'에서는 바로 context 만 지정해 주는 방식이었는데 context와 dockerfile 을 함께 지정해 줄 수 있습니다. 여기서 context라는 부분이 중요합니다. context는 docker 를 구동할 가장 상위 디렉터리를 말합니다. 즉 모든 파일에 대한 추적을 context 내에서 할 수 있습니다. 어떤 파일을 복사할 때 ../ 으로 시작하게 하면 permission 관련 오류가 발생합니다. 앞에 Dockerfile 들을 설명할 때 ADD 항목에서 ./ 으로 시작하게 만들었었는데, 이것은 이 context 로 지정된 위치를 기준으로해서 ADD 가 되기 때문입니다. 혹시 docker-compose를 사용하지 않고 docker 를 바로 명령어로 사용하실 분들이 있다면 ADD 부분 작성할 때 고려를 잘하셔야 합니다. 이것 때문에 docker-compose.yml 파일을 프로젝트 최상위 폴더에 만들었습니다. 꼭 그래야 하는 것 은 아니고 다른 방법으로 해결가능하지만 가장 편한 방법인 것 같습니다.
  • ports : 외부로 노출할 포트입니다. docker 명령어에 -p 옵션과 같은 기능입니다. 리스트로 넣을 수 있고 하나만 넣을 수도 있습니다. ':' 를 구분자로 앞은 host, 뒤는 container의 포트입니다.
  • restart : Restart policy 입니다. docker 명령어에서 --restart 옵션과 같은 기능인 것으로 보이는데, 이상하게 policy가 always 밖에 동작하지 않네요. https://docs.docker.com/engine/reference/run/#restart-policies-restart 을 보시면 분명 네 가지의 옵션이 있는데 always 외에는 동작이 확인되지 않았습니다. docker 명령의 restart 옵션과 같은 기능입니다. https://docs.docker.com/engine/reference/run/#restart-policies-restart에 명시된 네가지의 옵션을 줄 수 있습니다. always를 주면 해당 서비스가 exit 0 코드를 주고 끝나는 한 계속해서 다시 시작해 줍니다. 제가 저 설정을 넣은 이유는 configuration server 가 올라와서 정상적으로 다른 서비스들에게 설정을 전달해주는데까지 시간이 좀 걸리기 때문에 다른 서비스들은 모두 failFast 를 true로 주고 restart 옵션을 always로 주는 방식을 선택했기 때문입니다. 나중에 실행 로그를 보시면, 서비스들이 configuration server로 부터 설정을 못 받으면 계속해서 재시동 되는것을 보실 수 있습니다.
    단, 이 옵션이 약간 문제인 부분은 서버 OS가 부팅될 때 바로 모든 서비스가 시작된다는 것입니다. 서버에서는 좋은 옵션일지 몰라도 개인용 개발 장비의 경우는 약간 문제가 될 수도 있겠네요. 제 경우는 우분투 SSD에 운영체제를 설치하고 일반 HDD 에 작업 폴더를 놓고 있는데, 전체를 LVM으로 묶지 않고 사용하다 보니 HDD가 나중에 마운트가 되면서 이상 동작하는 문제가 발생했었습니다. 혹시 저와 비슷한 분들은 컴퓨터를 끄시기 전에 저 설정을 주석 처리하시고 build 명령어 한번 돌려주시고(옵션만 주석해도 되는 데 안전을 위해서) 컴퓨터를 종료해주세요.
    그 외의 옵션으로 no, on-failure, unless-stopped 옵션이 있으며 내용을 잘 살피시고 적당한 옵션을 넣으시면 됩니다. 옵션들에 대한 내용은 https://www.javacodegeeks.com/2016/06/ensuring-containers-always-running-dockers-restart-policy.html 의 내용을 차분하게 확인하시면 도움이 될 것으로 보입니다.
  • volumes : 이 부분은 실제 github 에 올라가 있는 소스에서는 보실 수 없습니다. 제가 포스트 작성할 때는 로컬 git을 사용해서 했었기 때문에 volumes를 이용해서 host의 local git 디렉터리를 container에 연결해 주기 위해 지정했던 내용입니다. docker 의 -v 또는 --volume 옵션과 같은 기능입니다.
이제 전체 파일 한번 보겠습니다.

version: '2'
services:
  rabbitmq:
    build:
      context: .
      dockerfile: ./docker/Dockerfile-rabbitmq
    ports:
      - "5672:5672"
      - "15672:15672"
  config:
    build:
      context: .
      dockerfile: ./docker/Dockerfile-config
    ports:
      - "8888:8888"
  peer1:
    build:
      context: .
      dockerfile: ./docker/Dockerfile-peer1
    ports:
      - "8761:8761"
    restart: always
  peer2:
    build:
      context: .
      dockerfile: ./docker/Dockerfile-peer2
    ports:
      - "8762:8762"
    restart: always
  product:
    build:
      context: .
      dockerfile: ./docker/Dockerfile-product
    ports:
      - "9011"
    restart: always
  review:
    build:
      context: .
      dockerfile: ./docker/Dockerfile-review
    ports:
      - "9012"
    restart: always
  recommendation:
    build:
      context: .
      dockerfile: ./docker/Dockerfile-recommendation
    ports:
      - "9013"
    restart: always
  composite:
    build:
      context: .
      dockerfile: ./docker/Dockerfile-composite
    ports:
      - "9001"
    restart: always
  edge:
    build:
      context: .
      dockerfile: ./docker/Dockerfile-edge
    ports:
      - "9000:9000"
    restart: always
  hystrix:
    build:
      context: .
      dockerfile: ./docker/Dockerfile-hystrix
    ports:
      - "7979:7979"
    restart: always
  turbine:
    build:
      context: .
      dockerfile: ./docker/Dockerfile-turbine
    ports:
      - "8989:8989"
    restart: always

전체 내용을 보시면 product, review, recommendation, composite 의 경우는 ports 가 {HOST}:{GUEST} 형태가 아니라 포트 하나만 들어간 것을 보실 수 있습니다.
이 부분은 후에 이 네가지 서비스는 sacle 조정을 해 보려고 지정한 내용입니다.

docker-compose 설정파일까지 작성이 완료되었습니다.

아직 docker-compose build 같은 거 하지 마세요.
이제 실제로 프로그램의 설정 몇 가지를 고쳐야 합니다.

소스 수정


우선, 각 서비스의 bootstrap.yml 파일을 찾아서 해당 소스의 configuration server URI 설정을 변경하도록 하겠습니다. 각 서비스가 docker에서 뜬다는 것은 결국 모든 서비스가 각각의 네트워크 설정을 가지게 된다는 것과 같습니다. docker-compose 가 없을 때는 docker 의  network setting 옵션을 이용해서 해주는 등의 번거로운 작업이 필요합니다만, docker-compose 를 이용하게 되었으니 간단히 서비스의 이름으로 접근할 수 있습니다. 즉 configuration server의 URI는 http://config:8888 이렇게 되겠습니다. 이제 수정해 보겠습니다. 예제로 composite api의 bootstrap.yml 파일을 보겠습니다. (api/composite/src/main/resources/bootstrap.yml)

spring:
  cloud:
    config:
      uri: http://config:8888
      failFast: true
  application:
    name: composite

bootstrap.yml 을 찾아서 config.uri 를 바꿔주면 됩니다.

제가 만든 프로젝트에서 모든 application.yml은 configuration 서버에 올렸는데, 딱 하나만 프로젝트에 있습니다. 바로 turbine 서버입니다. 어떻게 해도 정상 동작을 하지 않아서 결국 이 프로젝트만 configuration server를 쓰지 않도록 수정을 했었습니다. 이 프로젝트의 경우는 application.yml에서 discovery server 설정을 변경해 줘야 합니다.


spring:
  application:
    name: turbine
  rabbitmq:
    host: rabbitmq

server:
  port: ${PORT:8989}

management:
  port: -1

eureka:
  client:
    service-url:
      defaultZone: http://discovery:8761/eureka/
  instance:
    preferIpAddress: false
    leaseRenewalIntervalInSeconds: 10
    leaseExpirationDurationInSeconds: 30
    metadataMap:
      instanceId: ${spring.application.name}:${spring.application.instance_id:${random.value}}

Configuration server에 등록된 서비스들의 설정 파일을 수정해야 합니다.
수정할 부분은 랜덤 포트를 쓰도록 했던 부분을 모두 포트 지정으로 바꿔줍니다. docker-compose.yml 에 지정한 포트와 맞추면 됩니다.

다음으로 모든 설정이 공유하는 설정용 git 에 등록된 application.yml 에서 discovery server의  default zone 설정을 변경해 주면 됩니다. Configuration server에 대한 설정과 같이 기존에 localhost 또는 127.0.0.1 등으로 되어있는 것을 "http://peer1:8761/eureka/, http://peer2:8762/eureka/"
으로 바꿔주면 됩니다. peer1.yml 과 peer2.yml 의 Discovery 관련 설정도 함께 변경해주면 됩니다.

자 이제 모든 준비는 마쳤습니다.

빌드 및 실행


우선 프로젝트를 다시 빌드하겠습니다.

./gradlew clean build

빌드가 성공했다면 docker-compose를 이용해서 이미지를 생성하겠습니다.

docker-compose build

아래 명령어로 이미지가 정상적으로 생성이 됐는지 확인합니다.

docker images

마지막으로 docker-compose로 서비스들을 모두 실행해 보겠습니다.
아래는 백그라운드로 실행하지 않는 명령어로 실행하면 모든 로그가 올라오며, CTRL+C 를 이용해서 모든 서비스를 중지할 수 있습니다.

docker-compose up

아래는 백그라운로 실행하는 명령어로 로그를 보고 싶다면 "docker-compose logs --follow" 명령을 이용해야 합니다.

docker-compose up -d

참고로 특정 서비스의 로그만 보고 싶을 경우에는 "docker-compose logs --follow composite" 식으로 명령을 주면 됩니다.

마지막으로 서비스를 중지하면서 기존에 생성된 container를 함께 삭제하고 싶다면 아래의 명령어로 가능합니다.
docker-compose down


테스트


테스트 하는 방법은 기존 Blog_03 과 거의 같습니다.
주요 URL 을 아래에 정리했으니 참고해서 테스트를 진행할 수 있습니다.


  • Eureka 확인 : http://localhost:8761/ 또는 http://localhost:8762/
  • Product List : http://localhost:9000/composite/product/
  • Product : http://localhost:9000/composite/product/{product id}
  • Hystrix dashboard : http://localhost:7979/hystrix
  • Turbine 의 확인 : Hystrix dashboard input 에 http://turbine:8989/ 를 넣고 Monitor Stream 클릭. (단, 최소 한번 이상 API 를 호출한 후에 해야만 정상적인 데이터가 나옴)

스케일의 조정


이제 스케일 조정을 해 보겠습니다. 앞에 docker-compose.yml 을 정리할 때 스케일 조정을 위해서 API 네개는 포트 지정을 조금 다르게 했었습니다.
우선 스케일 조정을 하는 명령어부터 알아보겠습니다.

docker-compose scale product=2

스케일의 조정은 실행 중에도 가능합니다.
또한, 스케일을 늘렸던 것을 줄이는 것도 가능한데 숫자를 낮추면 뒤에 생성된 container부터 중지가 됩니다.
product, review, recommendation, composite 은 스케일 조정이 가능하니 마음꺼 해보세요.
스케일을 조정하신 후에 Eureka dashboard에서 확인하시거나 아니면 docker-compose logs -f 를 해서 로그를 보시면 재미있으실 거예요. 최소한 저에겐 굉장히 재미있는 경험이었습니다.
단, 현재 설정에서 나머지 서비스들은 스케일을 조정하실 경우 포트 관계로 오류가 발생합니다.
참고해주세요.

결론


실제 서비스에 적용해 보지 못한 현시점에서 누군가 docker와 docker-compose의 장점을 물어본다면 좀 망설이게 됩니다. docker 는 필요한 소프트웨어나 모듈들을 마음껏 깔아보고 지우고를 할 수 있는 부분이 편하고 docker-compose 는 docker 를 이용해서 만든 서비스들을 손쉽게 빌드/실행/제거 할 수 있는 장점이 있을 뿐이지요.
하지만, 최근 주목을 받고 있고 구글의 퀴베르네시스 같은 docker 를 지원하는 다양한 솔루션이 탄생하고 있다는 것은 분명 이점이 크다는 것이지 않을까 생각합니다. 개발자답지 않은 단순한 생각이긴 하네요. 우선은 한 번 사용해 볼 만 하다는 것을 기본으로 아직은 차근차근 그 특징들을 알아가는 중이니 이해들 하시기 바랍니다.
현시점에서 가장 눈에 들어오는 부분은 scale up 관련된 부분입니다. 외국의 사례들을 보다보면 docker와 docker compose 를 이용해서 손쉽게 scale 을 조정하는 경우들을 볼 수 있습니다. 꽤 매력적인데 좀 더 실제적인 사례를 찾아보면서 실험을 하는 중입니다. 일단 간단한 형태의 스케일 조정은 테스트를 진행해서 정리해 보았습니다만, 더 실제적인 내용에 대해서 언젠가 포스팅 할 날이 있기를 바랄 뿐입니다.
오늘은 간단한 내용일 것이라고 예상을 했는데 생각보다 복잡하고 장황한 정리가 되었네요. 원래 쉬운 것을 어렵게 정리하는데 재능을 물려받았나 봅니다.

덧붙이는 말

제 기존 포스트를 보셨던 분들은 반말에서 존댓말로 바뀐 걸 의아하게 생각하실지도 모르겠습니다. 원래 잊어버리기 전에 정리 해 놓자는 목적에서 시작했던 블로글라 쓰기 편한 반말을 이용했었습니다. 그런데 최근 접속 통계를 보다 보니 적지 않은 분들이 방문하시고 계시네요. 왔다 바로 나가시는 분들이 더 많지만 말이죠. 아무튼, 뭔가 죄송한 마음에 앞으로는 블로그를 존댓말로 정리할까 합니다. 내용도 부실하고 글도 싸가지가 없으면 너무 죄송하니까 말이죠.