1 编译

1.1 Go编译注入信息

Go微服务的编译是微服务的第一步,也是比较重要的一个环节。我们可以在编译的时候注入很多编译信息,例如应用名称、应用版本号、框架版本号、编译所在机器、编译时间,我们可以直接注入到二进制里。编译完成后,我们可以使用./micro --version ,查看该服务的基本情况,如下图所示。

image

我们还可以在微服务启动后,将这些编译信息写入prometheus或者etcd中。当线上出现什么问题的时候,我们能够快速知道微服务在线上使用的哪个版本、编译在什么时间,提升我们排查微服务问题的速度。

接下来我们就来看下如何在Go微服务里编译这些信息

1.2 常用编译指令

go build指令比较多。我们把微服务里常用的命令展示在下表:

参数备注
-o目标地址
-race开启竞态检测
-ldflags传递参数
-n打印编译时会用到的所有命令,但不真正执行
-x打印编译时会用到的所有命令
-tag根据tag版本编译

-o

编译到指定地址

  1. go build -o micro

-race

开启竞态检查编译。通过这个编译方式。你的程序可以在运行的时候崩溃

  1. go build -o micro -race
  2. curl http://127.0.0.1:8080/race

我们开启race编译后,访问该地址,就可以看到代码中出现race的报错

-ldflags

  • -w 去掉DWARF调试信息,得到的程序就不能用gdb调试了
  • -s 去掉符号表,panic时候的stack trace就没有任何文件名/行号信息了,这个等价于普通C/C++程序被strip的效果
  • -X 设置包中的变量值
  1. go build -o micro -ldflags "-X main.buildName=micro\
  2. -X main.buildGitRevision=f8c315083e7b739f0f055ee46a747c8e109d7539-dirty\
  3. -X main.buildStatus=Modified -X main.buildUser=`whoami` \
  4. -X main.buildHost=`hostname -f` -X main.buildTime=`date +%Y-%m-%d--%T`"

编译演示代码

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/spf13/pflag"
  5. "runtime"
  6. )
  7. var (
  8. buildName = "unknown"
  9. buildGitRevision = "unknown"
  10. buildUser = "unknown"
  11. buildHost = "unknown"
  12. buildStatus = "unknown"
  13. buildTime = "unknown"
  14. )
  15. var (
  16. version bool
  17. run bool
  18. )
  19. func init() {
  20. pflag.BoolVarP(&version, "version", "v", false, `查看版本号`)
  21. pflag.BoolVarP(&run, "run", "r", false, `运行程序`)
  22. pflag.Parse()
  23. }
  24. func main() {
  25. if version == true {
  26. fmt.Println(LongForm())
  27. }
  28. if run == true {
  29. fmt.Println("go to micro")
  30. }
  31. }
  32. func LongForm() string {
  33. return fmt.Sprintf(`Name: %v
  34. GitRevision: %v
  35. User: %v@%v
  36. GolangVersion: %v
  37. BuildStatus: %v
  38. BuildTime: %v
  39. `,
  40. buildName,
  41. buildGitRevision,
  42. buildUser,
  43. buildHost,
  44. runtime.Version(),
  45. buildStatus,
  46. buildTime)
  47. }

-tag

用于编译打tag,灰度测试代码使用。例如

  1. go build -o micro -tag="build1"

1.3 EGO编译指令

脚本

脚本代码路径1 编译 - 图2 (opens new window)

脚本核心代码如下:

  1. echo "github.com/gotomicro/ego/core/eapp.appName=${APP_NAME}"
  2. echo "github.com/gotomicro/ego/core/eapp.buildVersion=${VERSION}"
  3. echo "github.com/gotomicro/ego/core/eapp.buildAppVersion=${BUILD_GIT_REVISION}"
  4. echo "github.com/gotomicro/ego/core/eapp.buildStatus=${tree_status}"
  5. echo "github.com/gotomicro/ego/core/eapp.buildUser=$(whoami)"
  6. echo "github.com/gotomicro/ego/core/eapp.buildHost=$(hostname -f)"
  7. echo "github.com/gotomicro/ego/core/eapp.buildTime=$(date '+%Y-%m-%d--%T')"

获取框架版本代码

应用大部分信息是通过编译时期通过脚本获取信息注入到二进制包的变量里,但应用获取ego框架版本则是通过遍历runtime/debug包的版本信息,匹配后获取对应的ego版本

  1. egoVersion := "unknown version"
  2. info, ok := debug.ReadBuildInfo()
  3. if ok {
  4. for _, value := range info.Deps {
  5. if value.Path == "github.com/gotomicro/ego" {
  6. egoVersion = value.Version
  7. }
  8. }
  9. }

信息展示

最终我们会将应用的编译信息和环境变量信息写入到以下prometheus中,方便我们查询业务的基本情况。

  1. GaugeVecOpts{
  2. Namespace: DefaultNamespace,
  3. Name: "build_info",
  4. Labels: []string{"name", "mode", "region", "zone", "app_version", "ego_version", "start_time", "build_time", "go_version"},
  5. }.Build()