시작하며
안녕하세요! 카카오페이증권 DevOps팀의 오픈소스 플랫폼 파트 리나&듀크입니다. 오픈소스 플랫폼 파트는 카카오페이증권에서 사용하는 다양한 오픈소스들로 플랫폼을 구성해서 서비스 개발팀에 제공 및 운영하는 조직입니다.
많은 기업에서 Kubernetes 기반의 컨테이너 환경을 활용하고 계실 텐데요. 그 과정에서 Redis를 Kubernetes 환경에 구성하는 것에 대한 고려와 고민이 많을 것으로 생각됩니다. Redis는 이미 많은 기업에서 사용되고 있고 Bare Metal 환경에서의 레퍼런스와 운영 노하우가 충분히 검증된 플랫폼인데요. 하지만 Redis는 안정성이 매우 중요한 플랫폼이고 컨테이너 환경에서는 레퍼런스가 많지 않아 실제 프로덕션 환경에 운영하는 데에 부담이 될 수 있습니다. 그럼에도 불구하고 컨테이너 환경의 많은 장점 때문에 카카오페이증권에서는 Kubernetes 기반에서 Redis Cluster와 Redis Sentinel 방식으로 구성하게 되었고 현재 프로덕션 환경에서 1년여 넘게 안정적으로 운영하고 있습니다.
이번 글에서는 Redis를 Kubernetes 환경에서 구성한 이야기에 대해 소개하고자 합니다.
Kubernetes 환경에 Redis를 구성하게 된 이유
Public Cloud만 운영한다면 여러 측면에서 Amazon ElastiCache와 같은 Managed Service가 여러 측면에서 좋은 선택입니다. 하지만 카카오페이증권은 Public Cloud(AWS)와 IDC를 모두 활용하는 하이브리드 아키텍처로 구성되어 있어 플랫폼 운영에 대한 효율화를 위해서 통일된 아키텍처로 운영하고자 하였습니다. Kubernetes 환경에 Redis를 구성하게 된 주요 사유들은 아래와 같습니다.
- 신속성 - 빠른 스케일 아웃
- 효율성 - 효율적인 리소스 활용
- 편의성 - Self Healing, 선언형 관리
- 확장성 - Redis as a Service
단시간 내에 대용량 트래픽에 대응하려면 빠른 스케일아웃이 필요합니다. IDC 환경에서 물리 장비로 대응하려면 사전에 여분의 물리 장비를 구성하여 버퍼를 잡아 두어야만 하는데요. Kubernetes 환경에는 리소스의 격리, 제한을 통해 해당 버퍼를 최소화하고 보다 효율적인 리소스 활용이 가능합니다. 그 과정에서 선언적 상태/설정 관리 등으로 더 빠르게 스케일 인/아웃이 가능하며 업그레이드, 모니터링, Self Healing 등 운영 측면에서도 많은 부분이 자동화되어 관리가 가능합니다. 또한 오픈소스 플랫폼 파트에서는 셀프서비스가 가능한 자동화된 Redis 플랫폼을 서비스 개발팀에 제공하기 위해 준비 중인데요. 서비스 개발팀이 필요할 때 스스로 적절한 타입(Redis Cluster/Redis Sentinel)과 스펙을 선택하여 Redis를 구성하고 사용할 수 있도록 하기 위해 위와 같은 장점들을 바탕으로 빠르고 간편한 구성이 가능한 Kubernetes 환경이 적합하다고 판단했습니다.
Kubernetes 환경의 Redis를 위한 목표 설정
성공적으로 Redis를 Kubernetes 환경에 구성하기 위한 몇 가지 목표들을 설정했습니다.
No. | 목표 | 상세 내역 |
---|---|---|
1 | 안정성 보장 | Amazon ElastiCache 나 Bare Metal Redis와 동일하게 안정적인 서비스 환경을 제공해야 한다. |
2 | 성능 보장 | Amazon ElastiCache 나 Bare Metal Redis 대비 동등하거나 그 이상의 성능이 보장되어야 한다. |
3 | 자동화 | Redis as a Service를 위한 자동화 방안이 포함되어야 한다. |
4 | 리소스 효율화 | Amazon ElastiCache 나 Bare Metal Redis 대비 리소스를 효율적으로 사용할 수 있어야 한다. |
5 | 운영 편의성 | Amazon ElastiCache 나 Bare Metal Redis 대비 운영이 편리해야 한다. |
6 | 다양성 | 서비스 성격에 맞는 용량과 성능에 맞춰 다양한 타입을 제공해야 한다. |
주요 고민과 해결 방안
HostNetwork
HostNetwork는 Pod이 호스트의 네트워크 인터페이스를 공유할 수 있도록 허용하는 Kubernetes의 네트워크 설정입니다. HostNetwork 설정을 통해 Pod가 Kubernetes 내부 IP가 아닌 자신이 배포된 호스트의 IP를 가질 수 있는데요. 일반적으로 Kubernetes 환경에서 HostNetwork는 권장되는 옵션은 아닙니다. 하지만 Kubernetes에서 Redis를 안정적으로 운영하기 위한 복잡한 네트워크 설정에 대한 위험도를 낮추고 Kubernetes 가 제공하는 여러 가지 장점을 취하기에 적절한 설정이라 판단되어 적용했습니다. HostNetwork 설정을 통해 Redis에서 얻게 된 이점들은 다음과 같은데요.
네트워크 성능 향상
Redis Cluster는 여러 Redis가 Fully Connected Mesh Topology1 형태로 구성되어 있기 때문에 Redis Pod 간의 통신을 위한 네트워크 성능이 매우 중요한데요. HostNetwork를 사용하면 Pod 간의 통신 시 Pod IP 주소를 직접 사용하기 때문에 네트워크 오버헤드를 줄이고 Redis Cluster 간의 통신 속도를 크게 향상할 수 있습니다.
Pod 간 직접 통신을 통한 단순화
HostNetwork는 Pod 간 직접 연결을 허용하여 네트워크 레이어에서 격리되지 않기 때문에 Redis Cluster 구성 시 Redis Pod 간에 직접 연결되어 Redis Cluster 구성 및 관리를 간편하게 할 수 있습니다.
Redis Cluster에서는 데이터가 여러 노드에 Sharding 되어 분산 저장되어 있습니다. 클라이언트에서는 특정 데이터를 요청할 때 요청된 데이터가 저장된 클러스터 구성 정보(Topology)를 리디렉션 응답으로 클라이언트에게 리턴하여 클라이언트가 원하는 데이터가 저장된 노드로 다시 연결할 수 있도록 처리합니다. 이러한 Redirect Connection 기능은 Kubernetes 클러스터 내에 Pod(Application) to Pod(Redis) 통신에는 이슈가 없지만 Kubernetes 클러스터 외부에서 요청 시 반환된 Pod IP(내부)로는 외부에서 접근이 불가능합니다. 이러한 상황에서 HostNetwork는 Node IP와 동일한 Pod IP를 반환하기 때문에 정상적으로 외부에서의 접근이 가능해집니다.
내/외부 네트워크 엔드포인트
Kubernetes 내부뿐 아니라 외부에 배포된 애플리케이션에서도 Kubernetes에 구성된 Redis를 사용하는 경우가 있는데요. 외부에서 Kubernetes에 구성된 Redis에 접속할 때도 내부와 마찬가지로 고가용성(HA), 부하분산, 확장성을 유지하기 위해 Redis의 네트워크 엔드포인트를 HAProxy와 Headless Service로 구성하였습니다.
High Availability, 부하분산을 위한 엔드포인트 - HAProxy
Redis Sentinel의 경우에는 앞단에 HAProxy를 두어 Kubernetes 외부에서 접근이 가능하도록 구성했는데요. HAProxy는 LB 기능을 제공하는 오픈소스 솔루션으로 가상 IP를 통해 여러 서버의 부하를 분산하는 기능을 제공하며 기본적으로 Reverse Proxy 방식으로 동작합니다.
클라이언트는 StandAlone 방식으로 HAProxy를 통해 Redis에 접속하며 HAProxy 가 Redis Sentinel의 역할을 대신하여 Redis Master와 Replica를 구분하고 TCP HealthCheck, Failover 등의 기능을 수행합니다. 클라이언트에서 HAProxy를 통해 Redis에 접근하는 방법을 간략하게 설명드리면 다음과 같은데요.
- 클라이언트가 HAProxy를 통해 Redis에 접속을 시도합니다.
- HAProxy는 Health Check를 통해 정상 상태의 Redis를 확인합니다.
- 설정된 정보를 바탕으로 Master와 Replica를 구분하여 Redis 서버로 트래픽을 전달합니다.
커스텀 설정을 추가하여 Sentinel의 역할을 위임하도록 구성하였는데요. HAProxy를 사용할 경우 다음과 같은 장점들이 있습니다.
고가용성 및 부하 분산
HAProxy를 사용하면 Redis Pod에 트래픽을 분산하여 단일 Pod의 과부하를 방지하고 시스템 전체의 안정성을 향상할 수 있습니다. 또한 Redis Pod 장애 발생 시 자동으로 트래픽을 다른 Pod로 전환하여 서비스 중단 없이 장애를 복구할 수 있도록 하는데요. Redis의 Master / Replica 확장 시 별도의 설정 작업이 필요 없기 때문에 Redis의 확장성 또한 보장해 줍니다. 서비스 트래픽의 증가로 인하여 새로운 Redis를 추가할 경우에도 자동으로 새로운 Redis Pod를 트래픽 분산에 포함해 쉽게 Redis를 확장할 수 있습니다.
보안 강화
HAProxy에서는 보안을 위해 IP 주소 제한, HTTP 기본 인증, SSL/TLS 암호화 등을 사용하여 악의적인 접근을 차단할 수 있었는데요. Redis와 연동 시에도 설정에서 인증 기반의 접근 제어를 통해 접속하기 때문에 Redis의 데이터를 보호할 수 있습니다.
tcp-check send AUTH Password // Redis에 접근하기 위한 인증정보
모니터링 및 통합
다양한 모니터링 도구와 통합되어 HAProxy와 Redis의 상태를 실시간으로 확인할 수 있는데요. Prometheus, Grafana와 통합되어 네트워크 성능, 트래픽 패턴, Redis 상태 등의 상세한 분석도 가능합니다.
간편한 운영 관리
HAProxy는 아래와 같이 설정 파일을 통해 Redis와의 연동이 간단하며 Helm Chart를 통해 손쉽게 배포할 수 있습니다.
backend bk_Redis_Master // Redis Master에 대한 설정
option tcplog
log stdout format raw daemon info // 로그 설정
option tcp-check
tcp-check send AUTH Password // Redis에 접근하기 위한 인증정보
tcp-check expect string +OK // 인증 OK 확인
tcp-check send PING // PING 기반 헬스체크
tcp-check expect string +PONG // PING에 대한 PONG 응답
tcp-check send info replication // info replication 명령어로 Redis 정보 확인
tcp-check expect string role:Master // Role이 Master인지 확인
tcp-check expect string repl_backlog_active:1 // repl_backlog_active로 Master를 확인
tcp-check send QUIT // QUIT 전송
tcp-check expect string +OK // QUIT 응답 확인
server-template Redis 3 redis-test.domain:6379 check inter 1s resolvers Kubernetes init-addr none // 1초 단위로 해당 주소로 헬스 체크
확장성을 위한 엔드포인트 - Headless Service
Redis Cluster는 클라이언트가 Redis Pod의 IP 주소로 직접 통신하게 되는데요. Kubernetes에서 일반 Service를 이용하게 되면 로드밸런싱으로 인해 원하는 Redis Pod로 바로 접근이 안 될 수 있습니다. 이러할 경우 Headless Service를 활용하면 됩니다. Headless Service는 일반 Service와 달리 Cluster IP 가 존재하지 않으며 클러스터 내 각 Pod에 고유한 DNS 레코드를 할당하지 않고 Pod의 IP 주소만 반환합니다. 이를 통해 클러스터 내부에서 Pod 간 직접 통신이 가능하기 때문에 Redis를 비롯하여 Cluster나 Master/Replica 기능을 제공하는 Stateful 한 서비스에서 주로 활용됩니다.
클러스터 간 연결 단순화
Headless Service를 사용하면 클라이언트는 Pod IP 주소를 알지 않고도 Headless Service 주소를 통해 Redis Cluster에 연결이 가능합니다.
~ kubectl get svc -n redis-cluster
NAME TYPE CLUSTER-IP
redis-cluster NodePort 10.250.229.29
redis-cluster-headless ClusterIP None
Headless Service는 일반 서비스와 같이 Cluster IP를 갖지 않고 대신 DNS를 갖고 있는데요. 실제로 Headless Service의 DNS를 Lookup 하게 되면 Redis Cluster의 모든 Pod IP가 조회되는 것을 확인할 수 있습니다.
~ nslookup redis-cluster-headless
Server: 169.254.25.10
Address: 169.254.25.10:53
Name: redis-cluster-headless.redis-cluster.svc.cluster.local
Address: 10.170.10.1
Name: redis-cluster-headless.redis-cluster.svc.cluster.local
Address: 10.170.10.2
Name: redis-cluster-headless.redis-cluster.svc.cluster.local
Address: 10.170.10.3
Name: redis-cluster-headless.redis-cluster.svc.cluster.local
Address: 10.170.10.4
Name: redis-cluster-headless.redis-cluster.svc.cluster.local
Address: 10.170.10.5
특정 Pod로 트래픽 연결
Headless Service를 이용하면 Redis Cluster 내의 여러 Redis Pod 중에서 특정 Redis Pod로 트래픽을 전달할 수 있는데요. 일반 Service의 경우에는 클라이언트의 요청을 Service 가 받아서 로드밸런싱 알고리즘에 의해 알맞은 Pod로 트래픽을 전달합니다. Headless Service는 클라이언트의 요청을 받아 전체 Pod의 정보를 리턴해주기 때문에 클라이언트는 원하는 Redis Pod에 접속할 수 있습니다.
- 클라이언트가 Headless Service 통해 Redis에 연결 요청을 합니다.
- 요청을 받은 Headless Service는 클라이언트에게 Redis Pod의 전체 리스트 정보를 전달합니다.
- 클라이언트는 해당 정보를 확인하여 원하는 Redis Pod에 접속합니다.
확장성
Redis Cluster를 확장하거나 축소할 때 Headless Service를 사용하면 새로운 Redis 인스턴스가 추가되거나 제거될 때 DNS 레코드가 자동으로 업데이트되어 클라이언트가 새로운 인스턴스에 연결할 수 있습니다.
apiVersion: v1
kind: Service
metadata:
...
spec:
clusterIP: None
clusterIPs:
- None
ports:
- name: tcp-redis
port: 6379
protocol: TCP
targetPort: tcp-redis
- name: tcp-redis-bus
port: 16379
protocol: TCP
targetPort: tcp-redis-bus
publishNotReadyAddresses: true
sessionAffinity: None
type: ClusterIP
클러스터 IP를 None으로 설정하여 DNS 레코드가 생성되지 않도록 하며, Target Port에 연결되어 있는 전체 Pod의 IP 정보를 리턴합니다. publishNotReadyAddresses 설정을 사용하여 준비되지 않은 Pod는 서비스에 포함시키지 않아 애플리케이션을 배포하고 업데이트하는 동안에도 서비스의 가용성을 유지할 수 있도록 합니다. 클라이언트의 요청이 상황에 따라 리턴되는 Pod으로 전달해야 하므로 sessionAffinity는 None으로 설정했습니다. Headless Service는 하위에 Endpoint 오브젝트를 생성하여 아래와 같이 Redis Pod의 IP 정보를 관리합니다.
endpoints:
- addresses:
- 10.xxx.xxx.xxx
conditions:
ready: true
serving: true
terminating: false
hostname: redis-cluster-01
nodeName: ip-10-xxx-xxx-xxx.ap-northeast-2.compute.internal
targetRef:
kind: Pod
name: redis-cluster-01
namespace: redis-cluster
uid: 7c5dde08-5c75-4223
zone: ap-northeast-2a
- addresses:
- 10.xxx.xxx.xxx
conditions:
ready: true
serving: true
terminating: false
hostname: redis-cluster-02
nodeName: ip-10-xxx-xxx-xxx.ap-northeast-2.compute.internal
targetRef:
kind: Pod
name: redis-cluster-02
namespace: redis-cluster
uid: 7bb27e8d-3b40-468e-aa8b
zone: ap-northeast-2c
Kubernetes 클러스터 업데이트 시 안정적인 Redis Migration 시나리오 확보
Kubernetes 클러스터의 버전 업그레이드는 Kubernetes를 운영하면서 피할 수 없는 작업이죠. 특히 관리형 Kubernetes 서비스인 EKS의 경우에는 업데이트 주기가 2~3달에 한 번씩 이루어지고 있으며 정해진 기간 안에 업데이트해야만 합니다. Redis의 경우에는 Stateful 하기 때문에 Kubernetes 업데이트 시 작업에 유의해야 하는데요. Kubernetes 업데이트로 인한 Redis의 신규 노드 그룹으로의 Migration 작업은 데이터의 무결성을 보장하는 중요한 과정으로 데이터 유실 없이 Migration 이 되어야 합니다. 안정적인 Redis Migration 시나리오를 확보하였고 다음과 같은 절차에 따라 운영하고 있습니다.
- 신규 Redis NodeGroup 생성
- 신규 Redis NodeGroup에 Redis 신규 pod 배포
- 기존 Redis에 신규 Redis를 Replica로 클러스터링
- 신규 Redis Replica를 Master로 승격
- 기존 Redis 제거
- Redis 정보 업데이트
Redis as a Service
카카오페이증권에서는 Redis를 플랫폼으로써 서비스 개발팀이 스스로 잘 사용할 수 있게 구성하는 것을 목표로 갖고 있는데요. Redis를 Self Service로 제공하기 위해 Redis 운영에 필요한 다양한 작업을 자동화하였습니다. Redis as a Service에서 제공하는 Redis의 주요 기능은 다음과 같습니다.
- Redis Cluster 또는 Redis Sentinel을 5분 내로 배포 가능
- Redis Master/Repica/Shard 확장을 위한 빠르고 편리한 스케일 아웃
- Failover 테스트 기능
- Redis 데이터 백업 설정
서비스 개발팀에서 개발자 생산성 플랫폼2을 이용하여 Self Service로 Redis를 사용할 수 있는 환경을 제공할 수 있게 준비 중인데요. 서비스 개발팀에서 스스로 Redis를 구성하고 운영함으로써 보다 빠르고 다양한 관점에서 사용성을 높여가기 위해 노력 중입니다.
고가용성
Pod Affinity, Zone Spread Out 설정을 통해 Redis Pod가 Zone 별 분산 배포되도록 구성하여 고가용성을 확보하였습니다.
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/instance
operator: In
values:
- kakaopaysec
topologyKey: topology.kubernetes.io/zone
weight: 100
- podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/instance
operator: In
values:
- kakaopaysec
topologyKey: kubernetes.io/Hostname
weight: 99
Redis는 데이터의 영속성(Persistence)을 보장하기 위해 AOF를 활용하고 있는데요. 일반적으로 Redis 장애 시에도 서비스에 영향을 주지 않도록 애플리케이션에는 Redis Fallback 이 구현되어 있습니다만 장애 상황 시에도 안정적으로 데이터를 유지하기 위해 lifecycleHooks의 prestop을 통해 장애가 발생한 Pod에 저장된 AOF 파일을 내부 오브젝트 스토리지로 백업하도록 설정하였습니다.
lifecycleHooks:
preStop:
exec:
command:
- /bin/sh
- '-c'
- >-
sleep 30; /redis/scripts/redis-backup.sh
평상시에도 장애 시 민첩한 대응을 위해 Redis의 AOF 파일을 주기적으로 내부에 구성된 오브젝트 스토리지에 저장하고 있으며 만약 기존 Redis가 정해진 RTO 내로 정상화가 어려울 것 같다고 판단될 경우에는 빠르게 신규 Redis를 구성 후 오브젝트 스토리지에 백업된 AOF 파일을 이용하여 데이터를 신규 Redis에서 안전하게 복구하고 서비스할 수 있습니다.
성과
안전성 확보
Redis Cluster의 고가용성을 향상하고 신속한 장애 복구를 통한 안정성을 확보할 수 있었습니다. Kubernetes의 Self Healing 등의 자동 복구 메커니즘을 활용하여 Redis Pod에 문제가 발생하더라도 시스템은 계속해서 안정적으로 운영될 수 있는 환경을 제공합니다.
성능 확보
Redis가 최적의 성능을 달성하기 위해 다양한 시나리오를 가지고 부하 테스트를 진행했고 그 결과를 기반으로 구성 변경 및 설정 튜닝을 여러 차례 반복하며 Bare Metal이나 Amazon ElastiCache에 준하는 성능을 확보할 수 있었습니다.
./src/redis-benchmark -h redis-test.domain -p 6379 --cluster -d 100 -n 1000000 -r 1000000 -c 3 -q -t PING_INLINE,SET,GET,INCR,LPUSH,RPUSH,LPOP,RPOP,HSET,MSET
Redis Benchmark를 활용한 테스트에서 기존에 사용 중인 Amazon ElastiCache와 성능 비교를 진행했는데요. AOF를 everysec으로 설정해도 Amazon ElastiCache와 동등한 성능을 발휘한 것을 테스트를 통해 확인할 수 있었습니다.
Redis는 물리 메모리보다 더 많은 데이터를 사용하게 될 경우 메모리 부족에 의한 SWAP이나 OOM으로 인해 Redis의 성능 저하나 장애를 일으킬 수 있는데요. Redis에서는 이러한 문제를 방지하고자 MaxMemory 설정을 권장하고 있습니다. MaxMemory 설정 또한 여러 번의 부하 테스트를 통해 최적화할 수 있었습니다.
MaxMemory 설정을 통해 메모리를 일정 범위 이상 사용하지 못하도록 제한할 수 있는데요. MaxMemory 이상으로 데이터가 저장되면 Eviction을 통해 기존 데이터를 제거하여 메모리를 유지할 수 있습니다. MaxMemory 설정을 하지 않을 경우에는 아래와 같이 OOM이 발생할 수 있으니 MaxMemory를 설정하여 메모리 관련 이슈가 발생하지 않도록 해야 합니다.
sandbox-rediscluster:/$ redis-benchmark -h redis-test.domain -p 6379 -d 100 -n 5000000 -r 1000000 -c 3 -q -t SET,GET,INCR,LPUSH,RPUSH,LPOP,RPOP,HSET,MSET
Error from server: OOM command not allowed when used memory > 'maxmemory'.
자동화
Redis 자동화를 통해 운영의 효율성을 크게 향상할 수 있었는데요. Redis 운영 시 반복적으로 하게 되는 신규 생성 작업부터 Master나 Replica를 확장하거나 축소하는 작업, Migration 하는 작업, 데이터를 백업하는 작업 등을 수동으로 하게 되면 Redis의 운영 방식, 규모, 작업자 숙련도에 따라서 작업 시간이 많이 소요될 수 있습니다. 이러한 Redis 운영 작업을 플랫폼 기반으로 자동화함으로써 일률적인 작업 시간 확보 및 작업 시간 단축(기존 대비 50% 이상), 휴먼 에러 방지 등을 통해 운영 효율성을 향상할 수 있었습니다.
Redis as a Service
# Redis Helm Chart 프로젝트 구조 예시
├── Chart
│ ├── templates/ # Redis Helm Chart templates 디렉토리
│ └── values/ # Cluster, Redis, Phase 별로 활용 가능한 values.yaml 디렉토리
│ └── cluster # Kubernetes Cluster 종류
│ └── redis # Redis 종류
│ └── sandbox-values.yml # Redis Custom Config
│ └── stage-values.yml
│ └── production-values.yml
├── chart.yaml
├── platforms.yml
├── README.md
...
└── values.yml # Redis Default Config
Kubernetes에 구성한 Redis는 Wallga 플랫폼3의 Custom Helm Chart를 활용하여 Redis Cluster, Redis Sentinel 등 필요한 플랫폼으로 빠르게 신규 구성을 할 수 있습니다. 그리고 플랫폼으로써 자유도를 높이기 위해 Redis Cluster와 Redis Sentinel 2가지 방식을 제공하고 있는데요. 서비스 개발팀에선 서비스 특성과 활용도에 맞게 선택하여 사용하고 있습니다.
- Redis Sentinel : 2~10GB 정도의 비교적 저용량의 데이터를 저장하거나 가벼운 Cache 용도로 활용하는 서비스
- Redis Cluster : Redis Sentinel에서 운영하기 어려운 대용량의 데이터를 저장하거나 Sharding 이 필요한 서비스
스케일 아웃 자동화
서비스 트래픽 증가에 따라 서비스에서 사용하는 Redis를 스케일 아웃해야 하는 경우가 종종 발생하는데요. Kubernetes에서 제공하는 스케일링 기능을 통해 확장이 필요한 시점에 Redis의 크기를 자유롭게 조정함으로써 민첩한 스케일 아웃이 가능합니다. 또한 메트릭 임계치 기반으로 트래픽에 따라 유연하게 스케일 아웃을 자동화할 수 있습니다.
Failover 테스트 자동화
Failover 테스트를 자동화함으로써 Redis가 생성될 때마다 서비스 개발팀에서는 Redis에 대한 Failover 테스트를 자동화된 플랫폼을 통해 간편하게 진행할 수 있습니다.
Redis Failover 상황에 대한 아키텍처인데요. Redis Cluster의 특정 Master에 장애가 발생한 상황을 가정해서 테스트 환경을 자동화하였습니다.
- Redis Cluster의 상태 확인
- Redis Cluster의 Mater Node 장애 발생 상황 적용(Master 2)
- 🧑🏻💻 가용성 테스트 진행
- 🧑🏻💻 가용성 테스트 완료
- Redis Cluster의 Mater Node 장애 해소 상황 적용(Master 2)
- Redis Cluster의 상태 확인
Failover 테스트를 위한 일련의 작업들을 기존에는 DevOps 팀에서 수행했는데요. 자동화 플랫폼을 통해 개발자가 직접 Failover 상황을 설정하여 애플리케이션을 테스트한 후 운영에 배포할 수 있습니다.
이와 같은 Redis 운영 자동화를 통해 Redis를 관리하는 데 필요한 시간과 노력을 절감하고 Redis의 빠른 배포 및 확장을 가능케 하여 개발 생산성을 높일 수 있었습니다.
리소스 효율화
Bare Metal 환경에 Redis를 구성하게 되면 일정 리소스의 버퍼 스펙을 가져가게 되고 그로 인해 리소스를 효율적으로 활용하기가 어려워지는데요. Kubernetes로 구성할 경우 리소스를 사용량에 따라 제한하고 보다 유동적으로 활용할 수 있게 됨으로써 리소스를 더욱 효과적으로 활용할 수 있게 됩니다.
운영 편의성
편리한 배포 환경
Redis의 Config를 ConfigMap으로 각 Redis 별로 추상화하여 선언적으로 관리할 수 있는데요. Bare Metal 환경의 경우 Redis의 Config를 개별 Redis 별로 관리해야 하지만 Kubernetes에 구성된 Redis는 하나의 ConfigMap을 통해 일원화하여 관리할 수 있습니다. 이를 통해 Redis의 설정 정보를 Helm Chart를 이용해서 편리하게 배포 및 관리할 수 있습니다.
apiVersion: v1
kind: ConfigMap
metadata:
...
data:
redis-default.conf: >-
# Redis configuration file.
# Note that in order to read the configuration file, Redis must be
# started with the file path as first argument:
# Note on units: when memory size is needed, it is possible to specify
# it in the usual form of 1k 5GB 4M and so forth:
# 1k => 1000 bytes
# 1kb => 1024 bytes
latency-monitor-threshold 25
list-compress-depth 0
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
stream-node-max-bytes 4096
stream-node-max-entries 100
...
모니터링
podAnnotations 설정으로 Prometheus에서 자동으로 Service Discovery 할 수 있도록 구성하였습니다.
podAnnotations:
redis.exporter.scrape: 'true'
redis.exporter.scrape.port: '6379'
Self Healing
Redis의 가용성을 보장하기 위해 여러 대의 Master와 Replica 구조로 구성되었는데요. Master 또는 Replica에 문제가 생기더라도 서비스에 영향이 없도록 빠르게 복구할 수 있어야 합니다. Bare Metal 환경에서는 Master 또는 Replica 서버에 문제가 발생하면 관리자가 수작업을 통해 새로운 서버에 Redis를 설치하고 기존 Redis Cluster에 조인해야 하는 상황이 발생할 수 있는데요. Kubernetes는 Pod의 상태를 주기적으로 확인하고 문제가 발생한 경우 해당 Pod를 다시 시작하는 셀프 힐링을 지원하고 있습니다. 이를 통해 Kubernetes에 구성된 Redis는 Master 또는 Replica 역할을 하는 Redis Pod에 장애가 발생하더라도 자동으로 정상 노드에서 재시작되어 가용성을 유지할 수 있습니다.
Observability
Redis Cluster / Redis Sentinel에서 수집되는 주요 Metric을 통합된 대시보드로 제공하고 있습니다. 또한 Kubernetes 내의 Redis Pod에 대한 상태 변화나 이상 상황에 대한 경고를 Opensearch Dashboard, Grafana, Slack 기반으로 설정하여 이슈가 발생했을 때 신속하게 인지할 수 있는 환경을 구축하였습니다.
마치며
Kubernetes 환경에 구성한 Redis Cluster/Redis Sentinel은 운영 환경에서 지난 1년 동안 큰 이슈없이 안정적으로 운영되고 있습니다. 컨테이너 환경에 Redis를 구성하는 것이 무조건 정답이 아닐 수도 있습니다. 여러 가지 환경적 요인, 리소스, Kubernetes 운영 경험 등 다양한 부분이 고려되어야 한다고 생각하는데요. 그럼에도 불구하고 Redis Cluster/Redis Sentinel 환경을 컨테이너 환경에 구성하는 것은 운영적인 측면을 포함한 다양한 부분에서 매우 매력적인 선택이라고 생각합니다. 앞으로 카카오페이증권의 DevOps 팀에서는 사용자 측면의 사용성과 활용도를 높이기 위한 다양한 노력을 해 나갈 예정인데요. 오픈소스 기반 컨테이너/플랫폼엔지니어링에 대한 관심과 도전, 성장의 즐거움을 느끼고 싶으신 많은 개발자 분들의 합류를 기다리고 있습니다! 많은 관심 부탁드립니다!
참고자료
Footnotes
-
Fully Connected Mesh Topology : 모든 노드가 서로를 확인하고 정보를 주고받을 수 있는 토폴로지. ↩
-
개발자 생산성 플랫폼 : 서비스 개발팀의 생산성 지표를 가시화하고, 소프트웨어 개발 및 릴리즈를 위해 필요한 다양한 업무들을 제공하는 플랫폼. ↩
-
Wallga 플랫폼 : 카카오페이증권의 CI/CD 플랫폼. 애플리케이션 빌드/배포되는 과정과 운영에 필요한 여러 가지 도구들을 통합하고 자동화함. ↩