컨테이너 환경에서 Redis Error
Redis Max Connection Problem
error message on program
Vertx를 사용하여 구성한 시스템에서 갑자기 redis와 통신하지 못하는 오류가 발생했다. ERR max number of clients
라는 에러와 함께 커넥션이 연결되지 않는다.
Caused by: redis.clients.jedis.exceptions.JedisDataException: ERR max number of clients reached
at redis.clients.jedis.Protocol.processError(Protocol.java:104) ~[jedis-2.4.2.jar:na]
at redis.clients.jedis.Protocol.process(Protocol.java:122) ~[jedis-2.4.2.jar:na]
at redis.clients.jedis.Protocol.read(Protocol.java:191) ~[jedis-2.4.2.jar:na]
at redis.clients.jedis.Connection.getRawObjectMultiBulkReply(Connection.java:221) ~[jedis-2.4.2.jar:na]
at redis.clients.jedis.Connection.getObjectMultiBulkReply(Connection.java:227) ~[jedis-2.4.2.jar:na]
at redis.clients.jedis.Jedis.sentinelGetMasterAddrByName(Jedis.java:2950) ~[jedis-2.4.2.jar:na]
at redis.clients.jedis.JedisSentinelPool.initSentinels(JedisSentinelPool.java:131) ~[jedis-2.4.2.jar:na]
at redis.clients.jedis.JedisSentinelPool.(JedisSentinelPool.java:73) ~[jedis-2.4.2.jar:na]
at redis.clients.jedis.JedisSentinelPool.(JedisSentinelPool.java:49) ~[jedis-2.4.2.jar:na]
디버깅을 위해 redis-cli를 통해 접속하여 명령을 전달하니 같은 에러를 출력하며 연결이 끊어진다.
일반적인 상황에서 위와 같은 문제는 config 파일에서 설정해 놓은 maxclients
의 임계치보다 많은 client 접속이 이뤄질때 발생하는 문제이다. 디버깅을 위해 컨테이너 내부에 net-tools
를 설치하여 소켓 연결정보를 확인한다.
- redis 설정 확인
#redis-cli
127.0.0.1:6379> config get maxclients
1) "maxclients"
2) "10000"
- redis에 연결된 커넥션 수 확인
#bash
apt-get update
apt-get install -y net-tools
netstat -antp | grep 6379 | grep ESTABLISHED | wc -l
root@2332af2f09f1:/data# netstat -antp
tcp 0 0 9.0.0.99:6379 44.128.0.3:55356 ESTABLISHED -
tcp 0 0 9.0.0.99:6379 44.128.0.3:34069 ESTABLISHED -
...
생략
...
tcp 0 0 9.0.0.99:6379 44.128.0.3:55356 ESTABLISHED -
tcp 0 0 9.0.0.99:6379 44.128.0.3:34069 ESTABLISHED -
tcp 0 0 9.0.0.99:6379 44.128.0.3:58011 ESTABLISHED -
tcp 0 0 9.0.0.99:6379 44.128.0.3:59263 ESTABLISHED -
tcp 0 0 9.0.0.99:6379 44.128.0.3:43884 ESTABLISHED -
tcp 0 0 9.0.0.99:6379 44.128.0.3:44968 ESTABLISHED -
tcp 0 0 9.0.0.99:6379 44.128.0.3:34991 FIN_WAIT2 -
tcp 0 0 9.0.0.99:6379 44.128.0.3:45384 ESTABLISHED -
tcp6 0 0 :::6379 :::* LISTEN -
root@2332af2f09f1:/data# netstat -antp | grep 6379 | wc -l
10796
Default로 설정된 maxclients는 1만개 인데, 연결된 커넥션 수가 1만개를 가득 채웠다. 이용자가 많은 서비스라면, maxclients 설정을 늘려주면 해결되는 문제이다. 관련 내용은 다음 블로그를 참고 하면 된다. (참고)
하지만, 필자가 구축한 서비스는 아직 공식 오픈 전인 서비스였기 때문에, 저렇게 많은 커넥션이 이루어져 있다는 것은 이상한 일이었다. 더 디버깅 하기 위해 컨테이너 밖의 연결 정보를 확인한다.
- Host OS 커넥션 확인
[13:31:53] /
root@pcjldcospriv1$ netstat -antp | grep 16379 | grep ESTABL
...
생략
...
26424/docker-proxy
tcp6 0 0 128.11.1.68:16379 9.0.0.102:57106 ESTABLISHED 26424/docker-proxy
tcp6 0 0 128.11.1.68:16379 9.0.0.108:58606 ESTABLISHED 26424/docker-proxy
tcp6 0 0 128.11.1.68:16379 9.0.0.104:50278 ESTABLISHED 26424/docker-proxy
tcp6 0 0 128.11.1.68:16379 9.0.0.102:56516 ESTABLISHED 26424/docker-proxy
tcp6 0 0 128.11.1.68:16379 9.0.0.103:44830 ESTABLISHED 26424/docker-proxy
[13:31:53] /
root@pcjldcospriv1$ netstat -antp | grep 16379 | grep ESTABL | wc -l
63
정확한 원인은 알 수 없지만, 컨테이너 외부에서 내부로 연결된 커넥션은 겨우 60개를 넘길 뿐인데, 컨테이너 내부에는 세션이 1만개가 넘게 잡혀있었다. 결론을 말하자면, redis 세팅에 tcp-keepalive
값이 설정되어 있지 않아서 발생한 문제이다.
- tcp-keepalive 설정 확인 및 적용
#redis-cli
# 설정 확인
127.0.0.1:6379> config get tcp-keepalive
1) "tcp-keepalive"
2) "0"
# 설정 적용
127.0.0.1:6379> config set tcp-keepalive 300
OK
위와 같이 설정하여 tcp-keepalive 세팅을 올바르게 설정할 수 있다. 하지만, 위 설정이 안되어 있었다면 다른 설정들도 올바르게 되지 않았을 가능성이 있으므로 본 포스팅 하단에서 주의해야 할 옵션들을 확인해볼 것을 권장한다.
필자는 에러를 해결하기 위해 Default config에 몇가지 설정을 적용하여 이미지를 새로 배포하였다. (적용한 설정 포스팅 하단 확인)
- Dockerfile
FROM redis:5.0.6
COPY ./redis.conf /usr/local/etc/redis/redis.conf
ENTRYPOINT redis-server /usr/local/etc/redis/redis.conf
- redis config 파일
redis의 버전별 기본 config 파일은 다음 링크에서 확인할 수 있다. 끝.
포스팅은 주의해야 할 옵션을 소개하고 마친다.
기본 config 사용시 주의할 옵션
- 연결된 클라이언트가 일정 시간 명령이 없을시, 커넥션 제거 (0으로 설정시 사용안함, Default 0)
# Close the connection after a client is idle for N seconds (0 to disable)
timeout 0
- 클라이언트가 살아있는지 체크하여, 응답 없을시 커넥션 제거 (0으로 설정시 사용안함, Default 300)
timeout과 다른점은, redis-server에서 직접 client에게 통신을 시도하여, 적극적으로 커넥션 제거를 시도한다는 점이다. timeout 0
설정과 함께 사용하여, 정상연결된 세션에 대해서는 커넥션을 유지하면서 불필요한 커넥션 제거가 가능하도록 한다.
# TCP keepalive.
#
# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence
# of communication. This is useful for two reasons:
#
# 1) Detect dead peers.
# 2) Take the connection alive from the point of view of network
# equipment in the middle.
#
# On Linux, the specified value (in seconds) is the period used to send ACKs.
# Note that to close the connection the double of the time is needed.
# On other kernels the period depends on the kernel configuration.
#
# A reasonable value for this option is 300 seconds, which is the new
# Redis default starting with Redis 3.2.1.
tcp-keepalive 300
- redis 외부접속 허용(Default 127.0.0.1)
미적용시 localhost(127.0.0.1)을 제외한 클러스터에서 접속이 불가능하게 된다.
# By default, if no "bind" configuration directive is specified, Redis listens
# for connections from all the network interfaces available on the server.
# It is possible to listen to just one or multiple selected interfaces using
# the "bind" configuration directive, followed by one or more IP addresses.
#
# Examples:
#
# bind 192.168.1.100 10.0.0.1
# bind 127.0.0.1 ::1
#
# ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the
# internet, binding to all the interfaces is dangerous and will expose the
# instance to everybody on the internet. So by default we uncomment the
# following bind directive, that will force Redis to listen only into
# the IPv4 loopback interface address (this means Redis will be able to
# accept connections only from clients running into the same computer it
# is running).
#
# IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES
# JUST COMMENT THE FOLLOWING LINE.
bind 127.0.0.1
#뭐시기 따시기 중략
protected-mode yes
- redis password 적용(Default 사용안함)
# Require clients to issue AUTH <PASSWORD> before processing any other
# commands. This might be useful in environments in which you do not trust
# others with access to the host running redis-server.
#
# This should stay commented out for backward compatibility and because most
# people do not need auth (e.g. they run their own servers).
#
# Warning: since Redis is pretty fast an outside user can try up to
# 150k passwords per second against a good box. This means that you should
# use a very strong password otherwise it will be very easy to break.
requirepass <사용할 비밀번호>