엔지니어링 블로그

GitHub 장애에도 Git과 CI를 계속 돌리기 위한 Forgejo hot standby

GitHub 장애에도 Forgejo와 NAS, MacBook runner로 CI를 이어가는 두 번째 control plane 구조

지난번 Github 장애가 있은지 시간이 흘렀지만 그때 Github 만 믿고 있으면 안된다는 생각이 들었다. 그래서 대안을 찾아보기로 했다. 내가 가진 자원은 맥북과 u5825 nas 한대인데 이것을 최대한 활용 하는 것이 목표이다.

Github 장애가 나면 단순히 저장소만 못 쓰는 게 아니다. 실제로는 PR, Github Actions, self-hosted runner 스케줄링, Release, OIDC 기반 배포까지 한꺼번에 영향을 받을 수 있다. NAS와 맥북에 runner가 살아 있어도 Github Actions control plane이 죽으면 실행할 작업을 받지 못한다.

그래서 필요한 것은 단순한 Git 미러가 아니라, Github 없이도 최소한 Git과 CI를 계속 돌릴 수 있는 두 번째 control plane이다.

여기서 가장 괜찮아 보였던 것이 Forgejo다.

Forgejo는 설치형 Git forge로 저장소, Pull Request, Issue, Release, Package Registry, Actions 기반 CI까지 제공한다. 완전 무료로 self-host할 수 있고 Github Actions와 비슷한 workflow 문법을 쓸 수 있어 기존 프로젝트를 옮기기도 비교적 쉽다.

처음에는 아예 Forgejo로 완전히 이관하는 것도 생각했다.

Forgejo
├─ Git
├─ PR
├─ CI
├─ Release
└─ Deploy
   ↓ mirror
GitHub
└─ backup / public mirror

하지만 결국 그렇게 하지는 않기로 했다.

이유는 Codex Security다. Myven에서 Codex Security를 실제로 돌려보면 일반적인 코드 리뷰보다 훨씬 깊게 보는 경우가 많았다. 단순히 현재 diff를 보는 것이 아니라 코드베이스 전체와 threat model을 기반으로 취약 경로를 찾는 식이라 결과가 꽤 좋았다.

Codex automatic Code Review도 Github PR에 자연스럽게 붙는다.

Forgejo를 canonical repository로 바꾸면 이런 Github 기반 기능을 그대로 쓰기 어려워진다. Git repository 자체는 mirror할 수 있지만 Forgejo의 PR, review comment, approval state까지 Github로 복제되는 것은 아니다.

그래서 현재 생각하는 구조는 다음과 같다.

                   GitHub
                   ├─ canonical repository
                   ├─ PR / Review
                   ├─ Codex Code Review
                   ├─ Codex Security
                   └─ Release
Developer
   │
   └──────────────► Forgejo
                    ├─ Git mirror
                    ├─ emergency PR
                    └─ Forgejo Actions
                         │
                 ┌───────┴────────┐
                 ▼                ▼
             U5825 NAS         MacBook
             max 3 jobs        max 2 jobs

즉 평상시에는 Github를 그대로 사용하고, Forgejo는 Github 장애 시 사용할 수 있는 hot standby 역할을 맡긴다.

핵심은 Forgejo에 저장소만 복사해두는 것이 아니다. CI까지 Github와 독립적으로 돌아가야 한다.

예를 들어 Github Actions에 다음처럼 CI 로직이 잔뜩 들어 있다면:

- name: test
  run: |
    # 긴 테스트 로직

가능하면 실제 작업을 repository script로 빼는 편이 좋다.

scripts/ci/
├─ lint.sh
├─ test-api.sh
├─ test-web.sh
├─ test-browser.sh
└─ build-images.sh

Github Actions에서는:

- run: ./scripts/ci/test-api.sh

Forgejo Actions에서도 똑같이:

- run: ./scripts/ci/test-api.sh

