Intro


틀려도 된다. 다만, 정답으로 가고 있는지 확인하자.

요즘 개발 환경과 운영 환경을 구축하면서 부쩍 이런 생각이 들고 있다.

항상 내가 개발하거나 설정하고 있는 내용이 맞는 것인지 정답인지 모르고 진행할 때가 많다.

그런데 너무 완벽하게 하려고 하다보니, 오히려 진도가 나가지 못하고 정해진 시간 내에 해야할 업무를 못하게 되고, 오히려 너무 많은 자료들이나 블로그 글을 보게 되니 내가 무엇을 하려고 했는지 엇갈리게 되는 아이러니한 상황이 발생하게 되었다.

아차! 싶었다. 내가 N년차 개발자도 아니고 비기너일뿐인데 완벽하게 해낼 수 없을 뿐더러 오히려 완벽하게 해냈다면 더 무서운 일이었을 것이다.

그래서 일단 기획하고 의도했던 내용을 수행한 이후 차차 조금씩 고쳐나가기로 생각을 달리 하기로 했다. 교육과정간 마음을 고쳐먹는 일이 상당히 많은 것 같다.

그렇다면 먼저 MVP 핵심 개발을 구현한 뒤 코드 리뷰를 통해 고쳐야 할 부분을 명확하게 알 수 있다. 그렇다면 고치는 작업에만 열중해서 할 수 있으니 보다 효율적인 작업을 할 수 있게 된다!




Week 21

카카오 클라우드 스쿨 21주차 96~100일까지의 공부하고 고민했던 흔적들을 기록하였습니다.

Spring Boot의 설정 파일(application.yml)을 어떻게 숨긴채로 배포할 수 있을까?

파이널 프로젝트에서 사용할 여러 Spring Boot 프로젝트의 초기 설정을 진행하다가 문득 누락한 내용이 있다는 것을 깨달았다.

그것은 바로 application.* 파일과 같은 설정 정보를 어떻게 배포할 때 적용시킬 수 있냐는 것이다.

Spring Boot 프로젝트에서는 application.yml에는 DB나 API Key와 같은 민감정보를 작성하고 이 파일이 그대로 Git에 올라가버리면 매우 위험해진다.

이를 위해 application.yml에 노출되면 안되는 정보들은 작성하지 않고 application-dev.yml, application-prod.yml와 같이 운영환경 별로 구분된 설정파일에 작성하였다. 그리고 application-*.yml 들은 git ignore에 추가하여 Git에 올라가지 않도록 하였다.

그런데 문제는 이후에 Spring Boot 애플리케이션을 배포하고 나서도 application-*.yml 파일을 일일이 운영환경에 수동으로 만들어줘야 한다는 것이다.

이러한 숨겨진 설정 파일들을 실제 배포 프로세스와 함께 자동으로 배포되게 할 수는 없을까? 찾아보다가 Git Submodules에 대해서 알게 되었다.

Git Submodules에 대해서

Git Submodules란 저장소 안에 다른 저장소를 원하는 디렉토리를 복제하는 기능이다.

만약 여러 프로젝트에서 공통으로 사용해야할 설정 파일(라이브러리나 설정 값 등)이 있을 때 A와 B 저장소에 C에서 사용하는 코드의 내용이 중복될 수도 있다. 이 때 서브모듈을 적용한다면 A와 B 저장소에 C 저장소를 공동으로 반영시킬 수 있도록 할 수 있는 것이다. 또한 C 저장소에 수정 작업을 진행하면 A와 B 저장소 모두 수정된 C 저장소의 내용을 적용받게 된다.

그렇다면 application.* 파일을 private 저장소의 Git Submodules으로 사용하면 되지 않을까?

Git Submodules 적용 과정

필자의 파이널 프로젝트 Developers에 Private 저장소를 하나 만들었다.

해당 저장소에 운영환경에서 사용할 설정 파일인 application-prod.yml 파일을 만들어보았다.

자 이제 서브 모듈로 사용할 Private 저장소가 준비되었으니, 메인 저장소에 이 저장소를 서브 모듈로 등록해보자.

메인 저장소로 이동하여 아래 명령어를 통해 서브모듈로 사용할 하위 저장소를 서브모듈로 등록한다.

git submodule add {submodule_repository_url}

상위 저장소에서 하위 저장소를 서브모듈로 추가하면, 상위 저장소에 .gitmodules라는 파일이 만들어진 것을 확인할 수 있다.

$ git submodule add https://github.com/start-dream-team/developers-secret.git
Cloning into '/Users/moon/study/kakaoCloudSchool/project/developers-live/developers-secret'...
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 6 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (6/6), done.

.gitmodules 파일의 내용은 다음과 같다.

