도커 이미지 최적화
저희 팀은 기존에 turborepo + pnpm 환경을 쓰고 있었습니다.
그리고 주요 실행 프로그램들은 apps라는 폴더에 두었고 apps에서 공통으로 사용하는 모듈은 packages에 두었습니다.
README.md
├── apps(주요 프로그램)
│ ├── client
│ ├── hub
│ ├── nginx
│ └── server
├── config
│ ├── cz.js
│ ├── mysql
│ ├── prometheus
│ └── tsconfig.base.json
├── docker-composes
│ ├── cloud-canvas-back.yml
│ ├── cloud-canvas-front-hub.yml
│ ├── cloud-canvas-front.yml
│ ├── cloud-canvas-local-back.yml
│ ├── cloud-canvas-local-client.yml
│ ├── cloud-canvas-local-hub.yml
│ ├── cloud-canvas-local.yml
│ ├── database
│ └── monitoring
├── infra
│ ├── dev
│ ├── init.tf
│ ├── modules
│ ├── prod
│ └── variables.tf
├── package.json
├── packages(apps에서 공통으로 사용하는 모듈)
│ ├── cli
│ ├── cloud-graph
│ ├── ncloud-sdk
│ └── terraform
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── scripts
│ └── verdaccio-publish
└── turbo.json
때문에, 도커 이미지를 만들기 위해서는 이러한 의존성을 잘 관리하는 것이 중요했습니다.
하지만 그룹 프로젝트에서 처음 도커 이미지 경량화를 시도하다가 엄청 낭패를 보았습니다.
3주차 당시에는 packages를 불러와서 빌드하는 경우가 없었습니다.
때문에 그것을 고려하지 않고 apps 내부만 신경써서 도커 이미지를 최적화 했습니다. 때문에 packages 모듈을 사용하고 있지 않았을 때는 해당 시스템이 정상적으로 동작했습니다.
문제는 이후에 packages를 사용했을 때부터 발생하게 돼서 도커 이미지가 만들어지지 않았습니다.
저희 팀은 그때 시간이 없어서 그 문제를 해결하지 못하고 COPY . . 을 활용해 임시 방편으로 도커 이미지를 만들었습니다.
그런 식으로 만들어진 도커 이미지는 2GB에 육박하게 되었고, NCP 컨테이너 레지스트리의 큰 비용 지출이 염려되는 상황이었습니다.
이번 주차에서는 pnpm + turborepo 환경에서 멀티 스테이징 빌드와 turbo prune 기능을 활용하여 도커 이미지를 최적화 할 수 있었습니다.(아직 반영을 못했습니다ㅠㅠ)
결론적으로 2GB → 240MB, 빌드 시간은 5분 → 약 40초로 줄일 수 있게 되었습니다.
테라폼 배포 기능 아키텍처 설계
변환된 테라폼 코드를 사용자가 웹 서비스 상에서 실제 인프라로 배포할 수 있도록, 테라폼 배포 기능을 설계하였습니다.
테라폼 코드가 실제 인프라에 배포되는 시간은 여러 요인에 의해 오랜 시간이 걸릴 수 있으므로, 이러한 작업 상황을 클라이언트가 실시간으로 파악할 수 있어야 했습니다.
그래서 저희는 클라이언트의 현황을 다음과 같이 4가지로 규정하였습니다.
- 대기중: 테라폼 컨테이너가 실행되기 까지 메시지 큐에서 대기 중
- 배포중: 테라폼 배포 서버에서 컨테이너를 실행해서 배포하는 중
- 재시도중(*/3): 배포 도중 실패하여 다시 시도하는 것.
- 배포 실패: 재시도중의 Count가 3번이 될 경우 배포 실패 상태로 표시한다. 또한 배포가 성공했다고 하더라도 tfstate 파일이 성공적으로 저장되지 않았다면, 배포된 리소스를 삭제하고 배포 실패 상태로 표시한다.
또한, 테라폼 코드를 활용해 실제 인프라로 배치할 때 테라폼 컨테이너를 활용하여 배포할 예정이므로, 테라폼 컨테이너의 개수를 서버의 사양에 맞게 한정할 필요가 있었습니다.
이로 인해서 여러 사용자에게 요청이 들어올 경우, 적정 테라폼 컨테이너 개수를 유지하면서 사용자의 요청을 처리할 수 있는 과정이 필요하였습니다. 따라서 저희는 메시지 큐를 도입하여 테라폼 배포 작업을 비동기적으로 진행할 수 있도록 설계하였습니다.
전반적인 워크플로우 그림은 다음과 같습니다.
- 클라이언트가 백엔드 서버에 테라폼 배포 요청을 보낸다. Count 라벨 0
- 백엔드 서버는 클라이언트의 배포 요청을 메시지 큐에 담는다.
- 메시지 큐에 담겨진 요청은 테라폼 배포 서버에 상황에 따라서 빠진다.
- 만약 테라폼 배포 서버에서 Docker API를 통해 조회된 컨테이너의 개수가 일정 개수 이상일 경우 메시지 큐에서 요청을 꺼내지 않는다.
- 그 반대면 요청을 꺼내고 테라폼 배포 서버에서 해당 요청을 기반으로 컨테이너를 실행한다.
- 만약 테라폼 배포 컨테이너에서 배포가 실패되었을 경우, 해당 요청을 Count에 +1을 하고 데드레터 큐로 이동한다.
- 데드레터큐로 이동되면, 다시 메시지 큐로 들어가게 되고, 테라폼 컨테이너 실행을 반복한다. 이때 Count 가 3이 될 경우 요청은 더 이상 데드레터큐에 들어가지 않고 사용자에게 배포 실패를 반환한다.
- 배포가 성공할 경우 사용자는 tfstate 파일과 배포된 인프라의 리소스를 확인할 수 있다.
이렇게 할 경우, 많은 사용자가 배포 요청을 보내도 비동기적으로 원활하게 작업을 처리할 수 있다고 판단했습니다.
프론트
테라폼 변환 도구
- JSON 파서 부분에 Strategy, Factory 패턴 적용
- zod를 사용해 속성 검증