Set up a proxyless gRPC service mesh on GKE
This pages describes how to deploy an example proxyless gRPC client and server to a Cloud Service Mesh.
Prerequisites
As a starting point, this guide assumes that you have already:
Requirements
This section lists the requirements for supported services:
- gRPC C++ - version 1.62.0 or later
- gRPC Java - version 1.65.0 or later
- gRPC Go - version 1.65.0 or later
- gRPC Python - version 1.65.0 or later
Set up the Service
Deploy a gRPC service:
C++
kubectl apply -f - <<EOF
---
apiVersion: v1
kind: Service
metadata:
name: helloworld
spec:
selector:
app: psm-grpc-server
ports:
- port: 50051
targetPort: 50051
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: psm-grpc-server
labels:
app: psm-grpc-server
spec:
replicas: 2
selector:
matchLabels:
app: psm-grpc-server
template:
metadata:
labels:
app: psm-grpc-server
spec:
containers:
- name: psm-grpc-server
image: grpc/csm-o11y-example-cpp-server:latest
imagePullPolicy: Always
args:
"--port=50051"
ports:
- containerPort: 50051
env:
- name: GRPC_XDS_BOOTSTRAP
value: "/tmp/grpc-xds/td-grpc-bootstrap.json"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NAMESPACE_NAME
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: CSM_WORKLOAD_NAME
value: psm-grpc-server
- name: OTEL_RESOURCE_ATTRIBUTES
value: k8s.pod.name=\$(POD_NAME),k8s.namespace.name=\$(NAMESPACE_NAME),k8s.container.name=\$(CONTAINER_NAME)
volumeMounts:
- mountPath: /tmp/grpc-xds/
name: grpc-td-conf
readOnly: true
initContainers:
- name: grpc-td-init
image: gcr.io/trafficdirector-prod/td-grpc-bootstrap:0.16.0
imagePullPolicy: Always
args:
- "--output=/tmp/bootstrap/td-grpc-bootstrap.json"
- "--vpc-network-name=default"
- "--xds-server-uri=trafficdirector.googleapis.com:443"
- "--generate-mesh-id"
volumeMounts:
- mountPath: /tmp/bootstrap/
name: grpc-td-conf
volumes:
- name: grpc-td-conf
emptyDir:
medium: Memory
EOF
Java
kubectl apply -f - <<EOF
---
apiVersion: v1
kind: Service
metadata:
name: helloworld
spec:
selector:
app: psm-grpc-server
ports:
- port: 50051
targetPort: 50051
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: psm-grpc-server
labels:
app: psm-grpc-server
spec:
replicas: 2
selector:
matchLabels:
app: psm-grpc-server
template:
metadata:
labels:
app: psm-grpc-server
spec:
containers:
- name: psm-grpc-server
image: grpc/csm-o11y-example-java-server:latest
imagePullPolicy: Always
args:
"50051"
"9464"
ports:
- containerPort: 50051
env:
- name: GRPC_XDS_BOOTSTRAP
value: "/tmp/grpc-xds/td-grpc-bootstrap.json"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NAMESPACE_NAME
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: CSM_WORKLOAD_NAME
value: psm-grpc-server
- name: OTEL_RESOURCE_ATTRIBUTES
value: k8s.pod.name=\$(POD_NAME),k8s.namespace.name=\$(NAMESPACE_NAME),k8s.container.name=\$(CONTAINER_NAME)
volumeMounts:
- mountPath: /tmp/grpc-xds/
name: grpc-td-conf
readOnly: true
initContainers:
- name: grpc-td-init
image: gcr.io/trafficdirector-prod/td-grpc-bootstrap:0.16.0
imagePullPolicy: Always
args:
- "--output=/tmp/bootstrap/td-grpc-bootstrap.json"
- "--vpc-network-name=default"
- "--xds-server-uri=trafficdirector.googleapis.com:443"
- "--generate-mesh-id"
volumeMounts:
- mountPath: /tmp/bootstrap/
name: grpc-td-conf
volumes:
- name: grpc-td-conf
emptyDir:
medium: Memory
EOF
Go
kubectl apply -f - <<EOF
---
apiVersion: v1
kind: Service
metadata:
name: helloworld
spec:
selector:
app: psm-grpc-server
ports:
- port: 50051
targetPort: 50051
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: psm-grpc-server
labels:
app: psm-grpc-server
spec:
replicas: 2
selector:
matchLabels:
app: psm-grpc-server
template:
metadata:
labels:
app: psm-grpc-server
spec:
containers:
- name: psm-grpc-server
image: grpc/csm-o11y-example-go-server:latest
imagePullPolicy: Always
args:
"--port=50051"
ports:
- containerPort: 50051
env:
- name: GRPC_XDS_BOOTSTRAP
value: "/tmp/grpc-xds/td-grpc-bootstrap.json"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NAMESPACE_NAME
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: CSM_WORKLOAD_NAME
value: psm-grpc-server
- name: OTEL_RESOURCE_ATTRIBUTES
value: k8s.pod.name=\$(POD_NAME),k8s.namespace.name=\$(NAMESPACE_NAME),k8s.container.name=\$(CONTAINER_NAME)
volumeMounts:
- mountPath: /tmp/grpc-xds/
name: grpc-td-conf
readOnly: true
initContainers:
- name: grpc-td-init
image: gcr.io/trafficdirector-prod/td-grpc-bootstrap:0.16.0
imagePullPolicy: Always
args:
- "--output=/tmp/bootstrap/td-grpc-bootstrap.json"
- "--vpc-network-name=default"
- "--xds-server-uri=trafficdirector.googleapis.com:443"
- "--generate-mesh-id"
volumeMounts:
- mountPath: /tmp/bootstrap/
name: grpc-td-conf
volumes:
- name: grpc-td-conf
emptyDir:
medium: Memory
EOF
Python
kubectl apply -f - <<EOF
---
apiVersion: v1
kind: Service
metadata:
name: helloworld
spec:
selector:
app: psm-grpc-server
ports:
- port: 50051
targetPort: 50051
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: psm-grpc-server
labels:
app: psm-grpc-server
spec:
replicas: 2
selector:
matchLabels:
app: psm-grpc-server
template:
metadata:
labels:
app: psm-grpc-server
spec:
containers:
- name: psm-grpc-server
image: grpc/csm-o11y-example-python-server:latest
imagePullPolicy: Always
args:
"--port=50051"
ports:
- containerPort: 50051
env:
- name: GRPC_XDS_BOOTSTRAP
value: "/tmp/grpc-xds/td-grpc-bootstrap.json"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NAMESPACE_NAME
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: CSM_WORKLOAD_NAME
value: psm-grpc-server
- name: OTEL_RESOURCE_ATTRIBUTES
value: k8s.pod.name=\$(POD_NAME),k8s.namespace.name=\$(NAMESPACE_NAME),k8s.container.name=\$(CONTAINER_NAME)
volumeMounts:
- mountPath: /tmp/grpc-xds/
name: grpc-td-conf
readOnly: true
initContainers:
- name: grpc-td-init
image: gcr.io/trafficdirector-prod/td-grpc-bootstrap:0.16.0
imagePullPolicy: Always
args:
- "--output=/tmp/bootstrap/td-grpc-bootstrap.json"
- "--vpc-network-name=default"
- "--xds-server-uri=trafficdirector.googleapis.com:443"
- "--generate-mesh-id"
volumeMounts:
- mountPath: /tmp/bootstrap/
name: grpc-td-conf
volumes:
- name: grpc-td-conf
emptyDir:
medium: Memory
EOF
The output is similar to
service/helloworld created
deployment.apps/psm-grpc-server created
Verify that the Pods have been created:
kubectl get pods
The output is similar to
NAME READY STATUS RESTARTS AGE psm-grpc-server-65966bf76d-2wwxz 1/1 Running 0 13s psm-grpc-server-65966bf76d-nbxd2 1/1 Running 0 13s
Wait for all Pods to be ready and have the
Status
Running before continuing.Deploy a HTTPRoute:
kubectl apply -f - <<EOF apiVersion: gateway.networking.k8s.io/v1beta1 kind: HTTPRoute metadata: name: app1 spec: parentRefs: - name: helloworld kind: Service group: "" rules: - backendRefs: - name: helloworld port: 50051 EOF
This command creates an HTTPRoute called app1 and sends all RPCs to the
helloworld
service.Note that the
parentRef
service is alsohelloworld
which means our HTTPRoute is attached to this service and will process all RPCs addressed to this service. In case of proxyless gRPC it means any client sending RPCs on a gRPC channel for targetxds:///helloworld.default.svc.cluster.local:50051
.Verify that the new app1 HTTPRoute has been created:
kubectl get httproute
The output is similar to
NAME HOSTNAMES AGE app1 72s
Set up the Client
This sections describes how to use a gRPC client to verify that Cloud Service Mesh is routing traffic correctly in the mesh.
Run a gRPC client and direct it to use the routing configuration specified by the HTTPRoute:
C++
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: psm-grpc-client
labels:
app: psm-grpc-client
spec:
replicas: 1
selector:
matchLabels:
app: psm-grpc-client
template:
metadata:
labels:
app: psm-grpc-client
spec:
containers:
- name: psm-grpc-client
image: grpc/csm-o11y-example-cpp-server:latest
imagePullPolicy: Always
args:
"--target=xds:///helloworld.default.svc.cluster.local:50051"
env:
- name: GRPC_XDS_BOOTSTRAP
value: "/tmp/grpc-xds/td-grpc-bootstrap.json"
- name: CSM_WORKLOAD_NAME
value: psm-grpc-client
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NAMESPACE_NAME
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: CONTAINER_NAME
value: psm-grpc-client
- name: OTEL_RESOURCE_ATTRIBUTES
value: k8s.pod.name=\$(POD_NAME),k8s.namespace.name=\$(NAMESPACE_NAME),k8s.container.name=\$(CONTAINER_NAME)
volumeMounts:
- mountPath: /tmp/grpc-xds/
name: grpc-td-conf
readOnly: true
initContainers:
- name: grpc-td-init
image: gcr.io/trafficdirector-prod/td-grpc-bootstrap:0.16.0
imagePullPolicy: Always
args:
- "--output=/tmp/bootstrap/td-grpc-bootstrap.json"
- "--vpc-network-name=default"
- "--xds-server-uri=trafficdirector.googleapis.com:443"
- "--generate-mesh-id"
volumeMounts:
- mountPath: /tmp/bootstrap/
name: grpc-td-conf
volumes:
- name: grpc-td-conf
emptyDir:
medium: Memory
EOF
Java
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: psm-grpc-client
labels:
app: psm-grpc-client
spec:
replicas: 1
selector:
matchLabels:
app: psm-grpc-client
template:
metadata:
labels:
app: psm-grpc-client
spec:
containers:
- name: psm-grpc-client
image: grpc/csm-o11y-example-java-server:latest
imagePullPolicy: Always
args:
"world"<br/>"xds:///helloworld.default.svc.cluster.local:50051"
"9464"
env:
- name: GRPC_XDS_BOOTSTRAP
value: "/tmp/grpc-xds/td-grpc-bootstrap.json"
- name: CSM_WORKLOAD_NAME
value: psm-grpc-client
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NAMESPACE_NAME
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: CONTAINER_NAME
value: psm-grpc-client
- name: OTEL_RESOURCE_ATTRIBUTES
value: k8s.pod.name=\$(POD_NAME),k8s.namespace.name=\$(NAMESPACE_NAME),k8s.container.name=\$(CONTAINER_NAME)
volumeMounts:
- mountPath: /tmp/grpc-xds/
name: grpc-td-conf
readOnly: true
initContainers:
- name: grpc-td-init
image: gcr.io/trafficdirector-prod/td-grpc-bootstrap:0.16.0
imagePullPolicy: Always
args:
- "--output=/tmp/bootstrap/td-grpc-bootstrap.json"
- "--vpc-network-name=default"
- "--xds-server-uri=trafficdirector.googleapis.com:443"
- "--generate-mesh-id"
volumeMounts:
- mountPath: /tmp/bootstrap/
name: grpc-td-conf
volumes:
- name: grpc-td-conf
emptyDir:
medium: Memory
EOF
Go
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: psm-grpc-client
labels:
app: psm-grpc-client
spec:
replicas: 1
selector:
matchLabels:
app: psm-grpc-client
template:
metadata:
labels:
app: psm-grpc-client
spec:
containers:
- name: psm-grpc-client
image: grpc/csm-o11y-example-go-server:latest
imagePullPolicy: Always
args:
"--target=xds:///helloworld.default.svc.cluster.local:50051"
env:
- name: GRPC_XDS_BOOTSTRAP
value: "/tmp/grpc-xds/td-grpc-bootstrap.json"
- name: CSM_WORKLOAD_NAME
value: psm-grpc-client
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NAMESPACE_NAME
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: CONTAINER_NAME
value: psm-grpc-client
- name: OTEL_RESOURCE_ATTRIBUTES
value: k8s.pod.name=\$(POD_NAME),k8s.namespace.name=\$(NAMESPACE_NAME),k8s.container.name=\$(CONTAINER_NAME)
volumeMounts:
- mountPath: /tmp/grpc-xds/
name: grpc-td-conf
readOnly: true
initContainers:
- name: grpc-td-init
image: gcr.io/trafficdirector-prod/td-grpc-bootstrap:0.16.0
imagePullPolicy: Always
args:
- "--output=/tmp/bootstrap/td-grpc-bootstrap.json"
- "--vpc-network-name=default"
- "--xds-server-uri=trafficdirector.googleapis.com:443"
- "--generate-mesh-id"
volumeMounts:
- mountPath: /tmp/bootstrap/
name: grpc-td-conf
volumes:
- name: grpc-td-conf
emptyDir:
medium: Memory
EOF
Python
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: psm-grpc-client
labels:
app: psm-grpc-client
spec:
replicas: 1
selector:
matchLabels:
app: psm-grpc-client
template:
metadata:
labels:
app: psm-grpc-client
spec:
containers:
- name: psm-grpc-client
image: grpc/csm-o11y-example-python-server:latest
imagePullPolicy: Always
args:
"--target=xds:///helloworld.default.svc.cluster.local:50051"
env:
- name: GRPC_XDS_BOOTSTRAP
value: "/tmp/grpc-xds/td-grpc-bootstrap.json"
- name: CSM_WORKLOAD_NAME
value: psm-grpc-client
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NAMESPACE_NAME
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: CONTAINER_NAME
value: psm-grpc-client
- name: OTEL_RESOURCE_ATTRIBUTES
value: k8s.pod.name=\$(POD_NAME),k8s.namespace.name=\$(NAMESPACE_NAME),k8s.container.name=\$(CONTAINER_NAME)
volumeMounts:
- mountPath: /tmp/grpc-xds/
name: grpc-td-conf
readOnly: true
initContainers:
- name: grpc-td-init
image: gcr.io/trafficdirector-prod/td-grpc-bootstrap:0.16.0
imagePullPolicy: Always
args:
- "--output=/tmp/bootstrap/td-grpc-bootstrap.json"
- "--vpc-network-name=default"
- "--xds-server-uri=trafficdirector.googleapis.com:443"
- "--generate-mesh-id"
volumeMounts:
- mountPath: /tmp/bootstrap/
name: grpc-td-conf
volumes:
- name: grpc-td-conf
emptyDir:
medium: Memory
EOF
The output is similar to
deployment.apps/psm-grpc-client created
Google Cloud Managed Service for Prometheus Setup (Optional)
You can deploy the Google Cloud Managed Service for Prometheus
PodMonitoring
resource to export the metrics to Cloud Monitoring.
For servers, run the following command:
kubectl apply -f - <<EOF apiVersion: monitoring.googleapis.com/v1 kind: PodMonitoring metadata: name: psm-grpc-server-gmp spec: selector: matchLabels: app: psm-grpc-server endpoints: - port: 9464 interval: 10s EOF
For clients, , run the following command:
kubectl apply -f - <<EOF apiVersion: monitoring.googleapis.com/v1 kind: PodMonitoring metadata: name: psm-grpc-client-gmp spec: selector: matchLabels: app: psm-grpc-client endpoints: - port: 9464 interval: 10s EOF
After deploying the
PodMonitoring
resource, each matching pod'slocalhost:9464/metrics
will be scraped every 10 seconds and will export the results to Cloud Monitoring.
To view the metrics in Cloud Monitoring, perform the following steps:
you can navigate to the Metrics Explorer section of your Google Cloud console, select Prometheus Target > Grpc to find the metrics.
Verify the proxyless gRPC service mesh setup and mesh Observability
Check the metrics output from the client Pod:
kubectl exec $(kubectl get po | grep psm-grpc-client | awk '{print $1;}') -- /usr/bin/curl -s http://localhost:9464/metrics
The output shows the metrics (with the service mesh labels) that were scraped from the endpoint:
Defaulted container "psm-grpc-client" out of: psm-grpc-client, grpc-td-init (init) # HELP exposer_transferred_bytes_total Transferred bytes to metrics services # TYPE exposer_transferred_bytes_total counter exposer_transferred_bytes_total 36047 # HELP exposer_scrapes_total Number of times metrics were scraped # TYPE exposer_scrapes_total counter exposer_scrapes_total 1 # HELP exposer_request_latencies Latencies of serving scrape requests, in microseconds # TYPE exposer_request_latencies summary exposer_request_latencies_count 1 exposer_request_latencies_sum 1246 exposer_request_latencies{quantile="0.5"} Nan exposer_request_latencies{quantile="0.9"} Nan exposer_request_latencies{quantile="0.99"} Nan # HELP grpc_client_attempt_rcvd_total_compressed_message_size_By Compressed message bytes received per call attempt # TYPE grpc_client_attempt_rcvd_total_compressed_message_size_By histogram grpc_client_attempt_rcvd_total_compressed_message_size_By_count{csm_mesh_id="gsmmesh-35av-my-cluster-3-us-east7-c-35av6nnbi9jz",csm_remote_workload_canonical_service="unknown",csm_remote_workload_cluster_name="my-cluster-3",csm_remote_workload_location="us-east7-c",csm_remote_workload_name="psm-grpc-server",csm_remote_workload_namespace_name="default",csm_remote_workload_project_id="grpc-testing",csm_remote_workload_type="gcp_kubernetes_engine",csm_workload_canonical_service="unknown",grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="xds:///helloworld.default.svc.cluster.local:50051"} 114 1695445356167 ...
Check the metrics output from the server:
kubectl exec $(kubectl get po | grep psm-grpc-server | awk '{print $1;}' | head -n 1) -- /usr/bin/curl -s http://localhost:9464/metrics
The output shows the metrics (with the service mesh labels) that were scraped from the endpoint:
Defaulted container "psm-grpc-server" out of: psm-grpc-server, grpc-td-init (init) # HELP exposer_transferred_bytes_total Transferred bytes to metrics services # TYPE exposer_transferred_bytes_total counter exposer_transferred_bytes_total 35945 # HELP exposer_scrapes_total Number of times metrics were scraped # TYPE exposer_scrapes_total counter exposer_scrapes_total 1 # HELP exposer_request_latencies Latencies of serving scrape requests, in microseconds # TYPE exposer_request_latencies summary exposer_request_latencies_count 1 exposer_request_latencies_sum 2369 exposer_request_latencies{quantile="0.5"} Nan exposer_request_latencies{quantile="0.9"} Nan exposer_request_latencies{quantile="0.99"} Nan # HELP target Target metadata # TYPE target gauge target_info{otel_scope_name="grpc-c++",otel_scope_version="1.62.0-dev",service_name="unknown_service",k8s_pod_name="psm-grpc-server-6f75c8f857-qst5k",k8s_namespace_name="default",k8s_container_name="$(CONTAINER_NAME)",telemetry_sdk_version="1.13.0",telemetry_sdk_name="opentelemetry",telemetry_sdk_language="cpp"} 1 1708157423871 # HELP grpc_server_call_sent_total_compressed_message_size_bytes Compressed message bytes sent per server call # TYPE grpc_server_call_sent_total_compressed_message_size_bytes histogram grpc_server_call_sent_total_compressed_message_size_bytes_count{csm_mesh_id="gsmmesh-fogj-my-cluster-us-central1-a-fogjnvmqo8fn",csm_remote_workload_canonical_service="unknown",csm_remote_workload_cluster_name="my-cluster",csm_remote_workload_location="us-central1-a",csm_remote_workload_name="test-workload-name",csm_remote_workload_namespace_name="default",csm_remote_workload_project_id="project-id",csm_remote_workload_type="gcp_kubernetes_engine",csm_workload_canonical_service="unknown",grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",otel_scope_name="grpc-c++",otel_scope_version="1.62.0-dev"} 1796 1708157423871 …
Troubleshoot Cloud Service Mesh observability
This section shows how to troubleshoot common issues.
An attribute value on the metric shows up as unknown
If an attribute value on the metric shows up as unknown, make sure that all binaries involved (both client and server) are set up a proxyless gRPC service mesh on GKE.
Note that Cloud Service Mesh Observability determines the mesh topological information through environment labels. Make sure that the Pod or service spec for both the client and the service specify all the labels as described in the example.
Describe the psm-grpc-server deployment:
kubectl describe Deployment psm-grpc-server | grep "psm-grpc-server:" -A 12
The output is similar to:
psm-grpc-server: Image: grpc/csm-example-server:2024-02-13 Port: 50051/TCP Host Port: 0/TCP Args: --port=50051 Environment: GRPC_XDS_BOOTSTRAP: /tmp/grpc-xds/td-grpc-bootstrap.json POD_NAME: (v1:metadata.name) NAMESPACE_NAME: (v1:metadata.namespace) CSM_WORKLOAD_NAME: psm-grpc-server OTEL_RESOURCE_ATTRIBUTES: k8s.pod.name=$(POD_NAME),k8s.namespace.name=$(NAMESPACE_NAME),k8s.container.name=$(CONTAINER_NAME)
Describe the psm-grpc-client deployment:
kubectl describe Deployment psm-grpc-client | grep "psm-grpc-client:" -A 12
The output is similar to:
psm-grpc-client: Image: grpc/csm-example-client:2024-02-13 Port: <none> Host Port: <none> Args: --target=xds:///helloworld.default.svc.cluster.local:50051 Environment: GRPC_XDS_BOOTSTRAP: /tmp/grpc-xds/td-grpc-bootstrap.json CSM_WORKLOAD_NAME: test-workload-name POD_NAME: (v1:metadata.name) NAMESPACE_NAME: (v1:metadata.namespace) CONTAINER_NAME: psm-grpc-client
Metrics are not displayed
If you're using the Prometheus exporter, verify that the URL for the Prometheus exporter is set up as expected.
Make sure that the gRPC channels are Cloud Service Mesh enabled. The channels
should have a target of the form xds:///
. gRPC servers are always enabled for
Cloud Service Mesh.