현재 진행 중인 baro-farm 프로젝트에서는 MySQL, Redis, Kafka, Elasticsearch를 k3s 기반 Kubernetes MSA 환경에 배포해야 했다. 기존에는 AWS의 관리형 서비스(RDS, ElastiCache 등)를 중심으로 인프라를 구성해 왔지만, Kubernetes 환경에서는 같은 인프라도 여러 방식으로 배포하고 운영할 수 있다는 점을 새롭게 알게 되었다. 이번 글에서는 baro-farm 프로젝트에서 각 인프라(MySQL, Redis, Elasticsearch, Kafka)를 어떤 방식으로 배포했는지, 그리고 왜 그런 선택을 했는지 정리해보려고 한다.
AWS RDS(Amazon Relational Database Service)는 관계형 데이터베이스를 손쉽게 생성하고 운영할 수 있도록 지원하는 관리형 Database as a Service이다. DB 설치, 버전 관리, 패치, 백업 등의 운영 작업을 AWS가 대신 처리해주기 때문에 개발자는 인프라 관리보다 애플리케이션 개발에 집중할 수 있다. RDS는 다음과 같은 기능을 제공한다.
Kubernetes 환경에서는 애플리케이션 Deployment에서 DB Endpoint를 Secret이나 환경 변수로 주입하여 외부 RDS와 쉽게 연결할 수 있다.
❌ 선택하지 않은 이유 : 이번 프로젝트에서는 Multi-AZ나 자동 백업 같은 기능이 필수는 아니었고, 프로젝트 규모 대비 RDS 비용이 부담스럽다고 판단했다.
Operator는 Kubernetes에서 특정 애플리케이션의 운영 지식을 코드로 구현한 Custom Controller이다. 즉 사람이 수행하던 운영 작업을 Kubernetes Controller가 자동으로 수행하도록 만든 구조다. MySQL Operator는 MySQL InnoDB Cluster를 Kubernetes 환경에서 선언적으로 관리할 수 있도록 지원한다. Helm을 통해 Operator를 설치하면 Operator Deployment, CRD(Custom Resource Definition) 리소스가 생성된다. 이후 사용자는 Custom Resource만 정의하면 MySQL 클러스터가 자동으로 생성된다.
apiVersion: mysql.oracle.com/v2
kind: InnoDBCluster
metadata:
name: mysql-cluster
spec:
instances: 3
router:
instances: 1
Operator는 다음 기능을 제공한다.
즉 Kubernetes 환경에서도 Managed DB에 가까운 운영 경험을 제공한다.
❌ 선택하지 않은 이유 : 이번 프로젝트에서는 DB 이중화나 자동 백업이 필수 요구사항이 아니었고, Operator Pod 및 추가 리소스를 운영하는 부담이 있다고 판단했다.
가장 기본적인 방법은 Kubernetes 리소스를 직접 YAML로 작성하는 방식이다. MySQL을 Kubernetes에 배포하려면 일반적으로 다음 리소스가 필요하다.
왜 StatefulSet과 PV/PVC가 필요한가?
DB는 Stateless 애플리케이션과 달리 상태(State)를 가지는 서비스이다. Pod가 재시작되거나 다른 노드로 이동하더라도 데이터는 유지되어야 한다. 이를 위해 Kubernetes에서는 다음 구조를 사용한다.
StatefulSet
PersistentVolume / PersistentVolumeClaim
즉 Kubernetes에서 DB를 운영할 때는 일반적으로 StatefulSet + Persistent Storage 구조가 사용된다.
❌ 선택하지 않은 이유 : 모든 리소스를 직접 관리해야 하기 때문에 운영 부담이 크고 유지보수성이 떨어진다고 판단했다.
Helm은 Kubernetes 리소스를 패키지 형태로 관리할 수 있는 패키지 매니저이다. 일반적으로 Kubernetes에서 애플리케이션을 배포하려면 StatefulSet, Service, PVC, ConfigMap 등 여러 리소스를 직접 정의해야 한다. 하지만 Helm을 사용하면 이러한 리소스를 Chart 형태로 템플릿화할 수 있고, values.yaml을 통해 설정을 쉽게 관리할 수 있다. 이번 프로젝트에서는 Bitnami MySQL Helm Chart를 사용해 MySQL을 배포했다.
Chart.yaml
apiVersion: v2
name: baro-mysql
description: MySQL deployment for baro-farm using the Bitnami MySQL chart as a dependency.
type: application
version: 0.1.0
dependencies:
- name: mysql
version: 14.0.3
repository: https://charts.bitnami.com/bitnami
values-mysql.yaml
global:
imageRegistry: public.ecr.aws
security:
allowInsecureImages: true
mysql:
auth:
existingSecret: mysql-credentials
database: "baro_db"
username: "baro_user"
architecture: standalone
primary:
persistence:
enabled: true
storageClass: gp3-ebs
size: 4Gi
resources:
requests:
cpu: "200m"
memory: "512Mi"
limits:
cpu: "500m"
memory: "1Gi"
service:
type: NodePort
nodePorts:
mysql: 32000
image:
repository: bitnami/mysql
tag: 9.4.0-debian-12-r1
✅ 선택한 이유 : Helm Chart를 사용하면 검증된 템플릿을 기반으로 리소스를 빠르게 배포할 수 있다. 또한 values.yaml을 통해 스토리지, 리소스, 인증 정보, 이미지 태그 등의 설정을 선언적으로 관리할 수 있어 운영과 유지보수가 훨씬 수월해진다.
MySQL 데이터를 어디에 저장할 것인지도 중요한 설계 포인트였다. AWS 기반의 k3s on EC2 환경에서 고려한 스토리지 옵션은 다음과 같았다.
1. EBS(Elastic Block Store) : EBS는 EC2 인스턴스에 연결하는 블록 스토리지 서비스이다.

