NapuCon 2016 Review


1. NapuCon 2016 후기

지금까지 컨퍼런스를 그렇게 많이 다닌 것은 아니지만 여태까지 참석한 컨퍼런스중에 가장 만족스러운 컨퍼런스였다.

작은 기업에서 사수가 없는 개발자로 생활을 하면서 서적, 블로그, 커뮤니티 등을 통해 습득한 지식을 나름대로 적용하고, 적용 여부를 검토하곤 했었는데 내가 잘 하고 있는 것이 맞나? 라는 생각과 누군가가 조언을 해 줄 사람이 있었으면 좋겠다는 생각에 답답해 하고 있었는데, 이번 컨퍼런스에서 그 답답함을 해결해 줬다.

케빈님을 통해서는 그 전에 케빈TV 를 보면서도 느꼇지만 역시 개발자는 잔실수로 삽질을 자주 하는구나 라는 것을 깨닫게 되었고, Rakuten의 전민수, 심재민 Architect님들을 통해 큰 규모에서는 저런식으로 요구사항을 맞춰가고, 어떤식으로 처리하는 지에 대해 알게되어 너무 좋았다. 데니스 님을 통해서는 요즘 관심이 있어 공부를 시작한 Docker에 대해 들어서 트렌드를 잘 쫓아가고 있는 지에 대해 검증을 받은 느낌이어서 좋았다.

개인적으로 세번째 세션과 마지막 세션, 치즈님과 박미정님의 발표가 가장 좋았다.

치즈님의 발표를 통해서 지금까지 개인적으로 공부하고, 적용하고 있는 부분들이 잘 하고있는지를 검토받은 느낌이 들었다. 그리고, 초고수가 되기 위해 본인이 했던 일들에 대해 들은 것이 나에게 많은 자극이 되었다. 개발자로써 성장하고 싶지만 아직까지 그렇게 많은 노력을 하고있진 않다는 것을 깨닫게 해주셨다.

박미정님의 발표는 스타트업에 합류한 이후 본인이 기존의 문제를 해결해 나가는 과정을 설명해주셔서 너무 좋았다. 물론 회사 대표를 잘 설득을 해야 된다는 큰 어려움이 있지만, 그 어려움을 극복하고 문제를 해결한 모습이 너무 대단해 보였다. 박미정님의 발표 마지막에 질문으로 아무리 노력을 해도 대표가 설득이 되지 않고, 거리가 좁혀지지 않을 경우 박미정님은 어떤 선택을 할 지에 대한 답변은 정말 현실적인 답변이어서 웃기기도 했지만, 앞으로 내가 어떻게 할지에 대해 다시한번 생각해보게 되는 계기가 되었다.

어떻게 보면 자바8 스승님인 케빈님과 사진을 찍지 않은 것이 뒤늦게 후회가 되지만 정말 좋은 컨퍼런스였고, 많은 것을 배우게 되었다.


NapuCon 2016


1. Session 1 - Kevin Lee

1.1. Funtional Programming

  • 함수형 언어란

  • 수학의 함수와 같은 의미

  • 함수형 언어를 사용해야 하는 이유(함수형 언어의 장점)

  • 동시성 - 유휴자원을 최소화 하여 서버를 최대한 활용하기 위함

  • 간결 - 코드의 질과 가독성이 높아짐

  • 명확 - 기능별 분리 및 메소드의 관심사가 명확해짐

  • 테스트가 간결해진다 - 레거시 코드 양산 방지

  • Side Effect가 OOP보다 줄어든다

  • 재미가 있음?????????

1.2. ETC

  • Test Case 작성의 필수라고 생각되는 Mock을 사용하지 않고 Interpreter Pattern을 통해 Test Code 작성하는 방법이 있음

2. Session 2 - Rakuten 전민수 Lead Architect, Rakuten 심재민 Architect

