AWS S3, CodeDeploy 연동하기

hwany·2020년 2월 26일
10

AWS

목록 보기
2/2
post-thumbnail

AWS S3, CodeDeploy 연동하기

EC2가 CodeDeploy를 연동 받을 수 있게 IAM 역할을 생성합니다.

먼저 설정하기 전에 사용자를 추가하는 것과 역할을 만드는 것은 다르다는 것을 이해할 필요가 있습니다.

IAM 역할이란 무엇입니까?

IAM 역할은 신뢰하는 개체에 권한을 부여하는 안전한 방법입니다. 개체의 예는 다음을 포함합니다.

  • 다른 계정의 IAM 사용자
  • AWS 리소스에서 작업을 수행해야 하는 EC2 인스턴스에서 실행 중인 애플리케이션 코드
  • 계정 내 리소스에서 작업을 수행하여 기능을 제공해야 하는 AWS 서비스
  • SAML을 통해 인증 연동을 사용하는 사내 디렉토리의 사용자

IAM 역할은 권한을 부여하는 더욱 안전한 방법으로 짧은 기간 동안 유효한 키를 발행합니다.

  • 역할
    • AWS 서비스에만 할당할 수 있는 권한
    • EC2, CodeDeploy, SQS 등
  • 사용자
    • AWS 서비스 외에 사용할 수 있는 권한
    • 로컬PC, IDC서버 등




역할 만들기


1. 유형의 개체 선택

지금 만들 권한은 EC2에서 사용할 것이기 때문에 사용자가 아닌 역할로 처리합니다.



2. 권한 정책 연결

EC2RoleForA정도까지 검색하여 AmazonEC2RoleforAWS-CodeDeploy를 선택합니다.



3. 태그 등록

본인이 원하는 이름으로 짓습니다.



4. 이름 등록 및 최종 확인

이름은 어떤 역할인지 알아보기 쉽게 짓습니다.




역할을 EC2 서비스에 등록

해당 EC2 인스턴스 설정에서 [IAM 역할 연결/바꾸기]를 선택합니다.

생성한 역할을 선택합니다.


역할을 선택한 후 해당 인스턴스를 재부팅해야 역할이 정상적으로 작동합니다.




CodeDeploy 에이전트 설치

  • 재부팅이 완료되었으면 CodeDeploy의 요청을 받을 수 있에 에이전트를 설치해야합니다.

  • Amazon Linux 2 AMI (centos)환경에서 작업하였습니다.

EC2에 접속해서 다음 명령어를 입력합니다.

$ aws s3 cp s3://aws-codedeploy-ap-northeast-2/latest/install . --region ap-northeast-2

다음과 같은 메시지가 콘솔창에 출력됩니다.

download: s3://aws-codedeploy-ap-northeast-2/latest/install to ./install

install 파일에 실행 권한을 추가하고 설치를 진행하는데, 설치가 안된다면 ruby를 설치하시면 됩니다.

chmod +x ./install && sudo ./install auto

Agent가 정상적으로 실행되고 있는지 상태 검사를 합니다.

다음과 같이 나오면 정상입니다.

$ sudo service codedeploy-agent status

The AWS CodeDeploy agent is running as PID 3530




CodeDeploy를 위한 권한 생성

  • CodeDeploy에서 EC2에 접근하려면 마찬가지로 권한이 필요합니다.

    AWS의 서비스이니 조금전과 마찬가지로 IAM역할을 생성합니다.


1. 유형의 개체 선택



2. 권한 선택

CodeDeploy는 권한이 하나뿐이라서 선택 없이 바로 다음으로 넘어가면 됩니다.



3. 태그 등록



4. 이름 등록 및 최종 확인

여기까지 진행하면 IAM의 역할에 두 역할이 생성됩니다.




CodeDeploy 생성

CodeDeploy는 AWS의 배포 삼형제 중 하나입니다.

  • Code Commit
    • 깃허브와 같은 코드 저장소의 역할 (거의 사용되지 않음)
  • Code Build
    • Travis CI, Circle CI와 마찬가지로 빌드용 서비스(역시 거의 사용되지 않음)
  • CodeDeploy
    • AWS의 배포 서비스
    • Commit이나 Build는 대체제가 있어 AWS서비스를 사용하지 않지만, CodeDeploy는 대체제가 없음
    • 오토 스케일링 그룹 배포, 블루 그린 배포, 롤링 배포, EC2 단독 배포 등 많은 기능을 지원

