# GitHub Actions 비용 → self-hosted runner 전환 검토

작성: 2026-08-31 KST · ANU · **상태: 보류 (회장 지시 — 나중에 재검토)**

## 1. 왜 이 검토가 시작됐나

**2026-08-30 하루에 Actions minutes 가 두 번 소진**됐다. 회장님이 두 번 다 budget 을 올려
해결했으나, 하루 두 번은 우연으로 보기 어렵다.

회장 질문(verbatim): **"github 의 비용 안 들이고 자체적으로 하는 방법은 없는거야? ㅠㅠ"**

## 2. 소진 원인 — 실측

### 2-1. 커밋 하나당 워크플로우 3개가 돈다
`ci.yml` · `diagnostic-pytest.yml` · `e2e.yml` — 전부 `runs-on: ubuntu-latest`(GitHub-hosted).
그 외 `extension-release.yml` · `taskctl-ci.yml` 도 동일.

### 2-2. `update-branch` 마다 전량 재실행된다
t3071 한 건에만 CI run 이 **4번** 돌았다:
원래(`a74d4658`) → update-branch(`de35c69e`) → rerun 2회.

### 2-3. 고칠 계획 없는 실패에 매번 minutes 를 쓴다
`ci` · `e2e-test` 는 **상시 실패**(선재). t3069·t3070·t3071 전부 동일.
`ci` 는 과거 `[Errno 28] No space left on device`, `e2e-test` 는 `task-2998` spec 이 원인으로 기록돼 있다.

### 2-4. 소진 시 증상 (판별법 — 재발 시 이걸로 확인하라)
- job 이 **1~2초 만에 실패**하고 `steps=0`
- 로그 요청 시 `BlobNotFound`
- **테스트 실패가 아니다.** job 이 러너를 못 받아 시작조차 못 한 것
- 워크플로우 run 의 `conclusion=failure` 인데 실패한 step 목록이 **비어 있다**
- budget 인상 후 rerun 하면 `steps=3~9` 로 정상 복구된다 (2026-08-30 실증)

★ 이 증상을 **코드 회귀로 오독하지 마라.** t3071 에서 `diagnostic` 이 실패했는데
재실행하니 `steps=9` success 였다. 같은 코드다.

## 3. 해법 — self-hosted runner

GitHub 이 과금하는 것은 **GitHub-hosted runner(자기네 서버) 사용 시간**이다.
자체 서버에 러너를 설치하면 **minutes 소모 0**이다.

변경점은 워크플로우의 `runs-on: ubuntu-latest` → `runs-on: self-hosted` 한 줄.

### 3-1. 이 환경의 적합성 — 보안 관문 통과

GitHub 공식 문서는 self-hosted runner 를 강하게 경고한다. 이유는
**public repo 에서 외부인이 fork PR 로 임의 코드를 우리 서버에서 실행**할 수 있기 때문.

InsuRo 는 **private repo · 협업자 1명(회장) · 포크 0** 이다(2026-08-20 실측 기록).
→ 그 위험이 **구조적으로 존재하지 않는다.** 이 경우엔 적합하다.

### 3-2. 성능은 이미 입증됨
ANU 가 2026-08-30 이 서버에서 회귀 **3,119건을 205초(3분 25초)** 에 완주했다.
CI 가 하는 일과 동일 작업이다.

## 4. ★ 걸림돌 — CPU 부하 (이게 보류 사유다)

2026-08-31 01:38 KST 실측:

| 항목 | 값 | 판정 |
|---|---|---|
| CPU | 6코어 (i5-8400 @2.80GHz) | — |
| 메모리 | 15Gi 중 여유 **7.8Gi** | 여유 있음 |
| 디스크 | 190G 여유 / 466G (58%) | 여유 있음 |
| **부하 평균** | **53.76 / 78.52 / 39.72** | **심각한 과부하** |

6코어에서 부하 53~78 은 정상치(≤6)의 **9~13배**다.
측정 시점에 봇 2개(t3072·t3073)가 `claude -p` 실호출을 병렬로 때리고 있었다.

**2026-08-30 서버 다운도 이것과 무관하지 않을 수 있다.**
여기에 CI 를 얹으면 다운 빈도가 늘어날 위험이 있다.

→ **병목은 CPU. 메모리·디스크가 아니다.**

## 5. 단계적 방안

### 1단계 — pytest 계열만 전환 (효과 대부분 여기서 나온다)
`ci.yml` · `diagnostic-pytest.yml` 만 `self-hosted` 로. 가벼운 guard 류는 GitHub-hosted 유지.
→ minutes 소진 주범이 사라진다.

### 2단계 — 러너 1대만 등록해 직렬화
러너가 1대면 job 이 자동 직렬화되어 부하 폭증이 없다.
CI 는 느려지지만, **지금은 러너를 못 받아 아예 실패하는 상황**이라 느린 편이 낫다.

### 3단계 — 봇 작업과 시간대 분리
관측 데이터 확보 후 결정.

## 6. 감수해야 할 것

- **서버가 죽으면 CI 도 멈춘다.** 2026-08-30 리붓 같은 일이 생기면 그 사이 CI 정지.
  GitHub-hosted 는 이 문제가 없다
- 러너 관리 부담 — systemd 유닛으로 등록하면 자동 재시작되어 부담은 작다
- 시크릿 노출면 증가는 **거의 없다.** 이 서버가 이미 `.env.keys` 로 전 자격증명을 보유

## 7. ★ 재검토 시 반드시 먼저 확인할 것

1. **평상시 부하** — 봇이 모두 idle 인 상태에서 `uptime` 재측정.
   여기서도 6 이상이면 CI 를 얹기 전에 부하 원인부터 규명해야 한다
2. 디스크 여유 (러너 작업공간 + 캐시가 쌓인다)
3. `ci` · `e2e-test` 상시 실패가 그때도 남아 있는지 —
   **고칠 계획 없는 실패에 자원을 쓰는 구조 자체가 더 근본 문제다**
4. t3065(Actions 동시성 최적화)가 여전히 보류 상태인지.
   2026-08-30 회장 판단 = **"효과 대비 위험 대비 보류"**

## 8. 되돌리는 법

`runs-on: self-hosted` → `runs-on: ubuntu-latest` 원복 + 러너 서비스 정지.
**워크플로우 파일 한 줄이라 롤백이 저렴하다** — 이게 이 방안의 가장 큰 장점이다.

## 9. 대안 (self-hosted 없이)

- **상시 실패하는 `e2e-test` 를 PR 트리거에서 제외**하거나 원인(`task-2998` spec) 수정
  → ANU 권고: 동시성 제어보다 위험이 낮고 효과가 확실하다
- `update-branch` 재실행 최소화 — 머지 직전에만 update
- t3065 재검토

## 10. 회장 결정 이력

- 2026-08-30 — t3065(동시성 최적화): **"효과 대비 위험 대비 보류하자"**
- 2026-08-31 — self-hosted 검토: **"나중에 다시 결정해볼게. 문서로 정리해둬"**
