Android 개발/Gradle・빌드

월요일마다 손으로 올리던 의존성 PR, Renovate로 자동화한 기록

stackD 2026. 8. 17. 18:00
반응형

 

지난 분기였어요. 미뤄둔 의존성 업데이트 28개를 한꺼번에 처리하다 작은 오류 하나 잡는 데 반나절을 날렸습니다. 그날 결심했죠.

 

매주 월요일 아침에 libs.versions.toml 열고 버전 숫자를 하나씩 올리던 그 루틴, 이제 그만하기로요. 라이브러리 릴리스 노트 일일이 읽고, 빌드 한 번 돌려보고, 커밋 메시지 쓰고... 이걸 손으로 반복하다 보니까 결국 "다음 주에 몰아서 하자"가 되더라고요. 그렇게 미루다 분기마다 20~30개가 쌓이는 악순환이었습니다.

 

그래서 Renovate를 붙였습니다. 한 달 정도 돌려보니 월요일 아침 풍경이 완전히 달라졌어요.

 

Renovate가 의존성 PR을 자동으로 올리는 방식

Renovate는 멘드(Mend.io)가 관리하는 오픈소스 의존성 자동화 도구입니다. 깃허브(GitHub), 깃랩(GitLab), Azure DevOps를 다 지원하고, npm·Maven·Go·Docker까지 거의 모든 생태계를 훑어요.

 

저처럼 Gradle 쓰는 분이라면 제일 반가운 점이 하나 있는데요. renovate.json에 Gradle 매니저만 켜두면 libs.versions.toml[versions] 블록을 별도 정규식 없이 알아서 스캔합니다. 새 버전이 나오면 변경 로그를 함께 붙여주고, 거기에 패키지 수명(Age)·채택률(Adoption)·CI 통과율로 산출되는 '병합 신뢰도(Merge Confidence)' 정보까지 묶어서 PR을 자동으로 올려줘요.

 

또 하나 마음에 드는 게 Dependency Dashboard였습니다. 깃허브 이슈로 의존성 현황이 한 장에 정리돼서, 어떤 게 대기 중이고 어떤 게 막혀 있는지 한눈에 보이더라고요.

 

 

첫날 PR 폭탄, schedule과 그룹핑으로 잡는 법

근데 처음 켜자마자 멘붕이 왔습니다. PR이 수십 개가 한꺼번에 쏟아졌어요. 이걸 'PR 폭탄(PR Fatigue)'이라고 부르는데, 자동화하려다 오히려 업무가 마비되는 상황이지요.

 

두 가지로 진정시켰습니다.

 

  1. schedule로 PR 생성 시점을 묶습니다. 저는 월요일 아침으로 고정해서 주중에 알림이 흩어지지 않게 했어요.
  2. 그룹핑 프리셋으로 여러 의존성을 한 PR로 묶습니다. Renovate는 프리셋만 지정하면 서로 다른 생태계까지 하나로 묶어줘서, PR 수가 3~5배 줄어든다고 해요.

 

실제로 그룹핑 적용 후 매주 받는 PR이 3~5개 수준으로 떨어졌습니다. 28개를 분기마다 몰아 받던 게, 이제는 한 주에 손가락으로 셀 정도로 정리된 셈이죠.

 

 

patch·minor는 automerge, major는 수동 리뷰로 분기

핵심은 packageRules입니다. patch랑 minor 버전은 자동 병합으로 넘기고, major는 사람이 직접 보게 갈라놨어요.

 

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": ["config:recommended"],
  "schedule": ["before 9am on monday"],
  "minimumReleaseAge": "7 days",
  "packageRules": [
    {
      "matchUpdateTypes": ["minor", "patch"],
      "automerge": true
    },
    {
      "matchUpdateTypes": ["major"],
      "automerge": false
    },
    {
      "matchPackageNames": ["kotlinx.coroutines", "androidx.compose"],
      "automerge": false
    }
  ]
}

 

자동 병합의 전제는 딱 하나, CI 통과입니다. 그래서 깃허브 branch protection으로 빌드·테스트 통과를 강제로 걸어뒀어요. 테스트 커버리지가 부실한 모듈은 아예 automerge 대상에서 뺐습니다. 검증 없이 병합하는 건 자동화가 아니라 사고니까요.

 

코루틴이나 젯팩 컴포즈(Jetpack Compose)처럼 마이너 업데이트에도 빌드가 자주 깨지는 라이브러리는 예외 목록에 따로 넣어서 손으로 봅니다. 이 부분은 자동화 욕심 부리다 데인 적이 있어서요.

 

 

minimumReleaseAge로 공급망 공격 막아두기

설정 중에 제일 중요하다고 보는 게 minimumReleaseAge예요. 위 설정에서 7 days로 잡아둔 그 줄입니다.

 

이게 뭐냐면, 패키지가 릴리스된 직후 곧바로 PR을 만들지 않고 7~14일을 기다리게 하는 장치입니다. 릴리스 직후에 악성 코드가 끼워진 패키지가 올라오는 공격이 있는데, 며칠 지나면 보통 발견·신고돼서 조치가 이뤄지거든요. 그 시간을 벌어주는 거예요.

 

이 설정 없이 automerge만 켜두면 어떻게 될까요. 악성 패키지가 발견되기 전에 자동으로 병합돼서, 공급망 공격이 그대로 내 코드에 들어오는 통로가 될 수 있습니다.

 

단, 보안 취약점(CVE) 패치는 예외로 둬서 즉시 적용되게 해야 합니다. 지연 정책 때문에 보안 패치까지 일주일 늦으면 그건 또 다른 위험이니까요.

 

 

Renovate vs Dependabot, 그리고 자동화의 한계

깃허브에 기본 내장된 Dependabot이랑 자주 비교되는데요. 제가 Renovate를 고른 이유는 두 가지였습니다. 그룹핑이 프리셋 기반이라 편하고, 자동 병합 기능이 내장이에요. Dependabot은 그룹을 직접 정의해야 하고, automerge도 별도 깃허브 액션 워크플로를 짜야 하는 것으로 알려져 있어요.

 

대신 솔직하게 단점도 말씀드릴게요. Renovate는 유연한 만큼 renovate.json이 금방 복잡해집니다. 이 설정 파일을 유지보수하는 것 자체가 새로운 관리 비용이 되더라고요. 그리고 자동 생성된 PR마다 CI가 도니까 빌드 시간과 비용도 늘어납니다.

 

가장 분명한 한계는 major 업데이트입니다. Breaking change가 들어간 버전은 결국 사람이 코드를 직접 고치고 판단해야 해요. 자동화가 줄여주는 건 어디까지나 patch·minor의 반복 노동까지라는 점, 이건 도입 전에 기대치를 맞춰두시는 게 좋습니다.

 

분기마다 28개를 몰아 받으며 반나절씩 태우던 자리가, 이제는 월요일 아침 PR 서너 개 훑고 커피 한 잔 마시면 끝나는 자리로 바뀌었어요. 그날 반나절 날리고 결심했던 게 헛되지 않았다고, 한 달 돌려보고 나서야 마음 놓고 말할 수 있겠네요.

 

반응형
LIST