Go Goosy Disk Docker Port Provisioners (GDP)


小伙伴們,她們中出了一個叛徒,他是誰?是誰?是誰?


由一則口口相傳的故事開始吧:

中午吃飯時間抽空小李跑到同座大樓的小張公司串門,小李是一名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

 


簡出:

對策篇:

  1. 上文提到的制作自己的Machine,可惜華而不為
  2. 提交patch,為社區貢獻,可惜遙遙無期
  3. 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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM