레이블이 grpc인 게시물을 표시합니다. 모든 게시물 표시
레이블이 grpc인 게시물을 표시합니다. 모든 게시물 표시

Undefined symbols for architecture arm64

# mac m1(arm64) 환경에서 my 라는 프로젝트에서 빌드를 할때
# my/include 와 my/lib 에 디렉토리에 소스로 빌드한 protobuf 등의 라이브리 결과물이 있고 
# 이를 링크하는 my 프로젝트에서 cmake > make 빌드 중 다음과 같은 에러가 발생했다.
Undefined symbols for architecture arm64:
"grpc_call_run_in_event_engine(grpc_call const*, absl::lts_20250127::AnyInvocable<void ()>)", referenced from:
... 생략 ...  "google::protobuf::internal::AssignDescriptors(google::protobuf::internal::DescriptorTable const* (*)(), absl::lts_20250127::once_flag*, google::protobuf::Metadata const&)", referenced from:
... 생략 ...

# 동적라이브러리(.dylib) 파일들 경로는 이상 없이 lib/을 링킹하고 있었다
# 해당 dylib 파일의 타입도 다음과 같이 arm64 로 빌드되어 있었다.
file /Users/ysoftman/workspace/my/lib/libprotobuf.25.6.0.dylib
/Users/ysoftman/workspace/my/lib/libprotobuf.25.6.0.dylib: Mach-O 64-bit dynamically linked shared library arm64

# protobuf 에서 AssignDescriptors() 심볼도 다음과 같이 확인하니 존재했다.
nm -gU /Users/ysoftman/workspace/my/lib/libprotobuf.25.6.0.dylib | rg AssignDescriptors
00000000000d2fa8 T __ZN6google8protobuf8internal17AssignDescriptorsEPFPKNS1_15DescriptorTableEvEPN4absl12lts_202407229once_flagERKNS0_8MetadataE
00000000000d2ff4 T __ZN6google8protobuf8internal17AssignDescriptorsEPKNS1_15DescriptorTableE

# protobuf 라이브러리에서 사용하는 dylib 를 다음과 같이 확인하니
# abseil(absl, c++ standard 에 없는 기능들이 있는 구글에서 만든 오픈소스 라이브러리) 2407 버전을 사용하게 되어 있다.(/opt/homebrew/opt.. 경로를 사용하고 있음)
# 그런데 위 에러에 보이는 absl 20250127 보다 이전이다.
otool -L  /Users/ysoftman/workspace/my/lib/libprotobuf.25.6.0.dylib
... 생략 ...
/opt/homebrew/opt/abseil/lib/libabsl_log_initialize.2407.0.0.dylib (compatibility version 2407.0.0, current version 0.0.0)
/opt/homebrew/opt/abseil/lib/libabsl_statusor.2407.0.0.dylib (compatibility version 2407.0.0, current version 0.0.0)
... 생략 ...

# my/lib/libprotobuf.25.6.0.dylib 소스 빌드시 프로젝트 absl 은 패키지(시스템 기본 설정, brew install abseil로 설치됨)를 이용하게 되어 있는게 문제였다.
# 참고로 brew uninstall abseil 하면 시스템에서 이미 mysql 등에서 사용하고 있어 디펜던시가 있다.
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_PREFIX:PATH=$PREFIX \
-DCMAKE_INSTALL_LIBDIR:PATH=lib \
-DCMAKE_CXX_STANDARD=17 \
-DBUILD_SHARED_LIBS=1 \
-Dprotobuf_BUILD_TESTS=0 \
-Dprotobuf_ABSL_PROVIDER=package .

# 정리하면 abseil 버전이 다음과 같이 2개가 있는 상태다.
# 시스템에 기본 버전: /opt/homebrew/Cellar/abseil/20240722.1
# 커스텀 빌드: /Users/ysoftman/workspace/my/lib/libabsl_xxx.2501.0.0.dylib

