현재 개발단계에서 은밀하게 관리해야할 파일은 두 개다.
리액트에서는 각종 라이브러리 키들이 포함된 .env
스프링에서는 s3, mail 인증등에 사용된 계정정보들이 포함된 application-private.yml
로컬환경에서는 이를 개발자들끼리 알아서 공유해서 빌드하면 되지만 CI/CD 파이프라인을 만드려면 어딘가에는 올려놓고 관리를 해야한다.
이 과정에서 2~3일정도 삽질을 했고 그에 대한 노하우를 기록해둔다.
목차
1. CICD 파이프라인 점검
1. 리액트는 빌드 후 환경변수 적용안됨
2. 스프링은 환경변수 적용에 우선순위가 있음
1. CICD 파이프라인 점검
현재 github Actions 에서 사용하는 CI/CD 파이프라인 Actions.yml 의 프로세스는 다음과 같다.
먼저 github 에서 레포지토리를 가져온후 github 클라우드상에서 Dockerfile 을 이용해 도커 이미지로 빌드한후 도커 허브에 푸쉬한다
(프론트엔드도 마찬가지로 작업하고 github Actiono 은 jenkins 와 다르게 병렬빌드를 지원해서 이 과정이 빠르다)
이후 EC2에 도커컴포즈 파일을 비롯해 도커컨테이너 실행에 필요한 파일 폴더들을 EC2에 밀어넣고 도커허브에 있던 이미지를 받아와 컨테이너를 모두 실행시킨다.
본래는 github secrets 에 SSH_HOST 나 SSH_KEY 같은 개별변수들은 그 때 그 때 파이프라인에 삽입시키고, .env 와 application-private.yml 같은 항목이 많고 변경이 잦을 파일은 통째로 secrets 에 올린 후, 배포단계에서 파일로 만들어 남들몰래(?) EC2에 밀어넣으려고 했다.
2. 리액트는 빌드 후 환경변수가 적용이 안됨
# 리액트 서버
frontend:
networks:
- idealstudy-network
container_name: frontend
image: idealstudy/frontend-app:latest
env_file:
- /home/ubuntu/secrets/.env
ports:
- "3000:80" # Nginx 80포트를 사용
위는 리액트 서버를 실행시키는 도커컴포즈 일부이다.
CI/CD 과정에서 EC2에 .env 파일을 생성시켰으니 env_file 로 함께 실행시키면 된다고 생각했으나 계속 환경변수가 적용이 안됐다.
결론은 리액트는 서버를 실행시키면서 환경변수를 읽는게 아니고 빌드시점에 이미 환경변수가 주입되기 때문이었다.
env_file 에 .env 를 명시하면 '도커 컨테이너 환경변수' 로는 들어가지만 이게 리액트 실행파일에 반영이 안되었던 것임.
해결방법은 CICD 과정에서 github 코드들을 도커 이미지 빌드를 하는 당시에 시크릿을 불러와서 .env 파일을 만들어 이와함께 빌드해서 도커허브에 올리는 것이다.
다만 이 방식은 누구나 도커 이미지를 받으면 .env 를 볼 수 있다는 단점이 있는데, 지금은 .env 가 API 요청주소, 게시판 라이브러리 api key정도라 문제가 안될듯해서 이렇게 처리를 하지만 향후 .env에 민감한 정보가 담아야한다면 선택지가 여러개가 있겠다.
- 이미지 빌드시 --build-args 로 .env 파일에 있는 변수를 주입하고 .dockerignore 에 .env 를 명시하면 .env 에 있던 변수를 반영해서 빌드는 하지만 .env 는 도커이미지에서 제외됨
- 기존과 같이 .env 는 제외해서 빌드를 하되, nginx 에서 .env 변수들을 등록해주는 방법이 있다고함 (아마 리버스프록시 역할을 하면서 환경변수를 동적으로 전달하는게 아닌가 싶음)
- 백엔드 API에 .env 파일에 대한 변수를 담아서 초기렌더링시 .env 에 있던 변수를 받아서 실행시키는 방법도 있을거같은데 이건 좀 괴상한 방법같음
=> 여하튼 정적파일을 제공하는 리액트는 한 번 빌드가 되면 환경변수는 내부적으로 변경이 안된다는 깨달음 (번들파일이나 운영체제의 실행관점에서 설명이 가능하면 좋겠는데 아직은 내 배움이 짧다)
3. 스프링은 환경변수 적용에 우선순위가 있음
profiles:
group:
development:
- "dev"
- "private"
deployment:
- "deploy"
- "private"
production:
- "prod"
- "private" # github 시크릿에서 가져옴
test:
- "test"
active: development
왜냐하면 원래 의도라면 JAR 에 빌드된 application.yml 에는 위와같이 profiles 가 세팅되어있어서 production 이라는 키워드를 활성화시키면 'prod' 와 'private' 이라는 프로파일이 활성화 되면서 application-prod.yml 과 application-private.yml 을 실행시키는 구조다.
# 스프링 서버
server:
networks:
- idealstudy-network
container_name: server
image: idealstudy/backend-app:latest # 로컬에 이미지가 있으면 사용
ports:
- "8080:8080"
volumes:
- /home/ubuntu/secrets/application-private.yml:/application-private.yml # EC2에서 복사한 파일 마운트 (Dockerfile 에서 작업디렉토리를 app 으로 설정)
environment: # 환경변수
- SPRING_PROFILES_ACTIVE=production
- SPRING_CONFIG_LOCATION=file:/application-private.yml
위와같이 도커컴포즈 파일을 실행시키면 마운트된 볼륨의 yml 을 읽어 실행은 시킬 수는 있다.
근데 다만 위와같은 파일을 실행시키면 결론은 아무런 yml 도 실행시키지 않는다.
왜냐하면 Spring Boot의 설정 우선순위 때문.
Spring Boot는 설정 파일을 로드할 때 다음과 같은 우선순위를 따진다.
- 외부 환경 변수 (SPRING_CONFIG_LOCATION, SPRING_CONFIG_NAME)
- JAR 내부 config/ 디렉토리의 application.yml
- JAR 내부 application.yml (클래스패스 기준)
- 외부 디렉토리 (./config/, ./)
- OS 환경 변수
- Spring Cloud Config Server (있는 경우)
Spring Boot는 기본적으로 application.yml 파일을 클래스패스 에서 자동으로 로드한다.
하지만, SPRING_CONFIG_LOCATION 을 사용하여 특정 위치의 설정 파일을 지정하면, Spring Boot는 이 위치에서 설정 파일을 로드하도록 우선순위를 조정하며 다른 설정파일은 무시될 수 있다.
지금 도커컴포즈 상에서는 SPRING_CONFIG_LOCATION 설정으로 외부 파일(file:/application-private.yml) 이 로드되는데 이 때 기본 설정인 application.yml 이 무시될 수 있기 때문이다.
(결론은 프로파일은 production 인데 application-private.yml 만 불러왔으니 아무것도 실행안시키는 것)
# 스프링 서버
server:
networks:
- idealstudy-network
container_name: server
image: idealstudy/backend-app:latest # 로컬에 이미지가 있으면 사용
ports:
- "8080:8080"
volumes:
- /home/ubuntu/secrets/application-private.yml:/application-private.yml # EC2에서 복사한 파일 마운트 (Dockerfile 에서 작업디렉토리를 app 으로 설정)
environment: # 환경변수
- SPRING_PROFILES_ACTIVE=production
- SPRING_CONFIG_LOCATION=classpath:/application.yml,file:/application-private.yml
위와같이 SPRING_CONFIG_LOCATION 에 classpath:/application.yml 로 설정파일을 함께 로드하면 문제가 해결된다.
classpath:의 의미
- 클래스패스는 JAR 파일 내부의 resources 디렉토리와 동일
- 즉, src/main/resources/application.yml 파일이 포함
classpath:/application.yml 은 아래 경로를 가리
- JAR 파일 내부의 /BOOT-INF/classes/application.yml
- 개발 중에는 src/main/resources/application.yml
이를 응용하면 다음과 같은 세팅도 가능하다
- JAR 내부 기본 설정을 유지하고 외부 설정을 덮어쓰고 싶을 때
SPRING_CONFIG_LOCATION=classpath:/application.yml,file:/application-private.yml
- 기본 설정을 무시하고 외부 설정만 사용하고 싶을 때 (application.yml 은 무시됨)
SPRING_CONFIG_LOCATION=file:/application-private.yml
- 다수의 외부 파일을 사용할 때
SPRING_CONFIG_LOCATION=file:/config/application.yml,file:/secrets/application-private.yml
** 참고로 environment와 env_file은 모두 환경 변수를 설정하는 데 사용되지만 environment는 직접 key: value 형식으로 설정하는 반면, env_file은 환경 변수들이 담긴 별도의 파일을 참조한다.
env_file을 사용하면 여러 환경 변수를 한 번에 관리할 수 있고, 환경 변수 파일을 다른 곳에서도 재사용할 수 있어 관리가 용이합니다. 반면 environment는 작은 프로젝트에서 간단하게 변수들을 설정할 때 유용하다.
'[개발일지] > CS 스터디 플랫폼' 카테고리의 다른 글
12장. IntelliJ가 해주는 역할이 뭐였을까. Cursor AI로 넘어오면서 생긴 일들 (=Cursor 쓰면 새로운 디펜던시 반영 어떻게 (0) | 2025.03.20 |
---|---|
11장. 상태관리 라이브러리 Context API 말고 다른거 쓰자. Redux? Zustand? (0) | 2025.02.28 |
9장. 프록시 서버가 포함된 HTTPS 아키텍쳐에서 발생가능한 이슈 (Mixed Content, CORS Policy) (0) | 2025.02.11 |
8장. EC2 .pem 파일을 분실했을 때 해결방법을 생각해보자 (a.k.a /home/ubuntu 폴더를 삭제해버렸다) (0) | 2025.02.04 |
7장. 도메인에 HTTPS 설정해야지. 인증서는 어디서? (0) | 2025.01.30 |