grpc: add health checks to python services (#28)

also converted line endings for recommendationservice/requirements.txt from
dos to unix.
This commit is contained in:
Ahmet Alp Balkan 2018-09-19 12:34:56 -07:00 committed by GitHub
parent fc6df2daea
commit 880ee16be2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 331 additions and 259 deletions

View file

@ -30,12 +30,12 @@ spec:
- containerPort: 8080 - containerPort: 8080
readinessProbe: readinessProbe:
periodSeconds: 5 periodSeconds: 5
tcpSocket: exec:
port: 8080 command: ["/bin/grpc_health_probe", "-addr=:8080"]
livenessProbe: livenessProbe:
periodSeconds: 5 periodSeconds: 5
tcpSocket: exec:
port: 8080 command: ["/bin/grpc_health_probe", "-addr=:8080"]
resources: resources:
requests: requests:
cpu: 100m cpu: 100m

View file

@ -30,12 +30,12 @@ spec:
- containerPort: 8080 - containerPort: 8080
readinessProbe: readinessProbe:
periodSeconds: 5 periodSeconds: 5
tcpSocket: exec:
port: 8080 command: ["/bin/grpc_health_probe", "-addr=:8080"]
livenessProbe: livenessProbe:
periodSeconds: 5 periodSeconds: 5
tcpSocket: exec:
port: 8080 command: ["/bin/grpc_health_probe", "-addr=:8080"]
env: env:
- name: PRODUCT_CATALOG_SERVICE_ADDR - name: PRODUCT_CATALOG_SERVICE_ADDR
value: "productcatalogservice:3550" value: "productcatalogservice:3550"

View file

@ -1,6 +1,13 @@
# Use the grpc.io provided Python image as the base image # Use the grpc.io provided Python image as the base image
FROM grpc/python:1.0 FROM grpc/python:1.0
# download the grpc health probe
RUN GRPC_HEALTH_PROBE_VERSION=v0.1.0-alpha.1 && \
wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \
chmod +x /bin/grpc_health_probe
# show python logs as they occur # show python logs as they occur
ENV PYTHONUNBUFFERED=0 ENV PYTHONUNBUFFERED=0

View file

@ -19,13 +19,14 @@ import argparse
import os import os
import sys import sys
import time import time
import grpc
from jinja2 import Environment, FileSystemLoader, select_autoescape, TemplateError from jinja2 import Environment, FileSystemLoader, select_autoescape, TemplateError
from google.api_core.exceptions import GoogleAPICallError from google.api_core.exceptions import GoogleAPICallError
import grpc
import demo_pb2 import demo_pb2
import demo_pb2_grpc import demo_pb2_grpc
from grpc_health.v1 import health_pb2
from grpc_health.v1 import health_pb2_grpc
# from opencensus.trace.ext.grpc import server_interceptor # from opencensus.trace.ext.grpc import server_interceptor
# from opencensus.trace.samplers import always_on # from opencensus.trace.samplers import always_on
@ -56,7 +57,12 @@ env = Environment(
) )
template = env.get_template('confirmation.html') template = env.get_template('confirmation.html')
class EmailService(demo_pb2_grpc.EmailServiceServicer): class BaseEmailService(demo_pb2_grpc.EmailServiceServicer):
def Check(self, request, context):
return health_pb2.HealthCheckResponse(
status=health_pb2.HealthCheckResponse.SERVING)
class EmailService(BaseEmailService):
def __init__(self): def __init__(self):
raise Exception('cloud mail client not implemented') raise Exception('cloud mail client not implemented')
super().__init__() super().__init__()
@ -79,7 +85,6 @@ class EmailService(demo_pb2_grpc.EmailServiceServicer):
"html_body": content "html_body": content
} }
) )
print("Message sent: {}".format(response.rfc822_message_id)) print("Message sent: {}".format(response.rfc822_message_id))
def SendOrderConfirmation(self, request, context): def SendOrderConfirmation(self, request, context):
@ -104,18 +109,30 @@ class EmailService(demo_pb2_grpc.EmailServiceServicer):
return demo_pb2.Empty() return demo_pb2.Empty()
class DummyEmailService(demo_pb2_grpc.EmailServiceServicer): class DummyEmailService(BaseEmailService):
def SendOrderConfirmation(self, request, context): def SendOrderConfirmation(self, request, context):
print('A request to send order confirmation email to {} has been received.'.format(request.email)) print('A request to send order confirmation email to {} has been received.'.format(request.email))
return demo_pb2.Empty() return demo_pb2.Empty()
class HealthCheck():
def Check(self, request, context):
return health_pb2.HealthCheckResponse(
status=health_pb2.HealthCheckResponse.SERVING)
def start(dummy_mode): def start(dummy_mode):
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))#, interceptors=(tracer_interceptor,)) server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))#, interceptors=(tracer_interceptor,))
service = None
if dummy_mode: if dummy_mode:
demo_pb2_grpc.add_EmailServiceServicer_to_server(DummyEmailService(), server) service = DummyEmailService()
else: else:
raise Exception('non-dummy mode not implemented') raise Exception('non-dummy mode not implemented yet')
server.add_insecure_port('[::]:8080')
demo_pb2_grpc.add_EmailServiceServicer_to_server(service, server)
health_pb2_grpc.add_HealthServicer_to_server(service, server)
port = os.environ.get('PORT', "8080")
print("listening on port: "+port)
server.add_insecure_port('[::]:'+port)
server.start() server.start()
try: try:
while True: while True:
@ -125,5 +142,5 @@ def start(dummy_mode):
if __name__ == '__main__': if __name__ == '__main__':
print('Starting the email service in dummy mode.') print('starting the email service in dummy mode.')
start(dummy_mode = True) start(dummy_mode = True)