[submodule "developers-secret"]
	path = developers-secret
	url = https://github.com/start-dream-team/developers-secret.git

그리고 main 브랜치를 기준으로 서브 모듈을 업데이트 하도록 .gitmodules 파일에 branch = main 구문을 추가하자.

[submodule "developers-secret"]
	path = developers-secret
    url = https://github.com/start-dream-team/developers-secret.git
	breanh = main

이제 마지막으로 설정한 서브모듈을 메인 저장소에 Commit & Push해야 비로소 서브모듈이 등록된다.

위와 같이 상위 저장소에 하위 저장소가 서브모듈로 등록된 것을 볼 수 있다.

서브모듈의 최신 변경사항 가져오기

만약 서브모듈로 사용하는 Private 저장소에 변경사항이 생긴다면 이를 상위 저장소에도 동일하게 반영해주어야 한다.

이 떄는 상위 저장소로 이동하여 아래 명령어를 입력한다면 손쉽게 서브모듈의 최신 커밋사항을 가져올 수 있게 된다.

git submodule update --remote

서브모듈이 포함된 저장소 클론하기

일반적으로 사용하는 클론 명령어로 해당 프로젝트를 클론한다면, 서브모듈 디렉토리는 빈 디렉토리로 받아오게 된다. 이 때는 --recurse-submodules 옵션을 사용하여 클론한다면 서브모듈도 함께 정상적으로 클론하게 된다.

git clone --recurse-submodules {project_url}

해당 명령어로 서브모듈을 등록한 상위 저장소를 클론하였다.