현재 프로젝트에서 Commit은 github, build는 Circle CI 또는 Travis CI가 하고 있기 때문에 추가로 사용할 서비스는 CodeDeploy입니다.


1. 애플리케이션 생성

CodeDeploy서비스로 이동해서 [애플리케이션 생성] 버튼을 클릭하여 CodeDeploy의 이름컴퓨팅 플랫폼을 선택합니다.

컴퓨팅 플랫폼에선 [EC2/온프레미스]를 선택합니다.



2. 배포 그룹 생성

생성이 완료되면 배포 그룹을 생성하라는 메시지를 볼 수 있습니다. 배포 그룹을 생성할 때 서비스 역할은 좀 전에 생성한 CodeDeploy용 IAM역할을 선택합니다.

배포 유형에서는 1대의 EC2에만 배포하므로 현재 위치를 선택합니다. 배포할 서비스가 2대 이상이라면 블루/그린을 선택하면 됩니다.

환경 구성은 [Amazon EC2 인스턴스]에 체크합니다.

배포 설정과 로드밸런서를 선택합니다. 배포 구성이란 한번 배포할 때 몇 대의 서버에 배포할지를 결정합니다.

2대 이상이라면 1대씩 배포할지, 30% 혹은 50%로 나눠서 배포할지 등등 여러 옵션이 있지만, 1대 서버다 보니 전체 배포하는 옵션으로 선택합니다. CodeDeployDefaultAllAtOnce는 한 번에 다 배포하는 것을 의미합니다.




Circle CI, S3, CodeDeploy 연동

먼저 S3에서 넘겨 줄 zip파일을 저장할 디렉토리를 하나 생성하겠습니다. EC2서버에 접속해서 다음과 같이 디렉토리를 생성합니다.

mkdir ~/app/step2 && mkdir ~/app/step2/zip

CIrcle CI의 Build가 끝나면 S3에 zip파일이 전송되고, 이 zip파일은 /home/ec2-user/app/setp2/zip로 복사되어 압축을 풀 예정입니다. AWS CodeDeploy의 설정은 appspec.yml로 진행합니다.

# appspec.yml

version: 0.0	# CodeDeploy 버전
os: linux
files:    
        - source: /	# CodeDeploy에서 전달해 준 파일 중 destination으로 이동시킬 대상을 루트로 지정(전체파일)
          destination: /home/ec2-user/app/step2/zip/	# source에서 지정된 파일을 받을 위치, 이후 jar를 실행하는 등은 destination에서 옮긴 파일들로 진행
          overwrite: yes

.circleci 디렉토리의 config.yml파일도 deploy부분에 CodeDeploy내용을 추가합니다.

version: 2.1

orbs:
  aws-s3: circleci/aws-s3@1.0.13
  aws-code-deploy: circleci/aws-code-deploy@0.0.12

