如何成为一名优秀的Docker代码贡献者

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

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

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

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

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

步骤1:从『func main()』开始

正如一句古话所述,从你知道的开始。如果你和大部分Docker用户一样,你可能主要使用Docker CLI。因此,让我们从程序的入口开始:‘main’函数
此处为本文的提示,我们将会使用一个名为Sourcegraph的站点,Docker团队就使用它完成在线检索和代码浏览,和你使用智能IDE所做的差不多。建议在阅读本文时,打开Sourcegraph放在一边,以更好地跟上文章的进度。

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

我们正在寻找对应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:选择一个问题并开始coding

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

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

有效地贡献

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

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

原文链接:5 steps to becoming a quality Docker contributor(翻译:孙科 校对:李颖杰)

原文发布时间为:2015-06-19

本文作者:codesun 

本文来自合作伙伴DockerOne,了解相关信息可以关注DockerOne。

原文标题:如何成为一名优秀的Docker代码贡献者

时间: 2024-08-02 20:25:45

如何成为一名优秀的Docker代码贡献者的相关文章

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

[编者的话]开源渐成主流,越来越多的开发者想参与开源社区.而时下最火热的Docker也许就是开发者入手开源项目的最好选择,它不仅是目前最流行的开源项目之一,而且在提交Issue方面的文档和流程都是目前我见过的开源项目里最好的.本文主要介绍了如何入手开源项目,一些小经验和小工具,一起来学习. 成为一个流行开源项目(如Docker)的贡献者有如下好处: 你可以参与改进很多人都在使用的项目,以此来获得认同感: 你可以与开源社区中的那些聪明绝顶的人通力合作: 你可以通过参与理解和改进这个项目来使自己成为

五步助你成为优秀的Docker代码贡献者

开源渐成主流,越来越多的开发者想参与开源社区.而时下最火热的Docker也许就是开发者入手开源项目的最好选择,它不仅是目前最流行的开源项目之一,而且在提交Issue方面的文档和流程都是目前我见过的开源项目里最好的.本文主要介绍了如何入手开源项目,一些小经验和小工具,一起来学习. 成为一个流行开源项目(如Docker)的贡献者有如下好处: 你可以参与改进很多人都在使用的项目,以此来获得认同感; 你可以与开源社区中的那些聪明绝顶的人通力合作; 你可以通过参与理解和改进这个项目来使自己成为一名更加出色

如何成为一名优秀的全栈工程师?

写在最前 我的前一篇文章<给职场新人的 10 点建议>发表后,得到了很多网友,特别是年轻程序员朋 友们的喜爱,这令我颇感意外,但又很受鼓舞.同时,我也收到了一些私信,大多来自那些毕业不久,刚步入工作岗位的职场新人.询问的内容也大多是如何选择职业方向,如何成为一名优秀的软件工程师,以及怎样快速提高自身的技能等.我在一一回复的同时,不禁想结合自身的经历,谈谈如何才能成为一名优秀的全栈工程 师. 什么是全栈工程师 全栈工程师一词,最早出现于Facebook工程师Calos Bueno的一篇文章 - 

程序开发-如何成长为一名优秀的数据挖掘工程师?

问题描述 如何成长为一名优秀的数据挖掘工程师? 想咨询在阿里.百度.腾讯等一些企业工作的前辈们,如何成长为一名数据挖掘工程师?我是研一的新生,想在研究生期间努力学习,将来能找一个好工作.但是学生不知道要往哪方面努力.学生基础比较差,现在在练习编程. 1.前辈们能不能给后辈提点建议.怎么去准备,去学习一些什么知识?最好是提供基本好的教材. 2. 数据挖掘工程师需要哪些基本的能力,会哪些技术. 3.求抱大腿,希望有前辈愿意带带我,学生深知喝水不忘挖井人这个道理,本人懂得感恩,是个人品不错的人. 解决

如何成为一名优秀的web前端工程师

我所遇到的前端程序员分两种: 第一种一直在问:如何学习前端? 第二种总说:前端很简单,就那么一点东西. 我从没有听到有人问:如何做一名优秀.甚至卓越的WEB前端工程师. 何为:前端工程师? 前端工程师,也叫Web前端开发工程师.他是随着web发展,细分出来的行业. Web前端开发技术主要包括三个要素:HTML.CSS和JavaScript! 它要求前端开发工程师不仅要掌握基本的Web前端开发技术,网站性能优化.SEO和服务器端的基础知识,而且要学会运用各种工具进行辅助开发以及理论层面的知识,包括

10个习惯助你成为一名优秀的程序员

当谈到编程时,很多人应该都有听过10x程序员的说法. 据说一个10x程序员的效率大约是其他程序员的10倍. 这是一个有争议的话题,无论你选择相信10x程序员,还是认为这十分可笑,下面的10个习惯可以在一定程度上助你成为一名势不可挡的优秀程序员. 1.积极使用搜索工具 作为开发人员,你需要了解如何利用搜索解惑,查看其他人对你正在研究的主题所讨论的话题和内容,并将学到的知识应用于手头的项目. 当面临一个问题,优秀的程序员应该知道如何花很少时间去查验和解决手头的问题. 2.保持初学者心态 技术发展如此

如何成为一名优秀物联网开发人员

物联网(IoT)行业正蓬勃发展.根据最近的Gartner报告,在2017年,全球使用的互联设备数量将达到84亿,超过全球人口总量. Gartner预测,到2020年,将会有超过208亿件物联网设备. 随着家庭互联.汽车互联和办公室互联变得越来越主流化,需要更多的开发人员来确保互联设备正常.安全地运行. IBM物联网开发人员生态系统主管Greg Gorman说:"'物联网开发人员'一词的含义很广泛,包括安全.网络.系统工程.云编程和硬件设备编程.开发人员应该在团队中灵活地扮演许多不同的角色.&qu

[转载]一名优秀的Flex开发者需要知道的10样东西

原文:http://blog.flexdevelopers.com/2010/04/10-things-good-flex-developer-should.html 翻译:http://bbs.9ria.com/viewthread.php?tid=54144&from=recommend_f   要想成为一名优秀的Flex编程人员,单单知道怎么样去使用Flex内建的容器和组件是不够的,而且是远远不够. 下面是我对这个问题的一些看法......并且给出了一些资源和关键字,你可以通过Google

周鸿祎:如何成为一名优秀的产品经理?

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 如今,用户体验这个词已经渗透到越来越多的行业,贯穿于整个企业的研发.推广和市场运作.例如,买 iPhone,从打开包装盒的那一刹那,到海底捞,从热情洋溢的引座员接待你的那一刻,体验之旅开始展开.可以毫不夸张地说,用户体验是促进销售的生产力,也是确立差异化的竞争力,是推动行业进步的力量. 尊重用户体验的企业,每个人都是产品经理,特别是客服人员.