편집자 – 이 글은 총 10편으로 구성된 시리즈의 일부입니다:
- 프로덕션급 Kubernetes로 복잡성 줄이기
- 고급 트래픽 관리로 Kubernetes의 복원력을 향상하는 방법
- Kubernetes에서 가시성을 개선하는 방법
- 트래픽 관리 도구를 사용하여 Kubernetes를 보호하는 6가지 방법
- 인그레스 컨트롤러 선택 가이드 1부: 요구 사항 파악하기
- Ingress 컨트롤러 선택 가이드, 2부: 위험 요소와 미래 대비
- Ingress 컨트롤러 선택 가이드, 3부: 오픈 소스 vs. 기본 제공 vs. 상용
- Ingress 컨트롤러 선택 가이드, 4부: NGINX Ingress 컨트롤러 옵션
- 서비스 메시를 선택하는 방법
- 동적 Kubernetes 클라우드 환경에서 NGINX Ingress Controller 성능 테스트하기(이 게시물)
전체 블로그 시리즈를 무료 eBook으로 다운로드할 수도 있습니다. Kubernetes를 테스트에서 프로덕션으로 전환하기.
프로덕션 환경에서 컨테이너화된 애플리케이션을 실행하는 기업이 늘어나면서 Kubernetes는 컨테이너 오케스트레이션의 표준 도구로서 입지를 계속 공고히 하고 있습니다. 동시에 COVID‑19 팬데믹으로 촉발된 재택근무 확산이 인터넷 트래픽 증가를 가속화하면서 클라우드 컴퓨팅 수요가 예상보다 몇 년 앞당겨졌습니다. 고객들이 대규모 네트워크 장애와 과부하를 겪고 있어 기업들은 인프라를 신속하게 업그레이드하고 있습니다.
클라우드 기반 마이크로서비스 환경에서 요구되는 수준의 성능을 달성하려면 차세대 하이퍼스케일 데이터 센터의 확장성과 성능을 활용하는 빠르고 완전히 동적인 소프트웨어가 필요합니다. Kubernetes를 사용하여 컨테이너를 관리하는 많은 조직은 사용자에게 애플리케이션을 제공하기 위해 NGINX 기반 Ingress 컨트롤러를 사용합니다.
이 블로그에서는 실제와 유사한 멀티 클라우드 환경에서 인터넷을 통한 클라이언트 연결의 지연 시간을 측정하여 세 가지 NGINX Ingress 컨트롤러의 성능을 테스트한 결과를 소개합니다:
- Kubernetes 커뮤니티에서 유지 관리하며 NGINX Open Source를 기반으로 하는 NGINX Ingress Controller입니다. 이전 블로그에서와 마찬가지로 여기서는 이를 커뮤니티 Ingress 컨트롤러라고 부릅니다. Google Container Registry에서 가져온 이미지를 사용하여 버전 0.34.1을 테스트했습니다.
- NGINX Open Source 기반 NGINX Ingress Controller, NGINX에서 유지 관리하는 버전 1.8.0.
- NGINX Plus 기반 NGINX Ingress Controller, NGINX에서 유지 관리하는 버전 1.8.0.
테스트 프로토콜 및 수집된 메트릭
부하 생성 프로그램 wrk2를 사용하여 클라이언트를 에뮬레이션하고, 정해진 기간 동안 HTTPS를 통해 지속적으로 요청을 보냈습니다. 테스트 대상 Ingress 컨트롤러(커뮤니티 Ingress 컨트롤러, NGINX Open Source 기반 NGINX Ingress Controller 또는 NGINX Plus 기반 NGINX Ingress Controller)는 요청을 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 참조) 및 레이어 7 라우팅을 수행하도록 구성되었으며, Kubernetes Service를 통해 Type LoadBalancer로 노출되었습니다. 백엔드 애플리케이션은 GKE-node-2에서 Kubernetes Deployment로 실행되었습니다.

클라우드 머신 유형 및 소프트웨어 구성에 대한 자세한 내용은 부록을 참조하세요.
테스트 방법론
클라이언트 배포
AWS 클라이언트 머신에서 다음 wrk2(버전 4.0.0) 스크립트를 실행했습니다. 이 스크립트는 2개의 wrk 스레드를 생성하며, 이 스레드들은 GKE에 배포된 Ingress 컨트롤러에 총 1,000개의 연결을 설정합니다. 각 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 Open Source를 기반으로 하며 정적 배포에서는 Ingress 컨트롤러의 재구성이 필요하지 않다는 점을 고려하면 이는 당연한 결과입니다.

