깃헙에 올리지 못하는 계정 정보가 담긴 properties 를 자동 배포할 때 어떻게 적용할 수 있는지 실습
📌 나의 상황
기본 application.properties는 두고 별도의 application-secret.properties를 두어 공개되지 말아야하는 정보를 넣어 사용하고 있다. 그리고 application.properties에 아래처럼 넣어서 application-secret.properties를 끌어오는 방식.
(아래처럼 인식해서 가져오기 때문에 끌어오는 파일 명은 application-[원하는이름].properties 로)
spring.profiles.include=secret
이 형식을 그대로 사용하기 위해서 기본 application.properties는 깃헙에 올라가게 두고, application-secret.properties는 .gitignore에 추가해 깃헙에 올라가지 않게 설정했다. 그리고 github의 secrets에는 application-secret의 내용을 넣을 예정
📌 실습
📍Github Secrets 설정
Github > Settings > Secrets and variables > Actions > New repository secret 클릭
📍gradle.yml 수정 (메인이되는 yml 파일)
yml 파일에서 jobs > steps 부분에서 아래 코드를 추가해야하는 데 반드시 build과정 전에! 이 코드가 있어야한다.
깃헙 프로젝트 배포 과정에서 application-secret.properties를 포함시켜준 뒤 빌드를 하고 그 jar를 복사해 EC2에서 실행해야 하기 때문. 빌드 뒤에 들어가버리면 jar파일에서 빠져버려 정상 작동하지 않는다.
# 중요 키 정보 있는 properties 따로 관리
- name: make application-secret.properties
run: |
cd ./src/main/resources
touch ./application-secret.properties
echo "${{ secrets.PROPERTIES_SECRET }}" > ./application-secret.properties
shell: bash
변수명에 해당하는 값을 출력해 현재위치 폴더에 있는 application-secret.properties에 쓴다
- echo : 출력 - ${{ secrets.PROPERTIES_SECRET }} : 윗단계에서 우리가 깃헙 settings에 저장한 변수명을 이런 형태로 적어주면 그 변수 값을 가져온다 - > : 왼쪽 내용을 오른쪽에 씀 (> : write and overwrite , >> : append)
3) shell : 사용할 쉘
이러면 지켜야하는 정보를 일반저긴 깃헙파일로 올리지 않고 다른 루트로 자동배포에 포함시킬수 있게 된다. 간단!
#!/usr/bin/env bash
PROJECT_ROOT="/home/ubuntu/app"
JAR_FILE="$PROJECT_ROOT/CICD_githubActions-0.0.1-SNAPSHOT.jar"
APP_LOG="$PROJECT_ROOT/application.log"
ERROR_LOG="$PROJECT_ROOT/error.log"
DEPLOY_LOG="$PROJECT_ROOT/deploy.log"
TIME_NOW=$(date +%c)
# build 파일 복사
echo "$TIME_NOW > $JAR_FILE 파일 복사" >> $DEPLOY_LOG
cp $PROJECT_ROOT/build/libs/*.jar $JAR_FILE
# jar 파일 실행
echo "$TIME_NOW > $JAR_FILE 파일 실행" >> $DEPLOY_LOG
nohup java -jar $JAR_FILE > $APP_LOG 2> $ERROR_LOG & # 계속 실행되게 nohup으로 실행
CURRENT_PID=$(pgrep -f $JAR_FILE) # nohup으로 실행 후 프로세스 아이디를 얻음
echo "$TIME_NOW > 실행된 프로세스 아이디 : $CURRENT_PID" >> $DEPLOY_LOG
📍 start.sh (새로운 jar파일을 실행)
#!/usr/bin/env bash
# #!은 명령어 집합표시, 뒤는 이 명령어들을 해석할 프로그램 위치와 프로그램
#-------------------------------------------------- 변수 선언 START
PROJECT_ROOT="/home/ubuntu/app" # 종료할 jar파일 위치
JAR_FILE="$PROJECT_ROOT/CICD_githubActions-0.0.1-SNAPSHOT.jar" # 종료할 jar파일 이름
DEPLOY_LOG="$PROJECT_ROOT/deploy.log" # 로그파일 생성
TIME_NOW=$(date +%c) # 현재시간
# 현재 구동 중인 애플리케이션 pid 확인
CURRENT_PID=$(pgrep -f $JAR_FILE)
#-------------------------------------------------- 변수 선언 END
#-------------------------------------------------- 명령어 START
# 프로세스가 켜져 있으면 종료
if [ -z $CURRENT_PID ]; then
# 리눅스에서 > 와 >> 의 차이 : > (뒤에 나오는 파일에 write or overwrite), >> (뒤에 나오는 파일에 추가 append)
echo "$TIME_NOW > 현재 실행중인 애플리케이션이 없습니다" >> $DEPLOY_LOG # 리눅스 터미널 출력 명령어
else
echo "$TIME_NOW > 실행중인 $CURRENT_PID 애플리케이션 종료 " >> $DEPLOY_LOG
kill -15 $CURRENT_PID # -15 프로세스를 안전하게 종료
fi
📌plain.jar 생성되지 않게 build.gradle 설정
빌드시 일반 jar파일 말고도 plain.jar파일이 하나 더 생성되는 데 이를 방지, 아래 코드를 추가하면 된다.
* plain.jar는 plain archive라고 해서 dependency를 모두 제외한 순수한 소스코드와 리소스만을 가지고 있는 파일
jar {
enabled = false
}
📌gradle.yml 수정
📍 수정 전
name: Java CI with Gradle
on: # 워크플로우를 실행할 이벤트 종류와 특정 브랜치 설정
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions: # 권한 설정
contents: read
jobs: # 수행할 워크플로우
build: # 빌드
runs-on: ubuntu-latest # 빌드가 실행할 OS
steps: # 단계 설정
- uses: actions/checkout@v3 # 워크플로우 실행전 체크아웃
- name: Set up JDK 11 # JDK 11 버전 설치
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
# Build
- name: Build with Gradle # gradle로 빌드 실행
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
with:
arguments: build
📍 수정 후
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle
name: Java CI/CD with Gradle
on: # 워크플로우를 실행할 이벤트 종류와 특정 브랜치 설정
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env: # 환경 설정
AWS_REGION: ap-northeast-2
S3_BUCKET_NAME: littlezerobucket
CODE_DEPLOY_APPLICATION_NAME: cicd_Test
CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: cicd_group # IAM의 CodeDeploy용 역할
permissions: # 권한 설정
contents: read
jobs: # 수행할 워크플로우
deploy: # Deploy
name: Deploy
runs-on: ubuntu-latest # 빌드가 실행할 OS
environment: production
steps: # 단계 설정
# 체크아웃
- name: Checkout
uses: actions/checkout@v3 # 워크플로우 실행전 체크아웃
# JDK 11 세팅
- name: Set up JDK 11 # JDK 11 버전 설치
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
# BUILD 권한 부여
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
# Build
# BUILD
- name: Build with Gradle # gradle로 빌드 실행
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
with:
arguments: clean build
# AWS 인증
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
# 빌드한 파일 S3 버킷에 업로드
- name: Upload to AWS S3
run: |
aws deploy push \
--application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
--ignore-hidden-files \
--s3-location s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip \
--source .
# S3 버킷에 올린 파일로 CodeDeploy 실행
- name: Deploy to AWS EC2 from S3
run: |
aws deploy create-deployment \
--application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
--deployment-config-name CodeDeployDefault.AllAtOnce \
--deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
--s3-location bucket=$S3_BUCKET_NAME,key=$GITHUB_SHA.zip,bundleType=zip
Error: The security token included in the request invalid.
문제: credential 검증에서 오류
원인: Code Deploy Application Name 값과 Code Deploy Application Group Name을 잘못넣어서 발생
해결: AWS CodeDeploy에서 생성한 애플리케이션 이름과 그 안의 배포 그룹 이름으로 설정해주니 해결
📍 둘다 AWS환경 설정 글에서 9번에서 만든 것을 yml에 넣어줘야 한다.
위에 애플리케이션에서 해당 이름 누르면 있는 배포 그룹
*** 2)
Error: Process completed with exit code 255.
appspec.yml was not found.
문제: appspec.yml을 찾지 못해 실행 못함
원인: appspec 파일명을 AppSpec으로 해서 찾지 못함
해결: 파일명을 모두 소문자로 해줬더니 해결
*** 3)
Error: Process completed with exit code 127.
command not found
문제: 요구하는 arguments를 못불러옴
원인: 해당 arguments 앞에 맞지 않는 arguments가 붙어 뒤를 읽지 못함
해결: 불필요한 arguments 삭제로 해결
*** 4)
CodeDeploy agent was not able to receive the lifecycle event. Check the CodeDeploy agent logs on your host and make sure the agent is running and can connect to the CodeDeploy server.
- CodeDeploy agent가 라이프사이클 이벤트를 받을 수 없으니 host의 로그를 살펴보라는 내용
ERROR [codedeploy-agent(1163)]: InstanceAgent::Plugins::CodeDeployPlugin::CommandPoller: Missing credentials - please check if this instance was started with an IAM instance profile
문제: github actions에서 Deploy가 성공했음에도 불구하고 실제로 AWS Deploy결과를 봤을 때 ApplicationStop에서 바로 실패한 케이스
원인: IAM 역할을 지정하지 않은 인스턴스에 CodeDeployAgent를 먼저 설치해 버려 CodeDeploy에 해당 역할을 실행할 수 있는 자격 증명이 없어서 생긴 문제
해결: CodeDeploy Agent를 EC2에서 다시 실행 시키면 된다.
- 1) 다시 실행
- 2) 상태 확인
sudo service codedeploy-agent restart
sudo service codedeploy-agent status
📌 위에 성공 로그 보다가 든 의문점
이 로그를 보면 현재 실행중인 애플리케이션이 없다고 한다. 하지만 아래처럼 쉘 스크립트를 넣어줬고, 기존 nohup을 죽이고 새로운 빌드 파일로도 변경이되서 nohup으로 돌아가는 상황. 근데 프로세스가 켜져있어서 stop.sh가 kill해줬다면 실행중인 프로세스 아이디와 애플리케이션 종료에 대한 로그도 찍혔어야하는 데 없다. 이미 stop전에 프로세스가 kill이 된 것으로 보인다.
# 프로세스가 켜져 있으면 종료
if [ -z $CURRENT_PID ]; then
# 리눅스에서 > 와 >> 의 차이 : > (뒤에 나오는 파일에 write or overwrite), >> (뒤에 나오는 파일에 추가 append)
echo "$TIME_NOW > 현재 실행중인 애플리케이션이 없습니다" >> $DEPLOY_LOG # 리눅스 터미널 출력 명령어
else
echo "$TIME_NOW > 실행중인 $CURRENT_PID 애플리케이션 종료 " >> $DEPLOY_LOG
kill -15 $CURRENT_PID # -15 프로세스를 안전하게 종료
fi
직접 kill하지 않아도 application을 stop해 주는 로직이 있는 걸까 싶어 이벤트를 보니 ApplicationStop이 마음에 걸린다.
혹시 정말 앞에서 해주고 있는 거 아닐까 싶어서
AfterInstall과정에서 stop.sh을 실행할 수 있도록 설정한 yml 파일 부분을 아예 주석처리 해버리고 테스트를 했는데
띠용 잘된다. 앞서 찍힌 로그와 달리 마지막 로그엔 stop.sh에 있는 현재 실행중인 애플리케이션이 없습니다 로그도 없다. 그럼 stop.sh가 없는 버젼으로 잘 실행됬다는 이야기..
찾아보니 배포 라이프사이클 이벤트 중에 ApplicationStop은 마지막으로 성공 배포한 버젼에서 appspec file과 스크립트를 가져와서 사용한다고 한다. 이 말은 즉 새로 배포한 파일에서의 applicationStop관련은 이번 배포가 아닌 다음 배포를 위해 적용된다는 뜻이기도 하다. 하지만 나는 전에도 후에도 applicationStop에 kill하는 걸 설정해준 적이 없는데? 아니면 전에 파일을 다시 역행하는 방식? 은.. 아닌거 같고 왜 kill처리를 주석해줬음에도 kill하는게 되는 걸까 궁금해!!
************* 액세스 키는 생성하면 그 순간에만 볼 수 있고 다운받을 수 있다. 다운받아 잘 보관해두자.
📌 5.IAM 역할 생성 - EC2 용
📍 1단계
📍 2단계
AmazoneS3FullAccess
AWSCodeDeployFullAccess
검색해서 체크
📍3단계
역할 이름 설정과 추가한 권한만 체크하고 역할 생성
📍 EC2 인스턴스 가서 IAM 설정
작업 > 보안 > IAM 역할 수정
📌 6.IAM 역할 생성 - Code Deploy 용
역할을 한개 더 생성해야 한다.
사용사례와 이름만 설정하고 역할 생성 클릭
📌 7.EC2 셋팅
EC2 접속!
📍 home/ubuntu/ 위치에 app폴더 생성
📍 apt 업데이트하고 패키지들 설치
- 1) apt 업데이트 apt은 ubuntu 패키지 관리 툴
- 2) ruby-full 설치 : 프로그래밍 언어인 ruby를 설치 (중간에 설치를 계속할껀지 물음 / 중간에 보라색화면 나오면 선택사항이 하나인 화면은 Enter 넘어가고 뒤에 뭔가 여러가지 사항이 나오는 화면은 tap을 이용해 ok로 이동해서 enter누르면 이미 자동으로 선택된 사항에 대해서 적용해 재시작한다.)
- 3) wget 설치 : HTTP/FTP를 사용해서 서버에서 파일을 내려받기 위한 오픈소스
깃헙에서 Actions > gradle 검색 > java with Gradle 의 Cofigure 클릭
📌Workflow 선택
📍 yml이 뭔 파일이지?
Yet another Markup Language의 약자로 yml 또는 yaml 확장자로 사용된다. 여러 configuration을 한 파일에서 관리하기 위한 파일로 yaml문법을 사용하여 구성한다. 기본적으로 key-value로 구성되어 있고 json과 상위 호환되어 시퀀스(배열,리스트), 매핑이 가능하다.
아래 gradle.yml 구성을 보면
on : 이 워크플로우가 수행될 깃헙 이벤트를 결정
permissions : 권한 설정
jobs : 수행할 워크플로우들을 차례대로 입력
📌 PR test
브랜치를 하나 새로 만들어 워크플로우 이벤트에 반응하게 main에 PR을 해봤다 그런데 앗, 트러블 발생!
Detail를 보니 gradle에서 예외발생. 빌드시 권한이 없어서 발생하는 문제라고..
gradle.yml 파일에 Build with Gradle 이전에 gradlew 권한을 부여하는 워크플로우를 추가한다.
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew