Merge branch 'master' into pull/28

# Conflicts:
#	.gitignore
#	kubernetes-manifests/frontend.yaml
#	kubernetes-manifests/loadgenerator.yaml
#	release/kubernetes-manifests.yaml
#	src/loadgenerator/Dockerfile
#	src/loadgenerator/loadgen.sh
This commit is contained in:
asamanta 2020-03-12 16:39:04 -04:00
commit a4148a51c8
29 changed files with 462 additions and 44 deletions

4
.gitignore vendored
View file

@ -9,5 +9,7 @@ pkg/
.idea
.skaffold-*.yaml
.kubernetes-manifests-*/
env/
.project
.eclipse.buildship.core.prefs
.eclipse.buildship.core.prefs

1
build.out Normal file
View file

@ -0,0 +1 @@
{"builds":[{"imageName":"gcr.io/lesser-weevil/emailservice","tag":"gcr.io/lesser-weevil/emailservice:v0.1.0-74-g468e8dc@sha256:7256c7e27888c37e06c19036032b594c5240d9a80bbe54963fda437346239742"},{"imageName":"gcr.io/lesser-weevil/productcatalogservice","tag":"gcr.io/lesser-weevil/productcatalogservice:v0.1.0-74-g468e8dc@sha256:19b199f0a976a3cebd5cf00d99d040a82c9824506de988d9485fffa828428898"},{"imageName":"gcr.io/lesser-weevil/recommendationservice","tag":"gcr.io/lesser-weevil/recommendationservice:v0.1.0-74-g468e8dc@sha256:3300a437de46c8d2e2144540ce0fe51ec2ea7b728c724021182337fd051bacad"},{"imageName":"gcr.io/lesser-weevil/shippingservice","tag":"gcr.io/lesser-weevil/shippingservice:v0.1.0-74-g468e8dc@sha256:dd8c49052601f9e3873d0c8034e6010645d83768df3526ee99cb43a71ce8344d"},{"imageName":"gcr.io/lesser-weevil/checkoutservice","tag":"gcr.io/lesser-weevil/checkoutservice:v0.1.0-74-g468e8dc@sha256:0955d209db61815892477b6f06b0f7a4c3c3d96e714c8c472faa5a442a2af875"},{"imageName":"gcr.io/lesser-weevil/paymentservice","tag":"gcr.io/lesser-weevil/paymentservice:v0.1.0-74-g468e8dc@sha256:07903a2bddf38920a940a2ab9aaddfbc7c06b0aa27dc07aaf8b9694722b44d92"},{"imageName":"gcr.io/lesser-weevil/currencyservice","tag":"gcr.io/lesser-weevil/currencyservice:v0.1.0-74-g468e8dc@sha256:df6b933e6b42a27035419119f93b471e4f9973b2934c0124da399c64dfc779a9"},{"imageName":"gcr.io/lesser-weevil/cartservice","tag":"gcr.io/lesser-weevil/cartservice:v0.1.0-74-g468e8dc@sha256:538f48876ad2f13603b4992bb3b75cde587333920f0a9f7e4d6d54924d349d3b"},{"imageName":"gcr.io/lesser-weevil/frontend","tag":"gcr.io/lesser-weevil/frontend:v0.1.0-74-g468e8dc@sha256:8b552b7de8dd9c45439d4a334f9d8dfeec0b989075a85e11cb17a81f2e47bab5"},{"imageName":"gcr.io/lesser-weevil/loadgenerator","tag":"gcr.io/lesser-weevil/loadgenerator:v0.1.0-74-g468e8dc@sha256:e874a37782981f587238a8b5ce807f7ce65d4c2f97c6bb847462de5a1aeb74c0"},{"imageName":"gcr.io/lesser-weevil/adservice","tag":"gcr.io/lesser-weevil/adservice:v0.1.0-74-g468e8dc@sha256:446a5743ebf2b7f27758c9d7588742223a42328838e7de222363f170d30e6231"},{"imageName":"gcr.io/lesser-weevil/pingjob","tag":"gcr.io/lesser-weevil/pingjob:v0.1.0-74-g468e8dc@sha256:0ade87236d2d837ca1d9b468497b7d05d729b6420120da35b8a9e64b20ce10a8"}]}

View file

@ -17,6 +17,8 @@ kind: Deployment
metadata:
name: adservice
spec:
revisionHistoryLimit: 1
replicas: 5
selector:
matchLabels:
app: adservice

