2017年11月

2017 年 11 月 18 日至 19 日,2017 中国开源年会在上海交大召开,来自集慧智佳的高级咨询师薛亮在开源治理分论坛上发表了题为《ABC 时代 GPL 许可证传染性问题探讨》的演讲,现将演讲的内容进行整理和补充,以飨读者。

我们目前所处的时代被称为“ABC(AI、Big Data、Cloud)时代”,也是大量采用开源软件的时代。在这个过程中,不可避免地会遇到开源软件合规的问题,而其中最让人感到困惑的,可以说就是 GPL 许可证传染性问题。那么 GPL 软件是不是真的像传说中的避之唯恐不及,其传染性风险令人谈之色变呢?本次演讲和大家简要探讨一下 GPL 传染性问题。

演讲内容主要包括四个部分,第一部分为“从合规到牟利”,简要介绍目前开源软件合规环境的变化情况。第二部分为“ GPL 传染性判断”,介绍从实务角度考虑,GPL 传染性判断的流程、方法和原则。第三部分为“MongoDB 案例”,介绍了采用 AGPL 许可证的 MongoDB 案例。第四部分为“开源智慧专栏”,简单介绍集慧智佳与 Linux 中国合作开办的“开源智慧专栏”。

第一部分题目为“从合规到牟利”,介绍了近些年开源软件合规和诉讼态势发生的变化,已经从单纯的“寻求合规”转变为“追求牟利”,而这种变化使得开源软件用户的合规风险变得越来越严重。

以最为严格的 GPL 许可证来说,自由软件基金会和自由软件管理机构是全球推行 GPL 许可的主要机构,其追求的主要目标之一是实现 GPL 的合规,对于那些在无意中违反 GPL 许可证的行为,本着“惩前毖后,治病救人”的原则,大多数还是以教育和帮助为主,并不会刻意追求罚款、赔偿等。

但是,随着开源软件合规和诉讼生态的发展,涌现了更多类型的新玩家。根据国外律师的观察,除了诸如 SFLC、SFC 等传统的合规机构之外,近年来出现了比较激进的例如 McHardy 这样的 版权流氓 Copyright Troll ,或者说是 GPL 牟利者,其主要目标已经从合规转变为牟利,要求对违规行为颁发禁令或进行赔偿。

根据“开源智慧专栏”发表的翻译文章《如何应对开源软件的版权牟利者? 开源律师说这样做!》,GPL 牟利者 McHardy 已经骚扰了 50 多个目标,据说有些国内企业也在其中。

面对越来越复杂的开源软件合规态势,作为开源软件用户来说,对于许可证合规问题,需要引起重视,而 GPL 传染性显然是其中最让人头疼的问题。

第二部分我们具体探讨一下“GPL 传染性判断”,主要是根据我们的研究和实务经验,对于如何评估和判断GPL软件的传染性进行梳理和总结。

对于软件企业的技术人员来说,开源代码是否好用,是他们选用开源代码的重要标准之一,而不会过多考虑许可证问题。但对于企业的合规和法律部门来说,自己企业的技术人员使用了哪些开源软件,这些开源软件采用哪些许可证,则是需要进行排查和梳理,做到心中有数。而对于采用 GPL 许可证的软件,为了避免传染性,是不是必须简单地“一刀切”,绝对禁止使用呢?

我们认为,根据 GPL 软件类型和使用场景的不同,其传染性风险也存在不同。其中一个关键的分界点,在于自用与分发。

自用的范畴比较广泛,在公司个人、部门使用,甚至在公司内部分发,都可以自由使用。

对于编译器、解释器等工具类软件,其主要作用是对代码进行加工,可以归为自用的范畴,但也要区分 GPL 工具类软件是否将自身代码混进其所加工的代码。例如 GCC 是 GPL 编译器,但使用 GCC 不会感染被编译的源文件。

对于采用 GPL、LGPL 许可证的软件,如果放在服务器/云上以 SaaS 方式对外提供服务,也可以算作自用的范畴。但是,采用 AGPL 许可证的软件除外,AGPL 专门针对 SaaS 方式进行约束。

我们接着再来看分发,对于一些必须分发出去的 GPL 软件,其类型也有多种。对于一些相对独立的软件,需要注意与自有代码是各自独立还是复杂的糅合,是否构成了结合作品。对于诸如 MQTT 等协议实现类的软件,其实现方式有多种,可以选择采用宽松许可证的开源软件。许多开源软件也采用双重授权,如果担心开源版本的风险问题,可以选择花钱购买其商业授权版本。

对于自有代码与 GPL 软件的链接/混合,也分几种情况。例如对于自有模块 A 和 GPL 模块 B,需要根据两者之间的通信亲密程度以及传输数据的复杂程度,判断两者是否构成了结合作品。对于 GPL 插件,需要分析自有代码主程序对其调用的方式,判断是否造成传染。

对于自有代码与 GPL 库的链接,根据 GPL 许可证,无论是采用静态链接或动态链接方式,都会造成自有代码被传染,必须进行公开。而之后发表的 LGPL 许可证,则对 GPL 库的链接稍微放松了限制。由于 LGPL 专门针对库而制定,采用 LGPL 的库相对来说应该已经考虑了对调用程序的影响,更容易避免被“传染”。

我们再来看一下“自用”,GNU 官方问答对于自用的解释非常宽泛,在一个企业集团的总公司、分公司、子公司等使用,都可以算作自用的范畴,不构成分发。

对于采用 GPL、LGPL 许可证的软件,如果放在服务器/云上以 SaaS 方式对外提供服务,不构成分发,但如果将其部署在用户终端上,则构成分发,带来传染性问题。

对于采用 AGPL 许可证的软件,即便是运行在服务器/云上,如果后续用户对其源代码进行了修改,也必须将修改版本发布出来。

需要注意的是,某个GPL软件的使用场景如果发生变化,之前对其传染性风险的判断也有可能变化,需要根据新的使用场景重新评估。

第三部分我们来看一个案例,MongoDB 是一个非常典型的使用 AGPL 许可证的开源软件。国外有文章甚至开玩笑说,正是因为有了 MongoDB,人们对 AGPL 许可证的看法有了明显改变,从 “never use AGPL” 转变成 “never use AGPL except for Mongo DB”。

