[GCP] spring boot + jenkins + docker 배포하기 - 1. 젠킨스 서버
GCP 프로젝트 생성부터 jenkins 설치까지는 이전 글에서 다뤘다.
우선, 이전에 만든 jenkins
인스턴스에서 [유사하게 만들기]로 web
인스턴스를 생성하자.
생성 후에는 외부 고정 IP도 할당하자.
(기존에 있던 instance-1
은 ubuntu 였으므로 일단 중지해놓자.)
현재 실행 중인 인스턴스는 아래와 같다.
jenkins
: 젠킨스 서버, Centos7web
: 배포 서버, Centos7
젠킨스 서버에서는 Docker를 사용하지 않고,
배포 서버에서는 Docker를 사용할 것이다.
0. 시나리오
이 글을 참고했다.
- 코드를 작성한다.
- 깃헙에 Push한다.
- Webhook이 걸려 젠킨스에게 Job을 실행하라고 한다.
- 젠킨스가 Build해준 결과를 배포 서버에 넘긴다.
- 배포 서버에서 컨테이너를 실행해 자동 배포한다.
이번 글에서는 젠킨스 인스턴스(jenkins
)에서 진행하고,
다음 글에서는 배포 인스턴스(web
)에서 진행할 것이다.
1. 메타데이터 ssh 접근 설정
키 생성과 등록은 이전 글에서 이미 진행했다.
2. 젠킨스 서버 구동 확인
젠킨스 인스턴스에 SSH 접속한 뒤에 아래 커맨드를 실행한다.
# 젠킨스 상태 확인
$ sudo service jenkins status
# 만약 정지되어 있으면 아래 커맨드로 실행
$ sudo service jenkins start systemctl enable jenkins
http://[젠킨스 서버 외부IP]:9090
으로 웹 브라우저에 접속한다.
3. Global Tool Configuration
이 글을 참고했다.
[Dashboard] > [Jenkins 관리] > [Global Tool Configuration]
만약 이런 에러가 발생한다면?
시도 1)
$ vi ~/.bash_profile
# 아래 코드 두 줄 추가
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-11.0.16.1.1-1.el7_9.x86_64
export PATH=$JAVA_HOME/bin:$PATH
# 변경사항 적용
$ source ~/.bash_profile
하지만 문제가 해결되지 않아 다시 원상복구 했다.
시도2)
java 다시 설치
# 설치할 수 있는 버전 확인
$ yum list java*jdk-devel
# 설치
$ sudo yum install java-11-openjdk-devel.x86_64
# 기본 자바 선택
$ sudo update-alternatives --config java
아래 코드로 java가 설치된 디렉토리를 확인해보자.
$ cd /usr/lib/jvm
해결되었다!
이제 바로 아래 있는 Git 설정도 해주자.
이제 Save 버튼을 누른다.
4. Publish Over SSH
이 글을 참고했다.
1) 설치
위 플러그인을 설치한다.
2) 공개키/개인키 확인
설정하기 전에, 공개키와 개인키를 확인해보자.
# 공개키 확인
$ cat .ssh/id_rsa.pub
# 개인키 확인
$ cat .ssh/id_rsa # 복사해두자!
이때 확인한 개인키를 복사해두자!
3) 설정
이제 Publish Over SSH 설정을 해보자.
[Jenkins 관리 > 시스템 설정]에 들어간다.
맨 아래 보면 Publish Over SSH 관련 설정이 있다.
아래와 같이 설정해주었다.
각각의 입력은 아래의 과정을 거치면 된다.
- 아래의 그림에서 Name은 가급적이면 GCP에서 인스턴스의 이름과 동일하게 지어주는것이 좋다. (나는 배포 인스턴스의 이름을 적었다.)
- Hostname은 IP를 적어주면 되는데 워커 인스턴스가 젠킨스와 같은 네트워크상에 있으면 내부IP를 사용해도 된다. (나는 내부 IP를 사용했다.)
(같은 Region에 내부 IP가 비슷하다면 같은 네트워크)- Username은 인스턴스와 연결된 Username에 대한 정보이다. GCP에서 로그인할때 사용하는 계정 ID를 적어주면 된다.
- Remote Directory는 홈 디렉토리를 사용했다. 홈 디렉토리 정보는 홈 경로(~)에서 스크립트 명령어 ‘pwd’를 입력하면 확인할 수 있다.
- Test Configuration을 클릭하여 정상적으로 설정이 되었는지 확인할 수 있다. 잘 되었다면 왼쪽 하단에 ‘Success’ 라는 출력을 확인할 수 있다.
- 모든 입력이 끝난 후에는 저장을 해주면 된다
5. Webhooks
6. Personal access tokens
🚨 생성된 secret은 다시는 확인할 수 없으니 복사 후 잘 보관해두자!!????
젠킨스에서 등록 (Dashboard>Jenkins 관리>Configure System)
이때, Credentials 는 처음에는 없을거고, Add를 눌러 아래와 같이 생성한다.
Secret에는 방금 생성한 webhook secret을 입력한다.
7. Jenkins Job 생성 (새로운 Item 생성)
CI / CD를 위한 잡을 만들어보자. 젠킨스 파일(선언적 파이프라인)로 만들어볼 것이다.
(나는 job-1
이라는 이름으로 생성했다.)
깃헙에 PUSH 되면 자동으로 젠킨스가 일을 해야하므로 Hook만 걸어놓자.
스크립트는 아래와 같다. (일단 뼈대만 만들어놓자.)
pipeline {
agent any
stages {
stage('Prepare') {
steps {
echo '=== Prepare ==='
}
}
stage('Build') {
steps {
echo '=== Build ==='
}
}
stage('Deploy') {
steps {
echo '=== Deploy ==='
}
}
}
}
1) git clone
이 글을 참고했다.
이제 git clone을 진행하자.
Pipeline Syntax를 클릭하자.
Sample Step에서 git: Git
을 선택하자.
🚨 주의!
레포지토리 주소 붙여넣을 시 .git 주소가 아닌, 주소창에서 레포지토리 URL 주소를 붙여넣는다.
Credentials는 깃허브 계정과 엑세스 토큰으로 새로 생성해 사용했다.
이 때, 엑세스 토큰은 직전에 생성한게 아니라 원래 사용하던 엑세스 토큰을 사용했다.
Kind는 Username with password로 사용한다 .
Username은 Github 계정명 Password는 Access Token 값을 사용한다.
Credentials까지 모두 추가되었다면 Generate Pipeline Script를 이용해서 생성된 Script를 복사해 Prepare
에 붙여넣을 것이다.
2) Pipeline Script 작성
Prepare
에는 방금 복사한 스크립트를 붙여넣을 것이다.
Build
부분에는, 빌드하기 위해 gradlew를 쓸 것이기 때문에 쉘 명령어 2줄만 추가해주자.
그럼 지금까지 작성한 파이프라인은 다음과 같다. (아직 끝나지 않았다!)
pipeline {
agent any
stages {
stage('Prepare') {
steps {
echo '=== Prepare ==='
git credentialsId: 'access-token', url: 'https://github.com/minju412/docker-test'
}
}
stage('Build') {
steps {
echo '=== Build ==='
sh '''
chmod +x gradlew
./gradlew clean build -x test
'''
}
}
stage('Deploy') {
steps {
echo '=== Deploy ==='
}
}
}
}
이번에는 Deploy
를 채울 것이다.
job-1
에서 Pipeline Syntax를 클릭하자.
Sample Step에서 sshPublisher: Send build artifacts over SSH
를 선택하자.
- 소스파일의 위치, 빌드한 결과는 build/libs/—.jar에 위치한다.
- 소스파일에서 원본 파일의 디렉토리를 어디까지 포함할 것인지에 대한 설정 (build/libs는 제거된다.)
- remote directory 기준으로 배포될 경로를 적는다. (결과 : /home/swanjj11/deploy)
- 전송을 마치고 실행할 Shell 명령어를 적는다. 이따 ‘init.sh’이라는 파일을 실행할 것이다.
Generate Pipeline Script
를 클릭한 뒤, 생성된 Snipet을 ‘Deploy’에 복붙하자.
최종 완성된 파이프라인 스크립트는 아래와 같다.
Dashboard > job-1 > 구성
에서 Pipeline Script를 작성하는 부분에 아래 코드를 붙여넣자.
pipeline {
agent any
stages {
stage('Prepare') {
steps {
echo '=== Prepare ==='
git credentialsId: 'access-token', url: 'https://github.com/minju412/docker-test'
}
}
stage('Build') {
steps {
echo '=== Build ==='
sh '''
chmod +x gradlew
./gradlew clean build
'''
}
}
stage('Deploy') {
steps {
echo '=== Deploy ==='
sshPublisher(publishers: [sshPublisherDesc(configName: 'web', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: 'sh init.sh', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'deploy/', remoteDirectorySDF: false, removePrefix: 'build/libs', sourceFiles: 'build/libs/*.jar')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: true)
])
} #마지막에 verbose를 true로 주면 트러블 슈팅하기 용이하다
}
}
}
이제 젠킨스에서 할 일은 끝났다.
다음 글에서 진행할 내용은 아래와 같다.
- 배포 서버에 도커파일 생성
- 배포 서버에 init.sh 작성
- 깃헙에 push 해보기
- 자동 배포 됐는지 확인
========================
8. 민감 정보 추가하기
이 글을 참고했다.
1) 프로젝트 application.yml
변경
민감 정보를 제거한 뒤에 깃허브에 업로드 하자.
(민감 정보는 따로 기록해두었다가 이후에 젠킨스에서 string parameter로 등록할 것이다.)
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://${MYSQL_IP}:${MYSQL_PORT}/${MYSQL_NAME}?${MYSQL_OPT}
username: ${MYSQL_USERNAME}
password: ${MYSQL_PASSWORD}
jpa:
hibernate:
ddl-auto: update
properties:
hibernate:
globally_quoted_identifiers: true
format_sql: true
dialect: org.hibernate.dialect.MySQL8Dialect
cache:
type: redis
redis:
host: ${REDIS_IP}
port: ${REDIS_PORT}
devtools:
livereload:
enabled: false
mvc:
pathmatch:
matching-strategy: ant_path_matcher
# log-resolved-exception: false
# ==== 페이징 할 때 기본값, 20개씩 조회 ==== #
data:
web:
pageable:
default-page-size: 20
# ==== S3 파일 업로드 용량 설정 ==== #
servlet:
multipart:
max-file-size: 3MB #업로드 파일 크기 제한
max-request-size: 5MB #업로드 파일 크기 총량 제한
# ==== JWT ==== #
jwt:
token:
jwt-secret-key: ${JWT_SECRET}
# === AWS S3 === #
cloud:
aws:
credentials:
access-key: ${S3_ACCESS_KEY}
secret-key: ${S3_SECRET_KEY}
s3:
bucket: posts-image-bucket
region:
static: ap-northeast-2
stack:
auto: false
# === Firebase === #
fcm:
api-url: ${FIREBASE_URL}
logging.level:
org.hibernate.SQL: debug
2) 젠킨스에서 String Parameter 선언
이 글을 참고했다.
중요🌟
만약에 String Parameter의 value에/
기호가 포함되어 있다면,
/
기호 앞에는 escape 문자(\
)를 넣어주어야 한다!
만약abc/def
라는 값이면abc\/def
가 되는 것이고,
만약abc//def
라면abc\/\/def
가 되는 것이다.
3) sed 스크립트 추가
Build 이전에 넣어준다.
$
문자 앞에는 \
을 넣어주어야 한다.
pipeline {
agent any
stages {
stage('Prepare') {
steps {
echo '=== Prepare ==='
git credentialsId: 'access-token', url: 'https://github.com/minju412/docker-test'
}
}
stage('Set String Parameters') {
steps {
echo '=== Set String Parameter ==='
echo '01. REDIS :: IP, Port Setting'
sh '''
sed -i "s/\${REDIS_IP}/\${REDIS_IP}/" "\${WORKSPACE}/src/main/resources/application.yml"
sed -i "s/\${REDIS_PORT}/\${REDIS_PORT}/" "\${WORKSPACE}/src/main/resources/application.yml"
'''
echo '02. Database :: UserName, Password Setting'
sh '''
sed -i "s/\${MYSQL_USERNAME}/\${MYSQL_USERNAME}/" "\${WORKSPACE}/src/main/resources/application.yml"
sed -i "s/\${MYSQL_PASSWORD}/\${MYSQL_PASSWORD}/" "\${WORKSPACE}/src/main/resources/application.yml"
'''
echo '03. Database :: IP, Port, Name, Opt Setting'
sh '''
sed -i "s/\${MYSQL_IP}/\${MYSQL_IP}/" "\${WORKSPACE}/src/main/resources/application.yml"
sed -i "s/\${MYSQL_PORT}/\${MYSQL_PORT}/" "\${WORKSPACE}/src/main/resources/application.yml"
sed -i "s/\${MYSQL_NAME}/\${MYSQL_NAME}/" "\${WORKSPACE}/src/main/resources/application.yml"
sed -i "s/\${MYSQL_OPT}/\${MYSQL_OPT}/" "\${WORKSPACE}/src/main/resources/application.yml"
'''
echo '04. Jwt :: Secret Setting'
sh '''
sed -i "s/\${JWT_SECRET}/\${JWT_SECRET}/" "\${WORKSPACE}/src/main/resources/application.yml"
'''
echo '05. AWS S3 :: Secret Setting'
sh '''
sed -i "s/\${S3_ACCESS_KEY}/\${S3_ACCESS_KEY}/" "\${WORKSPACE}/src/main/resources/application.yml"
sed -i "s/\${S3_SECRET_KEY}/\${S3_SECRET_KEY}/" "\${WORKSPACE}/src/main/resources/application.yml"
'''
echo '06. Firebase :: Url Setting'
sh '''
sed -i "s/\${FIREBASE_URL}/\${FIREBASE_URL}/" "\${WORKSPACE}/src/main/resources/application.yml"
'''
}
}
stage('Build') {
steps {
echo '=== Build ==='
sh '''
chmod +x gradlew
./gradlew clean build --exclude-task test
ls -al ./build
'''
}
}
stage('Deploy') {
steps {
echo '=== Deploy ==='
sshPublisher(publishers: [sshPublisherDesc(configName: 'web', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: 'sh init.sh', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'deploy/', remoteDirectorySDF: false, removePrefix: 'build/libs', sourceFiles: 'build/libs/*.jar')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: true)
])
}
}
}
}
추가한 절차를 통해서 application.yml
의 정보가 바뀐다.
Deploy
에서 마지막에 verbose를 true로 주면 트러블 슈팅하기 용이하다
4) 파라미터와 함께 빌드
참고
이 방법 말고 조금 더 효율적으로 관리하기 위해서 Spring Cloud Config Server를 이용할 수도 있는데,
궁금하다면 이 글을 참고하자.
💛 개인 공부 기록용 블로그입니다. 👻