小伙伴们, 她们中出了一个叛徒, 他是谁? 是谁? 是谁?
由一则口口相传的故事开始吧:
中午吃饭时间抽空小李跑到同座大楼的小张公司串门, 小李是一名 docker 顾问熟称砖家, 这间公司老板想挖小李, 他盯了前台不到三秒, 移开视线走到前同事小张的隔间边上打招呼, 看到小张眉头紧锁, 正在专著的看代码, 小李把头凑过去, 客套起来, 正在忙呢, 在学什么新技术? 小张开口吼到: 在翻小王的代码, 你认识的那个烟友! 小李一愣, 哦那一个小王啊~, 研究别人的技术, 挺钻的呀, 小李嘴上奉承道 (心里暗忖着小张工资以前没我高, 跳到这里估计也还在当下手吧), 小张叹了一口气, 嘣出三个字: 烦着呢, 小李映入眼前的是满屏密密麻麻的代码, 左边的行标显示 9999, 不愧是千足代码, 还有那么一小行格空的注释, 醒目的打着时间 ---5 年前 ---- 这是旧代码, 小李摇头晃脑, 有所觉悟
那就不打扰你了, 想起烟瘾又犯了, 正巧认识的小王也是同道中人, 下意识就问, 小王人呢? 小张硕小的脑袋一震, 青筋暴起, 指了指打印机房外一个空台子, 上面积了一层薄灰, 放着一些杂物, 像似好久没有人办公了, 与此同时, 一阵阴风从窗角吹过, 扬起的窗帘把小张的脸映射成斑斑点点, 小张把迟滞的目光瞟向了窗外的浮云, 仿佛小王就在云端, 小王离职了吗? 小李不甘心的问了一句, 突然一排没有窗户的老板格间悠悠的打开一扇门, 仿佛有人藏在门后很久了, 一个光头中年把头探了出来利索的喊了一句, 小张进来, 谈话! 小张战战兢兢的放下鼠标, 快速使了一个撤离眼色, 轻声说, 小王的老板老王现在变成了我的老板... 就是以前那一个咚咚咚画白板的那个... 小李装作若有所悟的样子恢恢手溜了
从此小李再也没有去小张的办公室, 至今小李还在打着寒颤
完
这个故事有什么寓意? 听过的人众口难调 (张冠李戴), 索性作为发散性话题, 放在本篇作引
标题 GDP 三个首字母的组合作为揭发 233 的 docker/machine 的后续, 为了符合标题的意义, 请 Follow me 一起探究其中的秘密
我们先从一段代码说起
- // b2d hosts need to wait for the daemon to be up
- // before continuing with provisioning
- if err = WaitForDocker(provisioner, engine.DefaultPort); err != nil {
- return err
- }
- if err = makeDockerOptionsDir(provisioner); err != nil {
- return err
- }
- provisioner.AuthOptions = setRemoteAuthOptions(provisioner)
- if err = ConfigureAuth(provisioner); err != nil {
- return err
- }
这是一段在 233 篇中重点划出揭露的片断
节选自 libmachine/provision/boot2docker.go
从 233 篇隐藏的逻辑可以判断, 此处, 导致了整个隐藏在 Docker Machine 中的 b2d 出现了 port 排异
函数 WaitForDocker 成为此段有争议的焦点
我们进入这段函数看看它到底是什么实现
- func checkDaemonUp(p Provisioner, dockerPort int) func() bool {
- reDaemonListening := fmt.Sprintf(":%d\\s+.*:.*", dockerPort)
- return func() bool {
- // HACK: Check netstat's output to see if anyone's listening on the Docker API port.
- netstatOut, err := p.SSHCommand("if ! type netstat 1>/dev/null; then ss -tln; else netstat -tln; fi")
- if err != nil {
- log.Warnf("Error running SSH command: %s", err)
- return false
- }
- return matchNetstatOut(reDaemonListening, netstatOut)
- }
- }
这段试图在 netstat 返回结果字符类型中执行匹配函数 matchNetstatOut
matchNetstatOut = regexp.MatchString(reDaemonListening, line)
匹配的主角很不幸在这里被强硬的设置为 engine.DefaultPort, 在 233 篇里曾试图枚举过 engine.DefaultPort 的一些用例, 不知道小伙伴们看出些什么端倪
在这里我们再次回到最先的那些函数片断, 注意到随后调用的 if err = ConfigureAuth(provisioner) 没有?
这是一段惊心动魄的代码, 路经在 libmachine/provision/utils.go
不妨我们一起看一下这个 ConfigureAuth 函数有哪些内涵???
- func (provisioner *Boot2DockerProvisioner) Service(name string, action serviceaction.ServiceAction) error {
- _, err := provisioner.SSHCommand(fmt.Sprintf("sudo /etc/init.d/%s %s", name, action.String()))
- return err
- }
- bits := 2048
- p.Service("docker", serviceaction.Stop);
- .
- log.Info("Copying certs to the local machine directory...");.
- .
- .
- dockerPort := engine.DefaultPort
- parts := strings.Split(u.Host, ":")
- if len(parts) == 2 {
- dPort, err := strconv.Atoi(parts[1])
- if err != nil {
- return err
- }
- dockerPort = dPort
- }
- .
- .
- .
- p.Service("docker", serviceaction.Start);
- .
- return WaitForDocker(p, dockerPort);
这是我抽象出来的慰慰代码, 在这段代码里, 吉祥寺 (似)dockerPort 被正确合理的设置成它应该有的值, 我把这段代码称呼为 2048 代码, 为了抵消你们不时涌现的 1024 的念头!
如果诸位可以认真地再多看几遍, 一定会产生出一群问号???
这里所有的 op 都以 SSH 的方式调用, 而 SSH 又隐藏了什么不为人知的小故事? 我们一起来猜测, 谁是哪一名画白板的人
- func (provisioner *Boot2DockerProvisioner) SSHCommand(args string) (string, error) {
- return drivers.RunSSHCommandFromDriver(provisioner.Driver, args)
- }
在 b2d 内部, SSH 的责任链落到 driver 头上, 对就是那一个 driver, 那一个, 那一个, 我在手动滑稽之 golang-vmware-driver 广告篇贴图之一, 不会错了
driver 为什么会对 SSH 轻车熟路?
- As:
- func GetSSHClientFromDriver(d Driver) (SSH.Client, error) {
- address, err := d.GetSSHHostname()
- if err != nil {
- return nil, err
- }
- port, err := d.GetSSHPort()
- if err != nil {
- return nil, err
- }
- var auth *SSH.Auth
- if d.GetSSHKeyPath() == "" {
- auth = &SSH.Auth{}
- } else {
- auth = &SSH.Auth{
- Keys: []string{d.GetSSHKeyPath()},
- }
- }
- client, err := SSH.NewClient(d.GetSSHUsername(), address, port, auth)
- return client, err
- }
- func RunSSHCommandFromDriver(d Driver, command string) (string, error) {
- client, err := GetSSHClientFromDriver(d)
- if err != nil {
- return "", err
- }
- log.Debugf("About to run SSH command:\n%s", command)
- output, err := client.Output(command)
- log.Debugf("SSH cmd err, output: %v: %s", err, output)
- if err != nil {
- return "", fmt.Errorf(`SSH command error:
- command : %s
- err : %v
- output : %s`, command, err, output)
- }
- return output, nil
- }
回到现象
- func WaitForSpecificOrError(f func() (bool, error), maxAttempts int, waitInterval time.Duration) error {
- for i := 0; i <maxAttempts; i++ {
- stop, err := f()
- if err != nil {
- return err
- }
- if stop {
- return nil
- }
- time.Sleep(waitInterval)
- }
- return fmt.Errorf("Maximum number of retries (%d) exceeded", maxAttempts)
- }
- func WaitForSpecific(f func() bool, maxAttempts int, waitInterval time.Duration) error {
- return WaitForSpecificOrError(func() (bool, error) {
- return f(), nil
- }, maxAttempts, waitInterval)
- }
如果 docker port 已经修改这里会抽风 10 次, 显示 SSH 调用 if ! type netstat 1>/dev/null; then ss -tln; else netstat -tln; fi 正确返回已经改变的 docker port 和 SSH port 侦听列表
然而无法 match engine.DefaultPort 带来的 err 是臆想不到的, err msg 更无法判断 b2d 的内部排异, 这迫使我试图寻找 AzureProvisioner, 这个不存在的名称
如果看到这里我再向你吐露 docker port 从 driver 中产生或许一点也不会吃惊, 最后的伪函数片段是
- // Driver defines how a host is created and controlled. Different types of
- // driver represent different ways hosts can be created (e.g. different
- // hypervisors, different cloud providers)
- type Driver interface {
- // Create a host using the driver's config
- Create() error
- // DriverName returns the name of the driver
- DriverName() string
- // GetCreateFlags returns the mcnflag.Flag slice representing the flags
- // that can be set, their descriptions and defaults.
- GetCreateFlags() []mcnflag.Flag
- // GetIP returns an IP or hostname that this host is available at
- // e.g. 1.2.3.4 or docker-host-d60b70a14d3a.cloudapp.NET
- GetIP() (string, error)
- // GetMachineName returns the name of the machine
- GetMachineName() string
- // GetSSHHostname returns hostname for use with SSH
- GetSSHHostname() (string, error)
- // GetSSHKeyPath returns key path for use with SSH
- GetSSHKeyPath() string
- // GetSSHPort returns port for use with SSH
- GetSSHPort() (int, error)
- // GetSSHUsername returns username for use with SSH
- GetSSHUsername() string
- // GetURL returns a Docker compatible host URL for connecting to this host
- // e.g. tcp://1.2.3.4:2376
- GetURL() (string, error)
- // GetState returns the state that the host is in (running, stopped, etc)
- GetState() (state.State, error)
- // Kill stops a host forcefully
- Kill() error
- // PreCreateCheck allows for pre-create operations to make sure a driver is ready for creation
- PreCreateCheck() error
- // Remove a host
- Remove() error
- // Restart a host. This may just call Stop(); Start() if the provider does not
- // have any special restart behaviour.
- Restart() error
- // SetConfigFromFlags configures the driver with the object that was returned
- // by RegisterCreateFlags
- SetConfigFromFlags(opts DriverOptions) error
- // Start a host
- Start() error
- // Stop a host gracefully
- Stop() error
- }
- u.Host from GetUrl() from Implementation of Driver
- That's ALL of it.
这些代码调用关系一一展示过之后
- // b2d hosts need to wait for the daemon to be up
- // before continuing with provisioning
这里, 仅有的注释让我更加确信, 以下一行是可以删除的代码, 我们都善于反向操作
然而我对改 Machine 始终抱 No 的态度, 或许将来可能会注册一个账户 submit patch, 但是让我犹豫的是, 我还没有写过任何 hello world go, 为什么比我更合适的人选没有呢???
这个疑问始终挥之不去, 到了放松环节, 请跟着我的节奏 Blame(扒) 一下精彩截图
图片被我约束了 html 尺寸, 请点击单独在 TAB 页观看图片链接
-->-->
a
简出:
对策篇:
上文提到的制作自己的 Machine, 可惜华而不为
提交 patch, 为社区贡献, 可惜遥遥无期
Register Injection by Init Driver AND YOU CAN HAVE 4069
作为一个万全的对策一定要有一些把握, forkersfolk 贡起宝典手册, 找到了入门
请看
- Init() functions can be used within a package block and regardless of how many times that package is imported
- The init() function will only be called once
- provisioners = make(map[string]*RegisteredProvisioner) <===hashmap
不仅如此, 在一个 go 中 init 可以反复反反复复存在, 按序调用
真是一大奇观, 请跟随我一起念作为结尾 Go
- Goosy
- Disk
- Docker
- Port
- Provisioner
来源: https://www.cnblogs.com/A-Z/p/docker_provision_inspection.html