具体看一下 MongoDB 的许可政策。MongoDB 的数据库部分采用严格的 AGPL v3.0 许可证,并且是双重许可,用户既可以选择开源版本,也可以选择商业授权版本。MongoDB 的驱动部分则采用宽松的 Apache v2.0 许可证。

通过对数据库和驱动分别适用不同的许可证,MongoDB 在坚守其自由软件本质的同时,也为用户大开方便之门。

其中需要注意,如果用户修改了 MongoDB 核心数据库的源代码,则必须将修改版本发布出来,回馈给社区。

反之,如果用户的程序仅是使用 MongoDB 数据库,没有对数据库源代码进行修改,则不必发布该用户程序。Copyleft 仅适用于 mongod 和 mongos 数据库,而驱动则采用 Apache 许可证,所以用户的程序如果通过 MongoDB 官方推荐的驱动与数据库进行交互,也不被视为 AGPL 范畴下的结合作品,而是单独的程序或作品,无需担心被传染。

从 MongoDB 这个案例可以看出,一些开源软件的著作权人为了保护和推广自己的开源项目,可以说是“煞费苦心”,绞尽脑汁地在许可证方面进行精巧的设计,给出一些“例外声明”,为用户“开后门”,让用户可以相对比较放心地使用,推动了这些开源软件的迅速普及。

“开源智慧专栏”由集慧智佳与国内领先的开源社区 Linux 中国合作创办,聚焦开源软件的知识产权问题,旨在传播域外动态,梳理经典判例,翻译重要文本,关注行业热点,分享实务经验。

在第三部分简要介绍了 GPL 传染性判断的方法和原则,其主要依据是自由软件基金会发布的 GPL 许可证官方问答。我们也对这个问题进行了翻译,陆续发表在本专栏中。此外,对于此前闹得沸沸扬扬的 Facebook 公司 react 许可证事件,我们也进行了实时的跟踪和解读。

以上所列为本专栏从创办至今所发表的文章列表,大家可以登录 Linux 中国的“开源智慧专栏”查看,或者扫描关注微信公众号,接收实时推送的相关文章。

最后,简单介绍一下我本人。我所供职的单位——集慧智佳是中国第一家在新三板挂牌(838286)的知识产权咨询公司,对开源知识产权问题一直进行持续的跟踪和研究,曾承担国家级的开源知识产权研究课题,并为国内知名的互联网软件企业提供开源知识产权风险排查服务。

我在集慧智佳互联网事业部担任高级咨询师,擅长专利检索、专利分析、专利布局、竞争对手跟踪、FTO 调查、开源软件知识产权风险分析;长期为联想、中国移动、海尔、东软等互联网企业、高科技公司提供知识产权咨询服务;担任“开源智慧专栏”主要撰稿人。

  • 2017 年 10 月 14 日,在 GNOME 2017 亚洲峰会上发表题为《开源软件的知识产权风险》演讲。
  • 2017 年 11 月 18 日,在 2017 中国开源年会上发表题为《ABC 时代 GPL 许可证传染性问题探讨》演讲(PDF)。

欢迎大家围绕开源软件知识产权问题与我进行交流!

不论是经验丰富的老程序员,还是没有经验的新手,Python 都是一个非常好的编程语言。

Python 是一个非常流行的编程语言,它可以用于创建桌面应用程序、3D 图形、视频游戏、甚至是网站。它是非常好的首选编程语言,因为它易于学习,不像一些复杂的语言,比如,C、 C++、 或 Java。 即使如此, Python 依然也是强大且健壮的,足以创建高级的应用程序,并且几乎适用于所有使用电脑的行业。不论是经验丰富的老程序员,还是没有经验的新手,Python 都是一个非常好的编程语言。

安装 Python

在学习 Python 之前,你需要先去安装它:

Linux: 如果你使用的是 Linux 系统, Python 是已经包含在里面了。但是,你如果确定要使用 Python 3 。应该去检查一下你安装的 Python 版本,打开一个终端窗口并输入:

python3 -V

如果提示该命令没有找到,你需要从你的包管理器中去安装 Python 3。

MacOS: 如果你使用的是一台 Mac,可以看上面 Linux 的介绍来确认是否安装了 Python 3。MacOS 没有内置的包管理器,因此,如果发现没有安装 Python 3,可以从 python.org/downloads/mac-osx 安装它。即使 macOS 已经安装了 Python 2,你还是应该学习 Python 3。

Windows: 微软 Windows 当前是没有安装 Python 的。从 python.org/downloads/windows 安装它。在安装向导中一定要选择 Add Python to PATH 来将 Python 执行程序放到搜索路径。

在 IDE 中运行

在 Python 中写程序,你需要准备一个文本编辑器,使用一个集成开发环境(IDE)是非常实用的。IDE 在一个文本编辑器中集成了一些方便而有用的 Python 功能。IDLE 3 和 NINJA-IDE 是你可以考虑的两种选择:

IDLE 3

Python 自带的一个基本的 IDE 叫做 IDLE。

 title=

它有关键字高亮功能,可以帮助你检测拼写错误,并且有一个“运行”按钮可以很容易地快速测试代码。

要使用它:

  • 在 Linux 或 macOS 上,启动一个终端窗口并输入 idle3
  • 在 Windows,从开始菜单中启动 Python 3。

    • 如果你在开始菜单中没有看到 Python,在开始菜单中通过输入 cmd 启动 Windows 命令提示符,然后输入 C:\Windows\py.exe
    • 如果它没有运行,试着重新安装 Python。并且确认在安装向导中选择了 “Add Python to PATH”。参考 docs.python.org/3/using/windows.html 中的详细介绍。
    • 如果仍然不能运行,那就使用 Linux 吧!它是免费的,只要将你的 Python 文件保存到一个 U 盘中,你甚至不需要安装它就可以使用。

Ninja-IDE

Ninja-IDE 是一个优秀的 Python IDE。它有关键字高亮功能可以帮助你检测拼写错误、引号和括号补全以避免语法错误,行号(在调试时很有帮助)、缩进标记,以及运行按钮可以很容易地进行快速代码测试。

 title=

要使用它:

  1. 安装 Ninja-IDE。如果你使用的是 Linux,使用包管理器安装是非常简单的;否则, 从 NINJA-IDE 的网站上 下载 合适的安装版本。
  2. 启动 Ninja-IDE。
  3. 转到 Edit 菜单,并选择 Preferences 设置。
  4. 在 Preferences 窗口中,点击 Execution 选项卡。
  5. 在 Execution 选项卡上,更改 pythonpython3

 title=

