최근 작년에 진행한 졸업 프로젝트를 리팩토링하고 있다.
프로젝트 EC2에서 스프링 부트 애플리케이션과 도커 컴포즈를 사용한 레디스 클러스터가 동작하고 있다.
DB로는 AWS RDS MySQL을 사용하고 있다.
또한 Github Action, S3, Code Deploy를 사용해 CI/CD 파이프 라인을 구축했다.
오늘은 CI/CD 파이프 라인을 구축하면서 있었던 오류에 대해 말해보고자 한다.
문제
CI/CD 파이프라인에는 스프링 부트 애플리케이션을 빌드하는 과정에서 작성한 모든 테스트(통합 테스트, 단위 테스트)를 수행하는 과정이 있다. 여기에서 문제가 발생했다.
빌드 통합 테스트 과정 중 EC2에서 동작하는 레디스 클러스터와 통신이 안됐다.
그 당시 로컬과 운영 환경에서 작성한 docker-compose.yml은 아래와 같다.
version: "3"
services:
redis-cluster:
image: redis
container_name: redis-master
volumes: # 작성한 설정 파일을 볼륨을 통해 컨테이너에 공유
- ./redis.conf:/usr/local/etc/redis/redis.conf
command: redis-server /usr/local/etc/redis/redis.conf
ports:
- 7000:7000
- 7001:7001
- 7002:7002
redis-node-1:
network_mode: "service:redis-cluster"
image: redis
container_name: redis-slave1
volumes:
- ./redis1.conf:/usr/local/etc/redis/redis.conf
command: redis-server /usr/local/etc/redis/redis.conf
redis-node-2:
network_mode: "service:redis-cluster"
image: redis
container_name: redis-slave2
volumes:
- ./redis2.conf:/usr/local/etc/redis/redis.conf
command: redis-server /usr/local/etc/redis/redis.conf
redis-cluster-entry:
network_mode: "service:redis-cluster"
image: redis
container_name: redis-cluster-entry
command: redis-cli -a xxxx --cluster create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 --cluster-yes
depends_on:
- redis-cluster
- redis-node-1
- redis-node-2
networks:
default:
external:
name: our_net
아래는 redis.conf 파일들이다.
port 7000
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 3000
appendonly yes
requirepass ****
protected-mode no
port 7001
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 3000
appendonly yes
requirepass ****
protected-mode no
port 7001
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 3000
appendonly yes
requirepass ****
protected-mode no
몇 가지 상황으로 문제를 확인해보았다.
- 로컬 스프링 부트 애플리케이션과 로컬 레디스 클러스터링은 통신이 잘되는 상황이다.
- 마찬가지로 운영 스프링 부트 애플리케이션과 운영 레디스 클러스터링 또한 통신이 잘 된다.
위 상황으로 비추어 보아 같은 VM에서 동작하면 레디스와 애플리케이션이 잘 통신한다.
- 로컬 redis-cli를 사용해서도 운영 레디스 클러스터링과 통신이 잘 되는 상황이다.
- 다만 로컬 스프링부트 애플리케이션과 운영 레디스 클러스터링은 통신이 안된다.
위 상황으로 비추어 보아 redis-cli 같은 툴은 다른 환경에서도 통신한다.
그러나 같은 VM이 아니면 레디스와 애플리케이션은 동작하지 않는다는 결론을 내렸다.
첫 번째 문제 해결 과정
문제를 해결하기 위해 여러 자료를 찾아보았다.
먼저 도커 공식 사이트에서 다음과 같은 문구를 확인했다.
Redis Cluster and Docker
Currently, Redis Cluster does not support NATted environments and in general environments where IP addresses or TCP ports are remapped.
Docker uses a technique called port mapping: programs running inside Docker containers may be exposed with a different port compared to the one the program believes to be using. This is useful for running multiple containers using the same ports, at the same time, in the same server.
To make Docker compatible with Redis Cluster, you need to use Docker's host networking mode. Please see the --net=host option in the Docker documentation for more information.
//해석
현재 Redis 클러스터는 NAT 환경과 IP 주소 또는 TCP 포트가 다시 매핑되는 일반적인 환경을 지원하지 않습니다.
Docker는 포트 매핑 이라는 기술을 사용합니다 . Docker 컨테이너 내에서 실행되는 프로그램은 프로그램이 사용한다고 생각하는 포트와 다른 포트로 노출될 수 있습니다. 이는 동일한 서버에서 동시에 동일한 포트를 사용하여 여러 컨테이너를 실행하는 데 유용합니다.
Docker가 Redis Cluster와 호환되도록 하려면 Docker의 호스트 네트워킹 모드를 사용해야 합니다 . 자세한 내용은 Docker 설명서--net=host 의 옵션을 참조하세요
요약하면 도커는 포트 매핑을 사용하므로 레디스 클러스터링을 사용하려면 도커 네트워크 모드 중에 Host 모드를 사용해야 한다는 뜻이다.
Host Mode
- Host Mode는 오직 리눅스에서만 동작한다.
- 호스트 네트워크 모드를 사용하는 경우 해당 컨테이너의 네트워크 스택은 Docker 호스트에서 격리되지 않으며(컨테이너는 호스트의 네트워킹 네임스페이스를 공유한다) 컨테이너에 자체 IP 주소가 할당되지 않는다.
- 포트 매핑이란 기술을 사용하지 않고 컴퓨터의 포트와 도커 컨테이너 포트가 동일하다고 생각하면 된다.
- 네트워킹은 성능을 최적화하는 데 유용할 수 있으며, NAT(네트워크 주소 변환)가 필요하지 않고 각 포트에 대해 "userland-proxy"가 생성되지 않기 때문에 컨테이너가 광범위한 포트를 처리해야 하는 상황에서 유용할 수 있다.
문제 해결
다음은 문제를 해결한 최종 docker-compose.yml이다.
version: "3"
services:
redis-cluster:
image: redis
network_mode: "host"
container_name: redis-master
volumes: # 작성한 설정 파일을 볼륨을 통해 컨테이너에 공유
- ./redis.conf:/usr/local/etc/redis/redis.conf
command: redis-server /usr/local/etc/redis/redis.conf
redis-node-1:
network_mode: "host"
image: redis
container_name: redis-slave1
volumes:
- ./redis1.conf:/usr/local/etc/redis/redis.conf
command: redis-server /usr/local/etc/redis/redis.conf
redis-node-2:
network_mode: "host"
image: redis
container_name: redis-slave2
volumes:
- ./redis2.conf:/usr/local/etc/redis/redis.conf
command: redis-server /usr/local/etc/redis/redis.conf
redis-cluster-entry:
network_mode: "host"
image: redis
container_name: redis-cluster-entry
command: redis-cli -a 4648 --cluster create localhost:7000 localhost:7001 localhost:7002 --cluster-yes
depends_on:
- redis-cluster
- redis-node-1
- redis-node-2
아래는 최종 redis.conf 파일들이다.
#redis.conf
port 7000
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 3000
appendonly yes
requirepass 4648
protected-mode no
#redis1.conf
port 7001
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 3000
appendonly yes
requirepass 4648
protected-mode no
#redus2.conf
port 7002
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 3000
appendonly yes
requirepass 4648
protected-mode no
로컬 애플리케이션과 통신을 성공했다. host를 사용해서 간단하게 문제를 해결할 수 있었다.
두 번째 문제 해결 과정
다음으로는 도커의 포트 매핑을 이용할 수 있게 하는 해결 방법이다.
레디스 4.0부터는 NAT/포트 매핑을 지원한다.
즉 Docker의 임시 포트를 사용하여 Redis 클러스터를 실행할 수 있다.
호스트 모드 네트워킹이나 정적으로 할당된 포트를 사용할 필요 없이 Docker에서 Redis 클러스터를 실행할 수 있다는 것이다.
그러려면 다음과 같은 설정을 redis.conf 파일마다 추가해야 한다.
cluster-announce-ip: 퍼블릭 IP 주소
cluster-announce-port: 알려줄 데이터 포트
cluster-announce-bus-port: 알려줄 클러스터 버스 포트
참고
redis 4.0 이전에는 기본 bridge 네트워크에서 Redis를 실행하면 내부 private ip 주소와 내부 전용 포트가 클러스터 응답으로 클라이언트에 알려지기 때문이다.
본인의 경우 레디스 클러스터와 연결이 안된 시점을 예로들면 올바르게 ec2 ip와 cluster port로 요청을 보내도 포트매핑 지원이 안되므로 응답은 bridge 네트워크에서의 ip와 포트가 돌아와서 실패한 것이다.
문제 해결
version: "3"
services:
redis-cluster:
image: redis
container_name: redis-master
volumes: # 작성한 설정 파일을 볼륨을 통해 컨테이너에 공유
- ./redis.conf:/usr/local/etc/redis/redis.conf
command: redis-server /usr/local/etc/redis/redis.conf
ports:
- 7000:7000
- 17000:17000
redis-node-1:
image: redis
container_name: redis-slave1
volumes:
- ./redis1.conf:/usr/local/etc/redis/redis.conf
command: redis-server /usr/local/etc/redis/redis.conf
ports:
- 7001:7001
- 17001:17001
redis-node-2:
image: redis
container_name: redis-slave2
volumes:
- ./redis2.conf:/usr/local/etc/redis/redis.conf
command: redis-server /usr/local/etc/redis/redis.conf
ports:
- 7002:7002
- 17002:17002
아래는 최종 redis.conf 파일들이다.
port 7000
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 3000
appendonly yes
requirepass xxxx
protected-mode no
cluster-announce-ip ec2 ip
cluster-announce-port 7000
cluster-announce-bus-port 17000
port 7001
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 3000
appendonly yes
requirepass xxxx
protected-mode no
cluster-announce-ip ec2 ip
cluster-announce-port 7001
cluster-announce-bus-port 17001
port 7002
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 3000
appendonly yes
requirepass xxxx
protected-mode no
cluster-announce-ip ec2 ip
cluster-announce-port 7002
cluster-announce-bus-port 17002
이제 아래 명령어를 입력해 컨테이너에 접속한다.
sudo docker exec -it redis-master /bin/bash
이후에 클러스터링 생성을 위해 다음과 같은 명령어를 입력한다.
redis-cli --cluster create --pass xxxx ec2-ip:7000 ec2-ip:7001 ec2-ip:7002
그럼 아래와 같이 클러스터링이 생성된다.
마찬가지로 로컬에서 통신을 해보면 잘 동작하는 것을 확인할 수 있다.
결론
필자는 일단 첫 번째 방식을 선택했다. 이유는 두 가지다.
- vm에서 nginx, spring boot, redis-cluster만 돌리므로 포트 걱정이 없다.
- 또한 컨테이너 내부로 들어가서 클러스터링 생성 명령을 치지 않아도 된다.
포트를 많이 사용해야 하는 상황이면 2번째 방식이 좋다고 생각한다.
클러스터링 생성을 자동화하는 방식을 찾아봐야겠지만!
글을 다 작성하고 난 후 자동화를 위해 다음과 같이 두 번째 방식 docker-compose.yml를 수정했다.
version: "3"
services:
redis-cluster:
image: redis
container_name: redis-master
volumes: # 작성한 설정 파일을 볼륨을 통해 컨테이너에 공유
- ./redis.conf:/usr/local/etc/redis/redis.conf
command: redis-server /usr/local/etc/redis/redis.conf
ports:
- 7000:7000
- 17000:17000
redis-node-1:
image: redis
container_name: redis-slave1
volumes:
- ./redis1.conf:/usr/local/etc/redis/redis.conf
command: redis-server /usr/local/etc/redis/redis.conf
ports:
- 7001:7001
- 17001:17001
redis-node-2:
image: redis
container_name: redis-slave2
volumes:
- ./redis2.conf:/usr/local/etc/redis/redis.conf
command: redis-server /usr/local/etc/redis/redis.conf
ports:
- 7002:7002
- 17002:17002
redis-cluster-entry:
image: redis
container_name: redis-cluster-entry
command: redis-cli -a 4648 --cluster create ec2-ip:7000 ec2-ip:7001 ec2-ip:7002 --cluster-yes
depends_on:
- redis-cluster
- redis-node-1
- redis-node-2
이상으로 포스팅을 마칩니다. 감사합니다.
참고 자료
https://redis.io/docs/management/scaling/
https://stackoverflow.com/questions/57158537/can-not-connect-springboot-to-redis-cluster-on-docker
댓글