- RESTful API 기반 Web application
- Spring Boot 2.0.4
- Gradle
- Mustache
- lombok 1.18.2
- PostgreSQL
- H2
- Eclipse
- Github
- Travis CI
- AWS RDS (PostreSQL)
- AWS EC2 (Linux)
- AWS S3
- AWS CodeDeploy
- ...
| 일정 | 주제 | 비고 |
|---|---|---|
| 9/4 화 | 0. 프로젝트 시작 | |
| 1. 개발환경 세팅 | Spring boot 프로젝트 구조 학습 | |
| 2. DB 모델링 | 제3정규화 | |
| 3. DB 생성 및 연동 | AWS RDS PostgreSQL 활용 | |
| 4. 회원가입/로그인 구현 | JWT 활용 | |
| 5. 상품목록 게시판 구현 | 다른 게시판은 추후에 추가 | |
| (6. 상품구매 구현) | 결제기능은 가상 처리 | |
| (7. 구매이력확인 구현) | ||
| 8. 화면 구현 | jQuery,Bootstrap 활용 | |
| 9. 테스트 및 배포자동화 구축 | AWS CodeDeploy 및 CI도구 활용 | |
| ~9/14 금 | 10. 배포 및 피드백 | NginX 활용 |
- Spring boot starter 사용
- 빌드 도구는 Gradle 사용
- 템플릿 엔진은 Mustache 사용
- 테스트용 메모리DB H2 사용
- 데이터베이스는 AWS RDS PostgreSQL 사용
- 서버는 AWS EC2 Linux 사용
- CI 도구는 Travis CI 사용
- ...
- Lucid Chart 사용
- 제3정규화
- 회원 목록
- 회원 정보 수정
- 회원 탈퇴(추후 구현)
- LocalDateTime 사용
...
- gradle 빌드 속도 높이기
${HOME}/.gradle/gradle.properties생성해서org.gradle.daemon=true입력
Github + EC2 + Travis CI + AWS S3 + AWS CodeDeploy
이동욱님 블로그에 상세한 설명이 있다.
- 프로젝트를 생성(수정)하여 Github에 push하면, Travis CI가 자동으로 테스트 및 빌드하여 S3에 배포 파일을 업로드하고, CodeDeploy가 배포 파일을 S3에서 받아와서 EC2에 올리면, EC2에 작성한 쉘스크립트에 의해 배포 파일이 실행되어 배포가 완료된다.
- Travis CI
- 프로젝트 테스트 및 빌드 자동화
- 빌드 완료 시, S3에 빌드된 배포 파일 업로드
- S3에 업로드 완료 시, 배포 파일을 EC2에 올리도록 CodeDeploy 실행
- AWS S3
- Travis CI가 빌드한 배포 파일의 저장소
- AWS Code Deploy
- S3에서 가져온 배포 파일을 EC2에 올림(배포)
- EC2에 올라간 파일이 실행되도록 하는 쉘스크립트 파일을 우회하여 실행
- EC2 생성
- AWS RDS PostgreSQL 생성
- EC2에 Git 설치 및 프로젝트 Clone
- Travis CI 연동 (테스트&빌드 자동화)
- AWS CodeDeploy 연동 (배포 자동화)
- Travis CI와 AWS S3 연동
- Travis CI & S3 & AWS CodeDeploy 연동
더 잘 이해하기 위해 구어체로 써보는 테스트&빌드&배포 자동화 과정
- 테스트와 빌드를 자동화하기 위해 Travis CI에서 프로젝트 저장소를 연동시키고,
- 프로젝트에
.Travis.yml파일을 생성해서 로컬에서 push할때 Travis CI가 테스트와 빌드하도록 설정한다. - 배포 자동화를 위해 AWS CodeDeploy와 연동해야 하는데, CodeDeploy를 쓰려면 Travis CI가 사용할 수 있는 사용자계정이 필요하다.
- AWS IAM에서 Travis CI가 AWS CodeDeploy용으로 사용할 사용자 계정을 생성한다.
- 사용자 생성시 만들어진 access key와 secret key를 다운로드하여 보관한다.
- 생성한 사용자 계정이 S3와 CodeDeploy에 접근할 수 있도록 FullAccess 정책들을 선택하여 연결한다.
- 빌드된 배포파일(.jar)을 보관할 S3 버킷을 생성한다.
- IAM에서 생성한 사용자에 EC2와 CodeDeploy을 위한 역할 2가지를 추가한다.(EC2RoleForCodeDeploy, CodeDeployRole)
- EC2 인스턴스에 IAM 역할(EC2RoleForCodeDeploy)을 연결하고, CodeDeploy Agent를 설치한다.
- CodeDeploy Agent보다 AWS CLI를 우선 설치하여 AWS를 커맨드로 다룰 수 있도록 한다.
- EC2가 CodeDeploy 이벤트를 수신할 수 있도록 CodeDeploy Agent를 설치해야한다.
- AWS CodeDeploy CLI를 설치하고, 이 때 생성되는 ./install 파일을 이용해 CodeDeploy Agent를 설치하고 실행한다.
- EC2 인스턴스가 부팅되면 자동으로 CodeDeploy Agent가 실행되도록
/etc/init.d에 쉘스크립트 파일을 생성한다. - CodeDeploy에는 저장 기능이 없으므로 Travis CI가 빌드한 결과물을 S3에 보관하고 CodeDeploy가 가져가도록
.travis.yml파일에 설정한다. - Github에 AWS access key와 secret key가 노출하지 않기 위해 Travis CI에서 키-값 등록
- AWS CodeDeploy 콘솔에서 어플리케이션을 생성하는데, CodeDeploy가 EC2에 접근 가능하게 하는 CodeDeployRole을 ARN으로 선택한다.
- Travis CI에서 빌드가 끝나면 S3에 zip파일이 전송된다. 이 파일을 받아올 디렉토리를 생성한다.
- 프로젝트에 CodeDeploy 설정용
appspec.yml파일을 생성하여 zip파일을 받아오기 위해 생성한 디렉토리를 입력한다. - CodeDeploy는
appspec.yml파일을 통해서 어떤 파일들을 어느 위치에 배포하고, 이후에 어떤 스크립트를 실행할 것인지 관리한다. - Travis CI가 CodeDeploy도 실행시키도록
.travis.yml파일에 설정 추가한다. - 배포 파일(.jar)을 실행해야 실제로 배포되는 것이므로, 배포 파일을 실행시키는
deploy.sh파일을 EC2에 생성한다. - CodeDeploy가 EC2에 배포를 마치면
deploy.sh파일을 실행하도록appspec.yml에 설정한다. deploy.sh파일은 프로젝트 내부에 있지 않기 때문에 CodeDeploy가 바로 실행할 수 없다. 그러므로 우회가 필요하다.deploy.sh가 실행되도록 하는execute-deploy.sh파일을 프로젝트 내부에 생성한다.
- Amazon Linux 2018 선택
- harusketch pem 키 생성
- Elastic IP 적용 (적용안하면 인스턴스 재시작 될 때마다 IP 바뀜)
- 보안그룹 설정(내 작업공간 IP, HTTP, HTTPS)
- EC2 터미널 접속
- SSH 접속 쉽게하기
- 키파일(.pem)을
~./ssh/로 복사$ cp harusketch.pem ~/.ssh/ ~/.ssh/에서 키 권한 변경$ chmod 600 ./harusketch.pem~/.ssh/디렉토리에config파일 생성$ nano config- config 파일에 아래와 같이 설정
### Haru Sketch Host haru(원하는 이름) HostName Elastic IP User ec2-user (ubuntu 사용시 ubuntu. 그 외에는 ec2-user) IdentityFile ~/.ssh/harusketch.pem ssh haru로 EC2 접속
- 키파일(.pem)을
- SSH 접속 쉽게하기
- EC2 인스턴스의 보안그룹 ID를 IP에 입력
- 작업환경 IP 입력
- RDS 보안그룹을 방금 생성한 보안그룹으로 변경
- DBeaver 실행하여 연결 추가
- Host 주소에 RDS 인스턴스의 endpoint 입력
- Database에 인스턴스 생성할때 지정한 DB이름 입력
- 생성
- Client-encoding(MySQL에서는 Character-set) 값이 UTF8이 default로 지정되어 있어서 default parameter group 사용 가능
- 현재 EC2의 자바 기본버전이 Java7 이므로 Java8로 버전업
$ sudo yum install -y java-1.8.0-openjdk-devel.x86_64$ sudo /usr/sbin/alternatives --config java
- 사용하지 않는 Java7 삭제
$ sudo yum remove java-1.7.0-openjdk
$ sudo yum install git
- 프로젝트를 저장할 디렉토리 생성
$ mkdir app$ mkdir app/git
- 생성한 디렉토리에서 프로젝트 clone
$ git clone -b develop --single-branch [프로젝트 저장소 ssh주소]
- 프로젝트 잘 받아졌는지 테스트
$ ./gradlew testgradlew파일 : EC2에 Gradle 설치하지 않았거나 Gradle 버전이 달라도 해당 프로젝트에 한해서 Gradle을 쓸 수 있도록 지원하는 Wrapper 파일
$~app/git에deploy.sh파일 생성#!/bin/bash REPOSITORY=/home/ec2-user/app/git cd $REPOSITORY/Restful-WebApp echo "> Git Pull" git pull echo "> 프로젝트 Build 시작" ./gradlew build echo "> Build 파일 복사" cp ./build/libs/*.jar $REPOSITORY/ echo "> 현재 구동중인 애플리케이션 pid 확인" CURRENT_PID=$(pgrep -f harusketch) echo "$CURRENT_PID" if [ -z $CURRENT_PID ]; then echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다." else echo "> kill -2 $CURRENT_PID" kill -9 $CURRENT_PID sleep 5 fi echo "> 새 어플리케이션 배포" JAR_NAME=$(ls $REPOSITORY/ |grep 'harusketch' | tail -n 1) echo "> 새 배포 버전의 이름은? ===> $JAR_NAME" nohup java -jar $REPOSITORY/$JAR_NAME &
deploy.sh에 실행권한 부여$ chmod 755 ./deploy.sh
$ ./deploy.shps -ef|grep harusketch로 프로세스 실행 확인
- EC2 인바운드 규칙 편집
- 8080 포트 오픈
- EC2 인스턴스의 퍼블릭 DNS에 :8080 붙여서 접속 확인
- Travis CI에 github 아이디로 로그인
- 프로젝트 저장소의 상태 활성화
- 프로젝트에
.travis.yml파일 생성language: java jdk: - openjdk8 branches: only: - develop ## 오직 develop 브랜치에 push 될 때만 수행 # Travis CI 서버의 Home cache: ## Gradle을 통해 의존성을 받게 되면 이를 해당 디렉토리에 캐시하여, 같은 의존성은 다음 배포때부터 다시 받지 않도록 설정 directories: - '$HOME/.m2/repository' - '$HOME/.gradle' script: "./gradlew clean build" ## develop 브랜치에 Push 되었을때 수행하는 명령어. 프로젝트 내부에 둔 gradlew를 통해 clean & build 수행 # CI 실행 완료시 메일로 알람 (slack도 가능) notifications: email: recipients: - ryanhan@cloudcash.kr
- Commit & Push 후 Travis CI 저장소 페이지 확인
여기까지가 테스트와 빌드 자동화.
- Travis CI가 사용 할 수 있도록 AWS Code Deploy용 계정 추가
- IAM 콘솔에서 사용자 추가
- 사용자 이름 입력 (harusketch-deploy)
- 액세스 유형: 프로그래밍 방식 액세스 체크
- 정책 선택
- 기존 정책 직접 연결
- S3로 검색하여
AmazonS3FullAccess선택 - deploy로 검색하여
AWSCodeDeployFullAccess선택
- 사용자 생성
- 액세스키와 비밀키 .csv 다운로드
- 빌드 된 jar 파일을 보관할 S3 버킷 생성
- 버킷 이름 입력 (harusketch-deploy)
- 다른 옵션 없이 생성
- 나 대신 access key & secret key를 사용해 원하는 기능을 진행하게 할 AWS Role 생성
- IAM - 역할 - 역할만들기
- 사용사례 선택 EC2
- 권한 정책 연결
- EC2RoleForAWSCodeDeploy 로 검색하여 선택
- 역할 이름 입력 (harusketch-EC2CodeDeployRole) 및 생성
- 사용사례 선택 CodeDeploy
- 권한 정책 연결 (AWSCodeDeployRole)
- 역할 이름 입력 (harusketch-CodeDeployRole) 및 생성
- EC2에 CodeDeploy Role 추가
- EC2 인스턴스 설정 - IAM 역할 연결/바꾸기
harusketch-EC2CodeDeployRole선택
- EC2에 CodeDeploy Agent 설치 (CodeDeploy에서 실행하는 이벤트를 EC2에서 받아서 처리할 수 있도록)
- EC2 ssh 접속
- AWS CLI 설치 (AWS를 커맨드로 다루기 위해)
$ sudo yum -y update$ sudo yum install -y aws-cli
- AWS CLI 기본 설정
/home/ec2-user/에서$ sudo aws configure- 액세스키와 비밀키 입력 (사용자 생성할 때 다운받은 .csv 파일)
- region name 입력 (ap-northeast-2)
- output format 입력 (json)
- AWS CodeDeploy CLI 설치
/home/ec2-user/에서$ aws s3 cp s3://aws-codedeploy-ap-northeast-2/latest/install . --region ap-northeast-2- 다운로드가 끝나면 생기는 ./install 파일 실행권한 부여
$ chmod +x ./install
- AWS CodeDeploy Agent 설치 및 실행
- 생성한 파일(./install)을 이용해 설치
$ sudo ./install auto - 설치가 완료되면 Agent 실행 확인
$ sudo service codedeploy-agent status
- 생성한 파일(./install)을 이용해 설치
- CodeDeploy 실행 자동화
- EC2 인스턴스가 부팅되면 자동으로 AWS CodeDeploy Agent가 실행되도록
/etc/init.d에 쉘스크립트 파일 생성$ sudo nano /etc/init.d/codedeploy-startup.sh#!/bin/bash echo 'Starting codedeploy-agent' sudo service codedeploy-agent start
CodeDeploy는 저장 기능이 없다. 따라서 Travis CI가 빌드한 결과물을 받아서 CodeDeploy가 가져갈 수 있도록 보관할 수 있는 공간이 필요한데, 보통 S3 쓴다.
# Travis CI & S3 연동
before_deploy: ## 매번 Travis CI에서 파일을 하나하나 복사하면 시간이 많이 걸리므로 프로젝트 폴더 채로 압축해서 S3에 전달하도록 설정
- zip -r harusketch * ## 현재 위치의 모든 파일을 `harusketch` 이름으로 압축
- mkdir -p deploy ## deploy 라는 디렉토리를 Travis CI가 실행중인 위치에서 생성
- mv harusketch.zip deploy/harusketch-zip
deploy:
- provider: s3
access_key_id: $AWS_ACCESS_KEY ## Travis repo settings에 설정된 값
secret_access_key: $AWS_SECRET_KEY ## Travis repo settings에 설정된 값
bucket: harusketch-deploy
region: ap-northeast-2
skip_cleanup: true
acl: public_read
local_dir: deploy ## before_deploy에서 생성한 디렉토리
wait-until-deployed: true
on:
repo: integerous/Restful-WebApp
branch: develop- Travis CI에서 키 값 등록 (Github에 AWS access key와 secret key를 노출하지 않기 위해)
- Travis CI - settings - Environment Variables
AWS_ACCESS_KEY와AWS_SECRET_KEY를 변수로 하는 .csv의 키 값들 등록.travis.yml파일 전체 코드
language: java
jdk:
- openjdk8
branches:
only:
- develop ## 오직 develop 브랜치에 push 될 때만 수행
# Travis CI 서버의 Home
cache: ## Gradle을 통해 의존성을 받게 되면 이를 해당 디렉토리에 캐시하여, 같은 의존성은 다음 배포때부터 다시 받지 않도록 설정
directories:
- '$HOME/.m2/repository'
- '$HOME/.gradle'
script: "./gradlew clean build" ## develop 브랜치에 Push 되었을때 수행하는 명령어. 프로젝트 내부에 둔 gradlew를 통해 clean & build 수행
# Travis CI & S3 연동
before_deploy: ## 매번 Travis CI에서 파일을 하나하나 복사하면 시간이 많이 걸리므로 프로젝트 폴더 채로 압축해서 S3에 전달하도록 설정
- zip -r harusketch * ## 현재 위치의 모든 파일을 `harusketch` 이름으로 압축
- mkdir -p deploy ## deploy 라는 디렉토리를 Travis CI가 실행중인 위치에서 생성
- mv harusketch.zip deploy/harusketch-zip
deploy:
- provider: s3
access_key_id: $AWS_ACCESS_KEY ## Travis repo settings에 설정된 값
secret_access_key: $AWS_SECRET_KEY ## Travis repo settings에 설정된 값
bucket: harusketch-deploy
region: ap-northeast-2
skip_cleanup: true
acl: public_read
local_dir: deploy ## before_deploy에서 생성한 디렉토리
wait-until-deployed: true
on:
repo: integerous/Restful-WebApp
branch: develop
# CI 실행 완료시 메일로 알람
notifications:
email:
recipients:
- ryanhan@cloudcash.kr- 어플리케이션 이름 입력 (harusketch)
- 배포 그룹 이름 입력 (harusketch-group)
- 현재 위치 배포
- ARN 으로 기존에 생성한
CodeDeployRole선택 (EC2CodeDeployRole아님) - 어플리케이션 생성
$ mkdir /home/ec2-user/app/travis$ mkdir /home/ec2-user/app/travis/build- Travis CI의 빌드가 끝나면 S3에 zip파일이 전송되고, 이 zip파일은
/home/ec2-user/app/travis/build로 복사되어 압축을 푼다.
.travis.yml과 같은 위치에appspec.yml파일을 아래와 같이 생성version: 0.0 ## CodeDeploy 버전. 프로젝트 버전이 아니기 때문에 0.0 외에 다른 버전을 사용하면 오류 발생 os: linux files: - source: / ## S3 버킷에서 복사할 파일의 위치를 나타냄 destination: /home/ec2-user/app/travis/build/ ## zip파일을 복사해 압축을 풀 위치를 지정
- Travis CI가 CodeDeploy도 실행시키도록
.travis.yml파일에 설정 추가한 최종 파일language: java jdk: - openjdk8 branches: only: - develop ## 오직 develop 브랜치에 push 될 때만 수행 # Travis CI 서버의 Home cache: ## Gradle을 통해 의존성을 받게 되면 이를 해당 디렉토리에 캐시하여, 같은 의존성은 다음 배포때부터 다시 받지 않도록 설정 directories: - '$HOME/.m2/repository' - '$HOME/.gradle' script: "./gradlew clean build" ## develop 브랜치에 Push 되었을때 수행하는 명령어. 프로젝트 내부에 둔 gradlew를 통해 clean & build 수행 # Travis CI & S3 연동 before_deploy: ## 매번 Travis CI에서 파일을 하나하나 복사하면 시간이 많이 걸리므로 프로젝트 폴더 채로 압축해서 S3에 전달하도록 설정 - zip -r harusketch * ## 현재 위치의 모든 파일을 `harusketch` 이름으로 압축 - mkdir -p deploy ## deploy 라는 디렉토리를 Travis CI가 실행중인 위치에서 생성 - mv harusketch.zip deploy/harusketch-zip deploy: - provider: s3 access_key_id: $AWS_ACCESS_KEY ## Travis repo settings에 설정된 값 secret_access_key: $AWS_SECRET_KEY ## Travis repo settings에 설정된 값 bucket: harusketch-deploy ## S3 버킷 region: ap-northeast-2 skip_cleanup: true acl: public_read local_dir: deploy ## before_deploy에서 생성한 디렉토리 wait-until-deployed: true on: repo: Integerous/Restful-WebApp branch: develop - provider: codedeploy access_key_id: $AWS_ACCESS_KEY ## Travis repo settings에 설정된 값 secret_access_key: $AWS_SECRET_KEY ## Travis repo settings에 설정된 값 bucket: harusketch-deploy ## S3 버킷 key: harusketch.zip ## S3 버킷에 저장된 harusketch.zip 파일을 EC2로 배포 bundle_type: zip application: harusketch ## AWS 콘솔로 등록한 CodeDeploy 어플리케이션 deployment_group: harusketch-group ## AWS 콘솔로 등록한 CodeDeploy 배포 그룹 region: ap-northeast-2 wait-until-deployed: true on: repo: Integerous/Restful-WebApp branch: develop # CI 실행 완료시 메일로 알람 notifications: email: recipients: - ryanhan@cloudcash.kr
- application.jar 파일을 실행시키는 것까지가 배포이므로
- EC2에 AWS CodeDeploy로 받은 파일을 실행시키는 배포 스크립트 생성
- jar 파일들을 모아둘 디렉토리 생성
$ mkdir /home/ec2-user/app/travis/jar - jar 디렉토리에 옮겨진 application.jar를 실행시킬
deploy.sh파일 생성nano /home/ec2-user/app/travis/deploy.sh
#!/bin/bash REPOSITORY=/home/ec2-user/app/travis echo "> 현재 구동중인 어플리케이션 PID 확인" CURRENT_PID=$(pgrep -f harusketch) echo "$CURRENT_PID" if [ -z $CURRENT_PID ]; then echo "> 현재 구동중인 어플리케이션이 없으므로 종료하지 않습니다." else echo "> kill -15 $CURRENT_PID" kill -15 $CURRENT_PID sleep 3 fi echo "> 새로운 어플리케이션 배포!" echo "> Build 파일 복사" cp $REPOSITORY/build/build/libs/*.jar $REPOSITORY/jar/ JAR_NAME=$(ls -tr $REPOSITORY/jar/ |grep 'harusketch' | tail -n 1) echo "> 새로 배포된 JAR 이름은?? ===> $JAR_NAME" nohup java -jar $REPOSITORY/jar/$JAR_NAME &
- jar 파일들을 모아둘 디렉토리 생성
appspec.yml전체 코드version: 0.0 ## CodeDeploy 버전. 프로젝트 버전이 아니기 때문에 0.0 외에 다른 버전을 사용하면 오류 발생 os: linux files: - source: / ## S3 버킷에서 복사할 파일의 위치를 나타냄 destination: /home/ec2-user/app/travis/build/ ## zip파일을 복사해 압축을 풀 위치를 지정 hooks: AfterInstall: ## 배포가 끝나면 아래 명령어를 실행 - location: execute-deploy.sh timeout: 180 ## CodeDeploy에서 바로 deploy.sh 를 실행시킬 수 없으므로, ## deploy.sh 를 실행하는 execute-deploy.sh 파일을 실행하여 우회한다.
- CodeDeploy에서 바로 deploy.sh 를 실생시킬 수 없으므로, deploy.sh 를 실행하는 execute-deploy.sh 파일을 실행하여 우회한다.
execute-deploy.sh#!/bin/bash /home/ec2-user/app/travis/deploy.sh > /dev/null 2> /dev/null < /dev/null &
현재까지의 상황
- 테스트, 빌드, 배포 모두 자동화
- 작업이 끝난 내용을
develop브랜치에 push하면 자동으로 EC2에 배포 - 문제점
- 배포하는 동안 스프링부트 프로젝트는 종료상태가 되어 서비스를 이용할 수 없다는 점.
- Nginx를 사용해서 무중단 배포해야 함
- EC2에 접속해서 Nginx 설치
$ sudo yum install nginx - Nginx 실행
$ sudo service nginx start - Nginx 실행 확인 1
$ ps -ef | grep nginx - Nginx 실행 확인 2
EC2 인스턴스의 퍼블릭 DNS 주소로 브라우져 접속하여 Welcome to nginx 화면 확인
- Nginx가 현재 실행중인 프로젝트를 연결하도록 설정
- Nginx 설정 파일 편집
sudo nano /etc/nginx/nginx.conf- 파일 내용 중
location / { 이 위치에 } -
proxy_pass http://localhost:8080; //요청이 오면 http://localhost:8080 으로 전달 # 아래 설정들은 실제 요청 데이터를 header의 각 항목에 할당 proxy_set_header X-Real-IP $remote_addr; //Request Header의 X-Real-IP에 요청자의 IP를 저장 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host;
- Nginx 재시작
$ sudo service nginx restart - 위의
Nginx 실행확인 2로 Nginx가 프로젝트를 프록시하는 것 확인
- 프로젝트의 환경설정 값을 다루는
EnvironmentBean을 DI받아 현재 활성화된 Profile을 반환하는 코드 작성
@RestController
@AllArgsConstructor
public class WebRestController {
private PostsService postsService;
private Environment env;
...
@GetMapping("/profile")
public String getProfile() {
return Arrays.stream(env.getActiveProfiles()).findFirst().orElse("");
}
}WebRestControllerTest생성하여 테스트
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
public class WebRestControllerTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
public void Profile확인() {
//when
String profile = this.restTemplate.getForObject("/profile", String.class);
//then
asserThat(profile).isEqualTo("local");
}
}- 테스트 내용은
/profile로 요청하면 현재 활성화된 Profile값 (local)이 반환되는지 비교
- 운영 환경의 yml 파일은 프로젝트 외부에 생성 (내부에 생성해서 git으로 올리면 운영환경의 중요 정보가 같이 올라감)
/app/config/haru-sketch/real-application.yml에 생성-
--- spring: profiles: set1 server: port: 8081 --- spring: profiles: set2 server: port: 8082
- 운영 환경의 yml 파일을 프로젝트가 호출할 수 있도록
Application.java의 코드 변경 - 원래 코드
@EnableJpaAuditing // JPA Auditing 활성화
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
System.out.println("====== SERVER STARTED ======");
}
}- 변경 후 코드
@EnableJpaAuditing //JPA Auditing 활성화
@SpringBootApplication
public class Application {
public static final String APPLICATION_LOCATIONS =
"spring.config.location=classpath:application.yml,"
+ "/app/config/haru-sketch/real-application.yml";
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class)
.properties(APPLICATION_LOCATIONS)
.run(args);
}
}- 스프링부트 프로젝트가 실행될 때, 프로젝트 내부에 있는
application.yml과 외부에 위치한/app/config/haru-sketch/real-application을 모두 불러오는지 확인해보기- Eclipse의 경우
Application.java파일 우클릭 -> Debug As -> Debug Configuration 로 이동 - Spring Boot App 하위의 프로젝트명 - Application 을 복사
- Name을
harusketch - [set1] Application으로 수정하고 Profile에set1입력 - [set1] Application을 실행하고 localhost:8081/profile 로 접속했을때 set1 뜨면 정상
- Eclipse의 경우
- EC2에도 로컬과 같이
/app/config/haru-sketch/real-application.yml생성하여 설정값 등록- 인스턴스의
퍼블릭DNS/profile로 접속하여 기본값인local출력되는지 확인
- 인스턴스의
- 지금까지
git,travis디렉토리를 생성했고, 3번째로nonstop디렉토리 생성- EC2 접속 후
$ mkdir ~/app/nonstop
- EC2 접속 후
- 기존의 스프링프로젝트.jar 복사
-
$ mkdir ~/app/nonstop/haru-sketch $ mkdir ~/app/nonstop/haru-sketch/build $ mkdir ~/app/nonstop/haru-sketch/build/libs $ cp ~/app/travis/build/build/libs/*.jar ~/app/nonstop/haru-sketch/build/lib - jar 파일을 모아둘 디렉토리 생성
$ mkdir ~/app/nonstop/jar - 스크립트 파일 생성
$ nano ~/app/nonstop/deploy.sh
#! /bin/bash
# 1. 스크립트에 필요한 변수값 할당
BASE_PATH=/home/ec2-user/app/nonstop
BUILD_PATH=$(ls $BASE_PATH/haru-sketch/build/libs/*.jar)
JAR_NAME=$(basename $BUILD_PATH)
echo ">> bulid 파일명: $JAR_NAME"
# 2. 빌드된 Jar 파일을 jar 디렉토리로 복사
echo ">> bulid 파일 복사"
DEPLOY_PATH=$BASE_PATH/jar/
cp $BUILD_PATH $DEPLOY_PATH
# 3. 현재 구동중인 set 확인
echo ">> 현재 구동중인 Set 확인"
CURRENT_PROFILE=$(curl -s http://localhost/profile) # curl에서 -s 옵션은 상태진행바를 노출시키지 않는 옵션
echo ">> $CURRENT_PROFILE"
# 4. NginX에 연결되어 있지 않은 Profile 찾기
# 쉬고있는 set 찾기 (set1이 사용중이면 set2가 쉬는 중이므로 IDLE에 할당, vice versa)
if [ $CURRENT_PROFILE == set1 ]
then
IDLE_PROFILE=set2
IDLE_PORT=8082
elif [ $CURRENT_PROFILE == set2 ]
then
IDLE_PROFILE=set1
IDLE_PORT=8081
else
echo ">> 일치하는 Profile이 없습니다. Profile: $CURRENT_PROFILE"
echo ">> set1을 할당합니다. IDLE_PROFILE: set1"
IDLE_PROFILE=set1
IDLE_PORT=8081
fi
# 5. 미연결된 Jar로 신규 Jar 심볼릭 링크 (ln)
echo ">> application.jar 교체"
IDLE_APPLICATION=$IDLE_PROFILE-haru-sketch.jar
IDLE_APPLICATION_PATH=$DEPLOY_PATH$IDLE_APPLICATION
ln -Tfs $DEPLOY_PATH$JAR_NAME $IDLE_APPLICATION_PATH
# -T : treat LINK_NAME as a normal file always
# -f : remove existing destination files
# -s : make symbolic links relative to link location
# 6. Nginx와 연결되지 않은 Profile을 종료
echo ">> $IDLE_PROFILE 에서 구동중인 어플리케이션 PID 확인"
IDLE_PID=$(pgrep -f $IDLE_APPLICATION)
if [ -z $IDLE_PID ]
then
echo ">> 현재 구동중인 어플리케이션이 없으므로 종료하지 않습니다."
else
echo ">> kill -15 $IDLE_PID"
kill -15 $IDLE_PID
sleep 5
fi
# 7. 6의 Profile로 Jar 실행
echo ">> $IDLE_PROFILE 배포"
nohup java -jar -Dspring.profiles.active=$IDLE_PROFILE $IDLE_APPLICATION_PATH &
echo ">> $IDLE_PROFILE 10초 후 Health Check 시작!"
echo ">> curl -s http://localhost:$IDLE_PORT/health "
sleep 10
# 8. 아래 코드를 10회 반복 수행
for retry_count in {1..10}
do
# 9. /health 요청 결과 저장
response=$(curl -s http://localhost:$IDLE_PORT/actuator/health)
## /health의 결과는 {"status":"UP"}과 같이 나옴. spring-boot-starter-actuator 의존성 덕분.
## actuator는 스프링부트 프로젝트의 여러 상태를 확인해줄 수 있는 의존성
up_count=$(echo $response | grep 'UP' | wc -l)
## response 결과에 "UP"이 있는지 확인
## echo $response | grep 'UP' 을 하면 UP이 포함된 문자열을 필터링 해줌
## | wc -l 로 필터링된 문자열의 갯수가 몇개인지 확인.(UP이 있다면 1개 이상)
# 10. UP 문자열이 있는지 확인해서 있다면 for문 종료, 없다면 메세지 출력 후 아래 코드 실행
if [ $up_count -ge 1 ]
then # $up_count >= 1 ("UP" 문자열이 있는지 검증)
echo ">> Health Check 성공"
break
else
echo ">> Health Check의 응답을 알 수 없거나 혹은 status가 UP이 아닙니다."
echo ">> Health Check : ${response}"
fi
# 11. 10회 다 실행될 동안 안되면 스크립트 종료
if [ $retry_count -eq 10 ]
then
echo ">> Health Check 실패."
echo ">> Nginx에 연결하지 않고 배포를 종료합니다."
exit 1
fi
echo ">> Health Check 연결 실패. 재시도..."
sleep 10
done- 스크립트 실행
$ ~/app/nonstop/deploy.sh - set1을 profile로 가진 프로젝트 실행 확인
$ ps ef|grep java
배포가 완료되어 어플리케이션이 실행되면, Nginx가 기존에 바라보던 Profile의 반대편을 바라보도록 변경하는 과정 필요
$ cd /etc/nginx 이 경로에 Nginx 설정에 관련된 모든 정보가 담겨있다.
우선 Nginx가 동적으로 Proxy Pass를 변경할 수 있도록 설정 수정
$ sudo nano /etc/nginx/nginx.conf
아래와 같이 수정
#Load configuration files for the default server block
include /etc/nginx/default.d/*.conf;
include /etc/nginx/conf.d/service-url.inc; //새로 추가
location / {
proxy_pass $srvice_url; // 수정
...include /etc/nginx/conf.d/service-url.inc;- Java의 import와 같이 service-url.inc 파일을 포함
- nginx.conf에서 service-url.inc에 있는 변수들을 그대로 사용 가능
proxy_pass $service_url;- service-url.inc에 있는
service_url변수를 호출
- service-url.inc에 있는
$ sudo nano /etc/nginx/conf.d/service-url.inc으로 파일 생성하고, 아래 내용 입력
set $service_url http://127.0.0.1:8001;
- 저장 후, 변경내용 반영을 위해 nginx 재시작.
$ sudo service nginx restart curl로 테스트해보면$ curl -s localhost/profile- 아래처럼 Proxy가 set1로 가는 것 확인
set1[ec2-user@ip-~~~]$
동적 프록시 환경이 구축된 Nginx가
배포 시점에 바라보는 Profile을 자동으로 변경하도록 스위치 스크립트 생성