배포 자동화 : Github Actions(CI) + AWS CodeDeploy(CD)

Jieun·2024년 1월 22일

프로젝트 기록

목록 보기
4/5

Spring 프로젝트 : Github Actions와 AWS CodeDeploy를 사용한 배포 자동화

처음엔 그나마 익숙한 도커를 사용한 자동배포를 시도했으나

가만히 있어도 늘어나는 pull 수와.. 환경변수 관련 에러와 보안 문제를 겪고 인도의 해커가 내 AWS 계정을 탈취해 휴학하고 직접 등록금을 버는 악몽을 꾸고..... CodeDeploy를 사용하는 방향으로 변경함

처음 적용해보는 배포자동화라 멍청한 실수도 많았고 그나마 의미있는 시행착오도 겪었다. 트러블슈팅을 중심으로 적어보려고 한다.


순서

  1. github actions
    • Gradle - build 진행
    • zip 파일로 압축
    • S3에 zip 파일 업로드
  2. CodeDeploy
    • S3에서 zip 파일 다운로드, build
    • appspec.yml -> shell script 실행
    • .jar 파일 실행 : 배포 완료!

사전작업

1. AWS - EC2, S3, CodeDeploy, IAM 설정

주의 : 전부 같은 리전에 생성하자

- 0. 사용자 생성

S3FullAccess
AWSCodeDeployRole
AWSCodeDeployFullAccess

  • 사용자 생성 > 해당 권한들 포함하도록 권한 연결

  • accessKey, secretKey 생성 > 저장


- 1. EC2 인스턴스 생성

  • 보안그룹 : 인바운드 규칙 확인
  • 키페어 확인
  • 탄력적 IP 연결 확인
  • IAM 프로필 연결 확인

※ IAM 프로필 연결

IAM에서
S3FullAccess
AWSCodeDeployRole
AWSCodeDeployFullAccess
세 개의 역할을 포함한 역할 생성

인스턴스 > 작업 > 보안 > IAM 역할수정 > 생성한 IAM 프로필 연결하기


- 2. S3 : 퍼블릭액세스 설정

해당 설정 해제해서 퍼블릭액세스 가능하도록 해야 함

모든 S3에 대한 Action allow 하도록 권한설정 편집필요함


이렇게 뜨면 설정 완료


- 3. CodeDeploy

  • app name 설정
  • 컴퓨팅 플랫폼 : EC2/온프레미스 선택

S3FullAccess
AWSCodeDeployRole
AWSCodeDeployFullAccess

세 개의 역할을 포함한 iam 역할 생성 -> arn 추가

  • 생성했던 EC2 인스턴스 연결
    ※ 생성한 EC2가 목록에 나오지 않는다면 EC2와 같은 리전에 있는지 확인

2. EC2 - CodeDeploy Agent 설치

wget https://bucket-name.s3.region-identifier.amazonaws.com/latest/install

나는 bucket-name이 내가 직접 생성한 S3 bucket 이름인 줄 알고.. 시도했으나... 당연히 404 error만 잔뜩 보고 실패했다
위 공식문서의 목록을 보고 작성하자


Github Actions - workflow 작성

  • master branch에 push, PR 날리면 aciton

  • 기본 checkout
  • JDK 세팅 : java version 17 사용

  • properties 파일 생성
  • 깃에 올리지 못하는 개인정보들이 담긴 파일들은 git secrets에 등록해 내부에서 작성함

!! 기존 properties 위치에 맞추는거 주의 !!


  • 권한 문제로 build 불가한 에러 발생했었음 : 미리 권한 부여
  • test 제외하고 clean, build

  • zip 파일 생성
  • S3에 업로드 할 떄 파일이름이 겹치지 않도록 랜덤파일명 깃허브 기본환경변수 $GITHUB_SHA 사용

  • S3와 CodeDeploy 사용하기 위한 AWS Credential 설정
  • 사용자 생성하고 생성했던 액세스키, 시크릿키 여기에 사용

      - name: Upload to S3
        run: aws s3 cp --region ${{ secrets.AWS_REGION }} ./$GITHUB_SHA.zip s3://${{ secrets.S3_BUCKET_NAME }}/$GITHUB_SHA.zip

      - name: Code Deploy
        run: aws deploy create-deployment --application-name ${{ secrets.AWS_APP_NAME }} --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name ${{ secrets.AWS_GROUP_NAME }} --s3-location bucket=${{ secrets.S3_BUCKET_NAME }},bundleType=zip,key=$GITHUB_SHA.zip
  • S3에 업로드
  • CodeDeploy
    --aplication-name : CodeDeploy app 설정시 설정한 app name
    --deployment-config-name : 배포구성 AllAtOnce
    --s3-locaiton
    	-bucket : S3 버켓 이름
        - bundleType : zip
        - key : zip파일 이름

