내가 개발 중인 서비스는 kubernetes 위에서 운영 중이며, DB 연결은 개발 시엔 Docker 로 MySQL을 띄워 연결하고 실 서비스는 Google Cloud Platform(이하 GCP)의 Cloud SQL에 연결하여 사용중이다. 이 때 kubernetes 에서 Cloud SQL 에 연결하는 방법은 크게 3가지가 있다.
- CloudSQL에 외부 고정IP를 설정하여 연결
- CloudSQL과 kubernetes 간에 VPC를 연결하여 내부 IP로 연결
- 링크를 이용한 연결
하지만 1번의 경우 GCP의 로드밸런서 대역만 방화벽을 오픈한다 해도 GCP 위에 공격을 위한 서버를 띄우면 공격자의 공격에 당할 수 있다. 2번의 경우엔 다른 Region/Zone에 있는 서버들에서 접속하게 하려면 Shared VPC 를 추가로 설정해줘야 한다. (뒤에서 이야기 하겠지만 현재는 같은 Region/Zone 에서만 접속하기 때문에 2번으로 할껄… 하는 후회 아닌 후회도 있다.) 위의 이유 + GCP문서를 보고 나는 3번 방법으로 kubernetes - Cloud SQL 의 연결을 설정하였다. (솔직히 말하면.. kubernetes에서 CloudSQL에 연결하는 방법에 대한 공식 문서가 있기에 3번으로 진행했다.)
Proxy docker를 이용한 CloudSQL 연결
모든 내용은 링크와 별 다를게 없을 것이라 문서 링크로 대체한다.
연결, 그 이후
설정을 완료한 이후 그동안 발생하지 않았던 문제가 표면으로 발생하였다. Rolling update 도중 들어오는 API request들에 대해 이상한 response를 주고 있는 것이다. 이 문제가 왜 일어나는지에 대해 확인해본 결과 아래의 결과로 원인을 분석할 수 있었다.
- Rolling update 시 기존의 pod 은 Terminating 단계에 들어간다.
- Terminating 단계에 들어간 pod 에도 여전히 API request가 들어간다.
- Terminating 단계에 들어가자마자 내가 Docker로 띄운 API 서버는
terminationGracePeriodSeconds
옵션에 의해 Terminating 상태가 끝날 때 까지 살아있다. - 하지만 proxy 용도로 띄운 도커 이미지는 Terminating 단계에 들어가자 마자 종료된다.
- 이로 인해 Terminating 내의 API 서버에서 수행하는 DB query가 실패한다.
- 결론적으로 Client는 Unexpected error를 만나게 된다.
이 사건을 통해 이미 기존에도 Terminating 단계에 있는 pod 에 API request가 들어가고 있었고, request에 대한 response가 반환되지 않는 버그가 있었음을 발견했다. 헌데 이제서야 이 문제를 발견할 수 있었던 건 response 를 미처 주지 못한것에 대한 로그는 morgan 로그를 통해 확인할 수 없었고 찰나의 순간이기 때문에 알지 못했던 것이다. DB 커넥션이 끊겨 API request에 대한 response 가 제대로 오지 않은 이 상황이 되어서야 문제를 발견하게 되었다.
이 문제를 해결하기 위해 링크를 참조하여 proxy docker 설정에 아래 내용을 추가하여 API 서버가 죽기 전에 DB 커넥션이 끊기는 문제를 수정하였다.
lifecycle:
preStop:
exec:
command:
- sleep
- "40"
또한 CronTask 류의 작업을 중지시키기 위해 Redis Pub/Sub 을 이용하여 배포 전 롤링업데이트 대상의 서버들의 CronTask 동작을 멈추게 하는 기능을 추가하였다.