[맛침반] Github Action을 이용한 CI⁄CD 환경 구축(3) - CD 구축

6720·2023년 12월 20일

프로젝트 맛침반

목록 보기
4/12

이번 목표

CI 구축에 이어서 CD 구축을 해보자!

준비물

해당 과정에서는 준비물이 많이 필요함.

  • AmazonEC2FullAccess 권한이 부여된 액세스 키 및 키 아이디
    • Github Action에게 임시 권한을 부여해야 하기 때문에 필요함.
    • 발급 참고 링크
    • 각 이름은 AWS_SECRET_ACCESS_KEY와 AWS_ACCESS_KEY_ID로 함.
  • Github Action의 SSH 접속을 위한 정보
    • EC2 서버 IP 주소
    • EC2 유저 이름
    • EC2 인스턴스 생성 시 발급받은 키(.pem)
      • .ppk -> .pem 변환하여 키의 텍스트 내용이 필요함.
      • .pem 파일은 읽을 수 있는 아스키코드로 이루어져 있음.
      • notepad, vscode 등등 편집기를 통해 읽기 가능
      • 변환 참고 링크
    • 각 이름은 AWS_SSH_HOST, AWS_SSH_USERNAME, AWS_SSH_KEY로 함.

준비물은 모두 Github Secrets에 넣어야 함.

진행 과정

CD yaml 작성

name: matchimban CI/CD with Gradle

# event trigger
on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

permissions:
  contents: read