github > aciton 칸에서 이렇게 뜨면 CI까지는 성공!

실패한다면 같은 칸에서 실행 결과 메세지 확인 가능하므로 참고하자


appspec.yml 작성

CodeDeploy에서 배포관리에 사용하는 파일

- 기본이름 : appspec.yml
- appspec.yml 위치 : 애플리케이션의 소스 코드 디렉터리 구조의 루트에 배치해야 한다

https://docs.aws.amazon.com/ko_kr/codedeploy/latest/userguide/reference-appspec-file.html
: 공식문서 참고

  • 위치 참고
version: 0.0
os: linux

files:
  - source: /
    destination: /home/ec2-user/app
hooks:
  AfterInstall:
    - location: scripts/deploy.sh
      timeout: 60
      runas: root
  • files - source - destination : 파일을 받은 후 인스턴스에 저장할 위치
  • hooks : CodeDeploy의 배포 lifecycle 마다 할 동작들을 명시할 수 있음
    AfterInstall
    • location : 실행시킬 shell script의 위치
      ※ deploy.sh 위치는 appspec.yml과 마찬가지로 프로젝트 최상단 기준임!!

deploy.sh

  1. appspec.yml에서 파일을 저장하기로 명시한 위치로 이동
  2. pgrep -f java : 현재 프로젝트가 실행되고 있다면 pid 얻음
  3. 구동중이라면 종료
  4. nohup java -jar $JAR_NAME > /home/ec2-user/nohup.out 2> /dev/null &
    : 저장한 jar_name으로 애플리케이션 실행
    : stdout은 /home/ec2-user/nohup.out 에 저장 : 추후에 cat으로 실행중에 발생한 에러 확인 가능 (들어가기 귀찮아서 홈에 만듦)
    : 2> /dev/null : stderr는 버림

## Trouble shooting ##

배포 자동화는 아예 처음 해보는 만큼 이틀 내내 걸렸으며 정말 어이없고 많은 에러를 만남..

1. EC2 CodeDeploy Agent 설치 404 error

위에 말한 부분, 버켓이름을 지정된 리스트에 있는 걸 써야하는데 내 버켓이름으로 썼더니 에러가 뜸
당연함. 그런 리소스는 존재하지 않으니까~

https://docs.aws.amazon.com/ko_kr/codedeploy/latest/userguide/resource-kit.html#resource-kit-bucket-names
: 이 공식문서를 참고합시다~


2. codedeploy Script does not exist at specified location deploy.sh

Script does not exist at specified location: /opt/codedeploy-agent/deployment-root/9f65c170-dcd0-466d-aa0c-94754940cd0a/d-F4OQQN7Z2/deployment-archive/deploy.sh

말 그대로 deploy.sh의 위치가 틀려서 난 오류
여러 블로그들을 이곳저곳 베끼며 참고하던 난.. 최상단>scripts 폴더 아래 만들면 된다는 말을 그냥 믿고 작성했으나 기준은 프로젝트 최상단이었다~

/scripts/deploy.sh 위치에 만들었다면

appspec.yml 작성시 location에 이렇게 작성해야함

항상.. 공식문서를.. 확인하자...


