현업에서 네트워크 이슈 딥다이브 중 공부한 curl 을 사용한 통신 과정에 대해 블로그에 정리해둡니다.
curl
- 명령줄(CLI)에서 다양한 프로토콜을 이용해 데이터를 전송할 수 있는 도구.
- Client for URL
- 브라우저의 역할을 GUI 없이 텍스트 기반 명령줄에서 수행해줌.
클라이언트 ↔ 서버 통신 과정
네트워크 구성: NLB -> Istio Ingres Gateway -> EKS Pod
여기서 클라이언트는 curl, 서버는 NLB, Istio Ingress Gateway Pod, 실제 애플리케이션 Pod
- curl 로 보낸 요청은 최종 목적지인 애플리케이션 파드로 전달됨. 요청은 여러 단계의 서버(프록시)를 거쳐 전달됩니다.
- curl -> NLB
- DNS 는 도메인을 NLB 의 공인 IP 주소로 알려줌
- curl 은 이 IP 주소와 TCP 연결을 맺음.
- NLB 는 Layer 4 장비로, HTTP 요청을 들여다보지 않고 다음 서버(프록시)로 전달
- NLB -> Istio Ingress Gateway Pod
- 현재 구성에서는 NLB 에 ACM 이 등록되어 있어 TLS 협상 대상이 NLB 임.
- NLB 는 외부 트래픽을 TLS 프로토콜로 받고, 직접 클라이언트와 TLS Handshake 를 수행하여 트래픽을 복호화함.
- NLB 는 받은 요청을 복호화하여 일반 HTTP 트래픽을 타겟 그룹(istio ingress gateway) 으로 전달함
- Istio proxy(Istio ingress gateway) 는 전달받은 요청을 등록된 Gateway. Virtualservice 를 보고 요청을 최종 목적지인 내부 서비스로 라우팅함
- Istio Ingress Gateway Pod -> application pod
- 요청이 파드 안에서 실행 중인 애플리케이션 컨테이너로 도착
- 해당 애플리케이션에서 비즈니스 로직을 수행하고 최종 응답을 생성함.