jobs:
  build:

    runs-on: ubuntu-latest
    
    steps:
    # jdk 세팅
	# checkout: 현재 Repository 코드를 checkout 하여 runner 환경에 가져옴.
    - uses: actions/checkout@v3
    - name: Set up JDK 11
      uses: actions/setup-java@v3
      with:
        java-version: '11'
        distribution: 'temurin'

    # gradle로 build하기 위한 실행권한 부여
    - name: Grant execute permission for gradlew
      run: chmod +x gradlew

    # gradle caching: Cache workflow 실행 시간 개선 위해 dependencies와 build output을 캐싱
    - name: Gradle Caching
      uses: actions/cache@v3
      with:
        path: |
          ~/.gradle/caches
          ~/.gradle/wrapper
        key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
        restore-keys: |
          ${{ runner.os }}-gradle-

    # gradle build
    - name: Build with Gradle
      uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
      with:
        arguments: build

    # docker build
    - name: Docker build
      run: |
        docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} -p ${{ secrets.DOCKER_HUB_PASSWORD }} # id와 패스워드를 이용한 로그인
        docker build -t roovies/matchimban:${GITHUB_SHA::7} . 
        docker push roovies/matchimban:${GITHUB_SHA::7}

    # 해당 Workflow가 돌아가는 runner os의 ip 획득
	# EC2 SSH 인바운드 규칙에 임시적으로 허용하기 위해
    - name: Get Github Actions IP
      id: ip
      uses: haythem/public-ip@v1.2
        
    # AWS 인증 정보 구성
	# 트러블 슈팅#1
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1 # 해당 모듈은 aws 인증정보를 구성한다.
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} # 액세스 키
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} # 비밀 액세스 키
        aws-region: ap-northeast-2

    # EC2 인바운드 규칙에 Github Actions IP 보안그룹 추가
    # AWS_SG_ID는 EC2 인스턴스의 보안그룹 고유ID
    - name: Add Github Actions IP to Security group
      run: |
        aws ec2 authorize-security-group-ingress --group-id ${{ secrets.AWS_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32
        
    # SSH 연결 및 서버 배포
    # 만약 세션 매니저를 사용하고 싶다면 이 부분의 설정을 세션 매니저로 변경
    # 이미지 build 시 민감정보를 포함하는 게 아닌, 컨테이너화 할 때 런타임에 민감정보를 사용하여 유출 방지
	# 트러블 슈팅#2
	# 트러블 슈팅#3
    - name: SSH Connection and Deploy to Server
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.AWS_SSH_HOST }}
        username: ${{ secrets.AWS_SSH_USERNAME }}
        key: ${{ secrets.AWS_SSH_KEY }}
        script: |
          sudo docker rm -f server
          sudo docker pull roovies/matchimban:${GITHUB_SHA::7}
          sudo docker tag roovies/matchimban:${GITHUB_SHA::7} matchimban
          sudo docker run -d --name server \\
            -e TZ=Asia/Seoul \\
            -e MYSQL_ID=${{ secrets.MYSQL_ID }} \\
            -e MYSQL_PASSWORD=${{ secrets.MYSQL_PASSWORD }} \\
            -e RDS_URL=${{ secrets.RDS_URL }} \\
            -p 8080:8080 matchimban

    # 배포가 끝나면 EC2 인바운드 규칙에서 Github Actions IP 삭제
    - name: Remove Github Actions IP From Security Group
      run: |
        aws ec2 revoke-security-group-ingress --group-id ${{ secrets.AWS_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32

환경변수 모음

  • AWS_ACCESS_KEY_ID: AWS 액세스 키 아이디 (AmazonEC2FullAccess 포함)
  • AWS_SECRET_ACCESS_KEY: AWS 비밀 액세스 키 (AmazonEC2FullAccess 포함)
  • AWS_SG_ID: EC2의 보안 그룹 ID (sg-…)
  • AWS_SSH_HOST: EC2 서버 주소
  • AWS_SSH_KEY: SSH키 (.pem키, ---BEGIN~END~KEY--- 포함)
  • AWS_SSH_USERNAME: EC2 유저 이름 (ex: ec2-user)
  • DOCKER_HUB_PASSWORD: 도커 허브 패스워드
  • DOCKER_HUB_USERNAME: 도커 허브 유저 이름
  • MYSQL_ID: DB 아이디
  • MYSQL_PASSWORD: DB 패스워드
  • RDS_URL: RDS 엔드포인트 (쌍따옴표 필수)

트러블 슈팅#1 Credentials could not be loaded,

Configure AWS credentials Credentials could not be loaded, please check your action inputs: Could not load credentials from any providers

테스트 중 Configure credentials 단계에서 오류 발생

수정 전 코드

- name: Configure AWS credentials
  uses: aws-actions/configure-aws-credentials@v1
  with:
    aws-access-key-id: ${{ secrets.AWS_ACCESS_ID }}
    aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    aws-region: ap-northeast-2

수정 후 코드

- 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: ap-northeast-2

AWS_ACCESS_KEY_IDAWS_ACCESS_ID로 적혀있어서 발생한 오류였음.
오타 주의!

트러블 슈팅#2 i/o timeout

======CMD======
sudo docker rm -f server
sudo docker pull ***/***:${GITHUB_SHA::7}
sudo docker tag ***/***:${GITHUB_SHA::7} ***
sudo docker run -d --name server \
  -e TZ=Asia/Seoul \
  -e RDS_URL=*** \
  -e MYSQL_ID=*** \
  -e MYSQL_PASSWORD=*** \
  -p 8080:8080 ***
======END======
2023/12/06 08:41:21 dial tcp ***:22: i/o timeout

EC2에서 도커 이미지를 가져오는 과정 + 컨테이너화 시키는 과정에서 timeout 발생
timeout이라 함은 보통 EC2에 접속하지 못했을 때 많이 보는 오류임. (ex: sk 22번 포트..)
그렇다면 Github Action이 EC2에 제대로 접근하지 못하고 있다는 뜻.
-> Github Action이 EC2에 접근할 수 있는 권한을 추가한 후 컨테이너화가 끝나면 접근 삭제하도록 변경

추가 코드

# EC2 인바운드 규칙에 Github Actions IP 보안그룹 추가
- name: Add Github Actions IP to Security group
  run: |
		aws ec2 authorize-security-group-ingress --group-id ${{ secrets.AWS_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32

# 배포가 끝나면 EC2 인바운드 규칙에서 Github Actions IP 삭제
- name: Remove Github Actions IP From Security Group
	run: |
		aws ec2 revoke-security-group-ingress --group-id ${{ secrets.AWS_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32

트러블 슈팅#3 -e: command not found

======CMD======
sudo docker rm -f server
sudo docker pull ***/***:${GITHUB_SHA::7}
sudo docker tag ***/***:${GITHUB_SHA::7} ***
sudo docker run -d --name server \
  -e TZ=Asia/Seoul \
  -e RDS_URL=*** \
  -e MYSQL_ID=*** \
  -e MYSQL_PASSWORD=*** \
  -p 8080:8080 ***
======END======
out: server
err: invalid reference format
err: Error parsing reference: "***/***:" is not a valid repository/tag: invalid reference format
err: bash: line 6: -e: command not found
err: "docker run" requires at least 1 argument.
err: See 'docker run --help'.
e:  docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
err: Create and run a new container from an image
2023/12/06 09:15:23 Process exited with status 127

도커 이미지 배포 과정에서 오류 발생

직접 EC2에서 테스트했을 때는 제대로 작동이 됐기 때문에 명령어 자체에는 문제가 없음.
대신 RDS_URL를 문자열로 받는 것을 확인 -> Github Secrets에서 쌍따옴표 추가

CI 단계에서 수정한 부분

도커 환경변수 주입 과정 변경

전에는 도커 이미지를 만들 때 환경변수를 주입하는 코드를 작성하였음.
하지만 이 과정에서 이미지를 DockerHub에 업로드하는데, 도커 무료 버전을 사용하기 때문에 보안에 취약할 수 있다고 함. (유료 버전을 사용하면 어느정도 보완할 수 있다고 함.)

그래서 도커 이미지화할 때가 아닌, 도커 컨테이너화할 때 환경변수를 주입하기로 했음.
도커 컨테이너화는 EC2 내부에서 진행하기 때문에 EC2에 대한 정보를 유출하지 않는 이상 접근 방법이 없기 때문임.

수정 전 코드

    - name: Docker build
      run: |
        docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} -p ${{ secrets.DOCKER_HUB_PASSWORD }}
        docker build --build-arg MYSQL_ID="${{ secrets.MYSQL_ID }}" \
        --build-arg MYSQL_PASSWORD="${{ secrets.MYSQL_PASSWORD }}" \
        --build-arg RDS_URL="${{ secrets.RDS_URL }}" \
        -t roovies/matchimban:${GITHUB_SHA::7} .
        docker push roovies/matchimban:${GITHUB_SHA::7}
    
    - name: SSH Connection and Deploy to Server
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.AWS_SSH_HOST }}
        username: ${{ secrets.AWS_SSH_USERNAME }}
        key: ${{ secrets.AWS_SSH_KEY }}
        script: |
          sudo docker rm -f server
          sudo docker pull roovies/matchimban:${GITHUB_SHA::7}
          sudo docker tag roovies/matchimban:${GITHUB_SHA::7} matchimban
          sudo docker run -d --name server \\
            -e TZ=Asia/Seoul \\
            -p 8080:8080 matchimban

수정 후 코드

    - name: Docker build
      run: |
        docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} -p ${{ secrets.DOCKER_HUB_PASSWORD }}
        docker build -t roovies/matchimban:${GITHUB_SHA::7} . 
        docker push roovies/matchimban:${GITHUB_SHA::7}
    
    - name: SSH Connection and Deploy to Server
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.AWS_SSH_HOST }}
        username: ${{ secrets.AWS_SSH_USERNAME }}
        key: ${{ secrets.AWS_SSH_KEY }}
        script: |
          sudo docker rm -f server
          sudo docker pull roovies/matchimban:${GITHUB_SHA::7}
          sudo docker tag roovies/matchimban:${GITHUB_SHA::7} matchimban
          sudo docker run -d --name server \\
            -e TZ=Asia/Seoul \\
            -e MYSQL_ID=${{ secrets.MYSQL_ID }} \\
            -e MYSQL_PASSWORD=${{ secrets.MYSQL_PASSWORD }} \\
            -e RDS_URL=${{ secrets.RDS_URL }} \\
            -p 8080:8080 matchimban

Dockerfile ARG, ENV 예약어 삭제

Dockerfile의 경우 Docker build 단계에서 사용이 됨.
하지만 Docker build 단계에서 주입하던 환경변수가 컨테이너화 부분으로 이동했으므로 환경변수 주입 예약어가 필요 없어짐.

수정 전 코드

FROM openjdk:11

ARG MYSQL_ID=my_id \
	MYSQL_PASSWORD=my_password \
	RDS_URL=my_rds_url

ENV MYSQL_ID=${MYSQL_ID} \
    MYSQL_PASSWORD=${MYSQL_PASSWORD} \
    RDS_URL=${RDS_URL}

ARG JAR_FILE=build/libs/*.jar

COPY ${JAR_FILE} app.jar

ENTRYPOINT ["java", "-Dspring.profiles.active=server", "-jar", "/app.jar"]

수정 후 코드

FROM openjdk:11

ARG JAR_FILE=build/libs/*.jar

COPY ${JAR_FILE} app.jar

ENTRYPOINT ["java", "-Dspring.profiles.active=server", "-jar", "/app.jar"]
profile
뭐라도 하자

0개의 댓글