├── web-app ├── .gitignore ├── conf │ ├── microservice.yaml │ └── chassis.yaml ├── main.go └── external │ └── index.html ├── calculator ├── .gitignore ├── conf │ ├── microservice.yaml │ └── chassis.yaml ├── main.go └── app │ └── calculator.go ├── .gitignore ├── k8s ├── webapp.conf │ ├── microservice.yaml │ └── chassis.yaml ├── calculator.conf │ ├── microservice.yaml │ └── chassis.yaml ├── startsc.sh ├── Dockerfile.webapp ├── Dockerfile.calculator ├── calculator.yaml ├── servicecenter.yaml └── webapp.yaml ├── scripts ├── flowcontrol.test.sh ├── circuitbreaker.test.sh └── distribute-image.sh ├── Makefile └── README.md /web-app/.gitignore: -------------------------------------------------------------------------------- 1 | web-app 2 | log/ -------------------------------------------------------------------------------- /calculator/.gitignore: -------------------------------------------------------------------------------- 1 | calculator 2 | log -------------------------------------------------------------------------------- /web-app/conf/microservice.yaml: -------------------------------------------------------------------------------- 1 | service_description: 2 | name: web-app 3 | version: 0.0.1 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | k8s/calculator 2 | k8s/web-app 3 | k8s/service-center 4 | k8s/frontend 5 | .idea/ 6 | -------------------------------------------------------------------------------- /k8s/webapp.conf/microservice.yaml: -------------------------------------------------------------------------------- 1 | service_description: 2 | name: web-app 3 | version: 0.0.1 -------------------------------------------------------------------------------- /calculator/conf/microservice.yaml: -------------------------------------------------------------------------------- 1 | service_description: 2 | name: calculator 3 | version: 0.0.1 -------------------------------------------------------------------------------- /k8s/calculator.conf/microservice.yaml: -------------------------------------------------------------------------------- 1 | service_description: 2 | name: calculator 3 | version: 0.0.1 -------------------------------------------------------------------------------- /k8s/startsc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | /root/frontend > /root/frontend.log 2>&1 & 3 | cd /root 4 | ./service-center 5 | -------------------------------------------------------------------------------- /k8s/Dockerfile.webapp: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | COPY ./web-app /root/ 3 | RUN chmod +x /root/web-app 4 | COPY ./webapp.conf /root/conf 5 | CMD ["/root/web-app"] 6 | -------------------------------------------------------------------------------- /k8s/Dockerfile.calculator: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | COPY ./calculator /root/ 3 | RUN chmod +x /root/calculator 4 | COPY ./calculator.conf /root/conf 5 | CMD ["/root/calculator"] 6 | -------------------------------------------------------------------------------- /calculator/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/go-chassis/go-chassis" 5 | "github.com/go-chassis/go-chassis/core/lager" 6 | "github.com/ServiceComb-samples/go-bmi/calculator/app" 7 | ) 8 | 9 | func main() { 10 | chassis.RegisterSchema("rest", &app.CalculateBmi{}) 11 | if err := chassis.Init(); err != nil { 12 | lager.Logger.Errorf("Init FAILED %s", err.Error()) 13 | return 14 | } 15 | chassis.Run() 16 | 17 | } 18 | -------------------------------------------------------------------------------- /scripts/flowcontrol.test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "The requests should be delayed after flow control is triggered, around request no.10" 3 | echo 4 | 5 | for i in {1..25} 6 | do 7 | start=$(date +%s%N) 8 | status=$(curl -I -m 10 -o /dev/null -s -w %{http_code} 'http://localhost:8889/calculator/bmi?height=178&weight=81') 9 | finish=$(date +%s%N) 10 | duration=$(( ($finish - $start) / 1000000 )) 11 | echo $i status: $status, in: $duration "ms" 12 | sleep 0.1 13 | done 14 | -------------------------------------------------------------------------------- /k8s/calculator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: calculator 5 | namespace: bmi 6 | labels: 7 | app: calculator 8 | spec: 9 | ports: 10 | - port: 8080 11 | targetPort: 8080 12 | name: http 13 | # externalIPs: [192.168.43.70] 14 | selector: 15 | app: calculator 16 | --- 17 | apiVersion: extensions/v1beta1 18 | kind: Deployment 19 | metadata: 20 | name: calculator 21 | namespace: bmi 22 | spec: 23 | replicas: 1 24 | template: 25 | metadata: 26 | labels: 27 | app: calculator 28 | version: v1 29 | spec: 30 | containers: 31 | - name: calculator 32 | image: bmi/calculator:v1 33 | imagePullPolicy: IfNotPresent 34 | ports: 35 | - containerPort: 8080 36 | --- 37 | 38 | -------------------------------------------------------------------------------- /web-app/conf/chassis.yaml: -------------------------------------------------------------------------------- 1 | APPLICATION_ID: bmi 2 | cse: 3 | service: 4 | registry: 5 | type: servicecenter 6 | address: http://127.0.0.1:30100 7 | handler: 8 | chain: 9 | consumer: 10 | default: loadbalance,transport,tracing-provider 11 | flowcontrol: 12 | Consumer: 13 | qps: 14 | enabled: true 15 | limit: 16 | calculator: 100 17 | circuitBreaker: 18 | Provider: 19 | enabled: true 20 | requestVolumeThreshold: 1 21 | sleepWindowInMilliseconds: 10000 22 | fallbackpolicy: 23 | Provider: 24 | policy: throwexception 25 | metrics: 26 | apiPath: /metrics 27 | enable: true 28 | enableGoRuntimeMetrics: true 29 | tracing: 30 | enabled: true 31 | collectorType: zipkin 32 | collectorTarget: http://localhost:9411/api/v1/spans -------------------------------------------------------------------------------- /k8s/webapp.conf/chassis.yaml: -------------------------------------------------------------------------------- 1 | APPLICATION_ID: bmi 2 | cse: 3 | service: 4 | registry: 5 | type: servicecenter 6 | address: http://servicecenter.bmi:30100 7 | handler: 8 | chain: 9 | consumer: 10 | default: loadbalance,transport,tracing-provider 11 | flowcontrol: 12 | Consumer: 13 | qps: 14 | enabled: true 15 | limit: 16 | calculator: 100 17 | circuitBreaker: 18 | Provider: 19 | enabled: true 20 | requestVolumeThreshold: 1 21 | sleepWindowInMilliseconds: 10000 22 | fallbackpolicy: 23 | Provider: 24 | policy: throwexception 25 | metrics: 26 | apiPath: /metrics 27 | enable: true 28 | enableGoRuntimeMetrics: true 29 | tracing: 30 | enabled: true 31 | collectorType: zipkin 32 | collectorTarget: http://localhost:9411/api/v1/spans 33 | -------------------------------------------------------------------------------- /k8s/servicecenter.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: servicecenter 5 | namespace: bmi 6 | labels: 7 | app: servicecenter 8 | spec: 9 | ports: 10 | - port: 30100 11 | # targetPort: 30100 12 | name: servicecenter 13 | - port: 30103 14 | targetPort: 30103 15 | name: frontend 16 | externalIPs: [192.168.43.70] 17 | type: NodePort 18 | selector: 19 | app: servicecenter 20 | --- 21 | apiVersion: extensions/v1beta1 22 | kind: Deployment 23 | metadata: 24 | name: servicecenter 25 | namespace: bmi 26 | spec: 27 | replicas: 1 28 | template: 29 | metadata: 30 | labels: 31 | app: servicecenter 32 | version: v1 33 | spec: 34 | containers: 35 | - name: servicecenter 36 | image: servicecomb/service-center 37 | imagePullPolicy: IfNotPresent 38 | ports: 39 | - containerPort: 30100 40 | - containerPort: 30103 41 | --- 42 | 43 | -------------------------------------------------------------------------------- /k8s/webapp.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: webapp 5 | namespace: bmi 6 | labels: 7 | app: webapp 8 | spec: 9 | ports: 10 | - port: 8889 11 | targetPort: 8889 12 | name: http 13 | selector: 14 | app: webapp 15 | --- 16 | apiVersion: extensions/v1beta1 17 | kind: Deployment 18 | metadata: 19 | name: webapp 20 | namespace: bmi 21 | spec: 22 | replicas: 1 23 | template: 24 | metadata: 25 | labels: 26 | app: webapp 27 | version: v1 28 | spec: 29 | affinity: 30 | nodeAffinity: 31 | requiredDuringSchedulingIgnoredDuringExecution: 32 | nodeSelectorTerms: 33 | - matchExpressions: 34 | - key: debug 35 | operator: Exists 36 | containers: 37 | - name: webapp 38 | image: bmi/webapp:v1 39 | imagePullPolicy: IfNotPresent 40 | ports: 41 | - containerPort: 8889 42 | --- 43 | 44 | -------------------------------------------------------------------------------- /calculator/conf/chassis.yaml: -------------------------------------------------------------------------------- 1 | APPLICATION_ID: bmi 2 | cse: 3 | service: 4 | registry: 5 | type: servicecenter 6 | address: http://127.0.0.1:30100 7 | protocols: 8 | rest: 9 | listenAddress: 0.0.0.0:8080 10 | advertiseAddress: 0.0.0.0:8080 11 | handler: 12 | chain: 13 | Provider: 14 | default: bizkeeper-provider,ratelimiter-provider,tracing-provider 15 | flowcontrol: 16 | Provider: 17 | qps: 18 | enabled: true 19 | limit: 20 | web-app: 1 21 | circuitBreaker: 22 | Provider: 23 | enabled: true 24 | requestVolumeThreshold: 3 25 | sleepWindowInMilliseconds: 10000 26 | fallbackpolicy: 27 | Provider: 28 | policy: throwexception 29 | metrics: 30 | apiPath: /metrics 31 | enable: true 32 | enableGoRuntimeMetrics: true 33 | tracing: 34 | enabled: true 35 | collectorType: zipkin 36 | collectorTarget: http://localhost:9411/api/v1/spans -------------------------------------------------------------------------------- /scripts/circuitbreaker.test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | invalidReqUrl='http://localhost:8889/calculator/bmi?height=178&weight=-81' 3 | validReqUrl='http://localhost:8889/calculator/bmi?height=178&weight=81' 4 | 5 | function badReq() { 6 | local status=$(curl -I -m 10 -o /dev/null -s -w %{http_code} $invalidReqUrl) 7 | echo $status 8 | } 9 | 10 | function goodReq() { 11 | #function_body 12 | local status=$(curl -I -m 10 -o /dev/null -s -w %{http_code} $validReqUrl) 13 | echo $status 14 | } 15 | 16 | echo "The first 5 requests are made to trigger circuit breaker with a negative weight" 17 | echo "The last 5 requests should receive error, even with valid parameter" 18 | 19 | echo "Request with url: $invalidReqUrl" 20 | for i in {1..5} 21 | do 22 | status=$(badReq) 23 | echo $i $status 24 | echo 25 | done 26 | 27 | echo '> circuit breaker should be triggered' 28 | echo 29 | 30 | echo "Request with url: $validReqUrl" 31 | for i in {1..5} 32 | do 33 | status=$(goodReq) 34 | echo $i $status 35 | echo 36 | done 37 | -------------------------------------------------------------------------------- /k8s/calculator.conf/chassis.yaml: -------------------------------------------------------------------------------- 1 | APPLICATION_ID: bmi 2 | cse: 3 | service: 4 | registry: 5 | type: servicecenter 6 | address: http://servicecenter.bmi:30100 7 | protocols: 8 | rest: 9 | listenAddress: 0.0.0.0:8080 10 | # Under k8s, don't set advertiseAddress, the chassis will register the service with the current IP automatically 11 | # advertiseAddress: 0.0.0.0:8080 12 | handler: 13 | chain: 14 | Provider: 15 | default: bizkeeper-provider,ratelimiter-provider,tracing-provider 16 | flowcontrol: 17 | Provider: 18 | qps: 19 | enabled: true 20 | limit: 21 | web-app: 1 22 | circuitBreaker: 23 | Provider: 24 | enabled: true 25 | requestVolumeThreshold: 3 26 | sleepWindowInMilliseconds: 10000 27 | fallbackpolicy: 28 | Provider: 29 | policy: throwexception 30 | metrics: 31 | apiPath: /metrics 32 | enable: true 33 | enableGoRuntimeMetrics: true 34 | tracing: 35 | enabled: true 36 | collectorType: zipkin 37 | collectorTarget: http://localhost:9411/api/v1/spans 38 | -------------------------------------------------------------------------------- /scripts/distribute-image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | imagename=$1 3 | if [ "$imagename" == "" ]; then 4 | echo "need an image name" 5 | exit 1 6 | fi 7 | 8 | imagefilename=$(echo $imagename | sed 's/\//_/g') 9 | 10 | # nodeIPs=$(kubectl get nodes -o wide | grep -v 'NAME.*STATUS' | awk '{print $1}') 11 | nodeIPs=$(kubectl get nodes -o jsonpath='{$.items[*].status.addresses[?(@.type=="ExternalIP")].address}') 12 | if [ "$nodeIPs" == "" ]; then 13 | nodeIPs=$(kubectl get nodes -o jsonpath='{$.items[*].status.addresses[?(@.type=="InternalIP")].address}') 14 | fi 15 | if [ "$nodeIPs" == "" ]; then 16 | nodeIPs=$(kubectl get nodes -o jsonpath='{$.items[*].status.addresses[?(@.type=="Hostname")].address}') 17 | fi 18 | 19 | if [ "$nodeIPs" == "" ]; then 20 | echo "Failed to get nodes' IP" 21 | exit 1 22 | fi 23 | 24 | 25 | localip=$(ip addr | grep 'inet.*wlp3s0' | awk '{print $2}' | awk '{split($0,a,"/")}; {print a[1]}') 26 | if [ "$localip" == "" ]; then 27 | echo "Failed to get local ip" 28 | exit 1 29 | fi 30 | 31 | echo saving to $imagefilename.tar 32 | docker save $imagename > /tmp/$imagefilename.tar 33 | 34 | for nodeip in ${nodeIPs[@]} 35 | do 36 | if [ "$nodeip" != "$localip" ]; then 37 | echo distributing $imagename "to" $nodeip 38 | scp /tmp/$imagefilename.tar root@$nodeip:/root/ 39 | ssh root@$nodeip "/usr/local/bin/docker load < /root/$imagefilename.tar" 40 | ssh root@$nodeip "rm /root/$imagefilename.tar" 41 | fi 42 | done 43 | 44 | rm /tmp/$imagefilename.tar 45 | -------------------------------------------------------------------------------- /web-app/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | 8 | "flag" 9 | "github.com/go-chassis/go-chassis" 10 | "github.com/go-chassis/go-chassis/client/rest" 11 | "github.com/go-chassis/go-chassis/core" 12 | "github.com/go-chassis/go-chassis/core/lager" 13 | "io/ioutil" 14 | ) 15 | 16 | func main() { 17 | http.HandleFunc("/", BmiPageHandler) 18 | http.HandleFunc("/calculator/bmi", BmiRequestHandler) 19 | 20 | if err := chassis.Init(); err != nil { 21 | lager.Logger.Errorf("Init FAILED %s", err.Error()) 22 | return 23 | } 24 | 25 | port := flag.String("port", "8889", "Port web-app will listen") 26 | address := flag.String("address", "0.0.0.0", "Address web-app will listen") 27 | fullAddress := fmt.Sprintf("%s:%s", *address, *port) 28 | http.ListenAndServe(fullAddress, nil) 29 | } 30 | 31 | func BmiRequestHandler(w http.ResponseWriter, r *http.Request) { 32 | heightstr := r.URL.Query().Get("height") 33 | weightstr := r.URL.Query().Get("weight") 34 | 35 | requestURI := fmt.Sprintf("cse://calculator/bmi?height=%s&weight=%s", heightstr, weightstr) 36 | restInvoker := core.NewRestInvoker() 37 | req, _ := rest.NewRequest("GET", requestURI, nil) 38 | resp, _ := restInvoker.ContextDo(context.TODO(), req) 39 | 40 | w.Header().Set("content-type", "application/json") 41 | w.WriteHeader(resp.StatusCode) 42 | 43 | defer resp.Body.Close() 44 | body,err:=ioutil.ReadAll(resp.Body) 45 | if err!=nil { 46 | lager.Logger.Errorf("Read response body ERROR: %s", err.Error()) 47 | } 48 | w.Write(body) 49 | } 50 | 51 | func BmiPageHandler(w http.ResponseWriter, r *http.Request) { 52 | http.ServeFile(w, r, "external/index.html") 53 | } 54 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | GOBIN=$(shell which go) 3 | GOBUILD=$(GOBIN) build 4 | 5 | DOCKERBIN = $(shell which docker) 6 | 7 | DEBUG_FLAGS := "" 8 | STATIC_FLAGS = '-w -extldflags "-static"' 9 | ifneq ($(origin DEBUG), undefined) 10 | STATIC_FLAGS = '-extldflags "-static"' 11 | # Get the golang version and coresponding gcflags pattern 12 | goVerStr := $(shell go version | awk '{split($$0,a," ")}; {print a[3]}') 13 | goVerNum := $(shell echo $(goVerStr) | awk '{split($$0,a,"go")}; {print a[2]}') 14 | goVerMajor := $(shell echo $(goVerNum) | awk '{split($$0, a, ".")}; {print a[1]}') 15 | goVerMinor := $(shell echo $(goVerNum) | awk '{split($$0, a, ".")}; {print a[2]}') 16 | gcflagsPattern := $(shell ( [ $(goVerMajor) -ge 1 ] && [ ${goVerMinor} -ge 10 ] ) && echo 'all=' || echo '') 17 | 18 | # @echo $(goVerStr) 19 | DEBUG_FLAGS = "$(gcflagsPattern)-l -N" 20 | endif 21 | 22 | 23 | .PHONY: calculator webapp 24 | 25 | k8s: k8s.calculator k8s.webapp k8s.servicecenter 26 | echo '{"apiVersion":"v1","kind":"Namespace","metadata":{"name":"bmi"}}' | kubectl apply -f - 27 | 28 | docker: docker.webapp docker.calculator 29 | 30 | bin: webapp calculator 31 | 32 | 33 | webapp: 34 | cd ./web-app; $(GOBUILD) -ldflags $(STATIC_FLAGS) -gcflags $(DEBUG_FLAGS) 35 | 36 | docker.webapp: webapp 37 | mv ./web-app/web-app ./k8s/ 38 | cd ./k8s; $(DOCKERBIN) build -t bmi/webapp:v1 -f ./Dockerfile.webapp . 39 | 40 | k8s.webapp: docker.webapp 41 | ./scripts/distribute-image.sh bmi/webapp:v1 42 | -kubectl delete -f ./k8s/webapp.yaml 43 | kubectl apply -f ./k8s/webapp.yaml 44 | 45 | calculator: 46 | cd ./calculator; $(GOBUILD) -ldflags $(STATIC_FLAGS) -gcflags $(DEBUG_FLAGS) 47 | 48 | docker.calculator: calculator 49 | mv ./calculator/calculator ./k8s/ 50 | cd ./k8s; $(DOCKERBIN) build -t bmi/calculator:v1 -f ./Dockerfile.calculator . 51 | 52 | k8s.calculator: docker.calculator 53 | ./scripts/distribute-image.sh bmi/calculator:v1 54 | -kubectl delete -f ./k8s/calculator.yaml 55 | kubectl apply -f ./k8s/calculator.yaml 56 | 57 | 58 | k8s.servicecenter: 59 | -kubectl delete -f ./k8s/servicecenter.yaml 60 | kubectl apply -f ./k8s/servicecenter.yaml 61 | -------------------------------------------------------------------------------- /calculator/app/calculator.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/go-chassis/go-chassis/core/registry" 10 | rf "github.com/go-chassis/go-chassis/server/restful" 11 | ) 12 | 13 | type CalculateBmi struct { 14 | } 15 | 16 | func (c *CalculateBmi) URLPatterns() []rf.Route { 17 | return []rf.Route{ 18 | {http.MethodGet, "/bmi", "Calculate","",nil,nil,nil}, 19 | } 20 | } 21 | 22 | func (c *CalculateBmi) Calculate(ctx *rf.Context) { 23 | 24 | var height, weight, bmi float64 25 | var err error 26 | result := struct { 27 | Result float64 `json:"result"` 28 | InstanceId string `json:"instanceId"` 29 | CallTime string `json:"callTime"` 30 | }{} 31 | errorResponse := struct { 32 | Error string `json:"error"` 33 | }{} 34 | 35 | heightStr := ctx.ReadQueryParameter("height") 36 | weightStr := ctx.ReadQueryParameter("weight") 37 | 38 | if height, err = strconv.ParseFloat(heightStr, 10); err != nil { 39 | errorResponse.Error = err.Error() 40 | ctx.WriteHeaderAndJSON(http.StatusBadRequest, errorResponse, "application/json") 41 | return 42 | } 43 | if weight, err = strconv.ParseFloat(weightStr, 10); err != nil { 44 | errorResponse.Error = err.Error() 45 | ctx.WriteHeaderAndJSON(http.StatusBadRequest, errorResponse, "application/json") 46 | return 47 | } 48 | 49 | if bmi, err = c.BMIIndex(height, weight); err != nil { 50 | errorResponse.Error = err.Error() 51 | ctx.WriteHeaderAndJSON(http.StatusBadRequest, errorResponse, "application/json") 52 | return 53 | } 54 | 55 | result.Result = bmi 56 | //Get InstanceID 57 | items := registry.SelfInstancesCache.Items() 58 | for microserviceID := range items { 59 | instanceID, exist := registry.SelfInstancesCache.Get(microserviceID) 60 | if exist { 61 | result.InstanceId = instanceID.([]string)[0] 62 | } 63 | } 64 | result.CallTime = time.Now().String() 65 | ctx.WriteJSON(result, "application/json") 66 | } 67 | 68 | func (c *CalculateBmi) BMIIndex(height, weight float64) (float64, error) { 69 | if height <= 0 || weight <= 0 { 70 | return 0, fmt.Errorf("Arugments must be above 0") 71 | } 72 | heightInMeter := height / 100 73 | bmi := weight / (heightInMeter * heightInMeter) 74 | return bmi, nil 75 | } 76 | -------------------------------------------------------------------------------- /web-app/external/index.html: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |