[맛침반] Github Action을 이용한 CI⁄CD 환경 구축(2) - CI 구축

6720·2023년 12월 18일

프로젝트 맛침반

목록 보기
3/12

이번 목표

지난번에 준비한 환경에서 CI 환경을 구축하자!

준비물

CI는 배포하기 이전의 과정을 의미하며, build 까지의 과정을 의미함.
프로젝트를 빌드하기 위해 필요한 준비물은 다음과 같음.

  • 도커 허브 유저 네임 & 비밀번호
    • 맛침반 CI/CD는 Docker Hub를 지나가기 때문에 도커 허브에 접근할 수 있어야 함.
  • MySQL 아이디 & 비밀번호 & RDS 서버 주소
    • 맛침반 프로젝트의 DB 연결을 위해 환경변수로 넣어줄 필요가 있음.

다음 준비물을 Github Secrets에 넣어야 함.

프로젝트 리퍼지토리 > Settings > Security > Secrets and variables > Actions > New repository secret

진행 과정

CI yaml 작성

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}

트러블 슈팅#1

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}

환경변수 등록 부분 추가
이로 인해 도커 이미지가 빌드될 때 환경변수가 같이 빌드되도록 설정함.

Dockerfile 수정

# (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"]

트러블 슈팅#2

▶ 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-store

Login 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로 이어짐.

트러블 슈팅#3

'=' expected, got 'MYSQL_PASSWORD’

ARG 예약어에 \를 사용하면 다음 오류 발생

ARG MYSQL_ID=my_id \
    MYSQL_PASSWORD=my_password \
    RDS_URL=my_rds_url

ARG 명령어에서 \를 사용하기 위해선 각 변수를 =으로 매칭시켜야 함.
한 줄로 작성하더라도 각각의 ARG 정의에는 =이 사용되어야 함.

application.yaml과 application-server.yaml 분리

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> {

}

트러블 슈팅#4

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)를 추가하여 해결

EC2에 도커 이미지 올리기 및 수동 컨테이너화

# 기존에 존재하던 컨테이너 삭제
$ sudo docker rm -f {도커 컨테이너 이름}

# 도커 이미지 받아오기
$ sudo docker pull {도커 사용자명}/{도커 이미지 이름}:{도커 이미지 ID}

# 도커 이미지에 태그 추가
$ sudo docker tag {도커 사용자명}/{도커 이미지 이름}:{도커 이미지 ID} {지정할 태그 이름}

# 도커 이미지 컨테이너화
$ sudo docker run -d --name {도커 컨테이너 이름} -p {호스트 포트}:{컨테이너 포트} {도커 이미지 태그, ID}

호스트 포트: 호스트 머신에서 접근하고자 하는 포트
컨테이너 포트: 컨테이너 내부에서 동작하는 애플리케이션이 노출한 포트 ex)
8080:8070 → 호스트의 8080 포트로 접근하면 해당 요청이 컨테이너 내부에서는 8070 포트로 전달되어야 함.

profile
뭐라도 하자

0개의 댓글