View file

@ -17,6 +17,8 @@ kind: Deployment
metadata:
name: cartservice
spec:
revisionHistoryLimit: 1
replicas: 10
selector:
matchLabels:
app: cartservice
@ -41,10 +43,10 @@ spec:
resources:
requests:
cpu: 200m
memory: 64Mi
memory: 128Mi
limits:
cpu: 300m
memory: 128Mi
memory: 256Mi
readinessProbe:
initialDelaySeconds: 15
exec:

View file

@ -17,6 +17,7 @@ kind: Deployment
metadata:
name: checkoutservice
spec:
revisionHistoryLimit: 1
selector:
matchLabels:
app: checkoutservice

View file

@ -17,6 +17,7 @@ kind: Deployment
metadata:
name: currencyservice
spec:
revisionHistoryLimit: 1
selector:
matchLabels:
app: currencyservice
@ -67,3 +68,22 @@ spec:
- name: grpc
port: 7000
targetPort: 7000
---
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: currencyservice
labels:
app: currencyservice
spec:
maxReplicas: 7
minReplicas: 1
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: currencyservice
metrics:
- type: Resource
resource:
name: cpu
targetAverageUtilization: 19

View file

@ -17,6 +17,7 @@ kind: Deployment
metadata:
name: emailservice
spec:
revisionHistoryLimit: 1
selector:
matchLabels:
app: emailservice

View file

@ -15,15 +15,82 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
name: frontend-a
spec:
revisionHistoryLimit: 1
selector:
matchLabels:
app: frontend
env: a
template:
metadata:
labels:
app: frontend
env: a
spec:
containers:
- name: server
image: frontend
ports:
- containerPort: 8080
readinessProbe:
initialDelaySeconds: 10
httpGet:
path: "/_healthz"
port: 8080
httpHeaders:
- name: "Cookie"
value: "shop_session-id=x-readiness-probe"
livenessProbe:
initialDelaySeconds: 10
httpGet:
path: "/_healthz"
port: 8080
httpHeaders:
- name: "Cookie"
value: "shop_session-id=x-liveness-probe"
env:
- name: PORT
value: "8080"
- name: PRODUCT_CATALOG_SERVICE_ADDR
value: "productcatalogservice:3550"
- name: CURRENCY_SERVICE_ADDR
value: "currencyservice:7000"
- name: CART_SERVICE_ADDR
value: "cartservice:7070"
- name: RECOMMENDATION_SERVICE_ADDR
value: "recommendationservice:8080"
- name: SHIPPING_SERVICE_ADDR
value: "shippingservice:50051"
- name: CHECKOUT_SERVICE_ADDR
value: "checkoutservice:5050"
- name: AD_SERVICE_ADDR
value: "adservice:9555"
# - name: JAEGER_SERVICE_ADDR
# value: "jaeger-collector:14268"
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-b
spec:
revisionHistoryLimit: 1
selector:
matchLabels:
app: frontend
env: b
template:
metadata:
labels:
app: frontend
env: b
annotations:
sidecar.istio.io/rewriteAppHTTPProbers: "true"
spec:
@ -100,6 +167,7 @@ spec:
type: LoadBalancer
selector:
app: frontend
env: b
ports:
- name: http
port: 80

View file

