在Golang项目中切换网络名称空间后,我遇到了GRPC客户端与服务器之间进行通信的问题。为了解决这个问题,我相应地修改了GRPC示例程序Hello World,结果是同样的问题。在golang应用程序中切换名称空间后使用GRPC时,是否存在任何已知的限制?
我已经阅读了有关在Golang中切换名称空间的问题,但是我想这是否会导致问题取决于GRPC行为。
GRPC客户端是否会生成任何其他goroutine?由于名称空间切换,此类goroutine不一定会在与原始goroutine锁定线程的相同名称空间中执行。
我假设GRPC服务器为每个客户端生成goroutine,但是在生成新的goroutine之前,它是否在原始goroutine中创建了套接字?这个问题还与产生的goroutine不必在调用grpc.Serve()的名称空间中执行相同(命名空间开关需要runtime.LockOSThread())。
在服务器和客户端在匹配的名称空间(ip netns exec ...)中启动的情况下,通信有效,但是在客户端内部执行名称空间切换的情况下,通信失败。当在服务器内部执行名称空间切换时,通信也可以进行,因此问题应该出在客户端。
greeter_client / main.go:
package main
import (
"fmt"
"log"
"os"
"runtime"
"syscall"
"time"
pb "grpctest/helloworld/helloworld"
"github.com/vishvananda/netns"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
const (
defaultName = "world"
defaultAddress = "localhost:50051"
nsEnv = "NAMESPACE"
addressEnv = "ADDRESS"
blockEnv = "DIALBLOCK"
)
func main() {
fmt.Printf("* Client thread id before runtime.LockOSThread(): %d\n", syscall.Gettid())
runtime.LockOSThread()
defer runtime.UnlockOSThread()
fmt.Printf("* Client thread id after runtime.LockOSThread(): %d\n", syscall.Gettid())
var dialOpts []grpc.DialOption
dialOpts = append(dialOpts, grpc.WithInsecure())
_, ok := os.LookupEnv(blockEnv)
if ok == true {
dialOpts = append(dialOpts, grpc.WithBlock())
fmt.Printf("* Dial in blocked mode\n")
} else {
fmt.Printf("* Dial in unblocked mode\n")
}
address, ok := os.LookupEnv(addressEnv)
if ok == false {
address = defaultAddress
}
fmt.Printf("* Talk to server at %s\n", address)
var origns netns.NsHandle
namespace, ok := os.LookupEnv(nsEnv)
if ok {
fmt.Printf("* Switch namespace to %s\n", namespace)
origns, err := netns.Get()
if err != nil {
log.Fatal("failed to get current namespace")
}
defer origns.Close()
newns, err := netns.GetFromName(namespace)
if err != nil {
log.Fatalf("failed to get new namespace: %s", namespace)
}
err = netns.Set(newns)
if err != nil {
log.Fatalf("failed to set new namespace: %s", namespace)
}
defer newns.Close()
}
fmt.Printf("* Client thread id before grpc.Dial(): %d\n", syscall.Gettid())
// Set up a connection to the server.
conn, err := grpc.Dial(address, dialOpts...)
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
fmt.Printf("* Client thread id before pb.NewGreeterClient(): %d\n", syscall.Gettid())
c := pb.NewGreeterClient(conn)
fmt.Printf("* Client thread id after pb.NewGreeterClient(): %d\n", syscall.Gettid())
// Contact the server and print out its response.
name := defaultName
if len(os.Args) > 1 {
name = os.Args[1]
}
r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name})
if err != nil {
fmt.Printf("could not greet: %v", err)
select {}
log.Fatalf("could not greet: %v", err)
}
fmt.Printf("* Client thread id after c.SayHello(): %d\n", syscall.Gettid())
log.Printf("Greeting: %s", r.Message)
time.Sleep(5 * time.Second)
if namespace != "" {
netns.Set(origns)
}
fmt.Printf("* Client thread id at exit: %d\n", syscall.Gettid())
}
greeter_server / main.go:
package main
import (
"fmt"
"log"
"net"
"os"
"runtime"
"syscall"
pb "grpctest/helloworld/helloworld"
"github.com/vishvananda/netns"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
const (
port = ":50051"
nsEnv = "NAMESPACE"
)
// server is used to implement helloworld.GreeterServer.
type server struct{}
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
fmt.Printf("* RPC call server thread id: %d\n", syscall.Gettid())
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
func main() {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var origns netns.NsHandle
namespace := os.Getenv(nsEnv)
if namespace != "" {
fmt.Printf("* Switch namespace to %s\n", namespace)
origns, err := netns.Get()
if err != nil {
log.Fatal("failed to get current namespace")
}
defer origns.Close()
newns, err := netns.GetFromName(namespace)
if err != nil {
log.Fatalf("failed to get new namespace: %s", namespace)
}
err = netns.Set(newns)
if err != nil {
log.Fatalf("failed to set new namespace: %s", namespace)
}
defer newns.Close()
}
fmt.Printf("* Main server thread id: %d\n", syscall.Gettid())
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
// Register reflection service on gRPC server.
reflection.Register(s)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
if namespace != "" {
netns.Set(origns)
}
fmt.Printf("* Main server exits (thread id: %d)\n", syscall.Gettid())
}
答案 0 :(得分:1)
我遇到了完全相同的问题,无论是否锁定OS线程,netns都会在每个新的goroutine上切换回原始名称空间。
我发现只要服务器在正确的网络中启动(我放弃尝试以编程方式执行此操作,而只是通过dialOpts = append(dialOpts, grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
netns.Set(newns)
return net.DialTimeout("tcp", addr, timeout)
}))
生成了服务器进程),则只需为该服务器设置自定义拨号程序GRPC客户端在连接到正确的netns期间还原goroutine:
{{1}}