Close

Git으로 전환할 때 Maven 종속성 다루기

Nicola Paolucci 얼굴 사진
Matt Shelton

개발자 애드보케이트


Git으로 마이그레이션하고 있으며 Git-flow를 좋아합니다. 이제 무엇을 해야 할까요? 모두 테스트해 봅시다! 우리 팀은 훌륭합니다. 팀은 Confluence에서 개발자 워크플로의 히트 목록을 만들었습니다. 모든 내용은 우리가 팀으로서 해왔던 작업과 향후에 해야 할 모든 이상한 작업을 기반으로 했습니다. 그러다가 자체 프로젝트를 미러링하는 프로젝트 구조에서(단, 코드가 없는 pom.xml) 모든 워크플로를 시도했습니다.

이 모든 상황에서 Maven 종속성이 가장 큰 문제로 나타났습니다.

Maven 빌드 번호 지정 Maven은 릴리스할 때까지 1.0.0-SNAPSHOT 빌드를 생성합니다. 릴리스하면 -SNAPSHOT이 제거되고 버전은 1.0.0이 됩니다. 빌드 프로세스는 후속 작업에서 1.1.0-SNAPSHOT과 같은 빌드가 생성될 수 있도록 이후 마이너 버전 증가를 지원할 수 있어야 합니다. 세 자리 숫자에만 얽매이지 않아도 됩니다. 프로젝트를 시작할 때 정의할 수 있지만 우리는 세 자리 숫자를 사용합니다. -SNAPSHOT 부분을 이해하는 것은 매우 중요합니다. 이것은 항상 프로젝트의 최신 사전 릴리스라는 것을 나타냅니다.

아티팩트


이러한 모든 워크플로에서 가장 큰 문제는 프로젝트 버전 및 프로젝트 간 종속성을 적절하게 관리하는 방법이었습니다.

빌드를 위해 Maven 종속성을 가져올 때마다 기본적으로 인터넷에서 종속성을 풀다운합니다. 이러한 아티팩트는 후속 빌드를 더 빠르게 수행할 수 있도록 로컬에 저장됩니다. 이것을 더 쉬운 방식으로 수행하는 한 가지 솔루션은 로컬 네트워크의 아티팩트 리포지토리가 외부 종속성의 로컬 캐시 역할을 하도록 사용하는 것입니다. LAN 가져오기는 가장 빠른 CDN에서 다운로드하는 것보다 거의 항상 더 빠릅니다. 우리는 Artifactory Pro를 아티팩트 리포지토리로 사용합니다. 또한 다중 모듈 구조를 가지고 있기 때문에 자체 빌드 아티팩트도 Artifactory에 저장합니다. 공통 패키지 중 하나를 빌드할 때 Maven 종속성 해결을 통해 특정 버전을 풀하여 아티팩트 리포지토리에서 아티팩트를 바로 가져올 수 있습니다.

이것은 순조롭게 작동합니다. 또한 Artifactory를 사용하면 인스턴스 간에 아티팩트를 동기화할 수 있으므로 Artifactory를 사용하여 프로덕션 배포를 위해 릴리스 리포지토리를 데이터 센터에 복제하려는 경우에도 별도의 프로세스를 구축하지 않아도 작업을 수행할 수 있습니다.

데이터베이스
관련 자료

전체 Git 리포지토리를 이동하는 방법

Bitbucket 로고
솔루션 보기

Bitbucket Cloud에서 Git에 대해 알아보기

Maven 종속성, 기능 브랜치 및 풀리퀘스트


우리 빌드는 모두 Artifactory에 저장됩니다. SVN에서는 최신 2개의 스냅샷 빌드를 보관하기 위한 스냅샷 리포지토리, 아직 승인되지 않은 릴리스 빌드를 위한 스테이징 리포지토리 및 프로덕션 환경으로 전환할 수 있는 빌드를 위한 전용 릴리스 리포지토리를 사용했습니다.[1] 이러한 빌드는 앞에서 설명한 것처럼 번호가 지정되어 있으며 리포지토리와 버전에 따라 예측 가능한 URL 패턴으로 검색할 수 있습니다.

각 개발자의 기본 워크플로는 작업을 위해 개발 브랜치에서 기능 브랜치를 만들고 완료한 다음 풀리퀘스트를 보내 해당 작업을 개발 브랜치에 다시 병합하는 것이었습니다. 단일 프로젝트의 경우 이 워크플로는 대부분 문제 없이 작동하지만 우리가 처음으로 직면한 문제와 전체 마이그레이션을 진지하게 다시 생각하게 만든 문제에 대해 이야기해 보겠습니다.

