쌩로그

[TroubleShooting] CI/CD 진행 중 빌드 단계에서 프로젝트의 설정 값을 환경 변수로부터 가져와야 할 때 본문

TroubleShooting & 고민/기타

[TroubleShooting] CI/CD 진행 중 빌드 단계에서 프로젝트의 설정 값을 환경 변수로부터 가져와야 할 때

.쌩수. 2025. 3. 11. 15:05
반응형

목록

  1. 문제 발생 배경 - 줄거리
  2. 문제 발생 배경 핵심
  3. 문제 해결
  4. 요약

1. 문제 발생 배경 - 줄거리

현재 WAS 를 배포하는 중이다.
배포에 GitLab 과 GitLab Runner 를 이용해서 CI/CD 를 적용하여 배포하는 중에 있었다.

  • 코드 수정 후 main 브랜치에 merge혹은 push를 하면 GitLab & GitLab Runner가 이를 감지하여 자동적으로 배포하도록 하고 있었다.

CI/CD 자체는 원활히 진행되었다.
그런데 CI 과정에서 스프링 프로젝트의 application.yml 에 환경 변수로부터 읽어와야 하는 값들이 있다.
예를 들면, DB user의 id와 password 다.

아래는 그 예시다.

spring:  
  datasource:  
    url: jdbc:RDB정보://호스트:포트이름/스키마?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Seoul  
    username: ${USER_NAME}  
    password: ${USER_PASSWD}

위와 같이 DB 정보를 환경변수로부터 가져오도록 yml 을 작성해놨을 것이다.
다른 예시를 들어보자면,
OAuth Secret Key 혹은 ID 도 있을 것이고,
JWT를 암호화/복호화하는데 쓰는 키값들도 대체로 ${} 형태로 넣어놓을 것이다.

이전에 GitHub Actions로 CI/CD를 구성할 때 환경 변수를 못 읽어오는 문제 때문에 프로젝트 진행에 2~3일을 해맸었다...

그 문제를 해결하고 기분이 좋은 나머지 변수와의 전쟁_export전성시대 이름으로 포스팅을 했었다.

이 때 크게 알게된 것은 build 할 때 환경 변수를 가져올 수 있는 것이어야 한다는 점이다.
참고로 그 때나 지금이나 배포하는 데 사용한 OS는 우분투다.

GitHub Actions와 GitLab Runner는 ci.yml 작성 방식이 다르다.
하지만 분명한 것은 GitHub Actions이든 GitLab Runner든 빌드할 때 환경변수를 가져올 수 있어야한다.

GitLab Runner는 배포 서버에 GitLab Runner를 설치를 한다.
설치를 하면 gitlab-runner라는 user가 만들어지고,

두 개의 job(build, deploy) script에 작성한 리눅스 명령어들이 해당 user로 인해서 만들어진 디렉터리 내에서 진행되는 것을 확인했다.

그리고 whoami 라는 명령어를 입력하면, 현재 user가 누구인지도 보이는데, 배포서버의 내이름 이니셜로된 user였다...

물론 .bashrc 로도 설정했지만, 스프링 프로젝트 실행시 DB의 user ID가 ${USER_NAME} 으로 들고와서 결국 서버 실행이 되지 않는 문제가 발생했다..

쉽게 말해서 DB 접근 ID가 kss 라면

kss@데이터소스주소... 로 Connetion을 연결해야하는데,
${USER_NAME}@데이터소스주소... 로 Connection을 맺고 있는 것이다...

환장할 노릇이었다..

GitLab 에서도 CI의 변수를 설정하는 기능을 제공해준다.
물론 당연히 썼다...

script를 통해서도 변수가 잘 나오는지 확인했지만, yml의 값은 여전히 부동이었다.

그래서 그냥 아예 다른 방법을 사용해서 해결했다.

참고로 아래는 취준 때 CI/CD를 구축하면서 모든 변수들을 CI.yml에 집어넣어놓은 예시다..

...

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up JDK 11
        uses: actions/setup-java@v3
        with:
          java-version: '11'
          distribution: 'temurin'

      - name: Set prod yml
        uses: microsoft/variable-substitution@v1
        with:
          files: ${{ env.RESOURCE_PATH }}
        env:
          spring.security.oauth2.client.registration.google.clientId: ${{secrets.G_CLIENT_ID}}
          spring.security.oauth2.client.registration.google.clientSecret: ${{secrets.G_CLIENT_SECRET}}
          spring.security.oauth2.client.registration.kakao.clientId: ${{secrets.K_CLIENT_ID}}
          spring.security.oauth2.client.registration.kakao.client-secret: ${{secrets.K_CLIENT_SECRET}}
          spring.datasource.url: jdbc:mysql://${{secrets.MYSQL_HOST}}:${{secrets.MYSQL_PORT}}/${{secrets.DATA_BASE_NAME}}?useSSL=false
          spring.datasource.username: ${{secrets.MYSQL_USER_NAME}}
          spring.datasource.password: ${{secrets.MYSQL_PASSWORD}}
          jwt.key.secret: ${{secrets.JWT_SECRET_KEY}}
          admin.email: ${{secrets.ADMIN_EMAILS}}
          spring.redis.host: ${{secrets.AWS_EC2_HOST}}
          spring.redis.port: ${{secrets.AWS_REDIS_PORT}}
          spring.redis.password: ${{secrets.REDIS_PASSWORD}}

      - name: init with Gradle
        uses: gradle/gradle-build-action@v2
      - run: gradle init
      - name: Build with Gradle
        uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
        with:
          gradle-version: 7.6.1 # gradle 버전
          arguments: build -x test  # build # test 제외


