잊지 않겠습니다.

nodejs + pm2 application을 구성한 dockfile을 구성하는 방법에 대해서 알아보도록 하겠습니다.

먼저 code를 어떤 방법으로 배포할지에 대한 process를 정하는 것이 중요합니다. 이는 Dockerfile을 어떻게 만들지에 대한 방향이 결정나게 됩니다.

배포 Process

제가 구성한 Docker를 이용한 배포 process는 다음과 같습니다.

  1. nvm 설치
  2. nvm을 이용한 node 설치
  3. pm2 설치
  4. ssh 인증 + git clone 을 통한 code download
  5. npm install
  6. .pm2/logs 위치에 따른 host 파일 공유
  7. docker run의 parameter pass를 통한 pm2 start json 파일 전송

1. nvm 설치 & node 설치 & pm2 설치

ubuntu image를 기본으로 하여 구성을 하도록 하겠습니다. nodejs application은 npm install을 통해서 node_modules을 다운로드 받아서 compile되는 환경이기 때문에 build환경역시 갖추는 것이 중요합니다.

FROM ubuntu # 빌드 환경 구성 RUN apt-get update RUN apt-get install -y wget build-essential keychain git python # NVM_DIR, NODE_VERSION에 따른 ENV 설정 ENV NVM_DIR /usr/local/nvm ENV NODE_VERSION 7.3.0 # NVM 다운로드 & 설치 RUN wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.32.1/install.sh | bash # Shell 변경 (sh -> bash) SHELL ["/bin/bash", "-c"] # nvm 구성 & node install RUN source $NVM_DIR/nvm.sh; \ nvm install $NODE_VERSION; \ nvm use --delete-prefix $NODE_VERSION; ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH # PM2 설치 RUN npm install -g pm2

2. ssh 인증 구성

ssh 인증을 위해서 private key를 image안에 COPY할 필요가 있습니다. 그리고 $(ssh-agent)값을 지정해줄 필요가 있습니다. 그런데, eval을 이용한 값 설정의 경우, Dockerfile에서 할 수 없습니다. 따라서 do-ssh.sh 실행 파일을 구성해서 처리할 image 내부에서 실행할 필요가 있습니다.

다음은 do-ssh.sh파일의 내용입니다.

eval "$(ssh-agent)" && ssh-agent -s ssh-add /root/.ssh/id_rsa
# .ssh Folder작성 & private key Copy RUN mkdir /root/.ssh COPY [ "id_rsa", "/root/.ssh/id_rsa" ] # SSH Key 권한 설정 - 권한이 600이 아닌경우, id_rsa key를 얻어내지 못합니다. RUN chmod 600 /root/.ssh/id_rsa # do-ssh.sh 파일 COPY COPY [ "do-ssh.sh", "/root/do-ssh.sh" ] # 실행권한 설정 RUN chmod 777 /root/do-ssh.sh # SHELL 변경 (bash -> sh) SHELL ["/bin/sh", "-c"] # do-ssh.sh RUN RUN /root/do-ssh.sh # SSH 접근 check. -o StrictHostKeyChecking=no option이 없으면 docker container에서 실행되지 않습니다. RUN ssh -T -v git@10.1.95.184 -o StrictHostKeyChecking=no

3. git code download & npm install

nodejs Application이 위치될 folder를 만들어주고, git clone을 통해서 code를 받고, npm install을 하는 구성입니다.

# apps folder 작성 RUN mkdir /apps # Shell 변경 (sh -> bash) SHELL ["/bin/bash", "-c"] WORKDIR /apps RUN git clone git@10.1.95.184:/home/git/repo/fms-api-v2.git -o StrictHostKeyChecking=no WORKDIR /apps/fms-api-v2 # npm 설치 RUN npm install

npm install을 통해서 필요한 node module들을 모두 받아주고 build과정을 거치게 됩니다.

4. PORT 공유 & PM2 Log volume 구성

제가 만든 application은 5000, 8000번 port를 open시켜서 사용합니다. 사용되는 PORT를 EXPOSE 시켜주는 과정이 필요하게 됩니다. 그리고 pm2의 log 파일을 host에 기록하도록 volume을 추가합니다.

# expose 5000, 8000 port EXPOSE 8000 EXPOSE 5000 # VOLUME 설정 pm2 log folder VOLUME /root/.pm2/logs

5. 뒷처리과정