2.1. Rakuten의 구조 및 업무 프로세스 - Rakuten 심재민 Architect

  • Application Layer Architect + System Layer Architect

  • 각 영역의 Architect가 설계

  • 누군가는 기술 검증 및 적용 여부 검토를 해서 적용

  • 잘 적용하고있는지, 바르게 사용하고 있는지 피드백 및 교육을 진행

  • 레거시 방지를 위해서 일정 부분에 대해서는 룰을 정해서 룰을 따라감

  • 업무 관련 Software

  • JIRA

  • Bitbucket

  • Confluence

  • HipChat

  • Integrated Development Environment Tool

  • Eclipse

  • IntelliJ

  • Persistence Middleware

  • MySQL

  • Oracle

  • MongoDB

  • Redis

  • Hadoop

  • Cassandra

  • Web (Application) Server

  • Apache

  • Apache Tomcat

  • Programming Language & Framework

  • Spring Framework

  • Laravel Framework

  • Angular JS

  • Software Development Methodology

  • Agile과 Waterfall을 혼용

2.2. Rakuten의 Realtime Search Platform 적용 사례 - Rakuten 전민수 Lead Architect

2.2.1. Verifications

  • Horizontal Scale Out

  • Low Latency

  • Trouble Shooting

  • Service Level Agreement 99.95%

  • Fault Tolerance

  • Cost Efficiency

  • Flexibility

  • Sync with Master Data

2.2.2. Target Middleware

  • MongoDB

  • 적합 판정으로 채택

  • MemSQL

  • 검증 결과 부적합 판정으로 제외

  • Cassandra

  • Java에서 Cassandra 사용에 대한 어려움이 존해

  • 특정 상황에서 퍼포먼스가 떨어지는 현상이 있음

  • Oracle Coherence

  • 비용 문제로 인하여 제외

  • ActiveMQ

  • 검증 결과 부적합 판정으로 제외

  • RabbitMQ

  • 적합 판정으로 채택

  • Redis

  • 당시 Clustering지원이 되지 않아 고민 후 자체 기술력으로 해결

2.2.3. How To

  • Front Response 2,000ms

  • Backend Response 500ms

  • Master - Slave Cache Memory DB

  • 지속적으로 Logging 및 Tracking

  • 지속적으로 Monitoring APM Management

  • Release Service include build CI / CD pipeline

2.2.4. Result

  • Provides reliable NoSQL-based search

  • 99.98% guarantee data Sync with Master DB

  • Removing repetitive tasks in Automation

  • Supports 11 languages

3. Session 3 - Kakao 서지연(Cheese)

3.1. 아름다운 코드 작성 습관

3.1.1. 룰을 정하자

  • 가독성이 좋아야 한다

  • 유지보수를 생각해야 된다

  • 코드확장을 고려해야 된다

  • style guide

3.1.2. 코드 리뷰

  • sonarqube

  • 정적 코드 분석 및 가시화

  • 잠재 위험 코드 파악

  • pull request로 진행

  • pull approve

3.1.3. Git Branch

  • 기본적인 Branch 구조는 지키자

  • master - 운영 환경 적용 및 버전 관리용

  • develop - 개발 서버 적용 및 테스트 진행용

  • hotfix - 긴급 수정 적용사항으로 master에서 파생하여 master와 develop에 바로 병합

  • feature - 신규 개발 진행용

3.2. 자동 검증 습관

  • CI / CD tool을 활용

  • 빌드 / 배포 자동화

  • 테스트 자동화

  • CI / CD tool

  • go

  • Travis CI

  • Jenkins

  • Code Coverage

  • CODACY

  • 레거시를 줄이자

3.3. 성장하는 습관

  • SNS Follow

  • Community 활동

  • 스스로 노력

  • 발표하기

4. Session 4 - Makeus 김호광(Dennis) CTO

4.1. Serverless Architecture

  • 경주 지진 시 국민안전처 홈페이지 다운

  • 동시접속자가 늘어나 Bandwith를 감당하지 못한 것으로 추측

  • 공기관 홈페이지가 클라우드로 이전하는 것은 거의 불가능

  • AWS는 SLA가 없음

  • 1,000건 중 2건정도 유실되는 것으로 확인

  • 결론은 Docker를 사용하자

