TCP 프록시 처리와 프로토콜 탐지
TCP 프록시 처리와 프로토콜 탐지 (TCP Proxying and Protocol Detection)
Linkerd는 TCP 트래픽을 그대로 프록시하면서, 트래픽이 HTTP인지 자동으로 탐지해 HTTP 레벨의 기능을 제공해요. 이 과정과 설정 방법을 자세히 알아봐요.
본문
Linkerd는 TLS 연결, WebSocket, HTTP 터널링을 포함한 모든 TCP 트래픽을 프록시할 수 있어요.
대부분의 경우 Linkerd는 별도 설정 없이 이 작업을 수행할 수 있어요. 이를 위해 Linkerd는 트래픽이 HTTP(HTTP/2와 gRPC 포함)인지 여부를 판단하는 프로토콜 탐지(protocol detection) 를 수행해요. 연결이 HTTP라고 판단되면 Linkerd는 자동으로 HTTP 레벨의 메트릭과 라우팅을 제공해요. 반대로 연결이 HTTP를 사용한다고 판단할 수 없다면, Linkerd는 해당 연결을 HTTP 메트릭과 라우팅 없는 일반 TCP 연결로 프록시해요. (두 경우 모두 상호 TLS(mTLS)나 바이트 레벨 메트릭 같은 비-HTTP 기능은 그대로 적용돼요.)
프로토콜 탐지는 클라이언트의 HTTP 트래픽이 암호화되지 않은 경우에만 가능해요. 애플리케이션 자체가 TLS 호출을 시작한다면 Linkerd는 연결을 복호화할 수 없고, 이를 불투명한(opaque) TCP 연결로 취급해요.
프로토콜 탐지 구성
참고
프록시 로그에 protocol detection timed out after 10s 같은 메시지가 나타나거나, 연결을 맺을 때 10초 지연이 발생한다면 프로토콜 탐지 타임아웃에 걸린 것이에요. 이 섹션에서 해결 방법을 이해할 수 있어요.
프로토콜 탐지를 수행하기 위해 Linkerd는 클라이언트가 보내는 바이트를 최대 10초 동안 기다려요. HTTP 라우팅 구성이 연결이 맺어질 위치를 결정할 수 있기 때문에, 프로토콜이 결정되기 전까지 Linkerd는 대상과의 연결을 맺을 수도 없어요.
아직 프로토콜을 판단할 충분한 데이터를 10초 안에 받지 못하면 Linkerd는 해당 연결을 불투명한 TCP 연결로 취급해 정상적으로 진행하며, 대상과 연결을 맺고 데이터를 프록시해요. 다만 Linkerd가 그 연결을 HTTP로 탐지하지 못했으므로 TCP로 취급하게 되고, 이 경우 HTTPRoutes로 구성한 정책은 적용되지 않아 예상치 못한 라우팅이나 인가(authorization) 동작이 발생할 수 있어요.
실제로 프로토콜 탐지 타임아웃은 보통 서버가 클라이언트보다 먼저 데이터를 보내는 프로토콜(SMTP 등)이나, 데이터를 보내지 않고 능동적으로 연결을 맺는 프로토콜(Memcache 등)을 사용할 때 발생해요. 또한 CPU 경합이나 연결 풀링 때문에 연결을 열어두고 데이터를 보내지 않는 경우에도 발생할 수 있어요.
이런 지연을 피하고 Linkerd가 올바른 프로토콜을 사용하도록 만들려면 Linkerd에 몇 가지 구성을 제공하면 돼요.
구성이 필요할 수 있는 프로토콜
아래 표는 추가 구성이 필요할 수 있는 일반적인 프로토콜 목록이에요.
| 프로토콜 | 표준 포트 | 기본 목록에 포함? | 참고 |
|---|---|---|---|
| SMTP | 25, 587 | 예 | |
| MySQL | 3306 | 예 | |
| MySQL with Galera | 3306, 4444, 4567, 4568 | 부분 | 포트 4567과 4568은 Linkerd의 기본 opaque 포트 목록에 없음 |
| PostgreSQL | 5432 | 예 | |
| Redis | 6379 | 예 | |
| ElasticSearch | 9300 | 예 | |
| Memcache | 11211 | 예 | |
| NATS | 4222, 6222, 8222 | 아니요 |
이 중 하나를 사용 중이라면 아래 의사결정 트리를 따라 어떤 구성을 적용해야 하는지 판단하세요.
Service 포트의 프로토콜 선언
Linkerd를 처음 시작할 때 자동 프로토콜 탐지는 대부분의 경우 잘 동작해요. 다만 연결이 충분한 데이터를 보내는 데 10초 이상 걸리면 프로토콜을 탐지하지 못할 위험이 있어요. 이런 상황은 클러스터가 과부하 상태이거나, 프록시나 앱의 리소스가 부족한 경우 등에 발생할 수 있어요.
이런 위험을 없애려면 Service 포트에 appProtocol 필드를 설정해서 해당 Service 포트와 통신할 때 사용할 프로토콜을 지정하고, 자동 프로토콜 탐지를 완전히 건너뛸 수 있어요.
| appProtocol | 프로토콜 | 참고 |
|---|---|---|
| linkerd.io/opaque | opaque | |
| linkerd.io/tcp | opaque | linkerd.io/opaque의 별칭. 완전히 동일하게 취급됨 |
| http | HTTP/1 | 소스 프록시가 대상 프록시로의 연결을 HTTP/2로 업그레이드할 수 있지만, 대상 워크로드는 여전히 HTTP/1로 보게 됨 |
| kubernetes.io/h2c | HTTP/2 |
appProtocol에 다른 값을 설정하면 Linkerd는 해당 연결을 불투명한 TCP로 취급해요. appProtocol을 설정하지 않으면 Linkerd는 계속해서 자동 프로토콜 탐지를 수행해요.
Opaque 포트 어노테이션
appProtocol 필드로 포트를 opaque로 표시할 수 없는 몇 가지 경우가 있어요. 이런 경우에는 config.linkerd.io/opaque-ports 어노테이션을 사용해서 포트 또는 포트 목록을 opaque로 표시해야 해요.
참고
여러 포트는 쉼표로 구분된 문자열로 지정할 수 있어요. 지정한 값은 기본 opaque 포트 목록을 추가(augment) 하는 게 아니라 대체(replace) 해요.
메시가 적용되지 않은 클라이언트로부터 트래픽을 받는 Pod
메시가 적용되지 않은(unmeshed) 클라이언트는 Linkerd 프록시가 없기 때문에 Service의 appProtocol 필드를 읽지 않아요. 따라서 Pod가 메시가 없는 클라이언트로부터 트래픽을 받는다면, 트래픽을 받는 Pod에 config.linkerd.io/opaque-ports 어노테이션을 설정해서 프로토콜 탐지를 건너뛰어야 해요. 그러면 Linkerd는 해당 연결을 불투명한 TCP로 취급해요.
Headless Service
마찬가지로 클라이언트가 headless 서비스를 통해 Pod에 연결하거나, 서비스를 거치지 않고 Pod에 직접 연결한다면 appProtocol 필드가 적용되지 않아요. 이 경우 트래픽을 받는 Pod에 config.linkerd.io/opaque-ports 어노테이션을 설정해 프로토콜 탐지를 건너뛰어야 해요. 그러면 Linkerd는 해당 연결을 불투명한 TCP로 취급해요.
이그레스 (Egress)
클러스터 밖의 대상에 연결할 때 Linkerd는 일치하는 EgressNetwork 리소스를 찾아요. 프로토콜 탐지를 건너뛰고 이 연결을 불투명한 TCP로 표시하려면 일치하는 EgressNetwork 리소스에 config.linkerd.io/opaque-ports 어노테이션을 설정해야 해요. 자세한 내용은 이그레스 트래픽 관리 문서를 참고하세요.
포트를 스킵 포트로 표시하기
때로는 프록시를 아예 우회해야 할 경우가 있어요. 이 경우 config.linkerd.io/skip-outbound-ports 어노테이션을 사용하면 해당 포트로 보내는 트래픽이 프록시를 완전히 우회해요. (관련 어노테이션으로 인바운드 연결에 대해 프록시를 우회하는 skip-inbound-ports도 있지만, 보통 디버깅 목적으로만 필요해요.)
opaque 포트와 마찬가지로 여러 스킵 포트를 쉼표로 구분된 문자열로 지정할 수 있어요.
이 어노테이션은 트래픽의 소스에 설정해야 해요.