GitHub Actions는 성공했는데 애플리케이션이 실행되지 않은 이유
GitHub Actions 워크플로가 초록색으로 끝났다. 빌드와 배포가 완료됐으니 서비스도 정상일 것이라고 생각했다. 하지만 EC2에서 확인한 Spring Boot 애플리케이션은 실행되지 않은 상태였다.
결론부터 말하면 배포 파일의 문제가 아니었다. 애플리케이션 코드는 세션 기반 인증을 기대하도록 바뀌었지만, 운영 환경의 AWS Systems Manager Parameter Store에는 기존 Basic Auth 설정이 남아 있었다. 코드는 배포됐지만 코드가 의존하는 운영 설정은 함께 바뀌지 않은 것이 원인이었다.
초록색 체크를 보고 배포 문제라고 생각하지 못했다
현재 GitHub Actions 워크플로는 코드를 빌드하고, 배포 파일을 EC2로 전송하고, 정해진 스크립트로 서비스를 재시작한다. 이 단계들이 오류 없이 끝났기 때문에 처음에는 배포가 완료됐다고 판단했다.
하지만 워크플로 성공은 워크플로에 작성된 명령이 성공했다는 뜻이다. 프로세스가 재시작 직후 종료되거나, 애플리케이션 초기화 중 외부 시스템 연결에 실패하더라도 배포 스크립트가 이를 확인하지 않으면 Actions는 성공으로 끝날 수 있다.
파일 전송과 프로세스 재시작이 끝나도 애플리케이션 초기화, 외부 인증, 헬스 체크까지 통과해야 실제 서비스가 정상이다.
EC2에서 실행 상태부터 확인했다
먼저 EC2에 접속해 systemd가 관리하는 서비스 상태를 확인했다.
1
sudo systemctl status <서비스명>
서비스가 정상적인 active (running) 상태가 아니었다. 여기서 배포 파일 전송보다 애플리케이션 기동 과정에 문제가 있을 가능성이 커졌다. 다음으로 해당 서비스의 로그를 확인했다.
1
2
sudo journalctl -u <서비스명>
sudo journalctl -u <서비스명> --since "2026-08-04 00:00:00"
처음에는 가장 최근 로그만 봤지만, 재시작이 반복되면 원인이 된 첫 오류가 뒤의 종료 메시지에 묻힐 수 있었다. 그래서 장애가 시작된 시간을 기준으로 범위를 좁히고, 오류를 나타내는 단어를 검색했다.
1
2
sudo journalctl -u <서비스명> | grep -i "error"
sudo journalctl -u <서비스명> | grep -iE "error|exception|failed"
단순히 error 한 줄만 찾는 것으로 끝내지 않았다. 해당 줄의 앞뒤 로그를 함께 보면서 어떤 초기화 작업이 진행 중이었는지 확인했다. 그 결과 외부 API 인증 또는 세션 검증 설정과 관련된 예외를 발견했다. 이 시점부터 문제의 범위를 배포 자체가 아니라 애플리케이션 설정과 외부 인증 과정으로 좁힐 수 있었다.
실제 원인은 코드와 운영 설정의 불일치였다
애플리케이션의 인증 방식은 기존 Basic Auth에서 세션 기반 인증으로 변경되어 있었다. 그러나 Parameter Store에는 이전 방식에서 사용하던 값이 남아 있었고, 새로운 인증 방식에 필요한 설정은 현재 코드가 기대하는 형태와 맞지 않았다.
같은 코드라도 런타임에 읽는 설정이 맞지 않으면 초기화 결과가 달라진다.
실패 과정은 다음처럼 정리할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
코드에서 인증 방식 변경
↓
운영 환경 설정은 기존 값 유지
↓
애플리케이션 시작 시 기존 설정을 읽음
↓
필수 설정 누락 또는 잘못된 인증 요청 발생
↓
Spring Bean 초기화 실패 또는 외부 API 호출 실패
↓
애플리케이션 종료
외부 서비스의 실제 이름, 파라미터 경로와 인증값은 로그나 문서에 남기지 않았다. 중요한 것은 특정 인증 방식 자체가 아니라, 코드의 설정 계약이 바뀌었는데 운영 설정 변경이 배포 작업에서 빠졌다는 점이다.
GitHub Actions가 이 문제를 해결하지 못한 이유
GitHub Actions가 Parameter Store를 수정할 수 없는 것은 아니다. 워크플로에 필요한 IAM 권한을 부여하고 AWS CLI나 스크립트를 실행하면 파라미터를 변경할 수 있다. 이번에는 현재 워크플로에 그 작업이 없었기 때문에 코드 배포만 진행됐고, Parameter Store 값은 자동으로 바뀌지 않았다.
그렇다고 모든 비밀값 변경을 배포 워크플로에 넣는 것이 항상 정답은 아니다. 배포 역할에 Parameter Store 쓰기 권한을 주면 권한 범위가 넓어진다. 잘못된 값이 자동 반영될 위험도 있고, 코드보다 설정을 더 엄격한 승인 절차로 관리해야 하는 환경도 있다. 보안, 권한, 변경 빈도와 운영 방식에 따라 자동화 범위를 결정해야 한다.
이번 문제는 GitHub Actions의 기능 부족이 아니었다. 코드 변경과 운영 설정 변경을 하나의 릴리스 단위로 확인하지 못한 것이 핵심이었다. 이전에 정리한 CI/CD는 YAML 작성이 아니라 안정적인 배포와 복구를 설계하는 과정이었다라는 글의 관점을 실제 장애에서 다시 확인했다. 배포 브랜치와 변경 흐름은 배포 준비와 다음 기능 개발을 병렬로 진행하기 위한 Git 브랜치 전략과도 연결된다.
Parameter Store를 수정하고 다시 검증했다
우선 운영 환경의 정확한 파라미터 경로와 이름을 확인했다. 개발·스테이징·운영 경로를 혼동하지 않았는지, 값이 String인지 SecureString인지도 함께 확인했다. 이후 세션 기반 인증에 필요한 값으로 수정하고 서비스를 재시작했다.
더 이상 사용하지 않는 Basic Auth 파라미터는 바로 삭제하지 않았다. 코드뿐 아니라 배포 스크립트, systemd 환경 파일, GitHub Actions와 다른 애플리케이션에서 참조하는지 먼저 확인했다. 참조가 없다는 것을 확인한 뒤 제거하거나 비활성화하는 편이 안전하다.
1
2
3
sudo systemctl restart <서비스명>
sudo systemctl status <서비스명>
sudo journalctl -u <서비스명> -n 100 --no-pager
재시작 후 active (running)만 확인하고 끝내지 않았다. 시작 로그에 예외가 없는지 보고, 마지막으로 헬스 체크 API를 호출해 실제 요청에 응답하는지 확인했다.
1
curl --fail --silent http://localhost:<포트>/actuator/health
Parameter Store를 변경할 때는 민감한 값을 로그에 출력하지 않고, 변경 이유와 이력을 남겨야 한다. 애플리케이션 설정 문서에도 필요한 파라미터와 재시작 여부를 함께 기록해야 다음 변경에서 같은 누락을 줄일 수 있다. 비밀값 관리의 기본 원칙은 .env에 넣으면 안전할까? 비밀값 관리와 1Password·Bitwarden에서 정리한 내용과도 이어진다.
배포 성공과 서비스 정상 동작은 서로 다른 단계다
이번 일을 겪고 나니 배포를 하나의 성공 상태로 뭉뚱그리면 안 된다는 점이 분명해졌다.
| 단계 | 확인할 내용 |
|---|---|
| 워크플로 성공 | 정의된 GitHub Actions 작업이 오류 없이 종료됐는가 |
| 빌드 성공 | 실행 가능한 산출물이 만들어졌는가 |
| 파일 전송 성공 | 올바른 파일이 대상 서버에 도착했는가 |
| 프로세스 실행 성공 | systemd가 프로세스를 실행 중인가 |
| 애플리케이션 초기화 성공 | Spring Context와 Bean이 정상 생성됐는가 |
| 외부 시스템 연결 성공 | DB·캐시·외부 인증 API 등에 연결되는가 |
| 실제 요청 처리 성공 | 헬스 체크와 핵심 요청에 정상 응답하는가 |
앞 단계의 성공이 다음 단계의 성공을 보장하지 않는다. 특히 재시작 명령은 프로세스를 시작하라는 요청이 성공했음을 뜻할 뿐, 애플리케이션이 초기화를 마치고 트래픽을 받을 준비가 됐다는 보장은 아니다.
먼저 배포 후 검증부터 추가하기
현재 규모에서 모든 운영 설정을 곧바로 IaC로 옮기거나 완전한 자동 롤백을 만드는 것보다, 배포 직후 상태와 헬스 체크를 확인해 실패를 워크플로에 반영하는 것이 우선이라고 판단했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
sudo systemctl restart <서비스명>
if ! sudo systemctl is-active --quiet <서비스명>; then
sudo journalctl -u <서비스명> -n 100 --no-pager
exit 1
fi
sleep 10
if ! curl --fail --silent http://localhost:<포트>/actuator/health; then
sudo journalctl -u <서비스명> -n 100 --no-pager
exit 1
fi
서비스명, 포트와 헬스 체크 경로는 실제 환경에 맞게 바꿔야 한다. 고정된 sleep만으로 충분하지 않다면 제한 시간 안에서 여러 번 확인하는 방식도 검토할 수 있다. 배포 전에 필수 환경 변수의 존재 여부를 검사하되, 값 자체는 출력하지 않아야 한다.
즉시 적용할 항목은 다음과 같이 정리했다.
- 배포 후
systemctl is-active로 프로세스 상태 확인 - 최근 애플리케이션 로그 확인
- 헬스 체크 API 호출 후 실패하면 워크플로도 실패 처리
- 필수 설정값 존재 여부 검증
- 배포 체크리스트에 Parameter Store 변경 여부 추가
이후에는 Spring Boot Actuator 기반 점검 범위 확장, Parameter Store 버전과 변경 이력 관리, 설정의 IaC 전환, 실패 시 이전 버전 롤백을 검토할 수 있다. 배포 알림에도 단순 완료 여부가 아니라 헬스 체크 결과를 포함하는 편이 유용하다. 다만 한 번에 모두 도입하기보다 먼저 배포 후 헬스 체크와 필수 설정 검증을 안정적으로 적용하는 것이 현실적이다.
오늘 배운 점
GitHub Actions의 초록색 체크는 배포 과정의 끝이 아니라, 정의된 자동화 작업의 성공 표시였다. 서비스가 정상인지 확인하려면 프로세스 상태, 초기화 로그, 외부 연결과 실제 응답까지 별도로 검증해야 한다.
코드가 기대하는 환경 설정도 코드와 같은 릴리스의 일부다. 인증 방식을 바꿀 때는 애플리케이션 구현뿐 아니라 Parameter Store, systemd 환경과 배포 체크리스트까지 함께 살펴봐야 한다.
마지막으로 문제를 해결할 때 최근 오류 한 줄만 보는 대신 장애 시점을 기준으로 로그 범위를 좁혀가니 원인에 더 빠르게 접근할 수 있었다. 다음 배포부터는 “파일이 올라갔는가”에서 멈추지 않고 “서비스가 요청을 처리하는가”까지 확인해야 한다.
핵심 정리
- GitHub Actions 성공은 실제 서비스의 정상 동작을 보장하지 않는다.
- 이번 장애의 원인은 코드와 Parameter Store의 인증 설정이 함께 변경되지 않은 것이었다.
systemctl로 상태를 보고journalctl과grep으로 장애 시점의 원인을 좁혔다.- 배포 후 헬스 체크와 필수 설정 검증을 워크플로의 성공 조건에 포함해야 한다.