본문으로 건너뛰기 GitLab 이슈와 Jira 연동하기 | 마일스톤을 실전에서 관리하는 방법

GitLab 이슈와 Jira 연동하기 | 마일스톤을 실전에서 관리하는 방법

GitLab 이슈와 Jira 연동하기 | 마일스톤을 실전에서 관리하는 방법

이 글의 핵심

개발팀은 GitLab으로 코드와 CI를 돌리고, 조직 전체는 Jira로 로드맵과 보고를 관리하는 상황은 생각보다 흔합니다. 이 글은 GitLab의 공식 Jira 연동 두 가지(프로젝트 이슈 연동, Development Panel)가 실제로 무엇을 해주고 무엇을 못 해주는지, 그 한계를 웹훅 브릿지나 마켓플레이스 커넥터로 메우는 절충안, 그리고 GitLab 마일스톤과 Jira 스프린트·버전을 억지로 양쪽에서 동시에 관리하지 않고 한쪽을 기준으로 삼는 전략을 실무 경험을 바탕으로 정리합니다.

들어가며: 기술적 선택이 아니라 조직적 현실

“GitLab을 쓰는데 왜 Jira도 필요한가요?”라는 질문은 순수하게 도구 비교로 접근하면 답이 안 나옵니다. 실제로 이 조합이 생기는 경로는 대부분 기술적 결정이 아니라 조직적 사정입니다. 개발팀은 코드 저장소, CI/CD 파이프라인, MR 리뷰까지 GitLab 안에서 전부 처리하는 것이 훨씬 편합니다. 이슈를 만들고 브랜치를 파고 MR을 올리고 파이프라인이 도는 것까지 한 화면에서 끊김 없이 이어지기 때문입니다. 반면 회사 전체 관점에서는 개발팀 하나만 GitLab을 쓰는 게 아니라 기획, 디자인, QA, 심지어 영업까지 걸쳐 있는 크로스 펑셔널 로드맵을 관리해야 하고, 이미 그 상위 조직이 Jira를 수년간 표준으로 써온 경우가 흔합니다. 이런 상황에서 개발팀에게 “Jira로 다 옮기라”고 하면 코드와 직접 연결되는 워크플로우(브랜치 이름, 커밋, MR, 파이프라인 상태)를 잃어버리고, 반대로 PM 조직에게 “GitLab 이슈판을 보라”고 하면 다른 팀들과의 통합 리포팅이 깨집니다.

그래서 현실적으로는 어느 한쪽으로 통합하는 대신 두 도구를 연동해서 각자 잘하는 일을 하게 두는 절충이 자주 선택됩니다. 이 글은 그 연동을 실제로 구현할 때 마주치는 기능별 한계와, 마일스톤처럼 “일정”이라는 단일한 진실을 두 시스템에 걸쳐 유지해야 하는 문제를 다룹니다. 참고로 이슈 트래커와 브랜치 네이밍의 관계는 Git 필수 명령어·팁 가이드에서, 코드 리뷰 과정에서 이슈 링크를 활용하는 방식은 코드 리뷰 모범 사례에서 조금 더 다루고 있습니다.

1. GitLab의 공식 Jira 연동, 정확히 무엇을 해주는가

GitLab이 제공하는 Jira 연동은 하나가 아니라 성격이 다른 두 가지입니다. 이름이 비슷해서 혼동하기 쉽지만, 어느 것을 설정했는지에 따라 실제로 얻는 결과가 완전히 다릅니다.

1.1 프로젝트 수준 Jira 이슈 연동 — 커밋/MR에서 참조하기