5. Session 5 - I/O Inc 박미정 CTO

5.1. 스타트업 극복기

  • 스타트업 조인 시 이미 레거시로 가득

  • 걷어낼 목록 정리

  • 우선순위 결정

  • 점진적인 개선

5.2. 함수형 언어 적용기

  • 현재까지는 함수형으로 따라가는 중

  • 한번에 변화하려 하지 않고, 조금씩 천천히 장점 파악 및 실제 적용 여부 검토중

  • 다른 사람들에게 변화를 강요하지 않고 스스로 받아들일 수 있도록 시도


Docker Intro


1. Docker?

1.1. Introduction

  • 컨테이너 가상화 기술을 사용하여 어플리케이션 실행 환경을 구축 및 운영하기 위한 플랫폼

  • Go 언어로 만들어졌고, 2013년부터 Docker사에서 개발

  • 인프라 환경을 컨테이너로 관리

  • Docker Hub에서 이미지를 공유

  • 높은 이식성으로 개발 및 테스트 환경에서는 동작하지만 운영 환경에서는 동작하지 않는 리스크를 줄일 수 있음

  • 벤더(Cloud Service, OS)뿐만 아니라 여러 오픈소스와도 쉽게 연계할 수 있음

  • CI Tool(Jenkins)과 연계하면 테스트 자동화 및 무중단 배포도 가능

1.2. 컨테이너 가상화란?

  • 호스트 가상화(VMWare, VirtualBox), 하이퍼바이저 가상화(Windows 8 이상에서 지원)는 가상화 소프트웨어를 통해 OS와 하이퍼바이저 위에 또 다른 OS를 여러 개 구동시키며 그 자체로 많은 리소스를 필요로함

  • 호스트 OS상에서 논리적으로 컨테이너를 나눠 어플리케이션 동작을 위한 라이브러리와 어플리케이션 등을 컨테이너 안에 넣고, 개별 서버처럼 사용하는 것

  • 오버헤드가 적어 가볍고 빠른 것이 특징

1.3. 이식성

  • 업무 어플리케이션 실행 모듈

  • 미들웨어 라이브러리

  • OS 및 네트워크 등 인프라 환경 설정

1.4. 기본 기능

  • 이미지 생성

  • 컨테이너 동작

  • 이미지 공개 및 공유

1.5. 컴포넌트 종류

1.5.1. Engine

  • 이미지 생성과 컴포넌트 구동 등을 위한 코어 기능

  • 커맨드 실행 및 이미지 생성 등을 수행

1.5.2. Kitematic

  • 이미지 생성과 컴포넌트 구동 등을 위한 GUI 툴

1.5.3. Registry

  • 이미지를 공개 및 공유하기 위한 기능

1.5.4. Compose

  • 여러 컨테이너의 구성 정보를 코드로 정의하고 커맨드를 통해 어플리케이션 실행 환경을 구성하는 컨테이너 통합 관리 툴

1.5.5. Machine

  • 클라우드 환경에 실행 환경을 커맨드로 자동 생성하기 위한 툴

1.5.6. Swarm

  • 여러 Docker gh스트를 클러스트화 하기 위한 툴

  • Manager는 클러스터 관리와 API를 제공하며 Node는 Docker 컨테이너를 실행

1.6. namespace

  • 컨테이너를 구분하는 구조

  • 컨테이너라는 독립된 환경을 만들고 이를 나누어 어플리케이션 실행 환경을 만들 때 Linux Kernel의 namespace 기능을 사용


프론트 정적 리소스버전관리 및 독립 프로젝트로 분리


1. 프론트 정적 리소스(javascript, css) 버전 관리 및 WAS에서 분리

nginx(loadbalancing), WAS와 같은 구조로 서버를 운영할 때, 정적인 리소스도 모두 WAS에 올라가는데, 정적인 리소스는 굳이 WAS까지 갈 필요 없이 웹 서버에서 처리해도 되지 않을까 라는 생각과, 여기저기 흩어져 있는 javascript, css를 한곳에 모으기 위해 알아보기 시작

