From 1278eae4d25134e32e2434045e7f866adc08f090 Mon Sep 17 00:00:00 2001 From: "liyuan.go" Date: Sat, 12 Jun 2021 20:35:18 +0800 Subject: [PATCH 1/2] feature(chap-33) --- .gitignore | 1 + chap-33-application-configuration/33.md | 442 ++++++++++++++++++ .../imgs/33-1-application.jpg | Bin 0 -> 30951 bytes 3 files changed, 443 insertions(+) create mode 100644 chap-33-application-configuration/33.md create mode 100644 chap-33-application-configuration/imgs/33-1-application.jpg diff --git a/.gitignore b/.gitignore index f2dd955..9ef7b5e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *.dll *.so *.dylib +.idea # Test binary, built with `go test -c` *.test diff --git a/chap-33-application-configuration/33.md b/chap-33-application-configuration/33.md new file mode 100644 index 0000000..c7c29d7 --- /dev/null +++ b/chap-33-application-configuration/33.md @@ -0,0 +1,442 @@ +# 第 33 章:应用程序配置 + +![](imgs/33-1-application.jpg) + +## 1 您将在本章学到什么? + +- 如何配置一个应用程序? +- 如何给程序加一个命令行选项? +- 如何创建和解析环境变量? + +## 2 涉及到的技术概念 + +- 环境变量 +- 解构 +- 命令行选项 + +## 3 介绍 + +当我们写程序时,(经常)会将一些重要的选项写进源码里,例如:服务监听的端口,使用的数据库名称等等。但是如果您尝试将您写的程序分享给别人呢?很明显在开发阶段您的用户不会和您使用同样的数据库名称;他们也想给自己的程序使用不同的端口号! + +并且还有安全问题呢!您想让每个人都知道您的数据库密码吗?您不应该提交这些凭证到您的代码中。 + +## 4 它是什么? + +当应用程序向用户提供控制执行方面的可能性时,它是可配置的[@rabkin2011static]。例如,用户可以自定义服务监听的端口,数据库凭证,HTTP 请求的超时时间等等。 + +## 5 该如何做呢? + +在许多开源应用程序中,配置项都是通过 键-值 数据结构处理的[@rabkin2011static]。配置经常通过一个独一无二的名字标识的。Unix 系统使用字符串作为选项名字。Windows 使用一种树状结构。 + +在实际的应用中,您总是会找到一个 class(或一个类型结构体)将配置选项暴露出去。这个 class 经常解析一个文件将值赋给每个选项。应用程序通常建议使用命令行选项来调整配置。 + +## 6 命令行选项 + +为了定义一个命令行选项的配置名称,我们可以使用标准库 `flag` 。 + +举个例子。您在构建一个 REST API 服务。您的应用将会暴露一个网络服务;您想让用户自己配置监听端口。 + +第一步,先定义一个新标识: + +``` Go +// configuration/cli/main.go + +port := flag.Int("port", 4242, "the port on which the server will listen") +``` + +第一个参数是标识名;第二个参数是默认值;第三个是帮助文本。返回值是一个 integer 型的指针。 + +``` Go +var port int +flag.IntVar(&port, "port", 4242, "the port on which the server will listen") +``` + +下一步就是解析这些标识: + +``` Go +flag.Parse() +``` + +在您使用自己定义的这些标识前,`Parse` 函数必须被调用。在函数内部,它将遍历命令行中给出的所有参数,并为您定义的标识变量赋值。 + +在第一版(`flag.Int`)中,变量将成为一个数字型指针(**`*int`**)。在第二版(`flag.Intvar`)将成为数字类型(**`int`**)。这种特殊性改变了您以后使用变量的方式: + +``` Go +// version 1 : Int + +port := flag.Int("port", 4242, "the port on which the server will listen") +flag.Parse() +fmt.Printf("%d",*port) + +// version 2 : IntVar + +var port2 int flag.IntVar(&port2, "port2", 4242, "the port on which the server will listen") flag.Parse() +fmt.Printf("%d\n",port2) +``` + +### 6.0.1 其他类型 + +`flag` 包提供了一些函数来添加不同类型的标识。下面左边是一些类型和 flag 包的相关函数。 + +Int64, Int64Var + +String, StringVar + +Uint, UintVar + +Uint64, Uint64Var + +Float64, Float64Var + +Duration, DurationVar + +Bool, BoolVar + +### 6.0.2 在命令行中的用法 + +有两种方法将指定的标识传给程序 + +``` Bash +$ myCompiledGoProgram -port=4242 +``` +或者 +``` Bash +$ myCompiledGoProgram -port 4242 +``` + +bool 标识类型禁止使用最后一种用法。您的应用可以简单的写成这样来设置标识为 true: + +``` Bash +$ myCompiledGoProgram -myBoolFlag +``` + +如此做,标识背后的变量将会被设置为 true。 + +您可以使用 help flag 来查看所有可用的标识(这是默认添加到您的程序中的) + +``` Bash +$ ./myCompiledGoProgram -help +Usage of ./myCompiledProgram: + -myBool + a test boolean + -port int + the port on which the server will listen (default 4242) + -port2 int + the port on which the server will listen (default 4242) +``` + +## 7 环境变量 + +一些云服务通过环境变量支持配置。环境变量使用 export 命令很容易配置。只需要在您的终端输入以下命令即可创建一个名为 MYVAR 的环境变量: + +``` Bash +$ export MYVAR=value +``` + +如果您想将环境变量传递给应用程序,使用以下语句: + +``` Bash +$ export MYVAR=test && export MYVAR2=test2 && ./myCompiledProgram +``` + +这里我们设置了两个环境变量,并启动了程序 `myCompiledProgram`。 + +### 7.0.1 如何获取环境变量的值 + +您可以使用 `os` 标准包获取环境变量的值。该包暴露了一个名为 `Getenv` 的方法。这个方法使用变量的名字作为参数并且返回它的值(以字符串的形式): + +``` Go +// configuration/env/main.go +myvar := os.Getenv("MYVAR") +myvar2 := os.Getenv("MYVAR2") +fmt.Printf("myvar : '%s'\n", myvar) +fmt.Printf("myvar2 :'%s'\n", myvar2) +``` + +您可以使用其他的方法获取环境变量的值:`LookupEnv`。它比前一种方法要好,因为如果变量不存在的话它会通知您: + +``` Go +// configuration/env/main.go +port, found := os.LookupEnv("DB_PORT") +if !found { + log.Fatal("impossible to start up, DB_PORT env var is mandatory") +} +portParsed, err := strconv.ParseUint(port, 10, 8) +if err != nil { + log.Fatalf("impossible to parse db port: %s", err) +} +log.Println(portParsed) +``` + +`LookupEnv` 将会检查环境变量是否存在: + +- 如果不存在,第二个结果(`found`)将等于 `false` +- 在上面的例子中,我们使用 `strconv.ParseUint` 方法将端口值解析为 uint16(基于十进制) + +### 7.0.2 如何获取所有的环境变量 + +您可以使用 Environ() 方法获取所有的环境变量,其返回一个字符串类型的 slice: + +``` Go +fmt.Println(os.Environ()) +``` + +返回的 slice 以『键=值』的格式组成所有的变量。slice 的每个元素都是一个 键-值 对。包并不会从键中分割值,您必须解析 slice 的值。 + +### 7.0.3 如何设置一个环境变量 + +os 包暴露了 `os.Setenv` 方法: + +``` Go +err := os.Setenv("MYVAR3","test3") +if err != nil { + panic(err) +} +``` + +`os.Setenv` 方法接收两个参数: + +- 设置的变量名 +- 它的值 + +注意,它会产生错误,您要记得处理错误。 + +## 8 基于文件的配置 + +应用可以保存为文件并且被应用自动解析。 + +文件的格式依赖于应用的需求。如果您需要一个带着等级和层级(以 Windows Registry 的方式)的复杂配置,可以考虑使用 JSON,YAML,或者 XML 格式。 + +我们看一下 JSON 配置文件的例子: + +``` Json +{ + "server": { + "host": "localhost", + "port": 80 + }, + "database": { + "host": "localhost", + "username": "myUsername", + "password": "abcdefgh" + } +} +``` + +我们把这个文件放置在托管应用程序的某个地方。在应用程序里解析这些文件以获取配置值。 + +为了解析它,首先在程序中创建这样一个结构体: + +``` Go +// configuration/file/main.go +type Configuration struct { + Server Server `json:"server"` + Database DB `json:"database"` +} +type Server struct { + Host string `json:"host"` + Port int `json:"port"` +} +type DB struct { + Host string `json:"host"` + Username string `json:"username"` + Password string `json:"password"` +} +``` + +我们创建了三种类型的结构体: + +- `Configuration` 包括了其它两种类型 +- `Server` 代表了 JSON 对象 **server** +- `DB` 存储了 JSON 对象 **database** + +您可以使用这些类型的结构体解析这个 JSON 配置文件。下一步就是打开文件读取内容: + +``` Go +// configuration/file/main.go + +confFile, err := os.Open("myConf.json") +if err != nil { + panic(err) +} +defer confFile.Close() +conf, err := ioutil.ReadAll(confFile) +if err != nil { + panic(err) +} +``` + +这里我们调用 `os.Open` 函数打开位于可执行文件同级目录中的 `myConf.json` 文件。在实际的环境中,您应该把文件上传到机器的合适位置。 + +通过使用 `ioutil.ReadAll` 函数获取整个文件的内容。该函数返回一个 byte 类型的 slice。下一步就是使用 `json.Unmarshal` 函数处理这个 slice: + +``` Go +// configuration/file/main.go + +myConf := Configuration{} +err = json.Unmarshal(conf, &myConf) +if err != nil { + panic(err) +} +fmt.Printf("%+v",myConf) +``` + +我们首先创建了一个变量 `myConf` 初始化为一个空结构体 `Configuration`。然后我们把从文件中提取出的 byte 类型的 slice 传给 `json.Unmarshal` 函数,和(第二个参数)一个 `myConf` 指针地址。 + +最后,我们的程序会输出如下: + +``` Go +{Server:{Host:localhost Port:80} Database:{Host:localhost Username:myUsername Password:abcdefgh}} +``` + +您可以在整个应用程序中共享这个变量。 + +## 9 github.com/spf13/viper 模块 + +`github.com/spf13/viper` 是非常受当前 Go 社区欢迎的配置模块,其拥有超过 14,000 星和超过 1.3k forks。 + +这个包允许您通过文件、环境变量、命令行、buffers 等等简单定义一个配置。它亦支持一个有趣的特性:如果您的配置项在应用程序的生命周期中改变了,它将会重载。 + +### 9.1 从文件中读取 + +尝试一下: + +``` Go +// configuration/viper/main.go +//... +// import "github.com/spf13/viper" +viper.SetConfigName("myConf") +viper.(".") +err := viper.ReadInConfig() +if err != nil { + log.Panicf("Fatal error config file: %s\n", err) +} +``` + +首先,您必须定义您想加载的配置文件的名字:`viper.SetConfigName("myConf")`。这里我们加载了名为 `myConf` 的文件,注意不能给 viper 文件的扩展名。其将会检索和解析正确的文件。 + +在下一行,必须告诉 viper 使用函数 `AddConfigPath` 搜索。点意味着『在当前文件夹下查找』名为 myConf 的文件。当前支持的扩展名如下: + +- json +- toml +- yaml +- yml +- properties +- props +- prop +- hcl + +注意应该为配置添加其它的路径。`AddConfig` 函数将会添加这些路径到一个 slice 中。 + +当我们调用 `ReadInConfig` 函数时,包将会搜寻这个特定的文件。 + +可以加载使用如下配置: + +``` Go +fmt.Println(viper.AllKeys()) +//[database.username database.password server.host server.port database.host] +fmt.Println(viper.GetString("database.username")) +// myUsername +``` + +使用 `AllKeys` 函数,可以检索应用程序的所有存在的配置键。使用 `GetString` 函数获取一个特定的配置变量。 + +`GetString` 函数接收配置项的键作为参数,键是配置文件层次结构的反映。这里的 `"database.username"` 意思是我们从 **database** 对象中获取 **username** 属性。 + +database 对象也有 host 熟悉,可以简单的通过 `"database.host"` 获取。 + +viper 暴露 `GetXXX` 函数这些类型 **`bool`**,**`float64`**,**`int`**,**`string`**,**`map[string]interface{}`**,**`map[string]string`**,slice of strings,time 和 duration。也可以简单地使用 Get 获取配置项的值,返回的类型是空接口。我并不推荐这么做,因为其可能导致类型错误。在我们的例子中,我们期望字符串,但是如果一个用户把数字填入配置文件中,它就会返回一个数字。您的程序将会出现预期外的罢工和 panic。 + +### 9.2 从环境变量中读取 + +Viper 可以使用 prefix 自动解析环境变量: + +``` Go +viper.SetEnvPrefix("myapp") +viper.AutomaticEnv() +``` + +有了这两行之后,您每次调用一个方法(`GetString`,`GetBool` 等等)viper 将会查找带着 MYAPP_ 前缀的环境变量: + +``` Go +fmt.Println(viper.GetInt("timeout")) +``` + +将会查找名为 `MYAPP_TIMEOUT` 的环境变量。 + +## 10 问题以及如何避免 + +### 10.1 文档缺失 + +许多项目都缺失关于配置项的文档。 + +通过在一个单独的文件中记录每一个可用配置选项,可以降低软件的采用门槛。 + +在公司内部,您可能不是处理应用程序部署的人,因此,您必须在您的估计中包括文档时间。更好的文档意味着减少了解决方案的上市时间。 + +来着 Berkeley University[@rabkin2011static] 的 Ariel Rabkin 和 Randy Katz 的一项有趣的研究表面,甚至一些大型开源项目(他们研究了 Cassandra,Hadoop,HBase 等七个大型项目)的配置文档都是不准确的: + +- 其中提到了应用程序源代码中不存在的配置项。他们指出,有时这些选项只是简单地注释到应用程序源代码中。 +- 应用程序源代码中存在一些甚至没有文档记录的选项。 + +解决方法很简单:为您的配置选项创建精确的文档,并在项目开发时保持最新。 + +### 10.2 配置错误 + +在一项研究中[@nagaraja2004understanding]技术操作人员被要求在一个真实的互联网系统(三层拍卖应用程序)上执行操作。据观察,50%的错误是配置错误! + +这个数字令人印象深刻,也非常有趣。系统可能会因为配置错误而失败。您无法阻止用户在配置中输入错误的端口号。但是应用程序可以保护自己不受错误配置的影响。 + +- 通过检查和警告用户。 +- 不使用默认值替换错误值。 + +``` Go +// configuration/error-detection/main.go +//... +dbPortRaw := os.Getenv("DATABASE_PORT") +dbPort, err := strconv.ParseUint(port, 10, 16) +if err != nil { + log.Panicf("Impossible to parse database port number '%s'. Please double check the env variable DATABASE_PORT",dbPortRaw) +} +``` + +在前面的代码里,我们检索环境变量 `DATABASE_PORT` 的值。接着尝试把它转换为 **`uint16`** 类型。如果出现错误,我们拒绝启动应用程序并且通知用户错误原因。 + +注意这里应该通知用户如何修复这个错误。这是一个好的例子,仅仅花费您十秒钟的时间写,但是可能省去了用户一个小时的查找错误时间。 + +再次检查程序是否有效地使用了所有配置变量。 + +## 11 安全 + +配置变量通常由这些组成:密码、访问令牌、加密密钥等等。泄露这些机密可能会造成损失。以上所有解决方案都不能完美地存储和保护生产机密。频繁变换这些机密信息也不是一个很好地解决方案。 + +一些开源解决方案可以处理这个具体的问题。它们提供了一系列功能来保护、监视和审计您的机密信息的使用。在写作的时候,似乎由 Hashicorp 编辑的 Vault 是最流行的。而且它是用 Go 写的! + +## 12 自我测试 + +### 12.1 问题 + +1. 如何给程序添加一个字符串类型的命令行选项? +2. 如何检查一个环境变量是否存在,并且在同一个调用中获取它的值? +3. 如何设置一个环境变量? +4. 然后使用文件配置应用程序? + +### 12.2 答案 + +1. 如何给程序添加一个字符串类型的命令行选项? + 1. ``` var password stringflag.StringVar(&password,"password", "default","the db password")// define other flags// parse input flagsflag.Parse() ``` + 2. ``` password2 := flag.String(“password2”,“default”, “the db password”) ``` +2. 如何检查一个环境变量是否存在,并且在同一个调用中获取它的值? + 1. 使用 `os` 包中的 `LookupEnv` 函数 + 2. ``` port, ok := os.LookupEnv(“DB_PORT”) if !ok { log.Fatal(“impossible to start up, DB_PORT env var is mandatory”) } ``` +3. 如何设置一个环境变量? + 1. 使用 `os` 包中的 `SetEnv` 函数 + 2. ``` err := os.Setenv(“MYVAR3”, “test3”) if err != nil { panic(err) } ``` +4. 然后使用 JSON 文件配置应用程序? + 1. 使用所有配置变量创建一个特定类型的文件 + 2. 在引导程序里,打开 JSON 文件 + 3. 解析文件。 + +## 13 关键要点 + +- 关键 \ No newline at end of file diff --git a/chap-33-application-configuration/imgs/33-1-application.jpg b/chap-33-application-configuration/imgs/33-1-application.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9df4fdc825a04f96d0daf6e38499371fdabe0f14 GIT binary patch literal 30951 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 literal 0 HcmV?d00001 -- Gitee From e3fd00a37cfb7e01c32be324739559ef167bb6f8 Mon Sep 17 00:00:00 2001 From: "liyuan.go" Date: Sun, 13 Jun 2021 18:43:24 +0800 Subject: [PATCH 2/2] feature(chap-33) --- chap-33-application-configuration/33.md | 36 +++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/chap-33-application-configuration/33.md b/chap-33-application-configuration/33.md index c7c29d7..0082d70 100644 --- a/chap-33-application-configuration/33.md +++ b/chap-33-application-configuration/33.md @@ -127,7 +127,7 @@ Usage of ./myCompiledProgram: ## 7 环境变量 -一些云服务通过环境变量支持配置。环境变量使用 export 命令很容易配置。只需要在您的终端输入以下命令即可创建一个名为 MYVAR 的环境变量: +一些云服务通过环境变量支持配置。环境变量使用 export 命令很容易配置。只需要在您的终端输入以下命令即可创建一个名为 MYVAR 的环境变量1: ``` Bash $ export MYVAR=value @@ -293,7 +293,7 @@ fmt.Printf("%+v",myConf) ## 9 github.com/spf13/viper 模块 -`github.com/spf13/viper` 是非常受当前 Go 社区欢迎的配置模块,其拥有超过 14,000 星和超过 1.3k forks。 +`github.com/spf13/viper` 是非常受当前 Go 社区欢迎的配置模块,其拥有超过 14,000 星和超过 1.3k forks2。 这个包允许您通过文件、环境变量、命令行、buffers 等等简单定义一个配置。它亦支持一个有趣的特性:如果您的配置项在应用程序的生命周期中改变了,它将会重载。 @@ -374,7 +374,7 @@ fmt.Println(viper.GetInt("timeout")) 在公司内部,您可能不是处理应用程序部署的人,因此,您必须在您的估计中包括文档时间。更好的文档意味着减少了解决方案的上市时间。 -来着 Berkeley University[@rabkin2011static] 的 Ariel Rabkin 和 Randy Katz 的一项有趣的研究表面,甚至一些大型开源项目(他们研究了 Cassandra,Hadoop,HBase 等七个大型项目)的配置文档都是不准确的: +来自 Berkeley University[@rabkin2011static] 的 Ariel Rabkin 和 Randy Katz 的一项有趣的研究表明,甚至一些大型开源项目(他们研究了 Cassandra,Hadoop,HBase 等七个大型项目)的配置文档都是不准确的: - 其中提到了应用程序源代码中不存在的配置项。他们指出,有时这些选项只是简单地注释到应用程序源代码中。 - 应用程序源代码中存在一些甚至没有文档记录的选项。 @@ -410,7 +410,7 @@ if err != nil { 配置变量通常由这些组成:密码、访问令牌、加密密钥等等。泄露这些机密可能会造成损失。以上所有解决方案都不能完美地存储和保护生产机密。频繁变换这些机密信息也不是一个很好地解决方案。 -一些开源解决方案可以处理这个具体的问题。它们提供了一系列功能来保护、监视和审计您的机密信息的使用。在写作的时候,似乎由 Hashicorp 编辑的 Vault 是最流行的。而且它是用 Go 写的! +一些开源解决方案可以处理这个具体的问题。它们提供了一系列功能来保护、监视和审计您的机密信息的使用。在写作的时候,似乎由 Hashicorp 编辑的 Vault 是最流行的3。而且它是用 Go 写的! ## 12 自我测试 @@ -439,4 +439,30 @@ if err != nil { ## 13 关键要点 -- 关键 \ No newline at end of file +- 应用程序配置可以通过命令行选项配置。 +- `flag` 标准包提供了添加这些选项的方法。 +``` Go +var password string +flag.StringVar(&password,"password", "default","the db password")// define other flags// parse input flagsflag.Parse() +``` +- 配置项在程序启动的时候通过命令行给出:```$ myCompiledGoProgram -password insecuredPass``` +- 配置项通常在应用程序启动的时候加载(在 main 函数中) +- 应用程序配置项也能通过环境变量设置 +- 可以使用 `os` 包检索环境变量 + - ```dbHost := os.GetEnv(“DB_HOST”)``` + - ```dbHost, found := os.LookupEnv(“DB_HOST”)``` +- 也可以通过文件(JSON,YAML...)处理配置项。做法就是创建对应的结构体并解构文件内容 +- `github.com/spf13/viper` 是处理应用程序配置的常用参考模块 +- 配置项应该被开发者认真记录为文档。及时更新的文档也是相当有竞争力的 +- 使用专用的机密信息管理解决方案应处理应用程序机密信息。 + +--- +1. 在Unix系统中,环境变量是在其上定义的进程的本地变量↩︎ +2. 截止到2021年2月22日 +3. 数据源自:https://github.com/search?o=desc&q=secrets+management&s=stars&type=Repositories 截止到2021年2月22日 + +## 参考文献 +- [rabkin2011static] Rabkin, Ariel, and Randy Katz. 2011. “Static Extraction of Program Configuration Options.” In Proceedings of the 33rd International Conference on Software Engineering, 131–40. ACM. +- [rabkin2011static] Rabkin, Ariel, and Randy Katz. 2011. “Static Extraction of Program Configuration Options.” In Proceedings of the 33rd International Conference on Software Engineering, 131–40. ACM. +- [rabkin2011static] Rabkin, Ariel, and Randy Katz. 2011. “Static Extraction of Program Configuration Options.” In Proceedings of the 33rd International Conference on Software Engineering, 131–40. ACM. +- [nagaraja2004understanding] Nagaraja, Kiran, Fábio Oliveira, Ricardo Bianchini, Richard P Martin, and Thu D Nguyen. 2004. “Understanding and Dealing with Operator Mistakes in Internet Services.” In OSDI, 4:61–76. -- Gitee