$ git clone --recurse-submodules https://github.com/start-dream-team/developers-live.git
Cloning into 'developers-live'...
remote: Enumerating objects: 111, done.
remote: Counting objects: 100% (111/111), done.
remote: Compressing objects: 100% (58/58), done.
remote: Total 111 (delta 19), reused 99 (delta 15), pack-reused 0
Receiving objects: 100% (111/111), 75.22 KiB | 3.27 MiB/s, done.
Resolving deltas: 100% (19/19), done.
Submodule 'developers-secret' (https://github.com/start-dream-team/developers-secret.git) registered for path 'developers-secret'
Cloning into '/Users/moon/study/kakaoCloudSchool/project/developers-live/developers-secret'...
remote: Enumerating objects: 6, done.        
remote: Counting objects: 100% (6/6), done.        
remote: Compressing objects: 100% (5/5), done.        
remote: Total 6 (delta 0), reused 0 (delta 0), pack-reused 0        
Receiving objects: 100% (6/6), done.
Submodule path 'developers-secret': checked out '1dba42995a382a40872bff165949a7bcf7086811'

콘솔 출력 문구부터 일반적인 클론 문구와 다르게 서브모듈과 관련된 문구가 추가적으로 출력되고, 아래와 같이 서브모듈로 등록한 하위 저장소의 파일들이 정상적 포함되어 있는 것을 확인할 수 있었다.

💡 서브모듈 적용시 주의할 점

서브모듈에 변경사항이 생겼다면 반드시 상위 저장소보다 먼저 변경사항을 Commit & Push하거나 Pull 해야한다. 만약 상위 저장소를 push/pull한 후 서브모듈(하위 저장소)을 push/pull 하게 된다면 상위 저장소에서 CI 등의 작업을 진행할 때 서브모듈의 변경점을 가져가지 못하게 되기 때문이다.

서브모듈의 설정 파일을 포함시켜서 빌드하기

필자의 Spring Boot 애플리케이션은 Gradle 빌드 도구를 사용하고 있기 때문에 Gradle를 이용해 서브모듈의 필요한 설정 파일을 가져오도록 해야 한다.

build.gradle

task copyPrivate(type: Copy) {
    copy {
        from './developers-secret'
        include "*.yml"
        into 'src/main/resources'
    }
}

bootJar {
	...
    copyPrivate
}

위와 같은 설정을 추가하여 서브모듈의 설정 파일을 src/main/resources 디렉토리로 가져올 수 있다. 이 때, 반드시 src/main/resources.gitignore에 추가하여 상위 저장소에 저장되지 않도록 해야함을 유의하자.

.gitignore

# build to not commit with secret
application-*.yml

그리고 copyPrivate이 bootJar 안에서 실행되어야 빌드 시 copyPrivate가 수행되어 실제로 서브모듈의 파일을 복사해오기 때문에 꼭 bootJar에 copyPrivate 옵션을 포함시켜야 한다.

이후 빌드를 진행해보니 정상적으로 빌드가 성공함을 확인하였다.

그리고 빌드가 진행된다면 copyPrivate 옵션이 실행되어 서브모듈의 application-prod.yml 파일을 src/main/resources 디렉토리로 복사해온 것을 확인할 수 있었다.


Jenkins에서의 빌드 여부 확인하기

애플리케이션 자체를 빌드할 때 정상적으로 숨긴 설정 파일을 포함시키는 것을 확인했으니 Jenkins의 CI 과정에서 정상적으로 서브모듈의 설정 파일들을 가지고 빌드하는지 확인해보자.

여기서 사용한 젠킨스 서버는 EC2 인스턴스에 Docker 컨테이너로 실행하여 사용하였다.

실습에 사용한 EC2 인스턴스는 Ubuntu 20.04, t2.small 환경에서 진행하였다.

빌드를 할 때 테스트를 진행하는데 애플리케이션에 포함된 테스트 코드에는 RepositoryTest 코드가 작성되어있다.

그렇기에 서브모듈로 등록한 application-prod.yml 파일을 포함시켜 빌드하지 않았다면 테스트코드를 테스트하는 작업에서 에러가 발생하여 빌드가 실패했을 것이다.

+ gradle clean build -Penv=prod
> Task :clean
> Task :compileJava
> Task :processResources
> Task :classes
> Task :jar
> Task :compileTestJava
> Task :processTestResources NO-SOURCE
> Task :testClasses

> Task :test
2023-03-23T17:23:02.300Z  INFO 1844 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2023-03-23T17:23:02.308Z  INFO 1844 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2023-03-23T17:23:02.333Z  INFO 1844 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

> Task :asciidoctor
2023-03-23T17:23:18.249Z [main] WARN FilenoUtil : Native subprocess control requires open access to the JDK IO subsystem
Pass '--add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED' to enable.

> Task :copyDocument
> Task :resolveMainClassName
> Task :bootJar
> Task :assemble
> Task :check
> Task :build

Deprecated Gradle features were used in this build, making it incompatible with Gradle 8.0.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

See https://docs.gradle.org/7.6/userguide/command_line_interface.html#sec:command_line_warnings

BUILD SUCCESSFUL in 1m 3s
10 actionable tasks: 10 executed
Post stage
[Pipeline] echo
Gradle jar build success
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

다행히도 빌드가 성공하였다. 이 말은 서브모듈로 등록한 설정파일을 애플리케이션 빌드에 잘 포함이 되었기 때문이다.


Git Submodules를 직접 적용해보니 생각보다 어렵지 않게 서브모듈을 적용할 수 있었다. 결국 서브모듈을 사용한다면, 여러 저장소에서 공동으로 사용되는 설정 파일 뿐만 아니라 상수나 로직이 포함된 소스코드도 사용할 수 있겠구나 싶었다.

그리고, 서브모듈을 사용하면 민감정보가 포함된 파일이 Private 저장소에 저장되기 때문에 민감정보를 Public 저장소에 노출시키지 않고도 데이터를 보호하는 것 뿐만 아니라 민감정보의 형상관리까지 가능해진다.

마지막으로 개발자 입장에서 서브모듈을 사용함으로, 민감정보가 포함된 설정파일을 수동으로 운영 환경에 일일이 만들어주지 않아도 된다는 점이 가장 크게 와닿았다.



Final..

파이널 프로젝트가 시작된 지 한달이 지났다. 이제 어느정도 프로젝트의 컨셉은 정해지졌지만, 아직 타이트한 일정이 우릴 기다리고 있다.

Server 단 개발 일정과 Client 개발 일정을 무리없이 2~3주안에 끝내야 하고, EKS Cluster 운영 환경에 배포될 Manifest 파일들도 작성해야 한다.

못할 것이라 생각하지는 않지만 좋은 퀄리티의 수준을 유지할 수 있을지 의문이 든다. 또한, 강사님이나 다른 조 팀장님들에게도 기술적 공유를 하며 피드백을 받으며 방향성에 대해서 끊임없이 생각하게 된다.

프로젝트 중간 점검 회고를 하며 이러한 생각들이 얼추 정리가 되었다.

지금 우리가 하려고 하는 서비스는 무엇인지, 팀원 개개인에게 어떤 기술적 성장을 이룰 수 있을 것인지, 분명히 알고 개발에 임하는 것인지 등을 고민할 수 밖에 없었다.

파이널 프로젝트 과정 속에서 결과물만을 바라보는 것이 아니라 함께 성장하기 위해서 느리더라도 명확하게 알고 넘어가기 위해서 주 단위 스프린트 안에서 진행할 일일 스프린트 횟수를 늘려서 현재 상황과 앞으로의 상황을 보고 일정 관리를 잘 할 수 있도록 해야겠다.

혹여 잘못된 내용이 있다면 지적해주시면 정정하도록 하겠습니다.

참고자료 출처

profile
찍어 먹기보단 부어 먹기를 좋아하는 개발자

0개의 댓글