Ninja-IDE 中的 Python3

告诉 Python 想做什么

关键字可以告诉 Python 你想要做什么。不论是在 IDLE 还是在 Ninja 中,转到 File 菜单并创建一个新文件。对于 Ninja 用户:不要创建一个新项目,仅创建一个新文件。

在你的新的空文件中,在 IDLE 或 Ninja 中输入以下内容:

    print("Hello world.")
  • 如果你使用的是 IDLE,转到 Run 菜单并选择 Run module 选项。
  • 如果你使用的是 Ninja,在左侧按钮条中点击 Run File 按钮。

 title=

在 Ninja 中运行文件

关键字 print 告诉 Python 去打印输出在圆括号中引用的文本内容。

虽然,这并不是特别刺激。在其内部, Python 只能访问基本的关键字,像 printhelp,最基本的数学函数,等等。

可以使用 import 关键字加载更多的关键字。在 IDLE 或 Ninja 中开始一个新文件,命名为 pen.py

警告:不要命名你的文件名为 turtle.py,因为名为 turtle.py 的文件是包含在你正在控制的 turtle (海龟)程序中的。命名你的文件名为 turtle.py ,将会把 Python 搞糊涂,因为它会认为你将导入你自己的文件。

在你的文件中输入下列的代码,然后运行它:

    import turtle

Turtle 是一个非常有趣的模块,试着这样做:

    turtle.begin_fill()
    turtle.forward(100)
    turtle.left(90)
    turtle.forward(100)
    turtle.left(90)
    turtle.forward(100)
    turtle.left(90)
    turtle.forward(100)
    turtle.end_fill()

看一看你现在用 turtle 模块画出了一个什么形状。

要擦除你的海龟画图区,使用 turtle.clear() 关键字。想想看,使用 turtle.color("blue") 关键字会出现什么情况?

尝试更复杂的代码:

    import turtle as t
    import time

    t.color("blue")
    t.begin_fill()

    counter=0

    while counter < 4:
        t.forward(100)
        t.left(90)
        counter = counter+1

    t.end_fill()
    time.sleep(5)

运行完你的脚本后,是时候探索更有趣的模块了。

通过创建一个游戏来学习 Python

想学习更多的 Python 关键字,和用图形编程的高级特性,让我们来关注于一个游戏逻辑。在这个教程中,我们还将学习一些关于计算机程序是如何构建基于文本的游戏的相关知识,在游戏里面计算机和玩家掷一个虚拟骰子,其中掷的最高的是赢家。

规划你的游戏

在写代码之前,最重要的事情是考虑怎么去写。在他们写代码 之前,许多程序员是先 写简单的文档,这样,他们就有一个编程的目标。如果你想给这个程序写个文档的话,这个游戏看起来应该是这样的:

  1. 启动掷骰子游戏并按下 Return 或 Enter 去掷骰子
  2. 结果打印在你的屏幕上
  3. 提示你再次掷骰子或者退出

这是一个简单的游戏,但是,文档会告诉你需要做的事很多。例如,它告诉你写这个游戏需要下列的组件:

  • 玩家:你需要一个人去玩这个游戏。
  • AI:计算机也必须去掷,否则,就没有什么输或赢了
  • 随机数:一个常见的六面骰子表示从 1-6 之间的一个随机数
  • 运算:一个简单的数学运算去比较一个数字与另一个数字的大小
  • 一个赢或者输的信息
  • 一个再次玩或退出的提示

制作掷骰子游戏的 alpha 版

很少有程序,一开始就包含其所有的功能,因此,它们的初始版本仅实现最基本的功能。首先是几个定义:

变量是一个经常要改变的值,它在 Python 中使用的非常多。每当你需要你的程序去“记住”一些事情的时候,你就要使用一个变量。事实上,运行于代码中的信息都保存在变量中。例如,在数学方程式 x + 5 = 20 中,变量是 x ,因为字母 x 是一个变量占位符。

整数是一个数字, 它可以是正数也可以是负数。例如,1-1 都是整数,因此,1421,甚至 10947 都是。

在 Python 中变量创建和使用是非常容易的。这个掷骰子游戏的初始版使用了两个变量: playerai

在命名为 dice_alpha.py 的新文件中输入下列代码:

    import random

    player = random.randint(1,6)
    ai = random.randint(1,6)

    if player > ai :
        print("You win")  # notice indentation
    else:
        print("You lose")

启动你的游戏,确保它能工作。

这个游戏的基本版本已经工作的非常好了。它实现了游戏的基本目标,但是,它看起来不像是一个游戏。玩家不知道他们摇了什么,电脑也不知道摇了什么,并且,即使玩家还想玩但是游戏已经结束了。

这是软件的初始版本(通常称为 alpha 版)。现在你已经确信实现了游戏的主要部分(掷一个骰子),是时候该加入到程序中了。

改善这个游戏

在你的游戏的第二个版本中(称为 beta 版),将做一些改进,让它看起来像一个游戏。

1、 描述结果

不要只告诉玩家他们是赢是输,他们更感兴趣的是他们掷的结果。在你的代码中尝试做如下的改变:

    player = random.randint(1,6)
    print("You rolled " + player)

    ai = random.randint(1,6)
    print("The computer rolled " + ai)

现在,如果你运行这个游戏,它将崩溃,因为 Python 认为你在尝试做数学运算。它认为你试图在 player 变量上加字母 You rolled ,而保存在其中的是数字。

你必须告诉 Python 处理在 playerai 变量中的数字,就像它们是一个句子中的单词(一个字符串)而不是一个数学方程式中的一个数字(一个整数)。

在你的代码中做如下的改变:

    player = random.randint(1,6)
    print("You rolled " + str(player) )

    ai = random.randint(1,6)
    print("The computer rolled " + str(ai) )

现在运行你的游戏将看到该结果。

2、 让它慢下来

计算机运行的非常快。人有时可以很快,但是在游戏中,产生悬念往往更好。你可以使用 Python 的 time 函数,在这个紧张时刻让你的游戏慢下来。

    import random
    import time

    player = random.randint(1,6)
    print("You rolled " + str(player) )

    ai = random.randint(1,6)
    print("The computer rolls...." )
    time.sleep(2)
    print("The computer has rolled a " + str(player) )

    if player > ai :
        print("You win")  # notice indentation
    else:
        print("You lose")

