블로그 | NGINX

동적 Kubernetes 클라우드 환경에서 NGINX Ingress 컨트롤러 성능 테스트

NGINX-F5-수평-검정-유형-RGB의 일부
아미르 라우드닷 썸네일
아미르 라우드다트
2020년 9월 22일 게시

편집자 - 이 게시물은 10부작 시리즈의 일부입니다.

  1. 프로덕션 등급 Kubernetes로 복잡성 감소
  2. 고급 트래픽 관리를 사용하여 Kubernetes의 복원력을 개선하는 방법
  3. Kubernetes에서 가시성을 개선하는 방법
  4. 트래픽 관리 도구를 사용하여 Kubernetes를 보호하는 6가지 방법
  5. Ingress 컨트롤러 선택 가이드, 1부: 귀하의 요구 사항을 식별하세요
  6. Ingress 컨트롤러 선택 가이드, 2부: 위험과 미래 대비
  7. Ingress 컨트롤러 선택 가이드, 3부: 오픈소스 대 기본값 대비 광고
  8. Ingress 컨트롤러 선택 가이드, 4부: NGINX Ingress 컨트롤러 옵션
  9. 서비스 메시를 선택하는 방법
  10. 동적 Kubernetes 클라우드 환경에서 NGINX Ingress 컨트롤러 성능 테스트(이 게시물)

또한 전체 블로그 세트를 무료 전자책인 ' 테스트에서 프로덕션까지 Kubernetes 활용' 으로 다운로드할 수 있습니다.

점점 더 많은 기업이 컨테이너화된 앱을 프로덕션에서 실행함에 따라, Kubernetes는 컨테이너 오케스트레이션을 위한 표준 도구로서의 입지를 더욱 공고히 하고 있습니다. 동시에, COVID-19 팬데믹으로 인해 재택근무 가 늘어나 인터넷 트래픽이 급증하면서 클라우드 컴퓨팅에 대한 수요도 몇 년 앞당겨졌습니다. 고객들이 심각한 네트워크 중단과 과부하를 겪고 있기 때문에 기업들은 자사 인프라를 신속하게 업그레이드하기 위해 노력하고 있습니다.

클라우드 기반 마이크로서비스 환경에서 필요한 수준의 성능을 달성하려면 차세대 하이퍼스케일 데이터 센터의 확장성과 성능을 활용하는 빠르고 완전히 동적인 소프트웨어가 필요합니다. 컨테이너를 관리하기 위해 Kubernetes를 사용하는 많은 조직은 NGINX 기반 Ingress 컨트롤러를 사용하여 사용자에게 앱을 제공합니다.

이 블로그에서는 현실적인 멀티 클라우드 환경에서 세 개의 NGINX Ingress 컨트롤러에 대한 성능 테스트 결과를 보고합니다. 인터넷에서 클라이언트 연결의 지연 시간을 측정합니다.

테스트 프로토콜 및 수집된 메트릭

우리는 부하 생성 프로그램 wrk2를 사용하여 클라이언트를 에뮬레이션하여 정의된 기간 동안 HTTPS를 통해 지속적인 요청을 만들었습니다. 테스트 중인 Ingress 컨트롤러(커뮤니티 Ingress 컨트롤러, NGINX 오픈 소스 기반 NGINX Ingress 컨트롤러 또는 NGINX Plus 기반 NGINX Ingress 컨트롤러)는 Kubernetes Pod에 배포된 백엔드 애플리케이션에 요청을 전달하고 애플리케이션에서 생성된 응답을 클라이언트에 반환했습니다. Ingress 컨트롤러의 스트레스 테스트를 위해 안정적인 클라이언트 트래픽 흐름을 생성하고 다음과 같은 성능 지표를 수집했습니다.

  • 대기 시간 – 클라이언트가 요청을 생성하고 응답을 받는 사이의 시간입니다. 우리는 백분위수 분포로 지연 시간을 보고합니다. 예를 들어, 지연 테스트에서 나온 샘플이 100개라면 99번째 백분위수에 있는 값은 100번의 테스트 실행에서 두 번째로 느린 응답 지연 시간입니다.
  • 연결 시간 초과 – Ingress 컨트롤러가 특정 시간 내에 요청에 응답하지 못해 자동으로 삭제되거나 무시되는 TCP 연결입니다.
  • 읽기 오류 – Ingress 컨트롤러의 소켓이 닫혀서 연결에서 읽기 시도가 실패합니다.
  • 연결 오류 – 클라이언트와 Ingress 컨트롤러 간에 TCP 연결이 설정되지 않았습니다.

