로그를 처음부터 끝까지 읽지 않고 첫 에러 줄부터 찾습니다.
| 순서 | 명령 | 목적 |
|---|---|---|
| 1 | `grep -n -m1 -iE 'error: | FAIL'` |
| 2 | grep -n -A2 'FAIL' |
원인까지 몇 줄 더 보기 |
| 3 | 실패 단계 확인 | build 인지 test 인지 구분 |
뒤 단계 로그는 앞 단계가 실패하면 대부분 의미가 없어 건너뛰어도 됩니다.
파이프라인에서만 재현되는 문제는 드뭅니다. 대부분 같은 명령을 로컬에서 그대로 실행하면 재현됩니다.
같은 JDK 버전, 같은 작업 폴더 상태(클린 체크아웃)에서 실행해야 차이가 사라집니다. 재현되면 수정 후 같은 명령으로 통과를 확인하고 나서 커밋합니다.
같은 코드, 같은 입력인데 성공과 실패가 번갈아 나오는 테스트입니다. 원인은 보통 타이밍(Thread.sleep), 실행 순서 의존, 공유 자원(포트·파일) 충돌입니다.
| 흔한 원인 | 예시 |
|---|---|
| 타이밍 의존 | Thread.sleep(50) 뒤 상태 확인 |
| 순서 의존 | 이전 테스트가 남긴 데이터 사용 |
| 공유 자원 | 같은 포트를 여러 테스트가 사용 |
| 외부 시스템 | 네트워크·시계 의존 |
재시도는 파이프라인을 통과시키는 임시 조치일 뿐 원인을 고치지 않습니다. 재시도로 통과했다는 기록(몇 번째 시도인지)을 남겨야 나중에 원인을 추적할 수 있습니다.
GitHub Actions 는 uses: nick-fields/retry-action 같은 서드파티 액션으로 재시도 스텝을 만들 수 있습니다. 재시도 횟수가 계속 늘어난다면 테스트를 고치거나 격리해야 할 신호입니다.
| 원인 | 설명 |
|---|---|
| 캐시 미사용 | 매번 의존성을 새로 내려받음 |
| 순차 실행 | 병렬로 돌릴 수 있는 잡을 하나로 묶음 |
| 불필요한 단계 | push 마다 lint 까지 전부 실행 |
| 얕지 않은 클론 | 전체 히스토리를 매번 내려받음 |
actions/cache 로 의존성을 캐시하고, 독립적인 테스트는 여러 잡으로 나눠 병렬로 돌립니다. git clone --depth 1 로 체크아웃 시간을 줄이고, lint 는 PR 트리거에서만 돌립니다.
단계별 시간을 표로 비교하면 어디를 먼저 고칠지 우선순위가 보입니다(예제 참고). 병목이 아닌 단계를 최적화해봐야 전체 시간은 거의 줄지 않습니다.
| 패턴 | 대상 |
|---|---|
ghp_[A-Za-z0-9]{20,} |
GitHub 개인 액세스 토큰 |
AKIA[0-9A-Z]{16} |
AWS 액세스 키 ID |
-----BEGIN ... PRIVATE KEY----- |
개인 키 파일 |
정규식은 알려진 형식만 잡으므로 완벽하지 않습니다. 그래도 커밋 전에 한 번 걸러내면 사고 대부분을 막습니다.
.git/hooks/pre-commit 스크립트가 git diff --cached 결과에서 패턴을 찾아 있으면 종료 코드 1 로 커밋을 막습니다. 개인 저장소마다 설치해야 하므로 팀 전체는 pre-commit 프레임워크나 서버 쪽 pre-receive 훅으로 강제하기도 합니다.
파일에서 지우고 커밋해도 과거 커밋 히스토리에는 그대로 남습니다. 키를 발급한 서비스에서 즉시 폐기(revoke)하고 새 키를 발급하는 것이 먼저입니다.
히스토리에서 완전히 지우려면 git filter-repo 같은 도구가 필요하지만, 이미 공유된 저장소라면 키 폐기가 히스토리 정리보다 우선입니다.
GitHub 가 관리하는 러너 대신 방화벽 안쪽 서버를 러너로 등록해 인터넷 접근 없이 파이프라인을 돌립니다. 러너는 서버 쪽으로 아웃바운드 연결만 하므로 인바운드 포트를 열 필요가 없습니다.
| 항목 | 값 |
|---|---|
| 라벨 | self-hosted, linux, closed-net |
| 등록 | config.sh --url ... --token ... |
| 상시 실행 | svc.sh install && svc.sh start |
워크플로는 runs-on: [self-hosted, closed-net] 처럼 라벨로 매칭합니다.
성공할 때마다 알림을 보내면 곧 무시당합니다(알림 피로). 실패, 특히 연속 실패나 배포 실패에만 보내는 것이 기본 원칙입니다.
if: failure() 조건을 붙인 마지막 잡에서 내부 웹훅으로 메시지를 보냅니다. 메일·메신저 어느 쪽이든 실패한 워크플로 이름과 실행 번호를 포함해야 바로 찾아갈 수 있습니다.
실패 대응 순서, 시크릿 유출 시 연락처, 러너 라벨, 알림 채널을 RUNBOOK 문서 한 곳에 모아둡니다. 담당자가 바뀌어도 같은 문서를 보고 같은 순서로 대응할 수 있습니다.