启动你的游戏去测试变化。

3、 检测关系

如果你多玩几次你的游戏,你就会发现,即使你的游戏看起来运行很正确,它实际上是有一个 bug 在里面:当玩家和电脑摇出相同的数字的时候,它就不知道该怎么办了。

去检查一个值是否与另一个值相等,Python 使用 ==。那是个“双”等号标记,不是一个。如果你仅使用一个,Python 认为你尝试去创建一个新变量,但是,实际上你是去尝试做数学运算。

当你想有比两个选项(即,赢或输)更多的选择时,你可以使用 Python 的 elif 关键字,它的意思是“否则,如果”。这允许你的代码去检查,是否在“许多”结果中有一个是 true, 而不是只检查“一个”是 true

像这样修改你的代码:

    if player > ai :
        print("You win")  # notice indentation
    elif player == ai:
        print("Tie game.")
    else:
        print("You lose")

多运行你的游戏几次,去看一下你能否和电脑摇出一个平局。

编写最终版

你的掷骰子游戏的 beta 版的功能和感觉比起 alpha 版更像游戏了,对于最终版,让我们来创建你的第一个 Python 函数

函数是可以作为一个独立的单元来调用的一组代码的集合。函数是非常重要的,因为,大多数应用程序里面都有许多代码,但不是所有的代码都只运行一次。函数可以启用应用程序并控制什么时候可以发生什么事情。

将你的代码变成这样:

    import random
    import time

    def dice():
        player = random.randint(1,6)
        print("You rolled " + str(player) )

        ai = random.randint(1,6)
        print("The computer rolls...." )
        time.sleep(2)
        print("The computer has rolled a " + str(player) )

        if player > ai :
            print("You win")  # notice indentation
        else:
            print("You lose")

        print("Quit? Y/N")
        cont = input()

        if cont == "Y" or cont == "y":
            exit()
        elif cont == "N" or cont == "n":
            pass
        else:
            print("I did not understand that. Playing again.")

游戏的这个版本,在他们玩游戏之后会询问玩家是否退出。如果他们用一个 Yy 去响应, Python 就会调用它的 exit 函数去退出游戏。

更重要的是,你将创建一个称为 dice 的你自己的函数。这个 dice 函数并不会立即运行,事实上,如果在这个阶段你尝试去运行你的游戏,它不会崩溃,但它也不会正式运行。要让 dice 函数真正运行起来做一些事情,你必须在你的代码中去调用它

在你的现有代码下面增加这个循环,前两行就是上文中的前两行,不需要再次输入,并且要注意哪些需要缩进哪些不需要。要注意缩进格式

        else:
            print("I did not understand that. Playing again.")

    # main loop
    while True:
        print("Press return to roll your die.")
        roll = input()
        dice()

while True 代码块首先运行。因为 True 被定义为总是真,这个代码块将一直运行,直到 Python 告诉它退出为止。

while True 代码块是一个循环。它首先提示用户去启动这个游戏,然后它调用你的 dice 函数。这就是游戏的开始。当 dice 函数运行结束,根据玩家的回答,你的循环再次运行或退出它。

使用循环来运行程序是编写应用程序最常用的方法。循环确保应用程序保持长时间的可用,以便计算机用户使用应用程序中的函数。

下一步

现在,你已经知道了 Python 编程的基础知识。这个系列的下一篇文章将描述怎么使用 PyGame 去编写一个视频游戏,一个比 turtle 模块有更多功能的模块,但它也更复杂一些。


作者简介:

Seth Kenlon - 一个独立的多媒体大师,自由文化的倡导者,和 UNIX 极客。他同时从事电影和计算机行业。他是基于 slackwarers 的多媒体制作项目的维护者之一, http://slackermedia.info


via: https://opensource.com/article/17/10/python-101

作者:Seth Kenlon 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

容器是一个日益流行的开发环境。作为一名开发人员,你可以选择多种工具来管理你的容器。本文将向你介绍 Ansible Container,并展示如何在类似生产环境中运行和测试你的应用程序。

入门

这个例子使用了一个简单的 Flask Hello World 程序。这个程序就像在生产环境中一样由 Apache HTTP 服务器提供服务。首先,安装必要的 docker 包:

sudo dnf install docker

Ansible Container 需要通过本地套接字与 Docker 服务进行通信。以下命令将更改套接字所有者,并将你添加到可访问此套接字的 docker 用户组:

sudo groupadd docker && sudo gpasswd -a $USER docker
MYGRP=$(id -g) ; newgrp docker ; newgrp $MYGRP

运行 id 命令以确保 docker 组在你的组成员中列出。最后,使用 sudo 启用并启动 docker 服务:

sudo systemctl enable docker.service
sudo systemctl start docker.service

设置 Ansible Container

Ansible Container 使你能够构建容器镜像并使用 Ansible playbook 进行编排。该程序在一个 YAML 文件中描述,而不是使用 Dockerfile,列出组成容器镜像的 Ansible 角色。

不幸的是,Ansible Container 在 Fedora 中没有 RPM 包可用。要安装它,请使用 python3 虚拟环境模块。

mkdir ansible-container-flask-example
cd ansible-container-flask-example
python3 -m venv .venv
source .venv/bin/activate
pip install ansible-container[docker]

这些命令将安装 Ansible Container 及 Docker 引擎。 Ansible Container 提供三种引擎:Docker、Kubernetes 和 Openshift。

设置项目

现在已经安装了 Ansible Container,接着设置这个项目。Ansible Container 提供了一个简单的命令来创建启动所需的所有文件:

ansible-container init

来看看这个命令在当前目录中创建的文件:

  • ansible.cfg
  • ansible-requirements.txt
  • container.yml
  • meta.yml
  • requirements.yml

该项目仅使用 container.yml 来描述程序服务。有关其他文件的更多信息,请查看 Ansible Container 的入门文档。

定义容器

如下更新 container.yml