동적 배포의 지연 시간 결과
이 그래프는 백엔드 애플리케이션의 복제본 Pod 수를 주기적으로 5개에서 7개로 늘렸다가 다시 줄이는 동적 배포에서 각 Ingress 컨트롤러에 발생한 지연 시간을 보여줍니다(자세한 내용은 백엔드 애플리케이션 배포 참조).
이 환경에서는 NGINX Plus 기반 Ingress 컨트롤러만 우수한 성능을 보이며, 99.99번째 백분위수까지 지연 시간이 거의 발생하지 않는다는 점이 분명합니다. 커뮤니티 Ingress 컨트롤러와 NGINX Open Source 기반 Ingress 컨트롤러는 모두 비교적 낮은 백분위수부터 눈에 띄는 지연 시간이 발생하지만, 그 양상은 상당히 다릅니다. 커뮤니티 Ingress 컨트롤러의 경우 지연 시간이 완만하지만 꾸준히 증가하다가 99번째 백분위수에서 약 5000ms(5초) 수준으로 유지됩니다. NGINX Open Source 기반 Ingress 컨트롤러의 경우 지연 시간이 99번째 백분위수에서 약 32초로 급격히 치솟고, 99.99번째 백분위수에서는 다시 60초로 증가합니다.
자세한 내용은 동적 배포의 시간 초과 및 오류 결과에서 설명하듯이, 커뮤니티 및 NGINX Open Source 기반 Ingress 컨트롤러에서 발생하는 지연 시간은 백엔드 애플리케이션의 엔드포인트 변경에 대응하여 NGINX 구성이 업데이트되고 다시 로드된 후 발생하는 오류와 시간 초과로 인한 것입니다.

다음은 이전 그래프와 동일한 테스트 조건에서 커뮤니티 Ingress 컨트롤러와 NGINX Plus 기반 Ingress 컨트롤러의 결과를 더 세분화해 보여주는 내용입니다. NGINX Plus 기반 Ingress 컨트롤러는 99.99백분위수까지 사실상 지연 시간이 발생하지 않으며, 이후 증가하기 시작해 99.9999백분위수에서 254ms에 근접합니다. 커뮤니티 Ingress 컨트롤러의 지연 시간은 꾸준히 증가하여 99백분위수에서 5000ms에 도달한 후 더 이상 증가하지 않습니다.