(프로젝트가 여러개가 되면 동일한 javascript 코드나 css가 여기저기 흩어져있는게 싫기도 해서 알아보기 시작했다)

결론적으로 원하는 그림대로 구성을 완료하기는 했지만 구조가 조금 복잡해진다는 단점이 생겼다

1.1. 준비물

  • git

  • nginx

  • npm

  • bower

  • grunt

  • nexus3(optional)

2. Step 1. webapp project

webapp project를 2개의 프로젝트로 나눈다. 전체적인 구조는 보통 사용하는 구조와 같고, resources 디렉토리를 gitsubmodule 프로젝트로 분리한다.

submodule 설정 관련 정보는 여기에서 확인

submodule을 등록하면 아래와 같이 .gitmodules파일이 생성된다.(굳이 git command를 사용하여 submodule을 등록하지 않고 .gitmodules파일을 생성해서 정보를 입력해줘도 된다. 이 때, 해당 디렉토리가 반드시 존재해야 된다.) submodule을 등록하기 전에 두 개의 git repository는 반드시 필요하다.

[submodule "src/main/webapp/resources"]
   path = src/main/webapp/resources
   url = git@your/repository.git

위 처럼 pathgit repository url을 등록하면 submodule등록이 완료된다.

최초 세팅 시 부모(?) 프로젝트를 받으며 submodule프로젝트까지 받을려면 아래와 같이 하면 된다.

git clone --recursive git@your/parent/repository.git

3. Step 2. resource project

resource projectnpm, bower, grunt, git, nexus(optional)을 사용한다. 우선 npm으로 필요한 패키지 정보를 받는다.

npm init
npm install -g grunt bower

이렇게 하면 gruntbower command를 사용할 수 있다.

여기서 bowernexuspackage를 등록하고, 전체적인 프론트 프로젝트(뒤에서 설명)에서 dependency를 추가하기 위해 사용한다.

bower init

위의 명령어를 사용하면 bower가 초기화 되고, bower.json파일이 생성된다.

아래는 bower.json파일의 정보다.

{
  "name": "resources",
  "version": "0.0.1",
  "description": "description of package",
  "main": [
    "css/",
    "js/",
    "img/"
  ],
  "authors": [
    "author"
  ],
  "license": "ISC",
  "homepage": "",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests",
    "package.json"
  ]
}

여기서 main은 프론트 프로젝트에서 사용할 파일, 디렉토리 목록을 나타낸 것이고, ignore는 무시할 목록들이다.

bower에서 사용할 versionbower.json에 등록(다른데다 해도 됨)한다.

bowergittag기능을 사용해서 버전을 관리한다.

예를들어 git tag 1.0.0 명령으를 사용하고, 원격 저장소에 push를 하면 해당 시점의 commit1.0.0이 되는 것이다.

매번 git tag 명령어로 버전을 등록하기는 힘드므로 grunt를 사용해서 자동화를 한다.

우선 필요한 것은 grunt-tag이다.

npm install --save-dev grunt-tag

grunt-git을 통해서도 관리를 할 수 있기는 하나, grunt-tag가 중복된 버전이 있을 시 하위에 등록된 것을 지워주고, 신규로 등록을 해주고, 원격 저장소까지 자동으로 올려줘서 grunt-tag를 선택했다.

package.json 파일과 동일한 위치에 Gruntfile.js를 생성해준다. 파일의 내용은 아래와 같다.

module.exports = function(grunt) {
   grunt.initConfig({
      tag: {
         options: {
            // Create or move the tag (default: true)
            tag: true,
            // Push the tag to remote (default: true)
            push: true,
            // File where th read the version (default: package.json)
            file: 'bower.json',
            // Name of the tag (default: '<%= version %>')
            tagName: '<%= version %&gt',
            // Message of the tag (default: 'Version <%= version %>')
            tagMsg: 'New version: <%= version %>',    // default: 'Version <%= version %>'
            // The remote where to push the tag (default: 'origin')
            remote: 'origin'
         }
      }
   });

   grunt.loadNpmTasks('grunt-tag');

   grunt.registerTask('default', ['tag']);
};