3. ERROR [codedeploy-agent(11590)]: InstanceAgent::Plugins::CodeDeployPlugin::CommandPoller: Error polling for host commands: Aws::Errors::MissingCredentialsError - unable to sign request without credentials set - /opt/codedeploy-agent/vendor/gems/aws-sdk-core-3.121.1/lib/aws-sdk-core/plugins/signature_v4.rb:74:in`sign_request'

이 에러에서 가장 많은 시간이 걸렸다

끝나버렸다 아직 시작도 안 해봤는데..

Github actions에서 CI는 성공했지만 CodeDeploy에서는 시작도 못 하고 Application Stop에서 에러가 나버렸다.
CodeDeploy에서 확인할 수 있는 에러는

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.

요러했고, 이거로는 단서가 너무 부족해서 EC2 인스턴스에 CodeDeploy 로그가 남는다고 해서 그걸 까보니까 나온 에러.

위치는 ~/etc/var/log/aws/codedeploy-agent/~.log

해당 스텝을 체크해보자

  1. CodeDeploy에 연결된 권한 확인
  2. EC2에 연결된 IAM 프로필의 권한 확인
  3. workflow의 Credentials 부분에 입력된 사용자 액세스키, 시크릿키, + 해당 사용자의 권한 확인
  4. 모든 권한 확인 후 CodeDeploy Agent restart (권한 갱신을 위해선 재시작 해야 한다고 함)
  5. 그래도 안 된다면 configure 명령어로 직접 credentials 입력하기
    https://goateedev.tistory.com/317 << 이 블로그 참고
    : 원래 3의 과정이 해당 명령어 없이 자격증명을 해주는 부분인 것 같은데.. 안되니까 직접 입력해준다
  6. 그리고.. cat 명령어로 인한 착각 ㅜ
    : 저 log 파일이 너무너무 길어서 나는 cat ~.log | more로 확인하고 있었다.. 저 log파일은 덮어씌워지는게 아니라 뒤에 덧붙여지기 때문에 저렇게 확인하면... 그냥 맨 처음에 났던 오류만 계속 확인하게 된다
    할 만한 방법은 다 해봤는데 도저히 해결이 안 돼서 저 로그를 노려봤는데 위에서 확인했을 때와 시간이 변하지 않는 다는 것을 확인했다 ^-^....
    혹시 내가 계속 같은 예전의 에러메시지를 보고 현재의 에러라고 착각하고 있는건 아닌지 확인해보자.............

4. The specified key does not exist.

나 같은 경우에는 workflow에서 정의한

      - name: Code Deploy
        run: aws deploy create-deployment --application-name ${{ secrets.AWS_APP_NAME }} --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name ${{ secrets.AWS_GROUP_NAME }} --s3-location bucket=${{ secrets.S3_BUCKET_NAME }},bundleType=zip,key=$GITHUB_SHA.zip
// 마지막 key=$GITHUB_SHA.zip

마지막 단계인 deploy에서 key이름을 잘못설정해서 생긴 에러였다.
key는 S3에 올라간 zip파일의 이름을 그대로 쓰면 된다.


5. nohup java -jar $JAR_NAME > /home/ec2-user/nohup.out 가 실행이 안 됨

CodeDeploy까지 모두 성공을 확인하고 드디어 포스트맨으로 연결을 확인했는데..

눈물이 흐를 뻔 했다.
심지어 아무 에러도 없고.. 뭔가 확인할 건덕지가 없었다..
에러가 없어..? 하고 생각해보니 stdout을 저장하는 /home/ec2-user/nohup.out 파일 자체가 없었다.
그냥 실행이 안 됐나?? 하고 처음에 작성했던 .jar 변수를 직접 인스턴스에서 찍어보니

SNAPSHOT.jar가 아니라 SNAPSHOT-plain.jar가 나왔다
우우우 붐따..
1. tail > head로 바꿔서 올바른 파일명 얻기에는 성공했으나 왜인지 이것도 안 돼서
2. 그냥 위치를 하드코딩 해버렸다

어쨋든 해결!


6. Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'FCMService': Invocation of init method failed

갑자기 의존성 주입..? spring 에러가 여기서 난다고..?
로컬로 돌릴 땐 스프링관련 에러는 전혀 없었는데 여기서 나서 당황스러웠다.

jsonparseexception : unexpected character : was expecting double quote to start field name

FCM을 사용하는 FCMService 있었는데, 여기서 JSON field name을 ""로 안 해서 JSON parse error가 났다고 한다.
내가 JSON을 썼나..? 여긴 response를 주는 부분도 아닌데..? 라고 고민했다.
글로 정리해보면 당연히 보이는데 codedeploy+JSON+FCM을 전부 연결해서
검색했을 땐 이런 사례가 단 하나도 나오지가 않아서 당황스러웠다.

github action과 codedeploy를 사용해서 배포 자동화를 하며 FCM을 이용해 notification을 만든경우, jsonparseexception : unexpected character : was expecting double quote to start field name가 발생한다면
github.secrets에 저장한 JSON 형식의 firebasekey의 ""를 인식하지 못 하고 plain text로 저장해 발생한 문제다!!

  • 해결방법
  1. "앞에 전부 \를 붙여준다
    : 단순히 수작업으로 \를 붙여서 "를 인식하게 해준다
  2. github action에 JSON을 만들어주는 라이브러리를 사용한다
    : https://github.com/marketplace/actions/create-json

나는 더 이상 CI단계에서 커밋하고 빨간줄 보고.. 이 과정을 거치고 싶지 않아서 그냥 secrets에 전부 \를 붙여서 수정했다.
해결!


어쨋든 성공!
다음엔 꼭 도커를 사용해보고 싶다

0개의 댓글