매우매우 위험한 파일을 container에 넣어둔것을 기억하고 있어야지됩니다. private key가 바로 그것이지요. 꼭 이 파일을 지워주는 과정이 필요합니다. 마지막으로pm2 실행에 대한 ENTRYPOINT와 CMD 설정이 필요합니다. 제가 만든 application은 설정 json파일에 따라서 동작환경이 변경되게 됩니다. 설정 json파일을 외부에서 parameter로 넣어줄 수 있도록 설정해주는 것이 필요합니다.

# Private Key 삭제 RUN rm /root/.ssh/id_rsa # ENTRYPOINT 설정 docker container에서 pm2를 실행시킬때는 pm2-docker를 통해서 실행합니다. ENTRYPOINT [ "pm2-docker" ] # CMD 설정 - parameter가 없는 경우, default 값을 지정하게 됩니다. CMD ["pm2/dev-system.json"]

6. build image & docker run

image를 만들기 위해서, docker build를 다음과 같이 구성합니다.

docker build -t nodejs-pm2-application .

이제 process가 진행되면 nodejs-pm2-application으로 image가 만들어지게 됩니다.

만들어진 image를 이용해서 container를 배포하는 명령어는 다음과 같습니다.

docker run -it --name node-app -p 5000:5000 -p 8000:8000 -v /logs/pm2:/root/.pm2/logs -t nodejs-pm2-application pm2/api-system1.json
  • -p option으로 5000, 8000번 port를 host에 연결합니다.
  • -v options으로 /root/.pm2/logs folder를 host의 /logs/pm2로 mount 시킵니다.
  • 마지막에 command로 /pm2/api-system1.json을 넣어 container의 COMMAND를 pm2-docker pm2/api-system1.json으로 변경시킵니다.

SUMMARY

Dockerfile을 구성하는 것은 Application의 형태에 따라, 개발환경에 따라 많은 차이를 가지고 오게 됩니다. nodejspythonruby와 같이 외부 library를 받아 compile하는 과정을 가지게 된다면 image내부에서 code를 직접 git으로 받아올 필요가 있습니다. 그러나, JAVAC#GO와 같이 universal compile이 되는 경우, Docker image를 만들때, compile시켜 ADD를 통해서 실행파일을 image안에 넣는것이 더 좋을것 같습니다.

다음은 전체 Dockerfile입니다.

FROM ubuntu # 빌드 환경 구성 RUN apt-get update RUN apt-get install -y wget build-essential keychain git python # NVM_DIR, NODE_VERSION에 따른 ENV 설정 ENV NVM_DIR /usr/local/nvm ENV NODE_VERSION 7.3.0 # NVM 다운로드 & 설치 RUN wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.32.1/install.sh | bash # Shell 변경 (sh -> bash) SHELL ["/bin/bash", "-c"] # nvm 구성 & node install RUN source $NVM_DIR/nvm.sh; \ nvm install $NODE_VERSION; \ nvm use --delete-prefix $NODE_VERSION; ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH # PM2 설치 RUN npm install -g pm2 # .ssh Folder작성 & private key Copy RUN mkdir /root/.ssh COPY [ "id_rsa", "/root/.ssh/id_rsa" ] # SSH Key 권한 설정 - 권한이 600이 아닌경우, id_rsa key를 얻어내지 못합니다. RUN chmod 600 /root/.ssh/id_rsa # do-ssh.sh 파일 COPY COPY [ "do-ssh.sh", "/root/do-ssh.sh" ] # 실행권한 설정 RUN chmod 777 /root/do-ssh.sh # SHELL 변경 (bash -> sh) SHELL ["/bin/sh", "-c"] # do-ssh.sh RUN RUN /root/do-ssh.sh # SSH 접근 check. -o StrictHostKeyChecking=no option이 없으면 docker container에서 실행되지 않습니다. RUN ssh -T -v git@10.1.95.184 -o StrictHostKeyChecking=no # apps folder 작성 RUN mkdir /apps # Shell 변경 (sh -> bash) SHELL ["/bin/bash", "-c"] WORKDIR /apps RUN git clone git@10.1.95.184:/home/git/repo/fms-api-v2.git -o StrictHostKeyChecking=no WORKDIR /apps/fms-api-v2 # npm 설치 RUN npm install # expose 5000, 8000 port EXPOSE 8000 EXPOSE 5000 # VOLUME 설정 pm2 log folder VOLUME /root/.pm2/logs # Private Key 삭제 RUN rm /root/.ssh/id_rsa # ENTRYPOINT 설정 docker container에서 pm2를 실행시킬때는 pm2-docker를 통해서 실행합니다. ENTRYPOINT [ "pm2-docker" ] # CMD 설정 - parameter가 없는 경우, default 값을 지정하게 됩니다. CMD ["pm2/dev-system.json"]

