인프라 구축은 참 쉽지 않다. 인프라 공부는 더더욱 어려운 것 같다.
무엇보다 실습을 하려면 비용이 참 많이 든다.
하지만, 원활한 개발을 위해서는 자동 배포를 구축해야지만, 서비스 개발에만 집중할 수 있다.
그래서 백엔드 개발자는 서버 개발 능력 이외에, 어느 정도 인프라 구축 및 환경에 대한 개념을 기본적으로 탑재해야 된다고 생각한다.
이번에 사이드 프로젝트 기획 및 인프라 구축을 해야될 일이 생겼다.
어떻게 최대한 쉽고 빠르게 구축할지 고민을 해뵜다.
이 전에는 jar 파일을 그대로 실행하기 위해 AWS EC2 인스턴스에 고대로 로컬과 비슷한 환경을 구성해 놓고, yaml 도 그대로 넣어놓고, 무중단 배포를 시도했다. Ubuntu 서버에서 할게 참 많았다.
도커를 공부하고, 사용해보면서 EC2 에 직접적인 환경 구축 대신 이 도커를 써보면 어떨까 하는 생각이 들었다.
그래서 고민 과정 및 결과를 나열해보겠다.
생각한 플로우
- Github actions 로 CI 진행
- Github actions 로 Docker image (Spring boot application) 생성 후 Docker hub 에 푸시,
- Docker hub 에 최신 image 가 푸시될 경우, EC2 에서 이를 감지, Docker hub 에서 pull 받아서 docker image run
이게 끝이다.
하지만, 최신 이미지가 허브에 올라갔다는 것을 EC2 에서 어떻게 감지하지?
다 Shell Script 로 짜야 하나 (도커 허브에 접속해서 최신 이미지가 있으면 찾고, pull 받아서 run)
생각만 해도 막막하다.
도움을 얻고자, 나보다 경력 있는 주변 지인들께 내가 생각한 자동 배포 플로우를 설명 드렸다.
아니..??
Github actions 로 충분히 다 가능하단다.
역시나 누군가 만들어 놓은 Github Actions 라이브러리가 있다.
참 세상 편하다..
https://github.com/appleboy/ssh-action
이 apple boy 를 이용하면, 충분히 가능하다.
굳이 최신 docker image 자동 감지니 뭐니, shell 을 짤 필요가 없다.
Github actions 작성하기
필독!!
- AWS EC2 인스턴스는 이미 만들어서 (나는 Ubuntu 22.4) 실행중이라고 가정하고 진행하겠다.
- spring boot application 도 이미 만들어졌다고 생각하면 된다.
- docker hub 에 repository 도 이미 만들었다고 생각하면 된다.
name: deploy
on:
push:
branches: [ "master" ]
jobs:
deploy:
name: spring boot server ci-cd
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build
run: ./gradlew build -x test
- name: Test
#run: ./gradlew asciidoctor
run: echo 'empty test'
# 도커를 허브에 로그인 하기, docker hub username 과 token 이 필요하다.
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# Jib 라이브러리 사용, docker file, docker compose 를 직접 작성할 필요가 없이 자동으로 만들어 주고, docker hub 에 push 까지 해준다..
- name: Jib
env:
DOCKERHUB_REPOSITORY_PATH: ${{ secrets.DOCKERHUB_REPOSITORY_PATH }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: ./gradlew jib -x test
-Djib.to.image=$DOCKERHUB_REPOSITORY_PATH
-Djib.to.tags=latest
-Djib.container.creationTime=USE_CURRENT_TIMESTAMP
-Djib.container.environment=SLACK_WEB_HOOK_URL=$SLACK_WEBHOOK_URL
-Djib.container.jvmFlags=-XX:MaxRAMPercentage=30.0,-XX:MinRAMPercentage=30.0,-Duser.timezone=Asia/Seoul,-Dspring.profiles.active=dev
# 지금 실행중인 github actions 환경의 ip 를 따온다.
- name: Get Github action IP
id: ip
uses: haythem/public-ip@v1.2
# 따온 github actions ip 를 배포할 EC2 의 보안 그룹에 추가하는 AWS CLI 실행.
- name: Add Github Actions IP to Security group
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}
run: |
aws ec2 authorize-security-group-ingress --group-id ${{ secrets.AWS_Security_Group_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32
# apple boy 라이브러리 이용, EC2 에 직접 접속해서, docker hub 에 push 된 spring boot image 를 pull 받고 작성된 docker compose 를 기반으로 실행한다.
- name: spring boot server deploy
uses: appleboy/ssh-action@master
env:
DOCKERHUB_REPOSITORY_PATH: ${{ secrets.DOCKERHUB_REPOSITORY_PATH }}
with:
host: ${{ secrets.REMOTE_IP }}
username: ${{ secrets.REMOTE_EC2_NAME }}
key: ${{ secrets.REMOTE_SSH_KEY }}
script: |
cd ~/app
sudo docker-compose down --rmi all
sudo docker-compose up -d
# docker 컨테이너가 정상 실행되면, 보안 그룹에 추가했던 github actions ip 를 삭제한다.
- name: Remove Github Actions IP from security group
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}
run: |
aws ec2 revoke-security-group-ingress --group-id ${{ secrets.AWS_Security_Group_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 > /dev/null
if: always()
# 이건 자유지만, slack 알림도 배포 성공 여부에 따라 오게 할 수 있다.
- name: Slack notification
uses: 8398a7/action-slack@v3
with:
username: github action
status: ${{ job.status }}
author_name: Github Action
fields: repo,message,commit # repo,message,commit,author,action,eventName,ref,workflow,job,took
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: always()
이걸 .github 폴더 안에 넣어주자.
안에 ${{secrets.~~~}} 로 되어 있는 건 github secret 환경 변수에 넣어주면 된다.
- AWS_ACCESS_KEY : AWS iam 사용자 access key
- AWS_SECRET_ACCESS_KEY : iam 사용자 secret key
- REMOTE_IP : EC2 퍼블릭 ip
- REMOTE_EC2_NAME : 나는 ubuntu 이므로, ubuntu 라 적으면 됨. (ssh 접속 할때, ~~@퍼블릭ip 이렇게 적을 때, 이 물결 이름임.)
- REMOTE_SSH_KEY : EC2 생성할 때, 보통 pem 키 발급 받는 그거임.
- 나머지 snake 케이스 아닌 변수들은, github actions 에서 제공하는 변수들이다.
EC2 에서 해야할 것.
일단 EC2 에 접속해보자잉.
그리고, app 이라는 디렉터리를 만들어라.
필요하면 아래대로, log 디렉터리도 만들어라.
그리고 docker-compose.yml 을 생성하자.
### docker-compose.yml
version: "3.7"
services:
attieadconfig:
container_name: springbootapp
image: {이미지 경로 적기}:latest
ports:
- 80:8080
deploy:
resources:
limits:
cpus: '0.30'
memory: 700m
logging:
driver: "json-file"
options:
max-file: "3"
max-size: "10m"
volumes:
- ~/app/logs:/logs
그리고 실행
github actions 가 실행되고,
배포가 생공했다면 자동 배포가 종료되었다.
보통 에러는 apple boy 스텝에서 터질 것이다.
그때 위에 올려두었던, apple boy github README 를 잘 읽어보면, 해결 방법이 상세히 적혀있을 것이다.
나의 경우는, authorized keys 에서 권한을 주어 해결했던 기억이 있다.
그럼 구축된 자동 배포가 든든히 뒷받침 되었으니,
즐거운 개발을 진행해보자~~!