0. Contents
1. Overview
1장이 통째로 네트워크 기초인 까닭
- 쿠버네티스 네트워크 책인데 1장은 통째로 네트워크 기초다
- 처음엔 돌아가는 길 같지만, 다 읽고 나면 이유가 보인다
- Pod, Service, CNI 는 새로운 네트워크가 아니다 → TCP, IP, NAT, 라우팅을 조합한 것이다
- 그래서 장애가 나면 결국 이 기초 중 하나를 들여다보게 된다
1장 구성: 두 편
- 이 글(01-1)은 컴퓨터 한 대 안의 이야기다
- macOS 노트북에 작은 Go 서버를 띄우고 curl 을 한 번 보낸다
- 그 요청이 어떤 모양으로 오가는지 tcpdump 로 찍어서 뜯어본다
- 같은 요청을 TLS 로 잠가 보고, UDP 로 바꿔 본다
- 상자가 컴퓨터 밖으로 나가는 이야기 — IP, 라우팅, ARP, VXLAN, 쿠버네티스 — 는 01-2 에서 이어진다
2. OSI Model — 외울 목록이 아니라 고장 위치 지도
2.1 일곱 층과 캡슐화 — 위에서 씌우고 아래에서 벗긴다
층으로 나눠 보는 이유 ①: 고장 위치 좁히기
- 고장 난 곳을 좁힐 수 있다
- “인터넷이 안 된다” 를 “몇 층이 안 된다” 로 바꿔 말할 수 있으면 반은 푼 것이다
층으로 나눠 보는 이유 ②: 층끼리의 독립
- 층끼리 서로의 속을 몰라도 된다
- IP 는 아래 선이 와이파이인지 광케이블인지 신경 쓰지 않는다
- 그래서 한 층을 바꿔도 나머지는 그대로 쓸 수 있다
캡슐화: 한 겹씩 포장
- 보낼 때는 위에서부터 한 겹씩 포장한다
- 이걸 캡슐화라고 부른다
패킷 속 세그먼트, 세그먼트 속 HTTP
- 패킷 안에 TCP 세그먼트가 있고, 세그먼트 안에 HTTP 가 있는 구조다
- 4.6 에서 이 상자를 실제로 뜯어본다
2.2 TCP/IP Model — 실제로는 네 층
실제 인터넷의 층 구성
- 실제 인터넷은 일곱 층을 네 층으로 묶어서 쓴다
층 모델의 한계: 커널 안에 서랍은 없다
- 층은 이해를 돕는 그림일 뿐이다
- 리눅스 커널 안에 일곱 칸짜리 서랍이 있는 건 아니다
- 그래서 한 층에 딱 들어가지 않는 기술도 있다 — eBPF 가 그렇다
- eBPF 는 층이 아니라 커널의 원하는 지점에 코드를 꽂는 방법이다
- 쿠버네티스에서는 주로 L3/L4 일을 맡긴다
3. Web Server — 같은 Hello 를 끝까지 따라간다
실습 서버: Go 로 만든 Hello 서버
- 책은 Go 로 만든 작은 서버 하나를 장 끝까지 쓴다
- 8080 포트에서 기다리다가 누가 오면 “Hello” 라고 답하는 게 전부다
- 핵심은 이 한 줄이다
http.ListenAndServe("0.0.0.0:8080", nil) // 8080 포트를 열고 기다린다
3.1 0.0.0.0 — 주소가 아니라 모든 문
전제: 컴퓨터의 IP 는 하나가 아니다
0.0.0.0을 이해하려면 먼저 이걸 알아야 한다
ifconfig | grep "inet "
통로(인터페이스)마다 붙는 IP
- 노트북에서 쳐 보면 여러 개가 나온다
127.0.0.1(자기 자신)192.168.x.x(와이파이)- VPN 이나 도커 주소까지
- IP 는 컴퓨터가 아니라 연결 통로(인터페이스)마다 하나씩 붙기 때문이다
서버가 정할 것: 받을 통로
- 그래서 서버를 띄울 때는 어느 통로로 오는 연결을 받을지 정해야 한다
0.0.0.0 의 정체: 설정값
0.0.0.0은 “모든 통로” 라는 뜻의 설정값이다- 실제 주소가 아니라서, 접속하는 쪽이
0.0.0.0으로 찾아갈 수는 없다
쿠버네티스에서 자주 나는 문제
- 컨테이너 안 서버를
127.0.0.1로 띄우면 Pod 바깥에서 들어오는 연결을 받지 않는다 - 결과는
connection refused - 컨테이너 안 서버는
0.0.0.0으로 띄운다
3.2 HTTP 메시지 — 첫 줄이 용건이다
curl 로 보내는 요청
- curl 로 요청을 보내 보자
-v를 붙이면 주고받는 내용이 다 보인다
curl -v http://127.0.0.1:8080/
> GET / HTTP/1.1 ← 보낸 것: 요청 줄
> Host: 127.0.0.1:8080
>
< HTTP/1.1 200 OK ← 받은 것: 상태 줄
< Content-Length: 5
<
Hello ← 본문
볼 것은 첫 줄 두 개뿐
GET / HTTP/1.1—/경로를 GET 으로 달라는 요청, 끝은 HTTP 버전HTTP/1.1 200 OK— 성공했다는 응답, 200 이 상태 코드다
상태 코드: 앞자리만 본다
- 2xx 성공
- 3xx 다른 곳으로 이동
- 4xx 요청 쪽 문제
- 5xx 서버 쪽 문제
- 404 가 왔다면 네트워크는 정상이다 → 서버가 HTTP 로 답할 만큼은 닿았다는 뜻
4. TCP — 인사하고 꼼꼼하게
TCP 의 일: 빠짐없이, 순서대로
- TCP 가 하는 일은 하나로 요약된다 — 데이터를 빠짐없이, 순서대로 전달하는 것
- 그걸 위해 다섯 가지를 한다
흐름 제어와 혼잡 제어 구분법
- 흐름 제어: 받는 쪽을 보호한다
- 혼잡 제어: 중간 길을 보호한다
4.1 포트 — 창구 번호와 임시 번호표
포트의 역할
- 한 컴퓨터에서 여러 프로그램이 동시에 통신할 수 있는 건 포트 덕분이다
- 포트는 컴퓨터 안의 창구 번호다
서버 포트와 임시 포트
- 서버는 정해진 번호에서 기다린다 — 우리 서버는 8080 이다
- 반대로 접속하는 쪽은 OS 가 그때그때 남는 번호를 골라 준다
- 이걸 임시(ephemeral) 포트라고 한다
결과: 탭마다 다른 출발 포트
- 브라우저 탭 세 개로 같은 서버에 붙어도 섞이지 않는 이유가 이것이다
- 탭마다 출발 포트가 다르다
4.2 seq 와 ack — 쪽 번호와 다음 쪽 요청
빠진 데이터를 찾는 번호 두 개
- TCP 가 빠진 걸 알아채는 방법은 번호다 — 책에 쪽 번호를 매기는 것과 같다
- seq: 지금 보내는 데이터가 몇 번째 바이트부터인지
- ack: 어디까지 받았고, 다음엔 몇 번째부터 달라는지
빠졌을 때: 재전송
- 중간에 한 덩어리가 빠지면 ack 숫자가 올라가지 않는다
- 보내는 쪽은 그걸 보고 다시 보낸다
TCP 헤더의 실제 모양
- 한 줄이 32비트(4바이트)다
- 색이 있는 칸만 알면 된다
실무에서 자주 보는 RST
- 아무도 기다리지 않는 포트에 연결을 시도하면 RST 가 돌아온다
- 화면에는
connection refused가 뜬다
4.3 3-Way Handshake — 세 번이어야 양쪽이 확인된다
데이터보다 연결이 먼저
- TCP 는 데이터를 보내기 전에 연결부터 맺는다
- 패킷 세 개를 주고받는다
두 번이면 남는 빈틈
- 두 번이면 서버는 자기가 보낸 SYN+ACK 이 클라이언트에 도착했는지 알 수 없다
- 세 번째 ACK 를 받아야 양쪽 모두 상대에게 닿았다는 걸 확인한다
함께 오가는 시작 번호
- 이 과정에서 서로 시작 번호(seq)도 알려 준다
- 시작 번호는 0 이 아니라 무작위다
4.4 연결 상태 — CLOSE_WAIT 가 쌓이면 내 코드다
연결 상태: 열한 가지 중 넷
- 연결에는 상태가 있다
- 열한 가지가 있지만 실무에서 보는 건 넷이다
- 서버와 클라이언트가 각자 어떤 상태를 지나가는지 보면 이렇다
쌓인 상태로 읽는 원인
- CLOSE_WAIT 가 수백, 수천 개씩 쌓이면 거의 확실히 코드 버그다
- 상대는 끊었는데 내 앱이
close()를 안 부르고 있다는 뜻이다
- 상대는 끊었는데 내 앱이
- TIME_WAIT 는 먼저 끊은 쪽에 잠시 남는 정상 상태다
- 너무 많으면 임시 포트가 모자라질 수 있다
ss -tanp # Linux: 모든 연결과 상태
netstat -an -p tcp # macOS
4.5 캡처한 12개 — 내용이 실린 건 두 개
직접 캡처하는 순서
- 서버를 띄운다
- 다른 창에서 tcpdump 를 켠다
- curl 을 한 번 보낸다
sudo tcpdump -i lo0 -nn tcp port 8080 -w ch01.pcap
옵션은 두 개만
-i lo0— 어느 통로를 볼지- curl 과 서버가 같은 노트북에 있으니 자기 자신 통로(루프백)를 본다
tcp port 8080— 무엇만 볼지, 8080 이 낀 TCP 패킷만
주의: 통로를 잘못 고르면 빈 캡처
- 통로를 잘못 고르면 아무것도 안 찍힌다
127.0.0.1로 보낸 요청은 와이파이(en0)를 지나가지 않는다
캡처 결과
12개 중 내용이 실린 두 개
- curl 한 번에 패킷이 12개 오갔다
- 그중 실제 내용이 실린 건 주황색 두 개, 요청(5번)과 응답(7번)뿐이다
- 나머지 열 개의 쓰임
- 연결을 맺는다
- 받았다고 확인한다
- 연결을 끊는다
- TCP 가 꼼꼼하다는 게 이런 뜻이다
9~12번: 연결 끊기
- 끝의 9~12번은 연결을 끊는 과정이다
- 먼저 끊은 쪽이 curl 이다 → 마지막에 TIME-WAIT 로 잠시 남는 쪽도 curl 이다
- 4.4 의 그림 그대로다
4.6 상자 뜯어보기 — 133바이트의 네 겹
요약 대신 바이트 원본
- 방금 화면에 찍힌 한 줄은 tcpdump 가 고른 요약이다
-w로 저장한 pcap 파일에는 바이트 원본이 그대로 들어 있다 → 요약에 없는 칸까지 다시 볼 수 있다- 요청이 실린 5번 패킷을 tshark 로 펼쳐 보자
tshark -r ch01.pcap -Y "frame.number==5" -V
캡슐화의 실물과 평문 GET
- 133바이트가 네 겹으로 되어 있다 — 2.1 에서 그린 캡슐화가 실물로 이렇게 생겼다
- 그림 아래 hex 줄에는 몇 바이트의 뜻을 적어 두었다
- 마지막
47 45 54가 중요하다- 글자 “GET” 그대로다
- HTTP 는 암호화되지 않은 평문이라 글자가 그대로 보인다
- 5장에서 같은 자리를 TLS 로 잠가서 다시 본다
맨 바깥에 없는 Ethernet
- lo0 은 랜카드를 거치지 않는 가상 통로다 → MAC 주소 대신 4바이트짜리 표시만 붙는다
- 진짜 Ethernet 과 MAC 은 01-2 에서 와이파이로 본다
4.7 tcpdump 의 함정 — 상대 번호와 체크섬
처음 보면 헷갈리는 것 ①: seq 가 갑자기 1
- 1번 줄에서 1229125408 이던 번호가 3번 줄부터 1 로 바뀐다
- 실제로 바뀐 게 아니다 → tcpdump 가 읽기 쉽게 시작 번호를 빼고 보여 주는 것이다
- 원래 숫자를 보려면
-S를 붙인다
처음 보면 헷갈리는 것 ②: 체크섬 경고
cksum incorrect가 뜬다- 체크섬은 패킷이 깨졌는지 검사하는 값이다
- tcpdump 는 찍은 바이트로 이 값을 직접 다시 계산해서 헤더 값과 비교한다
- 그런데 요즘은 체크섬을 랜카드가 내보내기 직전에 채운다
- tcpdump 는 그보다 먼저 찍는다
- 그래서 내가 보낸 패킷에 뜨는
incorrect는 대부분 무시해도 된다
5. TLS — 같은 요청을 잠그면
5.1 세 가지 약속 — 기밀성, 무결성, 인증
평문의 위험과 TLS
- 4.6 에서 “GET” 이 글자 그대로 보였다
- 같은 와이파이에 있는 누군가가 엿보면 다 읽힌다는 뜻이다
- TLS 는 이걸 막는다 — HTTPS 의 S 가 TLS 다
TLS 가 잠그는 범위
- TLS 는 HTTP 만 잠근다
- IP 주소와 TCP 포트는 여전히 보인다
- 그래서 HTTPS 에서도 tcpdump 는 쓸모 있다 → 연결이 됐는지, 어디서 끊겼는지는 그대로 보인다
5.2 1.3 과 1.2 — 왕복 하나와 인증서 하나
비교 실험: 책의 1.2 와 지금의 1.3
- 책이 설명하는 TLS 연결 과정은 TLS 1.2 시절 방식이다
- 지금은 TLS 1.3 이 기본이다
- 무엇이 달라졌는지 직접 비교해 봤다
- 8443 포트에 HTTPS 서버를 띄웠다
- 같은 요청을 1.3 과 1.2 로 한 번씩 보냈다
C=/opt/homebrew/opt/curl/bin/curl # macOS 기본 curl 은 TLS 1.3 이 안 된다
$C -sk --http1.1 --tlsv1.3 https://127.0.0.1:8443/
$C -sk --http1.1 --tlsv1.2 --tls-max 1.2 https://127.0.0.1:8443/
차이 세 가지
- 1.3 이 한 왕복 빠르다
- 1.3 은 첫 인사에 암호 열쇠를 만들 재료를 미리 실어 보낸다
- 그래서 서버 답장을 받자마자 바로 GET 을 보낸다
- 1.2 는 한 번 더 주고받은 뒤에야 보낸다
- 1.3 은 인증서까지 숨긴다
- 1.2 는 서버 인증서가 평문으로 보였고, 1.3 은 보이지 않았다
- 1.3 은 열쇠를 네트워크로 보내지 않는다
- 양쪽이 재료를 하나씩 내놓고 각자 같은 열쇠를 계산한다
- 그래서 나중에 서버 열쇠가 유출돼도 지난 통신은 풀리지 않는다
캡처에서 본, 책에 없는 것
- 1.3 의 열쇠 재료가
X25519MLKEM768이었다 - 양자 컴퓨터에도 견디도록 설계된 최신 방식이 이미 기본으로 쓰이고 있다
5.3 같은 자리의 다른 바이트 — 열쇠가 있으면 다시 GET
바이트로 확인: 열쇠 파일
- 정말 잠겼는지 바이트로 확인해 보자
- 실습 서버는 세션 열쇠를 파일(
tls-keys.log)로 남기게 해 두었다 - 이 파일을 tshark 에 주면 잠긴 내용을 풀 수 있다
tshark -r tls.pcap -Y tls # 열쇠 없이
tshark -r tls.pcap -o tls.keylog_file:tls-keys.log -Y tls # 열쇠로
결과: 잠그면 의미 없는 바이트
- 평문일 때 “GET” 이 보이던 자리가, 잠그면 의미 없는 바이트가 된다
- 열쇠를 넣으면 다시
GET / HTTP/1.1이 나온다 - 이 열쇠 파일은 통신을 푸는 열쇠 그 자체다 → 실습이 아니면 켜 두지 않는다
6. UDP — 인사 없이 던지고 끝
TCP 의 비용과 UDP
- TCP 의 꼼꼼함에는 비용이 든다 — 4.5 의 12개 중 열 개가 그 비용이었다
- UDP 는 그 비용을 아예 내지 않는다
- 연결도, 확인도, 재전송도 없이 데이터를 보내고 끝이다
UDP 를 쓰는 곳
- 짧게 묻고 짧게 답하면 끝나는 DNS
- 늦게 오느니 조금 끊기는 게 나은 인터넷 전화와 영상
6.1 8바이트 헤더 — seq 도 ack 도 없다
헤더 크기 비교
- UDP 헤더는 칸이 네 개, 8바이트가 전부다
- TCP 헤더와 같은 축척으로 그리면 차이가 바로 보인다
번호가 없을 때 잃는 것
- seq 도 ack 도 없다 → 몇 번째인지도 받았는지도 알 방법이 없다
6.2 열린 포트와 닫힌 포트 — 거절은 ICMP 가 한다
실험: 열린 9000 번과 빈 9001 번
- 같은 Hello 서버를 UDP 9000 번으로 만들고, 두 번 보내 봤다
- 한 번은 서버가 있는 9000 번
- 한 번은 아무도 없는 9001 번
echo hi | nc -u -w1 127.0.0.1 9000 # Hello 가 돌아온다
echo hi | nc -u -w1 127.0.0.1 9001 # 아무것도 안 돌아온다
왼쪽: 패킷 두 개로 끝
- 보내고, 답이 오고, 끝
- TCP 였다면 12개였다
오른쪽: OS 가 대신 보내는 ICMP 거절
- UDP 에는 TCP 의 RST 같은 거절 신호가 없다
- 그래서 OS 가 대신 ICMP 라는 별도 메시지로 “그 포트는 없다” 를 돌려준다
- 그런데 nc 는 그걸 모른 채 정상 종료했다
- UDP 는 상대가 받았는지 모른다는 말이 이런 뜻이다
6.3 QUIC — UDP 위에 다시 쌓은 꼼꼼함
QUIC 의 출발점
- UDP 에 신뢰성이 없다는 건 UDP 자체 이야기다
- UDP 위에 꼼꼼함을 다시 쌓으면 된다
- 그게 QUIC 이고, HTTP/3 이 QUIC 을 쓴다
QUIC 이 다시 넣은 것과 효과
- TCP 가 하던 재전송과 혼잡 제어를 다시 구현했다
- TLS 1.3 까지 안에 넣었다
- 덕분에
- 연결과 암호화 준비가 한 번에 끝난다
- 와이파이에서 LTE 로 바뀌어도 연결이 끊기지 않는다
정리: TCP, UDP, QUIC 의 관계
- TCP: 연결하고 꼼꼼하게 보낸다
- UDP: 연결 없이 보내고 끝이다
- QUIC: UDP 위에 TCP 의 꼼꼼함과 TLS 를 다시 얹은 것이다
7. Discussion
핵심 메시지
컴퓨터 한 대 안에서 curl 한 번을 끝까지 따라가 보면, 층은 외울 목록이 아니라 상자 몇 겹이다. 그 상자를 tcpdump 로 직접 뜯어 본 경험이 있으면, 캡처 화면이 더 이상 암호처럼 보이지 않는다. 다음 편은 이 상자가 컴퓨터 밖으로 나가 라우터를 건너고, 쿠버네티스 노드 사이를 오가는 이야기다.
8. References
"TLS allows client/server applications to communicate over the Internet in a way that is designed to prevent eavesdropping, tampering, and message forgery."
"QUIC provides applications with flow-controlled streams for structured communication, low-latency connection establishment, and network path migration."
