k8s etcdctl unexpected EOF

# k8s bitnami-etcd(etcd 3.5.3) 를 새로 구성 후 로컬에서 접속하기 위해 etcd service 에 nodeport 30090 을 추가했다.
# 로컬에서 go.etcd.io/etcd/client/v3 로 Get 을 수행하면 다음과 같은 에러가 발생한다.
{"level":"warn","ts":"2023-02-04T10:46:30.933+0900","logger":"etcd-client","caller":"v3@v3.5.7/retry_interceptor.go:62","msg":"retrying of unary invoker failed","target":"etcd-endpoints://0xc0001bc000/10.10.10.10:30090","attempt":0,"error":"rpc error: code = Internal desc = unexpected EOF"}

# 하지만 이전 etcd service 로 변경하면 에러 없이 수행된다.
# k8s ysoftman_app --> etcd 도 에러 없이 수행된다.


#####


# etcdctl(etcd client cli)로 설치해서 확인을 해보자.
brew install etcd

# etcdctl 및 api 버전 확인
etcdctl version
etcdctl version: 3.5.7
API version: 3.5

# endpoint 정보 확인하면 정상적으로 응답한다.
etcdctl --endpoints=10.10.10.10:30090 --write-out=table endpoint status
etcdctl --endpoints=10.10.10.10:30090 --write-out=table endpoint health

# etcd 클러스터(pod) 3개가 정상적으로 응답한다.
etcdctl --endpoints=10.10.10.10:30090 member list

# 특정 키 값을 조회해도 정상적으로 응답한다.
etcdctl --endpoints=10.10.10.10:30090 get "/user/key-ysoftman

# 그런데 prefix 로 키들을 조회 하면 위 에러(unexpected EOF)가 발생한다.
# 이전 etcd endpoint 를 사용하면 잘된다.
etcdctl --endpoints=10.10.10.10:30090 get --prefix "/user/"

# limit 로 조회 개수를 줄이면 동작한다.
etcdctl --endpoints=10.10.10.10:30090 get --prefix "/user/" --limit=10
# limit 로 조회 안되는 지점을 찾아보니 157 부터 조회가 안된다.
# 하지만 157 번째 키를 단건으로 조회하면 조회가 되는걸 보니 데이터값 자체의 문제는 아닌것으로 보인다.
etcdctl --endpoints=10.10.10.10:30090 get --prefix "/user/" --limit=157

# --print-value-only 로 결과에서 키는 제외하는 옵션을 줘도 157이상이면 에러가 발생한다.
# 찾아보니 클라에서 출력시 키만 제외하는것 같다.
etcdctl --endpoints=10.10.10.10:30090 get --prefix "/user/" --print-value-only --limit 157

# key 만 조회하면 450 건까지 조회된다.
etcdctl --endpoints=10.10.10.10:30090 get --prefix "/user/" --keys-only --limit 450


#####


# bitnami-etcd logLeve=debug 설정 후 요청에 대한 debug 로그 확인해보자
# -limit 156 옵션으로 정상 응답오는 경우
"message": "{\"level\":\"debug\",\"ts\":\"2023-02-06T16:26:28.408Z\",\"caller\":\"v3rpc/interceptor.go:182\",\"msg\":\"request stats\",\"start time\":\"2023-02-06T16:26:28.406Z\",\"time spent\":\"2.424678ms\",\"remote\":\"xxx.xxx.xxx.xxx:26355\",\"response type\":\"/etcdserverpb.KV/Range\",\"request count\":0,\"request size\":37,\"response count\":544,\"response size\":65215,\"request content\":\"key:\\\"/user/\\\" range_end:\\\"/user0\\\" limit:156 \"}",

# limit 없이 전체 조회한 경우, 로컬 Unexpected EOF 발생했을때 로그
"message": "{\"level\":\"debug\",\"ts\":\"2023-02-06T16:36:15.365Z\",\"caller\":\"v3rpc/interceptor.go:182\",\"msg\":\"request stats\",\"start time\":\"2023-02-06T16:36:15.361Z\",\"time spent\":\"3.948059ms\",\"remote\":\"xxx.xxx.xxx.xxx:42568\",\"response type\":\"/etcdserverpb.KV/Range\",\"request count\":0,\"request size\":34,\"response count\":544,\"response size\":264867,\"request content\":\"key:\\\"/user/\\\" range_end:\\\"/user0\\\" \"}",