동적 배포의 타임아웃 및 오류 결과
이 표는 지연 시간 결과의 원인을 더 자세히 보여줍니다.
NGINX Open Source 커뮤니티 NGINX Plus연결 오류 33365 0 0연결 시간 초과 309 8809 0읽기 오류 4650 0 0
NGINX Open Source 기반 Ingress 컨트롤러에서는 백엔드 애플리케이션의 엔드포인트가 변경될 때마다 NGINX 구성을 업데이트하고 다시 로드해야 하므로 많은 연결 오류, 연결 시간 초과 및 읽기 오류가 발생합니다. 연결/소켓 오류는 NGINX가 다시 로드되는 짧은 시간 동안 클라이언트가 더 이상 NGINX 프로세스에 할당되지 않은 소켓에 연결하려고 할 때 발생합니다. 연결 시간 초과는 클라이언트가 Ingress 컨트롤러에 연결을 설정했지만 백엔드 엔드포인트를 더 이상 사용할 수 없을 때 발생합니다. 오류와 시간 초과 모두 지연 시간에 심각한 영향을 미쳐 99번째 백분위수에서 32초까지 급증하고, 99.99번째 백분위수에서는 다시 60초까지 치솟습니다.
커뮤니티 Ingress 컨트롤러에서는 백엔드 애플리케이션이 확장 및 축소되면서 엔드포인트가 변경되어 8,809건의 연결 시간 초과가 발생했습니다. 커뮤니티 Ingress 컨트롤러는 엔드포인트가 변경될 때 구성을 다시 로드하지 않도록 Lua 코드를 사용합니다. 결과에 따르면 NGINX 내에서 Lua 핸들러를 실행하여 엔드포인트 변경을 감지하면 엔드포인트가 변경될 때마다 구성을 다시 로드해야 하는 NGINX Open Source 기반 버전의 일부 성능 한계를 해결할 수 있습니다. 그럼에도 불구하고 연결 시간 초과는 여전히 발생하며 상위 백분위수에서 상당한 지연 시간을 초래합니다.
NGINX Plus 기반 Ingress 컨트롤러에서는 오류나 시간 초과가 발생하지 않았으며, 동적 환경이 성능에 미치는 영향은 사실상 없었습니다. 이는 엔드포인트가 변경될 때 NGINX Plus API를 사용하여 NGINX 구성을 동적으로 업데이트하기 때문입니다. 앞서 언급했듯이 최대 지연 시간은 254ms였으며, 99.9999 백분위수에서만 발생했습니다.
결론
성능 결과에 따르면 동적인 Kubernetes 클라우드 환경에서 시간 초과와 오류를 완전히 없애려면 Ingress 컨트롤러가 이벤트 핸들러나 구성 다시 로드 없이 백엔드 엔드포인트의 변경 사항에 맞춰 동적으로 조정되어야 합니다. 이러한 결과를 바탕으로 NGINX Plus API가 동적 환경에서 NGINX를 동적으로 재구성하기 위한 최적의 솔루션이라고 할 수 있습니다. 테스트 결과, 사용자의 만족도를 유지하는 데 필요한 고도의 동적 Kubernetes 환경에서 완벽한 성능을 달성한 것은 NGINX Plus 기반 Ingress 컨트롤러뿐이었습니다.
부록
클라우드 머신 사양
머신 클라우드 제공업체 머신 유형Client AWS m5a.4xlargeGKE-node-1 GCP e2-standard-32GKE-node-2 GCP e2-standard-32
NGINX Open Source 및 NGINX Plus Ingress Controller 구성
Kubernetes 구성
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용입니다.
nginx‑plus에 대한 참조는 NGINX Open Source용 구성에서 필요에 따라 조정되었습니다. - NGINX App Protect는 이미지(
gcr.io/nginx-demos/nap-ingress:edge)에 포함되어 있지만 비활성화되어 있습니다(-enable-app-protect플래그가 생략됨).
ConfigMap
kind: ConfigMap
apiVersion: v1
metadata:
name: nginx-config
namespace: nginx-ingress
data:
worker-connections: "10000"
worker-rlimit-nofile: "10240"
keepalive: "100"
keepalive-requests: "100000000"커뮤니티 NGINX Ingress Controller 구성
Kubernetes 구성
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-admissionConfigMap
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"백엔드 앱 구성
Kubernetes 구성
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
---ConfigMaps
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_portnServer name:$hostnamenDate: $time_localnURI: $request_urinRequest ID: $request_idn';}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
---작성자 소개
관련 블로그 게시물
시청하기: KubeCon North America 2023에서의 NGINX Gateway Fabric
이 영상에서는 KubeCon의 NGINX 부스를 찾아가, 저희가 공개한 Kubernetes Gateway API 구현을 살펴봅니다.
F5 NGINX로 대규모 WAF 플릿을 확인하고 관리하는 2가지 방법
앱 보안을 강화하는 두 가지 방법을 소개합니다. NGINX Management Suite를 통해 WAF 운영에 대한 가시성을 높이고, NGINX App Protect WAF를 활용해 코드형 보안을 구현하는 것입니다.
Ingress 컨트롤러 선택 가이드, 4부: NGINX Ingress Controller 옵션
개발 주체, 개발 철학, 프로덕션 환경 준비도, 보안 및 지원을 기준으로 어떤 NGINX Ingress 컨트롤러가 가장 적합한지 알아보세요.
진화하는 공격 환경에 NGINX App Protect Denial of Service가 적응하는 방법
NGINX App Protect Denial of Service(DoS)가 계층 7 DoS 공격을 방어하기 위해 완화 방법을 실시간으로 조정하는 방식을 설명합니다. 정상적인 사용자 행동과 서버 상태에 대한 모델을 생성하여 공격을 나타내는 이상 징후를 감지하고 맞춤형 완화 시그니처를 적용할 수 있습니다.
NGINX Instance Manager를 활용한 대규모 구성 업데이트 및 인증서 관리
NGINX Instance Manager 버전 1.0에는 두 가지 새로운 기능이 도입되었습니다. NGINX 인스턴스에 태그와 사용자 역할을 지정하면 전체 그룹에 구성을 한 번에 적용할 수 있습니다. 인증서 검사는 만료 예정인 인증서를 감지하여 서비스 중단을 방지할 수 있도록 인증서를 교체할 수 있게 해줍니다.
Netflix가 자사 CDN의 핵심으로 NGINX를 선택한 이유
Netflix가 글로벌 콘텐츠 전송 네트워크인 Open Connect의 스트리밍 미디어 기술로 NGINX를 선택한 이유를 NGINX 개발자 Gleb Smirnoff에게서 알아보세요.