version: "2"
settings:
  conductor:
    # The Conductor container does the heavy lifting, and provides a portable
    # Python runtime for building your target containers. It should be derived
    # from the same distribution as you're building your target containers with.
    base: fedora:26
    # roles_path:   # Specify a local path containing Ansible roles
    # volumes:      # Provide a list of volumes to mount
    # environment:  # List or mapping of environment variables

  # Set the name of the project. Defaults to basename of the project directory.
  # For built services, concatenated with service name to form the built image name.
  project_name: flask-helloworld

services: 
  # Add your containers here, specifying the base image you want to build from.
  # To use this example, uncomment it and delete the curly braces after services key.
  # You may need to run `docker pull ubuntu:trusty` for this to work.
  web:
    from: "fedora:26"
    roles: 
      - base
    ports:
      - "5000:80"
    command: ["/usr/bin/dumb-init", "httpd", "-DFOREGROUND"]
    volumes:
      - $PWD/flask-helloworld:/flaskapp:Z

conductor 部分更新了基本设置以使用 Fedora 26 容器基础镜像。

services 部分添加了 web 服务。这个服务使用 Fedora 26,后面有一个名为 base 的角色。它还设置容器和主机之间的端口映射。Apache HTTP 服务器为容器的端口 80 上的 Flask 程序提供服务,该容器重定向到主机的端口 5000。然后这个文件定义了一个卷,它将 Flask 程序源代码挂载到容器中的 /flaskapp 中。

最后,容器启动时运行 command 配置。这个例子中使用 dumb-init,一个简单的进程管理器并初始化系统启动 Apache HTTP 服务器。

Ansible 角色

现在已经设置完了容器,创建一个 Ansible 角色来安装并配置 Flask 程序所需的依赖关系。首先,创建 base 角色。

mkdir -p roles/base/tasks
touch roles/base/tasks/main.yml

现在编辑 main.yml ,它看起来像这样:

---
- name: Install dependencies 
  dnf: pkg={{item}} state=present
  with_items:
    - python3-flask
    - dumb-init
    - httpd
    - python3-mod_wsgi

- name: copy the apache configuration
  copy:
    src: flask-helloworld.conf
    dest: /etc/httpd/conf.d/flask-helloworld.conf
    owner: apache
    group: root
    mode: 655

这个 Ansible 角色是简单的。首先它安装依赖关系。然后,复制 Apache HTTP 服务器配置。如果你对 Ansible 角色不熟悉,请查看角色文档

Apache HTTP 配置

接下来,通过创建 flask-helloworld.conf 来配置 Apache HTTP 服务器:

$ mkdir -p roles/base/files
$ touch roles/base/files/flask-helloworld.conf

最后将以下内容添加到文件中:

<VirtualHost *>
    ServerName example.com

    WSGIDaemonProcess hello_world user=apache group=root
    WSGIScriptAlias / /flaskapp/flask-helloworld.wsgi

    <Directory /flaskapp>
        WSGIProcessGroup hello_world
        WSGIApplicationGroup %{GLOBAL}
    Require all granted
    </Directory>
</VirtualHost>

这个文件的重要部分是 WSGIScriptAlias。该指令将脚本 flask-helloworld.wsgi 映射到 /。有关 Apache HTTP 服务器和 mod\_wsgi 的更多详细信息,请阅读 Flask 文档

Flask “hello world”

最后,创建一个简单的 Flask 程序和 flask-helloworld.wsgi 脚本。

mkdir flask-helloworld
touch flask-helloworld/app.py
touch flask-helloworld/flask-helloworld.wsgi

将以下内容添加到 app.py

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

然后编辑 flask-helloworld.wsgi ,添加这个:

import sys
sys.path.insert(0, '/flaskapp/')

from app import app as application

构建并运行

现在是时候使用 ansible-container buildansible-container run 命令来构建和运行容器。

ansible-container build

这个命令需要一些时间来完成,所以要耐心等待。

ansible-container run

你现在可以通过以下 URL 访问你的 flask 程序: http://localhost:5000/

结论

你现在已经看到如何使用 Ansible Container 来管理、构建和配置在容器中运行的程序。本例的所有配置文件和源代码在 Pagure.io 上。你可以使用此例作为基础来开始在项目中使用 Ansible Container。


via: https://fedoramagazine.org/build-test-applications-ansible-container/

作者:Clement Verna 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

大规模容器应用编排起步

通过《面向 Java 开发者的 Kubernetes》,学习基本的 Kubernetes 概念和自动部署、维护和扩展你的 Java 应用程序的机制。下载该电子书的免费副本

在 《Java 的容器化持续交付》 中,我们探索了在 Docker 容器内打包和部署 Java 应用程序的基本原理。这只是创建基于容器的生产级系统的第一步。在真实的环境中运行容器还需要一个容器编排和计划的平台,并且,现在已经存在了很多个这样的平台(如,Docker Swarm、Apach Mesos、AWS ECS),而最受欢迎的是 Kubernetes。Kubernetes 被用于很多组织的产品中,并且,它现在由原生云计算基金会(CNCF)所管理。在这篇文章中,我们将使用以前的一个简单的基于 Java 的电子商务商店,我们将它打包进 Docker 容器内,并且在 Kubernetes 上运行它。

“Docker Java Shopfront” 应用程序

我们将打包进容器,并且部署在 Kubernetes 上的 “Docker Java Shopfront” 应用程序的架构,如下面的图所示:

在我们开始去创建一个所需的 Kubernetes 部署配置文件之前,让我们先学习一下关于容器编排平台中的一些核心概念。

Kubernetes 101

Kubernetes 是一个最初由谷歌开发的开源的部署容器化应用程序的 编排器 orchestrator 。谷歌已经运行容器化应用程序很多年了,并且,由此产生了 Borg 容器编排器,它是应用于谷歌内部的,是 Kubernetes 创意的来源。如果你对这个技术不熟悉,一些出现的许多核心概念刚开始你会不理解,但是,实际上它们都很强大。首先, Kubernetes 采用了不可变的基础设施的原则。部署到容器中的内容(比如应用程序)是不可变的,不能通过登录到容器中做成改变。而是要以部署新的版本替代。第二,Kubernetes 内的任何东西都是 声明式 declaratively 配置。开发者或运维指定系统状态是通过部署描述符和配置文件进行的,并且,Kubernetes 是可以响应这些变化的——你不需要去提供命令,一步一步去进行。

