Linux 1.0 之旅：回顾这一切的开始

>
> 通过安装 SLS 1.05 展示了 Linux 内核在这 26 年间走过了多远。
>
>
>

![Happy anniversary, Linux: A look back at where it all began](/data/attachment/album/201709/03/200233sptfve0p013twzdq.png "Happy anniversary, Linux: A look back at where it all began")

我第一次安装 Linux 是在 1993 年。那时我跑的是 MS-DOS，但我真的很喜欢学校机房电脑的 Unix 系统，就在那里度过了我大学本科时光。 当我听说了 Linux，一个 Unix 的免费版本，可以在我家的 386 电脑上运行的时候，我立刻就想要试试。我的第一个 Linux 发行版是 [Softlanding Linux System](https://en.wikipedia.org/wiki/Softlanding_Linux_System) (SLS) 1.03，带有 11 级补丁的 0.99 alpha 版本的 Linux 内核。它要求高达 2 MB 的内存，如果你想要编译项目需要 4 MB，运行 X windows 则需要 8 MB。

我认为 Linux 相较于 MS-DOS 世界是一个巨大的进步。 尽管 Linux 缺乏运行在 MS-DOS 上的广泛的应用及游戏，但我发现 Linux 带给我的是巨大的灵活性。不像 MS-DOS ，现在我可以进行真正的多任务，同时运行不止一个程序。并且 Linux 提供了丰富的工具，包括一个 C 语言编译器，让我可以构建自己的项目。

一年后，我升级到了 SLS 1.05，它支持全新的 Linux 内核 1.0。 更重要的，Linux 引入了内核模块。通过内核模块，你不再需要为支持新硬件而编译整个内核；取而代之，只需要从包含 Linux 内核之内的 63 个模块里加载一个就行。在 SLS 1.05 的发行自述文件中包含这些关于模块的注释：

>
> 内核的模块化旨在正视减少并最终消除重新编译内核的要求，无论是变更、修改设备驱动或者为了动态访问不常用的驱动。也许更为重要的是，个别工作小组的工作不再影响到内核的正确开发。事实上，这让以二进制发布官方内核现在成为了可能。
>
>
>

在 8 月 25 日，Linux 内核将迎来它的第 26 周年（LCTT 译注：已经过去了 =.= ）。为了庆祝，我重新安装了 SLS 1.05 来提醒自己 Linux 1.0 内核是什么样子，去认识 Linux 自二十世纪 90 年代以来走了多远。和我一起踏上 Linux 的怀旧之旅吧！

### 安装

SLS 是第一个真正的 “发行版”，因为它包含一个安装程序。 尽管安装过程并不像现代发行版一样顺畅。 不能从 CD-ROM 启动安装，我需要从安装软盘启动我的系统，然后从 **login** 提示中运行安装程序。

![Installing SLS 1.05 from the login prompt](/data/attachment/album/201709/03/200234llktk4abge6qtkb4.png "Installing SLS 1.05 from the login prompt")

在 SLS 1.05 中引入的一个漂亮的功能是支持彩色的文本模式安装器。当我选择彩色模式时，安装器切换到一个带有黑色文字的亮蓝色背景，不再是我们祖祖辈辈们使用的原始的普通黑白文本。

![Color-enabled text-mode installer in SLS 1.05](/data/attachment/album/201709/03/200234z1tet1604h77m1cb.png "Color-enabled text-mode installer in SLS 1.05")

SLS 安装器是个简单的东西，文本从屏幕底部滚动而上，显示其做的工作。通过对一些简单的提示的响应，我能够创建一个 Linux 分区，挂载上 ext2 文件系统，并安装 Linux 。 安装包含了 X windows 和开发工具的 SLS 1.05，需要大约 85 MB 的磁盘空间。依照今天的标准这听起来可能不是很多，但在 Linux 1.0 出来的时候，120 MB 的硬件设备才是主流设备。

![Creating a partition for Linux, putting an ext2 filesystem on it, and installing Linux](/data/attachment/album/201709/03/200235wsn2uitws5l09s9t.png "Creating a partition for Linux, putting an ext2 filesystem on it, and installing Linux")

![First boot](/data/attachment/album/201709/03/200235beiun1ufey3uanjo.png "First boot")

### 系统级别

当我第一次启动到 Linux 时，让我想起来了一些关于这个早期版本 Linux 系统的事情。首先，Linux 没有占据很多的空间。在启动系统之后运行一些程序来检查的时候，Linux 占用了不到 4 MB 的内存。在一个拥有 16MB 内存的系统中，这就意味着节省了很多内存用来运行程序。

![Checking out the filesystem and available disk space](/data/attachment/album/201709/03/200236cbwkwzyawhadm8y8.png "Checking out the filesystem and available disk space")

熟悉的 `/proc` 元文件系统在 Linux 1.0 就存在了，尽管对比我们今天在现代系统上看到的，它并不能提供许多信息。在 Linux 1.0， `/proc` 包含一些接口来探测类似 `meminfo` 和 `stat` 之类的基本系统状态。

![The familiar /proc meta filesystem](/data/attachment/album/201709/03/200236hlqi0l2g05tl0lh1.png "The familiar /proc meta filesystem")

在这个系统上的 `/etc` 文件目录非常简单。值得一提的是，SLS 1.05 借用了来自 [BSD Unix](https://en.wikipedia.org/wiki/Berkeley_Software_Distribution) 的 **rc** 脚本来控制系统启动。 初始化是通过 **rc** 脚本进行的，由 `rc.local` 文件来定义本地系统的调整。后来，许多 Linux 发行版采用了来自 [Unix System V](https://en.wikipedia.org/wiki/UNIX_System_V) 的很相似的 **init** 脚本，后来又是 [systemd](https://en.wikipedia.org/wiki/Systemd) 初始化系统。

![The /etc directory](/data/attachment/album/201709/03/200237b4imixd85zhri644.png "The /etc directory")

### 你能做些什么

随着我的系统的启动运行，接下来就可以使用了了。那么，在这样的早期 Linux 系统上你能做些什么？

让我们从基本的文件管理开始。 每次在你登录的时候，SLS 会让你使用 Softlanding 菜单界面（MESH），这是一个文件管理程序，现代的用户们可能觉得它和 [Midnight Commander](https://midnight-commander.org/) 很相似。 而二十世纪 90 年代的用户们可能会拿 MESH 与更为接近的 [Norton Commander](https://en.wikipedia.org/wiki/Norton_Commander) 相比，这个可以说是在 MS-DOS 上最流行的第三方文件管理程序。

![The Softlanding menu shell (MESH)](/data/attachment/album/201709/03/200237xuc27l10ttemjztl.png "The Softlanding menu shell (MESH)")

除了 MESH 之外，在 SLS 1.05 中还少量包含了一些全屏应用程序。你可以找到熟悉的用户工具，包括 Elm 邮件阅读器、GNU Emacs 可编程编辑器，以及古老的 Vim 编辑器。

![Elm mail reader](/data/attachment/album/201709/03/200238g821lltit8m1xfr1.png "Elm mail reader")

![GNU Emacs programmable editor](/data/attachment/album/201709/03/200238c9zn4ivenvhveqrs.png "GNU Emacs programmable editor")

SLS 1.05 甚至包含了一个可以让你在终端玩的俄罗斯方块版本。

![Tetris for terminals](/data/attachment/album/201709/03/200239iyviailypdpvvtvr.png "Tetris for terminals")

在二十世纪 90 年代，多数住宅的网络接入是通过拨号连接的，所以 SLS 1.05 包含了 Minicom 调制解调器拨号程序。Minicom 提供一个与调制解调器的直接连接，并需要用户通过贺氏调制解调器的 **AT** 命令来完成一些像是拨号或挂电话这样的基础功能。Minicom 同样支持宏和其他简单功能来使连接你的本地调制解调器池更容易。

![Minicom modem-dialer application](/data/attachment/album/201709/03/200239l0wzqyuoyy9498z2.png "Minicom modem-dialer application")

但如果你想要写一篇文档时怎么办？ SLS 1.05 的存在要比 LibreOffice 或者 OpenOffice 早很长时间。在二十世纪 90 年代，Linux 还没有这些应用。相反，如果你想要使用一个文字处理器，可能需要引导你的系统进入 MS-DOS，然后运行你喜欢的文字处理器程序，如 WordPerfect 或者共享软件 GalaxyWrite。

但是所有的 Unix 系统都包含一套简单的文本格式化程序，叫做 nroff 和 troff。在 Linux 系统中，他们被合并成 GNU groff 包，而 SLS 1.05 包含了 groff 的一个版本。我在 SLS 1.05 上的一项测试就是用 nroff 生成一个简单的文本文档。

![A simple nroff text document](/data/attachment/album/201709/03/200240z6pnnfcxnfy6fpx0.png "A simple nroff text document")

![nroff text document output](/data/attachment/album/201709/03/200241snm0mb41m7jxoxlm.png "nroff text document output")

### 运行 X windows

获取安装 X windows 并不特别容易，如 SLS 安装文件承诺的那样：

>
> 在你的 PC 上获取安装 X windows 可能会有一些发人深省的体验，主要是因为 PC 的显示卡类型太多。Linux X11 仅支持 VGA 类型的显示卡，但在许多类型的 VGA 中仅有个别的某些类型是完全支持的。SLS 存在两种 X windows 服务器。全彩的 XFree86，支持一些或所有 ET3000、ET400、PVGA1、GVGA、Trident、S3、8514、Accelerated cards、ATI plus 等。
>
>
> 另一个服务器 XF86\_Mono，能够工作在几乎所有的 VGA 卡上，但只提供单色模式。因此，相比于彩色服务器，它会占用更少的内存并拥有更快的速度。当然就是看起来不怎么漂亮。
>
>
> X windows 的配置信息都堆放在目录 “/usr/X386/lib/X11/”。需要注意的是，“Xconfig” 文件为监视器和显示卡定义了时序。默认情况下，X windows 设置使用彩色服务器，如果彩色服务器出现问题，你可以切换到单色服务器 x386mono，因为它已经支持各种标准的 VGA。本质上，这只是将 /usr/X386/bin/X 链接到它。
>
>
> 只需要编辑 Xconfig 来设置鼠标驱动类型和时序，然后键入 “startx” 即可。
>
>
>

这些听起来令人困惑，但它就是这样。手工配置 X windows 真的可以是一个发人深省的体验。幸好，SLS 1.05 包含了 syssetup 程序来帮你确定系统组件的种类，包括了 X windows 的显示设置。在一些提示过后，经过一些实验和调整，最终我成功启动了 X windows！

![The syssetup program](/data/attachment/album/201709/03/200241k0dr4h548c2444dv.png "The syssetup program")

但这是来自于 1994 年的 X windows，它仍然并没有桌面的概念。我可以从 FVWM （一个虚拟窗口管理器）或 TWM （选项卡式的窗口管理器）中选择。TWM 直观地设置提供一个功能简单的图形环境。

![TWM](/data/attachment/album/201709/03/200243grmcggnmjvwj9iio.png "TWM")

### 关机

我已经在我的 Linux 寻根之旅沉浸许久，是时候最终回到我的现代桌面上了。最初我跑 Linux 的是一台仅有 8MB 内存和 一个 120MB 硬盘驱动器的 32 位 386 电脑，而我现在的系统已经足够强大了。拥有双核 64 位 Intel Core i5 处理器，4 GB 内存和一个 128 GB 的固态硬盘，我可以在我的运行着 Linux 内核 4.11.11 的系统上做更多事情。那么，在我的 SLS 1.05 的实验结束之后，是时候离开了。

![Shutting down](/data/attachment/album/201709/03/200243e9w82pgh9n39cpza.png "Shutting down")

再见，Linux 1.0。很高兴看到你的茁壮成长。

（题图：图片来源：[litlnemo](https://www.flickr.com/photos/litlnemo/19777182/)。由 Opnesource.com 修改。[CC BY-SA 2.0.](https://creativecommons.org/licenses/by-sa/2.0/)）

---

via: <https://opensource.com/article/17/8/linux-anniversary>

作者：[Jim Hall](https://opensource.com/users/jim-hall) 译者：[softpaopao](https://github.com/softpaopao) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


Linux 桌面市场份额首次突破 3%

可怜的BSD!

我不知道他们是怎么统计的，又没有算上我？

感觉我也没被算上

说句公道话哈，如果不是工作需要（比如编程等），其实还是用windows的时候多一些。从软件支持方面来说，还是windows上软件支持较多，游戏类就不说了，各种玩家几乎都是在windows上玩；各大银行的网银主要还是支持windows，支持linux的极少。从人群方面来说，除了软件行业的人用于工作之外，其他人用linux的很少，比如父母年纪的中老年人，主要用电脑看电视剧、电影啥的，再专业一点看看股票、期货啥的，这类软件还是在windows上操作简单些。所以说，windows的使用人群很庞大，linux一时是超过不了windows的，起码在中国是这样的。

deepin最有可能在中国开花结果

贡献了一点力量，目前家里只有我一个人在用linux

用了一年多了，不错，如果政府给予一定支持，linux真的很不错～

linux和BSD都在用啊。

说句公道话哈，如果不是工作需要（比如PS等），其实还是用Linux的时候多一些，从软件支持方面来说，linux下的开源软件已经足够办公、美术设计等，游戏类就不说了，一个steam打败了无数盗版exe游戏，看看steam上越来越多支持linux的游戏，G胖万岁，各大网银都可以手机解决，网上购物有短信接收验证码就够了。中老年人不想被各种全家桶逼疯天天麻烦子女的话，还是linux省事。所有说linux的潜力很大，但是中国人惯性很大，不愿意做出头鸟，不愿意宣传个性，所以linux一时是超过不了windows的，起码在中国是这样的。

一直搞不懂他们这个数据咋统计的，是否统计了虚拟机。 我们公司开发人员里用虚拟机装ubuntu的好多人，不知道算上了不，不过算上的话，市场份额可能加起来大于100%了。。。

最后一段才是真相吧

他们是根据 WEB 访问日志里面的 agent 字符串判断的。

就服你~~

建议你这篇文章都不要看，呵呵

Android也是Linux，敢拉通来比吗？

为啥我感觉macos越来越多呀

MACOS 看起来很多的样子，竟然只有5%

为Linux说话的人用着windows系统 <br />
为windows说话的人用着Linux系统

![](/data/attachment/album/201709/04/221008vxubu9tr9pjep9ko.jpg)

根据市场调研机构 Net Applications 的[统计](https://www.netmarketshare.com/operating-system-market-share.aspx?qprid=9&qpcustomb=0)，今年 8 月份 Linux 桌面市场份额首次突破了 3%。

![](/data/attachment/album/201709/04/215918k5022z02bxz9xh0e.jpg)

Windows 依旧保持在 90% 以上，苹果 MacOS 也略有下滑，占 5.94%，Linux 则自从上个月达到 2.53% 之后，首次突破到了 3.37%。其它操作系统份额可以忽略不计。

根据 Net Applications 的历史数据，Linux 桌面份额是在 2009 年首次突破 1%，2016 年 6 月首次[突破](http://www.solidot.org/story?sid=50178) 2%，并基本上一直保持住了这个份额。显然从 2% 到 3% 的进度比 1% 到 2% 要快得多。

而据另外一家统计公司 StatCounter 的[统计数据](http://gs.statcounter.com/os-market-share/desktop/worldwide/)，Linux 的份额仅有 1% 多点，如果加上 ChromeOS 刚刚过了 2%。

![](/data/attachment/album/201709/04/220550gvwr434m3avb473u.jpg)


Oracle 终于干掉了 Sun！

Solaris 12不重要，ZFS还在就行

下一个被Oracle抛弃的会是谁？

JavaEE，然后MySQL，最后VBOX<br />
手动滑稽

埃里克森想搞什么，强势上云？

Oracle现在都是主推云服务，但是没有私有云，国企没法用，不知道以后怎么发展。

JavaEE已经交给eclipse基金会了

>
> **随着 Solaris 团队的彻底完蛋，看起来 Sun 微系统公司最终连块骨头都没剩下。**
>
>
>

![](/data/attachment/album/201709/05/161941p7lmxfechzfiowcp.jpg)

来自前 Sun 社区的消息表明，[一月份的传闻](http://www.mercurynews.com/2017/01/20/oracle-lays-off-450-employees/)（Oracle 裁员 450 人）成为了现实，上周五，[Oracle 裁掉了 Solaris 和 SPARC 团队的核心员工](https://twitter.com/drewfisher314/status/903804762373537793)，选择这个时候（<ruby> 美国劳动节 <rp>  （ </rp> <rt>  Labor Day </rt> <rp>  ） </rp></ruby>前的周末）或许是希望这个消息被大众所忽略。

![](/data/attachment/album/201709/04/231420q0z6clxbv4l6t4tt.jpg)

可以肯定，这意味着对于这些产品只会剩下骨架级的维护，特别是， [Solaris 12 也取消了](https://arstechnica.com/information-technology/2017/01/oracle-sort-of-confirms-demise-of-solaris-12-effort/)。这是一个典型的 Oracle 式的“<ruby> 无声结局 <rp>  （ </rp> <rt>  silent EOL </rt> <rp>  ） </rp></ruby>”，无论他们怎么声称保证履行对富士通和其它客户的合同条款。

随着该硬件的淘汰，我估计这是最后一个被 Oracle 收购处置的 Sun 资产了。在收购 Sun 微系统公司之后，[Oracle 的埃里森对 Sun 的管理毫不留情](http://www.businessinsider.com/wow-larry-ellison-just-tore-ex-sun-ceo-jonathan-schwartz-a-new-one-2010-5?IR=T)，而且最大化地利用其手中的机会。那么，Sun 的资产都在 Oracle 手里都有什么遭遇呢？我并不是很关注 Oracle 的业务，但是可以从下列报告中一窥：

* Java 被称之为“皇冠上的宝石”，但是其买下 Java SE 的[真实原因](https://www.theguardian.com/technology/2012/apr/18/oracle-google-court-smartphone)其实是——试图起诉 Google 赔偿 80 亿美金，而且还两次败诉。
* 埃里森说 [Java 是中间件成功的关键](https://betanews.com/2009/04/20/industry-in-a-box-sun-acquisition-will-lead-to-oracle-java/)，但是现在 Java EE 正准备[交给一个开源基金会](https://www.redhat.com/en/blog/java-ee-moving-open-source-foundation)。
* Oracle 批评 Sun 没能从 Java 上挣到钱（忽略了 1996 - 2000 年 Java 使硬件市场获利的事实）并提出了一个根本不会产生收入的[免费模型](http://www.theregister.co.uk/2010/11/06/oracle_dueling_jvms/)。
* 他们 [拥抱了 NetBeans](https://www.infoworld.com/article/2627862/application-development/oracle-hails-java-but-kills-sun-cloud.html)，而现在它被捐献给了 Apache 基金会。
* 对 [MySQL 安全修复](http://www.bytebot.net/blog/archives/2012/07/20/security-fixes-in-mysql-critical-patch-updates)的官僚主义导致一部分社区成员转而和 MySQL 创始人 Monty 创建了 MariaDB 分支，而新分支已经足以支撑起[一家公司](http://mariadb.com/)。
* 埃里森说要[重建 Sun 的硬件业务](https://web.archive.org/web/20100516032914/http://abcnews.go.com:80/Business/wireStory?id=10630034)，但是[其负责人已经在一个月前离职了](https://www.theregister.co.uk/2017/08/02/oracle_john_fowler_bails/)，而剩下的员工也属于裁员的一部分。
* 尽管 Sun 的前老板 [McNealy 明白 Solaris 只有开源才能赢得市场](https://www.theregister.co.uk/2010/12/07/mcnealy_sun_and_open_source/?page=3)，Oracle 表示“你说的没错”，然后把 Solaris [弄死了](http://www.osnews.com/story/23683/Oracle_Kills_OpenSolaris_Moves_Development_Behind_Closed_Doors)。其结果就是，上周末的裁员——而这在今年一月份就已经有了传闻。
* [Hudson 处理失当](https://www.infoq.com/news/2011/01/hudson-jenkins2)，意味着其 CI 业务只能跟在 CloudBees 从 Hubson 分支而来的 Jenkins 后面。
* Oracle 放弃了 Sun 的身份管理项目，而现在 Forgerock 用它撑起了一个价值五亿美金的业务，为那些 Oracle 所不在意的客户服务。
* Oracle 决定 [取消 Sun Cloud](https://www.infoworld.com/article/2627862/application-development/oracle-hails-java-but-kills-sun-cloud.html) ，并拆除了 Solaris 中的云服务功能，然后现在是云服务的天下了。
* Oracle 重命名了 StarOffice，然后[宣布了一个云端版本](https://web.archive.org/web/20101217212955/http://www.oracle.com/us/corporate/press/195766)，但是没卵用。感觉该项目将走向末日，遭受到了严厉对待的社区决定另起锅灶去做 LibreOffice。
* 只有 VirtualBox 看起来还好。

也许这并不是 Sun 失败的真正原因——[在开源 Solaris 上花了太长时间](https://www.theregister.co.uk/2010/12/07/mcnealy_sun_and_open_source/?page=3)，并在 2000 - 2002 年间试图用以市场为主导的方式来替代 Sun 一贯的工程为主导的方式——埃里森指责那些承担力挽狂澜责任的、硕果仅存的领袖们 McNealy、Zander、Tolliver 和他们的团队。埃里森从来没有理解过 Schwartz 所采取的开创性做法，而是在[博客中嘲讽](https://web.archive.org/web/20100516032944/http://abcnews.go.com:80/Business/wirestory?id=10630034&page=2)和[取消所有正在进行中的“科学项目”](https://web.archive.org/web/20100516032949/http://abcnews.go.com:80/Business/wirestory?id=10630034&page=3)，拆除合作伙伴渠道，溯源开源社区。

与本周 HPE 将放弃的旧产品[卖给 Micro Focus](https://www.ft.com/content/16ce31c4-8d5e-11e7-9084-d0c17942ba93) ，让其照料这些产品的做法相反，Oracle 的做法更残酷。Oracle [说过它将“重振 Sun 品牌”](https://www.infoworld.com/article/2627785/m-a/oracle-s-ambitious-plans-for-integrating-sun-s-technology.html)，但是事实上他们杀死的产品比 Sun 管理层曾经管理过的都要多——毫无疑问，这是“交易的艺术”。今天，和许多前 Sun 同事一样，这件事使我非常悲伤。

*（本文由 [Patreon 赞助支持](https://patreon.com/webmink)， 欢迎你也成为支持者之一！）*


一周工作所用的日常 Git 命令

![](/data/attachment/album/201709/05/142738ef9g209isze0fmv2.png)

像大多数新手一样，我一开始是在 StackOverflow 上搜索 Git 命令，然后把答案复制粘贴，并没有真正理解它们究竟做了什么。

![](/data/attachment/album/201709/05/142748s4a4ppxo6i96u9hr.png)

*Image credit: [XKCD](https://xkcd.com/1597/)*

我曾经想过：“如果有一个最常见的 Git 命令的列表，以及它们的功能是什么，这不是极好的吗？”

多年之后，我编制了这样一个列表，并且给出了一些最佳实践，让新手们甚至中高级开发人员都能从中发现有用的东西。

为了保持实用性，我将这个列表与我过去一周实际使用的 Git 命令进行了比较。

几乎每个开发人员都在使用 Git，当然很可能是 GitHub。但大多数开发者大概有 99％ 的时间只是使用这三个命令：

```
git add --all
git commit -am "<message>"
git push origin master

```

如果你只是单枪匹马，或者参加一场黑客马拉松或开发一次性的应用时，它工作得很好，但是当稳定性和可维护性开始成为一个优先考虑的事情后，清理提交、坚持分支策略和提交信息的规范性就变得很重要。

我将从常用命令的列表开始，使新手更容易了解 Git 能做什么，然后进入更高级的功能和最佳实践。

### 经常使用的命令

要想在<ruby> 仓库 <rp>  （ </rp> <rt>  repo </rt> <rp>  ） </rp></ruby>中初始化 Git，你只需输入以下命令即可。如果你没有初始化 Git，则不能在该仓库内运行任何其他的 Git 命令。

```
git init

```

如果你在使用 GitHub，而且正在将代码推送到在线存储的 GitHub 仓库中，那么你正在使用的就是<ruby> 远程 <rp>  （ </rp> <rt>  remote </rt> <rp>  ） </rp></ruby>仓库。该远程仓库的默认名称（也称为别名）为 `origin`。如果你已经从 Github 复制了一个项目，它就有了一个 `origin`。你可以使用命令 `git remote -v` 查看该 `origin`，该命令将列出远程仓库的 URL。

如果你初始化了自己的 Git 仓库，并希望将其与 GitHub 仓库相关联，则必须在 GitHub 上创建一个，复制新仓库提供的 URL，并使用 `git remote add origin <URL>` 命令，这里使用 GitHub 提供的 URL 替换 `<URL>`。这样，你就可以添加、提交和推送更改到你的远程仓库了。

最后一条命令用在当你需要更改远程仓库时。如果你从其他人那里复制了一个仓库，并希望将远程仓库从原始所有者更改为你自己的 GitHub 帐户。除了改用 `set-url` 来更改远程仓库外，流程与 `git remote add origin` 相同。

```
git remote -v
git remote add origin <url>
git remote set-url origin <url>

```

复制仓库最常见的方式是使用 `git clone`，后跟仓库的 URL。

请记住，远程仓库将连接到克隆仓库原属于的帐户。所以，如果你克隆了一个属于别人的仓库，你将无法推送到 GitHub，除非你使用上面的命令改变了 `origin`。

```
git clone <url>

```

你很快就会发现自己正在使用分支。如果你还不理解什么是分支，有许多其他更深入的教程，你应该先阅读它们，再继续下面的操作。（[这里是一个教程](https://guides.github.com/introduction/flow/)）

命令 `git branch` 列出了本地机器上的所有分支。如果要创建一个新的分支，可以使用命令 `git branch <name>`，其中 `<name>` 表示分支的名字，比如说 `master`。

`git checkout <name>` 命令可以切换到现有的分支。你也可以使用 `git checkout -b` 命令创建一个新的分支并立即切换到它。大多数人都使用此命令而不是单独的 `branch` 和 `checkout` 命令。

```
git branch
git branch <name>
git checkout <name>
git checkout -b <name>

```

如果你对一个分支进行了一系列的更改，假如说此分支名为 `develop`，如果想要将该分支合并回主分支（`master`）上，则使用 `git merge <branch>` 命令。你需要先检出（`checkout`）主分支，然后运行 `git merge develop` 将 `develop` 合并到主分支中。

```
git merge <branch>

```

如果你正在与多个人进行协作，你会发现有时 GitHub 的仓库上已经更新了，但你的本地却没有做相应的更改。如果是这样，你可以使用 `git pull origin <branch>` 命令从远程分支中拉取最新的更改。

```
git pull origin <branch>

```

如果您好奇地想看到哪些文件已被更改以及哪些内存正在被跟踪，可以使用 `git status` 命令。如果要查看每个文件的更改，可以使用 `git diff` 来查看每个文件中更改的行。

```
git status
git diff --stat

```

### 高级命令和最佳实践

很快你会到达一个阶段，这时你希望你的提交看起来整洁一致。你可能还需要调整你的提交记录，使得提交更容易理解或者能还原一个意外的有破坏性的更改。

`git log` 命令可以输出提交的历史记录。你将使用它来查看提交的历史记录。

你的提交会附带消息和一个哈希值，哈希值是一串包含数字和字母的随机序列。一个哈希值示例如下：`c3d882aa1aa4e3d5f18b3890132670fbeac912f7`。

```
git log

```

假设你推送了一些可能破坏了你应用程序的东西。你最好回退一个提交然后再提交一次正确的，而不是修复它和推送新的东西。

如果你希望及时回退并从之前的提交中检出（`checkout`）你的应用程序，则可以使用该哈希作为分支名直接执行此操作。这将使你的应用程序与当前版本分离（因为你正在编辑历史记录的版本，而不是当前版本）。

```
git checkout c3d88eaa1aa4e4d5f

```

然后，如果你在那个历史分支中做了更改，并且想要再次推送，你必须使用强制推送。

**注意**：强制推送是危险的，只有在绝对必要的时候才能执行它。它将覆盖你的应用程序的历史记录，你将失去之后版本的任何信息。

```
git push -f origin master

```

在其他时候，将所有内容保留在一个提交中是不现实的。也行你想在尝试有潜在风险的操作之前保存当前进度，或者也许你犯了一个错误，但希望在你的版本历史中避免尴尬地留着这个错误。对此，我们有 `git rebase`。

假设你在本地历史记录上有 4 个提交（没有推送到 GitHub），你要回退这是个提交。你的提交记录看起来很乱很拖拉。这时你可以使用 `rebase` 将所有这些提交合并到一个简单的提交中。

```
git rebase -i HEAD~4

```

上面的命令会打开你计算机的默认编辑器（默认为 Vim，除非你将默认修改为其他的），提供了几个你准备如何修改你的提交的选项。它看起来就像下面的代码：

```
pick 130deo9 oldest commit message
pick 4209fei second oldest commit message
pick 4390gne third oldest commit message
pick bmo0dne newest commit message

```

为了合并这些提交，我们需要将 `pick` 选项修改为 `fixup`（如代码下面的文档所示），以将该提交合并并丢弃该提交消息。请注意，在 Vim 中，你需要按下 `a` 或 `i` 才能编辑文本，要保存退出，你需要按下 `Esc` 键，然后按 `shift + z + z`。不要问我为什么，它就是这样。

```
pick 130deo9 oldest commit message
fixup 4209fei second oldest commit message
fixup 4390gne third oldest commit message
fixup bmo0dne newest commit message

```

这将把你的所有提交合并到一个提交中，提交消息为 `oldest commit message`。

下一步是重命名你的提交消息。这完全是一个建议的操作，但只要你一直遵循一致的模式，都可以做得很好。这里我建议使用 [Google 为 Angular.js 提供的提交指南](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#-git-commit-guidelines)。

为了更改提交消息，请使用 `amend` 标志。

```
git commit --amend

```

这也会打开 Vim，文本编辑和保存规则如上所示。为了给出一个良好的提交消息的例子，下面是遵循该指南中规则的提交消息：

```
feat: add stripe checkout button to payments page

- add stripe checkout button
- write tests for checkout

```

保持指南中列出的<ruby> 类型 <rp>  （ </rp> <rt>  type </rt> <rp>  ） </rp></ruby>的一个优点是它使编写更改日志更加容易。你还可以在<ruby> 页脚 <rp>  （ </rp> <rt>  footer </rt> <rp>  ） </rp></ruby>（再次，在指南中规定的）中包含信息来引用<ruby> 问题 <rp>  （ </rp> <rt>  issue </rt> <rp>  ） </rp></ruby>。

**注意：**如果你正在协作一个项目，并将代码推送到了 GitHub，你应该避免重新引用（`rebase`）并压缩（`squash`）你的提交。如果你开始在人们的眼皮子底下更改版本历史，那么你可能会遇到难以追踪的错误，从而给每个人都带来麻烦。

Git 有无数的命令，但这里介绍的命令可能是您最初几年编程所需要知道的所有。

---

Sam Corcos 是 [Sightline Maps](http://sightlinemaps.com/) 的首席开发工程师和联合创始人，Sightline Maps 是最直观的 3D 打印地形图的平台，以及用于构建 Phoenix 和 React 的可扩展生产应用程序的中级高级教程网站 [LearnPhoenix.io](http://learnphoenix.io/)。使用优惠码：free*code*camp 取得 LearnPhoenix 的20美元。

（题图：[GitHub Octodex](https://octodex.github.com/)）

---

via: <https://medium.freecodecamp.org/git-cheat-sheet-and-best-practices-c6ce5321f52>

作者：[Sam Corcos](https://medium.freecodecamp.org/@SamCorcos?source=post_header_lockup) 译者：[firmianay](https://github.com/firmianay) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


使用 Ansible 部署无服务（serverless）应用

Summary: 无服务是托管服务发展方向的又一步，并且与 Ansible 的无代理体系结构相得益彰。

>
> <ruby> 无服务 <rt>  serverless </rt></ruby>是<ruby> 托管服务 <rt>  managed service </rt></ruby>发展方向的又一步，并且与 Ansible 的无代理体系结构相得益彰。
>
>
>

![](/data/attachment/album/201709/05/223609d2d6dxaevxo2466a.jpg)

[Ansible](https://www.ansible.com/) 被设计为实际工作中的最简化的部署工具。这意味着它不是一个完整的编程语言。你需要编写定义任务的 YAML 模板，并列出任何需要自动完成的任务。

大多数人认为 Ansible 是一种更强大的“处于 for 循环中的 SSH”，在简单的使用场景下这是真的。但其实 Ansible 是*任务*，而非 SSH。在很多情况下，我们通过 SSH 进行连接，但它也支持 Windows 机器上的 Windows 远程管理（WinRM），以及作为云服务的通用语言的 HTTPS API 之类的东西。

在云中，Ansible 可以在两个独立的层面上操作：<ruby> 控制面 <rt>  control plane </rt></ruby>和<ruby> 实例资源 <rt>  on-instance resource </rt></ruby>。控制面由所有*没有*运行在操作系统上的东西组成。包括设置网络、新建实例、供给更高级别的服务，如亚马逊的 S3 或 DynamoDB，以及保持云基础设施安全和服务客户所需的一切。

实例上的工作是你已经知道 Ansible 可以做的：启动和停止服务、配置文件<ruby> 模版化 <rt>  templating </rt></ruby>、安装软件包以及通过 SSH 执行的所有与操作系统相关的操作。

现在，什么是<ruby> <a href="https://en.wikipedia.org/wiki/Serverless_computing">  无服务 </a> <rt>  serverless </rt></ruby>呢？这要看你问谁，无服务要么是对公有云的无限延伸，或者是一个全新的范例，其中所有的东西都是 API 调用，以前从来没有这样做过。

Ansible 采取第一种观点。在 “无服务” 是专门术语之前，用户不得不管理和配置 EC2 实例、虚拟私有云 （VPC） 网络以及其他所有内容。无服务是托管服务方向迈出的另一步，并且与 Ansible 的无代理体系结构相得益彰。

在我们开始 [Lambda](https://aws.amazon.com/lambda/) 示例之前，让我们来看一个简单的配置 CloudFormation 栈任务：

```
- name: Build network
  cloudformation:
    stack_name: prod-vpc
    state: present
    template: base_vpc.yml
```

编写这样的任务只需要几分钟，但它是构建基础架构所涉及的最后的半手动步骤 - 点击 “Create Stack” - 这将 playbook 与其他放在一起。现在你的 VPC 只是在建立新区域时可以调用的另一项任务了。

由于云提供商是你帐户中发生些什么的真相来源，因此 Ansible 有许多方法来取回并使用 ID、名称和其他参数来过滤和查询运行的实例或网络。以 `cloudformation_facts` 模块为例，我们可以从我们刚刚创建的模板中得到子网 ID、网络范围和其他数据。

```
- name: Pull all new resources back in as a variable
  cloudformation_facts:
    stack_name: prod-vpc
  register: network_stack
```

对于无服务应用，除了 DynamoDB 表，S3 bucket 和其他任何其他功能之外，你肯定还需要一个 Lambda 函数的补充。幸运的是，通过使用 `lambda` 模块， Lambda 函数可以以上次任务的堆栈相同的方式创建：

```
- lambda:
    name: sendReportMail
    zip_file: "{{ deployment_package }}"
    runtime: python3.6
    handler: report.send
    memory_size: 1024
    role: "{{ iam_exec_role }}"
  register: new_function
```

如果你有其他想用来交付无服务应用的工具，这也是可以的。开源的[无服务框架](https://serverless.com/)有自己的 Ansible 模块，它也可以工作：

```
- serverless:
    service_path: '{{ project_dir }}'
    stage: dev
  register: sls
- name: Serverless uses CloudFormation under the hood, so you can easily pull info back into Ansible
  cloudformation_facts:
    stack_name: "{{ sls.service_name }}"
  register: sls_facts
```

这不是你需要的全部，因为无服务项目也必须存在，你将在那里大量的定义你的函数和事件源。对于此例，我们将制作一个响应 HTTP 请求的函数。无服务框架使用 YAML 作为其配置语言（和 Ansible 一样），所以这应该看起来很熟悉。

```
# serverless.yml
service: fakeservice

provider:
  name: aws
  runtime: python3.6

functions:
  main:
    handler: test_function.handler
    events:
      - http:
          path: /
          method: get
```

在 [AnsibleFest](https://www.ansible.com/ansiblefest?intcmp=701f2000000h4RcAAI) 中，我将介绍这个例子和其他深入的部署策略，以最大限度地利用你已经拥有的 playbook 和基础设施，还有新的无服务实践。无论你是否能到，我希望这些例子可以让你开始使用 Ansible，无论你是否有任何服务要管理。

*AnsibleFest 是一个单日会议，汇集了数百名 Ansible 用户、开发人员和行业合作伙伴。加入我们吧，这里有产品更新、鼓舞人心的交谈、技术深度潜水，动手演示和整天的网络。*

（题图： opensource.com）

---

via: <https://opensource.com/article/17/8/ansible-serverless-applications>

作者：[Ryan Scott Brown](https://opensource.com/users/ryansb) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


我对 Go 的错误处理有哪些不满，以及我是如何处理的

其实习惯也就好了，go主张的思维是尽快处理错误，当有错误是立马处理，或者返回给上级处理

错误处理的确很纠结

Go 错误处理的样板代码既视感确实很强。

![](/data/attachment/album/201709/06/093641llo033walz5k70pk.png)

写 Go 的人往往对它的错误处理模式有一定的看法。按不同的语言经验，人们可能有不同的习惯处理方法。这就是为什么我决定要写这篇文章，尽管有点固执己见，但我认为听取我的经验是有用的。我想要讲的主要问题是，很难去强制执行良好的错误处理实践，错误经常没有堆栈追踪，并且错误处理本身太冗长。不过，我已经看到了一些潜在的解决方案，或许能帮助解决一些问题。

### 与其他语言的快速比较

[在 Go 中，所有的错误都是值](https://blog.golang.org/errors-are-values)。因为这点，相当多的函数最后会返回一个 `error`, 看起来像这样：

```
func (s *SomeStruct) Function() (string, error)

```

因此这导致调用代码通常会使用 `if` 语句来检查它们：

```
bytes, err := someStruct.Function()
if err != nil {
  // Process error
}

```

另外一种方法，是在其他语言中，如 Java、C#、Javascript、Objective C、Python 等使用的 `try-catch` 模式。如下你可以看到与先前的 Go 示例类似的 Java 代码，声明 `throws` 而不是返回 `error`：

```
public String function() throws Exception

```

它使用的是 `try-catch` 而不是 `if err != nil`：

```
try {
  String result = someObject.function()
  // continue logic
}
catch (Exception e) {
  // process exception
}

```

当然，还有其他的不同。例如，`error` 不会使你的程序崩溃，然而 `Exception` 会。还有其他的一些，在本篇中会专门提到这些。

### 实现集中式错误处理

退一步，让我们看看为什么要在一个集中的地方处理错误，以及如何做到。

大多数人或许会熟悉的一个例子是 web 服务 - 如果出现了一些未预料的的服务端错误，我们会生成一个 5xx 错误。在 Go 中，你或许会这么实现：

```
func init() {
    http.HandleFunc("/users", viewUsers)
    http.HandleFunc("/companies", viewCompanies)
}

func viewUsers(w http.ResponseWriter, r *http.Request) {
    user // some code
    if err := userTemplate.Execute(w, user); err != nil {
        http.Error(w, err.Error(), 500)
    }
}

func viewCompanies(w http.ResponseWriter, r *http.Request) {
    companies = // some code
    if err := companiesTemplate.Execute(w, companies); err != nil {
        http.Error(w, err.Error(), 500)
    }
}

```

这并不是一个好的解决方案，因为我们不得不重复地在所有的处理函数中处理错误。为了能更好地维护，最好能在一处地方处理错误。幸运的是，[在 Go 语言的官方博客中，Andrew Gerrand 提供了一个替代方法](https://blog.golang.org/error-handling-and-go)，可以完美地实现。我们可以创建一个处理错误的 Type：

```
type appHandler func(http.ResponseWriter, *http.Request) error

func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if err := fn(w, r); err != nil {
        http.Error(w, err.Error(), 500)
    }
}

```

这可以作为一个封装器来修饰我们的处理函数：

```
func init() {
    http.Handle("/users", appHandler(viewUsers))
    http.Handle("/companies", appHandler(viewCompanies))
}

```

接着我们需要做的是修改处理函数的签名来使它们返回 `errors`。这个方法很好，因为我们做到了 [DRY](https://en.wikipedia.org/wiki/Don't_repeat_yourself) 原则，并且没有重复使用不必要的代码 - 现在我们可以在单独一个地方返回默认错误了。

### 错误上下文

在先前的例子中，我们可能会收到许多潜在的错误，它们中的任何一个都可能在调用堆栈的许多环节中生成。这时候事情就变得棘手了。

为了演示这点，我们可以扩展我们的处理函数。它可能看上去像这样，因为模板执行并不是唯一一处会发生错误的地方：

```
func viewUsers(w http.ResponseWriter, r *http.Request) error {
    user, err := findUser(r.formValue("id"))
    if err != nil {
      return err;
    }
    return userTemplate.Execute(w, user);
}

```

调用链可能会相当深，在整个过程中，各种错误可能在不同的地方实例化。[Russ Cox](https://research.swtch.com/go2017)的这篇文章解释了如何避免遇到太多这类问题的最佳实践：

>
> “在 Go 中错误报告的部分约定是函数包含相关的上下文，包括正在尝试的操作（比如函数名和它的参数）。”
>
>
>

这个给出的例子是对 OS 包的一个调用：

```
err := os.Remove("/tmp/nonexist")
fmt.Println(err)

```

它会输出：

```
remove /tmp/nonexist: no such file or directory

```

总结一下，执行后，输出的是被调用的函数、给定的参数、特定的出错信息。当在其他语言中创建一个 `Exception` 消息时，你也可以遵循这个实践。如果我们在 `viewUsers` 处理中坚持这点，那么几乎总是能明确错误的原因。

问题来自于那些不遵循这个最佳实践的人，并且你经常会在第三方的 Go 库中看到这些消息：

```
Oh no I broke

```

这没什么帮助 - 你无法了解上下文，这使得调试很困难。更糟糕的是，当这些错误被忽略或返回时，这些错误会被备份到堆栈中，直到它们被处理为止：

```
if err != nil {
  return err
}

```

这意味着错误何时发生并没有被传递出来。

应该注意的是，所有这些错误都可以在 `Exception` 驱动的模型中发生 - 糟糕的错误信息、隐藏异常等。那么为什么我认为该模型更有用？

即便我们在处理一个糟糕的异常消息，*我们仍然能够了解它发生在调用堆栈中什么地方*。因为堆栈跟踪，这引发了一些我对 Go 不了解的部分 - 你知道 Go 的 `panic` 包含了堆栈追踪，但是 `error` 没有。我推测可能是 `panic` 会使你的程序崩溃，因此需要一个堆栈追踪，而处理错误并不会，因为它会假定你在它发生的地方做一些事。

所以让我们回到之前的例子 - 一个有糟糕错误信息的第三方库，它只是输出了调用链。你认为调试会更容易吗？

```
panic: Oh no I broke
[signal 0xb code=0x1 addr=0x0 pc=0xfc90f]

goroutine 1103 [running]:
panic(0x4bed00, 0xc82000c0b0)
/usr/local/go/src/runtime/panic.go:481 +0x3e6
github.com/Org/app/core.(_app).captureRequest(0xc820163340, 0x0, 0x55bd50, 0x0, 0x0)
/home/ubuntu/.go_workspace/src/github.com/Org/App/core/main.go:313 +0x12cf
github.com/Org/app/core.(_app).processRequest(0xc820163340, 0xc82064e1c0, 0xc82002aab8, 0x1)
/home/ubuntu/.go_workspace/src/github.com/Org/App/core/main.go:203 +0xb6
github.com/Org/app/core.NewProxy.func2(0xc82064e1c0, 0xc820bb2000, 0xc820bb2000, 0x1)
/home/ubuntu/.go_workspace/src/github.com/Org/App/core/proxy.go:51 +0x2a
github.com/Org/app/core/vendor/github.com/rusenask/goproxy.FuncReqHandler.Handle(0xc820da36e0, 0xc82064e1c0, 0xc820bb2000, 0xc5001, 0xc820b4a0a0)
/home/ubuntu/.go_workspace/src/github.com/Org/app/core/vendor/github.com/rusenask/goproxy/actions.go:19 +0x30

```

我认为这可能是 Go 的设计中被忽略的东西 - 不是所有语言都不会忽视的。

如果我们使用 Java 作为一个随意的例子，其中人们犯的一个最愚蠢的错误是不记录堆栈追踪：

```
LOGGER.error(ex.getMessage()) // 不记录堆栈追踪
LOGGER.error(ex.getMessage(), ex) // 记录堆栈追踪

```

但是 Go 似乎在设计中就没有这个信息。

在获取上下文信息方面 - Russ 还提到了社区正在讨论一些潜在的接口用于剥离上下文错误。关于这点，了解更多或许会很有趣。

### 堆栈追踪问题解决方案

幸运的是，在做了一些查找后，我发现了这个出色的 [Go 错误](https://github.com/go-errors/errors)库来帮助解决这个问题，来给错误添加堆栈跟踪：

```
if errors.Is(err, crashy.Crashed) {
  fmt.Println(err.(*errors.Error).ErrorStack())
}

```

不过，我认为这个功能如果能成为语言的<ruby> 第一类公民 <rt>  first class citizenship </rt></ruby>将是一个改进，这样你就不必做一些类型修改了。此外，如果我们像先前的例子那样使用第三方库，它可能没有使用 `crashy` - 我们仍有相同的问题。

### 我们对错误应该做什么？

我们还必须考虑发生错误时应该发生什么。[这一定有用，它们不会让你的程序崩溃](https://davidnix.io/post/error-handling-in-go/)，通常也会立即处理它们：

```
err := method()
if err != nil {
  // some logic that I must do now in the event of an error!
}

```

如果我们想要调用大量方法，它们会产生错误，然后在一个地方处理所有错误，这时会发生什么？看上去像这样：

```
err := doSomething()
if err != nil {
  // handle the error here
}

func doSomething() error {
  err := someMethod()
  if err != nil {
    return err
  }
  err = someOther()
  if err != nil {
    return err
  }
  someOtherMethod()
}

```

这感觉有点冗余，在其他语言中你可以将多条语句作为一个整体处理。

```
try {
  someMethod()
  someOther()
  someOtherMethod()
}
catch (Exception e) {
  // process exception
}

```

或者只要在方法签名中传递错误：

```
public void doSomething() throws SomeErrorToPropogate {
  someMethod()
  someOther()
  someOtherMethod()
}

```

我个人认为这两个例子实现了一件事情，只是 `Exception` 模式更少冗余，更加弹性。如果有什么的话，我觉得 `if err！= nil` 感觉像样板。也许有一种方法可以清理？

### 将失败的多条语句做为一个整体处理错误

首先，我做了更多的阅读，并[在 Rob Pike 写的 Go 博客中](https://blog.golang.org/errors-are-values)发现了一个比较务实的解决方案。

他定义了一个封装了错误的方法的结构体：

```
type errWriter struct {
    w   io.Writer
    err error
}

func (ew *errWriter) write(buf []byte) {
    if ew.err != nil {
        return
    }
    _, ew.err = ew.w.Write(buf)
}

```

让我们这么做：

```
ew := &errWriter{w: fd}
ew.write(p0[a:b])
ew.write(p1[c:d])
ew.write(p2[e:f])
// and so on
if ew.err != nil {
    return ew.err
}

```

这也是一个很好的方案，但是我感觉缺少了点什么 - 因为我们不能重复使用这个模式。如果我们想要一个含有字符串参数的方法，我们就不得不改变函数签名。或者如果我们不想执行写操作会怎样？我们可以尝试使它更通用：

```
type errWrapper struct {
    err error
}

```

```
func (ew *errWrapper) do(f func() error) {
    if ew.err != nil {
        return
    }
    ew.err = f();
}

```

但是我们有一个相同的问题，如果我们想要调用含有不同参数的函数，它就无法编译了。然而你可以简单地封装这些函数调用：

```
w := &errWrapper{}

w.do(func() error {
    return someFunction(1, 2);
})

w.do(func() error {
    return otherFunction("foo");
})

err := w.err

if err != nil {
// process error here
}

```

这可以用，但是并没有太大帮助，因为它最终比标准的 `if err != nil` 检查带来了更多的冗余。如果有人能提供其他解决方案，我会很有兴趣听。或许这个语言本身需要一些方法来以不那么臃肿的方式传递或者组合错误 - 但是感觉似乎是特意设计成不那么做。

### 总结

看完这些之后，你可能会认为我在对 `error` 挑刺儿，由此推论我反对 Go。事实并非如此，我只是将它与我使用 `try catch` 模型的经验进行比较。它是一个用于系统编程很好的语言，并且已经出现了一些优秀的工具。仅举几例，有 [Kubernetes](https://kubernetes.io/)、[Docker](https://www.docker.com/)、[Terraform](https://www.terraform.io/)、[Hoverfly](http://hoverfly.io/en/latest/) 等。还有小型、高性能、本地二进制的优点。但是，`error` 难以适应。 我希望我的推论是有道理的，而且一些方案和解决方法可能会有帮助。

---

作者简介：

Andrew 是 OpenCredo 的顾问，于 2015 年加入公司。Andrew 在多个行业工作多年，开发基于 Web 的企业应用程序。

---

via: <https://opencredo.com/why-i-dont-like-error-handling-in-go>

作者：[Andrew Morgan](https://opencredo.com/author/andrew/) 译者：[geekpi](https://github.com/geekpi) 校对：[jasminepeng](https://github.com/jasminepeng)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


编译器简介： 在 Siri 前时代如何与计算机对话

Summary: 简单说来，一个编译器（compiler）不过是一个可以翻译其他程序的程序。传统的编译器可以把源代码翻译成你的计算机能够理解的可执行机器代码。

![](/data/attachment/album/201709/07/093809kb68i4bnllnnsqyg.jpg)

简单说来，一个<ruby> 编译器 <rt>  compiler </rt></ruby>不过是一个可以翻译其他程序的程序。传统的编译器可以把源代码翻译成你的计算机能够理解的可执行机器代码。（一些编译器将源代码翻译成别的程序语言，这样的编译器称为源到源翻译器或<ruby> 转化器 <rt>  transpilers </rt></ruby>。）[LLVM](http://llvm.org/) 是一个广泛使用的编译器项目，包含许多模块化的编译工具。

传统的编译器设计包含三个部分：

![](/data/attachment/album/201709/07/094151bai6rw6iavwe3aaa.jpg)

* <ruby> 前端 <rt>  Frontend </rt></ruby>将源代码翻译为<ruby> 中间表示 <rt>  intermediate representation </rt></ruby> (IR)\* 。[clang](http://clang.llvm.org/) 是 LLVM 中用于 C 家族语言的前端工具。
* <ruby> 优化器 <rt>  Optimizer </rt></ruby>分析 IR 然后将其转化为更高效的形式。[opt](http://llvm.org/docs/CommandGuide/opt.html) 是 LLVM 的优化工具。
* <ruby> 后端 <rt>  Backend </rt></ruby>通过将 IR 映射到目标硬件指令集从而生成机器代码。[llc](http://llvm.org/docs/CommandGuide/llc.html) 是 LLVM 的后端工具。

注：LLVM 的 IR 是一种和汇编类似的低级语言。然而，它抽离了特定硬件信息。

### Hello, Compiler

下面是一个打印 “Hello, Compiler!” 到标准输出的简单 C 程序。C 语法是人类可读的，但是计算机却不能理解，不知道该程序要干什么。我将通过三个编译阶段使该程序变成机器可执行的程序。

```
// compile_me.c
// Wave to the compiler. The world can wait.

#include <stdio.h>

int main() {
  printf("Hello, Compiler!\n");
  return 0;
}

```

### 前端

正如我在上面所提到的，`clang` 是 LLVM 中用于 C 家族语言的前端工具。Clang 包含 <ruby> C 预处理器 <rt>  C preprocessor </rt></ruby>、<ruby> 词法分析器 <rt>  lexer </rt></ruby>、<ruby> 语法解析器 <rt>  parser </rt></ruby>、<ruby> 语义分析器 <rt>  semantic analyzer </rt></ruby>和 <ruby> IR 生成器 <rt>  IR generator </rt></ruby>。

**C 预处理器**在将源程序翻译成 IR 前修改源程序。预处理器处理外部包含文件，比如上面的 `#include <stdio.h>`。 它将会把这一行替换为 `stdio.h` C 标准库文件的完整内容，其中包含 `printf` 函数的声明。

通过运行下面的命令来查看预处理步骤的输出：

```
clang -E compile_me.c -o preprocessed.i

```

**词法分析器**（或<ruby> 扫描器 <rt>  scanner </rt></ruby>或<ruby> 分词器 <rt>  tokenizer </rt></ruby>）将一串字符转化为一串单词。每一个单词或<ruby> 记号 <rt>  token </rt></ruby>，被归并到五种语法类别之一：标点符号、关键字、标识符、文字或注释。

compile\_me.c 的分词过程：

![](/data/attachment/album/201709/07/094152i316a2sk6sw6tdkw.jpg)

**语法分析器**确定源程序中的单词流是否组成了合法的句子。在分析记号流的语法后，它会输出一个<ruby> 抽象语法树 <rt>  abstract syntax tree </rt></ruby>（AST）。Clang 的 AST 中的节点表示声明、语句和类型。

compile\_me.c 的语法树：

![](/data/attachment/album/201709/07/094153uf3lyzly5f3j3wf3.jpg)

**语义分析器**会遍历抽象语法树，从而确定代码语句是否有正确意义。这个阶段会检查类型错误。如果 `compile_me.c` 的 main 函数返回 `"zero"`而不是 `0`， 那么语义分析器将会抛出一个错误，因为 `"zero"` 不是 `int` 类型。

**IR 生成器**将抽象语法树翻译为 IR。

对 compile\_me.c 运行 clang 来生成 LLVM IR：

```
clang -S -emit-llvm -o llvm_ir.ll compile_me.c

```

在 `llvm_ir.ll` 中的 main 函数：

```
; llvm_ir.ll
@.str = private unnamed_addr constant [18 x i8] c"Hello, Compiler!\0A\00", align 1

define i32 @main() {
  %1 = alloca i32, align 4 ; <- memory allocated on the stack
  store i32 0, i32* %1, align 4
  %2 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([18 x i8], [18 x i8]* @.str, i32 0, i32 0))
  ret i32 0
}

declare i32 @printf(i8*, ...)

```

### 优化程序

优化程序的工作是基于其对程序的运行时行为的理解来提高代码效率。优化程序将 IR 作为输入，然后生成改进后的 IR 作为输出。LLVM 的优化工具 `opt` 将会通过标记 `-O2`（大写字母 `o`，数字 2）来优化处理器速度，通过标记 `Os`（大写字母 `o`，小写字母 `s`）来减少指令数目。

看一看上面的前端工具生成的 LLVM IR 代码和运行下面的命令生成的结果之间的区别：

```
opt -O2 -S llvm_ir.ll -o optimized.ll

```

在 `optimized.ll` 中的 main 函数：

```
optimized.ll

@str = private unnamed_addr constant [17 x i8] c"Hello, Compiler!\00"

define i32 @main() {
  %puts = tail call i32 @puts(i8* getelementptr inbounds ([17 x i8], [17 x i8]* @str, i64 0, i64 0))
  ret i32 0
}

declare i32 @puts(i8* nocapture readonly)

```

优化后的版本中， main 函数没有在栈中分配内存，因为它不使用任何内存。优化后的代码中调用 `puts` 函数而不是 `printf` 函数，因为程序中并没有使用 `printf` 函数的格式化功能。

当然，优化程序不仅仅知道何时可以把 `printf` 函数用 `puts` 函数代替。优化程序也能展开循环并内联简单计算的结果。考虑下面的程序，它将两个整数相加并打印出结果。

```
// add.c
#include <stdio.h>

int main() {
  int a = 5, b = 10, c = a + b;
  printf("%i + %i = %i\n", a, b, c);
}

```

下面是未优化的 LLVM IR：

```
@.str = private unnamed_addr constant [14 x i8] c"%i + %i = %i\0A\00", align 1

define i32 @main() {
  %1 = alloca i32, align 4 ; <- allocate stack space for var a
  %2 = alloca i32, align 4 ; <- allocate stack space for var b
  %3 = alloca i32, align 4 ; <- allocate stack space for var c
  store i32 5, i32* %1, align 4  ; <- store 5 at memory location %1
  store i32 10, i32* %2, align 4 ; <- store 10 at memory location %2
  %4 = load i32, i32* %1, align 4 ; <- load the value at memory address %1 into register %4
  %5 = load i32, i32* %2, align 4 ; <- load the value at memory address %2 into register %5
  %6 = add nsw i32 %4, %5 ; <- add the values in registers %4 and %5\. put the result in register %6
  store i32 %6, i32* %3, align 4 ; <- put the value of register %6 into memory address %3
  %7 = load i32, i32* %1, align 4 ; <- load the value at memory address %1 into register %7
  %8 = load i32, i32* %2, align 4 ; <- load the value at memory address %2 into register %8
  %9 = load i32, i32* %3, align 4 ; <- load the value at memory address %3 into register %9
  %10 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([14 x i8], [14 x i8]* @.str, i32 0, i32 0), i32 %7, i32 %8, i32 %9)
  ret i32 0
}

declare i32 @printf(i8*, ...)

```

下面是优化后的 LLVM IR：

```
@.str = private unnamed_addr constant [14 x i8] c"%i + %i = %i\0A\00", align 1

define i32 @main() {
  %1 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([14 x i8], [14 x i8]* @.str, i64 0, i64 0), i32 5, i32 10, i32 15)
  ret i32 0
}

declare i32 @printf(i8* nocapture readonly, ...)

```

优化后的 main 函数本质上是未优化版本的第 17 行和 18 行，伴有变量值内联。`opt` 计算加法，因为所有的变量都是常数。很酷吧，对不对？

### 后端

LLVM 的后端工具是 `llc`。它分三个阶段将 LLVM IR 作为输入生成机器代码。

* **指令选择**是将 IR 指令映射到目标机器的指令集。这个步骤使用虚拟寄存器的无限名字空间。
* **寄存器分配**是将虚拟寄存器映射到目标体系结构的实际寄存器。我的 CPU 是 x86 结构，它只有 16 个寄存器。然而，编译器将会尽可能少的使用寄存器。
* **指令安排**是重排操作，从而反映出目标机器的性能约束。

运行下面这个命令将会产生一些机器代码：

```
llc -o compiled-assembly.s optimized.ll

```

```
_main:
    pushq   %rbp
    movq    %rsp, %rbp
    leaq    L_str(%rip), %rdi
    callq   _puts
    xorl    %eax, %eax
    popq    %rbp
    retq
L_str:
    .asciz  "Hello, Compiler!"

```

这个程序是 x86 汇编语言，它是计算机所说的语言，并具有人类可读语法。某些人最后也许能理解我。

![](/data/attachment/album/201709/07/094231ubzkqc00b7bsv2eu.jpg)

---

相关资源：

1. [设计一个编译器](https://www.amazon.com/Engineering-Compiler-Second-Keith-Cooper/dp/012088478X)
2. [开始探索 LLVM 核心库](https://www.amazon.com/Getting-Started-LLVM-Core-Libraries/dp/1782166920)

（题图：deviantart.net）

---

via: <https://nicoleorchard.com/blog/compilers>

作者：[Nicole Orchard](https://nicoleorchard.com/) 译者：[ucasFL](https://github.com/ucasFL) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


Minikube：使用 Kubernetes 进行本地开发

Summary: 这篇博客文章介绍了 Kubernetes 在开发模式中的用法，它的灵感来自于一个视频教程，你可以在“无痛 Docker 教程”中找到它。

我觉得还行。

如果你的运维团队在使用 Docker 和 Kubernetes，那么建议开发上采用相同或相似的技术。这将减少不兼容性和可移植性问题的数量，并使每个人都会认识到应用程序容器是开发和运维团队的共同责任。

![](/data/attachment/album/201709/07/114634gfl4anlnlnnqh6wx.jpg)

这篇博客文章介绍了 Kubernetes 在开发模式中的用法，它的灵感来自于一个视频教程，你可以在“[无痛 Docker 教程](http://painlessdocker.com/)”中找到它。

![](/data/attachment/album/201709/07/114634vqju644r19iv94j4.jpg)

Minikube 是一个允许开发人员在本地使用和运行 Kubernetes 集群的工具，从而使开发人员的生活变得轻松。

在这篇博客中，对于我测试的例子，我使用的是 Linux Mint 18，但其它 Linux 发行版在安装部分没有区别。

```
cat /etc/lsb-release

```

```
DISTRIB_ID=LinuxMint
DISTRIB_RELEASE=18.1
DISTRIB_CODENAME=serena
DISTRIB_DESCRIPTION=”Linux Mint 18.1 Serena”

```

![](/data/attachment/album/201709/07/114635xsd4mmt2md5zp8g9.png)

### 先决条件

为了与 Minkube 一起工作，我们应该安装 Kubectl 和 Minikube 和一些虚拟化驱动程序。

* 对于 OS X，安装 [xhyve 驱动](https://git.k8s.io/minikube/docs/drivers.md#xhyve-driver)、[VirtualBox](https://www.virtualbox.org/wiki/Downloads) 或者 [VMware Fusion](https://www.vmware.com/products/fusion)，然后再安装 Kubectl 和 Minkube。

```
curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl

chmod +x ./kubectl

sudo mv ./kubectl /usr/local/bin/kubectl

curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.21.0/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/

```
* 对于 Windows，安装 [VirtualBox](https://www.virtualbox.org/wiki/Downloads) 或者 [Hyper-V](https://msdn.microsoft.com/en-us/virtualization/hyperv_on_windows/quick_start/walkthrough_install)，然后再安装 Kubectl 和 Minkube。

```
curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.7.0/bin/windows/amd64/kubectl.exe

```

将二进制文件添加到你的 PATH 中（这篇[文章](https://www.windows-commandline.com/set-path-command-line/)解释了如何修改 PATH）

下载 `minikube-windows-amd64.exe`，将其重命名为 `minikube.exe`，并将其添加到你的 PATH 中。[在这](https://github.com/kubernetes/minikube/releases)可以找到最新版本。
* 对于 Linux，安装 [VirtualBox](https://www.virtualbox.org/wiki/Downloads) 或者 [KVM](http://www.linux-kvm.org/)，然后再安装 Kubectl 和 Minkube。

```
curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl

chmod +x ./kubectl

sudo mv ./kubectl /usr/local/bin/kubectl

curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.21.0/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/

```

### 使用 Minikube

我们先从这个 Dockerfile 创建一个镜像：

```
FROM busybox
ADD index.html /www/index.html
EXPOSE 8000
CMD httpd -p 8000 -h /www; tail -f /dev/null

```

添加你希望在 index.html 中看到的内容。

构建镜像：

```
docker build -t eon01/hello-world-web-server .

```

我们来运行容器来测试它：

```
docker run -d --name webserver -p 8000:8000 eon01/hello-world-web-server

```

这是 `docker ps` 的输出：

```
docker ps

CONTAINER ID        IMAGE                          COMMAND                  CREATED             STATUS              PORTS                    NAMES
2ad8d688d812        eon01/hello-world-web-server   "/bin/sh -c 'httpd..."   3 seconds ago       Up 2 seconds        0.0.0.0:8000->8000/tcp   webserver

```

让我们提交镜像并将其上传到公共 Docker Hub 中。你也可以使用自己的私有仓库：

```
docker commit webserver
docker push eon01/hello-world-web-server

```

删除容器，因为我们将与 Minikube 一起使用它。

```
docker rm -f webserver

```

启动 Minikube：

```
minkube start

```

检查状态：

```
minikube status

```

我们运行一个单一节点：

```
kubectl get node

```

运行 webserver：

```
kubectl run webserver --image=eon01/hello-world-web-server --port=8000

```

webserver 应该会暴露它的端口：

```
kubectl expose deployment webserver --type=NodePort

```

为了得到服务 url 输入：

```
minikube service webserver --url

```

使用下面的命令得到 Web 页面的内容：

```
curl $(minikube service webserver --url)

```

显示运行中集群的摘要：

```
kubectl cluster-info

```

更多细节：

```
kubectl cluster-info dump

```

我们还可以使用以下方式列出 pod：

```
kubectl get pods

```

使用下面的方式访问面板：

```
minikube dashboard

```

如果你想访问 Web 程序的前端，输入：

```
kubectl proxy

```

如果我们要在容器内部执行一个命令，请使用以下命令获取 pod id：

```
kubetctl get pods

```

然后像这样使用：

```
kubectl exec webserver-2022867364-0v1p9 -it -- /bin/sh

```

最后完成了，请删除所有部署：

```
kubectl delete deployments --all

```

删除所有 pod：

```
kubectl delete pods --all

```

并且停止 Minikube。

```
minikube stop

```

我希望你享受这个介绍。

### 更加深入

如果你对本文感到共鸣，您可以在[无痛 Docker 教程](http://painlessdocker.com/)中找到更多有趣的内容。

我们 [Eralabs](http://eralabs.io/) 将很乐意为你的 Docker 和云计算项目提供帮助，[联系我们](http://eralabs.io/)，我们将很乐意听到你的项目。

请订阅 [DevOpsLinks](http://devopslinks.com/)：成千上万的 IT 专家和 DevOps 爱好者在线社区。

你可能也有兴趣加入我们的新闻订阅 [Shipped](http://shipped.devopslinks.com/)，一个专注于容器，编排和无服务技术的新闻订阅。

你可以在 [Twitter](https://twitter.com/eon01)、[Clarity](https://clarity.fm/aymenelamri/) 或我的[网站](http://aymenelamri.com/)上找到我，你也可以看看我的书：[SaltStack For DevOps](http://saltstackfordevops.com/)。

不要忘记加入我的最后一个项目 [DevOps 的职位](http://jobsfordevops.com/)！

如果你喜欢本文，请推荐它，并与你的关注者分享。

---

作者简介：

Aymen El Amri - 云和软件架构师、企业家、作者、www.eralabs.io 的 CEO、www.devopslinks.com 的创始人，个人页面：www.aymenelamri.com

---

via: <https://medium.com/devopslinks/using-kubernetes-minikube-for-local-development-c37c6e56e3db>

作者：[Aymen El Amri](https://medium.com/@eon01) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


一个开源软件许可证合规的经济高效模式

Summary: 使用开源的方式有利于你的盈亏底线以及开源生态系统。

使用开源的方式有利于你的盈亏底线以及开源生态系统。

>
> 使用开源的方式有利于你的盈亏底线以及开源生态系统。
>
>
>

![An economically efficient model for open source software license compliance](/data/attachment/album/201709/07/124533d9gmnsrc0m9pij9c.png "An economically efficient model for open source software license compliance")

“<ruby> 合规性工业联合体 <rt>  The Compliance Industrial Complex </rt></ruby>” 是一个术语，它会唤起那些组织参与精心设计并且花费昂贵流程的以遵守开源许可条款的反乌托邦想象。由于“生活经常模仿艺术”，许多组织采用了这种做法，可惜的是它们剥夺了许多开源模型的好处。本文介绍了一种经济高效的开源软件许可证合规性方法。

开源许可证通常对从第三方授权的代码分发者有三个要求：

1. 提供开源许可证的副本
2. 包括版权声明
3. 对于 copyleft 许可证（如 GPL），将相应的源代码提供给接受者。

*（与任何一般性声明一样，可能会有例外情况，因此始终建议审查许可条款，如有需要，请咨询律师的意见。）*

因为源代码（以及任何相关的文件，例如：许可证、README）通常都包含所有这些信息，所以最简单的遵循方法就是随着二进制/可执行程序一起提供源代码。

替代方案更加困难并且昂贵，因为在大多数情况下，你仍然需要提供开源许可证的副本并保留版权声明。提取这些信息来结合你的二进制/可执行版本并不简单。你需要流程、系统和人员来从源代码和相关文件中复制此信息，并将其插入到单独的文本文件或文档中。

不要低估创建此文件的时间和费用。虽然有工具也许可以自动化部分流程，但这些工具通常需要人力资源（例如工程师、质量经理、发布经理）来准备代码来扫描并对结果进行评估（没有完美的工具，几乎总是需要审查）。你的组织资源有限，将其转移到此活动会增加机会成本。考虑到这笔费用，每个后续版本（主要或次要）的成本将需要进行新的分析和修订。

也有因不选择发布不能被很好识别的源码而导致增加的其他成本。这些根源在于不向开源项目的原始作者和/或维护者发布源代码, 这一活动称为上游化。独自上游化一般不满足大多数开源许可证的要求，这就是为什么这篇文章主张与你的二进制/可执行文件一起发布源代码。然而，上游化和提供源代码以及二进制/可执行文件都能提供额外的经济效益。这是因为你的组织不再需要保留随着每次发布合并开源代码修改而产生的私有分支 - 由于你的内部代码库与社区项目不同，这将是越来越消耗和凌乱的工作。上游化还增强了开源生态系统，它会鼓励社区创新，从中你的组织或许也会得到收益。

那么为什么大量的组织不会为其产品发布源代码来简化其合规性工作？在许多情况下，这是因为他们认为这可能会暴露他们竞争优势的信息。考虑到这些专有产品中的大量代码可能是开源代码的直接副本，以支持诸如 WiFi 或云服务这些当代产品的基础功能，这种信念可能是错误的。

即使对这些开源作品进行了修改来适配其专有产品，这些更改也往往是微不足道的，并包含了很少的新的版权部分或可用来专利的内容。因此，任何组织都应该通过这种方式来查看其代码，因为它可能会发现其代码库中绝大部分是开源的，只有一小部分是真正专有的、与竞争对手区分开来的部分。那么为什么不分发和向上游提交这些没有差别的代码呢？

考虑一下拒绝遵从工业联合体的思维方式, 以降低成本并大大简化合规性。使用开源的方式，并体验发布你的源代码的乐趣，以造福于你的盈亏底线和开源生态系统，从中你将继续收获更多的利益。

---

作者简介

Jeffrey Robert Kaufman - Jeffrey R. Kaufman 是全球领先的开源软件解决方案提供商红帽公司的开源知识产权律师。Jeffrey 还担任着 Thomas Jefferson 法学院的兼职教授。 在加入红帽前，Jeffrey 在高通担任专利法律顾问，为首席科学家办公室提供开源顾问。 Jeffrey 在 RFID、条形码、图像处理和打印技术方面拥有多项专利。[更多关于我](https://opensource.com/users/jkaufman)

（题图： opensource.com）

---

via: <https://opensource.com/article/17/9/economically-efficient-model>

作者：[Jeffrey Robert Kaufman](https://opensource.com/users/jkaufman) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


Headless Chrome 入门

Summary: 在 Chrome 59　中开始搭载 Headless Chrome。这是一种在无需显示的环境下运行 Chrome 浏览器的方式。从本质上来说，就是不用 chrome 浏览器来运行 Chrome 的功能！它将 Chromium 和 Blink 渲染引擎提供的所有现代 Web 平台的功能都带入了命令行。

![](/data/attachment/album/201709/08/113337g9jjamqsizxmie3s.png)

### 摘要

在 Chrome 59　中开始搭载 [Headless Chrome](https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md)。这是一种在<ruby> 无需显示 <rt>  headless </rt></ruby>的环境下运行 Chrome 浏览器的方式。从本质上来说，就是不用 chrome 浏览器来运行 Chrome 的功能！它将 Chromium 和 Blink 渲染引擎提供的所有现代 Web 平台的功能都带入了命令行。

它有什么用？

<ruby> 无需显示 <rt>  headless </rt></ruby>的浏览器对于自动化测试和不需要可视化 UI 界面的服务器环境是一个很好的工具。例如，你可能需要对真实的网页运行一些测试，创建一个 PDF，或者只是检查浏览器如何呈现 URL。

>
> **注意：** Mac 和 Linux 上的 Chrome 59 都可以运行无需显示模式。[对 Windows 的支持](https://bugs.chromium.org/p/chromium/issues/detail?id=686608)将在 Chrome 60 中提供。要检查你使用的 Chrome 版本，请在浏览器中打开 `chrome://version`。
>
>
>

### 开启<ruby> 无需显示 <rt>  headless </rt></ruby>模式（命令行界面）

开启<ruby> 无需显示 <rt>  headless </rt></ruby>模式最简单的方法是从命令行打开 Chrome 二进制文件。如果你已经安装了 Chrome 59 以上的版本，请使用 `--headless` 标志启动 Chrome：

```
chrome \
  --headless \                   # Runs Chrome in headless mode.
  --disable-gpu \                # Temporarily needed for now.
  --remote-debugging-port=9222 \
  https://www.chromestatus.com   # URL to open. Defaults to about:blank.

```

>
> **注意：**目前你仍然需要使用 `--disable-gpu` 标志。但它最终会不需要的。
>
>
>

`chrome` 二进制文件应该指向你安装 Chrome 的位置。确切的位置会因平台差异而不同。当前我在 Mac 上操作，所以我为安装的每个版本的 Chrome 都创建了方便使用的别名。

如果您使用 Chrome 的稳定版，并且无法获得测试版，我建议您使用 `chrome-canary` 版本：

```
alias chrome="/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome"
alias chrome-canary="/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary"
alias chromium="/Applications/Chromium.app/Contents/MacOS/Chromium"

```

在[这里](https://www.google.com/chrome/browser/canary.html)下载 Chrome Cannary。

### 命令行的功能

在某些情况下，你可能不需要[以脚本编程的方式](https://developers.google.com/web/updates/2017/04/headless-chrome#node)操作 Headless Chrome。可以使用一些[有用的命令行标志](https://cs.chromium.org/chromium/src/headless/app/headless_shell_switches.cc)来执行常见的任务。

#### 打印 DOM

`--dump-dom` 标志将打印 `document.body.innerHTML` 到标准输出：

```
chrome --headless --disable-gpu --dump-dom https://www.chromestatus.com/

```

#### 创建一个 PDF

`--print-to-pdf` 标志将页面转出为 PDF 文件：

```
chrome --headless --disable-gpu --print-to-pdf https://www.chromestatus.com/

```

#### 截图

要捕获页面的屏幕截图，请使用 `--screenshot` 标志：

```
chrome --headless --disable-gpu --screenshot https://www.chromestatus.com/

# Size of a standard letterhead.
chrome --headless --disable-gpu --screenshot --window-size=1280,1696 https://www.chromestatus.com/

# Nexus 5x
chrome --headless --disable-gpu --screenshot --window-size=412,732 https://www.chromestatus.com/

```

使用 `--screenshot` 标志运行 Headless Chrome 将在当前工作目录中生成一个名为 `screenshot.png` 的文件。如果你正在寻求整个页面的截图，那么会涉及到很多事情。来自 David Schnurr 的一篇很棒的博文已经介绍了这一内容。请查看 [使用 headless Chrome 作为自动截屏工具](https://medium.com/@dschnr/using-headless-chrome-as-an-automated-screenshot-tool-4b07dffba79a)。

#### REPL 模式 (read-eval-print loop)

`--repl` 标志可以使 Headless Chrome 运行在一个你可以使用浏览器评估 JS 表达式的模式下。执行下面的命令：

```
$ chrome --headless --disable-gpu --repl https://www.chromestatus.com/
[0608/112805.245285:INFO:headless_shell.cc(278)] Type a Javascript expression to evaluate or "quit" to exit.
>>> location.href
{"result":{"type":"string","value":"https://www.chromestatus.com/features"}}
>>> quit

```

### 在没有浏览器界面的情况下调试 Chrome

当你使用 `--remote-debugging-port=9222` 运行 Chrome 时，它会启动一个支持 [DevTools 协议](https://chromedevtools.github.io/devtools-protocol/)的实例。该协议用于与 Chrome 进行通信，并且驱动 Headless Chrome 浏览器实例。它也是一个类似 Sublime、VS Code 和 Node 的工具，可用于应用程序的远程调试。#协同效应

由于你没有浏览器用户界面可用来查看网页，请在另一个浏览器中输入 `http://localhost:9222`，以检查一切是否正常。你将会看到一个<ruby> 可检查的 <rt>  inspectable </rt></ruby>页面的列表，可以点击它们来查看 Headless Chrome 正在呈现的内容：

![](/data/attachment/album/201709/08/113716ghwls9r2wtu2mwhl.jpg)

*DevTools 远程调试界面*

从这里，你就可以像往常一样使用熟悉的 DevTools 来检查、调试和调整页面了。如果你以编程方式使用 Headless Chrome，这个页面也是一个功能强大的调试工具，用于查看所有通过网络与浏览器交互的原始 DevTools 协议命令。

### 使用编程模式 （Node）

#### Puppeteer 库 API

[Puppeteer](https://github.com/GoogleChrome/puppeteer) 是一个由 Chrome 团队开发的 Node 库。它提供了一个高层次的 API 来控制无需显示版（或 完全版）的 Chrome。它与其他自动化测试库，如 Phantom 和 NightmareJS 相类似，但是只适用于最新版本的 Chrome。

除此之外，Puppeteer 还可用于轻松截取屏幕截图，创建 PDF，页面间导航以及获取有关这些页面的信息。如果你想快速地自动化进行浏览器测试，我建议使用该库。它隐藏了 DevTools 协议的复杂性，并可以处理诸如启动 Chrome 调试实例等繁冗的任务。

安装：

```
yarn add puppeteer

```

**例子** - 打印用户代理：

```
const puppeteer = require('puppeteer');

(async() => {
  const browser = await puppeteer.launch();
  console.log(await browser.version());
  browser.close();
})();

```

**例子** - 获取页面的屏幕截图：

```
const puppeteer = require('puppeteer');

(async() => {

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://www.chromestatus.com', {waitUntil: 'networkidle'});
await page.pdf({path: 'page.pdf', format: 'A4'});

browser.close();
})();

```

查看 [Puppeteer 的文档](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md)，了解完整 API 的更多信息。

#### CRI 库

[chrome-remote-interface](https://www.npmjs.com/package/chrome-remote-interface) 是一个比 Puppeteer API 更低层次的库。如果你想要更接近原始信息和更直接地使用 [DevTools 协议](https://chromedevtools.github.io/devtools-protocol/)的话，我推荐使用它。

**启动 Chrome**

chrome-remote-interface 不会为你启动 Chrome，所以你要自己启动它。

在前面的 CLI 章节中，我们使用 `--headless --remote-debugging-port=9222` [手动启动了 Chrome](https://developers.google.com/web/updates/2017/04/headless-chrome#cli)。但是，要想做到完全自动化测试，你可能希望从你的应用程序中启动 Chrome。

其中一种方法是使用 `child_process`：

```
const execFile = require('child_process').execFile;

function launchHeadlessChrome(url, callback) {
  // Assuming MacOSx.
  const CHROME = '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome';
  execFile(CHROME, ['--headless', '--disable-gpu', '--remote-debugging-port=9222', url], callback);
}

launchHeadlessChrome('https://www.chromestatus.com', (err, stdout, stderr) => {
  ...
});

```

但是如果你想要在多个平台上运行可移植的解决方案，事情会变得很棘手。请注意 Chrome 的硬编码路径：

**使用 ChromeLauncher**

[Lighthouse](https://developers.google.com/web/tools/lighthouse/) 是一个令人称奇的网络应用的质量测试工具。Lighthouse 内部开发了一个强大的用于启动 Chrome 的模块，现在已经被提取出来单独使用。[chrome-launcher NPM 模块](https://www.npmjs.com/package/chrome-launcher) 可以找到 Chrome 的安装位置，设置调试实例，启动浏览器和在程序运行完之后将其杀死。它最好的一点是可以跨平台工作，感谢 Node！

默认情况下，**chrome-launcher 会尝试启动 Chrome Canary**（如果已经安装），但是你也可以更改它，手动选择使用的 Chrome 版本。要想使用它，首先从 npm 安装：

```
yarn add chrome-launcher

```

**例子** - 使用 `chrome-launcher` 启动 Headless Chrome：

```
const chromeLauncher = require('chrome-launcher');

// Optional: set logging level of launcher to see its output.
// Install it using: yarn add lighthouse-logger
// const log = require('lighthouse-logger');
// log.setLevel('info');

/**
 * Launches a debugging instance of Chrome.
 * @param {boolean=} headless True (default) launches Chrome in headless mode.
 *     False launches a full version of Chrome.
 * @return {Promise<ChromeLauncher>}
 */
function launchChrome(headless=true) {
  return chromeLauncher.launch({
    // port: 9222, // Uncomment to force a specific port of your choice.
    chromeFlags: [
      '--window-size=412,732',
      '--disable-gpu',
      headless ? '--headless' : ''
    ]
  });
}

launchChrome().then(chrome => {
  console.log(`Chrome debuggable on port: ${chrome.port}`);
  ...
  // chrome.kill();
});

```

运行这个脚本没有做太多的事情，但你应该能在任务管理器中看到启动了一个 Chrome 的实例，它加载了页面 `about:blank`。记住，它不会有任何的浏览器界面，我们是无需显示的。

为了控制浏览器，我们需要 DevTools 协议！

#### 检索有关页面的信息

>
> **警告：** DevTools 协议可以做一些有趣的事情，但是起初可能有点令人生畏。我建议先花点时间浏览 [DevTools 协议查看器](https://chromedevtools.github.io/devtools-protocol/)。然后，转到 `chrome-remote-interface` 的 API 文档，看看它是如何包装原始协议的。
>
>
>

我们来安装该库：

```
yarn add chrome-remote-interface

```

**例子** - 打印用户代理：

```
const CDP = require('chrome-remote-interface');

...

launchChrome().then(async chrome => {
  const version = await CDP.Version({port: chrome.port});
  console.log(version['User-Agent']);
});

```

结果是类似这样的东西：`HeadlessChrome/60.0.3082.0`。

**例子** - 检查网站是否有 [Web 应用程序清单](https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/)：

```
const CDP = require('chrome-remote-interface');

...

(async function() {

const chrome = await launchChrome();
const protocol = await CDP({port: chrome.port});

// Extract the DevTools protocol domains we need and enable them.
// See API docs: https://chromedevtools.github.io/devtools-protocol/
const {Page} = protocol;
await Page.enable();

Page.navigate({url: 'https://www.chromestatus.com/'});

// Wait for window.onload before doing stuff.
Page.loadEventFired(async () => {
  const manifest = await Page.getAppManifest();

  if (manifest.url) {
    console.log('Manifest: ' + manifest.url);
    console.log(manifest.data);
  } else {
    console.log('Site has no app manifest');
  }

  protocol.close();
  chrome.kill(); // Kill Chrome.
});

})();

```

**例子** - 使用 DOM API 提取页面的 `<title>`：

```
const CDP = require('chrome-remote-interface');

...

(async function() {

const chrome = await launchChrome();
const protocol = await CDP({port: chrome.port});

// Extract the DevTools protocol domains we need and enable them.
// See API docs: https://chromedevtools.github.io/devtools-protocol/
const {Page, Runtime} = protocol;
await Promise.all([Page.enable(), Runtime.enable()]);

Page.navigate({url: 'https://www.chromestatus.com/'});

// Wait for window.onload before doing stuff.
Page.loadEventFired(async () => {
  const js = "document.querySelector('title').textContent";
  // Evaluate the JS expression in the page.
  const result = await Runtime.evaluate({expression: js});

  console.log('Title of page: ' + result.result.value);

  protocol.close();
  chrome.kill(); // Kill Chrome.
});

})();

```

### 使用 Selenium、WebDriver 和 ChromeDriver

现在，Selenium 开启了 Chrome 的完整实例。换句话说，这是一个自动化的解决方案，但不是完全无需显示的。但是，Selenium 只需要进行小小的配置即可运行 Headless Chrome。如果你想要关于如何自己设置的完整说明，我建议你阅读“[使用 Headless Chrome 来运行 Selenium](https://intoli.com/blog/running-selenium-with-headless-chrome/)”，不过你可以从下面的一些示例开始。

#### 使用 ChromeDriver

[ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/) 2.3.0 支持 Chrome 59 及更新版本，可与 Headless Chrome 配合使用。在某些情况下，你可能需要等到 Chrome 60 以解决 bug。例如，Chrome 59 中屏幕截图已知存在问题。

安装：

```
yarn add selenium-webdriver chromedriver

```

例子：

```
const fs = require('fs');
const webdriver = require('selenium-webdriver');
const chromedriver = require('chromedriver');

// This should be the path to your Canary installation.
// I'm assuming Mac for the example.
const PATH_TO_CANARY = '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary';

const chromeCapabilities = webdriver.Capabilities.chrome();
chromeCapabilities.set('chromeOptions', {
  binary: PATH_TO_CANARY // Screenshots require Chrome 60\. Force Canary.
  'args': [
    '--headless',
  ]
});

const driver = new webdriver.Builder()
  .forBrowser('chrome')
  .withCapabilities(chromeCapabilities)
  .build();

// Navigate to google.com, enter a search.
driver.get('https://www.google.com/');
driver.findElement({name: 'q'}).sendKeys('webdriver');
driver.findElement({name: 'btnG'}).click();
driver.wait(webdriver.until.titleIs('webdriver - Google Search'), 1000);

// Take screenshot of results page. Save to disk.
driver.takeScreenshot().then(base64png => {
  fs.writeFileSync('screenshot.png', new Buffer(base64png, 'base64'));
});

driver.quit();

```

#### 使用 WebDriverIO

[WebDriverIO](http://webdriver.io/) 是一个在 Selenium WebDrive 上构建的更高层次的 API。

安装：

```
yarn add webdriverio chromedriver

```

例子：过滤 chromestatus.com 上的 CSS 功能：

```
const webdriverio = require('webdriverio');
const chromedriver = require('chromedriver');

// This should be the path to your Canary installation.
// I'm assuming Mac for the example.
const PATH_TO_CANARY = '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary';
const PORT = 9515;

chromedriver.start([
  '--url-base=wd/hub',
  `--port=${PORT}`,
  '--verbose'
]);

(async () => {

const opts = {
  port: PORT,
  desiredCapabilities: {
    browserName: 'chrome',
    chromeOptions: {
      binary: PATH_TO_CANARY // Screenshots require Chrome 60\. Force Canary.
      args: ['--headless']
    }
  }
};

const browser = webdriverio.remote(opts).init();

await browser.url('https://www.chromestatus.com/features');

const title = await browser.getTitle();
console.log(`Title: ${title}`);

await browser.waitForText('.num-features', 3000);
let numFeatures = await browser.getText('.num-features');
console.log(`Chrome has ${numFeatures} total features`);

await browser.setValue('input[type="search"]', 'CSS');
console.log('Filtering features...');
await browser.pause(1000);

numFeatures = await browser.getText('.num-features');
console.log(`Chrome has ${numFeatures} CSS features`);

const buffer = await browser.saveScreenshot('screenshot.png');
console.log('Saved screenshot...');

chromedriver.stop();
browser.end();

})();

```

### 更多资源

以下是一些可以带你入门的有用资源：

文档

* [DevTools Protocol Viewer](https://chromedevtools.github.io/devtools-protocol/) - API 参考文档

工具

* [chrome-remote-interface](https://www.npmjs.com/package/chrome-remote-interface) - 基于 DevTools 协议的 node 模块
* [Lighthouse](https://github.com/GoogleChrome/lighthouse) - 测试 Web 应用程序质量的自动化工具；大量使用了协议
* [chrome-launcher](https://github.com/GoogleChrome/lighthouse/tree/master/chrome-launcher) - 用于启动 Chrome 的 node 模块，可以自动化

样例

* "[The Headless Web](https://paul.kinlan.me/the-headless-web/)" - Paul Kinlan 发布的使用了 Headless 和 api.ai 的精彩博客

### 常见问题

**我需要 `--disable-gpu` 标志吗？**

目前是需要的。`--disable-gpu` 标志在处理一些 bug 时是需要的。在未来版本的 Chrome 中就不需要了。查看 [https://crbug.com/546953#c152](https://bugs.chromium.org/p/chromium/issues/detail?id=546953#c152) 和 [https://crbug.com/695212](https://bugs.chromium.org/p/chromium/issues/detail?id=695212) 获取更多信息。

**所以我仍然需要 Xvfb 吗？**

不。Headless Chrome 不使用窗口，所以不需要像 Xvfb 这样的显示服务器。没有它你也可以愉快地运行你的自动化测试。

什么是 Xvfb？Xvfb 是一个用于类 Unix 系统的运行于内存之内的显示服务器，可以让你运行图形应用程序（如 Chrome），而无需附加的物理显示器。许多人使用 Xvfb 运行早期版本的 Chrome 进行 “headless” 测试。

**如何创建一个运行 Headless Chrome 的 Docker 容器？**

查看 [lighthouse-ci](https://github.com/ebidel/lighthouse-ci)。它有一个使用 Ubuntu 作为基础镜像的 [Dockerfile 示例](https://github.com/ebidel/lighthouse-ci/blob/master/builder/Dockerfile.headless)，并且在 App Engine Flexible 容器中安装和运行了 Lighthouse。

**我可以把它和 Selenium / WebDriver / ChromeDriver 一起使用吗？**

是的。查看 [Using Selenium, WebDrive, or ChromeDriver](https://developers.google.com/web/updates/2017/04/headless-chrome#drivers)。

**它和 PhantomJS 有什么关系？**

Headless Chrome 和 [PhantomJS](http://phantomjs.org/) 是类似的工具。它们都可以用来在无需显示的环境中进行自动化测试。两者的主要不同在于 Phantom 使用了一个较老版本的 WebKit 作为它的渲染引擎，而 Headless Chrome 使用了最新版本的 Blink。

目前，Phantom 提供了比 [DevTools protocol](https://chromedevtools.github.io/devtools-protocol/) 更高层次的 API。

**我在哪儿提交 bug？**

对于 Headless Chrome 的 bug，请提交到 [crbug.com](https://bugs.chromium.org/p/chromium/issues/entry?components=Blink&blocking=705916&cc=skyostil%40chromium.org&Proj=Headless)。

对于 DevTools 协议的 bug，请提交到 [github.com/ChromeDevTools/devtools-protocol](https://github.com/ChromeDevTools/devtools-protocol/issues/new)。

---

作者简介

[Eric Bidelman](https://developers.google.com/web/resources/contributors#ericbidelman) 谷歌工程师，Lighthouse 开发，Web 和 Web 组件开发，Chrome 开发

---

via: <https://developers.google.com/web/updates/2017/04/headless-chrome>

作者：[Eric Bidelman](https://developers.google.com/web/resources/contributors#ericbidelman) 译者：[firmianay](https://github.com/firmianay) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


ImageMagick 入门：使用命令行来编辑图片

Summary: ImageMagick 不仅仅是一个图像查看器，它还提供了大量的图像编辑工具和选项。本教程将详细介绍如何在 ImageMagick 中使用 display 命令和其他命令行工具。

>
> 了解使用此轻量级图像编辑器查看和修改图像的常见方法。
>
>
>

![Getting started with ImageMagick](/data/attachment/album/201709/08/132945zalli0apiaalll6l.png "Getting started with ImageMagick")

在最近一篇关于[轻量级图像查看器](https://opensource.com/article/17/7/4-lightweight-image-viewers-linux-desktop)的文章中，作者 Scott Nesbitt 提到了 `display`，它是 [ImageMagick](https://www.imagemagick.org/script/index.php) 中的一个组件。ImageMagick 不仅仅是一个图像查看器，它还提供了大量的图像编辑工具和选项。本教程将详细介绍如何在 ImageMagick 中使用 `display` 命令和其他命令行工具。

现在有许多优秀的图像编辑器可用，你可能会想知道为什么有人会选择一个非 GUI 的、基于命令行的程序，如 ImageMagick。一方面，它非常可靠。但更大的好处是，它允许你建立一个以特定的方式编辑大量图像的方式。

这篇对于常见的 ImageMagick 命令的介绍应该让你入门。

### display 命令

让我们从 Scott 提到的命令开始：`display`。假设你有一个目录，其中有很多想要查看的图像。使用以下命令开始 `display`：

```
cd Pictures
display *.JPG

```

这将按照字母数字顺序顺序加载你的 JPG 文件，每张放在一个简单的窗口中。左键单击图像可以打开一个简单的独立菜单（ImageMagick 中唯一的 GUI 功能）。

![display menu](/data/attachment/album/201709/08/132946bhqvih5wqtygfoiz.png "display menu")

你可以在 **display** 菜单中找到以下内容：

* **File** 包含选项 Open、Next、Former、Select、Save、Print、Delete、New、Visual Directory 和 Quit。 *Select* 来选择要显示的特定文件，*Visual Directory* 显示当前工作目录中的所有文件（而不仅仅是图像）。如果要滚动显示所有选定的图像，你可以使用 *Next* 和 *Former*，但使用键盘快捷键（下一张图像用空格键，上一张图像用退格）更容易。
* **Edit** 提供 Undo、Redo、Cut、Copy 和 Paste，它们只是辅助命令进行更具体的编辑过程。 当你进行不同的编辑功能看看它们做什么时 *Undo* 特别有用。
* **View** 有 Half Size、Original Size、Double Size、Resize、Apply、Refresh 和 Restore。这些大多是不用说明的，除非你在应用其中之一后保存图像，否则图像文件不会更改。*Resize* 会打开一个对话框，以像素为单位，带有或者不带尺寸限制，或者是百分比指定图片大小。我不知道 *Apply* 会做什么。
* **Transform** 显示 Crop、Chop、Flop、Flip、Rotate Right、Rotate Left、Rotate、Shear、Roll 和 Trim Edges。*Chop* 使用点击拖动操作剪切图像的垂直或水平部分，将边缘粘贴在一起。了解这些功能如何工作的最佳方法是操作它们，而不是看看。
* **Enhance** 提供 Hue、Saturation、Brightness、Gamma、Spiff、Dull、Contrast Stretch、Sigmoidal Contrast、Normalize、Equalize、Negate、Grayscale、Map 和 Quantize。这些是用于颜色和调整亮度和对比度的操作。
* **效果** 有 Despeckle、Emboss、Reduce Noise、Add Noise、Sharpen、Blur、Threshold、Edge Detect、Spread、Shade、Raise 和 Segment。这些是相当标准的图像编辑效果。
* **F/X** 选项有 Solarize、Sepia Tone、Swirl、Implode、Vignette、Wave、Oil Paint 和 Charcoal Draw，在图像编辑器中也是非常常见的效果。
* **Image Edit** 包含 Annotate、Draw、Color、Matte、Composite、Add Border、Add Frame、Comment、Launch 和 Region of Interest。*Launch \_ 将打开 GIMP 中的当前图像（至少在我的 Fedora 中是这样）。 \_Region of Interest* 允许你选择一个区域来应用编辑。按下 Esc 取消选择该区域。
* **Miscellany** 提供 Image Info、Zoom Image、Show Preview、Show Histogram、Show Matte、Background、Slide Show 和 Preferences。 *Show Preview* 似乎很有趣，但我努力让它工作。
* **Help** 有 Overview、Browse Documentation 和 About Display。 *Overview* 提供了大量关于 display 的基本信息，并且包含大量内置的键盘快捷键，用于各种命令和操作。在我的 Fedora 中，*Browse Documentation* 没有作用。

虽然 `display` 的 GUI 界面提供了一个称职的图像编辑器，但 ImageMagick 还提供了 89 个命令行选项，其中许多与上述菜单项相对应。例如，如果我显示的数码相片目录中的图像大于我的屏幕尺寸，我不用在显示后单独调整大小，我可以指定：

```
display -resize 50% *.JPG

```

上面菜单中的许多操作都可以通过在命令行中添加一个选项来完成。但是还有其他的选项在菜单中没有，包括 `-monochrome`，将图像转换为黑白（不是灰度），还有 `-colors`，你可以指定在图像中使用多少种颜色。例如，尝试这些：

```
display -resize 50% -monochrome *.JPG

```

```
display -resize 50% -colors 8 *.JPG

```

这些操作会创建有趣的图像。试试增强颜色或进行其他编辑后减少颜色。记住，除非你保存并覆盖它们，否则原始文件保持不变。

### convert 命令

`convert` 命令有 237 个选项 - 是的， 237 个！ - 它提供了你可以做的各种各样的事情（其中一些 `display` 也可以做）。我只会覆盖其中的几个，主要是图像操作。你可以用 `convert` 做的两件简单的事情是：

```
convert DSC_0001.JPG dsc0001.png

```

```
convert *.bmp *.png

```

第一个命令将单个文件（DSC\_0001）从 JPG 转换为 PNG 格式，而不更改原始文件。第二个将对目录中的所有 BMP 图像执行此操作。

如果要查看 ImageMagick 可以使用的格式，请输入：

```
identify -list format

```

我们来看几个用 `convert` 命令来处理图像的有趣方法。以下是此命令的一般格式：

```
convert inputfilename [options] outputfilename

```

你有多个选项，它们按照从左到右排列的顺序完成。

以下是几个简单的选项：

```
convert monochrome_source.jpg -monochrome monochrome_example.jpg

```

![monochrome example](/data/attachment/album/201709/08/132948t2lt12m32tzmi2tl.jpg "monochrome example")

```
convert DSC_0008.jpg -charcoal 1.2 charcoal_example.jpg

```

![charcoal example](/data/attachment/album/201709/08/133000zpvpxvd6crz4wzxp.jpg "charcoal example")

`-monochrome` 选项没有关联的设置，但 `-charcoal` 变量需要一个相关因子。根据我的经验，它需要一个小的数字（甚至小于 1）来实现类似于炭笔绘画的东西，否则你会得到很大的黑色斑点。即使如此，图像中的尖锐边缘也是非常明显的，与炭笔绘画不同。

现在来看看这些：

```
convert DSC_0032.JPG -edge 3 edge_demo.jpg

```

```
convert DSC_0032.JPG -colors 4 reduced4_demo.jpg

```

```
convert DSC_0032.JPG -colors 4 -edge 3 reduced+edge_demo.jpg

```

![edge and reduced colors example](/data/attachment/album/201709/08/133007rc9wcc7icbzr92l8.jpg "edge and reduced colors example")

原始图像位于左上方。在第一个命令中，我使用了一个 `-edge` 选项，设置为 3（见右上角的图像） - 对于我的喜好而言小于它的数字都太精细了。在第二个命令（左下角的图像）中，我们将颜色的数量减少到了 4 个，与原来没有什么不同。但是看看当我们在第三个命令中组合这两个时，会发生什么（右下角的图像）！也许这有点大胆，但谁能预期到从原始图像或任何一个选项变成这个结果？

`-canny` 选项提供了另外一个惊喜。这是另一种边缘检测器，称为“多阶算法”。单独使用 `-canny` 可以产生基本黑色的图像和一些白线。我后面跟着一个 `-negate` 选项：

```
convert DSC_0049.jpg -canny 0x1 -negate canny_egret.jpg
convert DSC_0023.jpg -canny 0x1 -negate canny_ship.jpg

```

![canny demo](/data/attachment/album/201709/08/133015u3k90x69kz50jkk0.jpg "canny demo")

这有点极简主义，但我认为它类似于一种笔墨绘画，与原始照片有相当显著的差异。它并不能用于所有图片。一般来说，它对有锐利线条的图像效果最好。不是焦点的元素可能会消失。注意白鹭图片中的背景沙滩没有显示，因为它是模糊的。同样注意下船舶图片，虽然大多数边缘显示得非常好，因为没有颜色，我们失去了图片的整体形象，所以也许这可以作为一些数字着色，甚至在印后着色的基础。

### montage 命令

最后，我想谈一下 `montage` （蒙太奇）命令。我已经在上面展示了这个例子，我将单个图像组合成复合图片。

这是我如何生成炭笔的例子（请注意，它们都在一行）：

```
montage -label %f DSC_0008.jpg charcoal_example.jpg -geometry +10+10
      -resize 25% -shadow -title 'charcoal demo' charcoal_demo.jpg

```

`-label` 选项会在每个图像下方标记它的文件名(`%f`)。不用 `-geometry` 选项，所有的图像将是缩略图大小（120 像素宽），`+10+10` 负责边框大小。接下来，我调整了整个最终组合的大小（`-resize 25%`），并添加了一个阴影（没有设置，因此是默认值），最后为这次 montage 操作创建了一个标题（`-title`）。

你可以将所有图像名称放在最后，最后一个图像的名称将是 `montage` 操作所保存的文件名。这可用于为命令及其所有选项创建别名，然后我可以简单地键入该别名、输入适当的文件名即可。我偶尔会这么做来减少 `montage` 操作需要输入的命令长度。

在 `-canny` 的例子中，我对 4 张图像进行了蒙太奇操作。我添加了 `-tile` 选项，确切地说是 `-tile 2x`，它创建了有两列的蒙太奇。我可以指定一个 `matrix`、`-tile 2x2` 或 `-tile x2` 来产生相同的结果。

ImageMagick 还有更多可以了解，所以我打算写更多关于它的文章，甚至可能使用 [Perl](https://opensource.com/sitewide-search?search_api_views_fulltext=perl) 脚本运行 ImageMagick 命令。ImageMagick 具有丰富的[文档](https://imagemagick.org/script/index.php)，尽管该网站在示例或者显示结果上还不足，我认为最好的学习方式是通过实验和更改各种设置和选项来学习。

（题图： opensource.com）

---

作者简介：

Greg Pittman - Greg 是肯塔基州路易斯维尔的一名退休的神经科医生，对计算机和程序设计有着长期的兴趣，从 1960 年代的 Fortran IV 开始。当 Linux 和开源软件相继出现时，他开始学习更多，并最终做出贡献。他是 Scribus 团队的成员。

---

via: <https://opensource.com/article/17/8/imagemagick>

作者：[Greg Pittman](https://opensource.com/users/greg-p) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


GitHub 的 DNS 基础设施

Summary: 在 GitHub，我们最近从头改进了 DNS。这包括了我们如何与外部 DNS 提供商交互以及我们如何在内部向我们的主机提供记录。为此，我们必须设计和构建一个新的 DNS 基础设施，它可以随着 GitHub 的增长扩展并跨越多个数据中心。

![](/data/attachment/album/201709/09/165851b7yv8bkcv7jwm8ww.jpeg)

在 GitHub，我们最近从头改进了 DNS。这包括了我们[如何与外部 DNS 提供商交互](https://githubengineering.com/enabling-split-authority-dns-with-octodns/)以及我们如何在内部向我们的主机提供记录。为此，我们必须设计和构建一个新的 DNS 基础设施，它可以随着 GitHub 的增长扩展并跨越多个数据中心。

以前，GitHub 的 DNS 基础设施相当简单直接。它包括每台服务器上本地的、只具备转发功能的 DNS 缓存服务器，以及一对被所有这些主机使用的缓存服务器和权威服务器主机。这些主机在内部网络以及公共互联网上都可用。我们在缓存守护程序中配置了<ruby> 区域 <rt>  zone </rt></ruby><ruby> 存根 <rt>  stub </rt></ruby>，以在本地进行查询，而不是在互联网上进行递归。我们还在我们的 DNS 提供商处设置了 NS 记录，它们将特定的内部<ruby> 域 <rt>  domain </rt></ruby>指向这对主机的公共 IP，以便我们网络外部的查询。

这个配置使用了很多年，但它并非没有缺点。许多程序对于解析 DNS 查询非常敏感，我们遇到的任何性能或可用性问题在最好的情况下也会导致服务排队和性能降级，而最坏情况下客户会遭遇服务中断。配置和代码的更改可能会导致查询率发生大幅度的意外变化。因此超出这两台主机的扩展成为了一个问题。由于这些主机的网络配置，如果我们只是继续添加 IP 和主机的话存在一些本身的问题。在试图解决和补救这些问题的同时，由于缺乏测量指标和可见性，老旧的系统难以识别问题的原因。在许多情况下，我们使用 `tcpdump` 来识别有问题的流量和查询。另一个问题是在公共 DNS 服务器上运行，我们处于泄露内部网络信息的风险之下。因此，我们决定建立更好的东西，并开始确定我们对新系统的要求。

我们着手设计一个新的 DNS 基础设施，以改善上述包括扩展和可见性在内的运维问题，并引入了一些额外的需求。我们希望通过外部 DNS 提供商继续运行我们的公共 DNS 域，因此我们构建的系统需要与供应商无关。此外，我们希望该系统能够服务于我们的内部和外部域，这意味着内部域仅在我们的内部网络上可用，除非另有特别配置，而外部域也不用离开我们的内部网络就可解析。我们希望新的 DNS 架构不但可以[基于部署的工作流进行更改](https://githubengineering.com/enabling-split-authority-dns-with-octodns/)，并可以通过我们的仓库和配置系统使用 API 自动更改 DNS 记录。新系统不能有任何外部依赖，太依赖于 DNS 功能将会陷入级联故障，这包括连接到其他数据中心和其中可能有的 DNS 服务。我们的旧系统将缓存服务器和权威服务器在同一台主机上混合使用。我们想转到具有独立角色的分层设计。最后，我们希望系统能够支持多数据中心环境，无论是 EC2 还是裸机。

### 实现

![](/data/attachment/album/201709/09/165355t81eey1yz08afztw.png)

为了构建这个系统，我们确定了三类主机：<ruby> 缓存主机 <rt>  cache </rt></ruby>、<ruby> 边缘主机 <rt>  edge </rt></ruby>和<ruby> 权威主机 <rt>  authority </rt></ruby>。缓存主机作为<ruby> 递归解析器 <rt>  recursive resolver </rt></ruby>和 DNS “路由器” 缓存来自边缘层的响应。边缘层运行 DNS 权威守护程序，用于响应缓存层对 DNS <ruby> 区域 <rt>  zone </rt></ruby>的请求，其被配置为来自权威层的<ruby> 区域传输 <rt>  zone transfer </rt></ruby>。权威层作为隐藏的 DNS <ruby> 主服务器 <rt>  master </rt></ruby>，作为 DNS 数据的规范来源，为来自边缘主机的<ruby> 区域传输 <rt>  zone transfer </rt></ruby>提供服务，并提供用于创建、修改或删除记录的 HTTP API。

在我们的新配置中，缓存主机存在于每个数据中心中，这意味着应用主机不需要穿过数据中心边界来检索记录。缓存主机被配置为将<ruby> 区域 <rt>  zone </rt></ruby>映射到其<ruby> 地域 <rt>  region </rt></ruby>内的边缘主机，以便将我们的内部<ruby> 区域 <rt>  zone </rt></ruby>路由到我们自己的主机。未明确配置的任何<ruby> 区域 <rt>  zone </rt></ruby>将通过互联网递归解析。

边缘主机是地域性的主机，存在我们的网络边缘 PoP（<ruby> 存在点 <rt>  Point of Presence </rt></ruby>）内。我们的 PoP 有一个或多个依赖于它们进行外部连接的数据中心，没有 PoP 数据中心将无法访问互联网，互联网也无法访问它们。边缘主机对所有的权威主机执行<ruby> 区域传输 <rt>  zone transfer </rt></ruby>，无论它们存在什么<ruby> 地域 <rt>  region </rt></ruby>或<ruby> 位置 <rt>  location </rt></ruby>，并将这些区域存在本地的磁盘上。

我们的权威主机也是地域性的主机，只包含适用于其所在<ruby> 地域 <rt>  region </rt></ruby>的<ruby> 区域 <rt>  zone </rt></ruby>。我们的仓库和配置系统决定一个<ruby> 区域 <rt>  zone </rt></ruby>存放在哪个<ruby> 地域性权威主机 <rt>  regional authority </rt></ruby>，并通过 HTTP API 服务来创建和删除记录。 OctoDNS 将区域映射到地域性权威主机，并使用相同的 API 创建静态记录，以及确保动态源处于同步状态。对于外部域 (如 github.com)，我们有另外一个单独的权威主机，以允许我们可以在连接中断期间查询我们的外部域。所有记录都存储在 MySQL 中。

### 可运维性

![](/data/attachment/album/201709/09/165406u3zedc4ll34ppdm4.png)

迁移到更现代的 DNS 基础设施的巨大好处是可观察性。我们的旧 DNS 系统几乎没有指标，只有有限的日志。决定使用哪些 DNS 服务器的一个重要因素是它们所产生的指标的广度和深度。我们最终用 [Unbound](https://unbound.net/) 作为缓存主机，[NSD](https://www.nlnetlabs.nl/projects/nsd/) 作为边缘主机，[PowerDNS](https://powerdns.com/) 作为权威主机，所有这些都已在比 GitHub 大得多的 DNS 基础架构中得到了证实。

当在我们的裸机数据中心运行时，缓存通过私有的<ruby> <a href="https://en.wikipedia.org/wiki/Anycast">  任播 </a> <rt>  anycast </rt></ruby> IP 访问，从而使之可以到达最近的可用缓存主机。缓存主机已经以机架感知的方式部署，在它们之间提供了一定程度的平衡负载，并且与一些电源和网络故障模式相隔离。当缓存主机出现故障时，通常将用其进行 DNS 查询的服务器现在将自动路由到下一个最接近的缓存主机，以保持低延迟并提供对某些故障模式的容错。任播允许我们扩展单个 IP 地址后面的缓存数量，这与先前的配置不同，使得我们能够按 DNS 需求量运行尽可能多的缓存主机。

无论地域或位置如何，边缘主机使用权威层进行区域传输。我们的<ruby> 区域 <rt>  zone </rt></ruby>并没有大到在每个<ruby> 地域 <rt>  region </rt></ruby>保留所有<ruby> 区域 <rt>  zone </rt></ruby>的副本成为问题。（LCTT 译注：此处原文“Our zones are not large enough that keeping a copy of all of them in every region is a problem.”，根据上下文理解而翻译。）这意味着对于每个区域，即使某个地域处于脱机状态，或者上游服务提供商存在连接问题，所有缓存服务器都可以访问具备所有区域的本地副本的本地边缘服务器。这种变化在面对连接问题方面已被证明是相当有弹性的，并且在不久前本来会导致客户面临停止服务的故障期间帮助保持 GitHub 可用。

那些区域传输包括了内部和外部域从它们相应的权威服务器进行的传输。正如你可能会猜想像 github.com 这样的区域是外部的，像 github.net 这样的区域通常是内部的。它们之间的区别仅在于我们使用的类型和存储在其中的数据。了解哪些区域是内部和外部的，为我们在配置中提供了一些灵活性。

```
$ dig +short github.com
192.30.253.112
192.30.253.113

```

公共<ruby> 区域 <rt>  zone </rt></ruby>被[同步](https://githubengineering.com/enabling-split-authority-dns-with-octodns/)到外部 DNS 提供商，并且是 GitHub 用户每天使用的 DNS 记录。另外，公共区域在我们的网络中是完全可解析的，而不需要与我们的外部提供商进行通信。这意味着需要查询 `api.github.com` 的任何服务都可以这样做，而无需依赖外部网络连接。我们还使用了 Unbound 的 `stub-first` 配置选项，它给了我们第二次查询的机会，如果我们的内部 DNS 服务由于某些原因在外部查询失败，则可以进行第二次查找。

```
$ dig +short time.github.net
10.127.6.10

```

大部分的 `github.net` 区域是完全私有的，无法从互联网访问，它只包含 [RFC 1918](http://www.faqs.org/rfcs/rfc1918.html) 中规定的 IP 地址。每个地域和站点都划分了私有区域。每个地域和/或站点都具有适用于该位置的一组子区域，子区域用于管理网络、服务发现、特定的服务记录，并且还包括在我们仓库中的配置主机。私有区域还包括 PTR 反向查找区域。

### 总结

用一个新系统替换可以为数百万客户提供服务的旧系统并不容易。使用实用的、基于需求的方法来设计和实施我们的新 DNS 系统，才能打造出一个能够迅速有效地运行、并有望与 GitHub 一起成长的 DNS 基础设施。

想帮助 GitHub SRE 团队解决有趣的问题吗？我们很乐意你加入我们。[在这申请](https://boards.greenhouse.io/github/jobs/669805#.WPVqJlPyvUI)。

---

via: <https://githubengineering.com/dns-infrastructure-at-github/>

作者：[Joe Williams](https://github.com/joewilliams) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


Samba 系列（十五）：用 SSSD 和 Realm 集成 Ubuntu 到 Samba4 AD DC

![](/data/attachment/album/201709/09/181412dtnflmmtymtod5py.jpg)

本教程将告诉你如何将 Ubuntu 桌面版机器加入到带有 SSSD 和 Realm 服务的 Samba4 活动目录域中，以在活动目录中认证用户。

### 要求：

1. [在 Ubuntu 上用 Samba4 创建一个活动目录架构](/article-8065-1.html)

### 第 1 步：初始配置

1、 在把 Ubuntu 加入活动目录前确保主机名被正确设置了。使用 `hostnamectl` 命令设置机器名字或者手动编辑 `/etc/hostname` 文件。

```
$ sudo hostnamectl set-hostname your_machine_short_hostname
$ cat /etc/hostname
$ hostnamectl

```

2、 接下来，编辑机器网络接口设置并且添加合适的 IP 设置，并将正确的 DNS IP 服务器地址指向 Samba 活动目录域控制器，如下图所示。

如果你已经配置了 DHCP 服务来为局域网机器自动分配包括合适的 AD DNS IP 地址的 IP 设置，那么你可以跳过这一步。

[![设置网络接口](/data/attachment/album/201709/09/181436ahce300hu5z5cdnc.jpg)](https://www.tecmint.com/wp-content/uploads/2017/07/Configure-Network-Interface.jpg)

*设置网络接口*

上图中，`192.168.1.254` 和 `192.168.1.253` 代表 Samba4 域控制器的 IP 地址。

3、 用 GUI（图形用户界面）或命令行重启网络服务来应用修改，并且对你的域名发起一系列 ping 请求来测试 DNS 解析如预期工作。 也用 `host` 命令来测试 DNS 解析。

```
$ sudo systemctl restart networking.service
$ host your_domain.tld
$ ping -c2 your_domain_name
$ ping -c2 adc1
$ ping -c2 adc2

```

4、 最后, 确保机器时间和 Samba4 AD 同步。安装 `ntpdate` 包并用下列指令和 AD 同步时间。

```
$ sudo apt-get install ntpdate
$ sudo ntpdate your_domain_name

```

### 第 2 步：安装需要的包

5、 这一步将安装将 Ubuntu 加入 Samba4 活动目录域控制器所必须的软件和依赖：Realmd 和 SSSD 服务。

```
$ sudo apt install adcli realmd krb5-user samba-common-bin samba-libs samba-dsdb-modules sssd sssd-tools libnss-sss libpam-sss packagekit policykit-1

```

6、 输入大写的默认 realm 名称，然后按下回车继续安装。

[![输入 Realm 名称](/data/attachment/album/201709/09/181438hjwwb6nruxwrkw6x.png)](https://www.tecmint.com/wp-content/uploads/2017/07/Set-realm-name.png)

*输入 Realm 名称*

7、 接着，创建包含以下内容的 SSSD 配置文件。

```
$ sudo nano /etc/sssd/sssd.conf

```

加入下面的内容到 `sssd.conf` 文件。

```
[nss]
filter_groups = root
filter_users = root
reconnection_retries = 3
[pam]
reconnection_retries = 3
[sssd]
domains = tecmint.lan
config_file_version = 2
services = nss, pam
default_domain_suffix = TECMINT.LAN
[domain/tecmint.lan]
ad_domain = tecmint.lan
krb5_realm = TECMINT.LAN
realmd_tags = manages-system joined-with-samba
cache_credentials = True
id_provider = ad
krb5_store_password_if_offline = True
default_shell = /bin/bash
ldap_id_mapping = True
use_fully_qualified_names = True
fallback_homedir = /home/%d/%u
access_provider = ad
auth_provider = ad
chpass_provider = ad
access_provider = ad
ldap_schema = ad
dyndns_update = true
dyndsn_refresh_interval = 43200
dyndns_update_ptr = true
dyndns_ttl = 3600

```

确保你对应地替换了下列参数的域名：

```
domains = tecmint.lan
default_domain_suffix = TECMINT.LAN
[domain/tecmint.lan]
ad_domain = tecmint.lan
krb5_realm = TECMINT.LAN

```

8、 接着，用下列命令给 SSSD 配置文件适当的权限：

```
$ sudo chmod 700 /etc/sssd/sssd.conf

```

9、 现在，打开并编辑 Realmd 配置文件，输入下面这行：

```
$ sudo nano /etc/realmd.conf

```

`realmd.conf` 文件摘录：

```
[active-directory]
os-name = Linux Ubuntu
os-version = 17.04
[service]
automatic-install = yes
[users]
default-home = /home/%d/%u
default-shell = /bin/bash
[tecmint.lan]
user-principal = yes
fully-qualified-names = no

```

10、 最后需要修改的文件属于 Samba 守护进程。 打开 `/etc/samba/smb.conf` 文件编辑，然后在文件开头加入下面这块代码，在 `[global]` 之后的部分如下图所示。

```
workgroup = TECMINT
client signing = yes
client use spnego = yes
kerberos method = secrets and keytab
realm = TECMINT.LAN
security = ads

```

[![配置 Samba 服务器](/data/attachment/album/201709/09/181440khjfh6dmhff2pdpn.jpg)](https://www.tecmint.com/wp-content/uploads/2017/07/Configure-Samba-Server.jpg)

*配置 Samba 服务器*

确保你替换了域名值，特别是对应域名的 realm 值，并运行 `testparm` 命令检验设置文件是否包含错误。

```
$ sudo testparm

```

[![测试 Samba 配置](/data/attachment/album/201709/09/181442pm7zp3ogp72ss3z2.jpg)](https://www.tecmint.com/wp-content/uploads/2017/07/Test-Samba-Configuration.jpg)

*测试 Samba 配置*

11、 在做完所有必需的修改之后，用 AD 管理员帐号验证 Kerberos 认证并用下面的命令列出票据。

```
$ sudo kinit ad_admin_user@DOMAIN.TLD
$ sudo klist

```

[![检验 Kerberos 认证](/data/attachment/album/201709/09/181444h4dtt9e07zd7tno1.jpg)](https://www.tecmint.com/wp-content/uploads/2017/07/Check-Kerberos-Authentication.jpg)

*检验 Kerberos 认证*

### 第 3 步： 加入 Ubuntu 到 Samba4 Realm

12、 键入下列命令将 Ubuntu 机器加入到 Samba4 活动目录。用有管理员权限的 AD DC 账户名字，以便绑定 realm 可以如预期般工作，并替换对应的域名值。

```
$ sudo realm discover -v DOMAIN.TLD
$ sudo realm list
$ sudo realm join TECMINT.LAN -U ad_admin_user -v
$ sudo net ads join -k

```

[![加入 Ubuntu 到 Samba4 Realm](/data/attachment/album/201709/09/181447ou8pzkdygi7pj8gd.jpg)](https://www.tecmint.com/wp-content/uploads/2017/07/Join-Ubuntu-to-Samba4-Realm.jpg)

*加入 Ubuntu 到 Samba4 Realm*

[![列出 Realm Domain 信息](/data/attachment/album/201709/09/181449bfmd94dm96eor84h.jpg)](https://www.tecmint.com/wp-content/uploads/2017/07/List-Realm-Domain-Info.jpg)

*列出 Realm Domain 信息*

[![加入用户到 Realm Domain](/data/attachment/album/201709/09/181449kowj143j4g487taj.jpg)](https://www.tecmint.com/wp-content/uploads/2017/07/Add-User-to-Realm-Domain.jpg)

*添加用户到 Realm Domain*

[![表列 Realm Domain 信息](/data/attachment/album/201709/09/181449x3vzhe27b7vd33vh.jpg)](https://www.tecmint.com/wp-content/uploads/2017/07/Add-Domain-to-Realm.jpg)

*添加 Domain 到 Realm*

13、 区域绑定好了之后，运行下面的命令确保所有域账户允许在这台机器上认证。

```
$ sudo realm permit -all

```

然后你可以使用下面举例的 `realm` 命令允许或者禁止域用户帐号或群组访问。

```
$ sudo realm deny -a
$ realm permit --groups ‘domain.tld\Linux Admins’
$ realm permit user@domain.lan
$ realm permit DOMAIN\\User2

```

14、 从一个 [安装了 RSAT 工具的](/article-8097-1.html) Windows 机器上你可以打开 AD UC 并浏览“<ruby> 电脑 <rt>  computers </rt></ruby>”容器，并检验是否有一个使用你机器名的对象帐号已经创建。

[![确保域被加入 AD DC](/data/attachment/album/201709/09/181449mt2kuqdbyq4k5lyq.jpg)](https://www.tecmint.com/wp-content/uploads/2017/07/Confirm-Domain-Added.jpg)

*确保域被加入 AD DC*

### 第 4 步：配置 AD 账户认证

15、 为了在 Ubuntu 机器上用域账户认证，你需要用 root 权限运行 `pam-auth-update` 命令并允许所有 PAM 配置文件，包括为每个域账户在第一次注册的时候自动创建家目录的选项。

按 [空格] 键检验所有配置项并点击 ok 来应用配置。

```
$ sudo pam-auth-update

```

[![PAM 配置](/data/attachment/album/201709/09/181452ih09uu3e11tu41zy.jpg)](https://www.tecmint.com/wp-content/uploads/2017/07/PAM-Configuration.jpg)

*PAM 配置*

16、 在系统上手动编辑 `/etc/pam.d/common-account` 文件，下面这几行是为了给认证过的域用户自动创建家目录。

```
session    required    pam_mkhomedir.so    skel=/etc/skel/    umask=0022

```

17、 如果活动目录用户不能用 linux 命令行修改他们的密码，打开 `/etc/pam.d/common-password` 文件并在 `password` 行移除 `use_authtok` 语句，最后如下：

```
password       [success=1 default=ignore]      pam_winbind.so try_first_pass

```

18、 最后，用下面的命令重启并启用以应用 Realmd 和 SSSD 服务的修改：

```
$ sudo systemctl restart realmd sssd
$ sudo systemctl enable realmd sssd

```

19、 为了测试 Ubuntu 机器是是否成功集成到 realm ，安装 winbind 包并运行 `wbinfo` 命令列出域账户和群组，如下所示。

```
$ sudo apt-get install winbind
$ wbinfo -u
$ wbinfo -g

```

[![列出域账户](/data/attachment/album/201709/09/181454wnn111fux3ovonv0.jpg)](https://www.tecmint.com/wp-content/uploads/2017/07/List-Domain-Accounts.jpg)

*列出域账户*

20、 同样，也可以针对特定的域用户或群组使用 `getent` 命令检验 Winbind nsswitch 模块。

```
$ sudo getent passwd your_domain_user
$ sudo getent group ‘domain admins’

```

[![检验 Winbind Nsswitch](/data/attachment/album/201709/09/181456w3n6fabun2s392s1.jpg)](https://www.tecmint.com/wp-content/uploads/2017/07/check-Winbind-nsswitch.jpg)

*检验 Winbind Nsswitch*

21、 你也可以用 Linux `id` 命令获取 AD 账户的信息，命令如下：

```
$ id tecmint_user

```

[![检验 AD 用户信息](/data/attachment/album/201709/09/181457o40kbocfkb3bg4b9.jpg)](https://www.tecmint.com/wp-content/uploads/2017/07/Check-AD-User-Info.jpg)

*检验 AD 用户信息*

22、 用 `su -` 后跟上域用户名参数来认证 Ubuntu 主机的一个 Samba4 AD 账户。运行 `id` 命令获取该 AD 账户的更多信息。

```
$ su - your_ad_user

```

[![AD 用户认证](/data/attachment/album/201709/09/181457npiti0065gijz645.jpg)](https://www.tecmint.com/wp-content/uploads/2017/07/AD-User-Authentication.jpg)

*AD 用户认证*

用 `pwd` 命令查看你的域用户当前工作目录，和用 `passwd` 命令修改密码。

23、 在 Ubuntu 上使用有 root 权限的域账户，你需要用下面的命令添加 AD 用户名到 sudo 系统群组：

```
$ sudo usermod -aG sudo your_domain_user@domain.tld

```

用域账户登录 Ubuntu 并运行 `apt update` 命令来更新你的系统以检验 root 权限。

24、 给一个域群组 root 权限，用 `visudo` 命令打开并编辑 `/etc/sudoers` 文件，并加入如下行：

```
%domain\ admins@tecmint.lan              ALL=(ALL:ALL) ALL

```

25、 要在 Ubuntu 桌面使用域账户认证，通过编辑 `/usr/share/lightdm/lightdm.conf.d/50-ubuntu.conf` 文件来修改 LightDM 显示管理器，增加以下两行并重启 lightdm 服务或重启机器应用修改。

```
greeter-show-manual-login=true
greeter-hide-users=true

```

域账户用“你的域用户”或“你的域用户@你的域” 格式来登录 Ubuntu 桌面。

26、 为使用 Samba AD 账户的简称格式，编辑 `/etc/sssd/sssd.conf` 文件，在 `[sssd]` 块加入如下几行命令。

```
full_name_format = %1$s

```

并重启 SSSD 守护进程应用改变。

```
$ sudo systemctl restart sssd

```

你会注意到 bash 提示符会变成了没有附加域名部分的 AD 用户名。

27、 万一你因为 `sssd.conf` 里的 `enumerate=true` 参数设定而不能登录，你得用下面的命令清空 sssd 缓存数据：

```
$ rm /var/lib/sss/db/cache_tecmint.lan.ldb

```

这就是全部了！虽然这个教程主要集中于集成 Samba4 活动目录，同样的步骤也能被用于把使用 Realm 和 SSSD 服务的 Ubuntu 整合到微软 Windows 服务器活动目录。

---

作者简介：

Matei Cezar - 我是一名网瘾少年，开源和基于 linux 系统软件的粉丝，有4年经验在 linux 发行版桌面、服务器和 bash 脚本。

---

via: <https://www.tecmint.com/integrate-ubuntu-to-samba4-ad-dc-with-sssd-and-realm/>

作者：[Matei Cezar](https://www.tecmint.com/author/cezarmatei/) 译者：[XYenChi](https://github.com/XYenChi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


LCTT 四岁了

Summary:
今天，Linux 中国旗下的翻译组 LCTT （Linux 中国翻译组 - Linux.Cn Translation Team）已经成立 4 年了！
作为这四年来，我日常参与的最重要的工作，也是我持续最长时间的一项活动，一时间竟然不知道该写点什么出来。

生日快乐~：D

生日快乐  居然是教师节啊

感谢无私的奉献

来自223.104.63.11的 Chrome Mobile 40.0|Android 6.0 用户

感谢无私奉献的你们

![](/data/attachment/album/201709/10/114210jwwl6ceqcj6jclz1.jpg)

今天，Linux 中国旗下的翻译组 LCTT （<ruby> <a href="https://linux.cn/lctt/">  Linux 中国翻译组 </a> <rp>  （ </rp> <rt>  Linux.Cn Translation Team </rt> <rp>  ） </rp></ruby>）已经成立 4 年了！

作为这四年来，我日常参与的最重要的工作，也是我持续最长时间的一项活动，一时间竟然不知道该写点什么出来。

首先，我列点数据出来吧：

* 成立时间： [2013 年 9 月 10 日](/article-1970-1.html)
* 持续活动时间：[4 年（1461 天）](https://github.com/LCTT/TranslateProject/graphs/contributors)
* 贡献者： [293 人](https://github.com/LCTT/TranslateProject/graphs/contributors)
* 提交：[21556 个](https://github.com/LCTT/TranslateProject/commits/master)
* 星标：[689 个](https://github.com/LCTT/TranslateProject/stargazers)
* 复刻： [518 个](https://github.com/LCTT/TranslateProject/network)
* 拉取请求：[5925 个](https://github.com/LCTT/TranslateProject/pulls)
* 翻译文章：[2833 篇](https://github.com/LCTT/TranslateProject)
* 单人最高翻译篇数：[533 篇](https://linux.cn/lctt/geekpi)（[@geekpi](/article-8729-1.html)）
* 10 篇以上翻译人数：[59 人](https://linux.cn/lctt-list)

以上数据来自 LCTT 主要项目 [TranslateProject](https://github.com/LCTT/TranslateProject) ，其中没有包括 LCTT 的一些次要项目，如 [comic](https://github.com/LCTT/comic)、[LFS-BOOK](https://github.com/LCTT/LFS-BOOK) 等的数据。

下图是 LCTT [TranslateProject](https://github.com/LCTT/TranslateProject) 这 4 年间的提交情况：

![](/data/attachment/album/201709/10/110210g59oyhn729qbhqnk.jpg)

*LCTT TranslateProject 的提交情况*

时光荏苒，几年来，我结识了许多朋友，也不断有新的小伙伴们加入到我们的团队中。我们翻译的文章也屡屡得到读者的好评，以至于在网络上被一再转载（虽然有些转载可耻地删除了作者和我们的译者姓名）。作为 LCTT 创始人，我对团队中的许多成员都耳熟能详，甚至有时候一看翻译的文风都可以可以猜测到译者是谁。虽然也有一些成员逐渐淡出了 LCTT，但是他们做下的功绩，却一直留存在网上，刻在 LCTT 的历史上。需要感谢的人太多，以至于我无法一一列出，我只说一句，愿所有志愿者的 LCTT 经历都是你的人生履历中一段鲜亮的色彩。

今年，我们还专门为 LCTT 开设了[主页](https://linux.cn/lctt)和[贡献排行榜](https://linux.cn/lctt-list)，也为每位译者创建了专属的专页，从他们的专页中，可以看到每位译者的贡献情况，我希望这个页面可以成为每位贡献者收藏到浏览器书签中的地址。

诚然，LCTT 作为一个运行了 4 年的开源志愿者组织，也存在一些问题。比如，翻译和校对的工作的不对等；没有细分几个不同的翻译方向；缺少一个直观的、掩盖了底层 GitHub 操作的流程界面；不能合理量化每位翻译者的贡献程度；次生项目的推进不力等等。我们也希望有更多的同仁一同来参与 LCTT 的建设，使之具有更活跃的生命力，更丰富的生态系统。

那么，这里是 LCTT => <https://linux.cn/lctt> ，你不来看看么？


如何使用拉取请求（PR）来改善你的代码审查

Summary: 通过使用 GitHub 的拉取请求（Pull Request）正确地进行代码审核，把时间更多的花在构建上，而在修复上少用点时间。

>
> 通过使用 GitHub 的<ruby> 拉取请求 <rt>  Pull Request </rt></ruby>正确地进行代码审核，把时间更多的花在构建上，而在修复上少用点时间。
>
>
>

![Measure](/data/attachment/album/201709/10/145704nfxpb1k1z9hkfumf.jpg)

如果你不是每天编写代码，你可能不知道软件开发人员日常面临的一些问题。

* 代码中的安全漏洞
* 导致应用程序崩溃的代码
* 被称作 “技术债务” 和之后需要重写的代码
* 在某处你所不知道地方的代码已经被重写

<ruby> 代码审查 <rt>  Code review </rt></ruby>可以允许其他的人或工具来检查代码，帮助我们改善所编写的软件。这种审查（也称为<ruby> 同行评审 <rt>  peer review </rt></ruby>）能够通过自动化代码分析或者测试覆盖工具来进行，是软件开发过程中两个重要的部分，它能够节省数小时的手工工作。同行评审是开发人员审查彼此工作的一个过程。在软件开发的过程中，速度和紧迫性是两个经常提及的问题。如果你没有尽快的发布，你的竞争对手可能会率先发布新功能。如果你不能够经常发布新的版本，你的用户可能会怀疑您是否仍然关心改进你的应用程序。

### 权衡时间：代码审查与缺陷修复

如果有人能够以最小争议的方式汇集多种类型的代码审查，那么随着时间的推移，该软件的质量将会得到改善。如果认为引入新的工具或流程最先导致的不是延迟，那未免太天真了。但是代价更高昂的是：修复生产环境中的错误花费的时间，或者在放到生产环境之前改进软件所花费的时间。即使新工具延迟了新功能的发布和得到客户欣赏的时间，但随着软件开发人员提高自己的技能，该延迟会缩短，软件开发周期将会回升到以前的水平，而同时缺陷将会减少。

通过代码审查实现提升代码质量目标的关键之一就是使用一个足够灵活的平台，允许软件开发人员快速编写代码，置入他们熟悉的工具，并对彼此进行同行评审。 GitHub 就是这样的平台的一个很好的例子。然而，只是把你的代码放在 [GitHub](https://github.com/about) 上并不会魔术般地使代码审查发生；你必须使用<ruby> 拉取请求 <rt>  Pull Request </rt></ruby>来开始这个美妙的旅程。

### 拉取请求：关于代码的现场讨论

<ruby> <a href="https://help.github.com/articles/about-pull-requests/">  拉取请求 </a> <rt>  Pull Request </rt></ruby>是 Github 上的一个工具，允许软件开发人员讨论并提出对项目的主要代码库的更改，这些更改稍后可以部署给所有用户看到。这个功能创建于 2008 年 2 月，其目的是在接受（合并）之前，对某人的建议进行更改，然后在部署到生产环境中，供最终用户看到这种变化。

拉取请求开始是以一种松散的方式让你为某人的项目提供更改，但是它们已经演变成：

* 关于你想要合并的代码的现场讨论
* 提升了所更改内容的可视功能
* 整合了你最喜爱的工具
* 作为受保护的分支工作流程的一部分可能需要显式的拉取请求评审

### 对于代码：URL 是永久的

看看上述的前两个点，拉取请求促成了一个正在进行的代码讨论，使代码变更可以更醒目，并且使您很容易在审查的过程中找到所需的代码。无论是对于新人还是有经验的开发人员，能够回顾以前的讨论，了解一个功能为什么以这种方式开发出来，或者与另一个相关功能的讨论相联系起来是无价的。当跨多个项目协调，并使每个人尽可能接近代码时，前后讨论的内容也非常重要。如果这些功能仍在开发中，重要的是能够看到上次审查以来更改了哪些内容。毕竟，[审查小的更改要比大的容易得多](https://blog.skyliner.io/ship-small-diffs-741308bec0d1)，但不可能全都是小功能。因此，重要的是能够找到你上次审查，并只看到从那时以来的变化。

### 集成工具：软件开发人员的偏执

再看下上述第三点，GitHub 上的拉取请求有很多功能，但开发人员总是偏好第三方工具。代码质量是个完整的代码审查领域，它涉及到其它组件的代码评审，而这些评审不一定是由人完成的。检测“低效”或缓慢的代码、具有潜在安全漏洞或不符合公司标准的代码是留给自动化工具的任务。类似 [SonarQube](https://github.com/integrations/sonarqube) 和 [Code Climatecan](https://github.com/integrations/code-climate) 这样工具可以分析你的代码，而像 [Codecov](https://github.com/integrations/codecov) 和 [Coveralls](https://github.com/integrations/coveralls) 的这样工具可以告诉你刚刚写的新代码还没有得到很好的测试。这些工具最令人称奇的是，它们可以集成到 GitHub 中，并把它们的发现汇报到拉取请求当中！这意味着该过程中不仅是人们在审查代码，而且工具也在会在那里报告情况。这样每个人都可以完全了解一个功能是如何开发的。

最后，根据您的团队的偏好，您可以利用[受保护的分支工作流](https://help.github.com/articles/about-protected-branches/)的<ruby> 必需状态 <rt>  required status </rt></ruby>功能来要求进行工具审查和同行评审。

虽然您可能只是刚刚开始您的软件开发之旅，或者是一位希望知道项目正在做什么的业务利益相关者，抑或是一位想要确保项目的及时性和质量的项目经理，你都可以通过设置批准流程来参与到拉取请求中，并考虑集成更多的工具以确保质量，这在任何级别的软件开发中都很重要。

无论是为您的个人网站，贵公司的在线商店，还是想用最新的组合以获得最大的收获，编写好的软件都需要进行良好的代码审查。良好的代码审查涉及到正确的工具和平台。要了解有关 GitHub 和软件开发过程的更多信息，请参阅 O'Reilly 的 《[GitHub 简介](https://www.safaribooksonline.com/library/view/introducing-github/9781491949801/?utm_source=newsite&utm_medium=content&utm_campaign=lgen&utm_content=how-to-use-pull-requests-to-improve-your-code-reviews-lower)》 一书， 您可以在其中了解创建项目、启动拉取请求以及概要了解团队的软件开发流程。

---

作者简介：

**Brent Beer**

Brent Beer 通过大学的课程、对开源项目的贡献，以及担任专业网站开发人员使用 Git 和 GitHub 已经超过五年了。在担任 GitHub 上的培训师时，他也成为 O’Reilly 的 《GitHub 简介》的出版作者。他在阿姆斯特丹担任 GitHub 的解决方案工程师，帮助 Git 和 GitHub 向世界各地的开发人员提供服务。

**Peter Bell**

Peter Bell 是 Ronin 实验室的创始人以及 CTO。培训是存在问题的，我们通过技术提升培训来改进它！他是一位有经验的企业家、技术专家、敏捷教练和 CTO，专门从事 EdTech 项目。他为 O'Reilly 撰写了 《GitHub 简介》，为代码学校创建了“精通 GitHub ”课程，为 Pearson 创建了“ Git 和 GitHub 现场课”课程。他经常在国际和国际会议上发表 ruby、 nodejs、NoSQL（尤其是 MongoDB 和 neo4j ）、云计算、软件工艺、java、groovy、j 等的演讲。

---

via: <https://www.oreilly.com/ideas/how-to-use-pull-requests-to-improve-your-code-reviews>

作者：[Brent Beer](https://www.oreilly.com/people/acf937de-cdf4-4b0e-85bd-b559404c580e), [Peter Bell](https://www.oreilly.com/people/2256f119-7ea0-440e-99e8-65281919e952) 译者：[MonkeyDEcho](https://github.com/MonkeyDEcho) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


漫谈传统的 Linux 初始化系统的运行级别

Summary: 了解运行级别是如何配置的，如何改变系统运行级别以及修改对应状态下运行的服务。

原文好歹提一下 systemd 嘛，结果通篇没有。在 debian 都要上 wayland 的今天，介绍 runlevel，实在有些脱离现实。

readlink $(which runlevel)

../bin/systemctl

runlevel有点过时啦!

我也不知道为什么会有这样一篇过时的文章——不过有些遗留系统还是用的这个。

了解运行级别是如何配置的，如何改变系统运行级别以及修改对应状态下运行的服务。

>
> 了解运行级别是如何配置的，如何改变系统运行级别以及修改对应状态下运行的服务。
>
>
>

![操作 Linux 的运行级别](/data/attachment/album/201709/10/212729wmyh9hzy35umhp0d.jpg)

在 Linux 系统中，<ruby> 运行级别 <rt>  run level </rt></ruby>是指运维的级别，用于描述一种表明什么服务是可用的系统运行状态。

运行级别 1 是严格限制的，仅仅用于系统维护；该级别下，网络连接将不可操作，但是管理员可以通过控制台连接登录系统。

其他运行级别的系统允许任何人登录和使用，但是不同级别中可使用的服务不同。本文将探索如何配置运行级别，如何交互式改变系统运行级别以及修改该状态下可用的服务。

Linux 系统的默认运行状态是一个在系统开机时使用的运行级别（除非有其他的指示），它通常是在 `/etc/inittab` 文件中进行配置的，该文件内容通常如下：

```
id:3:initdefault

```

包括 Debian 系统在内的一些系统，默认运行级别为 2，而不是上述文件中的 3，甚至都没有 `/etc/inittab` 文件。

运行级别在默认情况下是如何被配置，其配置依赖于你所运行的 Linux 操作系统的具体发行版本。 例如，在某些系统中， 运行级别 2 是多用户模式，运行级别 3 是多用户模式并支持 NFS （网络文件系统）。 在另外一些系统，运行级别 2 - 5 基本相同，运行级别 1 是单用户模式。例如，Debian 系统的所用运行级别如下：

```
0 = 停机
1 = 单用户（维护模式）
2 = 多用户模式
3-5 = 同 2 一样
6 = 重启

```

在 Linux 系统上，运行级别 3 用于共享文件系统给其它系统，可以方便地只通过改变系统的运行级别来启动和停止文件系统共享。系统从运行级别 2 改变到 3 系统将允许文件系统共享，反之从运行级别 3 改变到 2 则系统不支持文件系统共享。

在某个运行级别中，系统运行哪些进程依赖于目录 `/etc/rc?.d` 目录的内容，其中 `?` 可以是 2、 3、 4 或 5 （对应于相应的运行级别）。

在以下示例中（Ubuntu 系统），由于这些目录的配置是相同的，我们将看见上述 4 个级别对应的目录中的内容是一致的。

```
/etc/rc2.d$ ls
README         S20smartmontools      S50saned      S99grub-common
S20kerneloops  S20speech-dispatcher  S70dns-clean  S99ondemand
S20rsync       S20sysstat            S70pppd-dns   S99rc.local
/etc/rc2.d$ cd ../rc3.d
/etc/rc3.d$ ls
README         S20smartmontools      S50saned      S99grub-common
S20kerneloops  S20speech-dispatcher  S70dns-clean  S99ondemand
S20rsync       S20sysstat            S70pppd-dns   S99rc.local
/etc/rc3.d$ cd ../rc4.d
/etc/rc4.d$ ls
README         S20smartmontools      S50saned      S99grub-common
S20kerneloops  S20speech-dispatcher  S70dns-clean  S99ondemand
S20rsync       S20sysstat            S70pppd-dns   S99rc.local
/etc/rc4.d$ cd ../rc5.d
/etc/rc5.d$ ls
README         S20smartmontools      S50saned      S99grub-common
S20kerneloops  S20speech-dispatcher  S70dns-clean  S99ondemand
S20rsync       S20sysstat            S70pppd-dns   S99rc.local

```

这些都是什么文件？它们都是指向 `/etc/init.d` 目录下用于启动服务的脚本符号连接。 这些文件的文件名是至关重要的， 因为它们决定了这些脚本文件的执行顺序，例如, S20 脚本是在 S50 脚本前面运行的。

```
$ ls -l
total 4
-rw-r--r-- 1 root root 677 Feb 16  2016 README
lrwxrwxrwx 1 root root  20 Aug 30 14:40 S20kerneloops -> ../init.d/kerneloops
lrwxrwxrwx 1 root root  15 Aug 30 14:40 S20rsync -> ../init.d/rsync
lrwxrwxrwx 1 root root  23 Aug 30 16:10 S20smartmontools -> ../init.d/smartmontools
lrwxrwxrwx 1 root root  27 Aug 30 14:40 S20speech-dispatcher -> ../init.d/speech-dispatcher
lrwxrwxrwx 1 root root  17 Aug 31 14:12 S20sysstat -> ../init.d/sysstat
lrwxrwxrwx 1 root root  15 Aug 30 14:40 S50saned -> ../init.d/saned
lrwxrwxrwx 1 root root  19 Aug 30 14:40 S70dns-clean -> ../init.d/dns-clean
lrwxrwxrwx 1 root root  18 Aug 30 14:40 S70pppd-dns -> ../init.d/pppd-dns
lrwxrwxrwx 1 root root  21 Aug 30 14:40 S99grub-common -> ../init.d/grub-common
lrwxrwxrwx 1 root root  18 Aug 30 14:40 S99ondemand -> ../init.d/ondemand
lrwxrwxrwx 1 root root  18 Aug 30 14:40 S99rc.local -> ../init.d/rc.local

```

如你所想，目录 `/etc/rc1.d` 因运行级别 1 的特殊而不同。它包含的符号链接指向非常不同的一套脚本。 同样也要注意到其中一些脚本以 `K` 开头命名，而另一些与其它运行级别脚本一样以 `S` 开头命名。这是因为当系统进入单用户模式时， 一些服务需要**停止**。 然而这些 K 开头的符号链接指向了其它级别 S 开头的符号链接的同一文件时， K（kill）表示这个脚本将以指示其停止的参数执行，而不是以启动的参数执行。

```
/etc/rc1.d$ ls -l
total 4
lrwxrwxrwx 1 root root  20 Aug 30 14:40 K20kerneloops -> ../init.d/kerneloops
lrwxrwxrwx 1 root root  15 Aug 30 14:40 K20rsync -> ../init.d/rsync
lrwxrwxrwx 1 root root  15 Aug 30 14:40 K20saned -> ../init.d/saned
lrwxrwxrwx 1 root root  23 Aug 30 16:10 K20smartmontools -> ../init.d/smartmontools
lrwxrwxrwx 1 root root  27 Aug 30 14:40 K20speech-dispatcher -> ../init.d/speech-dispatcher
-rw-r--r-- 1 root root 369 Mar 12  2014 README
lrwxrwxrwx 1 root root  19 Aug 30 14:40 S30killprocs -> ../init.d/killprocs
lrwxrwxrwx 1 root root  19 Aug 30 14:40 S70dns-clean -> ../init.d/dns-clean
lrwxrwxrwx 1 root root  18 Aug 30 14:40 S70pppd-dns -> ../init.d/pppd-dns
lrwxrwxrwx 1 root root  16 Aug 30 14:40 S90single -> ../init.d/single

```

你可以改变系统的默认运行级别，尽管这很少被用到。例如，通过修改前文中提到的 `/etc/inittab` 文件，你能够配置 Debian 系统的默认运行级别为 3 （而不是 2），以下是该文件示例：

```
id:3:initdefault:

```

一旦你修改完成并重启系统， `runlevel` 命令将显示如下：

```
$ runlevel
N 3

```

另外一种可选方式，使用 `init 3` 命令，你也能改变系统运行级别（且无需重启立即生效）， `runlevel` 命令的输出为：

```
$ runlevel
2 3

```

当然，除非你修改了系统默认级别的 `/etc/rc?.d` 目录下的符号链接，使得系统默认运行在一个修改的运行级别之下，否则很少需要通过创建或修改 `/etc/inittab` 文件改变系统的运行级别。

### 在 Linux 系统中如何使用运行级别？

为了扼要重述在系统中如何使用运行级别，下面有几个关于运行级别的快速问答问题：

**如何查询系统当前的运行级别？**

使用 `runlevel` 命令。

**如何查看特定运行级别所关联的服务进程？**

查看与该运行级别关联的运行级别开始目录（例如， `/etc/rc2.d` 对应于运行级别 2）。

**如何查看系统的默认运行级别？**

首先，查看 `/etc/inittab` 文件是否存在。如果不存在，就执行 `runlevel` 命令查询，你一般就已经处在该运行级别。

**如何改变系统运行级别？**

用 `init` 命令（例如 `init 3`）临时改变运行级别，通过修改或创建 `/etc/inittab` 文件永久改变其运行级别。

**能改变特定运行级别下运行的服务么？**

当然，通过改变对应的 `/etc/rc?.d` 目录下的符号连接即可。

**还有一些其他的什么需要考虑？**

当改变系统运行级别时，你应该特别小心，确保不影响到系统上正在运行的服务或者正在使用的用户。

（题图：[Vincent Desjardins](https://www.flickr.com/photos/endymion120/4824696883/in/photolist-8mkQi2-8vtyRx-8vvYZS-i31xQj-4TXTS2-S7VRNC-azimYK-dW8cYu-Sb5b7S-S7VRES-fpSVvo-61Zpn8-WxFwGi-UKKq3x-q6NSnC-8vsBLr-S3CPxn-qJUrLr-nDnpNu-8d7a6Q-T7mGpN-RE26wj-SeEXRa-5mZ7LG-Vp7t83-fEG5HS-Vp7sU7-6JpNBi-RCuR8P-qLzCL5-6WsfZx-5nU1tF-6ieGFi-3P5xwh-8mnxpo-hBXwSj-i3iCur-9dmrST-6bXk8d-8vtDb4-i2KLwU-5jhfU6-8vwbrN-ShAtNm-XgzXmb-8rad18-VfXm4L-8tQTrh-Vp7tcb-UceVDB) [(CC BY 2.0)](https://creativecommons.org/licenses/by/2.0/legalcode)）

---

via: <https://www.networkworld.com/article/3222070/linux/maneuvering-around-run-levels-on-linux.html>

作者：[Sandra Henry-Stocker](https://www.networkworld.com/author/Sandra-Henry_Stocker/) 译者：[penghuster](https://github.com/penghuster) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


Kubernetes 是什么？

Summary: Kubernetes，简称 k8s（k，8 个字符，s——明白了？）或者 “kube”，是一个开源的 Linux 容器自动化运维平台，它消除了容器化应用程序在部署、伸缩时涉及到的许多手动操作。

![](/data/attachment/album/201709/10/223444v5al5nfs70zm09gz.png)

Kubernetes，简称 k8s（k，8 个字符，s——明白了？）或者 “kube”，是一个开源的 [Linux 容器](https://www.redhat.com/en/containers/whats-a-linux-container)自动化运维平台，它消除了容器化应用程序在部署、伸缩时涉及到的许多手动操作。换句话说，你可以将多台主机组合成集群来运行 Linux 容器，而 Kubernetes 可以帮助你简单高效地管理那些集群。构成这些集群的主机还可以跨越[公有云](https://www.redhat.com/en/topics/cloud-computing/what-is-public-cloud)、[私有云](https://www.redhat.com/en/topics/cloud-computing/what-is-private-cloud)以及混合云。

Kubernetes 最开始是由 Google 的工程师设计开发的。Google 作为 [Linux 容器技术的早期贡献者](https://en.wikipedia.org/wiki/Cgroups)之一，曾公开演讲介绍 [Google 如何将一切都运行于容器之中](https://speakerdeck.com/jbeda/containers-at-scale)（这是 Google 的云服务背后的技术）。Google 一周内的容器部署超过 20 亿次，全部的工作都由内部平台 [Borg](http://blog.kubernetes.io/2015/04/borg-predecessor-to-kubernetes.html) 支撑。Borg 是 Kubernetes 的前身，几年来开发 Borg 的经验教训也成了影响 Kubernetes 中许多技术的主要因素。

*趣闻: Kubernetes logo 中的七个辐条来源于项目原先的名称, “[Seven of Nine 项目](https://cloudplatform.googleblog.com/2016/07/from-Google-to-the-world-the-Kubernetes-origin-story.html)”（LCTT 译注：Borg 是「星际迷航」中的一个宇宙种族，Seven of Nine 是该种族的一名女性角色）。*

![](/data/attachment/album/201709/10/223447rd0hj5bvhv84jd24.png)

红帽作为最早与 Google 合作开发 Kubernetes 的公司之一（甚至早于 Kubernetes 的发行），已经是 Kubernetes 上游项目的[第二大贡献者](http://stackalytics.com/?project_type=kubernetes-group&metric=commits)。Google 在 2015 年把 Kubernetes 项目捐献给了新成立的 <ruby> <a href="https://www.cncf.io/">  云计算基金会 </a> <rt>  Cloud Native Computing Foundation </rt></ruby>（CNCF）。

### 为什么你需要 Kubernetes ？

真实的生产环境应用会包含多个容器，而这些容器还很可能会跨越多个服务器主机部署。Kubernetes 提供了为那些工作负载大规模部署容器的编排与管理能力。Kubernetes 编排让你能够构建多容器的应用服务，在集群上调度或伸缩这些容器，以及管理它们随时间变化的健康状态。

Kubernetes 也需要与网络、存储、安全、监控等其它服务集成才能提供综合性的容器基础设施。

![Kubernetes 解释－图表](/data/attachment/album/201709/10/223447ewa7a8tpq7hrqldy.png "Kubernetes 解释－图表")

当然，这取决于你如何在你的环境中使用容器。一个初步的 Linux 容器应用程序把容器视作高效、快速的虚拟机。一旦把它部署到生产环境或者扩展为多个应用，很显然你需要许多组托管在相同位置的容器合作提供某个单一的服务。随着这些容器的累积，你的运行环境中容器的数量会急剧增加，复杂度也随之增长。

Kubernetes 通过将容器分类组成 “pod” 来解决了容器增殖带来的许多常见问题。pod 为容器分组提供了一层抽象，以此协助你调度工作负载以及为这些容器提供类似网络与存储这类必要的服务。Kubernetes 的其它组件帮助你对 pod 进行负载均衡，以保证有合适数量的容器支撑你的工作负载。

正确实施的 Kubernetes，结合类似 [Atomic Registry](http://www.projectatomic.io/registry/)、[Open vSwitch](http://openvswitch.org/)、[heapster](https://github.com/kubernetes/heapster)、[OAuth](https://oauth.net/) 和 [SELinux](https://selinuxproject.org/page/Main_Page) 的开源项目，让你可以管理你自己的整个容器基础设施。

### Kubernetes 能做些什么？

在生产环境中使用 Kubernetes 的主要优势在于它提供了在物理机或虚拟机集群上调度和运行容器的平台。更宽泛地说，它能帮你在生产环境中实现可以依赖的基于容器的基础设施。而且，由于 Kubernetes 本质上就是运维任务的自动化平台，你可以执行一些其它应用程序平台或管理系统支持的操作，只不过操作对象变成了容器。

有了 Kubernetes，你可以：

* 跨主机编排容器。
* 更充分地利用硬件资源来最大化地满足企业应用的需求。
* 控制与自动化应用的部署与升级。
* 为有状态的应用程序挂载和添加存储器。
* 线上扩展或裁剪容器化应用程序与它们的资源。
* 声明式的容器管理，保证所部署的应用按照我们部署的方式运作。
* 通过自动布局、自动重启、自动复制、自动伸缩实现应用的状态检查与自我修复。

然而 Kubernetes 依赖其它项目来提供完整的编排服务。结合其它开源项目作为其组件，你才能充分感受到 Kubernetes 的能力。这些必要组件包括：

* 仓库：Atomic Registry、Docker Registry 等。
* 网络：OpenvSwitch 和智能边缘路由等。
* 监控：heapster、kibana、hawkular 和 elastic。
* 安全：LDAP、SELinux、 RBAC 与 支持多租户的 OAUTH。
* 自动化：通过 Ansible 的 playbook 进行集群的安装和生命周期管理。
* 服务：大量事先创建好的常用应用模板。

[红帽 OpenShift 为容器部署预先集成了上面这些组件。](https://www.redhat.com/en/technologies/cloud-computing/openshift)

### Kubernetes 入门

和其它技术一样，大量的专有名词有可能成为入门的障碍。下面解释一些通用的术语，希望帮助你理解 Kubernetes。

* **Master（主节点）：** 控制 Kubernetes 节点的机器，也是创建作业任务的地方。
* **Node（节点）：** 这些机器在 Kubernetes 主节点的控制下执行被分配的任务。
* **Pod：** 由一个或多个容器构成的集合，作为一个整体被部署到一个单一节点。同一个 pod 中的容器共享 IP 地址、进程间通讯（IPC）、主机名以及其它资源。Pod 将底层容器的网络和存储抽象出来，使得集群内的容器迁移更为便捷。
* **Replication controller（复制控制器）：** 控制一个 pod 在集群上运行的实例数量。
* **Service（服务）：** 将服务内容与具体的 pod 分离。Kubernetes 服务代理负责自动将服务请求分发到正确的 pod 处，不管 pod 移动到集群中的什么位置，甚至可以被替换掉。
* **Kubelet：** 这个守护进程运行在各个工作节点上，负责获取容器列表，保证被声明的容器已经启动并且正常运行。
* **kubectl：** 这是 Kubernetes 的命令行配置工具。

[上面这些知识就足够了吗？不，这仅仅是一小部分，更多内容请查看 Kubernetes 术语表。](https://kubernetes.io/docs/reference/)

### 生产环境中使用 Kubernetes

Kubernetes 是开源的，所以没有正式的技术支持机构为你的商业业务提供支持。如果在生产环境使用 Kubernetes 时遇到问题，你恐怕不会太愉快，当然你的客户也不会太高兴。

这就是[红帽 OpenShift](https://www.redhat.com/en/technologies/cloud-computing/openshift) 要解决的问题。OpenShift 是为企业提供的 Kubernetes ——并且集成了更多的组件。OpenShift 包含了强化 Kubernetes 功能、使其更适用于企业场景的额外部件，包括仓库、网络、监控、安全、自动化和服务在内。OpenShift 使得开发者能够在具有伸缩性、控制和编排能力的云端开发、托管和部署容器化的应用，快速便捷地把想法转变为业务。

而且，OpenShift 还是由头号开源领导公司红帽支持和开发的。

### Kubernetes 如何适用于你的基础设施

![Kubernetes 图表](/data/attachment/album/201709/10/223447rv2xkrdckn4rrafa.png "Kubernetes 图表")

Kubernetes 运行在操作系统（例如 [Red Hat Enterprise Linux Atomic Host](https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux/options)）之上，操作着该节点上运行的容器。Kubernetes 主节点（master）从管理员（或者 DevOps 团队）处接受命令，再把指令转交给附属的节点。这种带有大量服务的切换工作自动决定最适合该任务的节点，然后在该节点上分配资源并指派 pod 来完成任务请求。

所以从基础设施的角度，管理容器的方式发生了一点小小的变化。对容器的控制在更高的层次进行，提供了更佳的控制方式，而无需用户微观管理每个单独的容器或者节点。必要的工作则主要集中在如何指派 Kubernetes 主节点、定义节点和 pod 等问题上。

#### docker 在 Kubernetes 中的角色

[Docker](https://www.redhat.com/en/containers/what-is-docker) 技术依然执行它原本的任务。当 kubernetes 把 pod 调度到节点上，节点上的 kubelet 会指示 docker 启动特定的容器。接着，kubelet 会通过 docker 持续地收集容器的信息，然后提交到主节点上。Docker 如往常一样拉取容器镜像、启动或停止容器。不同点仅仅在于这是由自动化系统控制而非管理员在每个节点上手动操作的。

---

via: <https://www.redhat.com/en/containers/what-is-kubernetes>

作者：[www.redhat.com](https://www.redhat.com/) 译者：[haoqixu](https://github.com/haoqixu) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出