
docker 通过重复使用同一标志(如 `--dns 127.0.0.1 --dns 8.8.8.8`)实现列表参数传递,这是 go 标准库 `flag` 包对实现了 `flag.value` 接口的自定义类型的通用支持方式,而非 docker 特有语法。
在 Docker CLI 中,像 --dns=[]、--dns-search=[] 这类带方括号 [] 的选项,明确表示其接受多个值。其底层实现依赖于 Go 语言标准库中的 flag 包——它并不将 --dns 视为“单值开关”,而是将其绑定到一个实现了 flag.Value 接口的自定义类型(例如 []string 的封装体)。该接口要求实现两个方法:
- String() string:返回当前值的字符串表示(通常用于帮助信息显示,如 "[]");
- Set(string) error:接收命令行中每次出现该 flag 时的参数值,并负责追加或更新内部存储。
因此,当你执行:
dockerd --dns 127.0.0.1 --dns 8.8.8.8 --dns 8.8.4.4
flag.Parse() 会依次调用三次 Set("127.0.0.1")、Set("8.8.8.8")、Set("8.8.4.4"),最终累积成一个包含三个 DNS 服务器地址的切片,进而注入所有容器的 /etc/resolv.conf。
✅ 正确用法示例(启动 dockerd 时配置 DNS):
sudo dockerd \ --dns 192.168.1.1 \ --dns 8.8.8.8 \ --dns-search example.com \ --dns-search internal.net
⚠️ 注意事项:
- 不可混用逗号分隔:--dns "127.0.0.1,8.8.8.8" 是无效的——Go flag 包不会自动解析逗号分隔字符串,这会导致只设置第一个值或报错;
- 顺序即优先级:Docker 按传入顺序写入 /etc/resolv.conf,系统默认优先使用首个 nameserver;
- 覆盖关系:容器内若手动修改 /etc/resolv.conf 或使用 --dns 覆盖运行时参数,将优先于 daemon 全局配置;
- 非 Docker 独有:该模式广泛见于其他 Go 编写的 CLI 工具(如 kubectl、terraform),是 Go 生态中处理多值 flag 的惯用实践。
? 扩展理解:你也可以自己实现类似逻辑。例如,定义一个整数累加器:
type intList []int
func (l *intList) String() string { return fmt.Sprint([]int(*l)) }
func (l *intList) Set(s string) error {
v, err := strconv.Atoi(s)
if err != nil { return fmt.Errorf("invalid int: %s", s) }
*l = append(*l, v)
return nil
}
func main() {
var nums intList
flag.Var(&nums, "num", "Add integer to sum (can be repeated)")
flag.Parse()
sum := 0
for _, n := range nums { sum += n }
fmt.Printf("Sum: %d\n", sum) // $ prog -num 1 -num 2 -num 3 → Sum: 6
}总结而言,--flag value --flag value 是 Go flag 包对列表参数的标准、推荐且最清晰的表达方式,既符合 POSIX 兼容性,又具备良好的可读性与可调试性。在编写或使用任何基于 Go 的命令行工具时,应优先采用此模式,而非自行设计分隔符解析逻辑。










