introduce
Through a complete example, add call chaining (Tracing) middleware to gorilla/mux -based microservices .
What is Tracing Middleware?
The call chain (Tracing) middleware records the tracing data for each API request, and users can use tools like Jaeger to view it.
We will use rk-boot to start the gorilla/mux microservice . rk-boot is a framework for launching various web services via YAML. Please refer to the last chapter of this article for details on rk-boot .
Please visit the following address for the full tutorial: https://github.com/rookie-ninja/rk-mux
Install
go get github.com/rookie-ninja/rk-boot/mux
quick start
rk-boot will use OpenTelemetry-CNCF to handle tracing by default.
1. Create boot.yaml
To verify, we enabled the following options:
- commonService : commonService contains a series of common APIs. Details
- jaeger exporter : The gorilla/mux service will send data to the local jaeger agent.
---
mux:
- name: greeter # Required
port: 8080 # Required
enabled: true # Required
commonService:
enabled: true # Optional, default: false
interceptors:
tracingTelemetry:
enabled: true # Optional, Enable tracing interceptor/middleware
exporter:
jaeger:
agent:
enabled: true # Optional, Export to jaeger agent
2. Create main.go
Added /v1/greeter API.
// Copyright (c) 2021 rookie-ninja
//
// Use of this source code is governed by an Apache-style
// license that can be found in the LICENSE file.
package main
import (
"context"
"fmt"
"github.com/rookie-ninja/rk-boot"
"github.com/rookie-ninja/rk-boot/mux"
"github.com/rookie-ninja/rk-mux/interceptor"
"github.com/rookie-ninja/rk-mux/interceptor/context"
"net/http"
)
func main() {
// Create a new boot instance.
boot := rkboot.NewBoot()
// Register handler
entry := rkbootmux.GetMuxEntry("greeter")
entry.Router.NewRoute().Methods(http.MethodGet).Path("/v1/greeter").HandlerFunc(Greeter)
// Bootstrap
boot.Bootstrap(context.TODO())
boot.WaitForShutdownSig(context.TODO())
}
func Greeter(writer http.ResponseWriter, request *http.Request) {
rkmuxinter.WriteJson(writer, http.StatusOK, &GreeterResponse{
Message: fmt.Sprintf("Hello %s!", request.URL.Query().Get("name")),
})
}
type GreeterResponse struct {
Message string
}
3. Folder structure
$ tree
.
├── boot.yaml
├── go.mod
├── go.sum
└── main.go
0 directories, 4 files
4. Start jaeger locally
$ docker run -d --name jaeger \
-e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
-p 5775:5775/udp \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 14268:14268 \
-p 14250:14250 \
-p 9411:9411 \
jaegertracing/all-in-one:1.23
5. Start main.go
$ go run main.go
6. Verify
- Send a request to the /rk/v1/healthy API in CommonService.
$ curl -X GET localhost:8080/rk/v1/healthy
{"healthy":true}
- Send a request to the /v1/greeter API.
$ curl -X GET "localhost:8080/v1/greeter?name=rk-dev"
{"Message":"Hello rk-dev!"}
- Visit the jaeger homepage: http://localhost:16686/
If AppName is not specified through rkentry.GlobalAppCtx.GetAppInfoEntry().AppName, rk is used as AppName by default.
output to Stdout
The output path, such as STDOUT, can be modified by modifying the boot.yaml file.
- boot.yaml
---
mux:
- name: greeter # Required
port: 8080 # Required
enabled: true # Required
commonService:
enabled: true # Optional, default: false
interceptors:
tracingTelemetry:
enabled: true # Optional, Enable tracing interceptor/middleware
exporter:
file:
enabled: true
outputPath: "stdout" # Optional, Output to stdout
output to file
Tracing information can be saved to the file by modifying the boot.yaml file.
- boot.yaml
---
mux:
- name: greeter # Required
port: 8080 # Required
enabled: true # Required
commonService:
enabled: true # Optional, default: false
interceptors:
tracingTelemetry:
enabled: true # Optional, Enable tracing interceptor/middleware
exporter:
file:
enabled: true
outputPath: "logs/tracing.log" # Optional, Log to files
YAML options
name | describe | Types of | Defaults |
---|---|---|---|
mux.interceptors.tracingTelemetry.enabled | Start the call chain interceptor | boolean | false |
mux.interceptors.tracingTelemetry.exporter.file.enabled | start file output | boolean | false |
mux.interceptors.tracingTelemetry.exporter.file.outputPath | output file path | string | stdout |
mux.interceptors.tracingTelemetry.exporter.jaeger.agent.enabled | jaeger agent as data output | boolean | false |
mux.interceptors.tracingTelemetry.exporter.jaeger.agent.host | jaeger agent address | string | localhost |
mux.interceptors.tracingTelemetry.exporter.jaeger.agent.port | jaeger agent port | int | 6831 |
mux.interceptors.tracingTelemetry.exporter.jaeger.collector.enabled | jaeger collector as data output | boolean | false |
mux.interceptors.tracingTelemetry.exporter.jaeger.collector.endpoint | jaeger collector address | string | http://localhost:16368/api/trace |
mux.interceptors.tracingTelemetry.exporter.jaeger.collector.username | jaeger collector username | string | "" |
mux.interceptors.tracingTelemetry.exporter.jaeger.collector.password | jaeger collector password | string | "" |
Introduction to rk-boot
rk-boot is a framework for launching various web services via YAML. Somewhat similar to Spring boot. By integrating the rk-xxx series of libraries, various web frameworks can be started. Of course, users can also customize the rk-xxx library and integrate it into rk-boot.
rk-boot highlights
Start different web frameworks through YAML files in the same format.
For example, we can start gRPC, Gin, Echo, GoFrame frameworks simultaneously in one process through the following files. Unify the microservice layout within the team.
- Dependency installation
go get github.com/rookie-ninja/rk-boot/grpc
go get github.com/rookie-ninja/rk-boot/gin
go get github.com/rookie-ninja/rk-boot/echo
go get github.com/rookie-ninja/rk-boot/gf
- boot.yaml
---
grpc:
- name: grpc-server
port: 8080
enabled: true
commonService:
enabled: true
gin:
- name: gin-server
port: 8081
enabled: true
commonService:
enabled: true
echo:
- name: echo-server
port: 8082
enabled: true
commonService:
enabled: true
gf:
- name: gf-server
port: 8083
enabled: true
commonService:
enabled: true
- main.go
// Copyright (c) 2021 rookie-ninja
//
// Use of this source code is governed by an Apache-style
// license that can be found in the LICENSE file.
package main
import (
"context"
"github.com/rookie-ninja/rk-boot"
_ "github.com/rookie-ninja/rk-boot/echo"
_ "github.com/rookie-ninja/rk-boot/gf"
_ "github.com/rookie-ninja/rk-boot/gin"
_ "github.com/rookie-ninja/rk-boot/grpc"
)
// Application entrance.
func main() {
// Create a new boot instance.
boot := rkboot.NewBoot()
// Bootstrap
boot.Bootstrap(context.Background())
// Wait for shutdown sig
boot.WaitForShutdownSig(context.Background())
}
- verify
# gRPC throuth grpc-gateway
$ curl localhost:8080/rk/v1/healthy
{"healthy":true}
# Gin
$ curl localhost:8081/rk/v1/healthy
{"healthy":true}
# Echo
$ curl localhost:8082/rk/v1/healthy
{"healthy":true}
# GoFrame
$ curl localhost:8083/rk/v1/healthy
{"healthy":true}
Web frameworks supported by rk-boot
Contributions of new web frameworks to the rk-boot series are welcome.
Refer to docs & rk-gin for example.
frame | development status | Install | rely |
---|---|---|---|
Gin | Stable | go get github.com/rookie-ninja/rk-boot/gin | rk-gin |
gRPC | Stable | go get github.com/rookie-ninja/rk-boot/grpc | rk-grpc |
Echo | Stable | go get github.com/rookie-ninja/rk-boot/echo | rk-echo |
GoFrame | Stable | go get github.com/rookie-ninja/rk-boot/gf | rk-gf |
Fiber | Testing | go get github.com/rookie-ninja/rk-boot/fiber | rk-fiber |
go-zero | Testing | go get github.com/rookie-ninja/rk-boot/zero | rk-zero |
GorillaMux | Testing | go get github.com/rookie-ninja/rk-boot/mux | rk-mux |
rk-mux 介绍
rk-mux 用于通过 YAML 启动 gorilla/mux Web 服务。
支持的功能
根据 YAML 文件初始化如下的实例,如果是外部以来,均保持原生用法。
实例 | 介绍 |
---|---|
mux.Router | 原生 gorilla/mux |
Config | 原生 spf13/viper 参数实例 |
Logger | 原生 uber-go/zap 日志实例 |
EventLogger | 用于记录 RPC 请求日志,使用 rk-query |
Credential | 用于从远程服务,例如 ETCD 拉取 Credential |
Cert | 从远程服务(ETCD 等等)中获取 TLS/SSL 证书,并启动 SSL/TLS |
Prometheus | 启动 Prometheus 客户端,并根据需要推送到 pushgateway |
Swagger | 本地启动 Swagger UI |
CommonService | 暴露通用 API |
TV | TV 网页,展示微服务的基本信息 |
StaticFileHandler | 启动 Web 形式的静态文件下载服务,后台存储支持本地文件系统 和 pkger. |
支持的中间件
rk-mux 会根据 YAML 文件初始化中间件。
Middleware | Description |
---|---|
Metrics | 收集 RPC Metrics,并启动 prometheus |
Log | 使用 rk-query 记录每一个 RPC 日志 |
Trace | 收集 RPC 调用链,并且发送数据到 stdout, 本地文件或者 jaeger open-telemetry/opentelemetry-go. |
Panic | Recover from panic for RPC requests and log it. |
Meta | 收集服务元信息,添加到返回 Header 中 |
Auth | 支持 [Basic Auth] & [API Key] 验证中间件 |
RateLimit | RPC 限速中间件 |
Timeout | RPC 超时中间件 |
CORS | CORS 中间件 |
JWT | JWT 验证 |
Secure | 服务端安全中间件 |
CSRF | CSRF 中间件 |
Mux 完整 YAML 配置
---
#app:
# description: "this is description" # Optional, default: ""
# keywords: ["rk", "golang"] # Optional, default: []
# homeUrl: "http://example.com" # Optional, default: ""
# iconUrl: "http://example.com" # Optional, default: ""
# docsUrl: ["http://example.com"] # Optional, default: []
# maintainers: ["rk-dev"] # Optional, default: []
#zapLogger:
# - name: zap-logger # Required
# description: "Description of entry" # Optional
#eventLogger:
# - name: event-logger # Required
# description: "Description of entry" # Optional
#cred:
# - name: "local-cred" # Required
# provider: "localFs" # Required, etcd, consul, localFs, remoteFs are supported options
# description: "Description of entry" # Optional
# locale: "*::*::*::*" # Optional, default: *::*::*::*
# paths: # Optional
# - "example/boot/full/cred.yaml"
#cert:
# - name: "local-cert" # Required
# provider: "localFs" # Required, etcd, consul, localFs, remoteFs are supported options
# description: "Description of entry" # Optional
# locale: "*::*::*::*" # Optional, default: *::*::*::*
# serverCertPath: "example/boot/full/server.pem" # Optional, default: "", path of certificate on local FS
# serverKeyPath: "example/boot/full/server-key.pem" # Optional, default: "", path of certificate on local FS
# clientCertPath: "example/client.pem" # Optional, default: "", path of certificate on local FS
# clientKeyPath: "example/client.pem" # Optional, default: "", path of certificate on local FS
#config:
# - name: rk-main # Required
# path: "example/boot/full/config.yaml" # Required
# locale: "*::*::*::*" # Required, default: *::*::*::*
# description: "Description of entry" # Optional
mux:
- name: greeter # Required
port: 8080 # Required
enabled: true # Required
# description: "greeter server" # Optional, default: ""
# cert:
# ref: "local-cert" # Optional, default: "", reference of cert entry declared above
# sw:
# enabled: true # Optional, default: false
# path: "sw" # Optional, default: "sw"
# jsonPath: "" # Optional
# headers: ["sw:rk"] # Optional, default: []
# commonService:
# enabled: true # Optional, default: false
# static:
# enabled: true # Optional, default: false
# path: "/rk/v1/static" # Optional, default: /rk/v1/static
# sourceType: local # Required, options: pkger, local
# sourcePath: "." # Required, full path of source directory
# tv:
# enabled: true # Optional, default: false
# prom:
# enabled: true # Optional, default: false
# path: "" # Optional, default: "metrics"
# pusher:
# enabled: false # Optional, default: false
# jobName: "greeter-pusher" # Required
# remoteAddress: "localhost:9091" # Required
# basicAuth: "user:pass" # Optional, default: ""
# intervalMs: 10000 # Optional, default: 1000
# cert: # Optional
# ref: "local-test" # Optional, default: "", reference of cert entry declared above
# logger:
# zapLogger:
# ref: zap-logger # Optional, default: logger of STDOUT, reference of logger entry declared above
# eventLogger:
# ref: event-logger # Optional, default: logger of STDOUT, reference of logger entry declared above
# interceptors:
# loggingZap:
# enabled: true # Optional, default: false
# zapLoggerEncoding: "json" # Optional, default: "console"
# zapLoggerOutputPaths: ["logs/app.log"] # Optional, default: ["stdout"]
# eventLoggerEncoding: "json" # Optional, default: "console"
# eventLoggerOutputPaths: ["logs/event.log"] # Optional, default: ["stdout"]
# metricsProm:
# enabled: true # Optional, default: false
# auth:
# enabled: true # Optional, default: false
# basic:
# - "user:pass" # Optional, default: []
# ignorePrefix:
# - "/rk/v1" # Optional, default: []
# apiKey:
# - "keys" # Optional, default: []
# meta:
# enabled: true # Optional, default: false
# prefix: "rk" # Optional, default: "rk"
# tracingTelemetry:
# enabled: true # Optional, default: false
# exporter: # Optional, default will create a stdout exporter
# file:
# enabled: true # Optional, default: false
# outputPath: "logs/trace.log" # Optional, default: stdout
# jaeger:
# agent:
# enabled: false # Optional, default: false
# host: "" # Optional, default: localhost
# port: 0 # Optional, default: 6831
# collector:
# enabled: true # Optional, default: false
# endpoint: "" # Optional, default: http://localhost:14268/api/traces
# username: "" # Optional, default: ""
# password: "" # Optional, default: ""
# rateLimit:
# enabled: false # Optional, default: false
# algorithm: "leakyBucket" # Optional, default: "tokenBucket"
# reqPerSec: 100 # Optional, default: 1000000
# paths:
# - path: "/rk/v1/healthy" # Optional, default: ""
# reqPerSec: 0 # Optional, default: 1000000
# jwt:
# enabled: true # Optional, default: false
# signingKey: "my-secret" # Required
# ignorePrefix: # Optional, default: []
# - "/rk/v1/tv"
# - "/sw"
# - "/rk/v1/assets"
# signingKeys: # Optional
# - "key:value"
# signingAlgo: "" # Optional, default: "HS256"
# tokenLookup: "header:<name>" # Optional, default: "header:Authorization"
# authScheme: "Bearer" # Optional, default: "Bearer"
# secure:
# enabled: true # Optional, default: false
# xssProtection: "" # Optional, default: "1; mode=block"
# contentTypeNosniff: "" # Optional, default: nosniff
# xFrameOptions: "" # Optional, default: SAMEORIGIN
# hstsMaxAge: 0 # Optional, default: 0
# hstsExcludeSubdomains: false # Optional, default: false
# hstsPreloadEnabled: false # Optional, default: false
# contentSecurityPolicy: "" # Optional, default: ""
# cspReportOnly: false # Optional, default: false
# referrerPolicy: "" # Optional, default: ""
# ignorePrefix: [] # Optional, default: []
# csrf:
# enabled: true
# tokenLength: 32 # Optional, default: 32
# tokenLookup: "header:X-CSRF-Token" # Optional, default: "header:X-CSRF-Token"
# cookieName: "_csrf" # Optional, default: _csrf
# cookieDomain: "" # Optional, default: ""
# cookiePath: "" # Optional, default: ""
# cookieMaxAge: 86400 # Optional, default: 86400
# cookieHttpOnly: false # Optional, default: false
# cookieSameSite: "default" # Optional, default: "default", options: lax, strict, none, default
# ignorePrefix: [] # Optional, default: []