DNS container 부하 시험

목적

  • DNS query를 여러 개의 DNS containers가 처리할 수 있도록 부하 분산 처리 방법 고안
  • 부하분산기(Load Balancer) 하단의 container 개수를 1개부터 증가시켜 가면서 QPS(queries per second)를 측정하여 하나의 물리 서버에 최대 DNS container 개수 파악

시험 환경

  • Model: Intel S5520UR (Intel 서버인거보니 제조사는 Supermicro 또는 Teratec 인 것 같음)
  • CPU: Intel Xeon CPU E5620 @ 2.40GHz 4 cores * 2 sockets * HT = 16 cores
  • RAM: Samsung DDR3 1067MGz 8GiB * 8 ea = 64GiB
  • RAID controller: Intel RAID Controller RS2BL080 (with megaraid_sas driver)
  • HDD: SATA 7200rpm 1TB * 5 ea
  • NIC: Intel 82575EB Gigabit NIC * 2 ea

부하분산기(Load Balancer)

본 실험에서는 iptables를 부하분산기로 사용한다. nginx도 2016년 3월에 릴리즈된 version 1.9.13에 UDP 부하분산 기능이 있다(https://www.nginx.com/blog/announcing-udp-load-balancing/ 참조). 그러나 nginx 를 이용한 UDP 부하분산은 이전 실험에서 성능이 그저 그렇다는 결과가 나와 iptables로 부하 분산 성능 시험을 한다.

iptables extension 모듈에 statistic 이라는 것이 있는데 이것을 이용하여 부하분산을 한다.

시험환경은 docker1 물리서버에 dns1,2,3 container를 아래와 같이 설정하였다.

  • docker1 host IP: 106.241.77.11
  • dns1 container IP:port = 172.17.0.5:53 -> docker1:1253
  • dns2 ” = 172.17.0.6:53 -> docker1:1153
  • dns3 ” = 172.17.0.7:53 -> docker1:1053

rule 적용은 다음과 같다. 아래 iptables rule 설정은 docker1 host 106.241.77.11 udp port 53 으로 들어오는 패킷을 3개 dns container의 IP:port로 분산 배분해준다.

# iptables -t nat -A PREROUTING -i enp1s0f0 -p udp -m udp --dport 53 -m state --state NEW -m statistic --mode nth --every 3 --packet 0 -j DNAT --to-destination 172.17.0.5:53
# iptables -t nat -A PREROUTING -i enp1s0f0 -p udp -m udp --dport 53 -m state --state NEW -m statistic --mode nth --every 2 --packet 0 -j DNAT --to-destination 172.17.0.6:53
# iptables -t nat -A PREROUTING -i enp1s0f0 -p udp -m udp --dport 53 -m state --state NEW -m statistic --mode nth --every 1 --packet 0 -j DNAT --to-destination 172.17.0.7:53

시험 OS가 CentOS 7.x 이기에 ethernet interface이름이 enp1s0f0 이다. debian 이라면 eth0 일 것이다.

DNS 성능 실험

DNS성능 부하 툴은 dnsperf(http://nominum.com/measurement-tools/)를 이용한다.

1차 실험

$ ./dnsperf -s 106.241.77.11 -d domain -l 30 -c 3 -Q 100000

추가한 옵션은 * -s: DNS server ip address * -d: 질의할 내용이 담긴 파일 * -l: 성능 시험 시간 (초) * -c: 가상의 client 개수 * -Q: queries per second

위 domain이라는 파일의 내용은 다음과 같다.

a.domain.local    A

a.domain.local의 A record를 질의하는 것이다.(물론 a.domain.local은 실제 fqdn이 아니다.) docker1 호스트에서 위 명령을 실행하여 부하를 준다.

하나의 dns container에 부하를 주면, 약 400cpu%를 차지한다. docker1 host가 16 cores를 가지고 있으니 25%에 해당한다. 그리고 하나의 dns server에 -Q 옵션없이 부하를 주어도 QPS(queries per second)는 25000 - 30000 정도의 값이 나온다.

위와 같이 QPS 100,000을 주면 하나의 DNS server 최대 부하의 3배정도 된다.

위와 같이 주면 3개의 named process가 사이좋게 약 350 - 400 cpu%를 사용한다. 따라서 전체 cpu cores의 75%를 사용하게 된다. 위 -c 3 옵션으로는 QPS 값은 87,000 정도 나옵니다. 즉, ideal한 값(100,000)의 87% 정도 수준으로 나오게 됩니다. -c 20으로 클라이언트 개수를 늘리면 93,000으로 93% 수준으로 근접합니다.

2차 실험

5개 DNS 부하분산 성능 시험 완료하였다.

DNS부하 명령:

dnsperf -s 106.241.77.11 -d domain.txt -l 30 -p 53 -c 8 -T 16 -t 10

dnsperf는 한 명령이 2개의 threads를 만든다. 따라서 clients 8개(-c), Thread 16개(-T)를 주었다. -Q로 QPS를 제한하지 않은 대신 query timeout을 default 5초에서 10초(-t)로 늘렸다.

결과: QPS는 124,000 대가 나왔다. 각 named는 250 - 300cpu%를 사용한다. (275% * 5 named = 1375% 사용) 전체 시스템 CPU 사용율은 48us%, 25sy%, 16si% = 89% 사용.(11% idle%)

5개 하기전에 4개 DNS 부하분산 성능 시험에서도120,000 대가 나왔으므로 120,000 대 QPS가 최대치인 것 같습니다.

그렇다면, 두 개의 dnsperf 를 동시에 돌리면? QPS가 133,196이 나왔다.

3개의 dnspef를 동시에 돌리면? QPS가 135,481이 나왔다.

물론 약간의 향상은 되었으나 별로 유의미하게 보이지 않습니다.

결론은 위 서버 스펙 기준으로 * bind9 container 한 개 당 최대 처리 QPS는 30,000 대이다. * bind9 container 여러 대를 iptables로 부하분산 처리하면 선형적으로 QPS가 증가한다. - 2개 부하분산: 60,000 대역 - 3개 부하분산: 90,000 대역 - 4개 부하분산: 120,000 대역 * bind9 container 4개를 초과한 LB 구성부터는 QPS가 선형적으로 증가하지 않는 포화곡선이 시작된다. - 5개 LB: 120,000 대역 * 부하기를 동시에 여러 개 구동하여 시험하여도 QPS는 최대 130,000 대역에 머문다.

부록

위 부하분산 방식은 docker proxy를 거치지 않는 방식이었다.

dnsperf -- iptables -- dns container1
                |----- dns container2
                |----- dns container3

위 1차 실험 후 2차 실험 전에 docker proxy를 거치는 방식으로 부하 분산 성능시험을 해 보았다. docker proxy를 거치는 iptables rules

# iptables -t nat-A PREROUTING -i enp1s0f0 -p udp -m udp --dport 53 -m state --state NEW -m statistic --mode nth --every 3 --packet 0 -j DNAT --to-destination 106.241.77.11:1053
# iptables -t nat -A PREROUTING -i enp1s0f0 -p udp -m udp --dport 53 -m state --state NEW -m statistic --mode nth --every 2 --packet 0 -j DNAT --to-destination 106.241.77.11:1153
# iptables -t nat -A PREROUTING -i enp1s0f0 -p udp -m udp --dport 53 -m state --state NEW -m statistic --mode nth --every 1 --packet 0 -j DNAT --to-destination 106.241.77.11:1253
dnsperf -- iptables -- docker-proxy -- container1
                                |----- container2
                                |----- container3

이 경우 docker proxy가 병목현상의 원인이 되는 것을 확인하였다.

각 named 는 350-400 cpu% 이하로 떨어지고, 각 docker proxy process는 100 cpu%를 차지한다. 즉, 합(450-500%)은 docker proxy를 거치지 않는 방식(400%)보다 많은 cpu%점유율을 가져간다. 그러나 QPS는 58,000대로 떨어진다.

docker proxy를 거치지 않은 1차 실험에서는 93,000대가 나왔었다.

역시 iptables만을 이용하는 kernel mode로만 작동할 때와 docker proxy(user mode)/iptables(kernel mode)가 switch되는 때와는 많은 차이를 보인다. dstat으로 관찰해 보니context swtich가 240kps 일어난다. docker proxy를 거치지 않는 방식은 context swtich가 190kps으로 줄어든다. 결론은 docker proxy를 거치면서 context swtich overhead가 크기 때문에 명확한 성능 차이가 보인다.

결론은

  • dnsperf – iptables – dns container
  • QPS: 93,000 - 94,000
  • context switch: 190kps
  • cpu%: named 400% * 3 containers = 1200% = 12 cores 사용
  • dnsperf – iptables – docker proxy – dns container
  • QPS: 58,000 - 59,000
  • context switch: 240kbps
  • cpu%: named 300%, docker proxy 100% = 400% * 3 containers = 1200% = 12 cores 사용

에필로그

이 문서에서 왜 dns container 만드는 방법에 대한 설명이 없는가 의아한 분들이 있을 것이다. 이유는 container를 내가 만들지 않았기 때문이다. 정확히 말하자면 docker image 를 내가 만들지 않았다. 이미 container는 있는 상태에서 부하 분산 성능 검증 요청을 받아 위와 같이 진행하였다. 개인적인 실험이었다면, BIND9을 사용할 리 없다. 난 djbdns 팬이니까…