├── .gitignore ├── Makefile ├── README.md ├── calculator ├── .gitignore ├── app │ └── calculator.go ├── conf │ ├── chassis.yaml │ └── microservice.yaml └── main.go ├── k8s ├── Dockerfile.calculator ├── Dockerfile.webapp ├── calculator.conf │ ├── chassis.yaml │ └── microservice.yaml ├── calculator.yaml ├── servicecenter.yaml ├── startsc.sh ├── webapp.conf │ ├── chassis.yaml │ └── microservice.yaml └── webapp.yaml ├── scripts ├── circuitbreaker.test.sh ├── distribute-image.sh └── flowcontrol.test.sh └── web-app ├── .gitignore ├── conf ├── chassis.yaml └── microservice.yaml ├── external └── index.html └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | k8s/calculator 2 | k8s/web-app 3 | k8s/service-center 4 | k8s/frontend 5 | .idea/ 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Develop Body Mass Index(BMI) via go-chassis 2 | 3 | 4 | ## Prerequisite 5 | 6 | ### Install Golang development environment 7 | 8 | * Install Git, details can refer to [Git Installing Guide](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) 9 | 10 | * Install Golang 1.11+, details can refer to [Golang Installing Guide](https://golang.org/doc/install) 11 | 12 | * Install go-chassis(SDK) by executing the following commands. 13 | 14 | ```bash 15 | mkdir -p $GOPATH/src/github.com/go-chassis 16 | cd $GOPATH/src/github.com/go-chassis 17 | git clone https://github.com/go-chassis/go-chassis.git 18 | export GO111MODULE=on 19 | go mod vendor 20 | ``` 21 | 22 | ### Run Service Center 23 | **Service Center** enables capabilities of service registration and service discovery in **ServiceComb**. It can run inside docker. 24 | ```bash 25 | docker pull servicecomb/service-center 26 | docker run -d -p 30100:30100 servicecomb/service-center:latest 27 | ``` 28 | 29 | 30 | ## Develop BMI microservice application in minutes 31 | 32 | The workflow of BMI application have been introduced in [Quick start](/golang/quick-start/). It contains two separate microservices: 33 | 34 | - **BMI calculator service**:provides computing services. 35 | 36 | - **Web service**:provides both user interface and gateway service. 37 | 38 | Now we will introduce the detailed implementation of these two microservices. The full code is on [github](https://github.com/ServiceComb-samples/go-bmi). 39 | ### Implementation of calculator 40 | The calculator service provides capability of calculating BMI. It contains three parts: 41 | 42 | * Business logic 43 | 44 | * Service registration and configuration 45 | 46 | * Start the framework 47 | 48 | #### Business logic 49 | 50 | Define the service structure 51 | ```go 52 | type CalculateBmi struct { 53 | } 54 | ``` 55 | 56 | Implement detailed calculation according to the formula \\(BMI=\frac{weight}{height^2}\\). 57 | 58 | ```go 59 | func (c *CalculateBmi) BMIIndex(height, weight float64) (float64, error) { 60 | if height <= 0 || weight <= 0 { 61 | return 0, fmt.Errorf("Arugments must be above 0") 62 | } 63 | heightInMeter := height / 100 64 | bmi := weight / (heightInMeter * heightInMeter) 65 | return bmi, nil 66 | } 67 | ``` 68 | 69 | Write handler function, which must be import the parameter type of `restful.Context`. `restful.Context` contains commonly used operations on Request and Response to `http` calls. Functions starts with `Read` is about read operations from Request , including `ReadEntity` , `ReadHeader` , `ReadPathParameter` , `ReadBodyParameter` , `ReadRequest` etc .; functions start with `Writer` is about write operation to Response , Including `WriteHeader` , `WriteJson` , `WriteError` and so on. 70 | 71 | ```go 72 | import ( 73 | rf "github.com/go-chassis/go-chassis/server/restful" 74 | ) 75 | …… 76 | func (c *CalculateBmi) Calculate(b *rf.Context) { 77 | …… //Define the Response structure, omit here 78 | heightStr := b.ReadQueryParameter("height") 79 | weightStr := b.ReadQueryParameter("weight") 80 | var height, weight, bmi float64 81 | var err error 82 | …… // Convert form string to float, omit here 83 | if bmi, err = c.BMIIndex(height, weight); err != nil { 84 | errorResponse.Error = err.Error() 85 | b.WriteHeaderAndJson(http.StatusBadRequest, errorResponse, "application/json") 86 | return 87 | } 88 | result.Result = bmi 89 | result.CallTime = time.Now().String() 90 | b.WriteJson(result, "application/json") 91 | } 92 | ``` 93 | Specify the corresponding URL route 94 | ```go 95 | func (c *CalculateBmi) URLPatterns() []rf.Route { 96 | return []rf.Route{ 97 | {http.MethodGet, "/calculator/bmi", "Calculate"}, 98 | } 99 | } 100 | ``` 101 | #### Service registration and configuration 102 | 103 | After the completion of the preparation of the business logic code, the business logic needs to be registered to the `Go-chassis` framework, and at the time of registration, the service name, ID and other attributes can be specified simultaneously. 104 | ```go 105 | chassis.RegisterSchema("rest",&CalculateBmi{}) 106 | ``` 107 | 108 | In addition to some of the attributes specified in the code, more attributes are configured through the configuration file. The configuration file includes `chassis.yaml` and `microservice.yaml` , placed in the `conf` directory under the code directory. Among them, `chassis.yaml` contains public service properties, such as public AppId information, registry type information, registry address, service protocol, transmission protocol information; `microservice.yaml` is about the private properties of microservices, including the service name, version and so on. 109 | 110 | `chassis.yaml` 111 | 112 | 113 | ```yaml 114 | APPLICATION_ID: bmi 115 | cse: 116 | service: 117 | registry: 118 | address: http://127.0.0.1:30100 119 | protocols: 120 | rest: 121 | listenAddress: 0.0.0.0:8080 122 | ``` 123 | 124 | `microservice.yaml` 125 | 126 | ```yaml 127 | service_description: 128 | name: calculator 129 | version: 0.0.1 130 | ``` 131 | 132 | #### Start the framework 133 | 134 | ```go 135 | import ( 136 | "github.com/go-chassis/go-chassis" 137 | "github.com/go-chassis/go-chassis/core/lager" 138 | ) 139 | …… 140 | if err := chassis.Init(); err != nil { //Init the chassis framwork 141 | lager.Logger.Errorf("Init FAILED %s", err.Error()) 142 | return 143 | } 144 | chassis.Run() //Run the microservice 145 | ``` 146 | 147 | ### Implementation of web-app service 148 | 149 | The web service provides both the user interface . It contains three parts: 150 | 151 | * Front-end static page 152 | 153 | * Request forward 154 | 155 | * Service configuration and startup 156 | 157 | #### Front-end static page 158 | 159 | Front-end static page is powered by `Bootstrap` , and through the golang official library `http.ServeFile` front-end static page will be displayed. 160 | 161 | ```go 162 | func BmiPageHandler(w http.ResponseWriter, r *http.Request) { 163 | http.ServeFile(w,r,"external/index.html") 164 | } 165 | ``` 166 | 167 | 168 | #### Request forward 169 | 170 | The `web-app` service forwards requests to the `calculator` service via `core.NewRestInvoker()` when it receives a request from the front-end page. In the process of forwarding the call, the user does not need to know the specific address and port of the `calculator` service. The service discovery process is automatically completed by the `go-chassis` framework. 171 | ```go 172 | func BmiRequestHandler(w http.ResponseWriter, r *http.Request) { 173 | queries := r.URL.Query() 174 | heightStr := queries.Get("height") 175 | weightStr := queries.Get("weight") 176 | 177 | requestURI := fmt.Sprintf("cse://calculator/bmi?height=%s&weight=%s", heightStr, weightStr) 178 | restInvoker := core.NewRestInvoker() 179 | req, _ := rest.NewRequest("GET", requestURI) 180 | resp, _ := restInvoker.ContextDo(context.TODO(), req) 181 | 182 | w.Header().Set("content-type", "application/json") 183 | w.WriteHeader(resp.GetStatusCode()) 184 | w.Write(resp.ReadBody()) 185 | } 186 | ``` 187 | #### Service configuration and startup 188 | 189 | The configuration file for the `web-app` service also includes two yaml files, `chassis.yaml` and `microservice.yaml` , as follows: 190 | `chassis.yaml` 191 | 192 | ```yaml 193 | APPLICATION_ID: bmi 194 | cse: 195 | service: 196 | registry: 197 | address: http://127.0.0.1:30100 # ServiceCenter address 198 | ``` 199 | 200 | `microservice.yaml` 201 | 202 | ```yaml 203 | service_description: 204 | name: web-app 205 | version: 0.0.1 206 | ``` 207 | 208 | Unlike the `calculator service` , the `web-app` is a consumer-type service within the `Go-chassis` framework, so just invoke `chassis.Init()` to initialize the `Go-chassis` framework. 209 | 210 | ```go 211 | func main() { 212 | http.HandleFunc("/", BmiPageHandler) 213 | http.HandleFunc("/calculator/bmi", BmiRequestHandler) 214 | 215 | if err := chassis.Init(); err != nil { 216 | lager.Logger.Errorf("Init FAILED %s", err.Error()) 217 | return 218 | } 219 | 220 | port := flag.String("port", "8889", "Port web-app will listen") 221 | address := flag.String("address", "0.0.0.0", "Address web-app will listen") 222 | fullAddress := fmt.Sprintf("%s:%s", *address, *port) 223 | http.ListenAndServe(fullAddress, nil) 224 | } 225 | ``` 226 | 227 | ### Play with kubernetes 228 | The docker builds & kubernetes orchestrations are already there in the `Makefile`. There are 3 stages from binary builds to kubernetes deployments: 229 | - build binaries: `make $target`, for example `make calculator`, this will build the calculator binary 230 | - build docker images: `make docker.$target`, for example `make docker.calculator`, this will build the calculator image 231 | - deploy image to k8s cluster: `make k8s.$target`, for example `make k8s.calculator`, this will deploy the calculator to the kubernetes cluster by a service and a deployment 232 | 233 | Handle all by `make`: 234 | - `make bin`: build all the binaries 235 | - `make docker`: execute `make bin`, then build the docker images 236 | - `make k8s`: execute `make docker`, then deploy the newly built image to the kubernetes cluster 237 | 238 | 239 | The k8s related make commands will call `./scripts/distribute-image.sh` to dispatch docker images to k8s clusters. The script will try to find the local IP and the k8s cluster's nodes IP, and execute some SSH command on the nodes. So there are 2 points you'll take care of: 240 | 1. Make sure `ip` command is executable, and modify the network adapter's name in `./scripts/distribute-image.sh:25`, the default is `wlp3s0`, replace it with your local network card's name 241 | 2. Make sure the current user has the permission to execute remote commands on the k8s cluster nodes. 242 | -------------------------------------------------------------------------------- /calculator/.gitignore: -------------------------------------------------------------------------------- 1 | calculator 2 | log -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /calculator/conf/microservice.yaml: -------------------------------------------------------------------------------- 1 | service_description: 2 | name: calculator 3 | version: 0.0.1 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /k8s/calculator.conf/microservice.yaml: -------------------------------------------------------------------------------- 1 | service_description: 2 | name: calculator 3 | version: 0.0.1 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/startsc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | /root/frontend > /root/frontend.log 2>&1 & 3 | cd /root 4 | ./service-center 5 | -------------------------------------------------------------------------------- /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/webapp.conf/microservice.yaml: -------------------------------------------------------------------------------- 1 | service_description: 2 | name: web-app 3 | version: 0.0.1 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /web-app/.gitignore: -------------------------------------------------------------------------------- 1 | web-app 2 | log/ -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /web-app/conf/microservice.yaml: -------------------------------------------------------------------------------- 1 | service_description: 2 | name: web-app 3 | version: 0.0.1 -------------------------------------------------------------------------------- /web-app/external/index.html: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 32 |
33 |
34 |

BMI Calculator

35 | 36 |
37 | 38 | 39 |
40 |
41 | 42 | 43 |
44 | 45 | 46 |
47 | 52 | 55 | 56 |
57 |
58 | 59 | 60 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------