@ -16,6 +16,7 @@ kind: Deployment
metadata:
name: loadgenerator
spec:
revisionHistoryLimit: 1
selector:
matchLabels:
app: loadgenerator
@ -29,6 +30,17 @@ spec:
spec:
terminationGracePeriodSeconds: 5
restartPolicy: Always
initContainers:
- name: wait-frontend
image: alpine:3.6
command: ['sh', '-c', 'set -x; apk add --no-cache curl &&
until timeout -t 2 curl -f "http://${FRONTEND_ADDR}"; do
echo "waiting for http://${FRONTEND_ADDR}";
sleep 2;
done;']
env:
- name: FRONTEND_ADDR
value: "frontend:80"
containers:
- name: main
image: loadgenerator
@ -43,4 +55,4 @@ spec:
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
memory: 512Mi

View file

@ -17,6 +17,7 @@ kind: Deployment
metadata:
name: paymentservice
spec:
revisionHistoryLimit: 1
selector:
matchLabels:
app: paymentservice

View file

@ -17,6 +17,7 @@ kind: Deployment
metadata:
name: productcatalogservice
spec:
revisionHistoryLimit: 1
selector:
matchLabels:
app: productcatalogservice

View file

@ -15,15 +15,18 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: recommendationservice
name: recommendationservice-v1
spec:
revisionHistoryLimit: 1
selector:
matchLabels:
app: recommendationservice
version: v1
template:
metadata:
labels:
app: recommendationservice
version: v1
spec:
terminationGracePeriodSeconds: 5
containers:
@ -58,6 +61,53 @@ spec:
cpu: 200m
memory: 450Mi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: recommendationservice-v2
spec:
selector:
matchLabels:
app: recommendationservice
version: v2
template:
metadata:
labels:
app: recommendationservice
version: v2
spec:
terminationGracePeriodSeconds: 5
containers:
- name: server
image: recommendationservice
ports:
- containerPort: 8080
readinessProbe:
periodSeconds: 5
exec:
command: ["/bin/grpc_health_probe", "-addr=:8080"]
livenessProbe:
periodSeconds: 5
exec:
command: ["/bin/grpc_health_probe", "-addr=:8080"]
env:
- name: PORT
value: "8080"
- name: PRODUCT_CATALOG_SERVICE_ADDR
value: "productcatalogservice:3550"
- name: ENABLE_PROFILER
value: "0"
resources:
requests:
cpu: 100m
memory: 220Mi
limits:
cpu: 200m
memory: 450Mi
revisionHistoryLimit: 1
---
#In order for istio to route traffic correctly, it needs a kubernetes service to route traffic to
#the workloads in question.
apiVersion: v1
kind: Service
metadata:
@ -67,6 +117,38 @@ spec:
selector:
app: recommendationservice
ports:
- name: grpc
port: 8080
targetPort: 8080
- name: grpc
port: 8080
targetPort: 8080
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: recommendationservice
spec:
hosts:
- recommendationservice
http:
- route:
- destination:
host: recommendationservice
subset: v1
weight: 90
- destination:
host: recommendationservice
subset: v2
weight: 10
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: recommendationservice
spec:
host: recommendationservice
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2

View file

@ -17,6 +17,7 @@ kind: Deployment
metadata:
name: redis-cart
spec:
revisionHistoryLimit: 1
selector:
matchLabels:
app: redis-cart

View file

@ -17,6 +17,7 @@ kind: Deployment
metadata:
name: shippingservice
spec:
revisionHistoryLimit: 1
selector:
matchLabels:
app: shippingservice
@ -24,6 +25,7 @@ spec:
metadata:
labels:
app: shippingservice
version: deployment
spec:
containers:
- name: server
@ -56,6 +58,47 @@ spec:
cpu: 200m
memory: 128Mi
---
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: shippingservice-rs
spec:
replicas: 2
selector:
matchLabels:
app: shippingservice
owner: rs
template:
metadata:
labels:
app: shippingservice
owner: rs
spec:
containers:
- name: server
image: shippingservice
ports:
- containerPort: 50051
env:
- name: PORT
value: "50051"
readinessProbe:
periodSeconds: 5
exec:
command: ["/bin/grpc_health_probe", "-addr=:50051"]
livenessProbe:
exec:
command: ["/bin/grpc_health_probe", "-addr=:50051"]
# env:
# - name: JAEGER_SERVICE_ADDR
# value: "jaeger-collector:14268"
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 200m
---
apiVersion: v1
kind: Service
metadata:

View file

@ -46,7 +46,7 @@ const request = {
};
function _moneyToString (m) {
return `${m.units}.${m.nanos.toString().padStart(9,'0')} ${m.currency_code}`;
return `${m.units}.${m.nanos.toString().padStart(9, '0')} ${m.currency_code}`;
}
client.getSupportedCurrencies({}, (err, response) => {

View file

@ -92,7 +92,13 @@ function _loadProto (path) {
*/
function _getCurrencyData (callback) {
const data = require('./data/currency_conversion.json');
callback(data);
// Currency conversion API slows down during peak hours.
let delay = 0;
const hour = new Date().getHours();
if (hour > 8 && hour < 17) {
delay = 100; // add 100ms slowdown
}
setTimeout(() => callback(data), delay);
}
/**

View file

@ -10,7 +10,9 @@
<p>
<small class="text-muted">
This website is hosted for demo purposes only. It is not an
actual shop. This is not an official Google project.
actual shop. This is not an official Google project. Demo
deployment for Google Cloud &amp; Signals Monitoring
Topology.
</small>
</p>
<small class="text-muted">

View file

@ -17,6 +17,9 @@
you catch up with the hipster trend and express your
personal style. Start shopping hip and vintage items now!
</p>
<p class="lead text-muted">
Cloud Topology
</p>
</div>
</section>

View file

@ -14,8 +14,5 @@ FROM base
COPY --from=builder /install /usr/local
COPY . .
RUN chmod +x ./loadgen.sh
RUN apt-get -qq update \
&& apt-get install -y --no-install-recommends \
curl
ENTRYPOINT ./loadgen.sh
ENTRYPOINT ["./loadgen.sh"]

View file

@ -1,4 +1,4 @@
#!/bin/bash
#!/bin/sh -eu
#
# Copyright 2018 Google LLC
#
@ -14,22 +14,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#!/bin/bash
set -e
trap "exit" TERM
if [[ -z "${FRONTEND_ADDR}" ]]; then
if [ -z "${FRONTEND_ADDR}" ]; then
echo >&2 "FRONTEND_ADDR not specified"
exit 1
fi
set -x
# if one request to the frontend fails, then exit
STATUSCODE=$(curl --silent --output /dev/stderr --write-out "%{http_code}" http://${FRONTEND_ADDR})
if test $STATUSCODE -ne 200; then
echo "Error: Could not reach frontend - Status code: ${STATUSCODE}"
exit 1
fi
# else, run loadgen
locust --host="http://${FRONTEND_ADDR}" --no-web -c "${USERS:-10}" 2>&1
locust \
-f locustfile.py \
--host="http://${FRONTEND_ADDR}" \
--no-web \
-c "${USERS:-10}" \
-r 1

View file

@ -14,7 +14,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import math
import random
import time
from locust import HttpLocust, TaskSet
products = [
@ -28,29 +30,39 @@ products = [
'LS4PSXUNUM',
'OLJCESPC7Z']
def index(l):
l.client.get("/")
def setCurrency(l):
currencies = ['EUR', 'USD', 'JPY', 'CAD']
l.client.post("/setCurrency",
{'currency_code': random.choice(currencies)})
{'currency_code': random.choice(currencies)})
def browseProduct(l):
l.client.get("/product/" + random.choice(products))
def viewCart(l):
l.client.get("/cart")
def addToCart(l):
product = random.choice(products)
l.client.get("/product/" + product)
l.client.post("/cart", {
'product_id': product,
'quantity': random.choice([1,2,3,4,5,10])})
'quantity': random.choice([1, 2, 3, 4, 5, 10])})
def checkout(l):
addToCart(l)
# For five minutes every other hour, credit cards passed by the user will be
# invalid and the paymentservice will fail.
now = time.localtime()
expiration_year = '2015' if (now.tm_hour % 2) & (now.tm_min < 5) else '2060'
l.client.post("/cart/checkout", {
'email': 'someone@example.com',
'street_address': '1600 Amphitheatre Parkway',
@ -60,23 +72,41 @@ def checkout(l):
'country': 'United States',
'credit_card_number': '4432-8015-6152-0454',
'credit_card_expiration_month': '1',
'credit_card_expiration_year': '2039',
'credit_card_expiration_year': expiration_year,
'credit_card_cvv': '672',
})
class UserBehavior(TaskSet):
min_wait = 500
max_wait = 15000
tasks = {index: 1,
setCurrency: 2,
browseProduct: 10,
addToCart: 2,
viewCart: 3,
checkout: 1}
def on_start(self):
index(self)
tasks = {index: 1,
setCurrency: 2,
browseProduct: 10,
addToCart: 2,
viewCart: 3,
checkout: 1}
def wait_function(self):
"""Wait time between user activity is diurnal.
Compute user's activity rate (wait time between actions) so traffic is
minimum at hrs=0.0|24.0 and maximum at hrs=12.0.
"""
now = time.localtime()
hrs = now.tm_hour + now.tm_min/60.0
# Compute scale factor is between 0 and 1.
traffic_scaler = -1.0 * math.cos(2.0 * math.pi * hrs / 24)
traffic_scaler = (traffic_scaler + 1) / 2.0
# Scale traffic between minimum and maximum wait times.
wait = self.max_wait + (self.min_wait - self.max_wait) * traffic_scaler
return round(wait)
class WebsiteUser(HttpLocust):
task_set = UserBehavior
min_wait = 1000
max_wait = 10000

View file

@ -1 +1 @@
locustio==0.8.1
locustio==0.9.0

View file

@ -2,7 +2,7 @@
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile --output-file requirements.txt requirements.in
# pip-compile --output-file=requirements.txt requirements.in
#
certifi==2018.11.29 # via requests
chardet==3.0.4 # via requests
@ -13,9 +13,9 @@ greenlet==0.4.15 # via gevent
idna==2.8 # via requests
itsdangerous==1.1.0 # via flask
jinja2==2.10 # via flask
locustio==0.8.1
locustio==0.9.0
markupsafe==1.1.0 # via jinja2
msgpack-python==0.5.6 # via locustio
msgpack==0.6.1 # via locustio
pyzmq==17.0.0 # via locustio
requests==2.21.0 # via locustio
six==1.12.0 # via locustio

View file

@ -15,6 +15,7 @@
const cardValidator = require('simple-card-validator');
const uuid = require('uuid/v4');
const pino = require('pino');
const grpc = require('grpc');
const logger = pino({
name: 'paymentservice-charge',
@ -28,6 +29,7 @@ class CreditCardError extends Error {
constructor (message) {
super(message);
this.code = 400; // Invalid argument error
this.status = grpc.status.INVALID_ARGUMENT;
}
}

18
src/pingjob/Dockerfile Normal file
View file

@ -0,0 +1,18 @@
FROM python:3-slim as base
FROM base as builder
RUN apt-get -qq update \
&& apt-get install -y --no-install-recommends \
g++
COPY requirements.txt .
RUN pip install --install-option="--prefix=/install" -r requirements.txt
FROM base
COPY --from=builder /install /usr/local
COPY . .
ENTRYPOINT ["./pinger.sh"]

87
src/pingjob/pinger.py Normal file
View file

@ -0,0 +1,87 @@
#!/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.
import random
import requests
import sys
BASE = sys.argv[1]
products = [
'0PUK6V6EV0',
'1YMWWN1N4O',
'2ZYFJ3GM2N',
'66VCHSJNUP',
'6E92ZMYYFZ',
'9SIQT8TOJO',
'L9ECAV7KIM',
'LS4PSXUNUM',
'OLJCESPC7Z']
def index():
requests.get(BASE + "/")
def setCurrency():
currencies = ['EUR', 'USD', 'JPY', 'CAD']
requests.post(BASE + "/setCurrency",
{'currency_code': random.choice(currencies)})
def browseProduct():
requests.get(BASE + "/product/" + random.choice(products))
def viewCart():
requests.get(BASE + "/cart")
def addToCart():
product = random.choice(products)
requests.get(BASE + "/product/" + product)
requests.post(BASE + "/cart", {
'product_id': product,
'quantity': random.choice([1, 2, 3, 4, 5, 10])})
def checkout():
addToCart()
requests.post(BASE + "/cart/checkout", {
'email': 'someone@example.com',
'street_address': '1600 Amphitheatre Parkway',
'zip_code': '94043',
'city': 'Mountain View',
'state': 'CA',
'country': 'United States',
'credit_card_number': '4432-8015-6152-0454',
'credit_card_expiration_month': '1',
'credit_card_expiration_year': '2039',
'credit_card_cvv': '672',
})
if not BASE:
print("ERROR: no frontend address")
else:
print("pinging" + BASE)
index()
browseProduct()
addToCart()
viewCart()
checkout()
print("pinging complete")

27
src/pingjob/pinger.sh Executable file
View file

@ -0,0 +1,27 @@
#!/bin/sh -eu
#
# 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.
#!/bin/bash
set -e
trap "exit" TERM
if [ -z "${FRONTEND_ADDR}" ]; then
echo >&2 "FRONTEND_ADDR not specified"
exit 1
fi
set -x
python pinger.py "${FRONTEND_ADDR}"

View file

@ -0,0 +1 @@
requests

View file

@ -0,0 +1,11 @@
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile --output-file=requirements.txt requirements.in
#
certifi==2018.11.29 # via requests
chardet==3.0.4 # via requests
idna==2.8 # via requests
requests==2.21.0
urllib3==1.24.1 # via requests