편집자 - 이 게시물은 10부작 시리즈의 일부입니다.
또한 전체 블로그 세트를 무료 전자책인 ' 테스트에서 프로덕션까지 Kubernetes 활용' 으로 다운로드할 수 있습니다.
점점 더 많은 기업이 컨테이너화된 앱을 프로덕션에서 실행함에 따라, Kubernetes는 컨테이너 오케스트레이션을 위한 표준 도구로서의 입지를 더욱 공고히 하고 있습니다. 동시에, COVID-19 팬데믹으로 인해 재택근무 가 늘어나 인터넷 트래픽이 급증하면서 클라우드 컴퓨팅에 대한 수요도 몇 년 앞당겨졌습니다. 고객들이 심각한 네트워크 중단과 과부하를 겪고 있기 때문에 기업들은 자사 인프라를 신속하게 업그레이드하기 위해 노력하고 있습니다.
클라우드 기반 마이크로서비스 환경에서 필요한 수준의 성능을 달성하려면 차세대 하이퍼스케일 데이터 센터의 확장성과 성능을 활용하는 빠르고 완전히 동적인 소프트웨어가 필요합니다. 컨테이너를 관리하기 위해 Kubernetes를 사용하는 많은 조직은 NGINX 기반 Ingress 컨트롤러를 사용하여 사용자에게 앱을 제공합니다.
이 블로그에서는 현실적인 멀티 클라우드 환경에서 세 개의 NGINX Ingress 컨트롤러에 대한 성능 테스트 결과를 보고합니다. 인터넷에서 클라이언트 연결의 지연 시간을 측정합니다.
우리는 부하 생성 프로그램 wrk2를
사용하여 클라이언트를 에뮬레이션하여 정의된 기간 동안 HTTPS를 통해 지속적인 요청을 만들었습니다. 테스트 중인 Ingress 컨트롤러(커뮤니티 Ingress 컨트롤러, NGINX 오픈 소스 기반 NGINX Ingress 컨트롤러 또는 NGINX Plus 기반 NGINX Ingress 컨트롤러)는 Kubernetes Pod에 배포된 백엔드 애플리케이션에 요청을 전달하고 애플리케이션에서 생성된 응답을 클라이언트에 반환했습니다. Ingress 컨트롤러의 스트레스 테스트를 위해 안정적인 클라이언트 트래픽 흐름을 생성하고 다음과 같은 성능 지표를 수집했습니다.
모든 테스트에서 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 와 함께 구성됩니다. 200
OK
HTTP 상태 코드.
우리는 백엔드 애플리케이션의 정적 및 동적 배포를 모두 사용하여 테스트 실행을 수행했습니다.
정적 배포에서는 Pod 복제본이 5개 있었고, Kubernetes API를 사용하여 변경 사항을 적용하지 않았습니다.
동적 배포의 경우, 다음 스크립트를 사용하여 백엔드 nginx 배포를 주기적으로 Pod 복제본 5개에서 7개로 늘리고, 그다음에는 5개로 줄였습니다. 이는 동적인 Kubernetes 환경을 에뮬레이트하고 Ingress 컨트롤러가 엔드포인트 변경에 얼마나 효과적으로 적응하는지 테스트합니다.
while [ 1 -eq 1 ]
do
kubectl scale deployment nginx --replicas=5
sleep 12
kubectl scale deployment 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 |
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nginx-ingress
namespace: nginx-ingress
spec:
selector:
matchLabels:
app: nginx-ingress
template:
metadata:
labels:
app: nginx-ingress
#annotations:
#prometheus.io/scrape: "true"
#prometheus.io/port: "9113"
spec:
serviceAccountName: nginx-ingress
nodeSelector:
kubernetes.io/hostname: gke-rawdata-cluster-default-pool-3ac53622-6nzr
hostNetwork: true
containers:
- image: gcr.io/nginx-demos/nap-ingress:edge
imagePullPolicy: Always
name: nginx-plus-ingress
ports:
- name: http
containerPort: 80
hostPort: 80
- name: https
containerPort: 443
hostPort: 443
- name: readiness-port
containerPort: 8081
#- name: prometheus
#containerPort: 9113
readinessProbe:
httpGet:
path: /nginx-ready
port: readiness-port
periodSeconds: 1
securityContext:
allowPrivilegeEscalation: true
runAsUser: 101 #nginx
capabilities:
drop:
- ALL
add:
- 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
에 대한 참조가 필요에 따라 조정되었습니다.gcr.io/nginx-demos/nap-ingress:edge
) 비활성화되어 있습니다( -enable-app-protect
플래그가 생략됨).kind: ConfigMap
apiVersion: v1
metadata:
name: nginx-config
namespace: nginx-ingress
data:
worker-connections: "10000"
worker-rlimit-nofile: "10240"
keepalive: "100"
keepalive-requests: "100000000"
apiVersion: apps/v1
kind: DaemonSet
metadata:
labels:
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: controller
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
template:
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
spec:
nodeSelector:
kubernetes.io/hostname: gke-rawdata-cluster-default-pool-3ac53622-6nzr
hostNetwork: true
containers:
- name: controller
image: us.gcr.io/k8s-artifacts-prod/ingress-nginx/controller:v0.34.1@sha256:0e072dddd1f7f8fc8909a2ca6f65e76c5f0d2fcfb8be47935ae3457e8bbceb20
imagePullPolicy: IfNotPresent
lifecycle:
preStop:
exec:
command:
- /wait-shutdown
args:
- /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:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
runAsUser: 101
allowPrivilegeEscalation: true
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
readinessProbe:
httpGet:
path: /healthz
port: 10254
scheme: HTTP
periodSeconds: 1
ports:
- name: http
containerPort: 80
protocol: TCP
- name: https
containerPort: 443
protocol: TCP
- name: webhook
containerPort: 8443
protocol: TCP
volumeMounts:
- name: webhook-cert
mountPath: /usr/local/certificates/
readOnly: true
serviceAccountName: ingress-nginx
terminationGracePeriodSeconds: 300
volumes:
- name: webhook-cert
secret:
secretName: ingress-nginx-admission
apiVersion: v1
kind: ConfigMap
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
data:
max-worker-connections: "10000"
max-worker-open-files: "10204"
upstream-keepalive-connections: "100"
keep-alive-requests: "100000000"
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
nodeSelector:
kubernetes.io/hostname: gke-rawdata-cluster-default-pool-3ac53622-t2dz
containers:
- name: nginx
image: nginx
ports:
- containerPort: 8080
volumeMounts:
- name: main-config-volume
mountPath: /etc/nginx
- name: app-config-volume
mountPath: /etc/nginx/conf.d
readinessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 3
volumes:
- name: main-config-volume
configMap:
name: main-conf
- name: app-config-volume
configMap:
name: app-conf
---
apiVersion: v1
kind: ConfigMap
metadata:
name: main-conf
namespace: default
data:
nginx.conf: |+
user nginx;
worker_processes 16;
worker_rlimit_nofile 102400;
worker_cpu_affinity auto 1111111111111111;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 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 on;
tcp_nodelay on;
access_log off;
include /etc/nginx/conf.d/*.conf;
}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: app-conf
namespace: default
data:
app.conf: "server {listen 8080;location / {default_type text/plain;expires -1;return 200 'Server address: $server_addr:$server_port\nServer name:$hostname\nDate: $time_local\nURI: $request_uri\nRequest ID: $request_id\n';}location /healthz {return 200 'I am happy and healthy :)';}}"
---
apiVersion: v1
kind: Service
metadata:
name: app-svc
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: nginx
---
"이 블로그 게시물에는 더 이상 사용할 수 없거나 더 이상 지원되지 않는 제품이 참조될 수 있습니다. 사용 가능한 F5 NGINX 제품과 솔루션에 대한 최신 정보를 보려면 NGINX 제품군을 살펴보세요. NGINX는 이제 F5의 일부가 되었습니다. 이전의 모든 NGINX.com 링크는 F5.com의 유사한 NGINX 콘텐츠로 리디렉션됩니다."