...

참 난잡하다..
현재 해결한 방법은 이보다 더 깔끔하다.
왜냐하면 YML 이 없어지기 때문이다.

2. 문제 발생 배경 핵심

  • CI 진행중 프로젝트의 yml 이 환경변수로부터 값을 못 가져오는 채로 build된다.
  • 그래서 스프링 애플리케이션이 시작되지 않는다.

3. 문제 해결

정말 간단하다.

YML 파일의 내용 자체를 값으로 넣어서 script의 명령어 중에 yml 파일에 덮어씌이도록 했다.

먼저는 yml 을 git.ignore 에 선언해서 더 이상 push 대상이 되지 않도록 했다.

...

build:  
  stage: build  
  script:  
    - echo "Compiling the code..."  
    - cat $APPLICATION_YML > src/main/resources/application.yml # YML 덮어쓰기
    - cat > shop.log   # 로그 파일 생성
    - sudo chmod 755 gradlew   # gradlew 권한
    - ./gradlew clean build -x test  # test 없이 clean 후 build 진행해서 jar 파일 생성
    - echo "Compile complete."

...

위에서 중요한 내용은

cat $APPLICATION_YML > src/main/resources/application.yml 이 줄이다.

하드 코딩된 yml 을

GitHub라면 Settings - Security-Secrets and variables - Actions 에서 New repository secret 으로 만들어서 Secret(Key)의 값으로 작성해놓는다.

GitLab이라면 Settings - CI/CD - Variables 에서 Add variable 에서 새로운 변수(Key)의 값으로 작성해놓는다.

위의 명령에 보다시피 나는 APPLICATION_YML 이라는 KEY로 만들어서 해당 값이 yml 파일에 덮어씌이도록 했다.

아래 사진은 해당 yml 파일 내용 자체를 작성한 예시다.
첫 번째는 Github Actions, 두 번째는 GitLab이다.

그리고 다시 pipeline을 돌렸고,
그 결과로 아래의 로그가 찍혔다.

...

2025-03-11T04:58:16.324Z  INFO 69903 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
2025-03-11T04:58:16.326Z  INFO 69903 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2025-03-11T04:58:16.741Z  INFO 69903 --- [           main] o.s.b.a.w.s.WelcomePageHandlerMapping    : Adding welcome page: class path resource [static/index.html]
2025-03-11T04:58:17.053Z  INFO 69903 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path ''
2025-03-11T04:58:17.060Z  INFO 69903 --- [           main] jpabook.jpashop.JpashopApplication       : Started JpashopApplication in 4.223 seconds (process running for 4.657)

Started JpashopApplication in 4.223 seconds (process running for 4.657) 을 보았다.

이처럼 YML 파일의 내용 자체를 값으로 넣어서 script의 명령어 중에 yml 파일에 덮어씌이도록 함으로써 해결했다.

4. 요약

프로젝트의 YML에서 환경변수를 가져오지 못하는 문제를
YML 파일 내용 자체를 CI의 변수의 값으로 선언해놓고,
script 실행 과정에서 yml 파일에 덮어 씌이도록 했다.

참고로 이 내용은 자동배포에 대한 내용이다.
수동 배포라면 그냥 배포할 서버에 환경 변수를 설정해놓으면 알아서 가져온다.


이전에 변수를 불러들이는 자동 배포는

빌드 시점에 환경변수를 가져올 수 있는지 없는지 여부,
OS 환경의 여부, 변수를 올바로 설정했는지 여부 등을 다 따져야 했다.
(그런데도 원활히 진행하지 못 했다마는)

YML 파일 내용 자체를 하나의 변수의 값으로 넣어놓고, yml 에 덮어씌우면 ci.yml 파일에 따로 변수를 덕지덕지 선언할 필요도, 또한 하나의 Secret(혹은 variable)을 일일이 넣어둘 필요가 없다.

그냥 yml 만 고쳐 써주면 되니 오히려 더 편해진다.

그리고 YML에 값이 노출될지 보안상의 여부도 걱정할 우려가 줄어들 것이다.
왜냐하면 YML 파일 자체를 push하지 않으니 말이다.
(물론 yml이 아예 프로젝트에 올라가지 않는다면 script 에서 생성하도록 해야한다.)

단 깃허브가 털리면 안 되는 건 당연한 것이다.
요즘 핸드폰 인증도 필요하던데.. 그 부분을 이용하자.

728x90
Comments