왜 사용하게 되었나
Github에 모든 파일을 올려서 사용할 수는 없습니다. 저희 팀에서는 application.yml 파일 안에 작성하는 DB 정보, API 키가 해당됐습니다.
그럼 로컬이 아닌 환경에서 application.yml 을 어떤식으로 관리해야 할까요? 방법은 다양합니다.
- 직접 파일을 생성해서 넣어준다.
- Github Secrets 를 사용해서 Secret을 등록한 뒤, application.yml 안의 값을 주입시킨다.
- private git repository를 만들어 서브 모듈로 사용하고 서브 모듈의 application.yml 파일을 사용하게 하기
저희 팀은 application.yml을 관리하는 방법으로 Github Secrets
와 .env
를 동시에 활용하기로 했습니다.
.env란
외부에 공유하면 안되는 중요한 정보들을 관리할 수 있는 파일입니다.
Kotlin + 스프링 환경에서는 다음과 같이 의존성 추가를 통해 사용할 수 있습니다.
implementation("me.paulschwarz:spring-dotenv:4.0.0") // dependency 추가
로컬 환경에서는 단순히 .env
파일을 생성하고 그 안에 값을 넣어두면 문제없이 작동하지만, 배포 환경에서 적용하려면 .env
파일을 동적으로 생성해야 합니다.(Github에는 올려두지 않을 것이기 때문)
방법은 다음과 같습니다.
- application.yml에서 사용할 각 정보들을 Github Secrets에 변수로 등록합니다.
- .env 파일을 생성하고 Github Secrets에 등록된 변수들을 모두 입력합니다.(자동화 필요)
- application.yml에 .env의 값이 주입됩니다.
- Application이 구동됩니다.
저는 팀 내에서 CI/CD 기능도 함께 담당하여 맡고 있기 때문에 위 방법 2번에서 .env
를 자동화하여 생성하는 로직을 Github Actions
를 활용하여 적용하고자 하였습니다.
혹시 Github Actions를 모르신다면 아래 공식 사이트를 방문해보시길 바랍니다.
**GitHub Actions를 사용하여 리포지토리에서 바로 소프트웨어 개발 워크플로를 자동화, 사용자 지정 및 실행합니다. CI/CD를 포함하여 원하는 작업을 수행하기 위한 작업을 검색, 생성 및 공유하고 완전히 사용자 정의된 워크플로에서 작업을 결합할 수 있습니다.
Github Actions 공식 문서
본론으로 들어가, 저희 팀에서 .env 파일을 자동화한 적용 과정을 설명하겠습니다.
name: Camp Daddy CI
on:
pull_request:
branches: [ main, dev ]
jobs:
test:
# ubuntu를 설치합니다.
name: 우분투 설치
runs-on: ubuntu-latest
permissions:
contents: read
steps:
# 현재 PR에 해당하는 레포지토리로 체크아웃을 합니다. develop, main으로 가지 않습니다.
- name: 레포지토리 체크아웃
uses: actions/checkout@v2
##############################
# +++ dotenv 파일 생성 추가 +++ #
##############################
# JDK를 설치합니다.
- name: JDK 17 설치
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'corretto'
# gradlew 파일에 실행 권한을 부여합니다.
- name: Gradle 권한 부여
run: chmod +x gradlew
# gradle을 사용하여 빌드와 테스트를 진행합니다.
- name: Gradle 빌드 & 테스트 실행
run: ./gradlew build test
# CI 결과를 Slack을 통해 팀 채널에 공유합니다.
- name: 슬랙 Alarm 전송
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
author_name: CI 결과 알림
fields: message, commit, author, ref, workflow
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: always()
이 중, .env 파일을 생성하고 값을 채워 넣는 로직을 최초에 다음과 같이 작성하였습니다. 아래로는 계속 트러블 슈팅과 관련된 얘기가 나올 예정이라, 혹시 바로 결과를 확인하실 분들은 아래로 내려가시면 됩니다
최초 .env 파일 생성 Github Actions 로직
- name: .env 생성
run: |
echo ${{ secrets.ENV }} > .en
이 로직의 문제는 echo 명령어가 수행될 때 쌍따옴표 “”
가 존재하지 않기 때문에 에러가 발생합니다. 따라서 echo 명령어 뒤에 “”
를 추가하여 수정했습니다.
${{ secrets.ENV }}
: Github Seccrets에 등록된 변수를 가져오는 방법입니다.
echo “” 추가 Github Actions 로직
- name: .env 생성
run: |
echo "${{ secrets.ENV }}" > .en
하지만 이 방법은 ENV로 등록한 내용들의 첫 줄만 가져올 뿐 아래에 있는 행들을 읽지 못했습니다.
여기서 약 3~4시간을 허비한 것 같습니다.
Github Secrets에 모든 변수를 직접 하나씩 넣어주는 방법으로 변경했습니다.
Github Secrets에 모든 변수를 직접 추가 Github Actions 로직
- name: .env 파일 생성
run: |
echo "Value of MY_SECRET1: $MY_SECRET1" > .env
echo "Value of MY_SECRET2: $MY_SECRET2" > .env
echo "Value of MY_SECRET3: $MY_SECRET3" > .env
echo "Value of MY_SECRET4: $MY_SECRET4" > .env
echo "Value of MY_SECRET5: $MY_SECRET5" > .env
echo "Value of MY_SECRET6: $MY_SECRET6" > .env
...
env:
MY_SECRET1: ${{ secrets.MY_SECRET1 }}
MY_SECRET2: ${{ secrets.MY_SECRET2 }}
MY_SECRET3: ${{ secrets.MY_SECRET3 }}
MY_SECRET4: ${{ secrets.MY_SECRET4 }}
MY_SECRET5: ${{ secrets.MY_SECRET5 }}
MY_SECRET6: ${{ secrets.MY_SECRET6 }}
...
Secrets에 등록된 변수를 하나씩 직접 ehco 명령어를 통해 .env에 적어주는 방식입니다.
이렇게 되면 Secret에 변수를 하나씩 등록할 때마다 CI 파일도 수정해야 하기 때문에 관리가 어려워질 것이라고 생각하여, 더 나은 방법을 찾아야 했습니다.
그러던 중 다음과 같은 글을 발견하였습니다.
해당 글에서는 동적인 방법으로 .env 파일을 추가하는 방법을 설명합니다. 이를 적용하였습니다.
최종 Github Actions 로직
- name: .env 파일 생성
run: |
jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' <<< "$SECRETS_CONTEXT" > .env
env:
SECRETS_CONTEXT: ${{ toJson(secrets) }}
이 방법을 사용하면 Secret이 계속 추가되어도 CI 파일은 수정하지 않아도 됩니다.
번외, 테스트 결과를 PR에 등록하여 결과를 공유한다.
- name: 테스트 결과 PR 등록
uses: EnricoMi/publish-unit-test-result-action@v1
if: ${{ always() }}
with:
files: '**/build/test-results/test/TEST-*.xml'
단위 테스트가 많아질수록 모든 테스트가 잘 작동하는지 파악하는 것은 어렵다고 판단하여, 간편하게 실패한 테스트를 볼 수 있는 방법이 없을까 고민하다가 적용하게 되었습니다.
근데 계속 .env를 사용해야 하나?
어차피 Secret에 등록한 변수를 application.yml 에 넣어주는 것은 똑같은데, .env를 활용해야하나 라는 생각이 들었습니다.
이 문제는 팀원들과 얘기를 해봐야 할 것 같습니다.
왜 사용하게 되었나
Github에 모든 파일을 올려서 사용할 수는 없습니다. 저희 팀에서는 application.yml 파일 안에 작성하는 DB 정보, API 키가 해당됐습니다.
그럼 로컬이 아닌 환경에서 application.yml 을 어떤식으로 관리해야 할까요? 방법은 다양합니다.
- 직접 파일을 생성해서 넣어준다.
- Github Secrets 를 사용해서 Secret을 등록한 뒤, application.yml 안의 값을 주입시킨다.
- private git repository를 만들어 서브 모듈로 사용하고 서브 모듈의 application.yml 파일을 사용하게 하기
저희 팀은 application.yml을 관리하는 방법으로 Github Secrets
와 .env
를 동시에 활용하기로 했습니다.
.env란
외부에 공유하면 안되는 중요한 정보들을 관리할 수 있는 파일입니다.
Kotlin + 스프링 환경에서는 다음과 같이 의존성 추가를 통해 사용할 수 있습니다.
implementation("me.paulschwarz:spring-dotenv:4.0.0") // dependency 추가
로컬 환경에서는 단순히 .env
파일을 생성하고 그 안에 값을 넣어두면 문제없이 작동하지만, 배포 환경에서 적용하려면 .env
파일을 동적으로 생성해야 합니다.(Github에는 올려두지 않을 것이기 때문)
방법은 다음과 같습니다.
- application.yml에서 사용할 각 정보들을 Github Secrets에 변수로 등록합니다.
- .env 파일을 생성하고 Github Secrets에 등록된 변수들을 모두 입력합니다.(자동화 필요)
- application.yml에 .env의 값이 주입됩니다.
- Application이 구동됩니다.
저는 팀 내에서 CI/CD 기능도 함께 담당하여 맡고 있기 때문에 위 방법 2번에서 .env
를 자동화하여 생성하는 로직을 Github Actions
를 활용하여 적용하고자 하였습니다.
혹시 Github Actions를 모르신다면 아래 공식 사이트를 방문해보시길 바랍니다.
**GitHub Actions를 사용하여 리포지토리에서 바로 소프트웨어 개발 워크플로를 자동화, 사용자 지정 및 실행합니다. CI/CD를 포함하여 원하는 작업을 수행하기 위한 작업을 검색, 생성 및 공유하고 완전히 사용자 정의된 워크플로에서 작업을 결합할 수 있습니다.
Github Actions 공식 문서
본론으로 들어가, 저희 팀에서 .env 파일을 자동화한 적용 과정을 설명하겠습니다.
name: Camp Daddy CI
on:
pull_request:
branches: [ main, dev ]
jobs:
test:
# ubuntu를 설치합니다.
name: 우분투 설치
runs-on: ubuntu-latest
permissions:
contents: read
steps:
# 현재 PR에 해당하는 레포지토리로 체크아웃을 합니다. develop, main으로 가지 않습니다.
- name: 레포지토리 체크아웃
uses: actions/checkout@v2
##############################
# +++ dotenv 파일 생성 추가 +++ #
##############################
# JDK를 설치합니다.
- name: JDK 17 설치
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'corretto'
# gradlew 파일에 실행 권한을 부여합니다.
- name: Gradle 권한 부여
run: chmod +x gradlew
# gradle을 사용하여 빌드와 테스트를 진행합니다.
- name: Gradle 빌드 & 테스트 실행
run: ./gradlew build test
# CI 결과를 Slack을 통해 팀 채널에 공유합니다.
- name: 슬랙 Alarm 전송
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
author_name: CI 결과 알림
fields: message, commit, author, ref, workflow
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: always()
이 중, .env 파일을 생성하고 값을 채워 넣는 로직을 최초에 다음과 같이 작성하였습니다. 아래로는 계속 트러블 슈팅과 관련된 얘기가 나올 예정이라, 혹시 바로 결과를 확인하실 분들은 아래로 내려가시면 됩니다
최초 .env 파일 생성 Github Actions 로직
- name: .env 생성
run: |
echo ${{ secrets.ENV }} > .en
이 로직의 문제는 echo 명령어가 수행될 때 쌍따옴표 “”
가 존재하지 않기 때문에 에러가 발생합니다. 따라서 echo 명령어 뒤에 “”
를 추가하여 수정했습니다.
${{ secrets.ENV }}
: Github Seccrets에 등록된 변수를 가져오는 방법입니다.
echo “” 추가 Github Actions 로직
- name: .env 생성
run: |
echo "${{ secrets.ENV }}" > .en
하지만 이 방법은 ENV로 등록한 내용들의 첫 줄만 가져올 뿐 아래에 있는 행들을 읽지 못했습니다.
여기서 약 3~4시간을 허비한 것 같습니다.
Github Secrets에 모든 변수를 직접 하나씩 넣어주는 방법으로 변경했습니다.
Github Secrets에 모든 변수를 직접 추가 Github Actions 로직
- name: .env 파일 생성
run: |
echo "Value of MY_SECRET1: $MY_SECRET1" > .env
echo "Value of MY_SECRET2: $MY_SECRET2" > .env
echo "Value of MY_SECRET3: $MY_SECRET3" > .env
echo "Value of MY_SECRET4: $MY_SECRET4" > .env
echo "Value of MY_SECRET5: $MY_SECRET5" > .env
echo "Value of MY_SECRET6: $MY_SECRET6" > .env
...
env:
MY_SECRET1: ${{ secrets.MY_SECRET1 }}
MY_SECRET2: ${{ secrets.MY_SECRET2 }}
MY_SECRET3: ${{ secrets.MY_SECRET3 }}
MY_SECRET4: ${{ secrets.MY_SECRET4 }}
MY_SECRET5: ${{ secrets.MY_SECRET5 }}
MY_SECRET6: ${{ secrets.MY_SECRET6 }}
...
Secrets에 등록된 변수를 하나씩 직접 ehco 명령어를 통해 .env에 적어주는 방식입니다.
이렇게 되면 Secret에 변수를 하나씩 등록할 때마다 CI 파일도 수정해야 하기 때문에 관리가 어려워질 것이라고 생각하여, 더 나은 방법을 찾아야 했습니다.
그러던 중 다음과 같은 글을 발견하였습니다.
해당 글에서는 동적인 방법으로 .env 파일을 추가하는 방법을 설명합니다. 이를 적용하였습니다.
최종 Github Actions 로직
- name: .env 파일 생성
run: |
jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' <<< "$SECRETS_CONTEXT" > .env
env:
SECRETS_CONTEXT: ${{ toJson(secrets) }}
이 방법을 사용하면 Secret이 계속 추가되어도 CI 파일은 수정하지 않아도 됩니다.
번외, 테스트 결과를 PR에 등록하여 결과를 공유한다.
- name: 테스트 결과 PR 등록
uses: EnricoMi/publish-unit-test-result-action@v1
if: ${{ always() }}
with:
files: '**/build/test-results/test/TEST-*.xml'
단위 테스트가 많아질수록 모든 테스트가 잘 작동하는지 파악하는 것은 어렵다고 판단하여, 간편하게 실패한 테스트를 볼 수 있는 방법이 없을까 고민하다가 적용하게 되었습니다.
근데 계속 .env를 사용해야 하나?
어차피 Secret에 등록한 변수를 application.yml 에 넣어주는 것은 똑같은데, .env를 활용해야하나 라는 생각이 들었습니다.
이 문제는 팀원들과 얘기를 해봐야 할 것 같습니다.