I have a Cloud Run gRPC service and I want it to listen to PubSub topic.
The only way to do it from the official documentations is to use a trigger but this only works with REST server that accept http requests.
I couldn't find any good example on the web of how to use pubsub with cloud run grpc services.
My service is built with Go following the official grpc instructions
main.go:
func main() {
ctx := context.Background()
pubSubClient, err := pubsub.NewClient(ctx, 'project-id')
if err != nil {
log.Fatal(err)
}
pubSubSubscription := pubSubClient.Subscription("subscription-id")
go func(sub *pubsub.Subscription) {
err := sub.Receive(ctx, func(ctx context.Context, m *pubsub.Message) {
defer m.Ack()
log.Printf("sub.Receiv called %+v", m)
})
if err != nil {
// handle error
}
}(pubSubSubscription)
....
lis, err := net.Listen("tcp", ":"+port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s, _ := server.New(ctx, ...)
grpcServer := grpc.NewServer()
grpcpkg.RegisterReportServiceServer(grpcServer, s)
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("failed to serve: %s", err)
}
}
This will work as long as there is at least one instance up and listening. But if there is no instances active, and a new pubsub message publishes, cloud run won't "wake up" the insane because it's autoscaling is utilized based on http requests.
The only way to make the above code work is by setting min-instance 1 in cloud run, but this can be not efficient for a lot of use cases (i.e the service is not active at night)
Is there any work around for this?
Is there any way to trigger a grpc cloud run service from a pubsub message?
The sub.Receive method you are attempting to use on the client library is meant for Pull message delivery and not Push delivery. You can read about how these two delivery types compare to make a choice.
If you wish to receive messages as incoming requests, you must use Push delivery and can follow the the Cloud Run Pubsub usage guide. Note that Cloud Pub/Sub only delivers messages as HTTPS POST requests with the specified JSON payload. To handle these requests from a gRPC server, you can use the gRPC Gateway plugin to generate a reverse proxy that exposes an HTTP API.
Once your Cloud Run instance is accepting HTTP traffic using the reverse proxy, it will be autoscaled with incoming requests, so you won't need to keep 1 instance always running. Note that this may mean that message processing latency is upto 10s on a cold start.
Related
My goal is to understand what is gRPC Client is for.
My questions:
Is gRPC client created by both the backend engineer (Go) and another engineer (Java) to communicate with the gRPC services?
i.e. the company hired a new backend engineer and he specialized in Java. He created a new service (i.e. service-garage, written in Java) and also created the garage.proto file. He need the service-user (written in Go), so he do protoc --java_out=. --grpc--java_out=. user.proto. By doing so, he can call the service-user method in Java inside the service-garage.
Is gRPC client an API gateway?
i.e. There are 2 services, service-user (written in Go) and service-garage (written in Java). There should be one backend engineer that write the gRPC client, so the frontend engineer can use this API gateway to send request to.
My current thought:
microservices use multiple ports i.e. 7000 and 9000
i.e. a service-user and a service-garage
services/service-user/main.go on port 7000
func main() {
srv := grpc.NewServer()
var garageSrv GaragesServer
model.RegisterGaragesServer(srv, garageSrv)
log.Println("Starting RPC server at", config.SERVICE_GARAGE_PORT)
l, err := net.Listen("tcp", config.SERVICE_GARAGE_PORT)
if err != nil {
log.Fatalf("could not listen to %s: %v", config.SERVICE_GARAGE_PORT, err)
}
log.Fatal(srv.Serve(l))
}
services/service-garage/main.go on port 9000
func main() {
srv := grpc.NewServer()
var userSrv UsersServer
model.RegisterUsersServer(srv, userSrv)
log.Println("Starting RPC server at", config.SERVICE_USER_PORT)
l, err := net.Listen("tcp", config.SERVICE_USER_PORT)
if err != nil {
log.Fatalf("could not listen to %s: %v", config.SERVICE_USER_PORT, err)
}
log.Fatal(srv.Serve(l))
}
microservices use multiple repositories
repository for gRPC protocol buffers (accessed by gRPC client and gRPC servers)
repositories for services (ie service-uer and service-garage`
repository for gRPC client with port 80 and 443. The frontend web developer or frontend mobile developer can send request to port 80 and 443 ie localhost:80/user/register and localhost:80/user/list
gRPC client is the API endpoint that the frontend web developer or frontend mobile developer send request to.
client-2/main.go
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/user/register", user.Register)
mux.HandleFunc("/user/list", user.List)
mux.HandleFunc("/garage/add", garage.Add)
mux.HandleFunc("/garage/list", garage.List)
err := http.ListenAndServe("localhost:8080", mux)
if err != nil {
log.Panicln(err)
}
}
Im using the following code which works as expected, I use from the cli gcloud auth application-default login and enter my credentials and I was able to run the code successfully from my macbook.
Now I need to run this code in my CI and we need to use different approach , what should be the approach to get the client_secret
and client_id or service account / some ENV variable, what is the way for doing it via GO code?
import "google.golang.org/api/compute/v1"
project := "my-project"
region := "my-region"
ctx := context.Background()
c, err := google.DefaultClient(ctx, compute.CloudPlatformScope)
if err != nil {
log.Fatal(err)
}
computeService, err := compute.New(c)
if err != nil {
log.Fatal(err)
}
req := computeService.Routers.List(project, region)
if err := req.Pages(ctx, func(page *compute.RouterList) error {
for _, router := range page.Items {
// process each `router` resource:
fmt.Printf("%#v\n", router)
// NAT Gateways are found in router.nats
}
return nil
}); err != nil {
log.Fatal(err)
}
Since you're using Jenkins you probably want to start with how to create a service account. It guides you on creating a service account and exporting a key to be set as a var in another CI/CD system.
Then refer to the docs from the client library on how to create a new client with source credential.
e.g.
client, err := storage.NewClient(ctx, option.WithCredentialsFile("path/to/keyfile.json"))
If you provided no source, it would attempt to read the credentials locally and act as the service account running the operation (not applicable in your use case).
Many CIs support the export of specific env vars. Or your script / conf can do it too.
But if you want to run in a CI why you need such configuration? Integration tests?
Some services can be used locally for unit/smoke testing. Like pubsub, there is a way to run a fake/local pubsub to perform some tests.
Or perhaps I did not understand your question, in this case can you provide an example?
I am building a multilingual SAAS website builder in Golang which is run per client. Each client can have their own website and can translate their website in the desired language.
Since the feature is per client, so I collected an API key from client, which I used to translate their site content.
Here is the code,
V2
package main
import (
"context"
"fmt"
"cloud.google.com/go/translate"
"google.golang.org/api/option"
)
func main() {
translationStrings := []string{"hello"}
ctx := context.Background()
opts := option.WithAPIKey(APIKEY)
c, err := translate.NewClient(ctx, opts)
if err != nil {
fmt.Println(err)
}
defer c.Close()
resp, err := c.Translate(ctx, translationStrings, language.French,
&translate.Options{
Source: language.English,
Format: translate.Text,
})
if err != nil {
fmt.Println(err)
}
fmt.Println(resp)
}
V3
translate "cloud.google.com/go/translate/apiv3"
translatepb "google.golang.org/genproto/googleapis/cloud/translate/v3"
c, err := translate.NewTranslationClient(ctx, opts)
if err != nil {
fmt.Println(err)
}
defer c.Close()
req := &translatepb.TranslateTextRequest{
Contents: translationStrings,
TargetLanguageCode: "sr-Latn",
}
resp, err := c.TranslateText(ctx, req)
The code with V2 works well but the same code with V3 does not work. It gives error:
API keys are not supported for gRPC APIs. Remove the WithAPIKey option from your client-creating call.
As stated in the error, it is asking me to remove WithAPIKey options. But if I remove this then how will I use the api key for each client.
I have chosen to work with V3 apis because the will be translated as a whole so it will be a large request. I have read in the docs that V3 api can work in batches.
So my questions are:
how can I use per client api key structure with api V3?
Is it okay to go with the api V2 for the purpose as stated above?
Cloud Translation API v3 does not currently support API keys. It is recommended that you create a service account for Cloud Translation API v3 requests. For information on creating a service account, see Creating and managing service accounts. Your service account must be added to one of the IAM roles added for Cloud Translation API v3.
I am using grpc in my project, if i have a grpc service call helloService, should i use GetNewHelloServiceClient to get a new client in every function? Or just get once in start program?
// for example:
c.GET("/hello", SayHello)
func SayHello() {
c := pb.GetNewHelloServiceClient()
res, err := c.SayHello(context.Background(), &request)
if err != nil {
return
}
fmt.print(res.Hello)
}
Create a gRPC client just once.
Lots of networking concepts in go are designed for reuse: http clients, http transports, sql.DB connection pools etc. They are all go-routine safe & should only be created once but reused many times.
In one of my services that happens to be my loadbalancer, I am getting the following error when calling the server method in one of my deployed services:
rpc error: code = Unimplemented desc = unknown service
fooService.FooService
I have a few other services set up with gRPC and they work fine. It just seems to be this one and I am wondering if that is because it is the loadbalancer?
func GetResult(w http.ResponseWriter, r *http.Request) {
conn, errOne := grpc.Dial("redis-gateway:10006", grpc.WithInsecure())
defer conn.Close()
rClient := rs.NewRedisGatewayClient(conn)
result , errTwo := rClient.GetData(context.Background(), &rs.KeyRequest{Key: "trump", Value: "trumpVal"}, grpc.WithInsecure())
fmt.Fprintf(w, "print result: %s \n", result) //prints nil
fmt.Fprintf(w, "print error one: %v \n", errOne) // prints nil
fmt.Fprintf(w, "print error two: %s \n", errTwo) // prints error
}
The error says there is no service called fooService.FooService which is true because the dns name for the service I am calling is called foo-service. However it is the exact same setup for my other services that use gRPC and work fine. Also my proto files are correctly configured so that is not an issue.
server I am calling:
func main() {
lis, err := net.Listen("tcp", ":10006")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
grpcServer := grpc.NewServer()
newServer := &RedisGatewayServer{}
rs.RegisterRedisGatewayServer(grpcServer, newServer)
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
The function I am trying to access from client:
func (s *RedisGatewayServer) GetData(ctx context.Context, in *rs.KeyRequest)(*rs.KeyRequest, error) {
return in, nil
}
My docker and yaml files are all correct also with the right naming and ports.
I had this exact problem, and it was because of a very simple mistake: I had put the call to the service registration after the server start. The code looked like this:
err = s.Serve(listener)
if err != nil {
log.Fatalf("[x] serve: %v", err)
}
primepb.RegisterPrimeServiceServer(s, &server{})
Obviously, the registration should have been called before the server was ran:
primepb.RegisterPrimeServiceServer(s, &server{})
err = s.Serve(listener)
if err != nil {
log.Fatalf("[x] serve: %v", err)
}
thanks #dolan's for the comment, it solved the problem.
Basically we have to make sure, the method value should be same in both server and client (you can even copy the method name from the pb.go file generated from server side)
func (cc *ClientConn) Invoke(ctx context.Context, method string, args, reply interface{}, opts ...CallOption) error {
this invoke function will be there inside all the methods which you have implemented in gRPC service.
for me, my client was connecting to the wrong server port.
My server listens to localhost:50052
My client connects to localhost:50051
and hence I see this error.
I had the same problem - I was able to perform rpc call from Postman to the service whereas apigateway was able to connect to the service but on method call It gave error code 12 unknown service and the reason was in my proto files client was under package X whereas server was under package Y.
Quite silly but yeah making the package for both proto under apigateway and service solved my problem.
There are many scenarios in which this can happen. The underlying issue seems to be the same - the GRPC server is reachable but cannot service client requests.
The two scenarios I faced that are not documented in previous answers are:
1. Client and server are not running the same contract version
A breaking change introduced in the contract can cause this error.
In my case, the server was running the older version of the contract while the client was running the latest one.
A breaking change meant that the server could not resolve the service my client was asking for thus returning the unimplemented error.
2. The client is connecting to the wrong GRPC server
The client reached the incorrect server that doesn't implement the contract.
Consider this scenario if you're running multiple different GRPC services. You might be mistakingly dialing the wrong one.