레벨 3 프로젝트를 시작하고 팀 내에서 CI / CD 구축을 담당했습니다.
그 중에서도 저는 CI 에 집중해서, 해당 부분에 대해 기록을 남깁니다.
CI는 개발자들이 자주 코드 변경 사항을 통합하고, 자동화된 빌드와 테스트를 통해 이러한 변경 사항이 문제 없이 작동하는지 확인하는 프로세스입니다.
CI 실행을 지원하기 위한 툴은 Github Actions 이외에도 Jenkins, CircleCI 등 많습니다.
저희 팀은 Github Actions 를 사용하기로 했는데요!
저는 처음이지만 이미 사용법을 알고 있는 팀원이 있어 러닝커브를 줄일 수 있겠다는 이유였습니다.
Github Actions 는 이름에서 알 수 있듯이 github 에서 제공하는 자동화 툴 입니다.
구성 요소 중 제가 사용한 구성 요소와 속성을 위주로 설명합니다.
이벤트가 트리거될 때 실행되는 자동화된 프로세스 == 실행할 스크립트 파일
.github/workflows
디렉토리의 YAML 파일로 위치해야 합니다.name
: 워크플로우의 이름
on
: 워크플로우를 트리거하는 이벤트 == 언제 실행되게 할 건지 설정합니다.
push
, pull_request
, schedule
등jobs
: 워크플로우 내에서 실행되는 잡들의 정의 == 어떤 작업이 실행되게 할 것인지 설정합니다.
name: BACKEND_CI
on:
pull_request:
types: [ opened, reopened, synchronize ]
branches: [ 'main' ]
# 현재 main 브랜치를 대상으로 PR이 열릴 때, 다시 열릴 때, PR의 소스 브랜치에 새로운 커밋이 푸시될 때 수행됨
잡은 여러 스텝(step)들로 구성됩니다.
ex) BE_CI 라는 jobs 이 있고, java 버전 확인, gradle 빌드 확인 등의 step(개별 작업)이 있는 형태
아래 이미지는 현재 총 3개의 jobs 로 이루어진 CI 형태
name
: 잡의 이름runs-on
: 잡이 실행될 환경 == job 을 실행하는 컴퓨터(github runner)ubuntu-latest
, windows-latest
, macos-latest
등needs
: 다른 잡이 완료된 후에 실행되도록 설정 == 설정해주지 않으면 각각의 잡은 기본적으로 병렬 실행됩니다.if
: 조건부 실행steps
: 잡 내에서 순차적으로 실행되는 스텝들 BE_CI: # name
runs-on: ubuntu-latest # ubuntu 환경에서 잡 실행
needs: PATH_CHANGES # PATH_CHANGES 라는 job 이 실행된 후에 해당 잡을 실행
if: ${{ needs.PATH_CHANGES.outputs.backend == 'true' }}
# PATH_CHANGES 의 outputs 이 true 일때만 실행
steps:
...
잡 내에서 순차적으로 실행되는 개별 작업
name
: 스텝의 이름uses
: 재사용 가능한 액션을 호출할 때 사용 == 특정 작업을 수행하기 위해 미리 정의된 기능이나 도구를 가져다 사용합니다. 다른 사람이 작성한 액션을 가져와서 사용할 수 있습니다.with
: 액션에 전달할 인자들run
: 쉘 명령어 또는 스크립트를 실행할 때 사용 steps:
- uses: actions/checkout@v4 # 레포지토리의 코드를 체크아웃하는 작업을 수행
with: # 현재 PR의 소스 브랜치 이름을 가져옴
ref: ${{ github.event.pull_request.head.ref }}
- name: Setup Java # 자바 버전 확인
uses: actions/setup-java@v4
with:
distribution: 'corretto'
java-version: '21'
- name: Setup Gradle # gradle 세팅
uses: gradle/actions/setup-gradle@v3
- name: Test with Gradle # gradle test
continue-on-error: true
id: gradle_test
run: |
cd backend
./gradlew test
CI 스크립트 개선 작업을 틈틈이 진행해 온 과정을 공유합니다.
초기 작성했던 스크립트의 문제점은 다음과 같습니다.
server-id: github
settings-path: ${{ github.workspace }}
name: Gradle Package
on:
pull_request:
types: [ opened, reopened, synchronize ]
branches: [ 'main' ]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
actions: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
- name: Early exit
if: ${{ !contains(github.event.pull_request.labels.*.name, '백엔드') }}
run: |
gh run cancel ${{ github.run_id }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/setup-java@v4
with:
distribution: 'corretto'
java-version: '21'
server-id: github
settings-path: ${{ github.workspace }}
- name: Setup Gradle
run: chmod +x ./backend/gradlew
- name: Build with Gradle
continue-on-error: true
id: gradle_build
run: |
cd backend
./gradlew build
- name: Get teamMember ListGet teamMember List
id: teamMembers
uses: actions/github-script@v6
with:
script: |
const fs = require('fs');
const workers = JSON.parse(fs.readFileSync('.github/workflows/teamMember.json'));
const mention = context.payload.pull_request.assignees.map((user) => {
const login = user.login;
const mappedValue = workers[login];
return mappedValue ? `<@${mappedValue}>` : `No mapping found for ${login}`;
})
return mention.join(', ');
- name: slack mention
uses: slackapi/slack-github-action@v1.24.0
with:
channel-id: ${{ secrets.ISSUE_CHANNEL }}
payload: |
{
"text": "pr 테스트 결과",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "pr 테스트 ${{ steps.gradle_build.outcome}} \n • 링크: <${{ github.event.pull_request.html_url }}|${{ github.event.pull_request.title }}> \n • pr 담당자: \${{ steps.teamMembers.outputs.result }}
}
}
]
}
env:
SLACK_BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
개선 작업은 며칠에 걸쳐 조금씩 수행했습니다.
개선된 사항은 다음과 같습니다.
PATH_CHANGES
, BE_CI
, BE_SLACK_MESSAGE
if: ${{ needs.PATH_CHANGES.outputs.backend == 'true' }}
조건으로 cancel 이 아닌 Success 판정 (아래 이미지는 프론트엔드 작업의 actions 수행 결과입니다.) - 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/actions/setup-gradle@v3
) 를 사용하는 방법name: BACKEND_CI
on:
pull_request:
types: [ opened, reopened, synchronize ]
branches: [ 'main' ]
env:
...
jobs:
PATH_CHANGES:
runs-on: ubuntu-latest
permissions:
pull-requests: read
outputs:
backend: ${{ steps.changes.outputs.backend }}
steps:
- uses: dorny/paths-filter@v3
id: changes
with:
filters: |
backend:
- 'backend/**'
BE_CI:
runs-on: ubuntu-latest
needs: PATH_CHANGES
if: ${{ needs.PATH_CHANGES.outputs.backend == 'true' }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'corretto'
java-version: '21'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
- name: Test with Gradle
continue-on-error: true
id: gradle_test
run: |
cd backend
./gradlew test
BE_SLACK_MESSAGE:
runs-on: ubuntu-latest
needs: BE_CI
if: ${{needs.BE_CI.result != 'skipped'}}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
- name: Get teamMember
id: teamMember
run: |
echo "SENDER_SLACK_ID=${{ env[github.event.sender.login] }}" >> $GITHUB_ENV
- name: Slack mention
uses: slackapi/slack-github-action@v1.24.0
with:
channel-id: ${{ secrets.ISSUE_CHANNEL }}
payload: |
{
"text": "pr 테스트 결과",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "pr 테스트 ${{ needs.BE_CI.result }} \n • 링크: <${{ github.event.pull_request.html_url }}|${{ github.event.pull_request.title }}> \n • pr 담당자: <@${{ env.SENDER_SLACK_ID }}>"
}
}
]
}
env:
SLACK_BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
릴리 멋있다~~~