위의 내용은 bower.json 파일에서 version 정보를 가져와 git tag에 등록을 하고, remote(origin) 저장소에 push를 하라는 내용이다.

버전 등록까지 완료가 됐으니 이제 nexus에 등록을 하도록 한다.

bower의 경우 nexus에 등록을 하더라도 저장되는 정보는 어떤 package가 어떤 repository랑 연결돼 있는지에 대한 정보 뿐이니, nexus 사용에 대한 필요성을 느끼지 못한다면 굳이 사용할 필요는 없다. 이런 사람은 이 파트는 그냥 넘어가도 된다.

나는 git repository url을 사용하기 싫기도 하고, 기존에 nexus를 사용하고 있어서 nexus를 사용했다.

bowernexus 3부터 지원이 되므로 nexus 3이 필요하다. 무료 버전 다운로드는 여기에서 할 수 있다.

nexus 3를 설치를 하면 기본 설정으로 bower repository는 생성되어 있지 않아서 신규로 생성을 해 줘야 한다.

관련 정보는 여기를 참조하길 바란다.

npm을 통해 필요한 패키지를 설치해준다.

npm install --save-dev bower-nexus3-resolver

이제 .bowerrc 라는 파일을 bower.json과 같은 위치에 생성을 하고 아래와 같이 작성한다.

{
   "registry" : {
      "search" : [
         "http://your/bower/nexus/repository/bower-public/"
      ],
      "register" : "http://ID:PASSWORD@your/bower/nexus/repository/bower-snapshots/"
   },
   "resolvers" : [ "bower-nexus3-resolver" ]
}

idpasswordnexus에서 등록한 repository 등록 권한이 있는 사용자 정보를 입력하면 된다.

  • 참고로 id와 password에 특수문자를 사용할 수 있기는 하나 일반적인 url에서 규칙으로 사용되는 특수문자는 사용하면 안된다.(ex: /, #, @)*

이렇게 작성을 한 후 커맨드 창에서 아래의 명령어를 입력한다.

bower register your-package-name git@your.repository.git

이렇게 하면 등록이 완료됐다.

아래의 명령어를 입력하면 package 정보를 확인할 수 있다.

bower info your-package-name

4. Step 3. front project

이 프로젝트에서는 전체적으로 사용하는 프론트 라이브러리나, 내가 만든 프론트 소스를 관리한다.

여기서 필요한 툴은 아래와 같다.

  • npm

  • bower

  • grunt

  • git

  • nexus(optional)

npmbower를 초기화 한다.

npm init
bower init

gruntbower를 앞에서 install했으므로 해당 명령은 건너뛴다.

nexus를 사용한다면 bower-nexus3-resolver 패키지를 등록하고 .bowerrc파일을 생성해서 아래와 같이 내용을 작성해 준다.

npm install --save-dev bower-nexus3-resolver
{
   "registry" : {
      "search" : [
         "http://nexus.mitpdev.co.kr/repository/bower-public",
         "http://bower.herokuapp.com/packages"
      ]
   },
   "resolvers" : [ "bower-nexus3-resolver" ]
}

bower에서 필요한 라이브러리 패키지 설치한다.

bower install --save jquery bootstrap

이렇게 하면 bower.json파일에 dependency가 추가된다.

나는 라이브러리도 버전별로 등록을 하고 싶어서 아래와 같이 등록을 했다.

{
  "name": "package name",
  "description": "",
  "main": "index.js",
  "authors": [
    "author"
  ],
  "license": "ISC",
  "homepage": "",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "static",
    "test",
    "tests"
  ],
  "dependencies": {
    "jquery-2.2.3": "jquery#2.2.3",
    "bootstrap-3.3.6": "bootstrap-css#3.3.6",
    "my-package-0.0.1": "my-package#0.0.1"
  }
}

만약 넥서스를 사용하지 않는다면 자신의 프로젝트를 아래와 같이 등록할 수 있다.

