关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » Docker »5 步助你成为一名优秀的 Docker 代码贡献者

    5 步助你成为一名优秀的 Docker 代码贡献者

    2015-06-25 00:00:00 出处:linux.cn
    分享

    【编者的话】开源渐成主流,越来越多的开发者想参与开源社区。而时下最火热的Docker也许就是开发者入手开源项目的最好选择,它不仅是目前最流行的开源项目之一,而且在提交Issue方面的文档和流程都是目前我见过的开源项目里最好的。本文主要介绍了如何入手开源项目,一些小经验和小工具,一起来学习。

    5 步助你成为一名优秀的 Docker 代码贡献者

    成为一个流行开源项目(如Docker)的贡献者有如下好处:

    你可以参与改进很多人都在使用的项目,以此来获得认同感; 你可以与开源社区中的那些聪明绝顶的人通力合作; 你可以通过参与理解和改进这个项目来使自己成为一名更加出色的程序员。

    但是,从一个新的基准代码(codebase)入手绝对是一件恐怖的事情。目前,Docker已经有相当多的代码了,哪怕是修复一个小问题,都需要阅读大量的代码,并理解这些部分是如何组合在一起的。

    不过,它们也并不如你想象的那么困难。你可以根据Docker的贡献者指南来完成环境的配置。然后按照如下5个简单的步骤,配合相关的代码片段来深入代码基。你所历练的这些技能,都将会在你的编程生涯的每个新项目中派上用场。那么还等什么,我们这就开始。

    步骤1:从’func main()’开始

    正如一句古话所述,从你知道的开始。假如你和大部分Docker用户一样,你可能主要使用Docker CLI。因此,让我们从程序的入口开始:‘main’函数。

    此处为本文的提示,我们将会使用一个名为Sourcegraph的站点,Docker团队就使用它完成在线检索和代码浏览,和你使用智能IDE所做的差不多。建议在阅读本文时,打开Sourcegraph放在一边,以更好地跟上文章的进度。

    在Sourcegraph站点,让我们搜索Docker仓库中的‘func main()’。

    5 步助你成为一名优秀的 Docker 代码贡献者

    我们正在寻找对应‘docker’命令的‘main’函数,它是‘docker/docker/docker.go’中的一个文件。点击搜索结果,我们会跳到其定义(如下所示)。花一点时间浏览一下这个函数:

    func main() {
    	if reexec.Init() {
    		return
    	}
    
    	// Set terminal emulation based on platform as required.
    	stdin, stdout, stderr := term.StdStreams()
    
    	initLogging(stderr)
    
    	flag.Parse()
    	// FIXME: validate daemon flags here
    
    	if *flVersion {
    		showVersion()
    		return
    	}
    
    	if *flLogLevel != "" {
    		lvl, err := logrus.ParseLevel(*flLogLevel)
    		if err != nil {
    			logrus.Fatalf("Unable to parse logging level: %s", *flLogLevel)
    		}
    		setLogLevel(lvl)
    	} else {
    		setLogLevel(logrus.InfoLevel)
    	}
    
    	// -D, --debug, -l/--log-level=debug processing
    	// When/if -D is removed this block can be deleted
    	if *flDebug {
    		os.Setenv("DEBUG", "1")
    		setLogLevel(logrus.DebugLevel)
    	}
    
    	if len(flHosts) == 0 {
    		defaultHost := os.Getenv("DOCKER_HOST")
    		if defaultHost == "" || *flDaemon {
    			// If we do not have a host, default to unix socket
    			defaultHost = fmt.Sprintf("unix://%s", api.DEFAULTUNIXSOCKET)
    		}
    		defaultHost, err := api.ValidateHost(defaultHost)
    		if err != nil {
    			logrus.Fatal(err)
    		}
    		flHosts = append(flHosts, defaultHost)
    	}
    
    	setDefaultConfFlag(flTrustKey, defaultTrustKeyFile)
    
    	if *flDaemon {
    		if *flHelp {
    			flag.Usage()
    			return
    		}
    		mainDaemon()
    		return
    	}
    
    	if len(flHosts) > 1 {
    		logrus.Fatal("Please specify only one -H")
    	}
    	protoAddrParts := strings.SplitN(flHosts[0], "://", 2)
    
    	var (
    		cli       *client.DockerCli
    		tlsConfig tls.Config
    	)
    	tlsConfig.InsecureSkipVerify = true
    
    	// Regardless of whether the user sets it to true or false, if they
    	// specify --tlsverify at all then we need to turn on tls
    	if flag.IsSet("-tlsverify") {
    		*flTls = true
    	}
    
    	// If we should verify the server, we need to load a trusted ca
    	if *flTlsVerify {
    		certPool := x509.NewCertPool()
    		file, err := ioutil.ReadFile(*flCa)
    		if err != nil {
    			logrus.Fatalf("Couldn't read ca cert %s: %s", *flCa, err)
    		}
    		certPool.AppendCertsFromPEM(file)
    		tlsConfig.RootCAs = certPool
    		tlsConfig.InsecureSkipVerify = false
    	}
    
    	// If tls is enabled, try to load and send client certificates
    	if *flTls || *flTlsVerify {
    		_, errCert := os.Stat(*flCert)
    		_, errKey := os.Stat(*flKey)
    		if errCert == nil && errKey == nil {
    			*flTls = true
    			cert, err := tls.LoadX509KeyPair(*flCert, *flKey)
    			if err != nil {
    				logrus.Fatalf("Couldn't load X509 key pair: %q. Make sure the key is encrypted", err)
    			}
    			tlsConfig.Certificates = []tls.Certificate{cert}
    		}
    		// Avoid fallback to SSL protocols < TLS1.0
    		tlsConfig.MinVersion = tls.VersionTLS10
    	}
    
    	if *flTls || *flTlsVerify {
    		cli = client.NewDockerCli(stdin, stdout, stderr, *flTrustKey, protoAddrParts[0], protoAddrParts[1], &tlsConfig)
    	} else {
    		cli = client.NewDockerCli(stdin, stdout, stderr, *flTrustKey, protoAddrParts[0], protoAddrParts[1], nil)
    	}
    
    	if err := cli.Cmd(flag.Args()...); err != nil {
    		if sterr, ok := err.(*utils.StatusError); ok {
    			if sterr.Status != "" {
    				logrus.Println(sterr.Status)
    			}
    			os.Exit(sterr.StatusCode)
    		}
    		logrus.Fatal(err)
    	}
    }

    在‘main’函数的顶部,我们看了许多与日志配置,命令标志读取以及默认初始化相关的代码。在底部,我们发现了对『client.NewDockerCli』的调用,它似乎是用来负责创建结构体的,而这个结构体的函数则会完成所有的实际工作。让我们来搜索『NewDockerCli』。

    步骤2:找到核心部分

    在很多的应用和程序库中,都有1到2个关键接口,它表述了核心功能或者本质。让我们尝试到达这个关键部分。

    点击‘NewDockerCli’的搜索结果,我们会到达函数的定义。由于我们感兴趣的只是这个函数所返回的结构体——「DockerCli」,因此让我们点击返回类型来跳转到其定义。

    func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, addr string, tlsConfig *tls.Config) *DockerCli {
    	var (
    		inFd          uintptr
    		outFd         uintptr
    		isTerminalIn  = false
    		isTerminalOut = false
    		scheme        = "http"
    	)
    
    	if tlsConfig != nil {
    		scheme = "https"
    	}
    	if in != nil {
    		inFd, isTerminalIn = term.GetFdInfo(in)
    	}
    
    	if out != nil {
    		outFd, isTerminalOut = term.GetFdInfo(out)
    	}
    
    	if err == nil {
    		err = out
    	}
    
    	// The transport is created here for reuse during the client session
    	tr := &http.Transport{
    		TLSClientConfig: tlsConfig,
    	}
    
    	// Why 32  See issue 8035
    	timeout := 32 * time.Second
    	if proto == "unix" {
    		// no need in compressing for local communications
    		tr.DisableCompression = true
    		tr.Dial = func(_, _ string) (net.Conn, error) {
    			return net.DialTimeout(proto, addr, timeout)
    		}
    	} else {
    		tr.Proxy = http.ProxyFromEnvironment
    		tr.Dial = (&net.Dialer{Timeout: timeout}).Dial
    	}
    
    	return &DockerCli{
    		proto:         proto,
    		addr:          addr,
    		in:            in,
    		out:           out,
    		err:           err,
    		keyFile:       keyFile,
    		inFd:          inFd,
    		outFd:         outFd,
    		isTerminalIn:  isTerminalIn,
    		isTerminalOut: isTerminalOut,
    		tlsConfig:     tlsConfig,
    		scheme:        scheme,
    		transport:     tr,
    	}
    }

    点击『DockerCli』将我们带到了它的定义。向下滚动这个文件,我们可以看到它的方法, ‘getMethod’,‘Cmd’,‘Subcmd’和‘LoadConfigFile’。其中,‘Cmd’值得留意。它是唯一一个包含docstring的方法,而docstring则表明它是执行每条Docker命令的核心方法。

    步骤3:更进一步

    既然我们已经找到了‘DockerCli’,这个Docker客户端的核心‘控制器’,接下来让我们继续深入,了解一条具体的Docker命令是如何工作的。让我们放大‘docker build’部分的代码。

    type DockerCli struct {
    	proto      string
    	addr       string
    	configFile *registry.ConfigFile
    	in         io.ReadCloser
    	out        io.Writer
    	err        io.Writer
    	keyFile    string
    	tlsConfig  *tls.Config
    	scheme     string
    	// inFd holds file descriptor of the client's STDIN, if it's a valid file
    	inFd uintptr
    	// outFd holds file descriptor of the client's STDOUT, if it's a valid file
    	outFd uintptr
    	// isTerminalIn describes if client's STDIN is a TTY
    	isTerminalIn bool
    	// isTerminalOut describes if client's STDOUT is a TTY
    	isTerminalOut bool
    	transport     *http.Transport
    }

    阅读‘DockerCli.Cmd’的实现可以发现,它调用了‘DockerCli.getMethod’方法来执行每条Docker命令所对应的函数。

    func (cli *DockerCli) Cmd(args ...string) error {
    	if len(args) > 1 {
    		method, exists := cli.getMethod(args[:2]...)
    		if exists {
    			return method(args[2:]...)
    		}
    	}
    	if len(args) > 0 {
    		method, exists := cli.getMethod(args[0])
    		if !exists {
    			fmt.Fprintf(cli.err, "docker: '%s' is not a docker command. See 'docker --help'./n", args[0])
    			os.Exit(1)
    		}
    		return method(args[1:]...)
    	}
    	return cli.CmdHelp()
    }

    在‘DockerCli.getMethod’中,我们可以看到它是通过对一个函数的动态调用实现的,其中这个函数名的形式为在Docker命令前预置“Cmd”字符串。那么在‘docker build’这个情况下,我们寻找的是‘DockerCli.CmdBuild’。但在这个文件中并没有对应的方法,因此让我们需要搜索‘CmdBuild’。

    func (cli *DockerCli) getMethod(args ...string) (func(...string) error, bool) {
    	camelArgs := make([]string, len(args))
    	for i, s := range args {
    		if len(s) == 0 {
    			return nil, false
    		}
    		camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
    	}
    	methodName := "Cmd" + strings.Join(camelArgs, "")
    	method := reflect.ValueOf(cli).MethodByName(methodName)
    	if !method.IsValid() {
    		return nil, false
    	}
    	return method.Interface().(func(...string) error), true
    }

    搜索结果显示‘DockerCli’中确实有一个‘CmdBuild’方法,因此跳到它的定义部分。由于‘DockerCli.CmdBuild’的方法体过长,因此就不在本文中嵌入了,但是这里有它的链接。

    这里有很多内容。在方法的顶部,我们可以看到代码会为Dockerfile和配置处理各种输入方法。通常,在阅读一个很长的方法时,倒过来读是一种很不错的策略。从底部开始,观察函数在最后做了什么。很多情况中,它们都是函数的本质,而之前的内容无非只是用来补全核心行为的。

    在‘CmdBuild’的底部,我们可以看到通过‘cli.stream’构造的‘POST’请求。通过一些额外定义的跳转,我们到达了‘DockerCli.clientRequest’,它构造一个HTTP请求,这个请求包含你通过‘docker build’传递给Docker的信息。因此在这里,‘docker build所做的就是发出一个设想的’POST‘请求给Docker守护进程。假如你愿意,你也可以使用’curl‘来完成这个行为。

    至此,我们已经彻底了解了一个单独的Docker客户端命令,或许你仍希望更进一步,找到守护进程接受请求的部分,并一路跟踪到它和LXC以及内核交互的部分。这当然是一条合理的路径,但是我们将其作为练习留给各位读者。接下来,让我们对客户端的关键组件有一个更加全面的认识。

    步骤4:查看使用示例

    更好地理解一段代码的方式是查看展示代码如何被应用的使用示例。让我们回到‘DockerCli.clientRequest’方法。在右手边的Sourcegraph面板中,我们可以浏览这个方法的使用例子。结果显示,这个方法在多处被使用,因为大部分Docker客户端命令都会产生传到守护进程的HTTP请求。

    5 步助你成为一名优秀的 Docker 代码贡献者

    为了完全理解一个代码片段,你需要同时知晓它是如何工作的以及是如何来使用的。通过阅读代码的定义部分让我们理解前者,而查看使用示例则是涵盖了后者。

    请在更多的函数和方法上尝试,理解它们的内部联系。假如这有帮助,那么请就应用的不同模块如何交互,画一张图。

    步骤5:选择一个问题并开始coding

    既然你已经对Docker的代码基有了一个大概的认识,那么可以查阅一下issue跟踪系统,看看哪些问题亟待解决,并在遇到你自己无法回答的问题时,向Docker社区的成员申援。由于你已经花了时间来摸索并理解代码,那么你应该已经具备条件来提出“聪明”的问题,并知道问题大概出在哪里。

    假如你觉得有必要,可以一路做好笔记,记录你的经历,并像本文一样作为博客发布。Docker团队会很乐意看到,你研究他们代码的经历。

    有效地贡献

    对一个巨大且陌生的基准代码的恐惧,俨然已经成为了一个阻止人们参与到项目中的误解。我们经常假设,对于程序员而言,工作的难点在于写代码,然而阅读并理解他人的代码却往往是最关键的一步。认识到这一切,并坚定地迎接任务,辅以优秀的工具,会帮助你克服心理防线,以更好地投入到代码中。

    那么,开始动手吧,检查一下Docker今天的代码。一个充满活力的开源社区和基准代码正等着你!

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    帝国CMS7.5编辑器上传图片取消宽高的三种方法

    帝国cms如何自动生成缩略图的实现方法

    Windows 12即将到来,将彻底改变人机交互

    帝国CMS 7.5忘记登陆账号密码怎么办?可以phpmyadmin中重置管理员密码

    帝国CMS 7.5 后台编辑器换行,修改回车键br换行为p标签

    Windows 11 版本与 Windows 10比较,新功能一览

    Windows 11激活产品密钥收集及专业版激活方法

    如何从 Windows 11 中完全删除/卸载 OneNote?无解!

    抖音安全与信任开放日:揭秘推荐算法,告别单一标签依赖

    ultraedit编辑器打开文件时,总是提示是否转换为DOS格式,如何关闭?

    IT头条

    华为Pura80系列新机预热,余承东力赞其复杂光线下的视频拍摄实力

    01:28

    阿里千问3开源首战告捷:全球下载破千万,国产AI模型崛起新高度!

    01:22

    DeepSeek R1小版本试升级:网友实测编程能力已达到国际一线水平

    23:15

    NVIDIA 与 Dell 合作,大规模交付 Blackwell AI 系统

    20:52

    Cerebras 以最快的 Llama 4 Maverick 性能引领 LLM 推理竞赛

    20:51

    技术热点

    PHP中的随机性——你觉得自己幸运吗?

    搞定Ubuntu Linux下WPA无线上网

    Java使用内存映射实现大文件的上传

    MySQL安全性指南

    MySQL两项性能的基本测试浅谈

    教您使用UniqueIdentifier选取SQL Server主键

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。