첫 번째는 GitLab 프로젝트 설정의 Settings → Integrations → Jira issues에서 활성화하는 기본 연동입니다. 이 연동을 켜면 커밋 메시지나 MR 제목·본문에 PROJ-123 같은 Jira 이슈 키를 적었을 때, GitLab이 해당 문자열을 Jira 이슈로 인식해서 두 가지를 해줍니다. 첫째, GitLab 쪽 MR이나 커밋 화면에서 그 키를 클릭하면 Jira 이슈로 바로 이동하는 링크가 생깁니다. 둘째, “스마트 커밋(smart commit)” 구문을 쓰면 Jira 이슈에 코멘트를 남기거나, 상태를 전이시키거나(예: PROJ-123 #close), 작업 시간을 기록하는(예: PROJ-123 #time 2h) 것까지 가능합니다.

# 스마트 커밋 예시 — 커밋 메시지 한 줄에 이슈 키와 명령을 함께 적는다
PROJ-123 결제 모듈 널 체크 누락 수정 #comment 수정 완료, QA 요청드립니다 #time 1h 30m #close

이 커밋을 푸시하면 GitLab이 커밋 메시지를 파싱해서 Jira REST API를 호출하고, PROJ-123 이슈에 코멘트를 남기고 작업 시간을 기록한 뒤 이슈를 닫는 상태로 전이시킵니다. 여기서 반드시 짚어야 할 점은 이게 커밋 메시지 트리거 기반의 단방향 액션이라는 것입니다. Jira 쪽에서 이슈를 다시 열거나 상태를 바꿔도 GitLab의 커밋이나 MR이 자동으로 반응하지 않습니다. 양방향 실시간 동기화처럼 느껴지지만 실제로는 “정해진 형식의 텍스트가 특정 방향으로 한 번 쏘는” 방식이라는 걸 팀 전체가 이해하고 있어야 나중에 “왜 상태가 안 바뀌었냐”는 혼란을 줄일 수 있습니다.

1.2 Jira Development Panel 연동 — Jira 이슈 화면에서 GitLab 상태 보기

두 번째는 방향이 반대입니다. Jira 이슈를 열었을 때 오른쪽 사이드바에 “Development” 패널이 나타나면서 그 이슈와 연결된 GitLab의 브랜치, 커밋, MR, 그리고 최근 파이프라인 상태(성공/실패)까지 보여주는 기능입니다. PM이나 QA가 Jira만 보고도 “이 티켓이 실제로 어느 MR로 처리되고 있고, 파이프라인이 통과했는지”를 확인할 수 있어서, 개발팀에게 매번 “이거 어디까지 됐어요?”라고 묻는 커뮤니케이션 비용을 줄여줍니다.

다만 이 패널이 제대로 동작하려면 GitLab 쪽에 Jira 앱(또는 GitLab.com이면 Atlassian 마켓플레이스의 GitLab.com 앱)을 설치하고 권한을 연결해야 하며, 셀프 매니지드 GitLab이냐 GitLab.com이냐, 그리고 어떤 플랜을 쓰느냐에 따라 설정 방법과 지원 범위가 갈립니다. 실무에서 자주 겪는 함정은 이 두 연동을 하나로 착각해서, “Jira 이슈 연동을 켰으니 Development Panel도 자동으로 뜨겠지”라고 기대하는 것입니다. 실제로는 서로 독립적인 설정이라 둘 다 각각 켜고 확인해야 합니다.

아래는 두 연동 방식과, 뒤에서 다룰 웹훅 브릿지·마켓플레이스 커넥터까지 포함해 데이터가 어느 방향으로 흐르는지를 정리한 그림입니다.

flowchart LR
    subgraph GitLab
        Commit["커밋 메시지"]
        MR["Merge Request"]
        Pipeline["CI 파이프라인"]
    end
    subgraph 연동방식
        A["프로젝트 Jira 이슈 연동\n(스마트 커밋)"]
        B["Jira Development Panel"]
        C["웹훅 커스텀 브릿지"]
        D["마켓플레이스 커넥터"]
    end
    subgraph Jira
        Issue["Jira 이슈"]
    end

    Commit -->|"PROJ-123 참조/스마트 커밋"| A --> Issue
    MR --> B --> Issue
    Pipeline --> B
    MR -->|웹훅 이벤트| C --> Issue
    Pipeline -->|웹훅 이벤트| C
    MR --> D --> Issue
    Issue -.->|상태 변경, 수동 확인 필요| C

그림에서 보이듯 GitLab에서 Jira로 가는 화살표는 대부분 존재하지만, Jira에서 GitLab으로 되돌아오는 화살표는 점선으로만 표시했습니다. 공식 연동 두 가지 모두 “GitLab의 활동을 Jira에 보여주는” 방향이 기본이고, Jira 쪽 변경을 GitLab에 자동 반영하는 진짜 양방향 동기화는 공식 기능만으로는 제공되지 않습니다. 이걸 원한다면 다음 절의 방법이 필요합니다.

2. 네이티브 연동이 부족할 때: 웹훅 브릿지와 마켓플레이스 커넥터

두 이슈 트래커 사이에 실시간 양방향 동기화(예: Jira에서 우선순위를 바꾸면 GitLab 이슈의 라벨도 같이 바뀌는 것)가 정말 필요하다면, 선택지는 크게 두 가지입니다.

2.1 자체 웹훅 브릿지

GitLab과 Jira 양쪽 모두 웹훅과 REST API를 제공하므로, 이벤트를 받아서 상대 시스템에 반영하는 중간 서비스를 직접 만들 수 있습니다. 예를 들어 GitLab에서 MR이 머지되면 웹훅이 발생하고, 이 웹훅을 받는 서버가 MR 본문에서 Jira 이슈 키를 추출해 Jira REST API로 상태를 전이시키는 구조입니다.

// 아주 단순화한 웹훅 브릿지 핸들러 예시
app.post('/webhooks/gitlab', async (req, res) => {
  const event = req.body;
  if (event.object_kind === 'merge_request' && event.object_attributes.state === 'merged') {
    // MR 제목/본문에서 Jira 이슈 키(PROJ-123 형식) 추출
    const match = event.object_attributes.description.match(/[A-Z]+-\d+/);
    if (match) {
      await jiraClient.transitionIssue(match[0], 'Done');
    }
  }
  res.sendStatus(200);
});

이 방식의 장점은 매핑 로직을 팀 상황에 완전히 맞출 수 있다는 것입니다. 예를 들어 “특정 라벨이 붙은 MR만 동기화한다”거나 “특정 브랜치 패턴만 반영한다” 같은 세부 규칙을 자유롭게 넣을 수 있습니다. 단점은 명확합니다. GitLab과 Jira 양쪽이 API를 변경하거나 Deprecation을 진행할 때마다 이 브릿지를 계속 따라 고쳐야 하고, 웹훅 전달이 실패했을 때(네트워크 문제, 서버 재시작 시점 등) 큐잉과 재시도 로직 없이는 이벤트가 조용히 누락됩니다. 즉 “만들고 나면 끝”이 아니라 하나의 작은 내부 서비스를 운영 부담으로 새로 짊어지는 선택입니다.

2.2 마켓플레이스 커넥터 앱

Atlassian 마켓플레이스나 GitLab 파트너 생태계에는 GitLab-Jira 양방향 동기화를 전문으로 하는 유료 커넥터 앱들이 있습니다. 이런 앱은 대개 필드 매핑 UI를 제공해서 “GitLab 이슈의 라벨을 Jira 컴포넌트에 매핑한다”처럼 코드 없이 설정할 수 있고, 벤더가 API 변경에 대응해 앱을 업데이트해줍니다.

이걸 “공짜 대안이 없어서 어쩔 수 없이 돈 쓰는 것”으로만 보면 판단을 그르치기 쉽습니다. 실제로는 트레이드오프입니다. 구독료는 대개 사용자 수나 연결된 프로젝트 수에 비례해서 커지기 때문에 조직이 커질수록 비용도 커지고, 동기화 로직이 벤더의 블랙박스 안에 있어서 “왜 이 필드는 안 동기화되지?” 같은 문제가 생기면 우리 쪽에서 디버깅할 수 없고 벤더 지원팀에 문의를 넣어야 합니다. 반대로 자체 브릿지는 초기 개발 비용과 이후의 유지보수 인력이 필요합니다. 결국 “동기화해야 하는 필드가 몇 개 안 되고 팀에 이걸 유지보수할 여력이 있는가”와 “그 시간을 아껴서 커넥터 구독료를 내는 게 더 싼가”를 실제 숫자로 비교해봐야 합니다. 어느 쪽이든 공짜로 완벽한 양방향 동기화를 얻는 방법은 없습니다.

3. 마일스톤: GitLab 마일스톤과 Jira 스프린트·버전은 개념이 다르다

마일스톤 동기화를 이야기하기 전에 먼저 짚어야 할 것은, GitLab의 마일스톤과 Jira의 스프린트/버전이 애초에 1:1로 대응하는 개념이 아니라는 점입니다.

GitLab의 마일스톤은 프로젝트 마일스톤과 그룹 마일스톤으로 나뉩니다. 프로젝트 마일스톤은 단일 리포지토리 안의 이슈·MR을 특정 기간에 묶는 용도이고, 그룹 마일스톤은 같은 그룹 아래 여러 프로젝트에 걸쳐 하나의 릴리스나 기간을 공유하고 싶을 때 씁니다. 마일스톤에는 시작일과 마감일이 있고, 여기 묶인 이슈의 완료 추이를 번다운 차트로 볼 수 있습니다. 이건 Jira의 스프린트(정해진 짧은 기간 동안의 작업 묶음, 보드와 벨로시티 중심)와 비슷해 보이지만, Jira의 버전(fix version)과도 겹치는 지점이 있습니다. 버전은 기간보다는 “이번 릴리스에 포함될 항목들”이라는 의미가 강하기 때문입니다. 즉 GitLab 마일스톤 하나를 Jira 쪽 스프린트에 매핑할지 버전에 매핑할지부터 팀이 합의해야 하는데, 이 합의 자체가 생략된 채로 연동을 시작하는 경우가 많습니다.

두 시스템을 동시에 소스 오브 트루스로 두지 않기

가장 흔히 저지르는 실수는 “GitLab 마일스톤 날짜와 Jira 스프린트 날짜를 둘 다 정확히 맞춰서 운영하자”는 목표를 세우는 것입니다. 이론적으로는 깔끔해 보이지만 실제로는 일정이 바뀌는 이유(우선순위 변경, 리소스 부족, 외부 일정 충돌)가 두 시스템 중 어느 쪽에서 먼저 반영되느냐가 매번 달라지기 때문에, 누군가 한쪽을 고치고 다른 쪽에 반영하는 걸 잊는 순간 두 날짜가 어긋나기 시작합니다. 그리고 어긋난 상태로 몇 주가 지나면 “어느 쪽이 진짜 마감일인지” 아무도 확신하지 못하는 상태가 됩니다.

이 문제를 구조적으로 없애는 방법은 마일스톤 일정을 결정하고 변경할 권한을 한 시스템에만 두고, 다른 시스템은 그 결과를 보여주기만 하는 읽기 전용 미러로 취급하는 것입니다. 구체적으로는 다음 두 기준 중 팀 상황에 맞는 쪽을 고르면 됩니다.

  • 실제 작업 속도와 스코프 조정이 코드 단위에서 매일 일어나는 팀이라면, GitLab 마일스톤을 소스 오브 트루스로 두고 Jira 버전 이름에 마일스톤 일정을 요약해서 수동 또는 스크립트로 갱신만 반영합니다.
  • 여러 팀의 로드맵을 조율하는 상위 조직(PMO, 프로덕트 오너 그룹)이 일정 변경 권한을 쥐고 있다면, Jira 스프린트/버전을 소스 오브 트루스로 두고 GitLab 마일스톤 날짜는 그 결정을 반영만 하는 쪽으로 갱신합니다.

어느 쪽을 고르든 핵심은 “날짜를 바꿀 수 있는 곳은 하나”라는 규칙을 팀 전체가 지키는 것입니다. 두 곳 다 자유롭게 날짜를 바꿀 수 있게 열어두면, 연동 도구가 얼마나 잘 만들어졌는지와 상관없이 결국 사람이 수동으로 대조하고 맞추는 작업이 반복됩니다.

4. 실전에서 겪은 함정 두 가지

스마트 커밋 구문은 처음 도입할 때 반드시 한 번은 막히는 지점이 있습니다. 저는 예전에 팀에 스마트 커밋을 처음 도입했을 때, 커밋 메시지에 PROJ_123 #close처럼 하이픈이 아니라 언더스코어를 쓴 커밋이 며칠 동안 조용히 무시되는 걸 겪은 적이 있습니다. Jira 이슈 키 형식은 프로젝트키-숫자처럼 하이픈을 쓰는 것이 규칙인데, 팀원 한 명이 평소 변수명 표기 습관(스네이크 케이스)대로 언더스코어를 썼던 것입니다. GitLab이나 Jira 쪽에서 별다른 에러를 띄워주지 않고 그냥 그 커밋 메시지를 이슈 키로 인식하지 못한 채 넘어갔기 때문에, 겉보기엔 정상적으로 푸시되고 CI도 통과했지만 Jira 이슈는 계속 열린 상태로 남아 있었습니다. 나중에 QA가 “이 티켓 아직 안 끝났나요?”라고 물어봐서야 원인을 찾았는데, 문제는 코드가 아니라 커밋 메시지의 오탈자 하나였습니다. 이후로는 커밋 메시지 템플릿에 Jira 키 형식을 예시로 박아두고, 가능하면 pre-commit 훅으로 이슈 키 패턴을 검증하는 방향으로 바꿨습니다.

두 번째는 마일스톤 드리프트입니다. 스프린트 막바지에 범위가 줄어들면서 Jira 쪽 스프린트 종료일이 이틀 앞당겨졌는데, GitLab 마일스톤 마감일은 그대로 남아 있었던 적이 있습니다. 처음엔 별문제 아니라고 생각했지만, 번다운 차트를 보고 일정을 판단하던 개발자들은 GitLab 마일스톤 기준으로 “아직 이틀 남았다”고 생각하고 있었고, PM은 Jira 기준으로 “이미 끝났어야 하는 스프린트”로 보고 있어서 회의에서 서로 다른 전제로 이야기하다가 한참 뒤에야 날짜가 다르다는 걸 알아챈 적이 있습니다. 그 이후로 팀은 “마일스톤 날짜는 GitLab에서만 바꾸고, Jira 버전 날짜는 매주 한 번 그 값을 그대로 복사해서 갱신한다”는 규칙을 정했습니다. 완벽한 자동화는 아니었지만, 적어도 “어디를 봐야 진짜 날짜인지”에 대한 혼란은 그 규칙 하나로 사라졌습니다.

마무리

GitLab과 Jira를 함께 쓰는 상황은 도구를 잘못 골라서가 아니라, 개발팀과 조직 전체가 서로 다른 이유로 서로 다른 도구를 표준으로 삼았기 때문에 생기는 경우가 대부분입니다. 이 현실을 인정하고 나면 연동 설계의 목표도 명확해집니다. GitLab의 프로젝트 Jira 연동과 Jira Development Panel은 각각 “GitLab에서 Jira 이슈를 참조·전이시키는 기능”과 “Jira에서 GitLab 개발 현황을 보여주는 기능”으로 방향이 다르며, 진짜 양방향 실시간 동기화가 필요하면 웹훅 브릿지의 유지보수 부담과 마켓플레이스 커넥터의 구독료 사이에서 팀 상황에 맞는 쪽을 골라야 합니다. 그리고 마일스톤처럼 일정이 걸린 항목은 두 시스템을 동시에 최신 상태로 유지하려는 시도 자체를 버리고, 한쪽을 소스 오브 트루스로 정한 뒤 다른 쪽은 그 결과를 반영만 하는 미러로 두는 것이 실무적으로 훨씬 오래 버티는 구조입니다.