GitHub Actions
GitHub Actions를 사용하면 저장소에서 바로 소프트웨어 개발 워크플로를 자동화하고 사용자 지정 및 실행할 수 있습니다.
CI/CD를 포함하여 원하는 작업을 수행하기 위한 작업을 검색, 생성 및 공유하고 완전히 사용자 정의된 워크플로에서 작업을 결합할 수 있습니다.
CI(Continuous Integration) / CD(Continuous Delivery / Deployment)
CI는 지속적인 통합(Continuous Integration)으로 핵심은 소프트웨어 개발 프로젝트가 분석, 설계, 코드, 빌드, 테스트, 배포 풀 사이클을 작은 주기로 진행해서 빠르게 자동화시켜 개선시키는 것입니다. 즉, CI는 코드, 빌드, 테스트를 자동화해서 조금 더 잦은 주기로 생명 주기를 짧게 하는 것입니다.
CD는 지속적인 제공(Continuous Delivery) / 지속적인 배포(Continuous Deployment)로 두 개의 콘셉트가 존재하는데 크게 다르지 않습니다. 지속적인 제공은 코드, 빌드, 테스트, 배포를 개발 환경에서 자동화하고 운영 환경에 배포할 때 수동으로 진행하는 콘셉트입니다. 지속적인 배포는 지속적인 제공과 조금 다른 부분이 코드부터 운영 환경까지 한 번에 자동화로 진행하는 것을 얘기합니다.
Actions 구성 요소
Workflows
워크플로는 하나 이상의 작업을 실행하는 구성 가능한 자동화 프로세스입니다.
저장소의 이벤트에 의해 트리거 되면 실행되고 수동, 혹은 정의된 일정(cron)에 따라 트리거 될 수 있습니다.
Event
이벤트는 워크플로 실행을 트리거하는 저장소의 특정 활동입니다.
GitHub에 풀 리퀘스트, 이슈를 생성하거나 Commit, Push 등과 같은 이벤트를 의미합니다.
워크플로를 트리거하는 이벤트에 관해 다음의 주소에서 참조하세요.
워크플로를 트리거하는 이벤트
Jobs
작업은 하나 이상의 Step이 존재하고 각 단계는 실행될 셸 스크립트이거나 실행될 동작의 단위입니다.
작업은 기본적으로 병렬적으로 수행되며, 종속을 통해 순차적으로도 가능합니다.
Actions
액션은 "작은 자동화 프로그램"이라고 생각하면 됩니다. 자주 반복되고, 복잡한 작업들을 미리 만들어진 액션을 사용해서 워크플로를 간단하게 만들 수 있습니다. 예를 들면, GitHub 저장소에서 프로젝트 코드를 가져오는 작업을 한다고 했을 때 수행할 내용들을 일일이 작성하지 않고 actions/checkout@v4로 미리 만들어진 액션을 가져다 사용할 수 있습니다.
내가 필요한 기능을 직접 만들어서 액션으로 등록할 수도 있고 다른 사람들이 만들어 놓은 액션을 GitHub Marketplace에서 검색해서 사용할 수도 있습니다.
Runners
러너는 트리거 될 때 워크플로를 실행하는 서버로 가상 머신에서 실행됩니다. 각 러너는 한 번에 하나의 작업만 실행할 수 있고, Ubuntu Linux, Microsoft Windows, MacOS 러너를 제공합니다.
Actions 예제
Workflow 생성
GitHub Repository에서 Actions 들어가서 "set up a workflow yourself" 클릭하면 .github/workflows/main.yml"로 만들어줍니다.
다른 방법으로는 IDE에서 프로젝트 하위에 .github/workflows 폴더를 생성하고 yml 파일을 생성하고 작성합니다. 주의할 점은 폴더명이 완전히 일치해야 합니다. 실수로 workflow로 한다거나 하면 Actions 수행이 안됩니다.
PR 시 Test를 수행하고 성공해야 Merge 할 수 있는 예제
"dev" 브랜치에 Pull Request를 했을 때 이벤트가 발생하도록 했고 Settings에서 Role 설정으로 Actions 수행이 실패하면 Merge 버튼이 비활성화되도록 했습니다.
프로젝트에서 AWS S3를 사용하고 있어서 로컬에서는 Docker에 Localstack을 사용하고 있어서 설치하고 S3를 만드는 작업도 있습니다. gradle 명령어로 test를 실행하고 test 결과를 PR Comment에 작성해 주는 것과 실패 시 실패 원인을 작성하는 작업도 추가했습니다.
name: PR Validation
on:
pull_request:
branches: ['dev']
jobs:
pr-test:
runs-on: ubuntu-latest
permissions:
contents: read
checks: write
pull-requests: write
steps:
- name: Git clone
uses: actions/checkout@v4
- name: Start LocalStack
uses: LocalStack/setup-localstack@v0.2.4
with:
image-tag: 'latest'
install-awslocal: 'true'
configuration: DEBUG=1
env:
ACTIVATE_PRO: 0
LOCALSTACK_API_KEY: 'test'
- name: Run Tests against LocalStack
run: |
awslocal s3 mb s3://pinup
awslocal s3 ls
echo "Test Execution complete!"
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: gradle
- name: Grant execute permission
run: chmod +x ./gradlew
- name: Run Tests
run: ./gradlew test
- name: Test result add PR comment
uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
files: '**/build/test-results/test/TEST-*.xml'
- name: Test fail, fail code line add Check comment
uses: mikepenz/action-junit-report@v5
if: always()
with:
report_paths: '**/build/test-results/test/TEST-*.xml'
token: ${{ github.token }}
Actions만 작성해서는 PR Merge 버튼이 비활성화가 되지 않습니다.
Settings > Rules > Rulesets > Branch rules > Require status checks to pass
"Status checks that are required"에 job의 이름으로 추가해줘야 합니다.
저는 jobs에 "pr-test"명으로 job을 만들어서 pr-test를 추가했습니다.
AWS EC2에 Docker Hub로 Deploy 예제
aws의 access key, secret key, 개인 정보 등 보안상 보이면 안 되는 값들은 GitHub의 Secrets and Variables 통해 사용하면 값이 보이지 않습니다.
Settings > secrets and variables > Actions > Repository secrets
application property로 사용하는 것도 secrets에 전체 복사해서 값을 추가해서 사용할 수 있습니다. ${{ secrets.지정한 Name }} 으로 사용하면 됩니다.
ec2에 접근하기 위해 ssh actions을 사용하고 있는데 ssh port 22를 사용하려면 aws security에 접근자의 ip를 허용해야 하기 때문에 GitHub의 ip를 얻어서 security에 등록하고 마지막에 다시 제거하도록 했습니다.
name: Deploy to EC2
on:
release:
types: [published]
env:
AWS_REGION: 'ap-northeast-2'
DOCKER_HUB_REGISTRY: 'testxxboy/actions-test'
jobs:
build:
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- name: Git clone
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: gradle
- name: Set up application & prod yml
run: |
echo "${{ secrets.APPLICATION }}" > ./src/main/resources/application.yml
- name: Grant execute permission
run: chmod +x ./gradlew
- name: Run clean gradle
run: ./gradlew clean
- name: Run build gradle
run: ./gradlew -Pprod build
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract version from tag
id: extract_version
run: |
FILENAME=$(basename build/libs/demo-*.jar)
VERSION=$(echo "$FILENAME" | sed -E 's/demo-([0-9.]+)\.jar/\1/')
echo "Extracted version: $VERSION"
echo "version=v$VERSION" >> $GITHUB_OUTPUT
- name: Build and push Docker image
id: push_image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: testxxboy/actions-test:${{ steps.extract_version.outputs.version }}
labels: version=${{ steps.extract_version.outputs.version }}
- name: Get GitHub IP
id: ip
run: |
echo "ipv4=$(curl -s https://api.ipify.org)" >> $GITHUB_OUTPUT
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Add GitHub IP to AWS 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
- name: Connect to EC2 & Execute Application
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
port: ${{ secrets.EC2_SSH_PORT }}
envs: DOCKER_IMAGE
script: |
CONTAINER_NAME=pinup
DOCKER_IMAGE=${{ env.DOCKER_HUB_REGISTRY }}:${{ steps.extract_version.outputs.version }}
if [ $(sudo docker ps -q -f name=^/${CONTAINER_NAME}$) ]; then
sudo docker stop $CONTAINER_NAME
sudo docker rm $CONTAINER_NAME
fi
docker images --format "{{.Repository}}:{{.Tag}} {{.ID}}" | grep "^${{ env.DOCKER_HUB_REGISTRY }}:" | while read repo_tag image_id
do
echo "Removing image: $repo_tag (ID: $image_id)"
docker rmi -f "$image_id"
done
docker pull $DOCKER_IMAGE
docker run -d --name pinup -p 8080:8080 $DOCKER_IMAGE
- name: Remove 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
🔗 Reference
'devOps' 카테고리의 다른 글
Nginx Let's Encrypt SSL 적용 (0) | 2025.03.13 |
---|---|
AWS EC2 Amazon Linux 2023 Nginx 설치 (0) | 2025.03.10 |
AWS EC2 Ubuntu Nginx 설치 (0) | 2025.03.01 |
Spring 프로젝트 AWS EC2 Docker 배포 (0) | 2025.02.05 |
Spring Boot 프로젝트 AWS EC2 배포하기 (1) | 2025.02.02 |