QNAP QuObjects를 S3 저장소로 안정적으로 운영하는 방법
S3 호환이라는 말의 실제 의미
QNAP QuObjects는 AWS S3 SDK를 사용할 수 있지만, AWS S3와 모든 동작이 완전히 같지는 않다. 특히 브라우저의 사전 요청인 OPTIONS, 서명된 URL의 query string, 성공한 PUT 응답의 CORS 헤더에서 차이가 드러날 수 있다. 따라서 단순히 서버에서 파일 하나를 업로드한 결과만으로 운영 준비가 끝났다고 판단하면 안 된다. 서버 SDK, 브라우저 Direct Upload, reverse proxy까지 하나의 요청 경로로 보고 검증해야 한다.
먼저 애플리케이션이 사용하는 계약을 하나로 고정한다.
- HTTPS endpoint 하나만 사용한다.
- region은 클라이언트와 서버에서 동일하게 지정한다.
- virtual-host 방식 대신 path-style을 사용한다.
- SigV4 서명을 사용하고 TLS 검증을 끄지 않는다.
- 이미지와 데이터베이스 백업은 서로 다른 bucket으로 분리한다.
클라이언트 설정을 명시적으로 고정하기
환경마다 SDK의 자동 추론 결과가 달라지지 않도록 endpoint와 path-style을 설정에 드러내는 편이 안전하다. credential은 파일에 적지 않고 운영 환경의 secret으로 주입한다.
qnap_s3:
service: S3
endpoint: <%= ENV.fetch("S3_ENDPOINT") %>
region: us-east-1
bucket: <%= ENV.fetch("S3_IMAGE_BUCKET") %>
access_key_id: <%= ENV.fetch("S3_ACCESS_KEY_ID") %>
secret_access_key: <%= ENV.fetch("S3_SECRET_ACCESS_KEY") %>
force_path_style: true
request_checksum_calculation: when_required
response_checksum_validation: when_required
endpoint에 내부 IP나 관리 포트를 직접 넣지 않는다. 인증서가 적용된 canonical hostname을 사용해야 브라우저, SDK, 운영 점검 명령이 같은 경로를 통과한다. 백업 도구가 같은 credential을 사용하더라도 별도의 환경변수 이름으로 명시해 권한 경계를 나중에 분리할 수 있게 한다.
CORS와 reverse proxy 경계 이해하기
브라우저 업로드가 실패하면 먼저 실제 presigned URL로 OPTIONS 요청을 재현한다. query가 없는 주소에 OPTIONS를 보내 200을 받은 결과는 충분하지 않다. QuObjects는 SigV4 query가 포함된 OPTIONS를 내부 오류로 처리할 수 있고, PUT으로 객체를 정상 저장하고도 Access-Control-Allow-Origin이나 Access-Control-Expose-Headers를 생략할 수 있다.
bucket CORS는 허용할 운영 origin을 정확히 지정하고 PUT, OPTIONS와 실제 전송 헤더만 허용한다. reverse proxy 보정이 필요하다면 범위를 매우 좁게 유지한다.
- OPTIONS 요청에서만 upstream으로 전달할 서명 query를 제거한다.
- 허용한 origin, PUT method, 이미지 bucket 경로가 모두 일치할 때만 ACAO를 추가한다.
- Direct Upload 완료 확인에 필요한 ETag만 노출한다.
- 다른 origin이나 백업 bucket에는 동일한 헤더를 자동 적용하지 않는다.
- 설정을 바꾸기 전에 원본을 백업하고
nginx -t성공 후 reload한다.
브라우저를 이용한 릴리스 검증
서버의 put_object 성공과 브라우저의 성공은 별개의 검증 항목이다. 실제 서비스 origin에서 다음 흐름을 끝까지 실행한다.
- 로그인한 관리자만 Direct Upload URL을 발급받는다.
- 본문 이미지와 커버 이미지를 각각 업로드한다.
- OPTIONS와 PUT이 모두 성공하고 PUT 응답에서 ETag를 읽을 수 있다.
- 발행된 글에서 WebP variant가 표시된다.
- 허용하지 않은 origin에는 ACAO가 반환되지 않는다.
- 삭제한 글의 원본과 variant가 background job을 통해 제거된다.
- 미첨부 blob과 실패한 job 수가 증가하지 않는다.
개발자 도구의 Network 탭에서 최종 PUT 상태와 응답 헤더를 확인하고, Console의 CORS 오류도 함께 기록한다. 자동화 테스트가 가능하다면 canonical origin으로 접속한 브라우저 시나리오를 릴리스 게이트에 포함한다.
장애 대응과 정리 원칙
업로드 장애가 발생하면 DNS와 인증서, OPTIONS, PUT, 객체 조회 순서로 범위를 좁힌다. 성공한 PUT 뒤 화면만 실패한다면 ETag 노출이나 JavaScript 완료 이벤트를 확인하고, 객체 자체가 없다면 서명 endpoint와 path-style을 먼저 확인한다. 정리 작업은 test prefix를 list-objects-v2로 다시 조회하면서 수행한다. QNAP이 zero-byte directory marker를 남길 수 있으므로 recursive delete 한 번으로 비었다고 가정하지 않는다.
무엇보다 운영 백업 prefix에는 실험용 cleanup 명령을 실행하지 않는다. 이미지 bucket과 백업 bucket을 분리하는 이유는 권한뿐 아니라 잘못된 정리 명령의 피해 범위를 줄이는 데도 있다. 서버 업로드, 실제 브라우저 업로드, 제한된 proxy 보정, 정리 후 재조회까지 통과해야 QNAP S3 경로가 운영 가능한 상태라고 판단할 수 있다.
댓글
아직 댓글이 없습니다.
댓글 남기기