不可变基础设施和声明式配置的这些原则有许多好处:它容易防止配置 偏移 drift ,或者 “ 雪花 snowflake ” 应用程序实例;声明部署配置可以保存在版本控制中,与代码在一起;并且, Kubernetes 大部分都可以自我修复,比如,如果系统经历失败,假如是一个底层的计算节点失败,系统可以重新构建,并且根据在声明配置中指定的状态去重新均衡应用程序。

Kubernetes 提供几个抽象概念和 API,使之可以更容易地去构建这些分布式的应用程序,比如,如下的这些基于微服务架构的:

  • 豆荚 Pod —— 这是 Kubernetes 中的最小部署单元,并且,它本质上是一组容器。 豆荚 Pod 可以让一个微服务应用程序容器与其它“挎斗” 容器,像日志、监视或通讯管理这样的系统服务一起被分组。在一个豆荚中的容器共享同一个文件系统和网络命名空间。注意,一个单个的容器也是可以被部署的,但是,通常的做法是部署在一个豆荚中。
  • 服务 —— Kubernetes 服务提供负载均衡、命名和发现,以将一个微服务与其它隔离。服务是通过复制控制器支持的,它反过来又负责维护在系统内运行期望数量的豆荚实例的相关细节。服务、复制控制器和豆荚在 Kubernetes 中通过使用“标签”连接到一起,并通过它进行命名和选择。

现在让我们来为我们的基于 Java 的微服务应用程序创建一个服务。

构建 Java 应用程序和容器镜像

在我们开始创建一个容器和相关的 Kubernetes 部署配置之前,我们必须首先确认,我们已经安装了下列必需的组件:

  • 适用于 Mac / Windows / Linux 的 Docker - 这允许你在本地机器上,在 Kubernetes 之外去构建、运行和测试 Docker 容器。
  • Minikube - 这是一个工具,它可以通过虚拟机,在你本地部署的机器上很容易地去运行一个单节点的 Kubernetes 测试集群。
  • 一个 GitHub 帐户和本地安装的 Git - 示例代码保存在 GitHub 上,并且通过使用本地的 Git,你可以复刻该仓库,并且去提交改变到该应用程序的你自己的副本中。
  • Docker Hub 帐户 - 如果你想跟着这篇教程进行,你将需要一个 Docker Hub 帐户,以便推送和保存你将在后面创建的容器镜像的拷贝。
  • Java 8 (或 9) SDK 和 Maven - 我们将使用 Maven 和附属的工具使用 Java 8 特性去构建代码。

从 GitHub 克隆项目库代码(可选,你可以 复刻 fork 这个库,并且克隆一个你个人的拷贝),找到 “shopfront” 微服务应用: https://github.com/danielbryantuk/oreilly-docker-java-shopping/

$ git clone [email protected]:danielbryantuk/oreilly-docker-java-shopping.git
$ cd oreilly-docker-java-shopping/shopfront

请加载 shopfront 代码到你选择的编辑器中,比如,IntelliJ IDE 或 Eclipse,并去研究它。让我们使用 Maven 来构建应用程序。最终生成包含该应用的可运行的 JAR 文件位于 ./target 的目录中。

$ mvn clean install
…
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 17.210 s
[INFO] Finished at: 2017-09-30T11:28:37+01:00
[INFO] Final Memory: 41M/328M
[INFO] ------------------------------------------------------------------------

现在,我们将构建 Docker 容器镜像。一个容器镜像的操作系统选择、配置和构建步骤,一般情况下是通过一个 Dockerfile 指定的。我们看一下,我们的示例中位于 shopfront 目录中的 Dockerfile:

FROM openjdk:8-jre
ADD target/shopfront-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8010
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

第一行指定了,我们的容器镜像将被 “ from ” 这个 openjdk:8-jre 基础镜像中创建。openjdk:8-jre 镜像是由 OpenJDK 团队维护的,并且包含了我们在 Docker 容器(就像一个安装和配置了 OpenJDK 8 JDK的操作系统)中运行 Java 8 应用程序所需要的一切东西。第二行是,将我们上面构建的可运行的 JAR “ 添加 add ” 到这个镜像。第三行指定了端口号是 8010,我们的应用程序将在这个端口号上监听,如果外部需要可以访问,必须要 “ 暴露 exposed ” 它,第四行指定 “ 入口 entrypoint ” ,即当容器初始化后去运行的命令。现在,我们来构建我们的容器:

$ docker build -t danielbryantuk/djshopfront:1.0 .
Successfully built 87b8c5aa5260
Successfully tagged danielbryantuk/djshopfront:1.0

现在,我们推送它到 Docker Hub。如果你没有通过命令行登入到 Docker Hub,现在去登入,输入你的用户名和密码:

$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username:
Password:
Login Succeeded
$
$ docker push danielbryantuk/djshopfront:1.0
The push refers to a repository [docker.io/danielbryantuk/djshopfront]
9b19f75e8748: Pushed 
...
cf4ecb492384: Pushed 
1.0: digest: sha256:8a6b459b0210409e67bee29d25bb512344045bd84a262ede80777edfcff3d9a0 size: 2210

部署到 Kubernetes 上

现在,让我们在 Kubernetes 中运行这个容器。首先,切换到项目根目录的 kubernetes 目录:

$ cd ../kubernetes

打开 Kubernetes 部署文件 shopfront-service.yaml,并查看内容:

---
apiVersion: v1
kind: Service
metadata:
  name: shopfront
  labels:
    app: shopfront
spec:
  type: NodePort
  selector:
    app: shopfront
  ports:
  - protocol: TCP
    port: 8010
    name: http

---
apiVersion: v1
kind: ReplicationController
metadata:
  name: shopfront
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: shopfront
    spec:
      containers:
      - name: shopfront
        image: danielbryantuk/djshopfront:latest
        ports:
        - containerPort: 8010
        livenessProbe:
          httpGet:
            path: /health
            port: 8010
          initialDelaySeconds: 30
          timeoutSeconds: 1

这个 yaml 文件的第一节创建了一个名为 “shopfront” 的服务,它将到该服务(8010 端口)的 TCP 流量路由到标签为 “app: shopfront” 的豆荚中 。配置文件的第二节创建了一个 ReplicationController ,其通知 Kubernetes 去运行我们的 shopfront 容器的一个复制品(实例),它是我们标为 “app: shopfront” 的声明(spec)的一部分。我们也指定了暴露在我们的容器上的 8010 应用程序端口,并且声明了 “livenessProbe” (即健康检查),Kubernetes 可以用于去决定我们的容器应用程序是否正确运行并准备好接受流量。让我们来启动 minikube 并部署这个服务(注意,根据你部署的机器上的可用资源,你可能需要去修 minikube 中的指定使用的 CPU 和 内存 memory ):

