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.protosyntax = "proto3";
package service1;
service ServiceOneServer {
rpc SayHello(HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
service2.protosyntax = "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-webprotoc -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!