앞서 말했듯이 우리 프로젝트 간에는 여러 종속성 계층이 있습니다. 역사적으로나 전략적으로나 우리 제품에는 그럴 만한 이유가 있습니다. 우리는 이 문제를 제거할 대체 아키텍처를 고려했지만 오히려 다른 문제가 생겼습니다. 물론 우리 삶을 더욱 편리하게 만들 수도 있습니다(또 실제로 그렇게 했지만 이 내용은 다음 게시물에서 다루겠습니다). 하지만 현재로서는 구조를 그대로 유지하는 것이 전략적입니다.

개발자 A(Angela라고 지칭)가 Jira에서 기능에 대한 작업을 시작합니다. 이 작업에는 2개의 브랜치가 필요합니다. 하나는 공통 프로젝트 브랜치, 다른 하나는 제품 X의 브랜치입니다. 공통 프로젝트용 버전은 2.1.0-SNAPSHOT이며, 제품 X용 버전은 2.4.0-SNAPSHOT입니다. Angela는 한동안 로컬에서 작업하다가 마침내 Bitbucket Server로 다시 푸시합니다. Bamboo는 이러한 변경 사항을 파악하여 공통 패키지를 구축하고 common-2.1.0-SNAPSHOT을 Artifactory에 업로드한 후 common-2.1.0-SNAPSHOT에 종속되는 제품 X를 구축하고 productX-2.4.0-SNAPSHOT도 업로드합니다. 단위 테스트도 통과합니다!

개발자 B는 Bruce라고 부르겠습니다. Bruce는 Jira에서 다른 제품인 제품 Y에 대한 기능을 작업하기 시작했습니다. 이 작업에도 2개의 브랜치가 필요합니다. 하나는 공통 프로젝트 브랜치, 다른 하나는 제품 Y의 브랜치입니다. 공통 프로젝트용 버전은 위와 마찬가지로 2.1.0-SNAPSHOT이며, 제품 Y용 버전은 2.7.0-SNAPSHOT입니다. Bruce는 한동안 로컬에서 작업하다가 마침내 변경 사항을 Bitbucket Server로 푸시합니다. Bamboo는 이러한 변경 사항을 파악하여 공통 패키지를 구축하고 common-2.1.0-SNAPSHOT을 Artifactory에 업로드한 후 common-2.1.0-SNAPSHOT에 종속되는 제품 X를 구축하고 productX-2.4.0-SNAPSHOT도 업로드합니다. 단위 테스트도 통과합니다!

그러는 동안 Angela는 제품 X 코드에서 작은 버그를 발견하고 수정 사항의 유효성을 검증하기 위해 단위 테스트를 작성합니다. 그녀는 테스트를 로컬로 실행하여 통과합니다. Angela가 변경 사항을 Bitbucket로 푸시하자 Bamboo에서 변경 사항을 파악하여 제품 X를 빌드합니다. 빌드는 성공하지만 일부 단위 테스트는 실패합니다. Angela가 새로 작성한 내용이 아니라, 기능에 대한 초기 변경 사항의 첫 번째 내용입니다. Bamboo 빌드가 Angela의 로컬 빌드가 찾지 못한 회귀를 발견한 것일까요? 이것이 어떻게 가능할까요?

Angela의 공통 종속성, 즉 Bamboo가 제품 X를 구축할 때 풀한 종속성은 더 이상 그녀의 복사본이 아니었기 때문입니다. Bruce가 기능 빌드를 완료하면서 Artifactory에 common-2.1.0-SNAPSHOT을 덮어썼습니다. 두 개발자 모두 자체 브랜치에서 분리되어 작업하고 있었기 때문에 소스 코드 충돌은 없었지만 Maven 아티팩트 검색의 정보 출처가 손상되었습니다.

머리를 맞대고 해결 방법을 찾기 시작했습니다.

이 문제를 발견하고 약 한 달 동안 우리는 이 문제를 해결하기 위해 모든 방법을 시도했습니다. TAM[2]을 통해 git-flow를 사용하는 Bamboo 팀원들과 이야기를 나누고 git-flow의 Java 구현인 git-flow를 관리하는 개발자와 이야기를 나눴습니다. 이러한 대화는 모두 큰 도움이 되었지만 각 개발자가 기능에 대해 작업할 때마다 수동 단계 목록을 거치도록 요구하는 프로세스 외에는 괜찮은 해결 방법을 찾을 수 없었습니다.