# 둘다 정상적으로 아이템 개수 (response count:544) 개의 아이템을 응답했다고 나온다.
# 전체 조회시 response size 도 크게(정상) 나온다.


#####


# linux 서버에 etcdctl 설치 후 --> k8s bitnami-etcd 에서는 전체 조회가 정상적으로 된다.


#####


# 로컬 etcd 에 k8s bitnami-etcd 스냅샷(ysoftman.db) 가져와 복구해서 올려서 테스트해보자
# ysoftman.db 를 복구해서 ./etcd-data(이미 있다면 삭제필요)에 준비
etcdutl snapshot restore ./ysoftman.db --data-dir ./etcd-data

# etcd 서버 올리기
etcd --data-dir ./etcd-data

# 로컬 etcd 로는 접속이 limit 없이 모두 조회된다.
etcdctl --endpoints=localhost:2379 get --prefix "/user/" --print-value-only | jq


#####


# etcd container 접속해서 etcdctl 로 확인해보자.
# 먼저 버전을 확인해보면 API 버전은 3.5 로컬과 같지만 etcdctl version 은 낮다.
# 참고로 로컬에 etcdctl 3.5.3 버전을 다운받아 조회했지만 안됐다.
I have no name!@bitnami-etcd-0:/opt/bitnami/etcd$ etcdctl version
etcdctl version: 3.5.3
API version: 3.5

# 전체 조회가 잘된다.
I have no name!@bitnami-etcd-0:/opt/bitnami/etcd$ etcdctl --endpoints=10.10.10.10:30090 get --prefix "/user/" --print-value-only


#####


# 상황별 테스트를 정리해보면
# 같은 클러스터 k8s pod <--> k8s bitnami-etcd service(nodeport) 정상
# 별도 linux <--> k8s bitnami-etcd service(nodeport) 정상
# 로컬(mac) <--> 로컬 etcd 정상
# 로컬(mac) <--> k8s bitnami-etcd service(nodeport) 경우만 응답 데이터 일정 크기 이후에 null 값 발생한다.

# etcdctl(https://github.com/etcd-io/etcd) 소스 빌드 및 테스트해보자.
# endpoints="이전 etcd" 로 빌드 테스트하면 전체 조회된다.
# endpoints="local etcd" 서버(k8s etcd 와 같은 데이터) 로 빌드 테스트하면 전체 조회된다.
cd etcd/etcdctl/
go build && ./etcdctl --endpoints=10.10.10.10:30090 get --prefix "/user/"

# etcdctl > etcd client > grpc(외부 패키지) > rpc_util.go 의 다음 함수에서
# 실제 read 한 msg 를 프린트해보면 데이터(json)가 끊겨져 있다.
# EOF 에러를 리턴하는 grpc 소스코드
func (p *parser) recvMsg()
p.r.Read(msg);
 ... > io.ErrUnexpectedEOF 에러 리턴

# 응답 메시지의 크기
# length 276518 인데, Read 에서 65530 만큼 읽다 EOF 발생
# msg(read 한 내용)을 파일로 출력해보면 65530 이후 210988 개의 null 이 포함되어 있다.
go build && ./etcdctl --endpoints=10.10.10.10:30090 get --prefix "/user/" > out.txt

# 찾아보니 grpc eof 관련해 연결 및 스트림의 window size 이슈가 있다.
# etcd client 접속 옵션 부분에
# 다음과 같이 grpc window 크기를 늘려주니 모든 결과가 조회된다.
opts := append(dopts, grpc.WithResolvers(c.resolver), grpc.WithInitialWindowSize(1_000_000), grpc.WithInitialConnWindowSize(1_000_000))

# etcdctl 에서는 InitialWindowSize 과 InitialConnWindowSize 를 설정하는 부분이 없어 grpc 의 64K 디폴트 및 최소값을 그대로 사용하게 된다.

# eof 시 read한 크기가 65530(대충 64K)정도인걸 보면 네트워트 환경(grpc 이슈에 보면 mac 에서 자주 발생하는것 같다.)에 따라 window size 작으면 스트림을 제대로 읽지 못하는 문제가 있다.
# etcdctl cli 나 library 에서는 window size 를 설정할 수 없어 현재로선 etcd 소스를 별도 수정해서 사용해야 한다. 흠...

comments:

댓글 쓰기