{
  ...
  "dependencies": {
      ...
    "my-package-0.0.1": "git@your/repository.git#0.0.1"
  }
}

이렇게 전체적인 프론트 관리 프로젝트를 별도로 두는 이유는, 한 곳으로 모아서 WAS가 아닌 Web Server에 올리기 위함도 있지만, javascriptcss파일을 난독화(minify)하고, gzip으로 압축하기 위함이다.

이제 grunt를 통해 빌드 자동화를 하도록 한다. Gruntfile.js 파일을 생성하고, 커맨드창에 아래의 명령어를 입력한다.

npm install --save-dev grunt-cli grunt-bower-task grunt-contrib-uglify grunt-contrib-cssmin grunt-contrib-compress

Gruntfile.js는 아래와 같이 내용을 채워준다.

module.exports = function(grunt) {
   var path = require('path');

   grunt.initConfig({
      pkg: grunt.file.readJSON('package.json'),
      bower: {
         install: {
            options: {
               copy: true,
               targetDir: './static',
               install: true,
               layout: function(type, component, source) {
                  var sourcePath = source.replace(/^.*\.([a-zA-Z]+)$/, '$1');

                  if (sourcePath.indexOf('bower_components') &gt= 0) {
                     sourcePath = source.replace('bower_components\\' + component + '\\', '');
                     sourcePath = sourcePath.replace('bower_components/' + component + '/', '');
                  }

                  return path.join(component, sourcePath);
               }
            }
         }
      },
      cssmin: {
         options: {
            // 'min', 'gzip'
            report: 'min'
         },
         target: {
            files: [{
               expand: true,
               cwd: 'static/',
               src: ['**/*.css', '!**/*.min.css'],
               dest: 'static/',
               ext: '.min.css',
               extDot: 'first'            }]
         }
      },
      uglify: {
         options: {
            // false, 'none', 'min', 'gzip'
            report: 'min',
            compress: {
               drop_console: true
            },
            // false, 'all', 'some', Function
            preserveComments: 'some'
         },
         target: {
            files: [{
               expand: true,
               cwd: 'static/',
               src: ['**/*.js', '!**/*.min.js'],
               dest: 'static/',
               ext: '.min.js'            }]
         }
      },
      compress: {
         options: {
            mode: 'gzip',
            level: 5
         },
         target: {
            files: [{
               expand: true,
               cwd: 'static/',
               src: ['**/*.js'],
               dest: 'static',
               ext: '.js.gz'
            }, {
               expand: true,
               cwd: 'static/',
               src: ['**/*.css'],
               dest: 'static',
               ext: '.css.gz'
            }, {
               expand: true,
               cwd: 'static/',
               src: ['**/*.min.js'],
               dest: 'static',
               ext: '.min.js.gz'
            }, {
               expand: true,
               cwd: 'static/',
               src: ['**/*.min.css'],
               dest: 'static',
               ext: 'min.css.gz'
            }]
         }
      }
   });

   grunt.loadNpmTasks('grunt-bower-task');
   grunt.loadNpmTasks('grunt-contrib-cssmin');
   grunt.loadNpmTasks('grunt-contrib-uglify');
   grunt.loadNpmTasks('grunt-contrib-compress');

   grunt.registerTask('default', ['bower', 'cssmin', 'uglify', 'compress']);
};

bower task에서 layout은 파일들의 디렉토리를 어떻게 나눌 것인지에 대한 내용이다. 기본 값은 byType인데 이대로는 내가 원하는 구조대로 되지 않아서 해당 부분을 수정했다. cssminuglify는 각각 cssjavascript 파일을 난독화(minify)하는 것이고, compressgzip으로 압축하는 것이다.

gruntregisterTask에서 등록한 순서대로 해당 명령들을 실행한다.

위같은 경우는 bower -> cssmin -> uglify -> compress 순으로 실행하게 된다.