무엇을 고려했는지 궁금하다면 우리가 시도한 모든 방법은 다음과 같습니다.

1. 브랜치를 만들었을 때 또는 만든 직후에 버전 번호를 수정합니다.

  • mvn jgitflow:feature-start를 사용하여 브랜치를 만들 수 있습니다.
  • Bitbucket Server 후크 또는 로컬 Git 후크를 사용할 수 있습니다.
  • 브랜치를 만든 후 mvn version:set-version을 사용하여 수동으로 설정할 수 있습니다.
  • [maven-external-version] 플러그인으로 변경을 자동화할 수 있습니다.

2. 브랜치를 완료하고 다시 개발 브랜치로 병합할 때 버전 번호를 수정합니다.

  • mvn jgitflow:feature-finish를 사용하여 브랜치를 완료할 수 있습니다.
  • Git 병합 드라이버를 사용하여 pom 충돌을 처리합니다.
  • Bitbucket Server의 비동기 post-receive 후크를 사용합니다.

3. 전부 수동으로 수행합니다. (농담입니다. 이 옵션을 그리 오래 고려하지도 않았습니다.)

가능한 워크플로


핵심 개념을 기억하고 되짚어보면 submodule이 어떤 워크플로는 잘 지원하지만 다른 워크플로는 최적으로 지원하지 않는 것을 이해할 수 있습니다. 하위 모듈이 괜찮은 선택이 되는 시나리오는 적어도 세 개 있습니다.

  • 구성 요소 또는 하위 프로젝트가 너무 빠르게 변경되거나 예정된 변경으로 인해 API가 중단되는 경우 안전을 위해 코드를 특정 커밋에 잠글 수 있습니다.

  • 자주 업데이트되지 않는 구성 요소가 있고 공급업체 종속성으로 추적하려는 경우입니다. 예를 들어 저는 vim 플러그인에 대해 이 작업을 수행합니다.

  • 프로젝트의 일부를 제3자에게 위임하고 특정 시간이나 릴리스에 작업을 통합하려는 경우입니다. 다시 한 번 강조하자면 업데이트가 너무 빈번하지 않을 때 효과적입니다.

잘 설명된 시나리오에 대해 finch에게 감사를 드립니다.

이러한 옵션에는 각각 부작용이 있었습니다. 주로 개발자가 기능 브랜치가 필요할 때마다 수동 단계를 수행해야 했습니다. 하지만 우리는 개발자가 항상 기능 브랜치를 만들기를 원했습니다. 또한 대부분의 경우 풀리퀘스트를 효과적으로 사용할 수 없었는데, 이것이 가장 큰 걸림돌이었습니다.

우리가 왜 잘못된 방향에서 이 문제에 접근하고 있었는지에 대한 (충격적인) 결론이 밝혀지기 전까지 거의 두 달 동안 1~2명이 이 문제에 매달렸습니다.

모든 것을 관리할 수 있는 하나의 버전


이제 와서 돌이켜보면 가장 큰 실수는 우리가 원하는 워크플로를 구현하는 데 이미 가지고 있던 도구를 사용하지 않고 git-flow 도구에만 집중했다는 것이었습니다. 우리는 다음을 보유하고 있었습니다.

  • Jira Software
  • Bamboo Server
  • Maven
  • Artifactory Pro

알고 보니 우리에게 필요한 도구는 이 두 가지가 전부였습니다.

우리 엔지니어 중 한 명은 빌드 관리 자체가 아니라 아티팩트를 덮어쓰는 것이 문제이므로 Artifactory를 대신 수정해야 한다는 아주 현명한 생각을 했습니다. 그의 아이디어는 Maven 속성을 사용하여 스냅샷 리포지토리 URL을 Jira 이슈 ID가 포함된 사용자 지정 URL로 설정한 다음 Artifactory에서 사용자 지정 템플릿을 사용하여 해당 아티팩트를 동적으로 만들어진 리포지토리에 작성한다는 것이었습니다. Maven의 종속성 확인자는 우리가 제품 및 공통 브랜치에서 작업하는 경우처럼 분기할 필요가 없는 경우 개발 스냅샷 리포지토리에서 아티팩트를 찾습니다.