Happy Coding~

저작자 표시 동일 조건 변경 허락
신고
Posted by xyzlast Y2K

queryDSL이 4.x대로 변경이 되면서 package명에 큰 변화가 생겼습니다. 기존 com.mysema.querydsl 에서 com.querydsl로 package명이 바뀌어서 간략화 되었지요.

기존 querydsl 처리 부분에 대한 build.gradle을 변경한다면 다음과 같습니다.


dependencies {
compile("org.springframework.boot:spring-boot-starter-web") {
exclude module: "spring-boot-starter-tomcat"
}
compile 'com.graphql-java:graphql-java:2.1.0'
compile("org.springframework.boot:spring-boot-starter-jetty")
compile("org.springframework.boot:spring-boot-starter-actuator")
testCompile("junit:junit")
testCompile("org.springframework.boot:spring-boot-starter-test")
compile 'com.querydsl:querydsl-apt:4.1.4'
compile 'com.querydsl:querydsl-jpa:4.1.4'
compile 'org.springframework.data:spring-data-jpa:1.10.4.RELEASE'
compile group: 'org.hibernate', name: 'hibernate-core', version: '5.2.3.Final'
compile "org.hibernate:hibernate-entitymanager:5.2.3.Final"
compile 'com.h2database:h2:1.4.187'
compile group: 'org.aspectj', name: 'aspectjrt', version: '1.8.9'
compile group: 'org.aspectj', name: 'aspectjweaver', version: '1.8.9'
compileOnly "org.projectlombok:lombok:1.16.12"
}


sourceSets {
main {
java {
srcDirs 'src/main/java', 'src/main/generated'
}
}
}

task generateQueryDSL(type: JavaCompile, group: 'build', description: 'Generates the QueryDSL query types') {
file(new File(projectDir, "/src/main/generated")).deleteDir()
file(new File(projectDir, "/src/main/generated")).mkdirs()
source = sourceSets.main.java
classpath = configurations.compile + configurations.compileOnly
options.compilerArgs = [
"-proc:only",
"-processor", "com.querydsl.apt.jpa.JPAAnnotationProcessor"
]
destinationDir = file('src/main/generated')
}

compileJava {
dependsOn generateQueryDSL
}

clean.doLast {
file(new File(projectDir, "/src/main/generated")).deleteDir()
}


기존의 generateQueryDSL task를 유지할 필요 없이, java compile option으로 QClass 생성을 넣어주는것으로 script를 간략화시키는것이 가능합니다. 또한 Q class가 존재할 때, 기존 Q Class를 지워주지 않으면 compile 시에 에러를 발생하게 되기 때문에 지워주는 작업역시 doFirst에 같이 실행하고 있습니다.

기존의 build.gradle보다 간략해보여서 전 개인적으로 맘에 드네요.

저작자 표시 동일 조건 변경 허락
신고
Posted by xyzlast Y2K
TAG gradle, java, JPA

Dependency Injection

angularjs2 2016.09.28 15:17

Dependency Injection은 angular, angular2의 핵심 기능입니다.

이미 Server side 부분에서 자주 언급되고 있는 것으로, 특정 객체가 사용하는 객체군들을 만들어서 객체를 사용하는 쪽에서 골라 사용하는 개념이지요.

angularjs2는 자체 Dependency Injection Framework를 가지고 있습니다. 모든 ComponentServicePipe,Delegate는 Dependency Injection Framework를 통해 사용되어야지 됩니다.

Root Dependency Injection Tree

angularjs2 applicaiton은 한개 이상의 NgModule을 갖습니다. bootstrap에 연결된 이 NgModule에서 선언된providers import의 경우에는 전체 Global Root Dependency Injection Tree에 등록되게 됩니다.

NgModule에 등록된 providers와 import는 모든 Component에서 사용가능합니다. 각 Component에서 한정적으로만 사용될 Service나 PipeDelegate의 경우에는 각 Component별 provider에 등록해서 사용하면 됩니다.

@Injectable

@Injectable annotation은 DI 대상을 지정합니다. service에서만 @Injectable annotation이 사용되게 됩니다. 다른@Component@Pipe@Directive annotation 모두 @Injectable의 subset이기 때문에 따로 지정할 필요는 없습니다.