# my/lib/ 로 설치 전 커스텀 빌드 소스들(cmake 정보등) my/abseil-cpp-20250127.0 에 있다.
# protobuf dylib 빌드시 다음 2개의 값을 설정하면 커스텀한(absl 2501버전) dylib 를 참조해 빌드할 수 있다.
# 참고로 아래 변수이름은 protobuf 소스 > cmake > abseil-cpp.cmake 를 참고해 알 수 있었다.
# protobuf cmake 에 수행시 다음과 같이 커스텀 absl 을 사용해 빌드(cmake and build)
-Dprotobuf_ABSL_PROVIDER=module \
-DABSL_ROOT_DIR="/Users/ysoftman/workspace/my/abseil-cpp-20250127.0" \

# grpc 도 에러나 발생하니 수정
# grpc 빌드는 absl protobuf re2 cares 다음과 같이 빌드(cmake and bulid)
# 참고로 아래 변수이름은 gproc 소스 > cmake > xxx.cmake 를 참고해 알 수 있었다.
-DgRPC_ABSL_PROVIDER=module \
-DABSL_ROOT_DIR="/Users/ysoftman/workspace/my/abseil-cpp-20250127.0" \
-DgRPC_PROTOBUF_PROVIDER=module \
-DPROTOBUF_ROOT_DIR="/Users/ysoftman/workspace/my/protobuf-25.6" \
-DgRPC_RE2_PROVIDER=module \
-DRE2_ROOT_DIR="/Users/ysoftman/workspace/my/re2-2024-07-02" \
-DgRPC_CARES_PROVIDER=module \
-DCARES_ROOT_DIR="/Users/ysoftman/workspace/my/c-ares-1.34.4" \

# 또는 PROVIDER=package 로 둔 상태에서 CMAKE_PREFIX_PATH(include,header경로)를 설정해도 된다.
-DCMAKE_PREFIX_PATH="/Users/ysoftman/workspace/my"

# 위와 같은 커스텀 dylib 를 생성하려면 
# absl, re2 --> protobuf
# absl, re2, protobuf --> grpc
# 로 관련된 모든 ~/Users/ysoftman/workspace/my...의 커스텀 경로를 해줘야해서 복잡하고 불편한다.
# 그리고 my 프로젝트에서 빌드시 커스텀 라이브러리 사용으로 또 다른 에러가 발생했다.
# 그냥 다음 패키지(라이브러리)들은 소스 빌드를 하지 않고 설치하고 -DxxxPROVIDER=package 로 원복
# 커스텀 라이브러리들 목록에서 제외(my/{include,lib}에 생성되지 않아)하면 my 프로젝트도 다음 시스템 패키지를 사용해 문제 없이 빌드됐다.
brew install abseil re2 protobuf grpc

build gRPC on mac

# mac m1(arm64) 에서 grpc 소스 빌드를 해보는데 다음 에러가 발생했다.
/Users/ysoftman/workspace/grpc-1.70.1/src/core/lib/promise/pipe.h:118:5: error: use of undeclared identifier 'VLOG'
  118 |     GRPC_TRACE_VLOG(promise_primitives, 2)
      |     ^
/Users/ysoftman/workspace/grpc-1.70.1/src/core/lib/debug/trace_impl.h:95:40: note: expanded from macro 'GRPC_TRACE_VLOG'
   95 |   if (GRPC_TRACE_FLAG_ENABLED(tracer)) VLOG(level)

# grpc 빌드에 protobuf, absl, c-ares, re2, ssl, zlib 등이 라이브러리가 필요하고
# cmake 실행시 다음과 같이 package(시스템 패키지)가 사용되도록 했다.
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_PREFIX=/Users/ysoftman/workspace/ysoftman-build-grpc \
-DBUILD_SHARED_LIBS=1 \
-DgRPC_CARES_PROVIDER=package \
-DgRPC_ABSL_PROVIDER=package \
-DgRPC_PROTOBUF_PROVIDER=package \
-DgRPC_RE2_PROVIDER=package \
-DgRPC_SSL_PROVIDER=package \
-DgRPC_ZLIB_PROVIDER=package \
-DgRPC_DOWNLOAD_ARCHIVES=0 \
-DgRPC_BUILD_GRPC_CSHARP_PLUGIN=0 \
-DgRPC_BUILD_GRPC_NODE_PLUGIN=0 \
-DgRPC_BUILD_GRPC_OBJECTIVE_C_PLUGIN=0 \
-DgRPC_BUILD_GRPC_PHP_PLUGIN=0 \
-DgRPC_BUILD_GRPC_RUBY_PLUGIN=0 .