$ minikube start --cpus 2 --memory 4096
Starting local Kubernetes v1.7.5 cluster...
Starting VM...
Getting VM IP address...
Moving files into cluster...
Setting up certs...
Connecting to cluster...
Setting up kubeconfig...
Starting cluster components...
Kubectl is now configured to use the cluster.
$ kubectl apply -f shopfront-service.yaml
service "shopfront" created
replicationcontroller "shopfront" created

你可以通过使用 kubectl get svc 命令查看 Kubernetes 中所有的服务。你也可以使用 kubectl get pods 命令去查看所有相关的豆荚(注意,你第一次执行 get pods 命令时,容器可能还没有创建完成,并被标记为未准备好):

$ kubectl get svc
NAME         CLUSTER-IP   EXTERNAL-IP   PORT(S)          AGE
kubernetes   10.0.0.1     <none>        443/TCP          18h
shopfront    10.0.0.216   <nodes>       8010:31208/TCP   12s
$ kubectl get pods
NAME              READY     STATUS              RESTARTS   AGE
shopfront-0w1js   0/1       ContainerCreating   0          18s
$ kubectl get pods
NAME              READY     STATUS    RESTARTS   AGE
shopfront-0w1js   1/1       Running   0          2m

我们现在已经成功地在 Kubernetes 中部署完成了我们的第一个服务。

是时候进行烟雾测试了

现在,让我们使用 curl 去看一下,我们是否可以从 shopfront 应用程序的健康检查端点中取得数据:

$ curl $(minikube service shopfront --url)/health
{"status":"UP"}

你可以从 curl 的结果中看到,应用的 health 端点是启用的,并且是运行中的,但是,在应用程序按我们预期那样运行之前,我们需要去部署剩下的微服务应用程序容器。

构建剩下的应用程序

现在,我们有一个容器已经运行,让我们来构建剩下的两个微服务应用程序和容器:

$ cd ..
$ cd productcatalogue/
$ mvn clean install
…
$ docker build -t danielbryantuk/djproductcatalogue:1.0 .
...
$ docker push danielbryantuk/djproductcatalogue:1.0
...
$ cd ..
$ cd stockmanager/
$ mvn clean install
...
$ docker build -t danielbryantuk/djstockmanager:1.0 .
...
$ docker push danielbryantuk/djstockmanager:1.0
...

这个时候, 我们已经构建了所有我们的微服务和相关的 Docker 镜像,也推送镜像到 Docker Hub 上。现在,我们去在 Kubernetes 中部署 productcataloguestockmanager 服务。

在 Kubernetes 中部署整个 Java 应用程序

与我们上面部署 shopfront 服务时类似的方式去处理它,我们现在可以在 Kubernetes 中部署剩下的两个微服务:

$ cd ..
$ cd kubernetes/
$ kubectl apply -f productcatalogue-service.yaml
service "productcatalogue" created
replicationcontroller "productcatalogue" created
$ kubectl apply -f stockmanager-service.yaml
service "stockmanager" created
replicationcontroller "stockmanager" created
$ kubectl get svc
NAME               CLUSTER-IP   EXTERNAL-IP   PORT(S)          AGE

kubernetes         10.0.0.1     <none>        443/TCP          19h
productcatalogue   10.0.0.37    <nodes>       8020:31803/TCP   42s
shopfront          10.0.0.216   <nodes>       8010:31208/TCP   13m
stockmanager       10.0.0.149   <nodes>       8030:30723/TCP   16s
$ kubectl get pods
NAME                     READY     STATUS    RESTARTS   AGE
productcatalogue-79qn4   1/1       Running   0          55s
shopfront-0w1js          1/1       Running   0          13m
stockmanager-lmgj9       1/1       Running   0          29s

取决于你执行 “kubectl get pods” 命令的速度,你或许会看到所有都处于不再运行状态的豆荚。在转到这篇文章的下一节之前,我们要等着这个命令展示出所有豆荚都运行起来(或许,这个时候应该来杯咖啡!)

查看完整的应用程序

在所有的微服务部署完成并且所有相关的豆荚都正常运行后,我们现在将去通过 shopfront 服务的 GUI 去访问我们完整的应用程序。我们可以通过执行 minikube 命令在默认浏览器中打开这个服务:

$ minikube service shopfront

如果一切正常,你将在浏览器中看到如下的页面:

结论

在这篇文章中,我们已经完成了由三个 Java Spring Boot 和 Dropwizard 微服务组成的应用程序,并且将它部署到 Kubernetes 上。未来,我们需要考虑的事还很多,比如,调试服务(或许是通过工具,像 TelepresenceSysdig),通过一个像 JenkinsSpinnaker 这样的可持续交付的过程去测试和部署,并且观察我们的系统运行。


本文是与 NGINX 协作创建的。 查看我们的编辑独立性声明.


作者简介:

Daniel Bryant 是一名独立技术顾问,他是 SpectoLabs 的 CTO。他目前关注于通过识别价值流、创建构建过程、和实施有效的测试策略,从而在组织内部实现持续交付。Daniel 擅长并关注于“DevOps”工具、云/容器平台和微服务实现。他也贡献了几个开源项目,并定期为 InfoQ、 O’Reilly、和 Voxxed 撰稿...


via: https://www.oreilly.com/ideas/how-to-manage-docker-containers-in-kubernetes-with-java

作者:Daniel Bryant 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Red Hat 已经学会了跟随开源社区,并将其商业化。你也可以。

开源中最强大但最困难的事情之一就是社区。红帽首席执行官 Jim Whitehurst 在与 Slashdot 的最近一次采访中宣称:“有强大社区的存在,开源总是赢得胜利”。但是建设这个社区很难。真的,真的很难。尽管预测开源社区蓬勃发展的必要组成部分很简单,但是预测这些部分在何时何地将会组成像 Linux 或 Kubernetes 这样的社区则极其困难。

这就是为什么聪明的资金似乎在随社区而动,而不是试图创建它们。

可爱的开源寄生虫

