Spring boot 3.xxx Docker CI / CD 구축 with Github Actions(1) 에서는
수동 배포를 했었다.
이번 정리는 자동배포다!!
자동배포 동작 흐름
1. 개발 환경(인텔리제이)에서 github로 commit & push 감지
2. workflows 안에 .yml 파일 실행 (배포 스크립트)
2-1. JDK Setting
2-2. Gradle caching (속도향상)
2-3. application.yml 파일 생성
2-4. Gradle chmod
2-5. gradle build
2-6. docker Login
2-7. docker build & push
2-8. deploy to EC2
아래는 Gradle.yml 전체 파일 내용이다.
그대로 붙여넣어주자.
아래 각 작업들 설명을 보면서 개인에 맞게 설정하자!!
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle
name: CI/CD github Actions & Docker
on:
push:
branches: [ "main", "develop" ]
permissions:
contents: read
jobs:
CI-CD:
runs-on: ubuntu-latest
steps:
# JDK setting - github actions에서 사용할 JDK 설정 (aws 과 project의 java 버전과 별도로 관리)
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
## 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-
# 환경별 yml 파일 생성(1) - dev
- name: make application-dev.yml
if: contains(github.ref, 'develop')
run: |
cd ./src/main/resources
touch ./application.yml
echo "${{ secrets.YML }}" > ./application.yml
shell: bash
# 환경별 yml 파일 생성(2) - prod
- name: make application-prod.yml
if: contains(github.ref, 'main')
run: |
cd ./src/main/resources
touch ./application.yml
echo "${{ secrets.YML }}" > ./application.yml
shell: bash
# gradle chmod
- name: Grant execute permission for gradlew
run: chmod +x gradlew
# gradle build
- name: Build with Gradle
run: ./gradlew clean build -x test
# docker login
- name: Docker Hub Login
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
# docker build & push to production
- name: Docker build & push to prod
if: contains(github.ref, 'main')
run: |
docker build -f Dockerfile -t ${{ secrets.DOCKER_REPO }}/agaproject .
docker push ${{ secrets.DOCKER_REPO }}/agaproject
# docker build & push to develop
- name: Docker build & push to dev
if: contains(github.ref, 'develop')
run: |
docker build -f Dockerfile -t ${{ secrets.DOCKER_REPO }}/agaproject .
docker push ${{ secrets.DOCKER_REPO }}/agaproject
## deploy to production
- name: Deploy to prod
uses: appleboy/ssh-action@master
id: deploy-prod
if: contains(github.ref, 'main')
with:
host: ${{ secrets.HOST_PROD }} # EC2 퍼블릭 IPv4 DNS
username: ubuntu
key: ${{ secrets.PRIVATE_KEY }}
envs: GITHUB_SHA
script: |
sudo docker ps
sudo docker rm -f $(docker ps -qa)
sudo docker pull ${{ secrets.DOCKER_REPO }}/agaproject
sudo docker run -d -p 8080:8080 ${{ secrets.DOCKER_REPO }}/agaproject
sudo docker image prune -f
## ## sudo docker run -d -p 8080:8080 ${{ secrets.DOCKER_REPO }}/agaproject
## deploy to develop
- name: Deploy to dev
uses: appleboy/ssh-action@master
id: deploy-dev
if: contains(github.ref, 'develop')
with:
host: ${{ secrets.HOST_DEV }} # EC2 퍼블릭 IPv4 DNS
username: ${{ secrets.USERNAME }} # ubuntu
password: ${{ secrets.PASSWORD }}
port: 22
key: ${{ secrets.PRIVATE_KEY }}
script: |
sudo docker ps
sudo docker rm -f $(docker ps -qa)
sudo docker pull ${{ secrets.DOCKER_REPO }}/agaproject
sudo docker run -d -p 8080:8080 ${{ secrets.DOCKER_REPO }}/agaproject
sudo docker image prune -f
name: CI/CD github Actions & Docker
on:
push:
branches: [ "main", "develop" ]
permissions:
contents: read
별도로 JDK를 관리하고 실행하는 부분이다.
각자 프로젝트에 맞게 JDK 버전을 맞추는 것을 권장한다.
# JDK setting - github actions에서 사용할 JDK 설정 (aws 과 project의 java 버전과 별도로 관리)
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
## 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-
현재 아래 명령어는 main이든 develop이든 application.yml로 생성하게끔 만들었다.
나중에 따로 생성할 필요가 있을 때 써먹으려고 미리 명령어를 입력해놓았다.
# 환경별 yml 파일 생성(1) - dev
- name: make application-dev.yml
if: contains(github.ref, 'develop')
run: |
cd ./src/main/resources
touch ./application.yml
echo "${{ secrets.YML }}" > ./application.yml
shell: bash
# 환경별 yml 파일 생성(2) - prod
- name: make application-prod.yml
if: contains(github.ref, 'main')
run: |
cd ./src/main/resources
touch ./application.yml
echo "${{ secrets.YML }}" > ./application.yml
shell: bash
# gradle chmod
- name: Grant execute permission for gradlew
run: chmod +x gradlew
다른 자료를 찾다보면 clean이 없는 경우도 있는데
저는 clean이 없으니까 에러가 났습니다.
# gradle build
- name: Build with Gradle
run: ./gradlew clean build -x test
따로 안 빼주고 docker login -u 명령어를 쓰다보면
가끔 에러가 발생한다.
# docker login
- name: Docker Hub Login
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
현재는 main과 develop 환경이 동일하기 때문에
docker build 할 때 docker hub repository 이름이 동일하지만,
나중에 환경이 달라지면 docker hub repository를 다르게 설정해야한다.
즉, 설정 파일(application.yml)이 달라지면(나누면)
docker hub repository도 다르게 설정해야함.
# docker build & push to production
- name: Docker build & push to prod
if: contains(github.ref, 'main')
run: |
docker build -f Dockerfile -t ${{ secrets.DOCKER_REPO }}/agaproject .
docker push ${{ secrets.DOCKER_REPO }}/agaproject
# docker build & push to develop
- name: Docker build & push to dev
if: contains(github.ref, 'develop')
run: |
docker build -f Dockerfile -t ${{ secrets.DOCKER_REPO }}/agaproject .
docker push ${{ secrets.DOCKER_REPO }}/agaproject
여기도 마찬가지로 분리시켰는데,
현재는 설정파일이 동일하기 때문에 상관없지만,
분리되면 여기도 docker repository를 달리해서 설정해줘야함.
## deploy to production
- name: Deploy to prod
uses: appleboy/ssh-action@master
id: deploy-prod
if: contains(github.ref, 'main')
with:
host: ${{ secrets.HOST_PROD }} # EC2 퍼블릭 IPv4 DNS
username: ubuntu
key: ${{ secrets.PRIVATE_KEY }}
envs: GITHUB_SHA
script: |
sudo docker ps
sudo docker rm -f $(docker ps -qa)
sudo docker pull ${{ secrets.DOCKER_REPO }}/agaproject
sudo docker run -d -p 8080:8080 ${{ secrets.DOCKER_REPO }}/agaproject
sudo docker image prune -f
## ## sudo docker run -d -p 8080:8080 ${{ secrets.DOCKER_REPO }}/agaproject
## deploy to develop
- name: Deploy to dev
uses: appleboy/ssh-action@master
id: deploy-dev
if: contains(github.ref, 'develop')
with:
host: ${{ secrets.HOST_DEV }} # EC2 퍼블릭 IPv4 DNS
username: ${{ secrets.USERNAME }} # ubuntu
password: ${{ secrets.PASSWORD }}
port: 22
key: ${{ secrets.PRIVATE_KEY }}
script: |
sudo docker ps
sudo docker rm -f $(docker ps -qa)
sudo docker pull ${{ secrets.DOCKER_REPO }}/agaproject
sudo docker run -d -p 8080:8080 ${{ secrets.DOCKER_REPO }}/agaproject
sudo docker image prune -f
보안이 필요한 부분들이라고 생각하면 된다.
이제 .yml파일을 생성해서 secrets에 정의된 내용들로
yml 파일이 실행되기 때문에 github에 .yml 파일을 올리지 않아도 된다.
secrets 파일을 설정하러가자
github -> settings -> Secrets and Variables -> Actions
클릭해서 이동한다.
아래는 secrets 파일 내용이다. (참고해서 생성하면 된다.)
DOCKER_USERNAME : DockerHub ID ( ex : user@gmail.com )
DOCKER_PASSWORD : DockerHub PW ( ex : password )
HOST_PROD : EC2 IPv4
( ex : 12.123.123.123 ( 탄력적 ip )
or
ec2-12-123-123-123.ap-northeast-2.compute.amazonaws.com
( 퍼블릭 ipv4 DNS ) )
PRIVATE_KEY : .pem 파일 복붙(--begin--, --end-- 까지 다 복붙해야함.)
HOST_DEV : EC2 IPv4
( ex : 12.123.123.123 )
USERNAME : EC2 사용자 계정 ID ( ex : ubuntu )
PASSWORD : EC2 사용자 계정 PW ( ex : 1234 )
비밀번호 설정 참고 블로그
YML : 프로젝트 .yml 파일에 설정한 내용
이제 main 이나 develop에서 push 하고
gitActions 동작이 잘되는지 확인하면 된다.
docker ps 명령어로 확인 가능하다.
sudo docker logs ${Container ID} 입력하면 log도 뜬다.
참고로 docker 명령어는 해당 velog를 참조하였다.
이렇게 자동 배포까지 해보았다.
정말 많은 삽질을 했지만, 그 만큼 또 많은 깨달음을 얻을 수 있었다.
하다가 진짜 에러 하나 해결하면 또 다른 에러가 뜨고
디버깅 파티였다....
하다하다 지쳐서 "에러 해결 못하겠다" 싶을 때가 있었는데,
이때 포기하고 싶었지만,
이것도 못하면 무슨 개발자를 하겠는가...라는 생각으로 8시간 넘게 삽질하고 고치고 했던 것 같다.
이 글을 읽고 있는 분들은 편하게 자동 배포를 하시기를 바라면서
트러블 슈팅으로 글을 마치도록 하겠습니다.
👏👏👏 감사합니다 👏👏👏
#참고 : 카카오 기술 블로그에 docker 속도향상(도커 캐시 적용)에 대해서
정리가 되어있는 곳도 링크를 걸고 가도록 하겠다!#
Error: Error: Cannot locate a Gradle wrapper properties file at
build 할 때 wrapper를 못찾는거 같아서 폴더를 벗겼습니다.
폴더구조는 아래와 같음
Error: Error: Cannot locate a Gradle wrapper properties file at
참고 : init 추가(1), init 추가(2)
아래 명령어를 추가했음. (전체 파일 내용에는 추가한 내용으로 들어가있다.)
# gradle chmod
- name: Grant execute permission for gradlew
run: chmod +x gradlew
결국, 두 개를 추가하니 해결되었는데,
나중에 init을 빼고 빌드가 되서 그냥 빼버렸다.
gitActions에서 push를 해도 아무리 상태가 안뜨길래 이상해서 명령어를 쭉 훓었는데 branches["main, develop"] 이렇게 같이 묶어서 정의를 했었다.
아래와 같이 "main", "develop" 정확하게 분류하니 해결이 되었다.
- on:
push:
branches: [ "main", "develop" ]
branches ["main, develoop"] 으로 해놓은거 위 처럼 변경
여러 블로그를 보면서 참고했는데 JDK Setting에서 대부분 11을 쓰길래 아무생각없이 11버전으로 했는데,
spring boot 3.xxx JDK 머시기 버전이 안 맞다고 해서
프로젝트도 3.xxx버전이고 해서 모든(로컬환경, 우분투)곳에 JDK 17로 맞췄다.
gitActions는 JDK를 별도로 관리한다는데
나는 버전을 맞춰야 실행이됐다.
# JDK setting - github actions에서 사용할 JDK 설정 (aws 과 project의 java 버전과 별도로 관리)
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
Run docker build -f Dockerfile -t ***/agaproject .
#0 building with "default" instance using docker driver
이 문제는 폴더구조에도 연관되어있으니 참고하시길!!
======CMD======
sudo docker ps
sudo docker pull /agaproject
sudo docker run -d -p 8081:8081 /agaproject
sudo docker image prune -f
======END======
2023/12/23 13:24:08 dial tcp ***:22: i/o timeout
자꾸 timeout이 떠서 .yml을 잘못정의했나 계속 찾았는데 문제가 없었다.
저번에 친구 EC2 서버 접속 도와주다가 인바운드 규칙 안 열었을때랑 에러가 같아서 aws ec2 서버 인바운드 규칙을 열어줬더니 해결되었다.
ssh로 접속하는 모든 ip를 허용하게끔 만들었음.
어차피 .pem 파일이 없거나,
EC2 서버 ID, PASSWORD를 모르면 접속이 안되니까 상관 없었다.
ID, PASSWORD 설정은 꼭 해주자!
com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
AWS RDS를 사용해 DB를 쓰고있는데 로컬에서는 연결 잘 하더니,
갑자기 docker 활용해서 EC2 서버에서 실행하니 DB 연결이 안 된다고 한다.
이것도 AWS 인바운드 규칙 문제인가 싶어서 보안 규칙을 봤는데 EC2 서버랑 규칙 규칙을 동일하게 설정해줬다.
많은 자료를 찾아보다가 ssl 문제라는 것을 깨달았고,
application.yml 파일에 DB 연결 url부분 맨 뒤에
&useSSL=false 이거를 추가하면 된다.
spring:
datasource:
url: jdbc:mysql://엔드포인트:3306/alarmdb?serverTimezone=Asia/Seoul&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username:
password:
실행 다 되었는데 Swagger가 안 열린다...
AWS 인바운드 규칙에 8080 포트를 열어주니 해결되었다.
Password 설정 방법은 아래와 같다.
EC2서버(우분투)에서 입력하자
# 1. sshd config 파일 들어가기
sudo vim /etc/ssh/sshd_config
# 2. PasswordAuthentication부분을 no에서 yes로 변경해주기
# 3. 시스템 재부팅 (위에 수정사항 적용하기)
sudo systemctl restart sshd
# 4. 패스워드 설정하기 (ubuntu 자리에 자기 ec2-user 이름 들어가면 된다.)
sudo passwd ubuntu
# 5. 로컬 (개발환경)에서 접속해보기
#ssh ec2-user@<ec2 instance-ip> -p <port 번호>
ssh ubuntu@00.00.00.0 -p 22
길고 긴 여정이 마무리되었다...
도움이 되었습니다.