# cmake 수행후 설정들이 어떻게 생성됐는지 확인해 보면
# re2 만 (x86_64)/usr/local/lib/cmake/re2 경로를 사용하고 있었다.
cmake -L ./grpc-1.70.1
... 생략 ...
Protobuf_DIR:PATH=/opt/homebrew/lib/cmake/protobuf
absl_DIR:PATH=/opt/homebrew/lib/cmake/absl
c-ares_DIR:PATH=/opt/homebrew/lib/cmake/c-ares
gRPC_ABSL_PROVIDER:STRING=package
gRPC_BUILD_CODEGEN:BOOL=ON
gRPC_BUILD_GRPCPP_OTEL_PLUGIN:BOOL=OFF
gRPC_BUILD_GRPC_CPP_PLUGIN:BOOL=ON
gRPC_BUILD_GRPC_CSHARP_PLUGIN:BOOL=0
gRPC_BUILD_GRPC_NODE_PLUGIN:BOOL=0
gRPC_BUILD_GRPC_OBJECTIVE_C_PLUGIN:BOOL=0
gRPC_BUILD_GRPC_PHP_PLUGIN:BOOL=0
gRPC_BUILD_GRPC_PYTHON_PLUGIN:BOOL=ON
gRPC_BUILD_GRPC_RUBY_PLUGIN:BOOL=0
gRPC_BUILD_MSVC_MP_COUNT:STRING=0
gRPC_BUILD_TESTS:BOOL=OFF
gRPC_CARES_PROVIDER:STRING=package
gRPC_DOWNLOAD_ARCHIVES:BOOL=0
gRPC_INSTALL:BOOL=ON
gRPC_INSTALL_BINDIR:STRING=bin
gRPC_INSTALL_CMAKEDIR:STRING=lib/cmake/grpc
gRPC_INSTALL_INCLUDEDIR:STRING=include
gRPC_INSTALL_LIBDIR:STRING=lib
gRPC_INSTALL_SHAREDIR:STRING=share/grpc
gRPC_PROTOBUF_PROVIDER:STRING=package
gRPC_RE2_PROVIDER:STRING=package
gRPC_SSL_PROVIDER:STRING=package
gRPC_USE_PROTO_LITE:BOOL=OFF
gRPC_ZLIB_PROVIDER:STRING=package
re2_DIR:PATH=/usr/local/lib/cmake/re2

# re2(regular expression library) 를 설치하면 /opt/homebrew/lib/cmake/re2 가 생된다.
brew install re2

# 이제 cmake 를 실행하고 설정 내용을 보면 re2 경로가 다음으로 변경된다.
re2_DIR:PATH=/opt/homebrew/lib/cmake/re2

# 다시 빌드 수행 중 다음 에러가 발생했다.
ld: warning: ignoring file '/usr/local/Cellar/openssl@3/3.2.0_1/lib/libssl.3.dylib': found architecture 'x86_64', required architecture 'arm64'
ld: warning: ignoring file '/usr/local/Cellar/openssl@3/3.2.0_1/lib/libcrypto.3.dylib': found architecture 'x86_64', required architecture 'arm64'
Undefined symbols for architecture arm64:

# export OPENSSL_ROOT_DIR=/usr/local/opt/openssl 환경변수 설정이 문제였다.
# openssl 을 설치하고
brew install openssl
# 다음과 같이 환경변수를 설정 또는 OPENSSL_ROOT_DIR 설정을 안하면 기본 /opt/homebrew/opt/openssl 을 사용한다.
export OPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl

# 다시 cmake > make 하면 빌드 성공~

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 소스를 별도 수정해서 사용해야 한다. 흠...