Jenkins가 2주에 걸쳐 EFS 버스트 크레딧을 서서히 소진한 방법
Source: Dev.to
TL;DR
우리의 Jenkins가 1/26에 실패하기 시작했지만, 근본 원인은 1/13에 시작되었습니다. 우리는 세 가지 복합적인 문제를 발견했습니다:
- Shared Library cache가 비활성화됨 (기존 문제)
- disposable agents로 전환 (1/13 변경)
- Increased build frequency (새해 효과)
Result: ≈ 50× 증가한 메타데이터 IOPS → EFS 버스트 크레딧이 2주 동안 소진됨.
Why You Should Care
Jenkins를 EFS에서 실행하고 있다면 이런 일이 발생할 수 있습니다. 증상은 갑자기 나타나지만, 근본 원인은 종종 몇 주 전부터 시작됩니다. 메트릭의 시계열 분석이 중요합니다.
미스터리: 증상 vs. 근본 원인
Previously, I wrote about how Jenkins became slow and Git clones started failing. We found ~15 GB of Git temporary files (tmp_pack_*) accumulated on EFS, causing metadata IOPS exhaustion.
We fixed it with Elastic Throughput and cleanup jobs. Case closed, right?
Not quite.
When I checked the EFS Burst Credit Balance graph, I noticed something important:
The credit started declining around 1/13, but symptoms appeared on 1/26.
Timeline
| 날짜 | 이벤트 |
|---|---|
| 1/13 | 크레딧 감소 시작 |
| 1/19 | 급격한 감소 |
| 1/26 | 크레딧 바닥 도달 |
| 1/26‑27 | 증상 발생 |
The tmp_pack_* accumulation was a symptom, not the root cause. Something changed on 1/13.
1월 13일에 무엇이 바뀌었나요?
솔직히, 이게 저를 난감하게 만들었어요. 몇 가지 생각은 있었지만 확실한 것은 없었습니다:
1. 에이전트 아키텍처 변경
1월 13일경에 우리는 Jenkins 에이전트 전략을 변경했습니다:
| 이전 (공유 에이전트) | 이후 (일회용 에이전트) |
|---|---|
| EC2 유형: c5.large 등. | EC2 유형: t3.small 등. |
| 여러 작업이 에이전트를 공유 | 작업당 하나의 에이전트, 사용 후 파기 |
| 작업 공간 재사용 | 매번 전체 git clone 수행 |
증분 업데이트를 위한 git pull | 매번 전체 복제를 위한 git clone |
목표는 비용 절감이었습니다. 메타데이터 IOPS 영향은 고려하지 않았습니다.
2. 연말연시 이후 개발 급증
팀들이 새해 연휴 이후 개발을 가속화하면서 전체 Jenkins 부하가 증가했습니다.
The Math: 50× Metadata IOPS Increase
Builds per day: 50 (estimated)
Files created per clone: 5,000
Shared‑agent approach:
Clone once = 5,000 metadata operations
Disposable‑agent approach:
50 builds × 5,000 files = 250,000 metadata operations/day
≈ 50× increase in metadata IOPS.
Add the New‑Year rush, and the numbers get even worse.
Source:
Jenkins에서 Git 캐시 이해
조사를 진행하던 중 다음 디렉터리를 발견했습니다:
/mnt/efs/jenkins/caches/git-3e9b32912840757a720f39230c221f0e
이는 Jenkins Git 플러그인의 베어‑레포지토리 캐시입니다.
Git 캐시 작동 방식
플러그인은 클론을 최적화하기 위해 다음을 수행합니다:
- 원격 레포지토리를
/mnt/efs/jenkins/caches/git-{hash}/에 베어 레포지토리 형태로 캐시합니다. - 작업 공간으로 클론할 때 이 캐시를
git clone --reference옵션으로 사용합니다. - 해시는 레포지토리 URL + 브랜치를 기반으로 생성됩니다.
문제점: 일회성 에이전트는 매 빌드마다 새로 생성되므로 이 캐시의 혜택을 받지 못할 수 있습니다.
스모킹 건: tmp_pack_* 위치
I revisited where the tmp_pack_* files lived:
jobs/sample-job/jobs/sample-pipeline/builds/104/libs/
└── 335abf.../root/.git/objects/pack/
└── tmp_pack_WqmOyE ← 100‑300 MB
These are in per‑build directories:
jobs/sample-job/jobs/sample-pipeline/
└── builds/
├── 104/
│ └── libs/.../tmp_pack_WqmOyE
├── 105/
│ └── libs/.../tmp_pack_XYZ123
└── 106/
└── libs/.../tmp_pack_ABC456
Every build was re‑checking out the Pipeline Shared Library, generating tmp_pack_* each time.
Question: Why is the Shared Library being fetched on every build?
근본 원인: 캐시 설정이 꺼짐
In Jenkins configuration I found the smoking gun:
공유 라이브러리 설정인 “컨트롤러에서 가져온 버전을 캐시하여 빠르게 검색” 옵션이 선택 해제되어 있었습니다.
Consequences:
- 공유 라이브러리 캐시가 완전히 비활성화되었습니다.
- 각 빌드마다 원격 저장소에서 전체를 가져옵니다.
.git/objects/pack/에 임시 파일이 생성됩니다.- 대규모 메타데이터 IOPS 사용량이 발생합니다.
The Fix: Enable Caching
- Enable “Cache fetched versions on controller for quick retrieval”.
- Set Refresh time in minutes to 180 minutes.
Choosing the Refresh Time
| Refresh interval | Effect |
|---|---|
| 60‑120 min | Fast updates, moderate IOPS reduction |
| 180 min (3 h) | Balanced – ~8 updates/day |
| 360 min (6 h) | Stable – ~4 updates/day |
| 1440 min (24 h) | Maximum IOPS reduction |
Why 180 min?
- Updates check ~8 times/day (9 am, 12 pm, 3 pm, 6 pm…).
- Shared Library changes reflected within half a day is acceptable.
- Significant IOPS reduction (once per 3 h instead of every build).
- Urgent changes can be forced via the “force refresh” feature.
I documented this in our runbook so we don’t forget.
영향 측정
| 기간 | 예상 관찰 |
|---|---|
| 단기 (24‑48 시간) | 새 tmp_pack_* 파일이 생성되지 않음; 메타데이터 IOPS 감소 |
| 중기 (1 주) | 버스트 크레딧 잔액 회복 추세; 빌드 성능 안정 |
| 장기 (1 개월) | 크레딧이 안정적으로 유지; 재발 없음 |
교훈
1. 증상 ≠ 근본 원인 타임라인
- 증상 발생: 1/26‑1/27
- 근본 원인: 약 1/13
- 크레딧 고갈: 2주에 걸쳐 점진적
시계열 분석이 중요합니다. 눈에 보이는 증상만 고쳐도 피상적인 해결책에 그칩니다.
2. 아키텍처 변경에는 숨겨진 비용이 있다
일회용 에이전트 변경으로 EC2 비용은 줄었지만 다른 곳에 문제를 만들었습니다.
아키텍처를 변경할 때:
- 사전에 성능 영향을 평가한다.
- 적절한 모니터링을 설정한다.
3. EFS 메타데이터 IOPS 특성
- 작은 파일을 대량으로 생성/삭제하는 것은 치명적이다.
- 파일 개수가 저장 용량보다 더 중요하다.
- 버스트 모드는 크레딧 관리를 필요로 한다.
- 크레딧 고갈은 점진적으로 일어난다.
특히
.git/objects/에 수천 개의 작은 파일이 포함된 경우, 동작이 일반 파일 I/O와 크게 다릅니다.
4. 복합 근본 원인
이 문제는 단일 원인이 아니라 세 가지 요인이 결합된 것이었다:
- 공유 라이브러리 캐시 비활성화 (기존부터)
- 일회용 에이전트 전환 (1/13)
- 빌드 증가 (새해)
각각만으로는 큰 문제를 일으키지 않을 수 있지만, 함께 작용하면 임계값을 초과했다.
미해결 질문
공유 라이브러리 캐싱을 활성화했지만, 여전히 일회성 에이전트를 사용하고 있습니다.
일회성 에이전트에서도 에이전트 측 Git 캐시를 효과적으로 활용할 수 있을까요?
가능한 접근 방식:
- 모든 에이전트에서 EFS Git 캐시 공유
- 작업 간 재사용을 위해 에이전트 수명을 약간 연장
- S3에 캐시를 저장하고 시작 시 동기화
비용과 성능 사이의 적절한 균형을 찾는 것이 여전히 과제입니다.
기술적 의사결정 및 엔지니어링 실천에 대해 제 블로그에서 더 많이 다루고 있습니다. 확인해 보세요.