Appearance
包管理与标准库
Go 项目依赖由 Go Modules 管理,核心文件是 go.mod 和 go.sum。标准库覆盖了文件、HTTP、JSON、时间、日志、命令行参数、进程调用和上下文控制,很多运维工具不需要引入太多第三方包。
一、go.mod 和 go.sum
创建模块:
bash
mkdir ops-checker
cd ops-checker
go mod init example.com/ops-checker生成的 go.mod:
go
module example.com/ops-checker
go 1.22module 是模块路径,通常写成代码仓库路径。go 行表示这个模块声明的 Go 语言版本。go.sum 记录依赖校验信息,依赖下载后自动生成。
添加依赖通常不手工编辑 go.mod,而是通过 go get 或代码 import 后执行 go mod tidy。
bash
go get github.com/spf13/cobra
go mod tidy常用命令:
| 命令 | 作用 |
|---|---|
go mod init | 初始化模块 |
go get | 添加或升级依赖 |
go mod tidy | 清理未使用依赖,补齐缺失依赖 |
go mod download | 下载依赖到本地缓存 |
go list -m all | 查看依赖列表 |
go mod vendor | 把依赖复制到 vendor/ |
go mod tidy 会根据源码里的 import 自动调整依赖。提交代码前跑一次,能减少无用依赖和缺失依赖。
二、模块代理和私有仓库
国内网络常用代理:
bash
go env -w GOPROXY=https://goproxy.cn,direct
go env GOPROXY私有仓库:
bash
go env -w GOPRIVATE=git.example.com/*
go env GOPRIVATEGOPRIVATE 告诉 Go:匹配这些路径的模块属于私有模块,不走公共代理和公共校验数据库。公司内网 GitLab、Gitea、GitHub Enterprise 都会用到这个配置。
三、项目结构
简单命令行工具:
text
ops-checker/
├── go.mod
├── go.sum
├── main.go
├── internal/
│ └── checker/
│ └── http.go
└── README.md目录约定:
| 目录 | 用途 |
|---|---|
main.go | 程序入口 |
internal/ | 只允许当前模块内部引用的包 |
cmd/ | 多命令入口,大项目常用 |
pkg/ | 对外复用包,普通小工具不一定需要 |
小工具不需要一开始就拆很复杂。单个 main.go 能写清楚时就先保持简单;逻辑变多后再拆到 internal/。
四、defer
defer 用来延迟执行,常用于关闭文件、关闭连接、释放锁。
go
file, err := os.Open("/etc/hosts")
if err != nil {
return err
}
defer file.Close() // 函数返回前关闭文件defer 的执行顺序是后进先出。多个资源依次打开时,关闭顺序会反过来。
五、context
context.Context 用于传递取消信号、超时和请求级信息。HTTP 请求、数据库请求、K8s Client 调用都会用到它。
go
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("work done")
case <-ctx.Done():
fmt.Println("timeout:", ctx.Err())
}
}带超时的上下文能防止网络请求无限等待。client-go、HTTP Client、数据库 Client 里都要重视超时。
六、JSON
解析 JSON:
go
package main
import (
"encoding/json"
"fmt"
)
type Host struct {
Name string `json:"name"`
IP string `json:"ip"`
}
func main() {
raw := []byte(`{"name":"web01","ip":"192.168.10.11"}`)
var host Host
if err := json.Unmarshal(raw, &host); err != nil {
fmt.Println("parse json failed:", err)
return
}
fmt.Println(host.Name, host.IP)
}输出 JSON:
go
data, err := json.MarshalIndent(host, "", " ")
if err != nil {
return err
}
fmt.Println(string(data))字段要能被 JSON 包访问,首字母需要大写。tag 控制输出字段名。
七、HTTP 标准库
Go 标准库自带 HTTP Client。
go
package main
import (
"context"
"fmt"
"net/http"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://example.com/health", nil)
if err != nil {
fmt.Println("create request failed:", err)
return
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("request failed:", err)
return
}
defer resp.Body.Close()
fmt.Println(resp.StatusCode)
}HTTP 请求要带超时。运维巡检工具如果没有超时,一批目标里一两个地址卡住,整个任务会一直等。
八、命令行参数
标准库 flag 可以处理简单参数。
go
package main
import (
"flag"
"fmt"
)
func main() {
host := flag.String("host", "127.0.0.1", "target host")
port := flag.Int("port", 80, "target port")
flag.Parse()
fmt.Printf("host=%s port=%d\n", *host, *port)
}运行:
bash
go run . -host 192.168.10.11 -port 8080参数很多、需要子命令时,可以考虑 cobra 这类第三方库。小工具先用标准库 flag 足够。
九、日志
标准库 log/slog 支持结构化日志。
go
package main
import (
"log/slog"
"os"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info(
"check result",
"host", "web01",
"service", "nginx",
"status", "ok",
)
}结构化日志比纯字符串更适合被日志平台检索。字段名稳定,后面按 host、service、status 查起来更方便。
十、调用系统命令
go
package main
import (
"bytes"
"context"
"fmt"
"os/exec"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "systemctl", "is-active", "nginx")
var stdout bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
fmt.Println("command failed:", err, stderr.String())
return
}
fmt.Println("state:", stdout.String())
}调用命令时优先用参数列表,不拼一整段 shell 字符串。外部输入混进 shell 字符串时,容易引出命令注入问题。