지난번에 준비한 환경에서 CI 환경을 구축하자!
CI는 배포하기 이전의 과정을 의미하며, build 까지의 과정을 의미함.
프로젝트를 빌드하기 위해 필요한 준비물은 다음과 같음.
다음 준비물을 Github Secrets에 넣어야 함.
프로젝트 리퍼지토리 > Settings > Security > Secrets and variables > Actions > New repository secret


name: matchimban CI with Gradle
# event trigger
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
# jdk 세팅
# checkout: 현재 Repository 코드를 checkout 하여 runner 환경에 가져옴.
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
# gradle로 build하기 위한 실행권한 부여
- name: Grant execute permission for gradlew
run: chmod +x gradlew
# gradle caching : Cache workflow 실행 시간 개선 위해 dependencies와 build output을 캐싱
- 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 build
- name: Build with Gradle
uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
with:
arguments: build
# docker build
# 트러블 슈팅#1
- name: Docker build
run: |
docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} -p ${{ secrets.DOCKER_HUB_PASSWORD }}
docker build --build-arg MYSQL_ID="${{ secrets.MYSQL_ID }}" \\
--build-arg MYSQL_PASSWORD="${{ secrets.MYSQL_PASSWORD }}" \\
--build-arg RDS_URL="${{ secrets.RDS_URL }}" \\
-t roovies/matchimban:${GITHUB_SHA::7} .
docker push roovies/matchimban:${GITHUB_SHA::7}
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
빌드가 완료되어 도커 허브에 저장된 도커 이미지를 EC2 서버에서 수동으로 설치 및 배포하는 과정에서 테스트 하는 도중 나온 오류
해당 오류는 스프링 부트 애플리케이션에서 데이터 소스를 구성하는 중에 문제가 발생했음을 나타냄. url이라는 속성이 지정되지 않았다는 뜻.
spring:
datasource:
url: ${RDS_URL}
username: ${MYSQL_ID}
password: ${MYSQL_PASSWORD}
...
다음처럼 application.yaml에서 url, username, password를 Github Secrets으로 설정해두긴 했지만 정작 환경변수 등록을 하지 않았음.
오류 코드
- name: Docker build
run: |
docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} -p ${{ secrets.DOCKER_HUB_PASSWORD }}
docker build -t roovies/matchimban:${GITHUB_SHA::7} .
docker push roovies/matchimban:${GITHUB_SHA::7}
수정 코드
- name: Docker build
run: |
docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} -p ${{ secrets.DOCKER_HUB_PASSWORD }}
docker build --build-arg MYSQL_ID="${{ secrets.MYSQL_ID }}" \\
--build-arg MYSQL_PASSWORD="${{ secrets.MYSQL_PASSWORD }}" \\
--build-arg RDS_URL="${{ secrets.RDS_URL }}" \\
-t roovies/matchimban:${GITHUB_SHA::7} .
docker push roovies/matchimban:${GITHUB_SHA::7}
환경변수 등록 부분 추가
이로 인해 도커 이미지가 빌드될 때 환경변수가 같이 빌드되도록 설정함.
# (1) base-image
FROM openjdk:11
# (2) ARG를 통해 인자로 전달받음.
# 트러블 슈팅#2
# 트러블 슈팅#3
ARG MYSQL_ID
ARG MYSQL_PASSWORD
ARG RDS_URL
# (3) ENV를 통해 전달받은 값을 실제 값과 매칭시킴.
ENV MYSQL_ID=${MYSQL_ID} \
MYSQL_PASSWORD=${MYSQL_PASSWORD} \
RDS_URL=${RDS_URL}
# (4) COPY에서 사용될 경로 변수
ARG JAR_FILE=build/libs/*.jar
# (5) jar 빌드 파일을 도커 컨테이너로 복사
COPY ${JAR_FILE} app.jar
# (6) jar 파일 실행 시 'SPRING_PROFILES_ACTIVE' 환경 변수 지정하여 실행
#'dev'는 원하는 프로파일로 변경하면 된다.
ENTRYPOINT ["java", "-Dspring.profiles.active=server", "-jar", "/app.jar"]
▶ Run docker login -u -p # id와 패스워드를 이용한 로그인
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
WARNING! Your password will be stored unencrypted in /home/runner/.docker/config.json.
Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-storeLogin Succeeded
#0 building with "default" instance using docker driver#1 [internal] load .dockerignore
#1 transferring context: 2B done
#1 DONE 0.0s#2 [internal] load build definition from Dockerfile
#2 transferring dockerfile: 2B done
#2 DONE 0.0s
ERROR: failed to solve: failed to read dockerfile: open /var/lib/docker/tmp/buildkit-mount2317783204/Dockerfile: no such file or directory
Error: Process completed with exit code 1.
CI 테스트 중 Docker Build 단계에서 오류 발생
Dockerfile에 환경변수 매칭에 대한 내용을 추가하지 않아서 발생한 것으로 추측
# (2) ARG를 통해 인자로 전달받음.
ARG MYSQL_ID
ARG MYSQL_PASSWORD
ARG RDS_URL
# (3) ENV를 통해 전달받은 값을 실제 값과 매칭시킴.
ENV MYSQL_ID=${MYSQL_ID} \
MYSQL_PASSWORD=${MYSQL_PASSWORD} \
RDS_URL=${RDS_URL}
근본적인 문제는 해결됐지만 파생 오류는 트러블 슈팅#3로 이어짐.
'=' expected, got 'MYSQL_PASSWORD’
ARG 예약어에 \를 사용하면 다음 오류 발생
ARG MYSQL_ID=my_id \
MYSQL_PASSWORD=my_password \
RDS_URL=my_rds_url
ARG 명령어에서 \를 사용하기 위해선 각 변수를 =으로 매칭시켜야 함.
한 줄로 작성하더라도 각각의 ARG 정의에는 =이 사용되어야 함.
application.yaml
# 기본적으로 활성화할 profile을 server로 설정
# 모든 프로파일들에서 공통으로 사용되는 데이터
# application-server.yaml을 application.yaml에 덧붙여 로드함.
# profile: 애플리케이션 설정을 특정 환경에서만 적용되게 하거나, 환경 별(local, develop, production 등)로 다르게 적용할 때 사용함.
spring:
profiles:
active: server
application-server.yaml
spring:
datasource:
url: ${RDS_URL}
username: ${MYSQL_ID}
password: ${MYSQL_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
database-platform: org.hibernate.dialect.MySQL8Dialect
database: mysql
show-sql: true
hibernate:
ddl-auto: none
properties:
hibernate:
format_sql: true
아무래도 yaml 파일이 더 늘어날 것 같아서 application과 application-server로 분리함.
application-server는 DB와 관련된 내용으로 작성하고 application은 모든 yaml 파일을 통합하는 데 사용함.
package com.project.matchimban;
import lombok.Setter;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
@Setter
public class Test {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
package com.project.matchimban;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@RestController
@RequiredArgsConstructor
public class TestController {
private final TestRepository testRepository;
@PostMapping("/test/{name}")
public String test(@PathVariable String name){
Test test = new Test();
test.setName(name);
testRepository.save(test);
return "completed";
}
}
package com.project.matchimban;
import org.springframework.data.jpa.repository.JpaRepository;
public interface TestRepository extends JpaRepository<Test, Long> {
}
Table ‘matchimban.hibernate sequence’ doesn’t exist
최종 테스트를 위해 도커 이미지를 EC2 환경에서 테스트 하는 도중 오류 발생
해당 오류는 Hibernate가 기본적으로 사용하는 시퀀스를 찾을 수 없다는 뜻
오류 코드
package com.project.matchimban;
import lombok.Setter;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
@Setter
public class Test {
@Id
@GeneratedValue
private Long id;
private String name;
}
수정 코드
package com.project.matchimban;
import lombok.Setter;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
@Setter
public class Test {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
Test 엔티티의 id에 @GeneratedValue(strategy = GenerationType.IDENTITY)를 추가하여 해결
# 기존에 존재하던 컨테이너 삭제
$ sudo docker rm -f {도커 컨테이너 이름}
# 도커 이미지 받아오기
$ sudo docker pull {도커 사용자명}/{도커 이미지 이름}:{도커 이미지 ID}
# 도커 이미지에 태그 추가
$ sudo docker tag {도커 사용자명}/{도커 이미지 이름}:{도커 이미지 ID} {지정할 태그 이름}
# 도커 이미지 컨테이너화
$ sudo docker run -d --name {도커 컨테이너 이름} -p {호스트 포트}:{컨테이너 포트} {도커 이미지 태그, ID}
호스트 포트: 호스트 머신에서 접근하고자 하는 포트
컨테이너 포트: 컨테이너 내부에서 동작하는 애플리케이션이 노출한 포트 ex)
8080:8070 → 호스트의 8080 포트로 접근하면 해당 요청이 컨테이너 내부에서는 8070 포트로 전달되어야 함.