本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 [署名 4.0 国际 (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/deed.zh) 本文作者: 苏洋 创建时间: 2022年07月04日 统计字数: 8072字 阅读时间: 17分钟阅读 本文链接: https://soulteary.com/2022/07/04/build-a-maintainable-golang-development-environment.html ----- # 搭建可维护的 Golang 开发环境 本篇文章将聊聊如何快速搭建 Linux 环境中的 Golang 开发环境。 在[《基础篇》](https://soulteary.com/2022/06/21/building-a-cost-effective-linux-learning-environment-on-a-laptop-the-basics.html)的内容中,我们聊过了如何基于 Ubuntu 22.04 搭建基础的 Linux 学习环境。接下来的文章里,我们先来聊聊如何在 Linux 环境中,快速安装配置各种可维护的语言环境。 ## 写在前面 在开始聊如何做之前,我们首先要了解为什么要这么做。 我知道有不少同学有安装好环境之后,然后持续使用一个环境,并“天长地久”的用下去的习惯。这样做的好处是“看起来简单省心”,但实际上却埋藏了许多隐患,举几个常见的例子: - 同事说代码、程序有问题,跑不起来,你说程序在你这边是正常的,你觉得他的使用方式不对,他觉得你的程序“兼容性”/“健壮性”有问题。 - 当你想把半年、一年前的项目跑起来,发现运行的时候多了非常多的“警告”,甚至是“报错”,尤其是当你换了一台电脑的时候。 - 项目扩充人手,你的同事也需要配置一套环境,你和他折腾的半天,虽然有着重重困难,什么版本不对,配置散落在系统的各个“犄角旮旯”等等,但是你们最终克服了困难,并加深了革命友情。 - 项目遇到大版本升级,因为一些历史原因,你需要同时使用两个不同的语言版本来做调试,不同版本存在兼容性问题(包括依赖兼容性问题),你的本地环境是升级还是不升呢。 解决这个问题的最佳方案有两个: - 尽可能简化你的环境,简化环境依赖(因为项目的多样性和复杂性,这有一些难)。 - 尽可能参考“基础架构即代码(IaC)”的思想去维护我们自己的开发环境,让我们所使用的内容,尽可能**配置化,透明化,可复现**。 它除了能够完成 golang 开发环境的快速安装之外,还能够保障多个版本的 golang 共存,不同版本的软件依赖包都保持正常工作。并且,它的实现和社区大名鼎鼎的 nvm-sh/nvm 、shyiko/jabba 是一致的,都是由 BASH 编写,和所需要管理的 Runtime 语言无关,能够更稳定的完成“管理工作”。 ## Golang 环境安装和配置使用 关于 Golang 的多版本管理和安装,我曾经写过两篇相关的内容,一篇是半年前的内容,分享如何对 “[Golang 进行多版本管理](https://soulteary.com/2021/12/15/golang-multi-version-management.html)”,另外一篇则是这篇的补充内容,分享如何针对 Mac M1 这类 ARM 设备使用 Golang 版本管理工具:[《M1 芯片 Mac 上更好的 Golang 使用方案》](https://soulteary.com/2022/05/12/better-golang-usage-on-m1-mac.html)。 如果你希望了解本章节之外的实践内容,或者过程中的思考,可以翻阅上面两篇内容。本篇文章的重点在于如何快速安装和配置,所以就不再展开相关“折腾安装工具”的细节啦。 ### 安装 Golang 版本管理工具:soulteary/gvm 关于 Golang 的安装和版本升降级,因为老牌开源软件 GVM (Go Version Manager)“年久失修”,所以我做了一个修正版:[https://github.com/soulteary/gvm](https://github.com/soulteary/gvm) 想要正常使用这个工具,我们需要先完成工具的基础依赖的安装: ```bash sudo apt install -y binutils bison gcc make ``` 接着,执行下面的命令,通过网络获取安装脚本,然后在本地执行脚本完成安装。(如果你因为网络或其他原因,无法执行这条命令,可以使用下文中的替代方案): ```bash curl -sSL https://github.com/soulteary/gvm/raw/master/binscripts/gvm-installer | bash ``` 不出意外的话,你将会看到类似下面的日志输出,意味着工具就此刻已经安装好啦。 ```bash Cloning from https://github.com/soulteary/gvm.git to /home/soulteary/.gvm No existing Go versions detected Installed GVM v1.0.24 Please restart your terminal session or to get started right away run `source /home/soulteary/.gvm/scripts/gvm` ``` 这里选择执行或者不执行 `source /home/soulteary/.gvm/scripts/gvm` 这条命令都可以(注意调整路径中的用户名),因为在接下来的文章中,我们将使用更靠谱的方式来将命令注册到我们所使用的 SHELL 环境中。 ## 使用国内镜像来安装:soulteary/gvm 为了让安装过程更加顺利,我们可以使用从国内镜像下载包含安装脚本的仓库代码,然后直接执行安装脚本,来完成 `gvm` 这个开源软件的安装。 先使用 `git clone` 下载完整的软件仓库: ```bash git clone https://gitcode.net/soulteary/gvm.git ``` 指定 `SRC_REPO` 参数为国内镜像地址,然后运行安装脚本: ```bash SRC_REPO=https://gitcode.net/soulteary/gvm.git bash gvm/binscripts/gvm-installer ``` 当脚本运行完毕,我们将会看到上文中提到过的日志输出,此刻 `gvm` 就安装完毕啦。 ```bash Cloning from https://gitcode.net/soulteary/gvm.git to /home/soulteary/.gvm No existing Go versions detected Installed GVM v1.0.24 Please restart your terminal session or to get started right away run `source /home/soulteary/.gvm/scripts/gvm` ``` 为了更方便的使用 `gvm`,我们还需要进行一些配置。 ### 配置 gvm 加速 Golang 下载/切换 gvm 支持使用两种方式来下载 “Golang”,然而不论是“下载源码编译安装”,还是下载适合当前操作系统的“预编译好的二进制文件”,我们都需要访问官方地址。 为了避免下载过程中因为网络问题,出现下载慢,或者无法下载的情况,节约我们的时间,我们需要对 `gvm` 进行一些简单的配置。 我们可以在当前使用的 “`SHELL`” 的 “`rc`” 文件中(比如`.bashrc` 或者 `.zshrc`),添加下面的内容,来在当前的环境中让 `gvm` 命令生效,同时,让我们能够使用更快的下载源来下载我们所需要的 “Golang”: ```bash export GO_BINARY_BASE_URL=https://golang.google.cn/dl/ [[ -s "$HOME/.gvm/scripts/gvm" ]] && source "$HOME/.gvm/scripts/gvm" export GOROOT_BOOTSTRAP=$GOROOT ``` 在“`rc`” 文件中添加了上述内容后,需要重启终端会话,才能够让会话生效。你可以使用 `CTRL+D` 退出登录,然后再重新使用 `SSH` 进行终端连接或者直接在本地创建一个新的会话(具体怎么做,取决于你是如何开启的会话)。 为了让配置过程清晰透明,上面的三条命令,我们来依次看看上面的命令都“做了什么事情”。 ```bash export GO_BINARY_BASE_URL=https://golang.google.cn/dl/ ``` 命令中的 `GO_BINARY_BASE_URL` 变量,定义了我们将从何处下载 Golang 的二进制文件或源码压缩包进行安装。当然,你也可以将其替换为下面的任意一个。 ```bash # 官方地址 https://go.dev/dl/ # 官方国内镜像地址 https://golang.google.cn/dl/ # 阿里云镜像 https://mirrors.aliyun.com/golang/ # 中科大镜像 http://mirrors.ustc.edu.cn/golang/ ``` 接下来,我们来看看三条命令中看似最复杂的命令: ```bash [[ -s "$HOME/.gvm/scripts/gvm" ]] && source "$HOME/.gvm/scripts/gvm" ``` 这条命令,是根据软件的实际安装情况来选择性加载 `gvm`。相比较前文中安装完毕 `gvm` 日志输出内容推荐我们直接使用 `source` 命令加载 `gvm`,这样可以更安全的执行命令,当且仅当 `~/.gvm` 存在的时候才会加载程序,将 `gvm` 注册到你当前的 `SHELL` 环境中。 ```bash export GOROOT_BOOTSTRAP=$GOROOT ``` 最后一条命令,则是为了确保 Golang 使用源码编译安装时,不会出错(golang 1.14后需要 ),感兴趣可以围观官方开源项目中的[这个 issue](https://github.com/golang/go/issues/12214)。 ## gvm 简明实用教程 `gvm` 是一个特别简单的命令,我们日常使用中其实只需要记得**两个命令**就好,第一个是 `gvm install`,第二个是 `gvm use`。 假设我们想安装 Golang 最新版本 1.18.3,那么只需要执行下面的命令: ```bash gvm install go1.18.3 -B ``` 在执行完毕命令之后,稍等片刻,当我们看到 `Installing go1.18.3 from binary source` 这条日志输出结果后,就意味着 Golang 已经被下载完毕了。如果你希望使用编译源码的方式安装 Golang 的话,可以去掉上面命令中的`-B` 参数: ```bash gvm install go1.18.3 ``` 虽然我们已经完成了 Golang 1.18.3 的安装,但是目前我们还不能直接使用它,需要再执行一条命令,将这个版本的 Golang “激活”: ```bash gvm use go1.18.3 --default ``` 在执行完命令之后,我们能够立刻看到类似 `Now using version go1.18.3` 的日志输出结果,接下来我们就可以随意的使用 `go` 这个命令了。 我们可以使用 `go version` 来验证刚刚下载的程序是否符合我们的诉求: ```bash go version go version go1.18.3 linux/amd64 ``` 未来如果 Golang 推出了新版本,我们想升级只需要按照上面的玩法,调整版本号,然后再执行一遍 `install` 和 `use` 命令就好了,**是不是很简单!** 当然,如果你只是想临时性的使用某个版本,比如 Golang 1.17 这个旧版本,可以稍微调整一下上面的命令,去掉 `use` 命令中的 `--default` 参数,只在当前 `SHELL` 会话中,让这个版本的 Golang 生效,随着我们关闭终端会话,Golang 的版本也会恢复到我们指定的默认版本,**再也不需要担心系统环境混乱的问题啦。** ```bash gvm install go1.17 -B gvm use go1.17 # 再次执行查看版本,可以看到版本号已经变化了 go version go version go1.17 linux/amd64 ``` ## 配置 Golang 软件包镜像 在日常开发和学习过程中,我们更多的是使用 Golang 来初始化项目和下载必要的软件包依赖。所以如何快速的下载到各种软件包也很重要,好在 Golang 提供了软件包代理配置选项 `GOPROXY`,我们可以通过在 “`SHELL`” 的 “`rc`” 文件中配置这个参数来完成下载提速: ```bash export GO111MODULE=on export GOPROXY="https://goproxy.cn" ``` 和上文中配置 `gvm` 一样,我们将上面的内容添加到所使用的 SHELL 的 “`rc`” 配置后,需要重新创建一个终端会话,让配置生效。 当然,你也可以将上面命令中使用的“镜像源”替换为下面任意一个: ```bash # 由七牛云赞助的项目 goproxy.cn # 阿里云 https://mirrors.aliyun.com/goproxy/ # 华为云 https://repo.huaweicloud.com/repository/goproxy/ # “一家的” goproxy.io proxy.golang.com.cn ``` 这里有一个题外话,初见“goproxy”的两个域名的时候,觉得域名十分相似,一番搜索,发现这两个域名虽然归属不同的开发者在维护,但是它们之间确实有一段缘分:[“goproxy.io 和 goproxy.cn 的关系”](https://github.com/goproxy/goproxy.cn/issues/61)。 ## Golang 环境验证:GoJieba 在完成环境配置之后,我们使用一个比较实用的 Golang 项目([https://github.com/yanyiwu/gojieba](https://github.com/yanyiwu/gojieba)),来验证环境是否“好用”。 随便创建一个程序目录,然后在其中创建一个名为 `main.go` 的文件,引用 “gojieba”,并对一些句子和词汇进行处理: ```go package main import ( "fmt" "strings" "github.com/yanyiwu/gojieba" ) func main() { var s string var words []string use_hmm := true x := gojieba.NewJieba() defer x.Free() s = "北京西站南广场东" words = x.CutAll(s) fmt.Println(s) fmt.Println("全模式:", strings.Join(words, "/")) words = x.Cut(s, use_hmm) fmt.Println(s) fmt.Println("精确模式:", strings.Join(words, "/")) s = "向量数据库" words = x.Cut(s, use_hmm) fmt.Println(s) fmt.Println("精确模式:", strings.Join(words, "/")) x.AddWord("向量数据库") s = "向量数据库" words = x.Cut(s, use_hmm) fmt.Println(s) fmt.Println("添加词典后,精确模式:", strings.Join(words, "/")) s = "前门到了,请您后门下车" words = x.Cut(s, use_hmm) fmt.Println(s) fmt.Println("新词识别:", strings.Join(words, "/")) s = "小明先去了北京西站南广场东,然后又去了南京东路北大街西" words = x.CutForSearch(s, use_hmm) fmt.Println(s) fmt.Println("搜索引擎模式:", strings.Join(words, "/")) s = "朝阳区三里屯优衣库" words = x.Tag(s) fmt.Println(s) fmt.Println("词性标注:", strings.Join(words, ",")) s = "元宇宙" words = x.Tag(s) fmt.Println(s) fmt.Println("词性标注:", strings.Join(words, ",")) s = "长江大桥" words = x.CutForSearch(s, !use_hmm) fmt.Println(s) fmt.Println("搜索引擎模式:", strings.Join(words, "/")) wordinfos := x.Tokenize(s, gojieba.SearchMode, !use_hmm) fmt.Println(s) fmt.Println("Tokenize:(搜索引擎模式)", wordinfos) wordinfos = x.Tokenize(s, gojieba.DefaultMode, !use_hmm) fmt.Println(s) fmt.Println("Tokenize:(默认模式)", wordinfos) keywords := x.ExtractWithWeight(s, 5) fmt.Println("Extract:", keywords) } ``` 在准备好程序文件之后,我们先执行 `go mod init main`,完成 Go 项目的初始化: ```go go: creating new go.mod: module main go: to add module requirements and sums: go mod tidy ``` 在执行完上面的命令后,我们的目录中将会多出来 “go.mod” 和 “go.sum” 两个文件,接着,我们来执行 `go mod tidy` 命令,让程序完成相关依赖的下载: ```go go: finding module for package github.com/yanyiwu/gojieba go: found github.com/yanyiwu/gojieba in github.com/yanyiwu/gojieba v1.1.2 ``` 因为我们配置了软件包镜像,所以应该在几秒内就能够完成项目的初始化。 在完成了项目初始化之后,我们执行 `go run main.go` 来验证下程序是否能运行,不出意外,将看到类似下面的输出结果: ```bash 北京西站南广场东 全模式: 北京/北京西/北京西站/京西/西站/南/广场/东 北京西站南广场东 精确模式: 北京西站/南/广场/东 向量数据库 精确模式: 向量/数据库 向量数据库 添加词典后,精确模式: 向量数据库 前门到了,请您后门下车 新词识别: 前门/到/了/,/请/您/后门/下车 小明先去了北京西站南广场东,然后又去了南京东路北大街西 搜索引擎模式: 小明/先去/了/北京/京西/西站/北京西/北京西站/南/广场/东/,/然后/又/去/了/南京/京东/东路/南京东路/北大/大街/北大街/西 朝阳区三里屯优衣库 词性标注: 朝阳区/ns,三里屯/ns,优衣库/x 元宇宙 词性标注: 元/m,宇宙/n 长江大桥 搜索引擎模式: 长江/大桥/长江大桥 长江大桥 Tokenize:(搜索引擎模式) [{长江 0 6} {大桥 6 12} {长江大桥 0 12}] 长江大桥 Tokenize:(默认模式) [{长江大桥 0 12}] Extract: [{长江大桥 11.1926274509}] ``` 当然,除了 `run` 之外,我们最常用的命令还有 `test` 和 `build`,本篇文章暂时不聊如何写单元测试,所以我们就先只验证 `build` 命令,执行 `go build .`,我们将在程序目录得到一个名为 `main` 的可执行文件。 手动执行命令 `./main`,不出意外,将得到和上面 `run` 一样的输出结果。至此,Golang 环境验证也就结束啦。 ## 最后 目前为止,我们已经聊完了“基础 Linux 环境搭建”、“Docker 环境安装和配置”、“Golang 的开发环境搭建”。 接下来的文章中,我会继续完成上篇文章中提到的几种不同的 K8S “发行版”的安装和配置,以及当今世界上流行的编程语言的环境配置。 希望对你有帮助。 --EOF