이제 command창에서 grunt 명령어를 입력하면 bower_components 디렉토리와 static 디렉토리가 생성되는 것을 확인할 수 있다. 여기서 bower_components는 설정하기 위해 필요한 파일들을 받아오는 역할만 할 뿐이므로 지워도 된다. 여기서 확인해야 될 것은 static 디렉토리다.

각각의 패키지별로 디렉토리가 생성되고, js, css파일이 나누어져서 구성되어 있다.

또한 min 파일과 gzip으로 압축한 파일들이 들어있는데, 여기서 필요없는 파일들은 Gruntfile.js 파일을 각자 입맛대로 수정해서 지우면 된다.

이 프로젝트 또한 git repository에 등록해서 서버에서 사용한다.(나는 bower_components, node_modules, static 디렉토리에 gitignore를 걸어놓고 실제 이 프로젝트는 의존성만 관리하는 식으로 등록해놨다 실제 빌드는 서버에서 수행하도록 했다.)

5. Step 4. front project(server)

front project를 서버에 세팅한다. git으로 관리하므로 우선 clone을 받는다.

git clone git@front/project/repository.git

그리고 grunt로 빌드를 한다.

이것으로 설정은 끝났다.

6. Step 5. nginx

이제 nginx설정을 한다. nginx에서 gzip 설정을 하고, root를 등록해준다.

gzip.conf

gzip on;
gzip_disable "Mozilla/4";
gzip_disable "msie6";
gzip_static on;
gzip_vary on;
gzip_min_length 100;
gzip_buffers 4 32k;
gzip_http_version 1.1;
gzip_comp_level 2;
gzip_types
        text/plain
        text/javascript
        text/css
        application/javascript;
server {
        listen          80;
        server_name     resources.your.domain;

        root    /your/front/project/directory/static;

        access_log      /var/log/nginx/resources_acces.log;
        error_log       /var/log/nginx/resources_error.log;

        location / {
        }
}

nginx를 재실행 한다.

service nginx restart

이제 모든 설정이 끝났다.

7. ETC…

추가로 webapp 프로젝트에서 설정해 줘야 할 것이 개발시, buildresource path를 다르게 설정하는 것인데, 이것은 각자의 프로젝트에서 사용하는 빌드 툴에 따라 알아서 설정하면 된다. maven의 경우 profile을 활용해서 properties 파일을 바꿔치는 방법이 있다. 또한, buildwebapp 프로젝트에서 resources 디렉토리를 exclude를 하는 설정을 하면 모든 설정이 완료된다.

이렇게 설정함으로써 프로젝트 구조 자체는 복잡해지지만, 여러 프로젝트가 있을 경우, 공통된 javascript를 모두 각각의 서버에 올릴 필요도 없고, 버전 관리까지 되므로, 리소스 캐시에 대한 문제점도 사라지게 된다.(혼자만의 생각)

각 서버별 사용하는 리소스의 버전도 properties에 등록을 해서 관리한다면, 프론트 리소스의 버전이 올라갔을 때 properties에 등록된 버전만 수정하면 된다.


MariaDB Replication(복제) 설정


1. Replication?

ReplicationMariaDB에서 제공하는 Master-Slave간에 데이터를 복제(이중화)하는 기능입니다. MariaDB에서는 Replication외에도 Galera Cluster를 이용하는 방법도 있습니다.

  • 적용 환경

Master OS : CentOS 7 Master DMBS : MariaDB 10.1.11

Slave OS : CentOS 7 Slave DBMS : MariaDB 10.1.11

Replication 설정 시 MasterSlave에 동일한 구조의 DB나 TABLE이 존재해야 합니다.

1.1. Master 설정

/etc/my.cnf.d/ 에서 server.cnf(혹은 my.cnf)파일에 다음과 같이 설정합니다.

[mysqld]
server-id               = 1
log_bin                 = /var/log/mysql/mariadb-bin
log_bin_index           = /var/log/mysql/mariadb-bin.index
expire_logs_days        = 10
max_binlog_size         = 100M

여기서 /var/log/mysql/은 소유자가 mysql이어야 합니다.

그 후 DBMS 재시작을 합니다.

