DevYGwan
SSH 방식으로 CICD 구축 시 주의할 점 :: 서버 내 환경 변수 설정 처리 본문
이번 주제는 github actions로 CICD를 적용하는 과정에서 발생한 문제를 공유하려고 합니다. 저는 서버를 배포하기 위해 AWS EC2 인스턴스를 사용해서 배포를 진행했습니다. CICD를 적용하는 방식에는 여러가지 방식이 있는데, 저는 SSH 방식으로 서버 내에서 빌드 & 배포를 진행하는 방식을 사용했습니다. 간단하게 SSH란, SSH는 Secure Shell의 줄임말로, 원격 호스트에 접속하기 위해 사용되는 보안 프로토콜입니다.
배포를 진행하면서 배포에는 필요하지만 외부에 공개되면 안되는 값들은 변수로 설정해 다른사람에게는 공개가 안될 수 있도록 처리해야 합니다. 제가 생각하기에 이러한 값들은 크게 2가지로 나눌 수 있다고 생각합니다.
- github actions가 CICD 동작 시에 기본적으로 알아야 할 배포 서버 관련 데이터
- 서버가 동작하는데 필요한 데이터 (springboot의 .properties & .yaml 파일에 들어가는 환경변수 값들)
그렇다면 이러한 데이터를 어디다 저장하는게 좋을까요?
※ github actions가 CICD 동작 시에 기본적으로 알아야 할 배포 서버 관련 데이터
github actions가 CI/CD 동작 시에 기본적으로 알아야 할 배포 서버 관련 데이터 (EC2 IP주소, EC2 Username 등)는 주로 Actions secrets에 저장하는 편입니다. github actions가 참조해야 할 데이터기 때문에 Github내에서 관리되는 것이 바람직하다고 생각해 해당 저장소에 저장했습니다.
CI/CD를 적용할 Github 내 Repository로 이동해 Setting-> Secrets and variables -> Actions에서 변수를 설정 가능
※ 서버가 동작하는데 필요한 데이터 - 환경변수 값
서버가 동작하는데 필요한 springboot 내의 환경변수 값들은 EC2 서버 내의 특정 포트로 실행되고 있는 프로세스(서버)가 사용하는 데이터이기 때문에 EC2 내에서 관리가 되는 것이 좋다고 생각했습니다. 물론 yaml 파일 내의 환경변수 값들을 변수로 설정하는 것이 아닌 값을 넣고 환경 변수 파일을 github내에 올리지 않고 배포 시에 복사해서 생성하는 식으로 처리할 수도 있습니다. 하지만 저는 환경 변수 파일은 올리돼, 노출되어선 안되는 민감한 데이터(Jwt secret key값, DB 정보 등)는 변수로 설정하는 형태를 선호합니다. 협업 시에도 어떤 값들이 필요한지 한번에 알 수 있고, 민감한 데이터만 따로 관리할 수 있다는 장점이 있기 때문입니다.
# appication.properties
open-ai.key=${OPEN_AI_KEY}
이렇게 처리하기 위해선 OPEN_AI_KEY 값을 서버 내에서 선언해줘야 됩니다. "EC2 환경변수 설정" 이라는 키워드로 검색해보면 정보가 많이 나옵니다. 대부분 ubuntu 기준, .bashrc 파일 젤 밑에다 export로 선언해놓고 source 명령어를 통해 적용하면 쉽게 설정할 수 있다고 나옵니다. 아래와 같은 방식으로 환경 변수를 등록할 수 있습니다.
# .bashrc 파일
export OPEN_AI_KEY=openaiapikey
# 활성화
source ~/.bashrc
그런데 이 방법을 통해 환경변수를 지정하면 CI/CD를 적용할때 문제가 발생합니다.
정확히 말하면 CI/CD를 ssh 방식으로 배포할 때 문제가 발생합니다. 설명하기에 앞서, 간단히 CI/CD를 설명드리자면,
- CI : 지속적 통합(Continuous Integration)
- CD : 지속적 제공/배포 (Continuous Delivery/Deployment)
소프트웨어 개발과 배포 과정을 자동화하고 효율적으로 만들어주는 일련의 과정을 포함하는 작업을 말합니다.
※ 예시 코드
CI/CD를 적용하기 위한 예시 코드는 다음과 같습니다.
name: deploy chatGPT server to AWS EC2
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
env:
PORT: ${{ secrets.SERVER_PORT }}
steps:
- name: ssh로 서버 접근 & git pull 후 재배포 진행
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_PRIVATE_KEY }}
script_stop: true
script: |
source ~/.bashrc
cd /home/ubuntu/spring-chatgpt-communication
git pull origin main
./gradlew clean build
sudo fuser -k -n tcp ${{ env.PORT }} || true
nohup java -jar build/libs/*SNAPSHOT.jar > ./output.log 2>&1 &
간단하게 설명드리자면, Github Actions에 저장된 secrets 변수값으로 설정된 EC2 인스턴스에 접근해 scripts에 해당하는 명령어를 실행합니다. EC2 인스턴스 접근은 "appleboy/ssh-action" 라는 액션을 사용하는데, 이 액션은 GitHub Actions 워크플로우 내에서 SSH(Secure Shell)를 통해 원격 서버에 연결하고 명령을 실행할 수 있도록 도와줍니다. 해당 액션을 사용한 이유는 github actions marketplace에 ssh action중 가장 많은 Stars수가 있기 때문입니다. 이때 ~/.bashrc 파일에 위에서 말했던 spring에서 사용하는 환경 변수에 대한 선언은 미리 되어 있다고 가정합니다. (Export 구문)
그런데, 이렇게 작성한 후 CICD를 처리하려고 했더니 계속 오류가 발생했습니다. 오류 내용은 다음과 같습니다.
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springConfig': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'OPEN_AI_KEY' in value "${OPEN_AI_KEY}"
OPEN_AI_KEY 키 값이 제대로 설정되지 않았다 라는 오류입니다. 그래서 scripts가 잘못되었는지 확인하기 위해 scipts 내용을 직접 입력해보았습니다. 그런데 이렇게하면 제대로 입력이 되는 것을 확인할 수 있었습니다. 그렇다면 scripts가 문제가 있는 것이 아니라 CI/CD과정에서 발생하는 문제라는 것을 인식했습니다.
※ 문제 해결
이 문제를 해결하기 위해 여러 시도를 했습니다. CICD내의 스크립트에 echo로 OPEN_AI_KEY을 echo로 출력해봤지만 계속해서 출력이 되지 않았고 계속해서 출력하기 위해 시도해봤지만 OPEN_AI_KEY 값이 설정되지 않았습니다. 그래서 이 문제를 진환이형이랑 공유하고 같이 고민한 결과, 다르게 접근해 볼 필요가 있다고 생각했습니다. 접근 과정은 크게 아래와 같습니다.
※ bashrc 말고 env.sh 로 테스트해보자.
-> 어 돼네? 그러면 bashrc 파일이 안되는 이유가 뭘까?
※ bashrc에서 위에 예상하지 못한 처리가 있나?
-> bashrc 파일을 확인해보자
-> 어? 생각하지 못한 코드가 있네? 특정 조건에 따라 return하는 문장이 있네?
-> 이 부분이 어떤 건지 검색해보자
※ 한번 export를 선언한 위치를 확인해보고 return하는 문장 위에 올려볼까?
-> 어 되네? 아 이 문제였구나!
※ 방법 1 : .bashrc 파일 대신에 env.sh로 변경
name: deploy chatGPT server to AWS EC2
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
env:
PORT: ${{ secrets.SERVER_PORT }}
steps:
- name: ssh로 서버 접근 & git pull 후 재배포 진행
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_PRIVATE_KEY }}
script_stop: true
script: |
source ~/env.sh # 바뀐 부분
cd /home/ubuntu/spring-chatgpt-communication
git pull origin main
./gradlew clean build
sudo fuser -k -n tcp ${{ env.PORT }} || true
nohup java -jar build/libs/*SNAPSHOT.jar > ./output.log 2>&1 &
바뀐 부분은 환경 변수 설정을 .bashrc -> env.sh 로 변경했습니다. 그랬더니 문제가 해결됐습니다... 허무하면서도 신기하더라구요... 그래서 .bashrc 파일은 왜 안되는지 고민해봤습니다. 그런데 문제는 .bashrc 파일이 아니였습니다. .bashrc 파일에서 변수 선언의 "위치" 문제였습니다.
※ 방법 2 : .bashrc 파일에서 Export의 위치를 젤 하단이 아닌 젤 상단으로 변경
.bashrc파일에서 변수를 선언할때 젤 하단에 선언했었는데, 이게 위의 내용으로 인해 제대로 동작하지 않을 수 있겠다 라는 생각이 들어 이번엔 변수 선언을 젤 상단에서 진행했습니다. 그랬더니 제대로 배포가 완료됐습니다... 왜 이런 결과가 도출돼었는지 .bashrc파일을 분석해보았습니다. 그랬더니,
# If not running interactively, don't do anything
case $- in
*i*) ;;
*) return;;
esac
이런 코드가 파일 상단에 있는 것을 확인할 수 있었습니다. 주석을 해석해보니 "interactively(상호작용)하게 실행하지 않으면 아무것도 실행되지 않는다." 라는 의미였습니다. 해당 내용을 검색해보니,
From what I have read, this command is to prevent the sourcing of the .bashrc file on a remote shell (rsh) or secure shell (ssh).
이런 의미였습니다. 직역하자면, 이 명령은 원격 셸(rsh) 또는 보안 셸(ssh)에서 .bashrc 파일을 소싱하는 것을 방지하는 것을 의미합니다. 따라서 EC2 내에서 작업할때는 문제가 되지 않았지만, ssh을 통해 원격으로 접속했을 경우에는 위의 코드로 인해 바로 return이 되기 때문에 export문이 제대로 실행이 안된 것이였습니다. 또한, 그렇기 때문에 이 문장 위에 export를 사용한다면 문제가 해결되는 것이였습니다. 보안상으로는 아래에다 놓는 것이 좋기 때문에 환경 변수 같은 값 선언을 .bashrc파일 젤 하단에다가 놓는 것을 권장한다는 것을 이번 기회에 알게 되었습니다.
정리
저는 당연히 .bashrc 파일에 문제가 있는 것이 아니고 제 .yml 파일에 문제가 있는 줄 알고 계속해서 삽질을 했는데 알고보니 .bashrc파일의 보안적인 처리로 인해 문제가 되었음을 알게 되었습니다. 확실히 잘 알고 쓰는 거랑 잘 모르고 쓰는 건 정말 다른 것 같다는 생각이 들었습니다. 지금이야 아주 간단한 CICD였지만, 복잡한 CICD였을 경우 문제를 찾는 것이 정말 어려웠을 것 같다는 생각이 들었습니다.
결론적으로 제가 선택한 방법은, spring 서버에서 사용하는 환경 변수 파일을 bashrc 파일이 아닌 다른 추가적인 파일(env.sh 같은)로 생성해 관리하는 것이였습니다. bashrc 파일은 제가 생각하지 못하는 내용이나 실제 ubuntu서버 관련된 데이터들을 가지고 있을 확률이 높기 때문에 spring서버 관련된 데이터는 따로 관리하는 것이 관리적인 측면에서 좋다고 생각했고 해당 경로를 변수로 설정하여 외부에 노출하지 않는다면 보안적으로 더 좋을 수 있을 것이라고 생각 했기 때문입니다.
따라서 저의 최종적인 .yml 코드는,
name: deploy chatGPT server to AWS EC2
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
env:
SERVER_PORT: ${{ secrets.SERVER_PORT }}
ENV_FILE_PATH: ${{ secrets.ENV_FILE_PATH }}
steps:
- name: ssh로 서버 접근 & git pull 후 재배포 진행
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_PRIVATE_KEY }}
script_stop: true
script: |
source ${{ env.ENV_FILE_PATH }}
cd /home/ubuntu/spring-chatgpt-communication
git pull origin main
./gradlew clean build
sudo fuser -k -n tcp ${{ env.SERVER_PORT }} || true
nohup java -jar build/libs/*SNAPSHOT.jar > ./output.log 2>&1 &
'Study > DEVELOP' 카테고리의 다른 글
Jitpack & github releases를 사용한 라이브러리 배포 (1) | 2024.09.13 |
---|---|
Coding Style : Case Style ( Naming Convention ) (0) | 2022.12.27 |
MVC Pattern (1) | 2022.12.27 |