넘겨받은 커머스에는 시험할 곳이 없었다. 그래서 테스트 서버를 새로 세우기로 했고, 이때 가장 먼저 정한 것은 「언제 열 것인가」였다. 테스트 서버가 실수로 운영 결제를 부르거나 운영 고객에게 문자를 보내면, 시험 환경이 곧 사고의 원천이 된다. 그래서 운영에 닿을 수 없다는 것을 장치마다 증명한 뒤에만 열기로 했다.
Table of contents
Open Table of contents
배경 — 시험할 곳이 없던 시스템
dev 프로젝트를 실측한 결과 상시로 도는 컴퓨트는 0 이었고, dev API 주소 뒤에는 서버가 없었다. 「쓸 때만 띄우는 구조」로 바꿀 것도 없었지만, 거꾸로 말하면 운영과 같은 코드를 운영 밖에서 돌려 볼 곳이 하나도 없다는 뜻이었다. 결제·문자·앱 푸시가 모두 실제 고객에게 닿는 시스템에서, 시험할 곳이 없으면 모든 수정이 운영에서 처음 검증된다.
판단 — 시험할 곳을 새로 세우는 방식
결정은 dev 프로젝트에 별도 GKE 클러스터를 두는 쪽이었다. 운영 클러스터에 네임스페이스만 나눠 두는 방식과 견주면 그쪽이 비용은 적다. 반면 시험 파드가 운영 서비스 계정·네트워크·설정을 공유하게 된다. 별도 클러스터가 맞다고 본 이유는 테스트 서버의 첫 요건이 「운영을 못 부른다」였기 때문이다. 대가는 클러스터와 Cloud NAT 의 월 비용이 새로 생긴다는 점이다.
구성은 다음과 같다.
- 클러스터는 GKE Standard zonal 하나이고, 외부 IP 가 없는 사설 노드
e2-standard-22대로 둔다 - 워크로드는 Workload Identity 로 권한을 받는다. 서비스 계정 키 파일을 새로 만들지 않는다
- 출구는 Cloud NAT 와 고정 IP 하나로 묶는다. dev 에서 나가는 모든 요청의 출발지가 하나가 된다
- 입구는
LoadBalancer서비스 하나에loadBalancerSourceRanges를 Cloudflare IP 대역으로 건다. 오리진 IP 로 Cloudflare 를 건너뛰지 못하게 하기 위해서다 - 네임스페이스 이름을 운영과 다르게(
-dev) 두고,kubectl은 현재 context 를 바꾸지 않은 채 언제나--context로 dev 클러스터를 명시한다
API 이미지는 운영 이미지를 복사하는 대신 dev 용으로 따로 빌드했다. 운영 이미지에는 「테스트 서버면 결제 승인 앞에서 멈춘다」 같은 관문이 없기 때문이다.
검증 — 운영 차단 장치 8항
테스트 서버를 여는 조건은 차단 장치 8항이 대조군까지 통과하는 것이었다.
| # | 장치 | 결과 |
|---|---|---|
| ① | dev 서비스 계정 16개가 운영 프로젝트 IAM 에 0줄 | 통과 |
| ② | 운영 projectId 로 띄운 파드가 기동 단계에서 exit 1 로 거부 | 통과 |
| ③ | dev 파드에서 운영 주소 6곳이 모두 403, 결제 VM 방화벽도 dev 출구 IP 거부 | 통과 |
| ④ | dev 문자 발송 1건이 「발송 안 함」, 운영 발송 기록은 전후 같음 | 통과 |
| ⑤ | 결제·선물 주문·Slack 이 관문에서 TEST_SERVER_BLOCKED | 통과 |
| ⑥ | 허용 밖 출구에서 dev 호스트 403, 오리진 LB 직접 접속은 시간초과 | 통과 |
| ⑦ | 운영 Realtime Database 호스트 차단 | 통과(단서 있음) |
| ⑧ | 운영 클러스터 네임스페이스 15개에 -dev 0, configmap 의 projectId 모두 운영 값 | 통과 |
대조군은 항마다 붙였다. 사무실에서 같은 운영 주소를 부르면 열리는지, 운영 projectId 가 아니면 파드가 Ready 가 되는지, 운영 발송 기록에 정상 발송 줄이 보이는지를 함께 봤다. 마지막 배포 뒤에는 운영 백오피스 파드 네 개의 시작 시각과 재시작 0, 이미지 digest 가 그대로인 것을 확인했고, dev 출구 IP 가 운영 존에 낸 요청이 0 인 것도 쟀다. 같은 조회가 배포 전 구간의 시험 요청 1건은 잡아냈다.
영향 범위 — 통과 표 옆의 단서
통과 표만 남기면 무엇을 못 쟀는지가 사라지므로 단서도 그대로 적었다. ⑦은 dev 에만 있는 주문 고객 11명은 운영에 0 이지만, 나머지 32명은 예전 사본이라 운영에도 같은 uid 가 있어 이 방법으로는 가를 수 없다. Firestore 는 호스트가 공용이라 관문이 막지 않고, 막는 근거는 ①의 IAM 이다. 그리고 iOS 의 dev 빌드에는 Debug 에도 운영 결제창 주소가 고정돼 있어, 그 자리를 나누기 전까지 iOS dev 빌드로 결제 시험을 하지 않는다.
결과 — 개방 조건이 지킨 운영 경계
- 개방 조건. 개방은 차단 장치 8항이 대조군까지 통과한 뒤에만 한다는 관문으로 정했고, 8/8 통과를 확인한 뒤 정식으로 열었다
- 운영 영향 0의 증거. 마지막 배포 뒤 운영 백오피스 파드의 재시작 0·이미지 digest 불변과, dev 출구 IP 가 운영에 낸 요청 0 을 함께 남겼다
- 비용과 남은 위험. 별도 클러스터와 Cloud NAT 의 월 비용이 새로 생겼다. 예전 사본이라 가를 수 없는 고객 32명과 iOS dev 빌드의 결제창 주소는 단서로 남았다
정리
- 테스트 서버의 첫 요건은 운영을 못 부르는 것이다. 출구를 IP 하나로 묶고 운영 쪽에서 그 IP 를 막는다
- 운영 이미지를 복사하지 않는다. 테스트 서버의 관문은 dev 빌드에만 있다
- 차단 장치는 대조군과 함께 통과시키고, 단서는 통과 표 옆에 그대로 남긴다