우리는 빌드 설정 파일에 이 편리한 속성 변수를 설정하고 Maven 빌드 수명 주기 초기에 Maven 플러그인을 작성하여 채웠습니다. 문서상으로 보면 이것은 획기적인 아이디어 같았고 팀이 이 문제를 해결하기 위해 더 열심히 일하도록 다시 활기를 불어넣어 주었습니다. 하지만 문제는 우리가 실제로 이것을 수행할 수 없다는 것이었습니다. Maven 수명 주기의 가장 초기 단계는 '검증'입니다. 검증을 위해 바인딩된 플러그인을 실행할 무렵에는 리포지토리 URL이 이미 확인되었습니다. 그렇기 때문에 변수는 채워지지 않았으며 URL에도 브랜치 이름이 지정되지 않았습니다. 개발 스냅샷과 별도의 리포지토리에 있는 레이아웃을 사용했지만 병렬 개발을 위해 분리되지는 않았습니다.

다시 머리를 맞대고 해결 방법을 찾아 나섭니다.

맥주를 한 잔 마신 후, 앞서 언급한 엔지니어는 Maven에 기능을 추가하는 또 다른 방법인 확장 기능에 대해 더 파고들었습니다.

“삶의 모든 문제의 원인이자 해결책은 바로 맥주” - 호머 심슨

플러그인 같은 확장 기능은 Maven 워크플로를 향상시킬 수 있는 강력한 기능을 제공하지만 수명 주기 목표보다 먼저 실행되고 Maven 내부 기능에 더 쉽게 액세스할 수 있습니다. 우리는 RepositorUtils 패키지를 활용하여 Maven이 사용자 지정 파서를 사용하여 URL을 다시 평가한 다음 업데이트된 값으로 다시 설정하도록 했습니다.[3]

확장 기능을 갖추고 테스트를 완료한 후, 우리는 "이것은 불가능한 일이야"에서 "월요일에 이 일은 꼭 가능하게 될 거야. 그러니 내일까지 10페이지 분량의 문서를 작성해야겠어"로 생각이 바뀌면서 마이그레이션 전 작업을 차례로 시작했습니다. 새로운 개발 워크플로를 달성하기 위해 도구가 함께 작동하는 방식을 비롯해 프로세스에서 배운 몇 가지 교훈에 대해 곧 자세히 설명하겠습니다.

[1]: 여기서 한 가지 단점은 Artifactory REST API를 실행하기 위해 작성한 스크립트를 사용하여 스테이징에서 릴리스로 빌드를 "승격"시켜야 한다는 것입니다. 충분히 빠르지만 더 많은 자동화가 필요합니다.

[2]: TAM(기술 담당 관리자). 자세한 내용은 여기를 참조하세요.

[3]: 초기 개발 작업 후 이것이 항상 작동하도록 하려면 훨씬 더 많은 작업을 수행해야 한다는 것을 깨달았습니다. 예를 들어, Artifactory에서 다른 엔지니어의 스냅샷이 로컬 스냅샷보다 최신인 경우 Maven은 원격 아티팩트를 가져옵니다. 그게 더 최신이고 더 나을 테니까요, 안 그런가요?


Footnotes

[1]: One downside here was that I had to use a script I wrote to hit the Artifactory REST API to "promote" builds from staging to release. It's fast enough, but begging for more automation.

[2]: Technical Account Manager. More information here.

[3]: After the initial development efforts, we found that we had to do even more to make this work 100% of the time, like when a snapshot is newer in Artifactory (from another engineer) than your local snapshot, Maven grabs the remote artifact because hey, it's NEWER, so it must be BETTER, right?

Matt Shelton
Matt Shelton

Matt Shelton은 DevOps 지지자이자 실행자로 아메리카 대륙에서 Atlassian에 대한 DevOps(및 기타 관련) 서비스 제공을 감독합니다. 블로그 글은 더 이상 거의 쓰지 않으며 더 효과적인 업무 방식을 지지하는 차세대 지지자를 양성하는 데 집중합니다.


이 문서 공유

여러분께 도움을 드릴 자료를 추천합니다.

이러한 리소스에 책갈피를 지정하여 DevOps 팀의 유형에 대해 알아보거나 Atlassian에서 DevOps에 대한 지속적인 업데이트를 확인하세요.

도구로 가득한 벽을 사용하여 협업하는 사람들

Bitbucket 블로그

DevOps 일러스트레이션

DevOps 학습 경로

Atlassian 전문가와 함께 하는 Demo Den 기능 데모

Bitbucket Cloud가 Atlassian Open DevOps와 작동하는 방법

DevOps 뉴스레터 신청

Thank you for signing up