在阅读 Whitehurst 的 Slashdot 采访时,这个想法让我感到震惊。虽然他谈及了从 Linux 桌面到 systemd 的很多内容,但 Whitehurst 关于社区的评论对我来说是最有说服力的:

建立一个新社区是困难的。我们在红帽开始做一点,但大部分时间我们都在寻找已经拥有强大社区的现有项目。在强大的社区存在的情况下,开源始终是胜利的。

虽然这种方法 —— 加强现有的、不断发展的社区,似乎是一种逃避,它实际上是最明智的做法。早期,开源几乎总是支离破碎的,因为每个项目都试图获得足够的成员。在某些时候,人们开始聚集两到三名获胜者身边(例如 KDE vs. Gnome,或者 Kubernetes vs. Docker Swarm 与 Apache Mesos)。但最终,很难维持不同的社区“标准”,每个人都最终围拢到一个赢家身边(例如 Kubernetes)。

参见:混合云和开源:红帽数字化转型的秘诀(Tech Pro Research)

这不是投降 —— 这是培养资源和推动标准化的更好方式。例如,Linux 已经成为如此强大的力量的一个原因是,它鼓励在一个地方进行操作系统创新,即使存在不同的分支社区(以及贡献代码的大量的各个公司和个人)不断为它做贡献。红帽的发展很快,部分原因是它在早期就做出了聪明的社区选择,选择了一个赢家(比如 Kubernetes),并帮助它取得更大的成功。

而这反过来又为其商业模式提供动力。

从社区混乱中赚钱

对红帽而言一件很好的事是社区越多,项目就越成功。但是,开源的“成功”并不一定意味着企业会接受该项目,这仅仅意味着他们愿意这样做。红帽公司早期的直觉和不断地支付分红,就是理解那些枯燥、平庸的企业想要开源的活力,而不要,好吧,还是活力。他们需要把它驯服,变得枯燥而又平庸。Whitehurst 在采访中完美地表达了这一点:

红帽是成功的,因为我们沉迷于寻找方法来增加我们每个产品的代码价值。我们认为自己是帮助企业客户轻松消费开源创新的。

仅举一例:对于我们所有的产品,我们关注生命周期。开源是一个伟大的开发模式,但其“尽早发布、频繁发布”的风格使其在生产中难以实施。我们在 Linux 中发挥的一个重要价值是,我们在受支持的内核中支持 bug 修复和安全更新十多年,同时从不破坏 API 兼容性。这对于运行长期应用程序的企业具有巨大的价值。我们通过这种类型的流程来应对我们选择进行产品化的所有项目,以确定我们如何增加源代码之外的价值。

对于大多数想要开源的公司来说,挑战在于他们可能认识到社区的价值,但是不知道怎么不去销售代码。毕竟,销售软件几十年来一直是一个非常有利可图的商业模式,并产生了一些地球上最有价值的公司。

参看红帽如何使 Kubernetes 乏味并且成功

然而, 正如 Whitehurst 指出的那样,开源需要一种不同的方法。他在采访中断言:“你不能出售功能, 因为它是免费的”。相反, 你必须找到其他的价值,比如在很长一段周期内让开源得到支持。这是枯燥的工作,但对红帽而言每年值数十亿美元。

所有这一切都从社区开始。社区更有活力,更好的是它能使开源产品市场化,并能赚钱。为什么?嗯,因为更多的发展的部分等价于更大的价值,随之让自由发展的项目对企业消费而言更加稳定。这是一个成功的公式,并可以发挥所有开源的好处,而不会将它受制于二十世纪的商业模式。


via: https://www.techrepublic.com/article/most-companies-cant-buy-an-open-source-community-clue-heres-how-to-do-it-right/

作者:Matt Asay 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Linux 基金会在其企业开源指南文集中为开发和使用开源软件增加了三篇新的指南。

这个有着 17 年历史的非营利组织的主要任务是支持开源社区,并且,作为该任务的一部分,它在 9 月份发布了六篇 企业开源指南,涉及从招募开发人员到使用开源代码的各个主题。

最近一段时间以来,Linux 基金会与开源专家组 TODO Group(Talk Openly, Develop Openly)合作发布了三篇指南。

这一系列的指南用于在多个层次上帮助企业员工 —— 执行官、开源计划经理、开发者、律师和其他决策者 —— 学习怎么在开源活动中取得更好的收益。

该组织是这样描述这三篇指南的:

  • 提升你的开源开发的影响力, —— 来自 Ibrahim Haddad,三星美国研究院。该指南涵盖了企业可以采用的一些做法,以帮助企业扩大他们在大型开源项目中的影响。
  • 启动一个开源项目,—— 来自 Christine Abernathy,Facebook;Ibrahim Haddad;Guy Martin,Autodesk;John Mertic,Linux 基金会;Jared Smith,第一资本(Capital One)。这个指南帮助已经熟悉开源的企业学习怎么去开始自己的开源项目。
  • 开源阅读列表。一个开源程序管理者必读书清单,由 TODO Group 成员编制完成的。

Some Offerings in the 'Open Source Guides Reading List' Enterprise Guide

企业开源指南的 ‘阅读列表’ 中的一部分产品 (来源:Linux 基金会)

在九月份发布的六篇指南包括:

  • 创建一个开源计划: 学习怎么去建立一个计划去管理内部开源使用和外部贡献。
  • 开源管理工具: 一系列可用于跟踪和管理开源项目的工具。
  • 衡量你的开源计划是否成功: 了解更多关于顶级组织评估其开源计划和贡献的 ROI 的方法。
  • 招聘开发人员: 学习如何通过创建开源文化和贡献开源项目来招募开发人员或从内部挖掘人才。
  • 参与开源社区: 了解奉献内部开发者资源去参与开源社区的重要性,和怎么去操作的最佳方法。
  • 使用开源代码: 在你的商业产品中集成开源代码时,确保你的组织符合它的法律责任。

更多的指南正在编制中,它们将和这些指南一样发布在 Linux 基金会和 GitHub 的网站上。

TODO Group 也发布了四个 案例研究 的补充材料,详细描述了 Autodesk、Comcast、Facebook 和 Salesforce 在构建他们的开源计划中的经验。以后,还有更多的计划。


via: https://adtmag.com/articles/2017/11/06/open-source-guides.aspx

作者:David Ramel 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出