✅ 선택한 이유 : EC2 인스턴스가 종료되더라도 EBS 볼륨 자체는 유지되기 때문에 데이터 영속성을 보장할 수 있다.
2. EFS(Elastic File System) : EFS는 여러 인스턴스에서 동시에 접근 가능한 NFS 기반 파일 스토리지이다.

❌ 선택하지 않은 이유 : 현재 프로젝트 규모에서 별도 스토리지 레이어(EFS/NFS)를 운영하는 것은 오버엔지니어링이라고 판단했다.
3. 로컬 스토리지 : 로컬 스토리지는 특정 노드에 직접 연결된 디스크라고 볼 수 있다. 예를 들어 k3s에서 기본 제공되는 local-path StorageClass가 여기에 해당한다.
❌ 선택하지 않은 이유 : MySQL과 같은 영구 데이터 저장에는 적합하지 않다고 판단했다.
Redis 역시 MySQL과 마찬가지로 Kubernetes 환경에서 AWS ElastiCache, Redis Operator, 직접 YAML 작성, Helm Chart 사용 등 다양한 방식으로 배포할 수 있다. 이번 프로젝트에서는 MySQL을 Helm Chart로 배포했을 때와 같은 이유로, Redis도 Helm Chart를 활용해 배포했다.
Redis는 이번 프로젝트에서 Refresh Token 저장소와 캐시 용도로만 사용되었기 때문에, 데이터 영속성이 반드시 필요한 상황은 아니었다. 따라서 별도의 PV/PVC를 구성해 영구 스토리지를 붙이지 않고, Stateless한 방식으로 운영했다.
Elasticsearch 역시 MySQL과 마찬가지로 Kubernetes 환경에서 AWS OpenSearch Service, Elasticsearch Operator, 직접 YAML 작성, Helm Chart 사용 등 다양한 방식으로 배포할 수 있다. 이번 프로젝트에서는 앞서 MySQL과 Redis를 Helm Chart로 배포했을 때와 같은 맥락으로, Elasticsearch도 Helm Chart를 활용해 배포했다.
다만 Elasticsearch는 상품 검색, 추천, 사용자 행동 로그 분석을 위한 검색 인덱스 데이터를 저장하는 핵심 컴포넌트이기 때문에, 장애가 발생하더라도 인덱스를 가능한 보존할 수 있어야 했다. 따라서 MySQL과 동일하게 EBS 기반 StorageClass를 사용해 PV/PVC를 구성하고, Elasticsearch 데이터를 EBS(gp3)에 영구적으로 저장하도록 설계했다.
Kafka 역시 MySQL과 마찬가지로 Kubernetes 환경에서 AWS MSK(Managed Streaming for Apache Kafka), Kafka Operator(Strimzi), 직접 YAML 작성, Helm Chart 사용 등 다양한 방식으로 배포할 수 있다. 이번 프로젝트에서는 이 중에서 Strimzi Kafka Operator를 사용해 Kafka 클러스터를 구성했다.
Kafka는 MySQL처럼 단순히 StatefulSet만 구성한다고 해서 끝나는 서비스가 아니다. Broker 관리, Topic 생성 및 설정, User/ACL 관리, KRaft 설정 등 운영 과정에서 고려해야 할 요소가 많다. 따라서 Operator를 위한 Pod 리소스가 추가로 필요하더라도, Helm 기반의 단순 배포보다는 Operator를 통해 Kafka 리소스를 선언적으로 관리하는 방식이 더 적합하다고 판단했다. 스토리지 구성은 MySQL과 동일하게 EBS 기반 StorageClass를 사용해 PV/PVC를 구성했으며, Kafka 로그 데이터를 EBS(gp3)에 영구적으로 저장하도록 설계했다.
| 인프라 | 고려한 옵션 | 최종 선택 | 스토리지 전략 |
|---|---|---|---|
| MySQL | AWS RDS, MySQL Operator, 직접 YAML 작성, Helm Chart | Helm Chart | EBS(gp3) 기반 PV/PVC |
| Redis | AWS ElastiCache, Redis Operator, 직접 YAML 작성, Helm Chart | Helm Chart | 영속 스토리지 미사용 |
| Elasticsearch | AWS OpenSearch, Elasticsearch Operator, 직접 YAML 작성, Helm Chart | Helm Chart | EBS(gp3) 기반 PV/PVC |
| Kafka | AWS MSK, Strimzi Operator, 직접 YAML 작성, Helm Chart | Strimzi Kafka Operator | EBS(gp3) 기반 PV/PVC |