service mariadb restart

MariaDB에 접속하여 Replication 사용자를 생성합니다.

CREATE USER 'replication_user'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO replication_user;

운영중인 서버라면 DB에 락을 걸어주고 MASTER정보를 조회합니다.

FLUSH TABLES WITH READ LOCK; # 운영중인 서버라면 테이블에 락을 걸어준다.
SHOW MASTER STATUS; # MASTER 정보를 조회
+--------------------+----------+--------------+------------------+
| File               | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+--------------------+----------+--------------+------------------+
| mariadb-bin.000005 |     2033 |              |                  |
+--------------------+----------+--------------+------------------+

여기서 File과 Position은 Slave 설정 시 필요하니 잘 기억해 두세요.

1.2. Slave 설정

/etc/my.cnf.d/ 에서 server.cnf(혹은 my.cnf)파일에 다음과 같이 설정합니다.

[mysqld]
server-id               = 2
log_bin                 = /var/log/mysql/mariadb-bin
log_bin_index           = /var/log/mysql/mariadb-bin.index
expire_logs_days        = 10
max_binlog_size         = 100M
relay_log               = /var/log/mysql/relay-bin
relay_log_index         = /var/log/mysql/relay-bin.index
relay_log_info_file     = /var/log/mysql/relay-bin.info
log_slave_updates
replicate-ignore-db     = test
replicate-ignore-db     = information_schema
replicate-ignore-db     = mysql

replicate-ignore-db는 복제하지 않을 DB를 지정해주는 것입니다. replicate-ignore-db 외에도 replicate-do-table, replicate-do-db 등 특정 DB 혹은 테이블만 지정해서 복제하는 방법도 있습니다.

이제 DBMS를 재시작 합니다.

service mariadb restart

이제 mariadb에 접속하여 다음의 명령어를 실행시킵니다. 여기서 MASTER_LOG_FILEMASTER_LOG_POSMaster 정보(Master 설정 시 조회한 정보)와 동일하게 설정합니다.

CHANGE MASTER TO
MASTER_HOST='Master IP',
MASTER_USER='replication_user',
MASTER_PASSWORD='password',
MASTER_PORT=portNumber,
MASTER_LOG_FILE='Master File',
MASTER_LOG_POS=Master Position,
MASTER_CONNECT_RETRY=10;

FLUSH PRIVILEGES;

START SLAVE;
SHOW SLAVE STATUS\G;
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: ip
                  Master_User: replication_user
                  Master_Port: port
                Connect_Retry: 10
              Master_Log_File: master file
          Read_Master_Log_Pos: master pos
               Relay_Log_File: relay file
                Relay_Log_Pos: relay pos
        Relay_Master_Log_File: relay master file
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB:
          Replicate_Ignore_DB: test,information_schema,mysql
           Replicate_Do_Table:
       Replicate_Ignore_Table:
      Replicate_Wild_Do_Table:
  Replicate_Wild_Ignore_Table:
                   Last_Errno: 0
                   Last_Error:
                 Skip_Counter: 0
          Exec_Master_Log_Pos: exec master pos
              Relay_Log_Space: relay log space
              Until_Condition: None
               Until_Log_File:
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File:
           Master_SSL_CA_Path:
              Master_SSL_Cert:
            Master_SSL_Cipher:
               Master_SSL_Key:
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error:
               Last_SQL_Errno: 0
               Last_SQL_Error:
  Replicate_Ignore_Server_Ids:
             Master_Server_Id: 1
               Master_SSL_Crl:
           Master_SSL_Crlpath:
                   Using_Gtid: No
                  Gtid_IO_Pos:
      Replicate_Do_Domain_Ids:
  Replicate_Ignore_Domain_Ids:
                Parallel_Mode: conservative

위의 명령어를 통해 slave 상태를 조회할 수 있습니다. slave상태를 조회 시 에러가 없다면 설정이 완료되었습니다. 이제 Master로 다시 돌아가서 락을 해제합니다.

UNLOCK TABLES;

Pagination