Git은 알고 있다고 가정한다. 이 문서의 목표는 Azure DevOps가 왜 존재하는지부터 시작해서, 실무에서 마주치는 모든 기능을 "아, 이런 거였지" 하고 바로 따라갈 수 있는 수준까지 해부하는 것이다.
Azure DevOps가 왜 존재하는가
팀 개발에서 Git만 쓰면 어떤 문제가 생기는지부터 생각해보자.
코드 버전 관리는 Git이 해준다. 근데 "이번 Sprint에 뭘 만들지", "PR을 누가 리뷰했고 언제 머지됐지", "코드가 머지되면 자동으로 빌드되고 배포까지 돼야 하는데" — 이런 것들은 Git이 해결해주지 않는다. Git은 코드 저장소일 뿐, 팀 개발 사이클 전체를 관리하는 플랫폼이 아니다.
결국 별도의 이슈 트래커(Jira), 별도의 CI/CD 툴(Jenkins), 별도의 코드 리뷰 툴이 따로따로 필요해진다.
툴이 분산되면 컨텍스트 스위칭이 발생하고, 연동 설정도 복잡해진다.
Azure DevOps는 이 모든 것을 하나의 플랫폼에 통합한 Microsoft의 답이다.
Azure DevOps
정의: 코드 관리부터 태스크 추적, 빌드/배포 자동화까지 팀 개발 사이클 전체를 하나의 플랫폼에서 관리하는 Microsoft의 DevOps 서비스.
핵심 포인트:
- Git 저장소(Repos)를 내장하고 있어서 Git의 상위 집합처럼 동작한다
- Microsoft 생태계(Teams, Outlook, MS365 Loop)와 자연스럽게 연동된다
- GitHub와 역할이 비슷하지만, 기업용 접근 권한 관리와 Boards가 훨씬 강력하다
- 5개의 서비스(Boards, Repos, Pipelines, Test Plans, Artifacts)로 구성되며 각각 독립적으로 쓸 수도 있다
┌─────────────────────────────────────────────────────────┐
│ Azure DevOps │
│ │
│ ┌──────────┐ ┌──────────┐ ┌────────────────────────┐ │
│ │ Boards │ │ Repos │ │ Pipelines │ │
│ │ 태스크/ │ │ Git 저장 │ │ CI/CD 자동화 │ │
│ │스프린트 │ │소 + PR │ │ 빌드/테스트/배포 │ │
│ └──────────┘ └──────────┘ └────────────────────────┘ │
│ │
│ ┌─────────────────┐ ┌──────────────────────────────┐ │
│ │ Test Plans │ │ Artifacts │ │
│ │ 테스트 케이스 │ │ 패키지/라이브러리 저장소 │ │
│ │ 관리 및 실행 │ │ (npm, pip, Go 모듈 등) │ │
│ └─────────────────┘ └──────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Organization과 Project 구조
Azure DevOps에 처음 들어가면 URL 구조가 낯설다. 구조를 먼저 이해해야 전체가 보인다.
Organization / Project 계층
정의: Azure DevOps에서 팀과 레포를 묶는 계층 구조로, Organization이 최상위이고 그 아래 Project가 있다.
Azure DevOps
└── Organization (my-company) # 회사 단위
├── Project A (backend-service) # 제품/팀 단위
│ ├── Boards (태스크 관리)
│ ├── Repos (코드 저장소)
│ ├── Pipelines (CI/CD)
│ └── Artifacts
└── Project B (frontend-service)
├── Boards
├── Repos
└── Pipelines
핵심 포인트:
- Organization은 회사나 팀 전체를 묶는 최상위 단위. URL에
dev.azure.com/조직명형태로 나타난다 - Project는 제품이나 서비스 단위로 쪼개는 단위. Boards, Repos, Pipelines가 Project 안에 속한다
- 권한 관리도 Organization 수준과 Project 수준으로 나뉜다
- Git 클론 URL은 항상
dev.azure.com/조직명/프로젝트명/_git/레포명형태다
# Azure Repos 클론 URL 구조
git clone https://my-company@dev.azure.com/my-company/backend-service/_git/backend-service
# ↑ 조직명 ↑ 조직명 ↑ 프로젝트명 ↑ 레포명
Agile과 Sprint — 팀 개발의 리듬
Boards를 이해하려면 그 배경이 되는 Agile(애자일) 방법론을 먼저 알아야 한다.
Boards의 모든 기능 — Sprint, Backlog, Board, Burndown Chart — 은 Agile의 개념을 도구로 구현한 것이기 때문이다.
왜 Agile인가 — Waterfall의 한계
소프트웨어 개발에서 전통적인 방식은 Waterfall(폭포수)이다.
요구사항 분석 → 설계 → 구현 → 테스트 → 배포를 순서대로, 각 단계를 완전히 끝낸 뒤 다음으로 넘어간다.
Waterfall — 한 방향으로 흐른다
요구사항 분석 (2개월)
↓
설계 (1개월)
↓
구현 (3개월)
↓
테스트 (1개월)
↓
배포
→ 총 7개월 후 첫 결과물
문제는 7개월 뒤에야 실제 동작하는 소프트웨어가 나온다는 것이다. 그 사이에 고객 요구사항이 바뀌면? 구현 단계에서 설계 결함이 발견되면? 이미 지나간 단계를 되돌리는 비용이 엄청나다. 특히 소프트웨어는 하드웨어와 달리 요구사항이 자주 바뀌기 때문에, "처음에 다 정하고 끝까지 실행"하는 모델이 잘 안 맞는다.
2001년에 소프트웨어 개발자 17명이 모여 Agile Manifesto(애자일 선언문)를 발표했다. 핵심 원리는 이렇다.
- 짧은 주기로 동작하는 소프트웨어를 반복 전달한다 (몇 개월이 아니라 몇 주 단위)
- 계획을 따르는 것보다 변화에 대응하는 것을 중시한다
- 문서보다 동작하는 소프트웨어를 가치 있게 본다
- 개인과 상호작용을 프로세스와 도구보다 우선한다
Sprint (스프린트) — Agile의 실행 단위
Agile의 가장 구체적인 실천 프레임워크가 Scrum(스크럼)이고, Scrum에서 반복 주기를 Sprint라고 부른다.
정의: 1~4주(보통 2주)의 고정된 기간 동안 완료할 작업 목록을 정하고, 그 기간 안에 동작하는 결과물을 만들어내는 개발 단위.
핵심 포인트:
- Sprint는 시간이 고정되어 있다. 2주로 정했으면 작업이 남아도 2주에 끝난다. 남은 작업은 다음 Sprint로 넘긴다
- 각 Sprint는 계획(Planning) → 실행 → 리뷰 → 회고(Retrospective)의 사이클을 반복한다
- Waterfall이 전체를 한 번에 하는 것이라면, Sprint는 작은 Waterfall을 2주마다 반복하는 것이다
- Sprint가 쌓이면 팀의 처리 속도(Velocity)가 측정되고, 이후 계획의 기준이 된다
Agile/Scrum — 2주 단위로 반복한다
Sprint 1 (2주) Sprint 2 (2주) Sprint 3 (2주)
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Planning │ │ Planning │ │ Planning │
│ 실행 (개발) │ │ 실행 (개발) │ │ 실행 (개발) │
│ Review │ │ Review │ │ Review │
│ Retrospective │ │ Retrospective │ │ Retrospective │
└─────────────────┘ └─────────────────┘ └─────────────────┘
↓ 동작하는 ↓ 동작하는 ↓ 동작하는
결과물 전달 결과물 전달 결과물 전달
| Waterfall | Agile/Scrum | |
|---|---|---|
| 전달 주기 | 전체 완료 후 한 번 | 2주마다 반복 |
| 요구사항 변경 | 비용이 크다 | Sprint 단위로 반영 가능 |
| 리스크 발견 시점 | 후반부 (테스트 단계) | 매 Sprint마다 조기 발견 |
| 계획 정확도 | 초기에 전체를 추정 (부정확) | Sprint 단위로 추정 (상대적으로 정확) |
Sprint를 구성하는 미팅들
Sprint는 단순히 "2주 동안 개발하는 것"이 아니다. 정해진 미팅 구조가 있고, 각 미팅이 Sprint 사이클을 굴리는 역할을 한다.
Sprint Planning (스프린트 계획)
Sprint 시작 시점에 하는 미팅이다. "이번 2주 동안 뭘 할 것인가"를 정한다.
- Backlog(아직 시작하지 않은 전체 작업 목록)에서 이번 Sprint에 처리할 항목들을 꺼내온다
- 각 항목의 작업량을 추정하고, 팀의 Capacity(수용 가능 작업량) 안에 들어오는지 확인한다
- 결과물: Sprint Backlog — 이번 Sprint에서 완료할 Work Item 목록
Daily Standup (데일리 스탠드업)
매일 아침(보통 15분 이내) 서서 하는 짧은 미팅이다. 앉아서 하면 길어지니까 서서 한다는 의미에서 "Standup"이다.
- 각 팀원이 3가지만 말한다: (1) 어제 한 것 (2) 오늘 할 것 (3) 막힌 것(Blocker)
- Board를 보면서 진행한다. 각자의 카드가 어떤 상태인지 시각적으로 확인한다
- 토론하는 자리가 아니다. 문제가 있으면 Standup 끝나고 관련 인원끼리 따로 논의한다
Sprint Review (스프린트 리뷰)
Sprint 끝에 하는 미팅이다. 이번 Sprint에서 만든 결과물을 보여주고 피드백을 받는다.
- 동작하는 소프트웨어를 데모한다. 슬라이드가 아니라 실제 동작 화면이다
- 이해관계자(PO, 기획자 등)가 피드백을 주고, 필요하면 Backlog에 새 항목이 추가된다
Sprint Retrospective (스프린트 회고)
Sprint Review 직후에 팀 내부에서 하는 미팅이다. 결과물이 아니라 프로세스를 되돌아본다.
- "이번 Sprint에서 잘한 것, 개선할 것, 시도해볼 것"을 논의한다
- 예: "PR 리뷰가 2일씩 밀렸다 → 리뷰어 지정 후 24시간 내 리뷰 규칙 도입하자"
- 개선 사항은 다음 Sprint에 반영한다
Sprint 사이클
Sprint Planning (시작)
↓
Daily Standup (매일)
↓
개발 진행 (Board에서 상태 관리)
↓
Sprint Review (끝 — 결과물 데모)
↓
Sprint Retrospective (끝 — 프로세스 개선)
↓
다음 Sprint Planning ...
Boards — 태스크 추적과 스프린트 관리
Boards는 위에서 설명한 Agile/Scrum의 개념들을 실제로 운영하는 도구다. "지금 팀이 뭘 하고 있는지"를 시각적으로 추적하는 곳이며, Jira와 역할이 동일하다.
왼쪽 메뉴 구조를 먼저 보면 Boards 섹션의 전체 모습이 파악된다.
Boards
├── Work items — 전체 Work Item 목록
├── Boards — 칸반 뷰 (카드를 열별로 배치해서 보는 형태)
├── Backlogs — Sprint에 아직 안 들어간 할 일 목록
├── Sprints — 현재/과거 Sprint별 뷰
├── Queries — 조건 걸어서 Work Item 검색
├── Delivery Plans — 여러 팀의 Sprint를 타임라인으로 보는 뷰
└── Analytics views — 팀 속도, Burndown Chart 등 통계
Work Item — 모든 작업의 기본 단위
Work Item (작업 항목)
정의: Azure DevOps에서 개발해야 할 모든 작업을 표현하는 기본 단위.
핵심 포인트:
- 모든 태스크, 버그, 기능 요구사항은 Work Item으로 표현된다
- Work Item은 계층 구조를 가진다 — 큰 단위가 작은 단위를 포함하는 Parent/Child 관계
- 각 Work Item은 상태(State)를 가지며 상태 전환이 곧 진행 흐름이다
- Boards에서 보이는 카드 하나하나가 Work Item이다
Work Item 계층 구조 — Epic, Feature, User Story, Task
Epic (에픽)
│ 가장 큰 단위. 여러 Sprint에 걸친 큰 목표.
│ 예: "결제 시스템 v2.0 출시"
│
└── Feature (기능)
│ Epic을 구성하는 기능 단위.
│ 예: "카드 결제 기능"
│
└── User Story (사용자 스토리)
│ 사용자 관점에서 작성한 요구사항.
│ 형식: "As a [누구], I want [무엇], So that [왜]"
│ 예: "As a 구매자, I want 카드를 저장하고 싶다,
│ So that 다음에 빠르게 결제할 수 있다"
│
└── Task (태스크)
실제 개발자가 처리하는 작업 단위.
예: "결제 API 엔드포인트 설계", "단위 테스트 작성"
시간 추정(Estimation)이 붙는 레벨이 보통 여기다.
Boards에서 Parent가 상위 Work Item, Child가 그걸 쪼갠 하위 Work Item이다. User Story 하나를 여러 Task로 쪼개는 게 일반적인 패턴이다.
위 계층 구조는 이론상 최대치이고, 실제 팀에서는 필요한 레벨만 골라서 쓴다. 스타트업처럼 빠르게 움직이는 팀은 Epic/Feature를 생략하고 User Story → Task 두 계층만 운영하는 경우가 많다. 계층이 복잡할수록 관리 오버헤드가 커지기 때문이다.
Work Item 상태 전환
Work Item이 만들어지면 상태가 바뀌면서 진행된다. 기본 흐름은 이렇다.
New → Active → Resolved → Closed
생성됨 작업 시작 완료됨 닫힘(검증 완료)
팀마다 커스텀 상태를 추가하기도 한다. 예를 들어 In Review(PR 리뷰 중) 같은 상태가 중간에 들어올 수 있다.
이 상태 전환이 칸반 뷰의 열(column)과 대응한다 — 카드를 드래그해서 열을 옮기면 상태가 바뀐다.
Sprint 운영 — Planning에서 Retrospective까지
위의 Agile 섹션에서 Sprint의 개념과 미팅 구조를 설명했다. 여기서는 그 과정이 Azure Boards에서 어떻게 구현되는지 구체적으로 본다.
Backlog — 아직 시작하지 않은 작업의 저장소
정의: Sprint에 배정되지 않은 전체 Work Item 목록. "언젠가 해야 할 일"이 쌓이는 곳이다.
핵심 포인트:
- 새로운 요구사항, 버그 리포트, 개선 아이디어는 일단 Backlog에 들어간다
- Backlog의 항목들은 우선순위(Priority)로 정렬된다. 위에 있을수록 먼저 해야 할 일이다
- Sprint Planning에서 Backlog 상위 항목들을 꺼내 Sprint에 배정한다
- Backlog가 지나치게 커지면 관리가 안 된다. 주기적으로 Backlog Refinement(백로그 정리)를 해서 더 이상 필요 없는 항목은 제거하고, 모호한 항목은 구체화한다
Backlog Refinement (Grooming)
Sprint Planning과 별개로, Sprint 중간에 하는 미팅이다. Backlog 항목들을 다듬는 작업이다.
- 모호한 User Story를 구체화한다 ("로그인 기능" → "이메일/비밀번호 로그인 + 소셜 로그인 분리")
- 너무 큰 Story를 쪼갠다
- 더 이상 필요 없는 항목을 삭제한다
- 다음 Sprint에 들어갈 후보를 미리 추려둔다
이 과정이 없으면 Sprint Planning이 길어진다. Backlog가 정리되어 있어야 Planning에서 빠르게 "이거, 이거, 이거 하자"고 결정할 수 있다.
Sprint Planning — 이번에 뭘 할지 정하는 시간
Sprint Planning은 Azure Boards에서 이렇게 진행된다.
[1] Backlogs 뷰에서 우선순위 높은 항목 확인
↓
[2] 각 항목의 작업량 추정 (Story Points)
↓
[3] 팀 Capacity 확인 — 이번 Sprint에 쓸 수 있는 총 작업량
↓
[4] Capacity 안에 들어오는 만큼 항목을 Sprint에 배정
↓
[5] 배정된 항목의 Iteration 필드에 Sprint 이름이 채워진다
예: Sprint 2026.W03-W04
Story Points (스토리 포인트)
Sprint Planning에서 각 Work Item의 작업량을 추정할 때 쓰는 단위가 Story Points다. "이 작업이 며칠 걸릴까"가 아니라 "이 작업이 다른 작업 대비 얼마나 복잡한가"를 상대적으로 매긴다.
보통 피보나치 수열(1, 2, 3, 5, 8, 13, 21)을 쓴다. 왜 1, 2, 3, 4, 5가 아니라 피보나치인가? 작업이 커질수록 추정 오차도 커지기 때문이다. 3짜리 작업과 5짜리 작업의 차이는 감이 오지만, 13짜리와 15짜리의 차이는 의미가 없다. 피보나치의 간격이 점점 벌어지는 특성이 "큰 작업일수록 정밀한 추정이 불가능하다"는 현실을 반영한다. 13이 나오면 "이건 쪼개야 한다"는 신호로 읽는다.
Capacity (수용 가능 작업량)
팀이 한 Sprint에 처리할 수 있는 총 Story Points를 Capacity라고 한다. Sprint 탭에서 각 팀원의 가용 시간(휴가, 미팅 등 제외)을 입력하면 팀 전체 Capacity가 계산된다. Sprint Planning에서 이 Capacity를 초과하지 않도록 Work Item을 배정하는 것이 핵심이다.
과도하게 배정하면 Sprint 끝에 미완성 항목이 쌓이고, 너무 적게 배정하면 Sprint 중간에 할 일이 없어진다. 처음 몇 Sprint는 Capacity를 정확히 모르기 때문에 시행착오를 거치며 조정하게 된다.
Sprint 진행 — Daily Standup과 Board 활용
Sprint가 시작되면 매일 Board를 보면서 작업을 진행한다.
칸반 뷰 — 상태별 카드 배치
Boards 탭의 칸반 뷰는 Work Item을 카드 형태로 상태별 열에 배치해서 보여준다.
┌─────────────┬──────────────────────────────┬───────────────┐
│ New │ Active 4/5 │ Resolved │
├─────────────┼──────────────────────────────┼───────────────┤
│ + New item │ 63 BI (Business Intel.) │ │
│ │ Active / Minjeong Jin │ │
│ │ 2/20 ← 하위 Story 완료 │ │
│ ├──────────────────────────────┤ │
│ │ 64 Sampling Optimizer │ │
│ │ Active │ │
│ │ 0/5 │ │
│ ├──────────────────────────────┤ │
│ │ 65 Modeling │ │
│ │ Active / Minjeong Jin │ │
│ │ 0/4 │ │
└─────────────┴──────────────────────────────┴───────────────┘
각 카드 안의 숫자(2/20, 0/5, 0/4)는 하위 User Story 완료/전체 개수를 나타낸다. 열 구조(New / Active / Resolved)가 Work Item 상태 전환의 시각화다.
Daily Standup에서의 활용. 매일 아침 이 Board를 화면에 띄워놓고 팀원들이 각자 카드 상태를 업데이트한다. "어제 이 카드를 Active로 옮겼고, 오늘 Resolved로 옮길 예정입니다. 막힌 건 없습니다." 15분 안에 끝내는 게 원칙이다.
Sprint 마무리 — Retrospective와 Velocity
Burndown Chart (번다운 차트)
Sprint 진행 중에 남은 작업량을 시각적으로 보여주는 그래프다. X축이 Sprint 기간(일), Y축이 남은 Story Points다. 이상적으로는 직선으로 내려가야 하지만, 실제로는 계단식으로 내려가거나 중간에 올라가기도 한다(작업이 추가되면).
Sprint 탭의 오른쪽 상단에서 Burndown Chart를 볼 수 있다. Sprint 중간에 선이 이상적 기울기보다 위에 있으면 "일정이 밀리고 있다"는 신호다.
Velocity (속도)
팀이 한 Sprint에 실제로 완료한 Story Points의 합이 Velocity다. Sprint가 3~4개 쌓이면 팀의 평균 Velocity가 나오고, 이 값이 다음 Sprint Planning의 기준이 된다.
예를 들어 최근 3개 Sprint의 Velocity가 21, 18, 24라면 평균 21 정도다. 다음 Sprint Planning에서 25포인트 이상 배정하면 과부하일 가능성이 높다.
Backlog (전체 할 일 목록)
↓ Sprint Planning에서 선택
Sprint 1 (2주) — Velocity: 21
├── Story A (5pt) → Closed
├── Story B (8pt) → Closed
├── Story C (8pt) → Closed
└── Story D (5pt) → 미완료 → 다음 Sprint로 이동
↓ Retrospective → 프로세스 개선
Sprint 2 (2주) — Velocity: 24
├── Story D (5pt, 이전에서 넘어옴) → Closed
├── Story E (8pt) → Closed
├── Story F (8pt) → Closed
└── Story G (3pt) → Closed
Work Item 상세 화면 해부
Work Item을 하나 열면 이런 구조로 되어 있다. Sprint 흐름에서 설명한 개념들이 실제 화면에서 어떻게 보이는지 확인할 수 있다.
┌─────────────────────────────────────────────────────────────────┐
│ USER STORY 73 │
│ [BI] T-Test Box Plot 차트 시각화 │
│ │
│ State: Active Reason: Implementation started │
│ Iteration: Sprint 2026.W03-W04 ← 어느 Sprint에 속하는지 │
├──────────────────────────────┬──────────────────────────────────┤
│ Description │ Planning │
│ │ Story Points: 8 ← 작업량 추정 │
│ As a │ Priority: 2 │
│ I want │ │
│ So that ├──────────────────────────────────┤
│ │ Development │
│ Acceptance Criteria │ Merged PR 121 ← 연결된 PR들 │
│ (테스트 통과 기준 작성) │ Merged PR 99 │
│ ├──────────────────────────────────┤
│ Discussion │ Related Work │
│ (팀원 코멘트) │ Parent: 63 BI (Feature) │
│ │ Child: 352, 381, 520, 587 ... │
│ │ (Task 11개) │
└──────────────────────────────┴──────────────────────────────────┘
각 필드가 의미하는 것들을 짚어보면 이렇다.
Iteration 필드는 이 Work Item이 어느 Sprint에 배정됐는지 나타낸다. Sprint 2026.W03-W04는 2026년 3~4주차 Sprint라는 뜻이다. Sprint Planning에서 Backlog 항목을 Sprint에 끌어올 때 이 값이 채워진다.
Story Points는 이 User Story의 복잡도와 작업량을 숫자로 추정한 값이다. 8이면 꽤 큰 스토리다. 이 값들이 모여서 Sprint Velocity 계산에 쓰인다.
Related Work의 Parent/Child 관계가 실제로 여기서 보인다. Parent: 63 BI (Business Intelligence)는 이 User Story가 Feature 63번 아래에 속한다는 뜻이고, Child로 달린 352, 381, 520, 587 등이 실제 Task들이다. Task가 11개나 달린 큰 스토리다.
Development 섹션은 이 Work Item과 연결된 PR을 자동으로 보여준다. PR을 생성할 때 Work Item 번호를 링크하면 머지 후 여기에 자동으로 기록된다. Work Item과 코드 변경 이력이 한 화면에서 추적되는 구조다.
실제 Work Items 목록 화면은 이런 형태로 보인다.
Work Items 목록 화면
┌──────┬─────────────────────────────────┬──────────────┬──────────┐
│ ID │ Title │ Assigned To │ State │
├──────┼─────────────────────────────────┼──────────────┼──────────┤
│ 590 │ [Task] GetColumns Snake->Camel │ 김혜수 │ Resolved │ ← 노란 체크 아이콘
│ 588 │ [Story] [Utils] 공통 유틸리티 │ 김혜수 │ Active │ ← 파란 책 아이콘
│ 73 │ [Story] T-Test Box Plot 차트 │ 김혜수 │ Active │ ← 파란 책 아이콘
└──────┴─────────────────────────────────┴──────────────┴──────────┘
아이콘으로 Work Item 타입을 구분한다. 파란 책 아이콘은 User Story, 노란 체크 아이콘은 Task다. Title 앞에 붙는 [BI], [Utils] 같은 접두사는 팀이 어떤 기능 영역인지 구분하기 위해 붙이는 컨벤션이다.
Repos — Azure 안의 Git 저장소
Repos는 Azure DevOps 안의 Git 저장소다. Git 자체는 동일하고, remote가 GitHub 대신 Azure로 바뀐 것뿐이다.
Azure Repos vs GitHub
| GitHub | Azure Repos | |
|---|---|---|
| 저장소 방식 | Git | Git (동일) |
| 코드 리뷰 | Pull Request | Pull Request (동일) |
| 권한 관리 | Organization/Team | Azure Active Directory |
| CI/CD 연동 | GitHub Actions | Azure Pipelines |
| 주요 사용처 | 오픈소스, 스타트업 | 기업, Microsoft 스택 |
Git 명령어는 완전히 동일하다. 달라지는 건 remote URL뿐이다.
# 클론
git clone https://my-company@dev.azure.com/my-company/my-project/_git/my-repo
# 이후 push/pull/fetch는 GitHub와 완전히 동일
git push origin feature/my-branch
git pull origin main
Repos의 하위 탭들도 간단히 짚는다.
- Commits: 레포에 쌓인 커밋 히스토리.
git log와 동일한 정보를 UI로 보여준다. 각 커밋을 클릭하면 변경된 파일의 diff를 볼 수 있다 - Pushes: push 단위로 묶어서 보여준다. 커밋이 여러 개인 push도 하나의 단위로 확인 가능하다
- Branches: 현재 어떤 브랜치들이 있는지 보여준다. 브랜치 이름 패턴을 보면 팀이 어떤 전략을 쓰는지 파악된다
- Tags: 특정 커밋에 버전 이름(v1.0.0 등)을 붙이는 탭. 배포 버전을 태그로 관리한다
Pipelines — CI/CD 자동화
Pipelines는 "코드가 머지되면 자동으로 빌드하고 배포한다"는 흐름을 코드로 정의하는 서비스다.
CI/CD란
CI(Continuous Integration, 지속적 통합)와 CD(Continuous Deployment, 지속적 배포)를 합친 개념이다.
- CI: 코드가 레포에 올라올 때마다 자동으로 빌드하고 테스트를 실행한다. 버그가 main에 합쳐지기 전에 잡는 것이 목적이다
- CD: CI를 통과한 코드를 자동으로 서버에 배포한다. 사람이 수동으로 배포 명령을 내릴 필요가 없어진다
개발자 코드 push / PR 머지
↓
[CI] 빌드 + 테스트 자동 실행
↓ 통과
[CD] 스테이징 환경 자동 배포
↓ (승인 후 또는 자동으로)
[CD] 프로덕션 환경 배포
azure-pipelines.yml
정의: Pipelines의 동작을 코드로 정의하는 YAML 파일로, 레포 루트에 위치한다.
핵심 포인트:
- 파이프라인 정의가 코드로 관리된다. UI 설정이 아니라 레포 안 파일로 남아 버전 관리가 된다
- trigger 섹션에서 어떤 이벤트에 실행할지 정의한다
- stages → jobs → steps 계층으로 구성된다
- GitHub Actions의
.github/workflows/*.yml과 역할이 동일하다
기본 구조는 이렇다.
# azure-pipelines.yml
trigger: # 파이프라인을 트리거하는 조건
branches:
include:
- main # main 브랜치에 push/머지될 때 실행
pool:
vmImage: 'ubuntu-latest' # 파이프라인이 실행될 환경 (빌드 에이전트)
stages:
- stage: Build # 빌드 단계
jobs:
- job: BuildApp
steps:
- task: GoTool@0 # Go 빌드 툴 설치
inputs:
version: '1.21'
- script: go build ./... # Go 빌드
displayName: 'Build Go application'
- script: go test ./... # 테스트 실행
displayName: 'Run tests'
- stage: Docker # Docker 이미지 빌드 단계
dependsOn: Build # Build 단계 완료 후 실행
jobs:
- job: BuildImage
steps:
- task: Docker@2
inputs:
command: 'buildAndPush'
repository: 'my-org/my-app'
dockerfile: 'Dockerfile'
- stage: Deploy # 배포 단계
dependsOn: Docker
jobs:
- deployment: DeployToK8s
environment: 'production'
steps:
- script: kubectl apply -f k8s/
displayName: 'Deploy to cluster'
Trigger 종류
파이프라인은 여러 이벤트에 반응하도록 설정할 수 있다.
| 트리거 종류 | 설명 | |
|---|---|---|
| CI 트리거 | trigger: branches: main |
특정 브랜치에 push될 때 |
| PR 트리거 | pr: branches: main |
PR이 생성/업데이트될 때 |
| 스케줄 트리거 | schedules: cron: "0 2 * * *" |
매일 새벽 2시처럼 정기 실행 |
| 수동 트리거 | UI에서 직접 실행 | 필요할 때 사람이 직접 실행 |
Agent — 파이프라인을 실행하는 주체
azure-pipelines.yml을 작성하는 건 "무엇을 할지 정의"하는 것이다. 그럼 그 정의를 실제로 실행하는 주체는 누구인가? 그게 Agent(에이전트)다.
Agent
정의: azure-pipelines.yml에 정의된 빌드/테스트/배포 명령을 실제로 실행하는 서버 프로세스.
핵심 포인트:
- 파이프라인이 트리거되면 Azure DevOps는 사용 가능한 Agent에게 작업을 할당한다
- Agent는 작업을 받아 순서대로 steps를 실행하고, 결과를 Azure DevOps에 보고한다
- Agent의 종류가 두 가지인데, 이 차이가 파이프라인 속도의 핵심이다
Azure DevOps (파이프라인 트리거)
↓ 작업 할당
┌───────────────────────────────────────┐
│ Agent Pool │
│ (사용 가능한 Agent들의 대기 공간) │
│ │
│ Agent 1 (실행 중) │
│ Agent 2 (대기 중) ← 여기서 작업 받음 │
│ Agent 3 (대기 중) │
└───────────────────────────────────────┘
↓
Agent가 steps 실행 → 결과 보고
Microsoft-hosted Agent — 편하지만 느리다
정의: Microsoft가 클라우드에서 관리하는 Agent. vmImage: 'ubuntu-latest'처럼 지정하면 이걸 쓰는 것이다.
핵심 포인트:
- 파이프라인이 실행될 때마다 완전히 새로운 가상 머신(VM)이 프로비저닝된다
- 작업이 끝나면 VM이 폐기된다. 다음 실행 때 또 새로 만든다
- 항상 깨끗한 환경이 보장되지만, VM 부팅 시간(콜드 스타트)이 매번 발생한다
- 사전 설치된 툴(Go, Docker, kubectl 등)이 많지만, 없는 건 매번 설치해야 한다
파이프라인 트리거
↓
[1~2분] VM 프로비저닝 (새 머신 생성) ← 여기서 시간 잡아먹힘
↓
[수십 초] 필요한 툴 설치
↓
실제 빌드/테스트/배포 실행
↓
VM 폐기
Self-hosted Agent — 직접 운영하는 에이전트
Microsoft-hosted Agent의 느린 속도 문제를 해결하는 방법이 바로 Self-hosted Agent다.
정의: 회사가 직접 소유하고 운영하는 서버에 Azure DevOps Agent 프로그램을 설치해서 파이프라인을 실행하는 방식.
핵심 포인트:
- VM을 매번 새로 만들지 않는다. 항상 켜져 있는 서버에서 바로 실행되므로 콜드 스타트가 없다
- 자주 쓰는 툴(Docker, Go, kubectl 등)을 미리 설치해두면 매 실행마다 다시 설치할 필요가 없다
- 사내 네트워크에 있는 서버를 Agent로 쓸 수 있다. 외부에서 접근 불가능한 사내 레지스트리나 DB에 배포할 때 필수다
- Agent 서버를 직접 관리해야 한다는 운영 부담이 생긴다
azure-pipelines.yml에서 Self-hosted Agent를 쓰도록 지정할 때는 pool 섹션을 바꾸면 된다.
# Microsoft-hosted Agent 사용
pool:
vmImage: 'ubuntu-latest'
# Self-hosted Agent 사용
pool:
name: 'MyCompanyAgentPool' # 등록한 Self-hosted Agent Pool 이름
Self-hosted Agent를 설정하는 방법은 간단하다. 서버에 Azure DevOps에서 제공하는 Agent 프로그램을 내려받아 실행하면, 그 서버가 Agent Pool에 등록되고 파이프라인 작업을 받기 시작한다.
# Self-hosted Agent 등록 흐름 (Linux 기준)
mkdir myagent && cd myagent
curl -O https://vstsagentpackage.azureedge.net/agent/3.x.x/vsts-agent-linux-x64-3.x.x.tar.gz
tar zxvf vsts-agent-linux-x64-3.x.x.tar.gz
./config.sh # Azure DevOps URL, PAT 토큰, Agent Pool 이름 입력
./run.sh # Agent 실행 → 이제 파이프라인 작업을 받을 준비 완료
Self-hosted Agent 구현 방식 — 실제로 어디에 올리나
Self-hosted Agent는 "항상 켜진 서버"가 있으면 어디든 올릴 수 있다.
방식 1 — 온프레미스 서버 (물리 서버 / 사내 VM)
가장 단순한 형태다. 회사가 직접 운영하는 서버에 Agent 프로그램을 설치하고 계속 실행해두는 것이다.
사내 서버 (항상 켜져 있음)
├── Go 1.21 설치됨
├── Docker 설치됨
├── kubectl 설치됨
└── Azure DevOps Agent 프로세스 실행 중
↑
파이프라인 트리거되면 여기서 바로 실행
장점은 사내 네트워크에 직접 붙어 있어서 사내 DB, 사내 레지스트리에 자유롭게 접근할 수 있다는 것이다. 단점은 서버가 죽으면 파이프라인도 멈춘다는 것이다.
방식 2 — 클라우드 VM (AWS EC2, Azure VM 등)
온프레미스 서버 대신 클라우드 VM을 상시 띄워두고 거기에 Agent를 올리는 방식이다. 구조는 방식 1과 동일하지만, 서버 관리가 클라우드 콘솔에서 이루어진다. Microsoft-hosted Agent의 콜드 스타트는 없애면서, 온프레미스 서버 없이 클라우드에서 완결되는 구조다.
방식 3 — Docker 컨테이너로 Agent 띄우기
Agent 자체를 Docker 컨테이너로 실행하는 방식이다. 툴 설치 환경을 Dockerfile로 코드화할 수 있어서 재현성이 높다.
# Dockerfile — Agent 이미지 빌드
FROM ubuntu:22.04
# 필요한 툴 설치
RUN apt-get update && apt-get install -y curl git
RUN curl -fsSL https://go.dev/dl/go1.21.linux-amd64.tar.gz | tar -C /usr/local -xz
RUN curl -fsSL https://get.docker.com | sh
# Azure DevOps Agent 다운로드
RUN mkdir /agent && cd /agent && \
curl -O https://vstsagentpackage.azureedge.net/agent/3.x.x/vsts-agent-linux-x64-3.x.x.tar.gz && \
tar zxvf vsts-agent-linux-x64-3.x.x.tar.gz
WORKDIR /agent
CMD ["./run.sh"]
이 이미지를 빌드해서 컨테이너로 실행하면 그 컨테이너가 Agent가 된다. k8s(또는 k3s) 위에 Agent를 Pod으로 띄우는 것도 이 방식의 연장선이다.
k3s 클러스터
└── azure-devops-agent Pod (항상 실행 중)
├── Go, Docker, kubectl 포함된 이미지
└── Azure DevOps에 Agent로 등록됨
↑
파이프라인 트리거 → 이 Pod에서 실행
k3s를 운영하는 팀에서는 이 방식이 특히 자연스럽다. 이미 클러스터를 관리하고 있으니까, Agent도 그 안에서 Pod으로 함께 운영하면 인프라가 한 곳에서 관리된다.
Runner라는 표현에 대해. Runner는 원래 GitHub Actions에서 쓰는 용어다. Azure DevOps에서는 Agent라고 부르지만, 역할이 완전히 동일하기 때문에 실무에서 두 용어를 혼용하는 경우가 많다. "Self-hosted Runner 달자"는 말을 들었다면 Self-hosted Agent를 붙이자는 얘기와 같다.
| Azure DevOps | GitHub Actions | |
|---|---|---|
| 실행 주체 명칭 | Agent | Runner |
| 클라우드 제공 | Microsoft-hosted Agent | GitHub-hosted Runner |
| 직접 운영 | Self-hosted Agent | Self-hosted Runner |
| 설정 파일 | azure-pipelines.yml |
.github/workflows/*.yml |
실무 트레이드오프 — 속도 문제와 대응
Azure DevOps를 선택하는 팀들도 Pipelines에 대해서는 불만을 갖는 경우가 많다. 이 한계를 이해해야 팀에서 나오는 "파이프라인 느리다", "Self-hosted 달자" 같은 대화를 따라갈 수 있다.
Microsoft-hosted Agent의 근본적인 속도 문제. Microsoft-hosted Agent는 매 실행마다 VM을 새로 만든다. 이 콜드 스타트 시간은 보통 1~3분이다. 빌드 자체가 30초면 끝나는 작은 프로젝트에서도 총 파이프라인 시간이 3~4분이 되는 상황이 발생한다. 동시에 여러 파이프라인이 실행되면 사용 가능한 Agent가 없어서 줄을 서야 하는 큐 대기 시간까지 추가된다.
GitHub Actions 대비 생태계 차이. GitHub Actions에는 Marketplace가 있어서 커뮤니티가 만든 수천 개의 Action을 바로 가져다 쓸 수 있다. Azure Pipelines에도 Task 라이브러리가 있지만 규모와 활성화 정도가 미치지 못한다.
코드가 Azure Repos에 있으면 선택지가 좁아진다. GitHub Actions를 쓰려면 코드가 GitHub에 있어야 한다. Azure Repos에 있는 코드를 GitHub Actions로 돌리려면 레포를 GitHub으로 미러링하는 별도 작업이 필요하다.
이 트레이드오프가 실무에서 자주 나오는 딜레마다. Microsoft 생태계(Teams, Outlook, Azure)를 이미 쓰고 있어서 Azure DevOps를 선택했지만, CI/CD 속도 때문에 Self-hosted Agent를 붙이는 작업이 추가로 필요해지는 상황이다.
Microsoft-hosted vs Self-hosted 정리
| Microsoft-hosted | Self-hosted | |
|---|---|---|
| 속도 | 콜드 스타트로 느림 | 빠름 (항상 켜진 서버) |
| 환경 관리 | Microsoft가 관리 | 직접 관리 |
| 사내 네트워크 접근 | 불가 | 가능 |
| 운영 부담 | 없음 | 서버 유지보수 필요 |
| 비용 | 실행 시간 과금 | 서버 운영 비용 |
| 적합한 상황 | 외부 공개 프로젝트, 소규모 팀 | 빠른 빌드, 사내 배포 필요한 팀 |
실제 CI/CD 파이프라인의 상세 구현(파이프라인 파일 3개, CI/CD 단계별 동작, 환경변수 관리, AWS 연동)은 06-깃-CICD.md에서 다룬다.
나머지 서비스 — Test Plans, Artifacts, Advanced Security
Azure DevOps의 핵심은 Boards, Repos, Pipelines 세 가지다. 나머지 서비스는 필요할 때 쓰는 보조 도구 성격이 강하다.
Test Plans
정의: 수동 테스트 케이스를 작성하고 실행 결과를 추적하는 서비스.
핵심 포인트:
- Test Suite(테스트 묶음) 안에 Test Case(테스트 케이스)를 작성한다
- 각 Test Case는 "이 기능을 이렇게 테스트하면 이런 결과가 나와야 한다"는 시나리오다
- 테스터가 실제로 실행하면서 Pass/Fail을 기록한다
- Pipelines의 자동화 테스트(
go test,jest등)와는 다르다. Test Plans는 사람이 직접 하는 수동 테스트(Manual Testing)를 관리하는 곳이다
Artifacts
정의: 팀 내부에서 쓰는 패키지나 라이브러리를 저장하고 공유하는 서비스.
핵심 포인트:
- npm, pip, Maven, Go 모듈 등 다양한 패키지 형식을 지원한다
- 외부 공개 레지스트리(npmjs.com 등)가 아닌 사내 전용 레지스트리를 운영할 수 있다
- Pipelines에서 빌드한 패키지를 Artifacts에 자동으로 올리는 구조를 많이 쓴다
Advanced Security
Repos 메뉴 안에 있는 기능으로, 코드에서 보안 취약점을 자동으로 찾아준다.
핵심 포인트:
- Secret Scanning: AWS 키, API 토큰 같은 민감한 정보가 코드에 실수로 커밋됐는지 감지한다
- Dependency Scanning: 사용 중인 오픈소스 라이브러리에 알려진 취약점(CVE)이 있는지 검사한다
- Code Scanning: 코드 자체의 보안 취약점(SQL Injection, XSS 등)을 정적 분석으로 찾는다
- PR 생성 시 자동으로 스캔이 돌아서 취약점이 발견되면 PR에 경고가 표시된다
권한 관리 — Azure Active Directory
Azure DevOps의 권한은 Microsoft의 ID 관리 서비스인 Azure Active Directory(AAD, 현재는 Microsoft Entra ID로 명칭 변경)를 기반으로 동작한다.
Azure Active Directory (AAD)
정의: Microsoft의 클라우드 기반 ID 및 접근 관리 서비스로, Azure DevOps의 사용자 인증과 권한 부여를 담당한다.
핵심 포인트:
- 회사 이메일 계정이 AAD에 등록된 계정이다
- Azure DevOps 접근 권한은 Organization 수준과 Project 수준으로 나뉜다
- 역할(Role)은 크게 세 가지다 — Reader(읽기), Contributor(읽기+쓰기), Project Administrator(관리자)
- 관리자가 AAD에서 계정을 Project에 추가하고 역할을 부여해야 접근이 가능해진다
Azure Active Directory (company.com 테넌트)
└── 사용자: user@company.com
↓ 권한 부여
Azure DevOps Organization
├── Project A
│ └── 역할: Contributor (읽기 + 쓰기 가능)
└── Project B
└── 역할: Reader (읽기만 가능)
Project에 권한이 없는 상태에서 클론을 시도하면 403 Forbidden 오류가 발생한다. 이 경우 Project Administrator에게 Contributor 권한 추가를 요청하면 된다.
실무에서 하루 흐름 — 전체 연결
Azure DevOps의 각 서비스가 실제로 어떻게 연결되는지, 기능 하나를 개발하는 과정을 처음부터 끝까지 따라가보자.
[1] Boards — 태스크 확인
아침 Daily Standup 전에 Boards 열기
→ 내 이름으로 할당된 Task 확인
→ Task 상태를 "Active"로 변경
↓
[2] Repos — 코드 작업
git checkout -b feature/my-feature
# 코드 작성
git add . && git commit -m "feat: add my feature"
git push origin feature/my-feature
↓
[3] Repos — PR 생성
Azure Repos에서 PR 생성
→ Reviewer 지정 (팀원)
→ Work Item 연결 (Boards의 Task와 PR을 링크)
↓
[4] Repos — 코드 리뷰
팀원이 PR 열어서 라인별 코멘트
→ 수정 필요하면 로컬에서 수정 후 push
→ 팀원 Approve
↓
[5] Pipelines — 자동 트리거 (PR 머지 시)
main 브랜치에 머지됨
→ Agent Pool에서 사용 가능한 Agent에게 작업 할당
→ Agent가 azure-pipelines.yml 순서대로 실행
├── 빌드
├── 테스트 실행
├── Docker 이미지 빌드
└── 클러스터 배포
↓
[6] Boards — Task 완료 처리
Task 상태를 "Closed"로 변경
→ Sprint Burndown Chart에 반영
Work Item과 PR을 연결하면 Boards에서 어떤 Task가 어떤 PR로 구현됐는지 추적할 수 있다. Azure DevOps가 단순한 툴 모음이 아니라 개발 사이클 전체를 연결하는 플랫폼인 이유가 여기에 있다.