컨테이너 로컬 파일에 저장하던 설정을 영속화해야 했고 계획은 간단해 보였다. 다른 데이터가 이미 S3를 쓰고 있으니 그 패턴을 재사용하면 된다는 것이었다.
Table of contents
Open Table of contents
원인 — 실재하지 않던 패턴
재사용하려면 기존 코드를 봐야 해서 put_object와 upload_file 호출을 grep -rn으로 서비스 코드 전체에서 검색했는데 0건이었다. 다시 훑어보니 있는 것은 읽기뿐이었다.
def _fetch_cdn_json(path):
r = requests.get(_CDN_BASE + path, headers={'X-Report-Key': _KEY})
return r.json()
S3를 쓴다고 본 그 데이터도 CloudFront에서 requests.get으로 받아 오기만 하고 있었고 쓰기는 여전히 컨테이너 로컬 파일이었다. 재배포마다 휘발되는 바로 그 자리였다.
검증 — 저장소에 파일을 올린 주체
저장소에 실제 파일이 있는 것은 맞아서 누가 올렸는지를 따로 확인했다. 일배치가 aws s3 cp로 올리는 것과 사람이 콘솔에서 수동으로 올린 것이 섞여 있었다.
서비스 코드가 올린 것이 아니었고 내가 기존 패턴이라고 본 것은 다른 프로세스의 일이었다. 파일의 존재가 그것을 만든 주체를 말해 주지 않는다는 것이 이 확인의 요지다.
조치 — 신규 쓰기 경로의 네 조각
가정이 뒤집히면서 작업 성격이 기존 코드 복사에서 신규 구현으로 바뀌었다. 무엇이 필요한지 정리하니 넷이 나왔다.
boto3의 put_object 호출을 넣어야 하고 requirements.txt에 없으면 그것부터 추가해야 한다. 태스크 실행 역할에 s3:PutObject가 없어서 권한을 붙여야 하고, 버킷 정책이 현재 s3:GetObject만 CloudFront 출처로 허용해서 정책도 바꿔야 한다. CloudFront가 앞에 있으니 올려도 바로 안 보이므로 CreateInvalidation까지 필요했다.
주의 — 정적 검증에 안 걸리는 실패
넷을 정리하고 나니 작업 성격이 코드 변경 한 건에서 코드와 권한과 정책과 캐시 운영으로 한 단계 무거워졌다. 규모가 다르므로 일정도 다시 잡아야 했다.
권한과 정책을 빠뜨리면 배포 후 런타임에서만 실패한다는 점이 특히 걸렸다. 코드 리뷰를 통과하고 로컬에는 권한이 있어서 테스트도 통과하며 배포까지 되고 나서 첫 쓰기에서 권한 오류가 난다. 정적 검증으로 안 잡히는 유형이므로 코드 리뷰만으로는 이 누락을 못 거른다.
두 가지 방법이 있었는데 읽기까지 boto3로 바꿔 한 경로로 통일하는 것과 읽기는 CloudFront 경유 함수를 그대로 두고 쓰기만 새로 짜는 것이다. 뒤쪽을 골랐다. 이유는 읽기 함수가 이미 돌고 있고 이번 일정에서 건드릴 이유가 없어서다. 대신 읽기와 쓰기가 다른 경로를 타는 비대칭을 감수했고 그것을 문서에 나눠 적었다.
남의 선택을 설명한 제약
정리하다 보니 같은 영역에서 다른 사람이 그 개념을 통째로 제거하는 방향으로 간 커밋이 이해됐다. 처음에는 왜 없애는지 의아했는데 이 쓰기 경로 부재로 설명이 됐다.
영속화하려면 넷이 필요하고 그것이 무거우니 기능 자체를 없애는 편이 낫다고 판단했을 수 있다. 남의 선택이 이상해 보이면 내가 모르는 제약이 있는 경우가 많다.
조사 방법으로는 둘을 썼다. 로컬 체크아웃이 아니라 배포 중인 리비전을 git grep put_object <배포 태그>로 직접 검색했고 코드에서 안 보이는 권한과 정책은 IAM 정책 JSON과 교차 확인했다. 코드만 보면 권한과 정책과 캐시가 안 보이는데 그것은 코드에 없기 때문이다.
정리
- 기존 패턴 재사용이라고 가정하기 전에 그 패턴이 실재하는지 검색한다
- 읽기는 있고 쓰기는 아예 없을 수 있다
- 저장소에 파일이 있다고 그 서비스가 올린 것이 아니다
- 신규 쓰기 경로는
put_object와s3:PutObject와 버킷 정책과CreateInvalidation이 같이 간다 - 권한과 정책을 빠뜨리면 배포 후 런타임에서만 실패한다
- 읽기 재사용과 쓰기 신규를 나눠 적는다
- 남의 선택이 이상해 보이면 내가 모르는 제약이 있을 수 있다
- 코드에 없다는 것이 필요 없다는 뜻은 아니다