소규모 AWS 인프라를 세우며 상태 초록불을 믿지 않기로 했다

공모전 시연용으로 EC2 한 대에 Spring Boot 서비스 8개를 올리는 인프라를 처음부터 끝까지 세웠다. 네트워크와 RDS부터 도메인과 HTTPS까지, 사전에 짜둔 계획이 실행 단계에서 거의 그대로 들어맞았다. 그런데 한 곳에서 systemctl은 전부 active인데 실제로는 잘못된 인증서를 물어올 뻔한 상태가 있었다. 그때부터 상태 초록불만 보고 넘어가지 않기로 했다. 이 글은 소규모 AWS 인프라 구축을 순서대로 밟으며, 어디서 무엇을 어떻게 검증했는지에 대한 기록이다

결과부터 적으면 이렇다. EC2 t3.large 한 대 위에서 8개 서비스가 systemd로 구동되고, Eureka에 7개가 등록됐다. 메모리는 7.6GiB 중 4.1GiB가 가용, 스왑은 1MiB 미만이었다. 24시간 상시 가동 시 월 약 $94, 야간 정지 운용 시 약 $55~60이다

순서: 네트워크 → RDS → EC2 → 배포 → HTTPS

인프라는 아래 순서로 세웠다. 앞 단계가 뒤 단계의 전제가 되기 때문에 순서를 지키는 게 중요하다

[보안 그룹]    →  [RDS]    →   [EC2]    →  [서비스 배포]   →  [도메인·HTTPS]
   SG 2개        PG 18      t3.large     8개 서비스         Let's Encrypt

보안 그룹은 EC2용과 RDS용 둘로 나눴다. RDS는 db.t4g.micro에 PostgreSQL 18, 20GB gp3로 올리고 논리 DB 6개를 분리했다. EC2는 t3.large에 Amazon Linux 2023, 부트스트랩은 2~3분 걸렸다. 배포는 스크립트 한 번으로 8개 서비스를 discovery → 나머지 → gateway 순서로 기동했다. 각 서비스는 포트가 실제로 리슨할 때까지 기다린 뒤 다음으로 넘어가게 했다. 마지막으로 도메인 A레코드와 HTTPS를 붙였다

비밀값 입력이 필요한 단계만 사람이 직접 실행했다

이번 작업에서 처음부터 정한 원칙이 하나 있다. 비밀값 입력이 필요 없는 스크립트는 자동화로 직접 실행하고, 대화형으로 비밀번호를 넣어야 하는 스크립트는 사람이 별도 터미널에서 직접 실행한다

자동 실행   : 보안 그룹 생성, EC2 생성, 서비스 배포 (비밀값 없음)
사람이 직접 : RDS 생성(마스터 비밀번호), EC2 셋업(시크릿 기입)

이유는 단순하다. RDS 마스터 비밀번호나 API 키 같은 값이 자동화 세션의 기록에 남으면 안 되기 때문이다. 실제로 이 프로젝트에서 시크릿 확인 명령 하나 때문에 키가 기록에 노출된 사고가 있었다. 이 경계를 처음부터 그어두면 그런 값이 자동화 흐름에 새어들 여지가 줄어든다

비용은 켜두는 시간이 아니라 붙여둔 자원에서 나온다

EC2를 만들고 나서 “이걸 24시간 돌릴 필요는 없다고 생각하고(데모버전이기 때문에) 정지 운용 방법을 정리했는데, 자원마다 규칙이 달라서 그대로 옮긴다

자원정지 가능 여부주의점
EC2자유롭게 정지·재기동EIP는 정지해도 유지돼 재기동 시 IP가 안 바뀐다
RDS정지되지만 최대 7일7일 지나면 AWS가 자동으로 다시 켠다
EIP정지 개념 없음붙여둔 것만으로 시간당 과금 (~월 $3.6)

EC2는 마음대로 껐다 켤 수 있고, EIP 덕분에 재기동해도 IP가 그대로다. 문제는 RDS다. 정지는 되지만 AWS가 최대 7일까지만 정지 상태를 유지하고 이후 자동으로 다시 켠다. 우회할 수 없는 정책이다. 일주일 넘게 안 쓸 거라면 자동으로 켜진 뒤 다시 꺼야 한다는 걸 기억해야 한다

EIP는 결이 다르다. 2024년 2월부터 AWS가 퍼블릭 IPv4 주소 자체에 시간당 과금(약 $0.005/시간, 월 $3.6)을 매긴다. 인스턴스를 꺼도 EIP를 붙여두는 한 이 비용은 그대로 남는다. 즉 비용은 “켜둔 시간”만이 아니라 “붙여둔 자원”에서도 나온다. 야간 정지로 $94를 $55~60까지 줄여도 EIP 몫은 계속 청구된다

상태가 active여도 정상이라는 뜻은 아니었다

이번 작업에서 가장 크게 배운 지점이다. systemctl list-units "seniorhub@*"는 8개 서비스가 전부 active running이라고 보고했다. 여기서 끝냈다면 문제를 놓쳤을 것이다. 상태값 대신 실제 동작을 확인하는 항목을 추가했다

[x] Eureka /eureka/apps     → 7개 서비스 등록 (discovery 제외)
[x] 최근 10분 에러 로그         → 8개 서비스 전부 없음
[x] 메모리 여유                → 7.6GiB 중 4.1GiB 가용, 스왑 1MiB 미만
[x] https://api.example.com → HTTPS 핸드셰이크 정상
[x] 인증서 issuer             → 정식 Let's Encrypt (스테이징 아님)

이 마지막 항목이 실제로 문제를 잡았다. 도메인·HTTPS 단계에서 컨테이너 상태는 멀쩡했지만, 인증서 발급 도구가 반복 실패 끝에 테스트용 스테이징 CA로 넘어가 있었다. openssl s_client로 발급기관을 직접 확인하지 않았다면, 브라우저가 신뢰하지 않는 인증서를 정상으로 착각하고 넘어갔을 것이다.

active는 “프로세스가 죽지 않았다”만 말한다. “서비스가 서로를 찾고, 에러 없이 돌고, 올바른 인증서를 내민다”는 상태값이 아니라 실제 응답으로만 확인된다

참고

AWS 퍼블릭 IPv4 과금 안내, RDS 인스턴스 정지 문서