[175.41.240.159] [2025-09-02 15:54:46.3N] [REQ-17535] [SUCCESS] [HTTP-200] connect:0.048334 appconnect:0.141238 starttransfer:0.185948 total:0.186507 ver:1.1
사용한 스크립트 일부:
# 본문은 버리고(-o /dev/null), -w 로 메트릭만 수집
metrics=$(curl -o /dev/null -sS \
--connect-timeout "$TIMEOUT" \
--max-time "$TIMEOUT" \
--http1.1 \
-w "$CURL_FMT" \
"$API_URL")
curl_exit_code=$?
end_ms=$(date +%s%3N)
actual_duration=$(echo "scale=3; ($end_ms - $start_ms) / 1000" | bc -l)
# 파싱
remote_ip=$(awk '{for(i=1;i<=NF;i++) if($i ~ /^remote_ip:/){sub("remote_ip:","",$i); print $i}}' <<< "$metrics")
http_code=$(awk '{for(i=1;i<=NF;i++) if($i ~ /^http_code:/){sub("http_code:","",$i); print $i}}' <<< "$metrics")
connect_t=$(awk '{for(i=1;i<=NF;i++) if($i ~ /^connect:/){sub("connect:","",$i); print $i}}' <<< "$metrics")
appconnect_t=$(awk '{for(i=1;i<=NF;i++) if($i ~ /^appconnect:/){sub("appconnect:","",$i); print $i}}' <<< "$metrics")
starttransfer_t=$(awk '{for(i=1;i<=NF;i++) if($i ~ /^starttransfer:/){sub("starttransfer:","",$i); print $i}}' <<< "$metrics")
total_t=$(awk '{for(i=1;i<=NF;i++) if($i ~ /^total:/){sub("total:","",$i); print $i}}' <<< "$metrics")
http_ver=$(awk '{for(i=1;i<=NF;i++) if($i ~ /^http_version:/){sub("http_version:","",$i); print $i}}' <<< "$metrics")
# 숫자 보정 및 total 보정
is_number "$connect_t" || connect_t=0
is_number "$appconnect_t" || appconnect_t=0
is_number "$starttransfer_t" || starttransfer_t=0
if ! is_number "$total_t"; then total_t=0; fi
# -w 가 0/빈값인 경우(특히 TIMEOUT) 실제 측정값으로 대체
if (( $(echo "$total_t == 0" | bc -l) )); then total_t="$actual_duration"; fi
[[ -z "$http_code" ]] && http_code=000
[[ -z "$remote_ip" ]] && remote_ip=""
connect ( curl <-> NLB )
- TCP 연결 수립 단계
- 클라이언트와 서버가 통신을 시작하기 위해 TCP 3-way Handshake 를 맺는 데 걸린 시간.
appconnect ( curl <-> NLB )
- TLS 보안 연결 단계. 아래 과정이 끝나야 안전한 보안 채널이 완성됨.
- TCP 연결이 수립된 후, 데이터를 암호화하기 위한 보안 연결(TLS Handshake) 를 맺는 데까지 걸린 시간.
- 1단계 (Client Hello)
- client 가 server 에게 client 가 사용 가능한 암호화 방식 목록 전달 및 SSL/TLS 인증서 요구
- ALPN(Application-Layer Protocol Negotiation) 목록을 보냄
- 어떤 애플리케이션 프로토콜(ex. HTTP/2 HTTP/1.1) 로 대화할지 미리 협상하는 기능
- 2단계 (server Hello)
- Server 가 Client 와 사용할 암호화 방식과 SSL/TLS 인증서 전달 (cipher suite )
- 3단계 (키 교환 및 완료)
- 양측이 인증서와 키를 확인하고, 앞으로의 모든 대화를 암호화할 방법을 합의 완료
- client 는 인증서를 확인하여 공개키를 획득하고, 공개키로 대칭키를 암호화하여 서버에 전달
- 서버는 암호화된 대칭키를 개인키로 복호화하여 대칭키를 획득
- 대칭키를 활용하여 데이터를 주고 받음
- handshake 가 끝나면 지연없이 선택한 Application-Layer Protocol 을 사용하여 통신을 시작함.
- 양측이 인증서와 키를 확인하고, 앞으로의 모든 대화를 암호화할 방법을 합의 완료
→ HTTPS 통신을 위해 클라이언트와 서버가 서로를 확인하고, 암호화할 비밀 키를 교환함.
starttransfer (Time To First Byte)
- 보안 채널 준비된 후, 실제 요청을 보내고 서버가 응답 데이터의 첫 조각을 보내주기까지 걸린 시간. 요청 전송 시간 + 서버 처리 시간 + 응답 시작 시간
- 이 시간이 길다면, 보통 서버 내부의 작업이 오래 걸리는 것.
- 서버의 성능을 직접적으로 보여줌
- 1단계 (HTTP Request)
- client 가 HTTP 요청을 보냄
- 2단계 (Server Processing)
- 서버는 요청을 받고 내용을 분석함.
- 3단계 (First Byte Response)
- 서버가 모든 준비를 마치고 응답 데이터의 첫번째 바이트를 client 에게 보내기 시작함.
curl 을 사용한 부하테스트 진행 시 주의사항 - keepalive 의 한계점
서로 다른 curl 프로세스는 네트워크 연결을 공유하지 않음
- 운영체제는 각 프로세스를 독립적인 메모리 공간과 자원을 가진 격리된 단위로 취급하기 때문에, 1번 curl 이 열어놓은 네트워크 소켓(연결 정보)를 2번 curl 이 사용할 수 없는 것.
- 따라서 쉘 스크립트로 단순히 curl 을 반복하는 것은 연결을 재사용하는 것이 아닌 설정한 요청의 개수만큼 프로세스를 생성하는 것. TCP 연결을 재사용(keep alive) 하지 않음.
# O: 이렇게 하나의 명령어로 실행해야 연결이 재사용됩니다.
curl http://example.com/page1 http://example.com/page2
# X: 이렇게 여러 명령어로 분리하면 연결 재사용이 불가능합니다.
curl http://example.com/page1
curl http://example.com/page2
'트러블슈팅' 카테고리의 다른 글
| Kubernetes 트러블슈팅 가이드: 파드부터 커널 레벨까지. (1) | 2025.10.18 |
|---|---|
| EKS에서 nginx의 EBS 볼륨 Permission Denied (0) | 2025.10.18 |
| EKS 기반 대규모 부하 테스트: NLB, KeepAlive, 커널 파라미터 튜닝 (0) | 2025.05.18 |