개발2016.07.26 23:45

개인적인 오픈소스 프로젝트 개발을 진행하면서 경험한 내용과 느낌을 간단하게 정리해보기로 했습니다. 현재 github(https://github.com/Pyohwan/JakduK)에 소스 코드가 올라가 있습니다. 또한 K리그 작두왕(https://jakduk.com)이 이 소스 코드로 돌아가는 것입니다.


현재 K리그 작두왕의 빌드 및 배포는 Jenkins를 통해서 이루어지고 있습니다. 빌드 및 배포를 수행하는 대상은 4곳인데요.

스테이징과 프로덕션으로 크게 나뉘어 집니다. 또한 RESTful만 담당하는 API 서버와, 웹쪽 렌더링을 담당하는 WEB 서버가 있습니다.


이번 글에서는 Jenkins로 빌드하고 원격지(프로덕션)에 배포하는 방법을 써볼까 합니다.


먼저 Jenkins가 설치된 서버가 있어야 합니다. 저는 Centos 6 서버에 rpm으로 Jenkins를 쉽게 설치했습니다. Jenkins 설치에 대해서는 구글에서 검색해보면 많이 나오니 생략하겠습니다.



JAKDUK Jenkins URL입니다. 물론 계정 정보가 있어야 접속할 수 있습니다.




현재 3개의 아이템이 등록되어 있습니다.


JAKDUK_API_Production 아이템의 Build with Parameters를 선택합니다.


TASK_TYPE에는 각각 build, bowerInstall, deply가 있습니다.





JAKDUK_API_Production 아이템의 구성을 살펴볼께요.


Repositories 설정은 Github에 있는 JAKDUK Repository URL를 사용하고 있고, 빌드에 사용할 branch를 설정해 줍니다.



빌드 환경이 중요합니다.

Execute shell을 선택하고, TASK_TYPE에 맞는 행동을 수행하도록 쉘 명령어를 사용했습니다.


WORK="/var/lib/jenkins/workspace"



if [ "$TASK_TYPE" == "build" ]; then

$WORK/JakduK_build/gradlew clean test build

elif [ "$TASK_TYPE" == "bowerInstall" ]; then

$WORK/JakduK_build/gradlew bowerInstall

elif [ "$TASK_TYPE" == "deploy" ]; then

SVR="172.30.1.33"

ssh jakduk@$SVR "sudo systemctl stop tomcat"

    ssh jakduk@$SVR "sudo rm -rf /usr/local/tomcat/webapps/ROOT"

    scp $WORK/JakduK_build/jakduk-web/build/libs/*.war tomcat@$SVR:/usr/local/tomcat/webapps/ROOT.war

    ssh jakduk@$SVR "sudo systemctl start tomcat"

fi



build TASK를 선택하면 gradlew로 test build를 수행하고, bowerInstall TASK를 선택하면 자바스크립트의 의존 라이브러리를 해결해주는 bower 툴을 실행합니다. (이부분은 RESTful 서버로 완전히 분리되면 사라질 예정이고, bowerInstall에 대한 부분은 https://github.com/JakduK/JakduK/blob/master/jakduk-web/build.gradle#L28 를 참고)

deploy TASK를 선택하면, 원격지(172.30.1.33)의 tomcat을 중지 하고, 빌드한 war를 이동 시킨 다음에 다시 tomcat을 시작합니다.


하지만 ssh나 scp를 통해 원격지 머신을 컨트롤 하기 위해서는 권한 문제를 해결해야 합니다. deploy TASK를 다시 한번 살펴보면 ssh 시에는 jakduk 유저를 사용하여 sudo 명령어를 수행하고, scp를 할때에는 tomcat 유저로 수행하는것을 볼 수 있습니다.


  1. Jenkins를 별도의 유저(jakduk)로 실행할것. 
  2. Jenkins에서 원격지(172.30.1.33)으로 암호 입력 없이 ssh 수행이 가능할것.


위 두가지를 해결해야만 Jenkins에서 원격지로 war를 배포할 수 있습니다.


Jenkins를 별도의 유저(jakduk)로 실행

Jenkins가 yum install을 통해 설치 되었다면 Jenkins는 기본적으로 jenkins 유저를 하나 만들고, jenkins 유저로 실행하게 됩니다. 하지만 jenkins유저는 nologin 설정이 있기 때문에, 로그인 할수 없는 유저입니다. 원격지의 자원을 마음대로 조정하기 위해서는 jenkins 유저의 공개키가 필요한데, 로그인을 할수 없으니 만들수가 없습니다. 따라서 jakduk유저를 새로 만들었습니다. 


useradd 명령어로 jakduk 유저를 만듭니다.


# useradd jakduk



JAKDUK Jenkins 서버에서 /etc/sysconfig/jenkins 파일을 열어봅니다.


## Type:        string

## Default:     "jenkins"

## ServiceRestart: jenkins

#

# Unix user account that runs the Jenkins daemon

# Be careful when you change this, as you need to update

# permissions of $JENKINS_HOME and /var/log/jenkins.

#

JENKINS_USER="jakduk"



위 JENKINS_USER의 값을 jakduk로 수정하고, jenkins를 재시작합니다.

Jenkins에서 원격지(172.30.1.33)으로 암호 입력 없이 ssh 수행

먼저 Jenkins 서버의 jakduk 유저의 공개키와 비밀키를 생성해야 합니다.

먼저 jakduk 유저로 바꾸고, ssh-keygen으로 키를 생성합니다.


# su - jakduk

# ssh-keygen


.ssh 디렉토리가 생성되고 ls 명령어로 확인해보면 키를 확인할 수 있습니다.


[jakduk@localhost .ssh]$ ll

합계 16

-rw-rw-r--. 1 jakduk jakduk  410 2016-07-26 00:30 authorized_keys

-rw-------. 1 jakduk jakduk 1671 2016-06-03 14:04 id_rsa

-rw-r--r--. 1 jakduk jakduk  410 2016-06-03 14:04 id_rsa.pub

-rw-r--r--. 1 jakduk jakduk 1965 2016-07-26 00:17 known_hosts


이제 id_rsa.pub를 원격지(172.30.1.33)에 넣어주면 됩니다.


원격지에 터미널로 접속하여, useradd 명령어로 jakduk, tomcat 유저를 생성합니다. 굳이 2개의 유저를 생성한 이유는 역할을 분리하기 위해서입니다. jakduk 유저는 systemctl 명령어와 rm 명령어만 사용하기 위함이고, tomcat 유저는 tomcat만 담당하기 위해서입니다.


먼저 jakduk 유저로 변경하고 .ssh 디렉토리를 생성합니다.


# mkdir .ssh


.ssh 로 이동하고나서 authorized_keys를 생성합니다. 이 파일 안에 Jenkins의 jakduk유저 공개키를 넣어줍니다. 그리고 나서 권한을 바꿔주세요.


# chmod 600 authorized_keys


이작업을 tomcat 유저도 동일하게 해주면 됩니다.


참고로 원격지 API 서버의 경우 Centos 7를 설치했습니다. jakduk 유저에 약간의 root 권한을 부여해보겠습니다.


[root@api ~]# cat /etc/sudoers.d/jakduk-user 

Cmnd_Alias TOMCAT = /usr/bin/systemctl * tomcat

Cmnd_Alias RM = /bin/rm


jakduk ALL=(ALL) NOPASSWD:TOMCAT,NOPASSWD:RM


jakduk 유저에게 systemctl과 rm 명령어를 비밀번호 입력없이 sudo로 사용할 수 있도록 했습니다.


또한 /etc/sudoers 파일을 수정해야 합니다. requiretty 옵션을 없애야만 Jenkins서버에서 API 서버로 명령을 날릴 수 있습니다.


#Defaults    requiretty


테스트

Jenkins 서버에서 다음의 명령어를 날려보세요. (꼭 한번은 해야 합니다. 왜냐하면 터미널로 최초 접속시 known_hosts에 등록 해야하기 때문입니다.)


[jakduk@localhost .ssh]$ ssh jakduk@172.30.1.33 "sudo systemctl stop tomcat"

[jakduk@localhost .ssh]$ ssh tomcat@172.30.1.33 "ls -al"

합계 28

drwx------. 3 tomcat tomcat 4096  7월 26 23:18 .

drwxr-xr-x. 4 root   root     32  7월 25 23:15 ..

-rw-------. 1 tomcat tomcat  504  7월 26 23:18 .bash_history

-rw-r--r--. 1 tomcat tomcat   18 11월 20  2015 .bash_logout

-rw-r--r--. 1 tomcat tomcat  193 11월 20  2015 .bash_profile

-rw-r--r--. 1 tomcat tomcat  231 11월 20  2015 .bashrc

drwx------. 2 tomcat tomcat   28  7월 26 23:09 .ssh

-rw-------. 1 tomcat tomcat 6416  7월 26 23:18 .viminfo



저작자 표시
신고

'개발' 카테고리의 다른 글

Jenkins로 원격지 API 서버에 war 배포하기  (2) 2016.07.26
Spring Security에 Spring Social 붙이기 1  (0) 2016.05.07
Posted by 노랑머플러

댓글을 달아 주세요

  1. 오호~ 좋은경험 공유 감사!

    난 이번에 gocd 로 했는데.. 나도 정리를 해봐야겟네 ㅎㅎ

    2016.07.28 13:23 신고 [ ADDR : EDIT/ DEL : REPLY ]

개발2016.05.07 21:43

개인적인 오픈소스 프로젝트 개발을 진행하면서 경험한 내용과 느낌을 간단하게 정리해보기로 했습니다. 현재 github(https://github.com/Pyohwan/JakduK)에 소스 코드가 올라가 있습니다. 또한 K리그 작두왕(https://jakduk.com)이 이 소스 코드로 돌아가는 것입니다.


첫번째 시간으로는 Spring Security에 Spring Social을 연동하는 방법에 대해 알아보겠습니다. 다음에 또 시간이 나면 다른 주제를 올리도록 할께요.


Spring Framework의 공식 문서는 아주 잘 구성되어 있기 때문에, Spring Security와 Spring Social에 대한 정보를 쉽게 살펴볼수 있습니다. (물론 영어) 여기서는 별달리 설명은 안하겠습니다.


먼저 사용하는 언어와 버전을 살펴보겠습니다.

  • Java 8
  • Spring Framework 4.2.5.RELEASE
  • Spring Social 1.1.4.RELEASE
  • mongodb


그렇다면 어떤 SNS을 통해 인증을 시도 해볼까요? 해외용과 국내용으로 하나씩 정했습니다. 바로 Facebook과 Daum. 그럼 이제 Provider 라이브러리를 찾아야 하는데요.


Facebook Provider은 글로벌 서비스 답게, Spring Social의 메인 프로젝트로 등록 되어 있습니다. (http://projects.spring.io/spring-social-facebook/)

하지만 Daum은 없어요. Spring Social 홈페이지의 하단에 Community Projects에 Spring Socail Daum이 있긴한데, 이건 2013년도에 어느분이 만들어 둔것으로 현재 사용할 수 없습니다. 왜냐하면 이 소스는 OAuth 1을 사용하는데 현재 Daum은 OAuth 1을 더이상 지원하지 않고, OAuth 2만 사용하기 때문입니다.


Kakao에서 공식적으로 Spring Social을 대응하고 있진 않으나 다행히 github에서 찾을 수 있었습니다.

https://github.com/Hongchae/spring-social-daum


마지막으로 mongodb용 UsersConnectionRepository를 구해야 합니다. Spring Social에서는 SNS계정으로 인증시 DB에 SNS 계정의 정보를 쓰고 읽는 UsersConnectionRepository를 제공합니다. RDB의 경우에는 Spring Social에서 친절하게 JdbcUsersConnectionRepository 라는 구현체를 제공해서 그냥 갖다 쓰면 됩니다. 하지만 mongodb도 Daum과 마찬가지로 Spring Social에 별달리 대응을 안해줍니다.


다행히 github에서 찾긴 했으나 막상 가져다 쓰니 에러가 있고, K리그 작두왕에 바로 적용하기엔 어려워 보여서 fork를 하고 소스코드를 조금 손을 봤습니다.

https://github.com/Pyohwan/spring-social-mongodb


자 준비는 여기까지 하고 소스코드를 빨리 봅시다.


1. 프로젝트에 spring-social-daum과 spring-social-mongodb jar 추가하기


서두에 밝혔듯이 spring-social-daum과 spring-social-mongodb는 github에서 구했기 때문에 maven 원격 저장소에 있지 않습니다. 따라서 수동으로 프로젝트 폴더에 추가해 줍니다.


custom-lib라는 폴더에 담아뒀습니다.


2. Maven dependncy 추가
소스 URL : https://github.com/Pyohwan/JakduK/blob/master/pom.xml


Spring Security는 이미 설정이 되어 있다고 보기 때문에 Spring Social만 추가하겠습니다.



<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-facebook</artifactId>
<version>${spring.social.facebook.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-security</artifactId>
<version>1.1.4.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-daum</artifactId>
<version>0.1.1</version>
<scope>system</scope>
<systemPath>${project.basedir}/custom-lib/spring-social-daum-0.1.1.jar</systemPath>
</dependency>

<dependency>
<groupId>net.exacode.spring.social</groupId>
<artifactId>spring-social-mongodb</artifactId>
<version>1.0.1-SNAPSHOT</version>
<scope>system</scope>
<systemPath>${project.basedir}/custom-lib/spring-social-mongodb-1.0.1-SNAPSHOT.jar</systemPath>
</dependency>


spring-social-facebook의 버전은 2.0.3.RELEASE입니다. spring-social-facebook dependncy를 추가하면 spring-social-core가 자동으로 딸려옵니다. spring-social-security는 Spring Security 설정에 Spring Social을 연동하기 위함입니다. spring-social-daum과 spring-social-mongodb는 프로젝트 폴더 경로를 넣어줍니다.


3. OAuth 접속 정보

properties 파일에 Facebook과 Daum의 OAuth 접속 정보를 기입합니다.

# facebook
facebook.app.id=xxx
facebook.app.secret=xxx

# daum
daum.client.id=xxx
daum.client.secret=xxx



보안상 xxx로 가렸습니다.


4. SocialConfigurer 구현체

소스 URL : https://github.com/Pyohwan/JakduK/blob/master/jakduk-web/src/com/jakduk/configuration/SocialConfig.java


Spring Social의 연결 정보 등을 정의 합니다.

@Configuration
@EnableSocial
public class SocialConfig implements SocialConfigurer {

@Autowired
private MongoTemplate mongoTemplate;

@Override
public void addConnectionFactories(ConnectionFactoryConfigurer connectionFactoryConfigurer, Environment environment) {
connectionFactoryConfigurer.addConnectionFactory(
new FacebookConnectionFactory(environment.getProperty("facebook.app.id")
, environment.getProperty("facebook.app.secret")));
connectionFactoryConfigurer.addConnectionFactory(
new DaumConnectionFactory(environment.getProperty("daum.client.id")
, environment.getProperty("daum.client.secret")));
}

@Override
public UserIdSource getUserIdSource() {
return new AuthenticationNameUserIdSource();
}

@Override
public UsersConnectionRepository getUsersConnectionRepository(
ConnectionFactoryLocator connectionFactoryLocator) {

MongoConnectionDao mongoConnectionDao = new MongoConnectionDao(mongoTemplate,
mongoConnectionConverter(connectionFactoryLocator));

return new GenericUsersConnectionRepository(mongoConnectionDao, connectionFactoryLocator);
}

...

addConnectionFactories 메소드에서 Facebook과 Daum에 해당하는 ConnectionFactory를 생성해 줍니다.

getUsersConnectionRepository 메소드에서 mongodb 용 UsersConnectionRepository를 생성합니다.


5. Spring Securty 설정에 Spring Social 추가

소스 URL : https://github.com/Pyohwan/JakduK/blob/master/jakduk-web/src/com/jakduk/configuration/SecurityConfig.java



@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**");
}

@Override
protected void configure(HttpSecurity http) throws Exception {

http
.csrf().disable() // CSRF 방어 비활성화
//Configures form login
.formLogin()
.loginPage("/login")
.usernameParameter("j_username")
.passwordParameter("j_password")
.successHandler(jakdukSuccessHandler())
.failureHandler(jakdukFailureHandler())
//Configures the logout function
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.logoutSuccessUrl("/logout/success")
.and()
.exceptionHandling().accessDeniedPage("/error/denied")
//Configures url based authorization
.and()
.authorizeRequests()
.antMatchers(
"/check/**",
"/logout",
"/home/**",
"/about/**",
"/auth/**"
).permitAll()
.antMatchers(
"/login", // 로그인
"/auth/*", // SNS 인증
"/signup", // SNS 계정으로 회원 가입
"/user/social", // OAUTH2 콜백
"/user/write", // JakduK 회원 가입
"/user/*/write", // SNS 계정으로 회원 가입
"/password/*" // 비밀번호 찾기
).anonymous()
.antMatchers(
"/user/**"
).authenticated()
.antMatchers(
"/board/*/write",
"/board/*/edit",
"/jakdu/write"
).hasAnyRole("USER_01", "USER_02", "USER_03")
.antMatchers("/admin/**").hasRole("ROOT")
.anyRequest().permitAll()
.and()
.rememberMe()
.key("jakduk_cookie_key_auto_login")
.and()
.apply(getSpringSocialConfigurer())
.and()
.sessionManagement()
.maximumSessions(3).expiredUrl("/error/maxSession");
}

configure 메소드 내용이 좀 긴데, 거의다 Spring Security 정책 설정이고 마지막 즈음에 있는 .apply(getSpringSocialConfigurer())가 Spring Social을 활성화 해주는 코드입니다.


// 로그인 성공 후 특정 URL일 경우 REDIRECT 안 시키는 로직을 추가 해야 한다.
private SpringSocialConfigurer getSpringSocialConfigurer() {
SpringSocialConfigurer configurer = new SpringSocialConfigurer();
return configurer;
}


getSpringSocialConfigurer()은 현재 별것 없습니다. SpringSocialConfigurer의 소스 코드를 들어가보면 Spring Security의 필터중에 AbstractPreAuthenticatedProcessingFilter가 있는데, 이것 앞에 Social 인증 필터를 하나 더 추가한것입니다. 즉 필터체인 과정에서 Spring Social을 하나 더 추가한 셈이지요.


다음에 계속..


저작자 표시
신고

'개발' 카테고리의 다른 글

Jenkins로 원격지 API 서버에 war 배포하기  (2) 2016.07.26
Spring Security에 Spring Social 붙이기 1  (0) 2016.05.07
Posted by 노랑머플러

댓글을 달아 주세요


티스토리 툴바