토폴로지

모든 테스트에서 AWS의 클라이언트 머신에서 실행되는 wrk2 유틸리티를 사용하여 요청을 생성했습니다. AWS 클라이언트는 Google Kubernetes Engine(GKE) 환경의 GKE-node-1 에 Kubernetes DaemonSet 으로 배포된 Ingress 컨트롤러의 외부 IP 주소에 연결되었습니다. Ingress 컨트롤러는 SSL 종료(Kubernetes Secret 참조) 및 Layer 7 라우팅을 위해 구성되었으며 LoadBalancer 유형 의 Kubernetes 서비스 를 통해 노출되었습니다. 백엔드 애플리케이션은 GKE-node-2 에서 Kubernetes 배포 로 실행되었습니다.

클라우드 머신 유형과 소프트웨어 구성에 대한 자세한 내용은 부록을 참조하세요.

테스트 방법론

클라이언트 배포

AWS 클라이언트 머신에서 다음 wrk2 (버전 4.0.0) 스크립트를 실행했습니다. GKE에 배포된 Ingress 컨트롤러에 1000개의 연결을 설정하는 2개의 작업 스레드를 생성합니다. 3분짜리 테스트를 실행하는 동안 스크립트는 초당 30,000개의 요청(RPS)을 생성합니다. 이는 프로덕션 환경에서 Ingress 컨트롤러의 부하를 잘 시뮬레이션한 것으로 간주됩니다.

wrk -t2 -c1000 -d180s -L -R30000 https://app.example.com:443/

어디:

  • -t – 스레드 수(2)를 설정합니다.
  • -c – TCP 연결 수(1000)를 설정합니다.
  • -d – 테스트 실행 기간을 초(180 또는 3분)로 설정합니다.
  • -L – 분석 도구로 내보내기 위한 자세한 대기 시간 백분위수 정보를 생성합니다.
  • -R – RPS 수(30,000)를 설정합니다.

TLS 암호화의 경우, 2048비트 키 크기와 완벽한 전방 비밀성을 갖춘 RSA를 사용했습니다.