Inject Provider

기본적으로 providers에는 그 객체가 지정됩니다. 다음 코드는 일반적인 providers code입니다.

providers: [ Logger ]

이는 축약된 표현으로 원 표현은 다음과 같습니다.

providers: [ { provide: Logger, useClass: Logger } ]

Logger라는 객체를 얻어낼려고 하면 Logger를 넘겨준다는 표현식이 됩니다. 이를 좀 응용하면 다른 일들을 할 수 있습니다. Logger로 지정해두고, AnotherLogger를 inject 시킬 수 있다는 뜻이 됩니다. 다음과 같이요.

providers: [ AnotherLogger, { provide: Logger, useClass: AnotherLogger } ]

위 표현식은 Logger와 AnotherLogger 2개의 instance가 생성되게 됩니다. 모든 Logger를 AnotherLogger로 바꾸어 사용하고 싶은 경우에는 다음과 같이 선언합니다.

providers: [ AnotherLogger, { provide: Logger, useExisting: AnotherLogger } ]

지정된 객체는 singleton instance로 동작하는 것이 기본값입니다. 사용될 때마다 새로운 객체를 얻어내기 위해서는Factory를 생성해야지 됩니다. Factory는 function 형태로 다음과 같이 선언될 수 있습니다.

const anotherLoggerFactory = (logger: Logger) => { return new AnotherLogger(logger); };

이렇게 선언된 Factory를 다음과 같이 providers에 등록하면 이제 AnotherLogger는 사용될 때마다 신규 객체를 생성해서 처리가 되게 됩니다.

providers : [ { provide: AnotherLogger, useFactory: anotherLoggerFactory, deps: [ Logger ] } ]

Value Provider

직접적인 을 제공하고 싶을 때 사용할 수 있습니다. applicaiton의 여러 설정들이나 상수값들을 지정할 때 사용 가능합니다. 다음과 같은 값을 전달할 때 사용할 수 있습니다.

const appConfig = { apiUrl: 'http://testserver:8080/api', userToken: 'jkfla;sjdifpqiowjerkjskadj;fla' };

그런데, 이와 같은 값을 어떻게 하면 provider에 지정이 가능할지 의문이 듭니다. 위 은 객체가 아닙니다. 객체가 아니기 때문에 DI에서 이용되는 Class가 무엇인지 알 수 없습니다. 그래서 angular2에서는 OpaqueToken이라는 것을 제공하고 있습니다. OpaqueToken을 이용해서 다음과 같이 선언후, @Inject에서 OpaqueToken을 지정해주면 됩니다.

import { OpaqueToken } from '@angular/core'; export const APP_CONFIG = new OpaqueToken('app.config'); .... import { APP_CONFIG } from './config'; providers: [ { provide: APP_CONFIG, useValue: appConfig } ] .... constructor(@Inject(APP_CONFIG) config: any) { console.log(config.apiUrl); }

Manual Injector

직접적으로 객체를 DI tree에서 가지고 와서 처리하고 싶을 때도 있을 수 있습니다. sping에서 ApplicationContext에서 바로 객체를 들고오고 싶을 그럴 때와 같이요. 이러한 경우에는 다음과 같이 처리하면 됩니다.

export class InjectorComponent { car: Car = this.injector.get(Car); heroService: HeroService = this.injector.get(HeroService); hero: Hero = this.heroService.getHeroes()[0]; constructor(private injector: Injector) { } }

Root Provider에 이미 Injector 서비스가 등록이 되어 있습니다. 이 등록된 서비스를 가지고 와서 사용하면 됩니다. 개인적으로는 정말 추천하지 않는 방법이긴 합니다.;

SUMMARY

angular2는 다양한 방법의 DI를 제공하고 있습니다. Spring이나 CastleNinject등을 사용해보신 분들이라면 매우 익숙하겠지만, 지금까지 FrontEnd만 해본 사람들에게는 복잡한 내용임은 사실입니다. 그렇지만, DI는 angular2에서 가장 중요한 기능중 하나입니다. 객체를 어떻게 다루고, 관리할지에 대한 pattern을 결정하는 설정이니까요. 개인적으로는OpaqueToken이 매우 인상적인것 같습니다.

저작자 표시 동일 조건 변경 허락
신고
Posted by xyzlast Y2K


티스토리 툴바