Dec 27, 2019
4 mins read
Envoy is a proxy for modern web app. More importantly, it has a first class support for gRPC.
In this example, I want to extend the helloworld example from the official grpc-web: When you have multiple service, how can one manages the traffic by Envoy.
I will use a cluster which managed by kubernetes, to integrate envoy with grpc-web, we have several things to do:
service1.proto
syntax = "proto3";
package service1;
service ServiceOneServer {
rpc SayHello(HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
service2.proto
syntax = "proto3";
package service2;
service ServiceTwoServer {
rpc SayHello(HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
protoc
and protoc-gen-grpc-web
protoc -I=. service1.proto \
--js_out=import_style=commonjs:. \
--grpc-web_out=import_style=commonjs,mode=grpcwebtext:.
protoc -I=. service2.proto \
--js_out=import_style=commonjs:. \
--grpc-web_out=import_style=commonjs,mode=grpcwebtext:.
inside the generated service1_grpc_web_pb.js
you cand find something like this:
const methodDescriptor_Service1_SayHello = new grpc.web.MethodDescriptor(
'/service1.ServiceOneServer/SayHello',
grpc.web.MethodType.UNARY,
...
note that the '/service1.ServiceOneServer/SayHello'
is the route that generated by grpc-web and we will observe the pattern /{package}.{service}/{api}
and use it to configure our envoy proxy.
I will skip the implementations of the service1
and service2
, you can do any languages that support gRPC
Now it’s time to write an envoy.yaml
file to configure the behavior of Envoy
# for envoy admin
admin:
access_log_path: /tmp/admin_access.log
address:
socket_address: { address: 0.0.0.0, port_value: 9901 }
# for the main proxy
static_resources:
listeners:
- name: listener_0
address:
# the port_value here specify the port which envoy will listen at
socket_address: { address: 0.0.0.0, port_value: 8080 }
filter_chains:
- filters:
- name: envoy.http_connection_manager
config:
codec_type: auto
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
# add all your service here
# the prefix in the routes' match is exactly the pattern in the previous generated code
routes:
- match: { prefix: "/service1.ServiceOne" }
route:
# this will map to name tag in line 50
cluster: service1
max_grpc_timeout: 10s
- match: { prefix: "/service2.ServiceTwo" }
route:
cluster: service2
max_grpc_timeout: 10s
cors:
allow_origin:
- "*"
allow_methods: GET, PUT, DELETE, POST, OPTIONS
allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
max_age: "1728000"
expose_headers: custom-header-1,grpc-status,grpc-message
http_filters:
- name: envoy.grpc_web
- name: envoy.cors
- name: envoy.router
clusters:
# the name here will be matched by the cluster tag in line 31
- name: service1
connect_timeout: 0.25s
type: strict_dns
http2_protocol_options: {}
lb_policy: round_robin
# the address and port_value here specify where the traffic should be pass to
# since it is inside k8s, the address can simply be the port name thanks to kubernetes dns
hosts: [{ socket_address: { address: service1, port_value: 10001 }}]
- name: service2
connect_timeout: 0.25s
type: strict_dns
http2_protocol_options: {}
lb_policy: round_robin
hosts: [{ socket_address: { address: service2, port_value: 10002 }}]
More explicitly, the service1’s yaml loos like this, note that the ports.name in containers is exactly the name used in envoy’s config:
hosts: [{ socket_address: { address: service1, port_value: 10001 }}]
apiVersion: v1
kind: Service
metadata:
labels:
app: service1
name: service1
spec:
ports:
- port: 10001
targetPort: 10001
selector:
app: service1
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: service1
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
replicas: 1
selector:
matchLabels:
app: service1
template:
metadata:
labels:
app: service1
spec:
containers:
- name: service1
image: somewhere/service1
imagePullPolicy: Never
ports:
- name: service1
containerPort: 10001
protocol: TCP
Add a dockerfile, the dockerfile itself is quite simple, all you have to do is to copy the envoy.yaml you just wrote to the envoy image
FROM envoyproxy/envoy:latest
COPY ./configs/envoy.yaml /etc/envoy/envoy.yaml
CMD /usr/local/bin/envoy -c /etc/envoy/envoy.yaml
apiVersion: v1
kind: Service
metadata:
name: envoy
spec:
type: NodePort
selector:
app: envoy
ports:
- name: envoy
protocol: TCP
port: 80
targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: envoy
spec:
replicas: 1
selector:
matchLabels:
app: envoy
template:
metadata:
labels:
app: envoy
spec:
containers:
- name: envoy
image: local/envoy
imagePullPolicy: Never
ports:
- name: envoy
containerPort: 8080
And finally you are done :)
Sharing is caring!