드림코딩의 Github Actions를 참고해서 작성하였습니다.
Github Actions 는 특정한 이벤트가 발생했을 때 내가 원하는 일을 자동으로 수행할 수 있도록 만들어주는 도구입니다.
어떤 일이 발생했을 때 수행할 것인지를 지정합니다. 이때 이벤트에는 깃허브에서 발생하는 대부분의 이벤트를 지정할 수 있습니다. 예를 들어 특정 브랜치에 커밋을 하거나 머지를 하는 등의 이벤트를 지정할 수 있습니다.
특정한 이벤트가 발생했을 때 어떤 일을 수행할 지를 지정합니다. Workflows 안에는 Job 이 있는데 하나의 workflow 안에는 하나 이상의 job 을 가질 수 있습니다.
.github/workflows
디렉토리에 yaml 파일을 만들고 여기에 작성합니다. 파일명은 아무거나 지정해도 됩니다.
Workflows 의 Job 은 동시에 병렬적으로 수행됩니다. 반대로 순차적으로 진행하도록 지정하는 것도 가능합니다.
하나의 Job 안에는 어떤 순서로 Job 이 실행되어야 하는지 Step 을 지정할 수 있습니다. 쉘 스크립트를 사용해서 어떤 step 을 해야 하는지 명시해줄 수 있습니다. 또 actions 를 사용할 수 있습니다.
step 에는 직접만든 Action 을 사용할 수 있지만 Github Actions 에는 우리가 재사용할 수 있는, 공개적으로 오픈된 액션들이 존재하며, actions/
으로 시작합니다.
지정한 Job 들을 실행하는 것이 바로 Runner( VM ) 입니다. 각각의 Job 들은 독립적인 각각의 Runner 라는 컨터이너에서 실행됩니다.
name: [workflow 이름]
on: [이벤트 지정]
jobs:
[job 이름]:
runs-on: [어떤 Runner(VM) 를 사용할 것인지]
steps: [어떤 순서대로 job 을 실행해야 하는지 step 을 지정]
## step 1
- uses:
## step 2
- name: [step 이름 지정]
uses:
with:
## step 3
- name: [step 이름 지정]
run:
...
Settings ➜ Secrets and Variables ➜ Actions 에 들어가서 Github Actions 에 쓰일 환경변수를 지정합니다.
Actions ➜ Java with Gradle 을 선택하면 어느 정도 작성되어 있는 yml 파일을 생성할 수 있습니다. 상단의 set up a workflow yourself 를 선택하면 처음부터 본인이 작성할 수 있습니다.
# Workflow 이름
name: CI/CD with Gradle
# Event 지정
on:
push:
branches: [ "main" ]
# Workflow 내 Job 을 정의
jobs:
# Job 의 이름
CI-CD-build:
# Runner 환경 정의
runs-on: ubuntu-latest
# Step 정의
steps:
# 정의된 Actions 의 체크아웃 사용
- uses: actions/checkout@v4
# 1. jdk 세팅
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
# 2. Gradle Caching
- 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-
# 3. craete firebase key
- name: create firebase key
run: |
cd ./src/main/resources
ls -a .
touch ./firebaseKey.json
echo "${{ secrets.FIREBASE_KEY }}" > ./firebaseKey.json
shell: bash
# 4. Gradle build
- name: Build with Gradle Wrapper
run: |
chmod +x ./gradlew
./gradlew clean build -x test
# 5. docker build & push
- name: docker build and push
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -f Dockerfile -t ${{ secrets.DOCKER_USERNAME }}/vitalroutes-spring ./
docker push ${{ secrets.DOCKER_USERNAME }}/vitalroutes-spring
# 6. Docker Compose Start
- name: SSH into Ubuntu Server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
password: ${{ secrets.SERVER_PASSWORD }}
script: |
docker-compose stop
docker rm -f $(docker ps -qa)
docker pull ${{ secrets.DOCKER_USERNAME }}/vitalroutes-spring
docker-compose up -d
# Workflow 이름
name: CI/CD with Gradle
workflow 의 이름을 지정하면 Actions 에 들어갔을 때 해당 이름으로 workflow 가 생성된 것을 확인할 수 있습니다.
# Event 지정
on:
push:
branches: [ "main" ]
# Workflow 내 Job 을 정의
jobs:
# Job 의 이름
CI-CD-build:
수행된 workflow 를 들어가면 Jobs 의 이름이 지정된 것을 볼 수 있고, 우측에 보면 on push 라고 되어 있습니다.
이벤트를 main 브랜치에 push 한 경우에 실행되도록 했기 때문에 정상적으로 실행된 것을 확인할 수 있습니다.
# Workflow 내 Job 을 정의
jobs:
# Job 의 이름
CI-CD-build:
# Runner 환경 정의
runs-on: ubuntu-latest
runs-on
으로 runner 가 수행될 환경을 지정합니다. 저는 우분투 서버를 사용하기 때문에 ubuntu 로 지정하였습니다.
steps:
# 정의된 Actions 의 체크아웃 사용
- uses: actions/checkout@v4
# 1. jdk 세팅
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
steps
를 이용해 job 안에서 수행할 작업들을 지정합니다. 먼저 미리 정의된 action 을 활용하기 위해 actions
를 이용해 체크아웃을 합니다.
그 후 runner 는 각각의 독립적인 환경이기 때문에 JDK 를 setup 합니다.
# 2. Gradle Caching
- 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 캐싱을 수행합니다. 해당 step을 작성하면 빌드가 조금 빠르다고 합니다.
# 3. craete firebase key
- name: create firebase key
run: |
cd ./src/main/resources
ls -a .
touch ./firebaseKey.json
echo "${{ secrets.FIREBASE_KEY }}" > ./firebaseKey.json
shell: bash
기존에는 로컬에서 테스트를 했기 때문에 main/resources
에 firebaseKey 파일이 있었는데 github 리포지토리에는 존재하지 않기 때문에 해당 파일을 생성해줍니다.
이때 Github Secret 에 지정한 환경변수인 FIREBASE_KEY 를 참조하도록 하였습니다. FIREBASE_KEY 는 파일의 내용을 붙여넣었으며, "
를 인식하기 위해서는 \"
를 사용해야 합니다.
// 기존 파일 내용
{
"type": "service_account",
"project_id": "...",
}
// secret 에 작성한 내용
{
\"type\": \"service_account\",
\"project_id\": \"...\",
}
COPY src/main/resources/firebaseKey.json firebaseKey.json
이 과정을 거치면 /main/resources 에 파일이 생성되며, 기존에 dockerfile 에 작성한 위의 명령어가 수행되어 도커 이미지 생성 시에 정상적으로 파일이 복사됩니다.
# 4. Gradle build
- name: Build with Gradle Wrapper
run: |
chmod +x ./gradlew
./gradlew clean build -x test
gradle 빌드를 수행합니다. 이때 chmod 를 통해 실행 권한을 부여해야 합니다.
# 5. docker build & push
- name: docker build and push
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -f Dockerfile -t ${{ secrets.DOCKER_USERNAME }}/vitalroutes-spring ./
docker push ${{ secrets.DOCKER_USERNAME }}/vitalroutes-spring
docker image 를 build 하고 push 하는 작업입니다. 해당 명령어에 필요한 USERNAME 과 PASSWORD 는 github secret 에 정의되어야 합니다.
# 6. Docker Compose Start
- name: SSH into Ubuntu Server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
password: ${{ secrets.SERVER_PASSWORD }}
script: |
docker-compose stop
docker rm -f $(docker ps -qa)
docker pull ${{ secrets.DOCKER_USERNAME }}/vitalroutes-spring
docker-compose up -d
그 후 서버에 접속하여 새로운 이미지를 pull 받고, docker-compose 를 실행하는 과정입니다. 이때 다른 사람의 정의한 action 을 사용하도록 uses
에 작성하였습니다.
먼저 서버의 정보를 with
에 작성하고, 서버에서 실행할 명령어들을 script
에 작성합니다.
docker-compose logs -f -t
그 후 서버에 접속해서 위 명령어를 입력하면 로그를 확인할 수 있습니다.
err: permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/json?all=1": dial unix /var/run/docker.sock:
위처럼 권한 오류가 뜨는 경우에는 아래 명령어를 통해 docker.sock 의 권한을 수정하면 됩니다.
sudo chmod 666 /var/run/docker.sock
지금까지 총 4단계에 걸쳐 서버 구축, Docker-Compose, NGINX 및 HTTPS 설정, Github Actions 를 진행하였고, 자동화된 CI/CD 구축을 완료하였습니다. 최종적인 구조는 위와 같습니다.