Github Actions로 AWS EC2에 자동 배포하기

    Github actions가 돌아가는 원리

    1. Github Action에서 프로젝트 빌드 후, jar 파일을 압축해서 AWS S3에 업로드
    2. Github Action이 CodeDeploy에게 S3에 있는 jar 파일을 배포하라고 전달
    3. CodeDeploy는 배포할 EC2 인스턴스 내부에 있는 CodeDeploy Agent에게 배포 명령을 내리고, Agent는 jar 파일을 S3에게 받아서 주어진 shell 스크립트에 따라 배포를 진행

     

    아래는 AWS, EC2 서버, 깃허브 액션 관련 설정 스텝이다

     

    참고 블로그 : https://bcp0109.tistory.com/363

     

    배포 가능한 EC2 서버가 있고, 깃허브에 배포할 레퍼지토리가 있다는 가정 하에 아래 설정을 진행

     

    1. EC2 인스턴스에 Tag 추가

    EC2에서 사용할 인스턴스 페이지 - 우측상단에 작업 - 인스턴스 설정 - 태그관리

    키 입력 후 저장

     

    2. IAM 역할 추가

    IAM 좌측에 엑세스 관리 - 역할 클릭 - 우측 상단 역할 만들기 - 

    AWS 서비스, EC2 선택 - 다음 - 권한 추가 AmazonS3FullAccess - 역할 이름 설정 후 생성 완료

     

    3. EC2 인스턴스에 IAM 역할 수정

    EC2에서 사용할 인스턴스 페이지 - 우측 상단에 작업 - 보안 - IAM 역할 수정

    2번에서 만든 IAM 역할 선택 후 저장

     

    4. EC2 서버에 CodeDeploy Agent 설치

    아래 aws 공식문서에서 해당하는 ubuntu 버전에 맞게 명령어를 입력하여 설치

    Ubuntu Server용 CodeDeploy 에이전트 설치

    ubuntu 가 아닌 Amazon Linux 또는 RHEL용 CodeDeploy 에이전트 설치는 아래 문서 참고

    Amazon Linux 또는 RHEL용 CodeDeploy 에이전트 설치

     

    ubuntu 22.04 에서는 위 방법으로 설치할 수 없다고 해서 Stackoverflow를 참고해서 아래 코드를 서버에 한 줄씩 입력하여 codedeploy를 설치했다.

     

    sudo apt-get install ruby-full ruby-webrick wget -y
    
    cd /tmp
    
    wget https://aws-codedeploy-us-east-1.s3.us-east-1.amazonaws.com/releases/codedeploy-agent_1.3.2-1902_all.deb
    
    mkdir codedeploy-agent_1.3.2-1902_ubuntu22
    
    dpkg-deb -R codedeploy-agent_1.3.2-1902_all.deb codedeploy-agent_1.3.2-1902_ubuntu22
    
    sed 's/Depends:.*/Depends:ruby3.0/' -i ./codedeploy-agent_1.3.2-1902_ubuntu22/DEBIAN/control
    
    dpkg-deb -b codedeploy-agent_1.3.2-1902_ubuntu22/
    
    sudo dpkg -i codedeploy-agent_1.3.2-1902_ubuntu22.deb
    
    sudo systemctl list-units --type=service | grep codedeploy
    
    sudo service codedeploy-agent status

    마지막 줄 sudo service codedeploy-agent status를 입력하면 초록색으로 active (running) 표시가 뜰 거다.

     

    5. S3 버킷 생성 (혹은 수정)

    버킷을 새로 생성하는 경우:

    S3 페이지 좌측 버킷 - 우측 버킷 만들기 

    - 버킷 이름 작성, AWS 리전: ap-northeast-2

    - 객체 소유권: ACL 비활성화됨(권장)

    - 모든 퍼블릭 엑세스 차단

    - 버킷 버전 관리 비활성화

    - 기본 암호화 비활성화

    버킷 만들기

     

    버킷 수정:

    해당 버킷 클릭 - 상단에 권한 클릭 - 퍼블릭 액세스 차단(버킷 설정) 편집 - 모든 퍼블릭 액세스 차단 선택 후 변경 사항 저장 - 객체 소유권 편집 - ACL 비활성화됨(권장) 선택 후 변경 사항 저장

     

    6. CodeDeploy 전용 IAM 역할 만들기

    IAM 좌측에 엑세스 관리 - 역할 클릭 - 우측 상단 역할 만들기 -

    AWS 서비스 선택

    다른 AWS 서비스 사용 사례: CodeDeploy 선택

    - 다음 -

    권한 추가 AWSCodeDeployRole - 역할 이름 설정 후 생성 완료

     

    7. CodeDeploy 애플리케이션, 배포 그룹 생성

    CodeDeploy 페이지 좌측 배포(CodeDeploy) - 애플리케이션 - 애플리케이션 생성

    - 애플리케이션 이름 입력, 컴퓨팅 플랫폼 EC2/온프레미스 선택 - 애플리케이션 생성

     

    CodeDeploy 페이지 좌측 배포(CodeDeploy) - 애플리케이션 - 위에서 생성한 애플리케이션 이름 클릭 - 배포 그룹 생성

    - 배포 그룹 이름 입력

    - 서비스 역할: 6번에서 만든 IAM 역할 선택

    - 배포 유형: 현재 위치 선택 - 

    - 환경 구성: Amazon EC2 인스턴스 선택

    - 태그 그룹 1 키:  1번에서 생성한 태그 키 선택

    - AWS CodeDeploy 에이전트 설치: 한 번만 선택

    - 배포 설정 CodeDeployDefault.AllAtOnce

    - 로드 밸런서: 로드 밸런싱 활성 체크 표시 없애기

    배포 그룹 생성

     

    8. Github Actions 에서 사용할 IAM 사용자 추가 (혹은 권한 추가)

    IAM 사용자를 새로 추가 시 

    IAM 좌측에 사용자 - 우측 사용자 추가 - 사용자 이름 입력, AWS 자격 증명 유형 선택: 액세스 키 - 프로그래밍 방식 액세스

    권한 설정 - 기존 정책 직접 연결 AWSCodeDeployFullAccess, AmazonS3FullAccess

    액세스 키 id, 비밀 액세스 키 저장

     

    기존 IAM 사용자에 권한 추가 시 

    IAM 좌측에 사용자 - 사용자 이름 클릭 - 권한 추가 - 기존 정책 직접 연결 AWSCodeDeployFullAccess, AmazonS3FullAccess - 다음

     

    9. AWS CodeDeploy 배포 과정에서 환경변수 사용

    코드에서 민감한 정보를 yaml 혹은 properties 파일에 적고 gitignore로 해당 파일을 제외하고 git에 올리면 로컬에서는 잘 작동하나 github action으로 배포할 때는 해당 값을 찾을 수가 없어 실행이 되지 않는다. 

    인텔리제이에서 환경 변수 설정을 해줘도 codedeploy 배포 시 해당 환경 변수를 찾을 수 없다고 뜬다. 

     

    다양한 해결 방법이 있었으나 내가 찾은 가장 간단한 방법은 aws parameter store를 이용하는 방법이다.

    (암호화만 하지 않으면 무료)

     

     

    9.1 AWS 파라미터 스토어에 파라미터 저장

     

    AWS System Manager 페이지 - 좌측 애플리케이션 관리 - 파라미터 스토어 - 파라미터 생성

    - 이름 설정

    이름에 /EC2/accesskey 이런 식으로 보통 '/' 로 구분하여 이름을 설정한다.

    /aws/accesskey로 설정하려 했으나 /aws 경로로로는 파라미터가 생성이 되지 않아 이름을 accesskey로만 설정했다.

    - 계층 : 표준

    - 유형: 문자열

    - 값: 해당 값 (IAM의 accesskey, secretkey 등)

     

    9.2 설정 파일에 환경 변수 설정

     

    yaml 혹은 properties 파일에 아래와 같이 ${} 로 변경

    뒤에 shell 스크립트를 작성할 때 Parameter store에 있는 값을 가져와 환경변수를 지정해주는 코드를 넣기 때문에 ${} 안에 있는 환경변수명은 현재 아무렇게 적어도 무방하다. 

     

    # AWS Account Credentials
    cloud.aws.credentials.accessKey=${AWS_ACCESS_KEY_ID}
    cloud.aws.credentials.secretKey=${AWS_SECRET_ACCESS_KEY}

     

    9.3 EC2 서버에서 AWS 자격 증명

     

    AWS Parameter store에서 환경변수를 가져오려면 aws 자격 증명을 해야 한다.

     

    EC2 서버에서 아래 명령어를 입력한다.

     

    sudo apt-get install awscli
    aws configure
    AWS Access Key ID [None]:
    AWS Secret Access Key [None]:
    Default region name [None]: ap-northeast-2
    Default output format [None]: json

     

    access key와 secret access key는 8번에서 만든 IAM 키를 넣으면 된다.

     

    10. Github Repository에 Secrets 추가

    배포할 레파지토리 상단 Settings - 좌측 하단 Secrets - Actions - 우측 상단 New repository secret

    키와 값을 입력

    AWS_ACCESS_KEY_ID : IAM access 키 값

    AWS_SECRET_ACCESS_KEY :  IAM secret 키 값

    BUCKET_NAME : 버킷 이름 (5번에서 만든 jar 파일 업로드할 s3 버킷)

     

    ※ 9.2번에 환경 변수 설정과는 무관하다.

     

     

    11. AppSpec 파일 작성

    CodeDeploy에서 배포하기 위해 appspec.yml을 참조한다. (확장자를 .yaml로 하면 CodeDeploy가 못 찾는다고 한다)

    AppSpec 파일 관련해서는 AWS 공식문서에 자세히 나와 있다.

     

    files:
      - source:  /
        destination: /home/ubuntu/app
        overwrite: yes
    
    permissions:
      - object: /
        pattern: "**"
        owner: ubuntu
        group: ubuntu
    
    hooks:
      AfterInstall:
        - location: scripts/stop.sh
          timeout: 100
          runas: ubuntu
      ApplicationStart:
        - location: scripts/start.sh
          timeout: 100
          runas: ubuntu

     

    appspec.yml 이름으로 위와 같은 파일을 만들고 레퍼지토리 폴더 가장 상위 디렉토리에 저장했다.

     

    files:
      - source:  /
        destination: /home/ubuntu/app
        overwrite: yes

    files

    'files' 섹션은 EC2/온프레미스 배포만 해당 (7번에서 CodeDeploy 배포 그룹 생성 시 한 설정)
    'resources' 섹션은 Amazon ECS 및 AWS Lambda 배포만 해당

     

    source

    - 인스턴스에 복사할 수정의 파일 또는 디렉터리를 식별

    - source가 파일을 나타내는 경우 지정한 파일만 인스턴스에 복사됨

    - source가 슬래시 하나인 경우 수정 버전의 모든 파일이 인스턴스에 복사됨

     

    destination: 인스턴스에서 파일이 복사되는 위치

     

    overwrite: 복사할 위치에 파일이 있는 경우 대체

     

    permissions:
      - object: /
        pattern: "**"
        owner: ubuntu
        group: ubuntu

     

    permissions

    files 섹션에서 정의한 파일이 인스턴스에 복사된 후 해당 파일에 대한 권한 설정 (EC2/온프레미스 배포만 해당)

     

    object: 권한이 지정되는 파일 또는 디렉터리


    pattern (optional): 매칭 되는 패턴에만 권한 부여


    owner (optional): object 의 소유자


    group (optional): object 의 그룹 이름

     

    ※ Ubuntu AMI 서버만 default username 이 ubuntu이며 AMI에 따라 ec2-user, root 등 여러 default username이 있다.

     

    hooks:
      AfterInstall:
        - location: scripts/stop.sh
          timeout: 100
          runas: ubuntu
      ApplicationStart:
        - location: scripts/start.sh
          timeout: 100
          runas: ubuntu

     

    hooks

     

    EC2/온프레미스 배포에 대한 'hooks' 섹션에는 배포 수명 주기 이벤트 후크를 하나 이상의 스크립트에 연결하는 매핑이 포함된다. 

    배포 이후에 수행할 스크립트를 지정한다는 뜻이다. 

     

    location: hooks 에서 실행할 스크립트 위치

    timeout (optional): 스크립트 실행에 허용되는 최대 시간이며, 넘으면 배포 실패로 간주됨

    runas (optional): 스크립트를 실행하는 사용자

     

    12. 배포 스크립트 작성

    AppSpec에서 hooks 세션에 작성한 stop.sh, start.sh를 작성

     

    레파지토리에 scripts 디렉토리를 만들고 안에 stop.sh, start.sh 셸 스크립트작성

     

    stop.sh

    #!/usr/bin/env bash
    
    PROJECT_ROOT="/home/ubuntu/app"
    JAR_FILE="$PROJECT_ROOT/spring-webapp.jar"
    
    DEPLOY_LOG="$PROJECT_ROOT/deploy.log"
    
    TIME_NOW=$(date +%c)
    
    # 현재 구동 중인 애플리케이션 pid 확인
    CURRENT_PID=$(pgrep -f $JAR_FILE)
    
    # 프로세스가 켜져 있으면 종료
    if [ -z $CURRENT_PID ]; then
      echo "$TIME_NOW > 현재 실행중인 애플리케이션이 없습니다" >> $DEPLOY_LOG
    else
      echo "$TIME_NOW > 실행중인 $CURRENT_PID 애플리케이션 종료 " >> $DEPLOY_LOG
      kill -15 $CURRENT_PID
    fi

    애플리케이션이 떠있으면 종료하는 스크립트

    주의할 점은 최상단에 '#!'는 주석이 아니라 하나의 기호이다.

    기타 셸 스크립트 문법 관련 설명은 생략하겠다. Shell Script 참고 블로그

     

    start.sh

    #!/usr/bin/env bash
    
    PROJECT_ROOT="/home/ubuntu/app"
    JAR_FILE="$PROJECT_ROOT/spring-webapp.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
    
    echo "> $JAR_NAME에 실행권한 추가"
    chmod +x $JAR_NAME
    
    export AWS_ACCESS_KEY_ID=$(aws ssm get-parameters --region ap-northeast-2 --names accesskey --query Parameters[0].Value | sed 's/"//g')
    export AWS_SECRET_ACCESS_KEY=$(aws ssm get-parameters --region ap-northeast-2 --names secretkey --query Parameters[0].Value | sed 's/"//g')
    
    # jar 파일 실행
    echo "$TIME_NOW > $JAR_FILE 파일 실행" >> $DEPLOY_LOG
    nohup java -jar $JAR_FILE > $APP_LOG 2> $ERROR_LOG &
    
    CURRENT_PID=$(pgrep -f $JAR_FILE)
    echo "$TIME_NOW > 실행된 프로세스 아이디 $CURRENT_PID 입니다." >> $DEPLOY_LOG
    

     

     

    애플리케이션을 실행하는 스크립트. 주석과 echo 부분을 보면 흐름은 이해될 것

     

    export AWS_ACCESS_KEY_ID=$(aws ssm get-parameters --region ap-northeast-2 --names accesskey --query Parameters[0].Value | sed 's/"//g')
    export AWS_SECRET_ACCESS_KEY=$(aws ssm get-parameters --region ap-northeast-2 --names secretkey --query Parameters[0].Value | sed 's/"//g')

    이 부분은 위에 9번 Aws parameter store에 저장한 환경변수를 가져오는 코드다.

     

    아래 형식으로 환경변수명과, AWS region명, 파라미터 Store에서 정한 변수명을 바꿔서 사용하면 된다.

    export 환경변수명=$(aws ssm get-parameters --region AWS region명 --names 파라미터 Store에서 정한 변수명 --query Parameters[0].Value | sed 's/"//g')

     

     

    13. build.gradle 파일 수정

    # build 파일 복사
    echo "$TIME_NOW > $JAR_FILE 파일 복사" >> $DEPLOY_LOG
    cp $PROJECT_ROOT/build/libs/*.jar $JAR_FILE

    12번 start.sh 파일을 보면 /build/libs/*.jar 파일을 복사해서 실행하는데

    Spring Boot 2.5 버전부터 빌드 시 -plain.jar와 일반 .jar 파일이 두 개 만들어지기 때문에 빌드 시 plain jar 파일은 만들어지지 않도록 해야 한다.

     

    따라서 build.gradle 에 아래 코드를 추가해야 한다.

    jar {
        enabled = false
    }

     

    14. Github Actions Workflow yml 파일 작성

    다시 깃허브 레퍼지토리로 돌아와서 상단 Actions를 클릭하면 선택할 수 있는 여러 템플릿이 있는데 

    일단 상단에 'set up a aworkflow yourself ->' 를 클릭한다.

    그러면 자동으로 레퍼지토리명/.github/workflows/main.yml 파일을 작성할 수 있게 된다.

     

    name: Deploy to Amazon EC2
    
    on:
      push:
        branches:
          - main
    
    env:
      AWS_REGION: ap-northeast-2
      S3_BUCKET_NAME: 버킷 이름
      CODE_DEPLOY_APPLICATION_NAME: CodeDeploy 앱 이름
      CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: CodeDeploy 배포 그룹 이름
    
    permissions:
      contents: read
    
    jobs:
      deploy:
        name: Deploy
        runs-on: ubuntu-latest
        environment: production
    
        steps:
        # (1) 기본 체크아웃
        - name: Checkout
          uses: actions/checkout@v3
    
        # (2) JDK 11 세팅
        - name: Set up JDK 11
          uses: actions/setup-java@v3
          with:
            distribution: 'temurin'
            java-version: '11'
            
        - name: Run chmod to make gradlew executable
          run: chmod +x ./gradlew
    
        # (3) Gradle build (Test 제외)
        - name: Build with Gradle
          uses: gradle/gradle-build-action@0d13054264b0bb894ded474f08ebb30921341cee
          with:
            arguments: clean build -x test
    
        # (4) AWS 인증 (IAM 사용자 Access Key, Secret Key 활용)
        - 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 }}
    
        # (5) 빌드 결과물을 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 .
    
        # (6) 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

     

    위 코드는 참고 블로그에서 가져왔다.

    env 부분에 버킷 이름, CodeDeploy 앱, 배포 그룹 이름만 바꿔서 작성하면 된다. 

     

    - name: Run chmod to make gradlew executable
      run: chmod +x ./gradlew

     

    (2) 번과 (3) 번 사이에 있는 코드를 안 넣었을 때는 ./gradlew: Permission denied 가 뜨며 build가 실패했다.

    ./gradlew 실행 권한이 없다는 뜻이므로

    chomod +x 명령어로 권한을 부여했다.

     

    위 코드를 작성하면 오른쪽에 Start commit을 누르면 실행이 된다. 실행 과정은 Actions에서 볼 수 있다.

     

    이제 해당 repository 에 push만 하면 자동으로 actions가 실행된다.

     

    15. 실행 확인

    CodeDeploy

    AWS CodeDeploy 페이지 좌측 배포(CodeDeploy) - 배포 배포 내역을 볼 수 있다. 배포 ID를 클릭하고 최하단에 배포 수명 주기 이벤트 부분에 View events를 보면 실행 과정이 나온다.

     

    S3

    S3 버킷에서도 zip 파일이 들어간 것을 확인할 수 있다.

     

    EC2

    EC2 서버에 접속하여 ps -ef | grep java를 입력하면 파일이 실행 중인지 볼 수 있으며 직접 ec2 서버를 켜서 확인할 수 있다.

     


    배포를 진행한 내 깃허브 레퍼지토리

    https://github.com/Suyoung225/myFolder

     

    GitHub - Suyoung225/myFolder: mini Project refactoring

    mini Project refactoring. Contribute to Suyoung225/myFolder development by creating an account on GitHub.

    github.com

     

    728x90

    댓글