를 호출한다.

이렇게 하면 Github Actions와 Forgejo Actions는 단순한 scheduler가 되고, 실제 CI 로직은 특정 서비스에 종속되지 않는다.

Github API 의존성도 주의해야 한다.

예를 들어 현재 CI가:

gh api /repos/.../actions/runs

를 호출해서 NAS runner 3개가 가득 찼는지 확인한 다음 Mac runner로 넘기는 구조라면, Github 장애 시 Forgejo에서도 그대로 사용할 수 없다.

Forgejo 쪽에서는 이런 동적 selector를 최대한 없애고 runner label과 concurrency를 사용하는 쪽이 단순하다.

NAS

  • linux-amd64
  • docker
  • browser
  • concurrency 3

MacBook

  • linux-arm64
  • docker
  • browser
  • concurrency 2

정확한 NAS 우선 스케줄링보다 장애 상황에서 단순하게 동작하는 것이 더 중요하다.

또 하나 놓치기 쉬운 부분이 uses: actions/…다.

Forgejo workflow에 다음이 있다고 하자.

- uses: actions/checkout@...
- uses: actions/setup-node@...

Forgejo 자체는 살아 있어도 이 action을 Github에서 받아와야 한다면 Github 장애에 완전히 독립적이지 않다.

마찬가지로 CI image가 GHCR에만 있으면 GHCR 장애에도 영향을 받는다.

그래서 장기적으로는 필수 action을 Forgejo에 mirror하거나, setup action을 script로 바꾸거나, 필요한 도구를 미리 넣은 CI toolchain image를 만들어두는 편이 낫다.

예를 들면:

myven-ci-toolchain
├─ Node
├─ pnpm
├─ Python
├─ uv
├─ Terraform
├─ gitleaks
└─ browser dependencies

를 amd64와 arm64로 만들어두면 NAS와 MacBook을 모두 활용할 수 있다.

Forgejo 자체는 NAS의 Docker 를 이용할 것이다. Unraid를 사용하는데 Docker UI 를 제공한다.

U5825 NAS
├─ Unraid docker
│   └─ Forgejo
│
└─ k3s
    └─ CI workloads

처럼 control plane과 workload를 분리하는 편이 낫다.

Github 장애가 실제로 발생하면 다음 정도로 사용할 수 있다.

branch
 ↓
Forgejo
 ├─ temporary PR
 └─ Forgejo Actions
      ├─ lint
      ├─ unit test
      ├─ E2E
      └─ build

Github가 복구되면 같은 branch를 다시 Github로 push하고 정상적인 Github PR을 만들어 Codex Review와 Codex Security를 거친 뒤 최종 merge한다.

결국 Forgejo를 도입하는 목적은 Github를 버리는 것이 아니다.

오히려:

GitHub
= 평상시 메인 개발 플랫폼
Forgejo
= Github 장애 시 독립적으로 쓸 수 있는 Git + CI 대안

으로 역할을 나누는 것이다.

원래는 Forgejo로 완전히 이관하는 쪽이 더 깔끔하다고 생각했다. 하지만 현재로서는 Codex Security의 결과가 충분히 좋아 Github를 포기할 이유가 크지 않다.

대신 Forgejo를 NAS에 두고, U5825 NAS와 MacBook을 runner로 활용해 Github가 없어도 테스트와 빌드까지는 계속할 수 있게 만들 계획이다.

중요한 검증 기준도 단순하다.

Github 도메인과 API, GHCR 접근을 막은 상태에서도 Forgejo CI가 끝까지 성공하는가?

이것만 실제로 주기적으로 확인할 수 있다면, 단순한 Git mirror보다 훨씬 실용적인 Github 장애 대비 환경이 된다.

https://x.com/studiojin_dev/status/2091818813876768896

Join the Investigation

Get the latest updates on my projects and indie hacking journey directly in your inbox.

No spam. Unsubscribe anytime.