jobs:
    build:
      # OOM오류를 피하기 위해 JVM 및 Gradle을 구성
      environment:
        _JAVA_OPTIONS: "-Xmx3g" # Heap memory
        GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=2"

      docker:
        - image: circleci/openjdk:8-jdk

      working_directory: ~/springboot2-webservice

      steps:
        # workflow에서 트리거된 git branch를 checkout
      	- checkout

      	# Download and cache depen에dencies, keys에 저장된 키로 캐시에 저장된 내용을 복사
      	- restore_cache:
          	keys:
            	- v1-dependencies-{{ checksum "build.gradle" }}

      	- run:
          	name: Downloading Dependencies
          	command: ./gradlew dependencies

      	- save_cache:
          	paths:
            	- ~/.gradle
          	key: v1-dependencies-{{ checksum "build.gradle" }}  # build.gradle 파일에서 무언가가 변경 될 때마다 캐시가 재생성되며,이 프로젝트의 다른 브랜치는 동일한 캐시 키를 생성합니다.

      	- run: ./gradlew clean build

  deploy:
    docker:
      - image: circleci/python:2.7
    steps:
      - checkout

      - run:
          name: before_deploy
          command: |
            zip -r springboot2-webservice *
            mkdir -p deploy
            mv springboot2-webservice.zip deploy/springboot2-webservice.zip

      - aws-s3/copy:
          from: deploy/*
          to: 's3://hwany-springboot-build'
          aws-region: AWS_DEFAULT_REGION

      - aws-code-deploy/deploy-bundle:
          application-name: hwany-springboot2-webservice
          deployment-group: hwany-springboot2-webservice-group
          deployment-config: CodeDeployDefault.AllAtOnce
          bundle-bucket:  hwany-springboot-build
          bundle-key: springboot2-webservice # Travis CI와 다르게 확장자는 제거해야 합니다.
          bundle-type: zip # default
          
          
workflows:
  version: 2.1

  build-deploy:
    jobs:
      - build
      - deploy:
          requires:
            - build
          filters:
            branches:
              only: master

commit하고 push를하면 Circle CI에서 build와 deploy를 수행합니다.

Circle CI가 끝나면 CodeDeploy화면 아래에서 배포가 수행된 것을 확인할 수 있습니다.

배포가 끝났다면 파일들이 잘 도착했는지 확인합니다.

$ cd app/step2/zip/
$ ll
합계 28
-rw-r--r-- 1 root root  458  126 01:19 README.md
-rw-r--r-- 1 root root  374  126 01:19 appspec.yml
-rw-r--r-- 1 root root 2327  126 01:19 build.gradle
drwxr-xr-x 3 root root   21  126 01:19 gradle
-rwxr-xr-x 1 root root 5305  126 01:19 gradlew
-rw-r--r-- 1 root root 2269  126 01:19 gradlew.bat
-rw-r--r-- 1 root root   45  126 01:19 settings.gradle
drwxr-xr-x 4 root root   30  126 01:19 src

들어온 것을 확인하니 잘 연동이 되는 줄 알았지만 문제가 발생했습니다!

build job에서 빌드 한 build디렉토리가 포함되지 않는 것이 문제였습니다. build와 deploy는 서로 다른 docker환경에서 실행되기 때문에 단순히 deploy에서 check out 후에 빌드하지 않고 zip으로 압축하는게 전부였습니다. 딱히 병렬로 수행할 작업이 없기 때문에 아래 그림처럼 BUILD JOB과 DEPLOY JOB을 같은 환경으로 묶어주기로 하였습니다.

코드를 다음과 같이 수정하였습니다.

version: 2.1

orbs:
  aws-s3: circleci/aws-s3@1.0.13
  aws-code-deploy: circleci/aws-code-deploy@0.0.12

executors:
  my-executor:
    docker:
      - image: circleci/openjdk:8-jdk

    working_directory: ~/springboot2-webservice

jobs:
  build:
    executor: my-executor

    # OOM오류를 피하기 위해 JVM 및 Gradle을 구성
    environment:
      _JAVA_OPTIONS: "-Xmx3g" # Heap memory
      GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=2"

    steps:
      # workflow에서 트리거된 git branch를 checkout
      - checkout

      # Download and cache depen에dencies, keys에 저장된 키로 캐시에 저장된 내용을 복사
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "build.gradle" }}

      - run:
          name: Downloading Dependencies
          command: ./gradlew dependencies

      - save_cache:
          paths:
            - ~/.gradle
          key: v1-dependencies-{{ checksum "build.gradle" }}  # build.gradle 파일에서 무언가가 변경 될 때마다 캐시가 재생성되며,이 프로젝트의 다른 브랜치는 동일한 캐시 키를 생성합니다.

      - run: ./gradlew clean build

      - run:
          name: before_deploy
          command: |
            zip -r springboot2-webservice *
            mkdir -p deploy
            mv springboot2-webservice.zip deploy/springboot2-webservice.zip

      - persist_to_workspace:
          root: .
          paths: deploy

  deploy:
    executor: my-executor

    steps:
      - attach_workspace:
          at: .

      - aws-s3/copy:
          from: deploy/*
          to: 's3://hwany-springboot-build'
          aws-region: AWS_DEFAULT_REGION

      - aws-code-deploy/deploy-bundle:
          application-name: hwany-springboot2-webservice
          deployment-group: hwany-springboot2-webservice-group
          deployment-config: CodeDeployDefault.AllAtOnce
          bundle-bucket:  hwany-springboot-build
          bundle-key: springboot2-webservice # 확장자 제거해야 함
          bundle-type: zip # default



workflows:
  version: 2.1

  build-deploy:
    jobs:
      - build
      - deploy:
          requires:
            - build
          filters:
            branches:
              only: master
합계 28
drwxrwxr-x 6 ec2-user ec2-user  171  126 22:41 .
drwxrwxr-x 3 ec2-user ec2-user   17  125 20:29 ..
-rw-r--r-- 1 root     root      442  126 22:41 README.md
-rw-r--r-- 1 root     root      374  126 22:41 appspec.yml
drwxr-xr-x 8 root     root       96  126 22:41 build
-rw-r--r-- 1 root     root     2327  126 22:41 build.gradle
drwxr-xr-x 3 root     root       21  126 22:41 gradle
-rwxr-xr-x 1 root     root     5305  126 22:41 gradlew
-rw-r--r-- 1 root     root     2269  126 22:41 gradlew.bat
-rw-r--r-- 1 root     root       45  126 22:41 settings.gradle
drwxr-xr-x 4 root     root       30  126 22:41 src

이렇게 해서 Circle CI와 S3, CodeDeploy가 연동이 완료되었습니다!




배포 자동화 구성

Circle CI, S3, CodeDeploy연동이 완료되었다면, 이것을 기반으로 실제로 Jar를 배포하여 실행까지 해보겠습니다.


1. deploy.sh파일 추가

먼저 step2환경에서 실행 될 deploy.sh를 생성하겠습니다.

step1까지는 서버에 저장하였지만 step2부터는 프로젝트에 있어야 합니다. Circle CI는 외부 서비스이니 EC2 서버에 직접 접근이 안됩니다. script 디렉토리를 만들고 deploy.sh를 포함시키면 됩니다.

step1의 deploy.sh와 다른 점은 Circle CI로 배포를 하면 다시 받아 빌드를 할 필요가 없기 때문에 git pull로 직접 빌드 했던 부분을 제거하였습니다.

#!/bin/bash

REPOSITORY = /home/ec2-user/app/step2
PROJECT_NAME = springboot2-webservice

echo "> Build 파일 복사"
cp $REPOSITORY/zip/build/libs/*.jar $REPOSITORY/

echo "> 현재 구동 중인 애플리케이션 pid 확인"

# 실행 중이면 종료하기 위해서 현재 수행 중인 프로세스id를 찾습니다.
# springboot2-webservice으로 된 다른 프로그램들이 있을 수 있어 springboot2-webservice된 jar 프로세스를 찾은 뒤 id를 찾습니다(awk '{print $1}').
CURRENT_PID = $(pgrep -fl springboot2-webservice | grep java | awk '{print $1}')

echo "현재 구동 중인 애플리케이션 pid: $CURRENT_PID"

if [ -z "$CURRENT_PID" ]; then
  ehco "> 현재 구동 중인 애플리케이션이 없으므로 종료하지 않습니다."
else
  echo "> kill -15 $CURRENT_PID"
  kill -15 $CURRENT_PID
  sleep 5
fi

echo "> 새 애플리케이션 배포"

JAR_NAME = $(ls -tr $REPOSITORY/*.jar | tail -n 1)

echo "> JAR name: JAR_NAME"
echo "> $JAR_NAME에 실행 권한 추가"
chmod +x $JAR_NAME

echo "> $JAR_NAME 실행"

nohup java -jar \
    -Dspring.config.location=classpath:/application.properties,classpath:/application-real.properties,/home/ec2-user/app/application-oauth.properties,/home/ec2-user/app/application-real-db.properties \
    -Dspring.profiles.active=real \
    $JAR_NAME > $REPOSITORY/nohup.out 2>&1 & # nohup실행 시 CodeDeploy는 무한 대기 합니다. 이 이슈를 해결하기 위해 nohub.out파일을 표준 입출력용으로 별도로 사용합니다. 이렇게 하지 않으면 nohup.out파일이 생기지 않고, CodeDeploy 로그에 표준 입출력이 출력됩니다. nohub이 끝나기 전까지 CodeDeploy도 끝나지 않으니 꼭 이렇게 해야합니다.



2. .circleci/config.yml 파일 수정

현재까지는 프로젝트의 모든 파일을 zip파일로 만드는데, 실제로 필요한 파일들은 Jar, appspec, yml, 배포 scipt들입니다.

...(생략)...
jobs:
  build:
  
      .....(생략).....

      - run:
          name: before_deploy
          command: |
            tar cvzf springboot2-webservice.tgz scripts/*.sh appspec.yml build/libs/*.jar

      - persist_to_workspace:
          root: .
          paths: .

  deploy:
    executor: my-executor

    steps:
      - attach_workspace:
          at: .

      - aws-s3/copy:
          from: springboot2-webservice.tgz
          to: 's3://hwany-springboot-build'
          aws-region: AWS_DEFAULT_REGION

      - aws-code-deploy/deploy-bundle:
          application-name: hwany-springboot2-webservice
          deployment-group: hwany-springboot2-webservice-group
          deployment-config: CodeDeployDefault.AllAtOnce
          bundle-bucket:  hwany-springboot-build
          bundle-key: springboot2-webservice # 확장자 제거해야 함
          bundle-type: tgz

workflows:
	.....(생략).....

Travis CI와 다르게 디렉토리없이 파일만 업로드 가능하기 때문에 파일만 업로드 하도록 수정하고, code-deploy부문에서 bundle-type은 tar.gz대신 tgz를 사용해야 동작합니다.



3. appspec.yml 파일 수정

appspec.yml 파일에 다음 코드를 추가합니다. location, timeout, runas의 들여쓰기를 주의해야 합니다. 들여쓰기가 잘못될 경우 배포가 실패합니다.

version: 0.0    # CodeDeploy 버전
os: linux
files:
  - source: /  # CodeDeploy에서 전달해 준 파일 중 destination으로 이동시킬 대상을 루트로 지정(전체파일)
    destination: /home/ec2-user/app/step2/zip/ # source에서 지정된 파일을 받을 위치, 이후 jar를 실행하는 등은 destination에서 옮긴 파일들로 진행
    overwrite: yes

permissions:  # CodeDeploy에서 EC2서버로 넘겨준 파일들을 모두 ec2-user권한을 갖도록 합니다.
  - object: /
    pattern: "**"
    owner: ec2-user
    group: ec2-user

hooks:  # CodeDeploy배포 단계에서 실행할 명령어를 지정합니다.
  ApplicationStart: # deploy.sh를 ec2-user권한으로 실행합니다.
    - location: scripts/deploy.sh
      timeout: 60 # 스크립트 실행 60초 이상 수행되면 실패가 됩니다.
      runas: ec2-user

여기까지 진행하면 배포에 성공한 것을 확인할 수 있습니다.





실제 배포 과정 체험

build.gradle에서 프로젝트 버전을 변경합니다.

version '1.0.1-SNAPSHOT'

변경된 내용을 확인할 수 있게 index.mustache의 내용을 바꾸고, push를 하면 아래와 같이 변경된 코드가 배포된 것을 확인할 수 있습니다.





CodeDeploy 로그 확인

CodeDeploy에 관한 내용은 대부분 /opt/codedeploy-agent/deployment-root에 있습니다.

/opt/codedeploy-agent/deployment-root/deployment-group-ID/deployment-ID/logs/scripts.log

이 곳의 로그를 확인하면 아래와 같이 확인할 수 있습니다.

2020-01-27 05:22:00 LifecycleEvent - ApplicationStart
2020-01-27 05:22:00 Script - scripts/deploy.sh
2020-01-27 05:22:00 [stdout]> Build 파일 복사
2020-01-27 05:22:00 [stdout]> 현재 구동 중인 애플리케이션 pid 확인
2020-01-27 05:22:00 [stdout]현재 구동 중인 애플리케이션 pid: 16658
2020-01-27 05:22:00 [stdout]> kill -15 16658
2020-01-27 05:22:05 [stdout]> 새 애플리케이션 배포
2020-01-27 05:22:05 [stdout]> JAR name: /home/ec2-user/app/step2/springboot2-webservice-1.0.1-SNAPSHOT.jar
2020-01-27 05:22:05 [stdout]> /home/ec2-user/app/step2/springboot2-webservice-1.0.1-SNAPSHOT.jar 에 실행 권한 추가
2020-01-27 05:22:05 [stdout]> /home/ec2-user/app/step2/springboot2-webservice-1.0.1-SNAPSHOT.jar 실행









*참고

https://docs.aws.amazon.com/ko_kr/codedeploy/latest/userguide/deployments-view-logs.html#deployments-view-logs-instance-unix

https://circleci.com/docs/2.0/workflows/#using-workspaces-to-share-data-among-jobs

1개의 댓글

comment-user-thumbnail
2022년 11월 21일

Very wonderful. Your post presented me with a wealth of information, allowing me to broaden my horizons. Thank you very much
Five Nights At Freddy's

답글 달기