View file

@ -9,6 +9,7 @@ google-cloud-trace==0.19.0
googleapis-common-protos==1.5.3 googleapis-common-protos==1.5.3
grpc-google-iam-v1==0.11.4 grpc-google-iam-v1==0.11.4
grpcio==1.12.1 grpcio==1.12.1
grpcio-health-checking==1.14.1
grpcio-tools==1.12.1 grpcio-tools==1.12.1
idna==2.7 idna==2.7
Jinja2==2.10 Jinja2==2.10

View file

@ -3,6 +3,11 @@ FROM grpc/python:1.0
# show python logs as they occur # show python logs as they occur
ENV PYTHONUNBUFFERED=0 ENV PYTHONUNBUFFERED=0
# download the grpc health probe
RUN GRPC_HEALTH_PROBE_VERSION=v0.1.0-alpha.1 && \
wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \
chmod +x /bin/grpc_health_probe
# get packages # get packages
WORKDIR /recommendationservice WORKDIR /recommendationservice
COPY requirements.txt requirements.txt COPY requirements.txt requirements.txt

File diff suppressed because one or more lines are too long

View file

@ -1,19 +1,3 @@
#!/usr/bin/python
#
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
import grpc import grpc
@ -441,11 +425,6 @@ class CheckoutServiceStub(object):
Args: Args:
channel: A grpc.Channel. channel: A grpc.Channel.
""" """
self.CreateOrder = channel.unary_unary(
'/hipstershop.CheckoutService/CreateOrder',
request_serializer=demo__pb2.CreateOrderRequest.SerializeToString,
response_deserializer=demo__pb2.CreateOrderResponse.FromString,
)
self.PlaceOrder = channel.unary_unary( self.PlaceOrder = channel.unary_unary(
'/hipstershop.CheckoutService/PlaceOrder', '/hipstershop.CheckoutService/PlaceOrder',
request_serializer=demo__pb2.PlaceOrderRequest.SerializeToString, request_serializer=demo__pb2.PlaceOrderRequest.SerializeToString,
@ -458,13 +437,6 @@ class CheckoutServiceServicer(object):
""" """
def CreateOrder(self, request, context):
# missing associated documentation comment in .proto file
pass
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def PlaceOrder(self, request, context): def PlaceOrder(self, request, context):
# missing associated documentation comment in .proto file # missing associated documentation comment in .proto file
pass pass
@ -475,11 +447,6 @@ class CheckoutServiceServicer(object):
def add_CheckoutServiceServicer_to_server(servicer, server): def add_CheckoutServiceServicer_to_server(servicer, server):
rpc_method_handlers = { rpc_method_handlers = {
'CreateOrder': grpc.unary_unary_rpc_method_handler(
servicer.CreateOrder,
request_deserializer=demo__pb2.CreateOrderRequest.FromString,
response_serializer=demo__pb2.CreateOrderResponse.SerializeToString,
),
'PlaceOrder': grpc.unary_unary_rpc_method_handler( 'PlaceOrder': grpc.unary_unary_rpc_method_handler(
servicer.PlaceOrder, servicer.PlaceOrder,
request_deserializer=demo__pb2.PlaceOrderRequest.FromString, request_deserializer=demo__pb2.PlaceOrderRequest.FromString,
@ -489,3 +456,47 @@ def add_CheckoutServiceServicer_to_server(servicer, server):
generic_handler = grpc.method_handlers_generic_handler( generic_handler = grpc.method_handlers_generic_handler(
'hipstershop.CheckoutService', rpc_method_handlers) 'hipstershop.CheckoutService', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,)) server.add_generic_rpc_handlers((generic_handler,))
class AdsServiceStub(object):
"""------------Ads service------------------
"""
def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.GetAds = channel.unary_unary(
'/hipstershop.AdsService/GetAds',
request_serializer=demo__pb2.AdsRequest.SerializeToString,
response_deserializer=demo__pb2.AdsResponse.FromString,
)
class AdsServiceServicer(object):
"""------------Ads service------------------
"""
def GetAds(self, request, context):
# missing associated documentation comment in .proto file
pass
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def add_AdsServiceServicer_to_server(servicer, server):
rpc_method_handlers = {
'GetAds': grpc.unary_unary_rpc_method_handler(
servicer.GetAds,
request_deserializer=demo__pb2.AdsRequest.FromString,
response_serializer=demo__pb2.AdsResponse.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'hipstershop.AdsService', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))

View file

@ -15,16 +15,19 @@
# limitations under the License. # limitations under the License.
import grpc import grpc
import demo_pb2
import demo_pb2_grpc
from concurrent import futures from concurrent import futures
import time import time
import traceback import traceback
import random import random
import os import os
import googleclouddebugger import googleclouddebugger
import demo_pb2
import demo_pb2_grpc
from grpc_health.v1 import health_pb2
from grpc_health.v1 import health_pb2_grpc
# TODO(morganmclean,ahmetb) tracing currently disabled due to memory leak (see TODO below) # TODO(morganmclean,ahmetb) tracing currently disabled due to memory leak (see TODO below)
# from opencensus.trace.ext.grpc import server_interceptor # from opencensus.trace.ext.grpc import server_interceptor
# from opencensus.trace.samplers import always_on # from opencensus.trace.samplers import always_on
@ -35,7 +38,7 @@ class RecommendationService(demo_pb2_grpc.RecommendationServiceServicer):
def ListRecommendations(self, request, context): def ListRecommendations(self, request, context):
max_responses = 5 max_responses = 5
# fetch list of products from product catalog stub # fetch list of products from product catalog stub
cat_response = stub.ListProducts(demo_pb2.Empty()) cat_response = product_catalog_stub.ListProducts(demo_pb2.Empty())
product_ids = [x.id for x in cat_response.products] product_ids = [x.id for x in cat_response.products]
filtered_products = list(set(product_ids)-set(request.product_ids)) filtered_products = list(set(product_ids)-set(request.product_ids))
num_products = len(filtered_products) num_products = len(filtered_products)
@ -50,6 +53,11 @@ class RecommendationService(demo_pb2_grpc.RecommendationServiceServicer):
response.product_ids.extend(prod_list) response.product_ids.extend(prod_list)
return response return response
def Check(self, request, context):
return health_pb2.HealthCheckResponse(
status=health_pb2.HealthCheckResponse.SERVING)
if __name__ == "__main__": if __name__ == "__main__":
print("initializing recommendationservice") print("initializing recommendationservice")
@ -78,16 +86,16 @@ if __name__ == "__main__":
if catalog_addr == "": if catalog_addr == "":
raise Exception('PRODUCT_CATALOG_SERVICE_ADDR environment variable not set') raise Exception('PRODUCT_CATALOG_SERVICE_ADDR environment variable not set')
print("product catalog address: " + catalog_addr) print("product catalog address: " + catalog_addr)
# stub for product catalog service
channel = grpc.insecure_channel(catalog_addr) channel = grpc.insecure_channel(catalog_addr)
stub = demo_pb2_grpc.ProductCatalogServiceStub(channel) product_catalog_stub = demo_pb2_grpc.ProductCatalogServiceStub(channel)
# create gRPC server # create gRPC server
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) # ,interceptors=(tracer_interceptor,)) server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) # ,interceptors=(tracer_interceptor,))
# add class to gRPC server # add class to gRPC server
demo_pb2_grpc.add_RecommendationServiceServicer_to_server(RecommendationService(), server) service = RecommendationService()
demo_pb2_grpc.add_RecommendationServiceServicer_to_server(service, server)
health_pb2_grpc.add_HealthServicer_to_server(service, server)
# start server # start server
print("listening on port: " + port) print("listening on port: " + port)

View file

@ -12,6 +12,7 @@ google-cloud-trace==0.19.0
google-python-cloud-debugger==2.8 google-python-cloud-debugger==2.8
googleapis-common-protos==1.5.3 googleapis-common-protos==1.5.3
grpcio==1.13.0 grpcio==1.13.0
grpcio-health-checking==1.14.1
grpcio-tools==1.0.0 grpcio-tools==1.0.0
httplib2==0.11.3 httplib2==0.11.3
idna==2.7 idna==2.7