CI 구축에 이어서 CD 구축을 해보자!
해당 과정에서는 준비물이 많이 필요함.
준비물은 모두 Github Secrets에 넣어야 함.
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 엔드포인트 (쌍따옴표 필수)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_ID가 AWS_ACCESS_ID로 적혀있어서 발생한 오류였음.
오타 주의!
======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
======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에서 쌍따옴표 추가
전에는 도커 이미지를 만들 때 환경변수를 주입하는 코드를 작성하였음.
하지만 이 과정에서 이미지를 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의 경우 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"]