백엔드 애플리케이션의 각 응답( https://app.example.com:443 에서 액세스)은 기본 서버 메타데이터 약 1KB 와 함께 구성됩니다. 200OK HTTP 상태 코드.

백엔드 애플리케이션 배포

우리는 백엔드 애플리케이션의 정적 및 동적 배포를 모두 사용하여 테스트 실행을 수행했습니다.

정적 배포에서는 Pod 복제본이 5개 있었고, Kubernetes API를 사용하여 변경 사항을 적용하지 않았습니다.

동적 배포의 경우, 다음 스크립트를 사용하여 백엔드 nginx 배포를 주기적으로 Pod 복제본 5개에서 7개로 늘리고, 그다음에는 5개로 줄였습니다. 이는 동적인 Kubernetes 환경을 에뮬레이트하고 Ingress 컨트롤러가 엔드포인트 변경에 얼마나 효과적으로 적응하는지 테스트합니다.

while [ 1 -eq 1 ]
do
kubectl 스케일 배포 nginx --replicas=5
sleep 12
kubectl 스케일 배포 nginx --replicas=7
sleep 10
done

성과 결과

정적 배포에 대한 대기 시간 결과

그래프에 표시된 대로 세 개의 Ingress 컨트롤러 모두 백엔드 애플리케이션의 정적 배포를 통해 비슷한 성능을 달성했습니다. 이는 모두 NGINX 오픈 소스를 기반으로 하며 정적 배포에 Ingress 컨트롤러의 재구성이 필요하지 않다는 점을 고려하면 합리적입니다.

동적 배포에 대한 대기 시간 결과

그래프는 주기적으로 백엔드 애플리케이션을 5개의 복제본 Pod에서 7개로 확장하고 다시 그 반대로 확장하는 동적 배포에서 각 Ingress 컨트롤러에서 발생한 지연 시간을 보여줍니다(자세한 내용은 백엔드 애플리케이션 배포 참조).

이 환경에서 우수한 성능을 보이는 것은 NGINX Plus 기반 Ingress 컨트롤러뿐이며, 99.99번째 백분위수까지 지연 시간이 사실상 전혀 발생하지 않습니다. 커뮤니티와 NGINX 오픈 소스 기반 Ingress 컨트롤러는 모두 비교적 낮은 백분위수에서 눈에 띄는 지연 시간을 경험하지만 패턴은 다소 다릅니다. 커뮤니티 Ingress 컨트롤러의 경우 지연 시간은 99번째 백분위수까지 완만하지만 꾸준히 증가한 후 약 5000ms(5초)에서 안정화됩니다. NGINX 오픈 소스 기반 Ingress 컨트롤러의 경우 지연 시간은 99번째 백분위수에서는 약 32초로 급격히 증가하고, 99.99번째 백분위수에서는 다시 60초로 증가합니다 .

동적 배포에 대한 시간 초과 및 오류 결과 에서 더 자세히 논의하겠지만, 커뮤니티와 NGINX 오픈 소스 기반 Ingress 컨트롤러에서 발생하는 지연은 백엔드 애플리케이션의 엔드포인트 변경에 대한 응답으로 NGINX 구성이 업데이트되고 다시 로드된 후에 발생하는 오류 및 시간 초과로 인해 발생합니다.

이전 그래프와 동일한 테스트 조건에서 커뮤니티 Ingress 컨트롤러와 NGINX Plus 기반 Ingress 컨트롤러의 결과를 보다 세부적으로 나타낸 것입니다. NGINX Plus 기반 Ingress 컨트롤러는 99.99번째 백분위수까지는 사실상 지연 시간이 발생하지 않으며, 99.9999번째 백분위수에서는 254ms에 가까워집니다. 커뮤니티 Ingress 컨트롤러의 지연 패턴은 99번째 백분위수에서 지연 시간이 5000ms로 꾸준히 증가한 후, 이 지점에서 지연 시간이 수평을 이룹니다.

동적 배포에 대한 시간 초과 및 오류 결과

이 표는 지연 결과의 원인을 더 자세히 보여줍니다.

  NGINX 오픈소스 지역 사회 NGINX 플러스
연결 오류 33365 0 0
연결 시간 초과 309 8809 0
읽기 오류 4650 0 0

NGINX 오픈 소스 기반 Ingress 컨트롤러를 사용하는 경우 백엔드 애플리케이션의 엔드포인트가 변경될 때마다 NGINX 구성을 업데이트하고 다시 로드해야 하므로 연결 오류, 연결 시간 초과, 읽기 오류가 많이 발생합니다. 연결/소켓 오류는 NGINX가 다시 로드되는 데 걸리는 짧은 시간 동안 발생하는데, 이는 클라이언트가 더 이상 NGINX 프로세스에 할당되지 않은 소켓에 연결하려고 할 때 발생합니다. 연결 시간 초과는 클라이언트가 Ingress 컨트롤러에 연결을 설정했지만 백엔드 엔드포인트를 더 이상 사용할 수 없는 경우 발생합니다. 오류와 시간 초과는 모두 지연 시간에 심각한 영향을 미쳐, 99번째 백분위수에서는 32초로 급증하고, 99.99번째에서는 다시 60초로 급증합니다 .

커뮤니티 Ingress 컨트롤러를 사용하는 경우 백엔드 애플리케이션이 확장되거나 축소됨에 따라 엔드포인트가 변경되어 연결 시간 초과가 8,809번 발생했습니다. 커뮤니티 Ingress 컨트롤러는 엔드포인트가 변경될 때 구성이 다시 로드되는 것을 방지하기 위해 Lua 코드를 사용합니다 . 결과에 따르면 NGINX 내부에서 Lua 핸들러를 실행하여 엔드포인트 변경을 감지하면 엔드포인트를 변경할 때마다 구성을 다시 로드해야 하는 요구 사항으로 인해 발생하는 NGINX 오픈 소스 기반 버전의 일부 성능 제한을 해결할 수 있습니다. 그럼에도 불구하고 연결 시간 초과는 계속 발생하며 높은 백분위수에서는 상당한 지연이 발생합니다.

NGINX Plus 기반 Ingress 컨트롤러를 사용했을 때는 오류나 시간 초과가 발생하지 않았습니다. 동적 환경이 성능에 미치는 영향이 사실상 전혀 없었습니다. 이는 엔드포인트가 변경될 때 NGINX Plus API를 사용하여 NGINX 구성을 동적으로 업데이트하기 때문입니다. 앞서 언급했듯이 가장 높은 지연 시간은 254ms였으며 99.9999 백분위수에서만 발생했습니다.

결론

성능 결과에 따르면 동적인 Kubernetes 클라우드 환경에서 시간 초과 및 오류를 완전히 없애려면 Ingress 컨트롤러가 이벤트 핸들러나 구성 다시 로드 없이 백엔드 엔드포인트의 변경 사항에 동적으로 조정되어야 합니다. 결과를 바탕으로, NGINX Plus API는 동적 환경에서 NGINX를 동적으로 재구성하는 데 최적의 솔루션이라고 말할 수 있습니다. 저희의 테스트에 따르면 NGINX Plus 기반 Ingress 컨트롤러만이 사용자를 만족시키는 데 필요한 매우 동적인 Kubernetes 환경에서 완벽한 성능을 달성했습니다.

충수

클라우드 머신 사양

기계 클라우드 제공자 기계 유형
고객 한국어: AWS m5a.4xlarge
GKE-노드-1 지씨피(GCP) e2-스탠다드-32
GKE-노드-2 지씨피(GCP) e2-스탠다드-32

NGINX 오픈 소스 및 NGINX Plus Ingress 컨트롤러에 대한 구성

쿠버네티스 구성

apiVersion: apps/v1
종류: DaemonSet
메타데이터:
이름: nginx-ingress
네임스페이스: nginx-ingress
스펙:
선택자:
matchLabels:
앱: nginx-ingress
템플릿:
메타데이터:
레이블:
앱: nginx-ingress
#주석:
#prometheus.io/scrape: "참"
#prometheus.io/포트: "9113"
사양:
serviceAccountName: nginx-ingress
노드 선택자:
kubernetes.io/호스트 이름: gke-rawdata-cluster-default-pool-3ac53622-6nzr
호스트 네트워크: true 
컨테이너:
- 이미지: gcr.io/nginx-demos/nap-ingress:edge
imagePullPolicy: 항상
이름: nginx-plus-ingress
포트:
- 이름: http
컨테이너 포트: 80
호스트 포트: 80
- 이름: https
컨테이너 포트: 443
호스트 포트: 443
- 이름: 준비 포트
컨테이너 포트: 8081
#- 이름: 프로메테우스
#컨테이너포트: 9113
준비 프로브:
httpGet:
경로: /nginx-ready
포트: 준비 포트
기간 초: 1
securityContext:
allowPrivilegeEscalation: true
runAsUser: 101 #nginx
기능:
삭제:
- ALL
추가:
- NET_BIND_SERVICE
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
args:
- -nginx-plus
- -nginx-configmaps=$(POD_NAMESPACE)/nginx-config
- -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret

참고사항:

  • 이 구성은 NGINX Plus용입니다. NGINX 오픈 소스 구성에서 nginx‑plus 에 대한 참조가 필요에 따라 조정되었습니다.
  • NGINX App Protect가 이미지에 포함되어 있지만( gcr.io/nginx-demos/nap-ingress:edge ) 비활성화되어 있습니다( -enable-app-protect 플래그가 생략됨).

컨피그맵

친절한: ConfigMap
apiVersion: v1
메타데이터:
이름: nginx-config
네임스페이스: nginx-ingress
데이터:
작업자 연결: "10000"
작업자-rlimit-nofile: "10240"
keepalive: "100"
keepalive-요청: "100000000"

커뮤니티 NGINX Ingress Controller 구성

쿠버네티스 구성

apiVersion: apps/v1
종류: DaemonSet
메타데이터:
레이블:
helm.sh/chart: ingress-nginx-2.11.1
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.34.1
    app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: 컨트롤러
이름: ingress-nginx-controller
네임스페이스: ingress-nginx
spec:
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: 컨트롤러
템플릿:
메타데이터:
레이블:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: 컨트롤러
spec:
nodeSelector: 
kubernetes.io/hostname: gke-rawdata-cluster-default-pool-3ac53622-6nzr
hostNetwork: true
컨테이너:
- 이름: 컨트롤러
이미지: us.gcr.io/k8s-artifacts-prod/ingress-nginx/컨트롤러:v0.34.1@sha256:0e072dddd1f7f8fc8909a2ca6f65e76c5f0d2fcfb8be47935ae3457e8bbceb20
imagePullPolicy: IfNotPresent
라이프사이클:
preStop:
exec:
명령:
- /wait-shutdown
인수:
- /nginx-ingress-controller
- --election-id=ingress-controller-leader
- --ingress-class=nginx
- --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
- --validating-webhook=:8443
- --validating-webhook-certificate=/usr/local/certificates/cert
- --validating-webhook-key=/usr/local/certificates/key
securityContext:
기능:
삭제:
- 모두
추가:
- NET_BIND_SERVICE
runAsUser: 101
allowPrivilegeEscalation: 참
환경:
- 이름: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
readinessProbe:
httpGet:
경로: /healthz
포트: 10254
계획: HTTP
기간초: 1
포트:
- 이름: http
컨테이너 포트: 80
프로토콜: TCP
- 이름: https
컨테이너 포트: 443
프로토콜: TCP
- 이름: 웹훅
컨테이너 포트: 8443
프로토콜: TCP
볼륨 마운트:
- 이름: 웹훅-인증서
마운트 경로: /usr/local/certificates/
읽기 전용: 참
서비스 계정 이름: ingress-nginx
종료 유예 기간 초: 300
볼륨:
- 이름: webhook-cert
비밀:
secretName: ingress-nginx-admission

컨피그맵

apiVersion: v1
종류: ConfigMap
메타데이터:
이름: ingress-nginx-controller
네임스페이스: ingress-nginx
데이터:
최대 작업자 연결: "10000"
최대-작업자-열린-파일: "10204"
업스트림-keepalive-연결: "100"
keep-alive-requests: "100000000"

백엔드 앱 구성

쿠버네티스 구성

apiVersion: apps/v1
종류: 배포
메타데이터:
이름: nginx
스펙:
선택자:
matchLabels:
앱: nginx 
템플릿:
메타데이터:
레이블:
앱: nginx
스펙:
노드 선택자:
kubernetes.io/호스트 이름: gke-rawdata-cluster-default-pool-3ac53622-t2dz 
컨테이너:
- 이름: nginx
이미지: nginx
포트:
- 컨테이너 포트: 8080
볼륨 마운트: 
- 이름: main-config-volume
마운트 경로: /etc/nginx
- 이름: app-config-volume
마운트 경로: /etc/nginx/conf.d
readinessProbe:
httpGet:
경로: /healthz
포트: 8080
기간초: 3
볼륨: 
- 이름: main-config-volume
configMap:
이름: main-conf
- 이름: app-config-volume
configMap: 
이름: app-conf
---

컨피그맵

apiVersion: v1
종류: ConfigMap
메타데이터:
이름: main-conf
네임스페이스: default
데이터:
nginx.conf: |+
사용자 nginx;
작업자 프로세스 16;
작업자_rlimit_nofile 102400;
작업자_cpu_affinity 자동 1111111111111111;
오류 로그 /var/log/nginx/error.log 알림;
pid /var/run/nginx.pid;

이벤트 {
작업자 연결 100000;
}

http {

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

sendfile 켜짐;
tcp_nodelay 켜짐;

access_log 꺼짐;

/etc/nginx/conf.d/*.conf 포함;
}

---

apiVersion: v1
종류: ConfigMap
메타데이터:
이름: app-conf
네임스페이스: 기본
데이터:
app.conf: "server {listen 8080;location / {default_type text/plain;expires -1;return 200 '서버 주소: $server_addr:$server_port\n서버 이름:$hostname\n날짜: $time_local\nURI: $request_uri\n요청 ID: $request_id\n';}location /healthz {return 200 '저는 행복하고 건강합니다 :)';}}"
---

서비스

apiVersion: v1
종류: 서비스
메타데이터:
이름: app-svc
사양:
포트:
- 포트: 80
대상 포트: 8080
프로토콜: TCP
이름: http
선택자:
앱: nginx
---

"이 블로그 게시물에는 더 이상 사용할 수 없거나 더 이상 지원되지 않는 제품이 참조될 수 있습니다. 사용 가능한 F5 NGINX 제품과 솔루션에 대한 최신 정보를 보려면 NGINX 제품군을 살펴보세요. NGINX는 이제 F5의 일부가 되었습니다. 이전의 모든 NGINX.com 링크는 F5.com의 유사한 NGINX 콘텐츠로 리디렉션됩니다."