From a4c5954a7387409420ff73d282cf5b704affc7a6 Mon Sep 17 00:00:00 2001 From: songshunqiang Date: Tue, 29 May 2018 22:13:12 +0800 Subject: [PATCH 01/14] submit tech/20180404 Containerization, Atomic Distributions, and the Future of Linux.md --- ... Distributions, and the Future of Linux.md | 90 ------------------- ... Distributions, and the Future of Linux.md | 88 ++++++++++++++++++ 2 files changed, 88 insertions(+), 90 deletions(-) delete mode 100644 sources/tech/20180404 Containerization, Atomic Distributions, and the Future of Linux.md create mode 100644 translated/tech/20180404 Containerization, Atomic Distributions, and the Future of Linux.md diff --git a/sources/tech/20180404 Containerization, Atomic Distributions, and the Future of Linux.md b/sources/tech/20180404 Containerization, Atomic Distributions, and the Future of Linux.md deleted file mode 100644 index 1fa68f3778..0000000000 --- a/sources/tech/20180404 Containerization, Atomic Distributions, and the Future of Linux.md +++ /dev/null @@ -1,90 +0,0 @@ -pinewall translating - -Containerization, Atomic Distributions, and the Future of Linux -====== - -![](https://www.linux.com/sites/lcom/files/styles/rendered_file/public/atomic-distro.jpg?itok=SjBeCDtq) -Linux has come a long way since Linus Torvalds announced it in 1991. It has become the dominant operating system in the enterprise space. And, although we’ve seen improvements and tweaks in the desktop environment space, the model of a typical Linux distribution has largely remained the same over the past 25+ years. The traditional package management based model has dominated both the desktop and server space. - -However, things took an interesting turn when Google launched Linux-based Chrome OS, which deployed an image-based model. Core OS (now owned by Red Hat) came out with an operating system (Container Linux) that was inspired by Google but targeted at enterprise customers. - -Container Linux changed the way operating systems update. It changed the way applications were delivered and updated. Is this the future of Linux distributions? Will it replace the traditional package-based distribution model? - -### Three models - -Matthias Eckermann, Director of Product Management for SUSE Linux Enterprise, thinks there are not two but three models. “Outside of the traditional (RHEL/SLE) and the image-based model (RH Atomic Host), there is a third model: transactional. This is where [SUSE CaaS Platform][1] and its SUSE MicroOS lives,” said Eckermann. - -### What’s the difference? - -Those who live in Linux land are very well aware of the traditional model. It’s made up of single packages and shared libraries. This model has its own benefit as application developers don’t have to worry about bundling libraries with their apps. There is no duplication, which keeps the system lean and thin. It also saves bandwidth as users don’t have to download a lot of packages. Distributions have total control over packages so security issues can be fixed easily by pushing updates at the system level. - -“Traditional packaging continues to provide the opportunity to carefully craft and tune an operating system to support mission-critical workloads that need to stand the test of time,” said Ron Pacheco, Director of Product Management at Red Hat Enterprise Linux. - -But the traditional model has some disadvantages, too. App developers must restrict themselves to the libraries shipped with the distro, which means they can’t take advantage of new packages for their apps if the distro doesn’t support them. It could also lead to conflict between two different versions. As a result, it creates administration challenges as they are often difficult to keep updated and in sync. - -### Image-based Model - -That’s where the image based model comes to the rescue. “The image-based model solves the problems of the traditional model as it replaces the operating system at every reiteration and doesn't work with single packages,” said Eckermann. - -“When we talk about the operating system as an image, what we’re really talking about is developing and deploying in a programmatic way and with better integrated life cycle management,” said Pacheco, giving the example of OpenShift, which is built on top of Red Hat Enterprise Linux. - -Pacheco sees the image-based OS as a continuum, from hand-tooling a deployed image to a heavily automated infrastructure that can be managed at a large scale; regardless of where a customer is on this range, the same applications have to run. “You don't want to create a silo by using a wholly different deployment model,” he said. - -The image-based model replaces the entire OS with new libraries and packages, which introduces its own set of problems. The image-based model has to be reconstructed to meet the needs of specific environments. For example, if the user has a specific need for installing a specific hardware driver or low-level monitoring option, the image model fails, or options to have finer granularity have to be re-invented. - -### Transactional model - -The third model is transactional updates, which follows the traditional package-based updates, but instead handles all packages as if they were images, updating all the packages that belong together in one shot like an image. - -“The difference is because they are single packages that are grouped together as well as on descending and the installation, the customer has the option to influence this if necessary. This gives the user extra flexibility by combining the benefits of both and avoiding the disadvantages associated with the traditional or image model,” said Eckermann. - -Pacheco said that it’s becoming increasingly common for carefully crafted workloads to be deployed as images in order to deploy consistently, reliably, and to do so with elasticity. “This is what we see our customers do today when they create and deploy virtual machines on premises or on public/private clouds as well as on traditional bare metal deployments,” he said. - -Pacheco suggests that we should not look at these models as strictly a “compare and contrast scenario,” but rather as an evolution and expansion of the operating system’s role. - -### Arrival of Atomic Updates - -Google’s Chrome OS and the Core OS popularized the concept of transactional updates, a model followed by both Red Hat and SUSE. - -“The real problem is the operating system underlining the container host operating system is not in focus anymore -- at least not in a way the administrator should care about. Both RH Atomic Host and SUSE CaaS Platform solve this problem similarly from a user experience perspective,” said Eckermann. - -[Immutable infrastructure][2], such as that provided by SUSE CaaS Platform, Red Hat Atomic Host, and Container Linux (formerly Core OS), encourages the use of transactional updates. “Having a model where the host always moves to a ‘known good’ state enables better confidence with updates, which in turn enables a faster flow of features, security benefits, and an easier-to-adopt operational model,” said Ben Breard, senior technology product manager, Red Hat. - -These newer OSes isolate the applications from the underlying host with Linux containers thereby removing many of the traditional limitations associated with infrastructure updates. - -“The real power and benefits are realized when the orchestration layer is intelligently handling the updates, deployments, and, ultimately, seamless operations,” added Breard. - -### The Future - -What does the future hold for Linux? The answer really depends on who you ask. Container players will say the future belongs to containerized OS, but Linux vendors who still have a huge market may disagree. - -When asked if, in the long run, atomic distros will replace traditional distributions, Eckermann said, “If I say yes, then I am following the trend; if I say no, I will be considered old-fashioned. Nevertheless, I say no: atomic distros will not replace traditional distros in the long run -- but traditional workloads and containerized workloads will live together in data centers as well as private and public cloud environments.” - -Pacheco maintained that the growth in Linux deployments, in general, makes it difficult to imagine one model replacing the other. He said that instead of looking at them as competing models, we should look at atomic distributions as part of the evolution and deployment of the operating system. - -Additionally, there are many use-cases that may need a mix of both species of Linux distributions. “Imagine the large number of PL/1 and Cobol systems in banks and insurance companies. Think about in-memory databases and core data bus systems,” said Eckermann. - -Most of these applications can’t be containerized. As much as we would like to think, containerization is not a silver bullet that solves every problem. There will always be a mix of different technologies. - -Eckermann believes that over time, a huge number of new developments and deployments will go into containerization, but there is still good reason to keep traditional deployment methods and applications in the enterprise. - -“Customers need to undergo business, design, and cultural transformations in order to maximize the advantages that container-based deployments are delivering. The good news is that the industry understands this, as a similar transformation at scale occurred with the historical moves from mainframes to UNIX to x86 to virtualization,” said Pacheco. - -### Conclusion - -It’s apparent that the volume of containerized workloads will increase in the future, which translates into more demand for atomic distros. In the meantime, a substantial percentage of workloads may remain on traditional distros that will keep them running. What really matters is that both players have invested heavily in new models and are ready to tweak their strategy as the market evolves. An external observer can clearly see that the future belongs to transactional/atomic models. We have seen the evolution of datacenter; we have come a long way from one application per server to function-as-a-service model. It is not far fetched to see Linux distros entering the atomic phase. - --------------------------------------------------------------------------------- - -via: https://www.linux.com/blog/2018/4/containerization-atomic-distributions-and-future-linux - -作者:[SWAPNIL BHARTIYA][a] -译者:[译者ID](https://github.com/译者ID) -校对:[校对者ID](https://github.com/校对者ID) -选题:[lujun9972](https://github.com/lujun9972) - -本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 - -[a]:https://www.linux.com/users/arnieswap -[1]:https://www.suse.com/products/caas-platform/ -[2]:https://www.digitalocean.com/community/tutorials/what-is-immutable-infrastructure diff --git a/translated/tech/20180404 Containerization, Atomic Distributions, and the Future of Linux.md b/translated/tech/20180404 Containerization, Atomic Distributions, and the Future of Linux.md new file mode 100644 index 0000000000..2a75db4754 --- /dev/null +++ b/translated/tech/20180404 Containerization, Atomic Distributions, and the Future of Linux.md @@ -0,0 +1,88 @@ +容器化,原子化发行版以及 Linux 的未来 +====== + +![](https://www.linux.com/sites/lcom/files/styles/rendered_file/public/atomic-distro.jpg?itok=SjBeCDtq) +自从 Linus Torvalds 在 1991 年发布 Linux 以来,Linux 已历经漫长的岁月。它已经成为企业级领域的主流操作系统。同时,我们看到桌面级领域出现了很多改进和调整,但在过去的 25+ 年,主流 Linux 发行版的模式很大程度上保持不变。基于软件包管理的传统模式依然统治着桌面级和服务器级市场。 + +但随着 Google 发布了基于 Linux 的 Chrome-OS,情况出现了微妙的转变,Chrome-OS 采用镜像模式。Core OS (目前归属于 Red Hat) 受 Google 启发推出了一款操作系统 Container Linux,主要面向企业级用户。 + +Container Linux 改变了操作系统更新的方式,也改变了应用分发和更新的方式。这会是 Linux 发行版的未来吗?这是否会取代基于软件包的传统发行版模式呢? + +### 三种模式 + +SLE (SUSE Linux Enterprise) 的产品管理总监 Matthias Eckermann 认为目前存在 3 种模式,而不是 2 种。Eckermann 提到:“除了传统模式 (RHEL/SLE) 和镜像模式 (RH Atomic Host),还存在第三种模型:事务模式。[SUSE CaaS 平台][1] 及 SUSE MicroOS 就采用这种模型。” + +### 差异有哪些 + +Linux 用户对传统模式非常熟悉,它由独立的软件包和共享库组成。这种模式有独特的优势,让应用开发者无需将共享库捆绑在应用中。由于库不会多次引入,使得系统简洁和轻便。这也让用户无需下载很多软件包,节省了带宽。发行版对软件包全权负责,通过推送系统级别的更新,可以轻松地解决安全隐患。 + +RHEL (Red Hat Enterprise Linux) 的产品管理总监 Ron Pacheco 表示,“传统的打包方式继续为我们提供精心构建和优化操作系统的机会,以便支持需要经过时间考验的任务关键型工作负载。” + +但传统模式也有一些弊端。应用开发者受限使用发行版包含的库,使其无法从发行版不支持的新软件中获益。这也可能导致不同版本之间相互冲突。最终,传统模式给管理员增加了挑战,使其难以让软件包一直处于最新版本状态。 + +### 镜像模式 + +镜像模式应运而生。Eckermann 表示,“镜像模式解决了传统模式遇到的问题,它在每次迭代更新时替换整个操作系统,其中也不包含独立的软件包”。 + +Pacheco 表示,“当我们用镜像作为操作系统的代名词进行讨论时,我们真正关心的是可编程式的开发部署以及更好的集成式生命周期管理”,基于 RHEL 搭建的 OpenShift 被他用作示例。 + +Pacheco 认为基于镜像的操作系统是一种延续,从手工打造并部署镜像,到可大规模管理的高度自动化基础设施;无论客户使用哪种类型,都需要运行同样的应用。他说,“你肯定不希望使用一个完全不同的部署模式,这需要重做很多工作”。 + +镜像模式替代了使用新库和软件包的完整操作系统,但也面临一系列问题。在镜像模式中,需要重建镜像才能适应特殊环境的需求。例如,用户有特殊需求,需要安装特定硬件的驱动或安装底层监控功能,镜像模式无法满足,需要重新设计功能以实现细粒度操作。 + +### 事务模式 + +第三种模式采用事务更新,基于传统的软件包更新,但将全部的软件包视为一个镜像,就像镜像那样在一次操作中更新全部软件包。 + +Eckermann 表示,“由于安装或回滚时操作对象是打包在一起的独立软件包,用户在需要时能够做相应的调整,这就是差别所在。结合传统模式和镜像模式的优点,避免两种模式的缺点,事务模式给用户提供了额外的灵活性。” + +Pacheco 表示,将精心构造的工作负载部署成镜像的做法越来越成为主流,因为这种部署方式具有一致性和可靠性,而且可以弹性部署。“这正是我们用户目前的做法,部署环境包括在预置设备或公有/私有云上创建并部署的虚拟机,或在传统的裸机上” + +Pacheco 建议我们将这几种模式视为操作系统角色的进化和扩展,而不是仅仅“使用场景的比较和对比“。 + +### 原子化更新的问世 + +Google 的 Chrome OS 和 Core OS 为我们普及了事务更新的概念,该模型也被 Red Hat 和 SUSE 采用。 + +Eckermann 表示,”我们必须认识到,用于容器主机的操作系统已经不再是关注点 —— 至少不是管理员的关注点。RH Atomic 主机和 SUSE CaaS 平台都解决了该问题,实现方式在用户看来很相似。“ + +SUSE CaaS 平台、Red Hat Atomic Host和 Container Linux (前身是 Core OS)提供的[不可变基础设施Immutable infrastructure][2] 推广了事务更新的使用。Red Hat 高级技术产品经理 Ben Breard 表示,”在事务模式中,主机总是会变更到已确认正确的新状态,这让我们更有信心执行更新,进而实现更快速的功能流、安全优势以及易于采用的操作模式“。 + +这些新型操作系统使用 Linux 容器将应用与底层系统隔离,解除了传统模式中基础设施更新的诸多限制。 + +Breard 补充道,“当编排层可以智能处理更新、部署,甚至最终实现无缝操作时,我们才会真正意识到该模式的威力和好处”。 + +### 展望未来 + +Linux 的未来会是什么样子?不同的人会给出不同的回答。容器支持者认为未来属于容器化的操作系统,但依然拥有庞大市场的 Linux 供应商显然不这么认为。 + +当被问到原子化发行版是否在很久以后将替换传统发行版时,Eckermann 表示,“如果我回答肯定的,那么表示我顺应潮流;如果回答是否的的,意味着我还是站在传统阵营。然而,我的回答是否定的,即 atomic 发行版在很久以后也不会替换传统发行版,传统负载和容器化负载将在数据中心、私有云以及公有云环境中共存。” + +Pacheco 认为,从 Linux 的部署增长情况来看,一般情况下很难想象一种模式替换另一种模式。与其将多种模式视为相互竞争的关系,不如将原子化发行版视为操作系统进化和部署的一部分。 + +此外,在一些使用案例中,我们需要同时使用多种 Linux 发行版。Eckermann 表示,“想一想银行和保险公司中大量的 PL/1 和 Cobol 系统。再想一想内存数据库和核心数据总线系统”。 + +这些应用大多数无法进行容器化。就我们目前来看,容器化不是解决所有问题的万金油。总是会同时存在多种不同的技术。 + +Eckermann 相信,随着时间的推移,大量新的开发和部署将采用容器化,但仍然有不错的理由,促使我们在企业级环境中保留传统的部署方式和应用。 + +Pacheco 认为,“用户需要经历业务、设计和文化的转型,才能最大化基于容器的部署带来的优势。好消息是业界已经认识到并开始大规模转变,就像历史上大型机转变成 UNIX,UNIX 转变成 x86,x86 转变成虚拟化那样”。 + +### 结论 + +很明显,未来容器化负载的使用量会持续增长,也就意味着原子化发行版的需求量持续增长。与此同时,仍会有不少工作负载运行在传统发行版中。重要的是,这两类用户都在新模式上大规模投入,以便市场改变时可以做相应的策略改变。从外部观察者的视角来看,未来属于事务/原子化模式。我们已经见证了数据中心的发展,我们花了很长时间完成了从每个服务器一个应用到函数即服务模型的转变。Linux 发行版进入原子化时代的日子也不会太远了。 + +-------------------------------------------------------------------------------- + +via: https://www.linux.com/blog/2018/4/containerization-atomic-distributions-and-future-linux + +作者:[SWAPNIL BHARTIYA][a] +译者:[pinewall](https://github.com/pinewall) +校对:[校对者ID](https://github.com/校对者ID) +选题:[lujun9972](https://github.com/lujun9972) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]:https://www.linux.com/users/arnieswap +[1]:https://www.suse.com/products/caas-platform/ +[2]:https://www.digitalocean.com/community/tutorials/what-is-immutable-infrastructure From d82ff5d2c7cb1bb974d4c3f84ab05a499d60e1bf Mon Sep 17 00:00:00 2001 From: "Xingyu.Wang" Date: Wed, 30 May 2018 05:20:01 +0800 Subject: [PATCH 02/14] PRF:20180104 4 Tools for Network Snooping on Linux.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @wyxplus 翻译很用心。 --- ...4 4 Tools for Network Snooping on Linux.md | 60 +++++++++---------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/translated/tech/20180104 4 Tools for Network Snooping on Linux.md b/translated/tech/20180104 4 Tools for Network Snooping on Linux.md index cd9a21f0dd..a82e59dd34 100644 --- a/translated/tech/20180104 4 Tools for Network Snooping on Linux.md +++ b/translated/tech/20180104 4 Tools for Network Snooping on Linux.md @@ -1,13 +1,15 @@ -四个Linux网络嗅探工具 +四个 Linux 上的网络嗅探工具 ====== -在计算机网络中,数据是暴露的,因为数据包传输是无法隐藏的,所以让我们来使用 `whois`,`dig`,`nmcli` 和 `nmap` 这四个工具来嗅探网络吧。 +![](https://www.linux.com/sites/lcom/files/styles/rendered_file/public/1280px-eudyptula_minor_family_exiting_burrow.jpg?itok=kGZ53AzN) -请注意,不要运行 `nmap` 在不属于自己的网络上,因为这有可能会被其他人解读成为恶意攻击。 +在计算机网络中,数据是暴露的,因为数据包传输是无法隐藏的,所以让我们来使用 `whois`、`dig`、`nmcli` 和 `nmap` 这四个工具来嗅探网络吧。 + +请注意,不要在不属于自己的网络上运行 `nmap` ,因为这有可能会被其他人认为恶意攻击。 ### 精简和详细域名信息查询 -您可能已经注意到,之前我们用心爱的 `whois` 命令查询域名信息,但现如今似乎没有提供同过去一样的详细程度。我们使用该命令查询 Linux.com 域名描述信息: +您可能已经注意到,之前我们用常用的老式 `whois` 命令查询域名信息,但现如今似乎没有提供同过去一样的详细程度。我们使用该命令查询 linux.com 的域名描述信息: ``` $ whois linux.com @@ -30,19 +32,19 @@ DNSSEC: unsigned [...] ``` -有很多令人讨厌的法律声明。但在哪有联系信息呢?该网站位于 whois.namecheap.com 站点上(见上面输出的第三行): +有很多令人讨厌的法律声明。但在哪有联系信息呢?该网站位于 `whois.namecheap.com` 站点上(见上面输出的第三行): ``` $ whois -h whois.namecheap.com linux.com - ``` -我就不复制出来,因为这实在太长了,包含了注册人,管理员和技术人员的联系信息。怎么回事啊,露西尔?(LCTT 译注:《行尸走肉》中尼根的棒子)有一些注册表,比如.com和.net是精简注册表,保存了一部分有限的域名信息。为了获取完整信息请使用 `-h` 或 `--host` 参数,该参数便会从域名的 `注册服务机构` 中获取。 -大部分顶级域名是需要详细的注册信息,如.info。试着使用`whois blockchain.info`命令来查看。 +我就不复制出来了,因为这实在太长了,包含了注册人,管理员和技术人员的联系信息。怎么回事啊,露西尔?(LCTT 译注:《行尸走肉》中尼根的棒子)有一些注册库,比如 .com 和 .net 是精简注册库,保存了一部分有限的域名信息。为了获取完整信息请使用 `-h` 或 `--host` 参数,该参数便会从域名的 `注册服务机构` 中获取。 + +大部分顶级域名是有详细的注册信息,如 .info。试着使用 `whois blockchain.info` 命令来查看。 想要摆脱这些烦人的法律声明?使用 `-H` 参数。 -### DNS解析 +### DNS 解析 使用 `dig` 命令比较从不同的域名服务器返回的查询结果,去除陈旧的信息。域名服务器记录缓存各地的解析信息,并且不同的域名服务器有不同的刷新间隔。以下是一个简单的用法: @@ -69,19 +71,18 @@ linux.com. 10800 IN A 151.101.193.5 ;; SERVER: 127.0.1.1#53(127.0.1.1) ;; WHEN: Tue Jan 16 15:17:04 PST 2018 ;; MSG SIZE rcvd: 102 - ``` -注意下靠近末尾的这行信息:SERVER: 127.0.1.1#53(127.0.1.1),这是您默认的缓存解析器。当地址是本地时,就相当于在您的电脑上安装DNS服务。在我看来这就是一个Dnsmasq工具(LCTT 译注:是一个小巧且方便地用于配置DNS和DHCP的工具),该工具被用作网络管理: + +注意下靠近末尾的这行信息:`SERVER: 127.0.1.1#53(127.0.1.1)`,这是您默认的缓存解析器。当地址是本地时,就表明您的电脑上安装了 DNS 服务。在我看来这就是一个 Dnsmasq 工具(LCTT 译注:是一个小巧且方便地用于配置 DNS 和 DHCP 的工具),该工具被用作网络管理: ``` $ ps ax|grep dnsmasq 2842 ? S 0:00 /usr/sbin/dnsmasq --no-resolv --keep-in-foreground --no-hosts --bind-interfaces --pid-file=/var/run/NetworkManager/dnsmasq.pid --listen-address=127.0.1.1 - ``` - `dig` 命令默认是返回A记录,也就是域名。IPv6则有AAAA记录: +`dig` 命令默认是返回 A 记录,也就是域名。IPv6 则有 AAAA 记录: ``` $ $ dig linux.com AAAA @@ -92,48 +93,44 @@ linux.com. 60 IN AAAA 64:ff9b::9765:4105 linux.com. 60 IN AAAA 64:ff9b::9765:8105 linux.com. 60 IN AAAA 64:ff9b::9765:c105 [...] - ``` -仔细检查下,发现Linux.com有IPv6地址。很好!如果您的网络服务支持IPv6那么您就可以用IPv6连接。(令人难过的是,我的移动宽带则没提供IPv6) +仔细检查下,发现 linux.com 有 IPv6 地址。很好!如果您的网络服务支持 IPv6 那么您就可以用 IPv6 连接。(令人难过的是,我的移动宽带则没提供 IPv6) - -假设您能使DNS改变您的域名,又或是您使用 `dig` 查询的结果有误。试着用一个公共DNS,如OpenNIC: +假设您能对您的域名做一些 DNS 改变,又或是您使用 `dig` 查询的结果有误。试着用一个公共 DNS,如 OpenNIC: ``` $ dig @69.195.152.204 linux.com [...] ;; Query time: 231 msec ;; SERVER: 69.195.152.204#53(69.195.152.204) - ``` + `dig` 回应您正在的查询是来自 69.195.152.204。您可以查询各种服务并且比较结果。 ### 上游域名服务器 -我想知道我的上游域名服务器是谁。为了查询,我首先看下`/etc/resolv/conf` 的配置信息: +我想知道我的上游域名服务器(LCTT 译注:此处指解析器)是谁。为了查询,我首先看下 `/etc/resolv/conf` 的配置信息: ``` $ cat /etc/resolv.conf # Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8) # DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN nameserver 127.0.1.1 - ``` -很幸运,不过我是已经知道。您的Linux发行版可能配置不同,您会看到您的上游服务器。接下来我们来试试网络管理器命令行工具 `nmcli`: +好吧,不过我已经知道了。您的 Linux 发行版可能配置不同,您会看到您的上游服务器。接下来我们来试试网络管理器命令行工具 `nmcli`: ``` $ nmcli dev show | grep DNS IP4.DNS[1]: 192.168.1.1 - ``` -很好,现在我们已经知道了,其实那是我的移动热点,并且我已经确认那是我的热点。我能够登录到简易管理面板,来查询上游服务器。然而许多消费者互联网网关不会让您看到或改变这些设置,因此只能尝试其他的方法,如 [我的域名服务器是什么?][1] +很好,现在我们已经知道了,其实那是我的移动热点,我能确认。我能够登录到简易管理面板,来查询上游服务器。然而许多用户级互联网网关不会让您看到或改变这些设置,因此只能尝试其他的方法,如 [我的域名服务器是什么?][1] -### 查找在您的网络中IPv4地址 +### 查找在您的网络中 IPv4 地址 -您的网络上有哪些IPv4地址已启用并正在使用中? +您的网络上有哪些 IPv4 地址已启用并正在使用中? ``` $ nmap -sn 192.168.1.0/24 @@ -145,8 +142,8 @@ Host is up (0.000071s latency). Nmap scan report for nellybly (192.168.1.3) Host is up (0.015s latency) Nmap done: 256 IP addresses (2 hosts up) scanned in 2.23 seconds - ``` + 每个人都想去扫描自己的局域网中开放的端口。下面的例子是寻找服务和他们的版本号: ``` @@ -171,10 +168,9 @@ Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 256 IP addresses (2 hosts up) scanned in 11.65 seconds - ``` -这些是有趣的结果。让我们尝试从不同的网络帐户进行相同的操作,以查看这些服务是否暴露于互联网中。如果您有智能手机,相当于您有第二个网络。您可以下载应用程序,还可以为您的Linux电脑提供热点。从热点控制面板获取广域网IP地址,然后重试: +这些是有趣的结果。让我们尝试从不同的互联网连接进行相同的操作,以查看这些服务是否暴露于互联网中。如果您有智能手机,相当于您有第二个网络。您可以下载应用程序,还可以为您的 Linux 电脑提供热点。从热点控制面板获取广域网IP地址,然后重试: ``` $ nmap -sV 12.34.56.78 @@ -183,13 +179,11 @@ Starting Nmap 7.01 ( https://nmap.org ) at 2018-01-14 17:05 PST Nmap scan report for 12.34.56.78 Host is up (0.0061s latency). All 1000 scanned ports on 12.34.56.78 are closed - ``` -果然不出所料,结果和我想象的一样。可以用手册来查询这些命令,以便了解更多有趣的嗅探技术。 +果然不出所料,结果和我想象的一样(LCTT 译注:这些服务和信息没有被暴露在公网上)。可以用手册来查询这些命令,以便了解更多有趣的嗅探技术。 -了解更多Linux的相关知识可以从Linux基金会和edX(LCTT译者注:edX是麻省理工和哈佛大学于2012年4月联手创建的大规模开放在线课堂平台)中获取免费的 ["介绍Linux" ][2]课程。 - +了解更多 Linux 的相关知识可以从 Linux 基金会和 edX(LCTT译注:edX 是麻省理工和哈佛大学于 2012 年 4 月联手创建的大规模开放在线课堂平台)中获取免费的 [“介绍 Linux”][2]课程。 -------------------------------------------------------------------------------- @@ -197,7 +191,7 @@ via: https://www.linux.com/learn/intro-to-linux/2018/1/4-tools-network-snooping- 作者:[Carla Schroder][a] 译者:[wyxplus](https://github.com/wyxplus) -校对:[校对者ID](https://github.com/校对者ID) +校对:[wxy](https://github.com/wxy) 本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 From ed87748648960e479ee24ed870fb0dbfc72a629f Mon Sep 17 00:00:00 2001 From: "Xingyu.Wang" Date: Wed, 30 May 2018 05:21:08 +0800 Subject: [PATCH 03/14] PUB:20180104 4 Tools for Network Snooping on Linux.md @wyxplus https://linux.cn/article-9692-1.html --- .../20180104 4 Tools for Network Snooping on Linux.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {translated/tech => published}/20180104 4 Tools for Network Snooping on Linux.md (100%) diff --git a/translated/tech/20180104 4 Tools for Network Snooping on Linux.md b/published/20180104 4 Tools for Network Snooping on Linux.md similarity index 100% rename from translated/tech/20180104 4 Tools for Network Snooping on Linux.md rename to published/20180104 4 Tools for Network Snooping on Linux.md From b9be02e00ac9cd8fbd76349934bb00e0f73ef287 Mon Sep 17 00:00:00 2001 From: "Xingyu.Wang" Date: Wed, 30 May 2018 05:26:25 +0800 Subject: [PATCH 04/14] PRF:20180518 How To Install Ncurses Library In Linux.md @geekpi --- ...How To Install Ncurses Library In Linux.md | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/translated/tech/20180518 How To Install Ncurses Library In Linux.md b/translated/tech/20180518 How To Install Ncurses Library In Linux.md index 47f094de34..0678c5a5be 100644 --- a/translated/tech/20180518 How To Install Ncurses Library In Linux.md +++ b/translated/tech/20180518 How To Install Ncurses Library In Linux.md @@ -1,69 +1,68 @@ 如何在 Linux 中安装 Ncurses 库 ====== + ![](https://www.ostechnix.com/wp-content/uploads/2018/05/install-ncurses-720x340.png) -**GNU Ncurses** 是一个允许用户编写基于文本的用户界面(TUI)的编程库。许多基于文本的游戏都是使用这个库创建的。一个受欢迎的例子是 [**PacVim**][1],这是一款学习 VIM 命令的 CLI 游戏。在这篇简要的指南中,我将解释如何在类 Unix 操作系统中安装 Ncurses 库。 +**GNU Ncurses** 是一个允许用户编写基于文本的用户界面(TUI)的编程库。许多基于文本的游戏都是使用这个库创建的。一个受欢迎的例子是 [PacVim][1],这是一款学习 VIM 命令的 CLI 游戏。在这篇简要的指南中,我将解释如何在类 Unix 操作系统中安装 Ncurses 库。 ### 在 Linux 中安装 Ncurses 库 Ncurses 在大多数 Linux 发行版的默认仓库中都有。例如,你可以使用以下命令将其安装在基于 Arch 的系统上: + ``` $ sudo pacman -S ncurses - ``` 在RHEL、CentOS 上: + ``` $ sudo yum install ncurses-devel - ``` 在 Fedora 22 和更新版本上: + ``` $ sudo dnf install ncurses-devel - ``` 在 Debian、Ubuntu、Linux Mint 上: + ``` $ sudo apt-get install libncurses5-dev libncursesw5-dev - ``` 默认仓库中的 GNU ncureses 可能有点老了。如果你想要最新的稳定版本,可以从源代码进行编译和安装,如下所示。 -从[**这里**][2]下载最新的 ncurses 版本。在写这篇文章时,最新版本是 6.1。 +从[这里][2]下载最新的 ncurses 版本。在写这篇文章时,最新版本是 6.1。 + ``` $ wget https://ftp.gnu.org/pub/gnu/ncurses/ncurses-6.1.tar.gz - ``` 解压 tar 文件: + ``` $ tar xzf ncurses-6.1.tar.gz - ``` 这将在当前目录中创建一个名为 ncurses-6.1 的文件夹。cd 到该目录: + ``` $ cd ncurses-6.1 - $ ./configure --prefix=/opt/ncurses - ``` 最后,使用以下命令进行编译和安装: + ``` $ make - $ sudo make install - ``` 使用命令验证安装: + ``` $ ls -la /opt/ncurses - ``` 就是这样。Ncurses 已经安装在 Linux 发行版上。继续使用 Ncurses 创建漂亮的 TUI。 @@ -73,7 +72,6 @@ $ ls -la /opt/ncurses 干杯! - -------------------------------------------------------------------------------- via: https://www.ostechnix.com/how-to-install-ncurses-library-in-linux/ @@ -81,7 +79,7 @@ via: https://www.ostechnix.com/how-to-install-ncurses-library-in-linux/ 作者:[SK][a] 选题:[lujun9972](https://github.com/lujun9972) 译者:[geekpi](https://github.com/geekpi) -校对:[校对者ID](https://github.com/校对者ID) +校对:[wxy](https://github.com/wxy) 本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 From 5b0ce17dfec2cd479e94e0b34adff44447bf45e6 Mon Sep 17 00:00:00 2001 From: "Xingyu.Wang" Date: Wed, 30 May 2018 05:26:50 +0800 Subject: [PATCH 05/14] PUB:20180518 How To Install Ncurses Library In Linux.md @geekpi https://linux.cn/article-9693-1.html --- .../20180518 How To Install Ncurses Library In Linux.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {translated/tech => published}/20180518 How To Install Ncurses Library In Linux.md (100%) diff --git a/translated/tech/20180518 How To Install Ncurses Library In Linux.md b/published/20180518 How To Install Ncurses Library In Linux.md similarity index 100% rename from translated/tech/20180518 How To Install Ncurses Library In Linux.md rename to published/20180518 How To Install Ncurses Library In Linux.md From 164b7dd3d41aecba43357c6b9f3ebefbe6728a02 Mon Sep 17 00:00:00 2001 From: "Xingyu.Wang" Date: Wed, 30 May 2018 05:38:54 +0800 Subject: [PATCH 06/14] PRF:20180424 Things You Should Know About Ubuntu 18.04.md @wyxplus --- ...ings You Should Know About Ubuntu 18.04.md | 45 ++++++++----------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/translated/tech/20180424 Things You Should Know About Ubuntu 18.04.md b/translated/tech/20180424 Things You Should Know About Ubuntu 18.04.md index 1e5ce8c6af..68953dd4b6 100644 --- a/translated/tech/20180424 Things You Should Know About Ubuntu 18.04.md +++ b/translated/tech/20180424 Things You Should Know About Ubuntu 18.04.md @@ -1,7 +1,7 @@ 你应该知道关于 Ubuntu 18.04 的一些事 ====== -[Ubuntu 18.04 版本][1] 即将到来。我可以在各种 Facebook 群组和论坛中看到许多来自 Ubuntu 用户的提问。我还在 Facebook 和 Instagram 上组织了 Q&A 会议,以了解 Ubuntu 用户对 Ubuntu 18.04 的想法。 +[Ubuntu 18.04 版本][1] 已经到来。我可以在各种 Facebook 群组和论坛中看到许多来自 Ubuntu 用户的提问。我还在 Facebook 和 Instagram 上组织了 Q&A 会议,以了解 Ubuntu 用户对 Ubuntu 18.04 的想法。 我试图在这里回答关于 Ubuntu 18.04 的常见问题。如果您有任何疑问,我希望这能帮助您解决疑问。如果您仍有问题,请随时在下面的评论区提问。 @@ -17,19 +17,19 @@ Canonical 公司知道有些人喜欢 Unity。这就是为什么它已经在 Universe 软件库(LCTT译者注:社区维护的软件库)中提供了 Unity 7。但这是一个社区维护版,官方并不直接参与开发。 -但我建议是使用默认的 GNOME,除非您真的无法容忍它,再在 [Ubuntu 18.04 上安装 Unity][3]。 +不过我建议是使用默认的 GNOME,除非您真的无法容忍它,再在 [Ubuntu 18.04 上安装 Unity][3]。 #### GNOME 是什么版本? 在这次发行的 Ubuntu 18.04 版本中,GNOME 版本号是 3.28。 -#### 我能够安装 vanilla GNOME? +#### 我能够安装原装的 GNOME 吗? 当然没问题! -因为存在一些 GNOME 用户可能不喜欢 Ubuntu 18.04 中的 Unity 风格。在 Ubuntu 中的 main(LCTT译者注:官方支持的软件库)和 universe 软件库有安装包可安装,能使您在 [Ubuntu 18.04 中安装 vanilla GNOME][4]。 +因为存在一些 GNOME 用户可能不喜欢 Ubuntu 18.04 中的 Unity 风格。在 Ubuntu 中的 main(LCTT译者注:官方支持的软件库)和 universe 软件库有安装包可安装,能使您在 [Ubuntu 18.04 中安装原装的 GNOME][4]。 -#### GNOME中的内存泄漏已修复了吗? +#### GNOME 中的内存泄漏已修复了吗? 已经修复了。[GNOME 3.28 中臭名昭着的内存泄漏][5] 已经被修复了,并且 [Ubuntu 官方已经在测试这个修复程序][6]。 @@ -39,23 +39,21 @@ Canonical 公司知道有些人喜欢 Unity。这就是为什么它已经在 Uni 这是一个长期支持(LTS)版本,与任何 LTS 版本一样,官方会支持五年。这意味着 Ubuntu 18.04 将在 2023 年 4 月之前能获得安全和维护更新。这对于除 Ubuntu Studio 之外的所有基于 Ubuntu 的 Linux 发行版也一样。 -#### Ubuntu 18.04 什么时候会发布? +#### Ubuntu 18.04 什么时候发布的? -Ubuntu 18.04 LTS 在 4 月 26 日发布。 所有基于 Ubuntu 的 Linux 发行版,如 Kubuntu,Lubuntu,Xubuntu,Budgie,MATE 等都会在同一天发布其 18.04 版本。 +Ubuntu 18.04 LTS 在 4 月 26 日发布。 所有基于 Ubuntu 的 Linux 发行版,如 Kubuntu、Lubuntu、Xubuntu、Budgie、MATE 等都会在同一天发布其 18.04 版本。 不过 [Ubuntu Studio 不会有 18.04 的 LTS 版本][7]。 -#### 是否能从16.04/17.10升级到 Ubuntu 18.04?我可以从使用 Unity 的 Ubuntu 16.04 升级到使用 GNOME 的 Ubuntu 18.04 吗? +#### 是否能从 16.04/17.10 升级到 Ubuntu 18.04?我可以从使用 Unity 的 Ubuntu 16.04 升级到使用 GNOME 的 Ubuntu 18.04 吗? 绝对没问题。当 Ubuntu 18.04 LTS 发布后,您可以很容易的升级到最新版。 -如果您使用的是 Ubuntu 17.10,请确保在软件和更新->更新中,将“有新版本时通知我”设置为“适用任何新版本”。 - +如果您使用的是 Ubuntu 17.10,请确保在“软件和更新”->“更新”中,将“有新版本时通知我”设置为“适用任何新版本”。 ![Get notified for a new version in Ubuntu][8] -如果您使用的是 Ubuntu 16.04,请确保在软件和更新->更新中,将“有新版本时通知我”设置为“适用长期支持版本”。 - +如果您使用的是 Ubuntu 16.04,请确保在“软件和更新”->“更新”中,将“有新版本时通知我”设置为“适用长期支持版本”。 ![Ubuntu 18.04 upgrade from Ubuntu 16.04][9] @@ -63,10 +61,8 @@ Ubuntu 18.04 LTS 在 4 月 26 日发布。 所有基于 Ubuntu 的 Linux 发行 即使 Ubuntu 16.04 使用的是 Unity,但您仍然可以 [升级到使用 GNOME 的 Ubuntu 18.04][10]。 - #### 升级到 Ubuntu 18.04 意味着什么?我会丢失数据吗? - 如果您使用的是 Ubuntu 17.10 或 Ubuntu 16.04,系统会提示您可升级到 Ubuntu 18.04。如果您从互联网上下载 1.5 Gb 的数据不成问题,则只需点击几下鼠标,即可在 30 分钟内升级到 Ubuntu 18.04。 您不需要通过 U 盘来重装系统。升级过程完成后,您将可以使用新的 Ubuntu 版本。 @@ -75,7 +71,7 @@ Ubuntu 18.04 LTS 在 4 月 26 日发布。 所有基于 Ubuntu 的 Linux 发行 #### 我什么时候能升级到 Ubuntu 18.04? -如果您使用的是 Ubuntu 17.10 并且正确设置(设置方法在之前提到的问题中),那么在 Ubuntu 18.04 发布的几天内应该会通知您升级到 Ubuntu 18.04。为避免 Ubuntu 服务器在发布日期负载量过大,因此不是每个人都会在同一天收到升级提示。 +如果您使用的是 Ubuntu 17.10 并且正确设置(设置方法在之前提到的问题中),那么在 Ubuntu 18.04 发布的几天内应该会通知您升级到 Ubuntu 18.04。为避免 Ubuntu 服务器在发布时的负载量过大,因此不是每个人都会在同一天收到升级提示。 对于 Ubuntu 16.04 用户,可能需要几周时间才能正式收到 Ubuntu 18.04 升级提示。通常,这将在第一次发布 Ubuntu 18.04.1 之后提示。该版本修复了 18.04 中发现的新 bug。 @@ -89,16 +85,13 @@ Ubuntu 18.04 LTS 在 4 月 26 日发布。 所有基于 Ubuntu 的 Linux 发行 如果您已经在使用 32 位版本的 Ubuntu 16.04 或 17.10,您依旧可以升级到 Ubuntu 18.04。 但是,您找到不到 32 位的 Ubuntu 18.04 ISO 镜像。换句话说,您无法安装 32 位版本的 Ubuntu 18.04。 - -有一个好消息是,Ubuntu MATE,Lubuntu 等其他官方版本仍然具有其新版本的 32 位 ISO 镜像。 +有一个好消息是,Ubuntu MATE、Lubuntu 等其他官方版本仍然具有其新版本的 32 位 ISO 镜像。 无论如何,如果您使用一个 32 位系统,那么很可能您的计算机硬件性能过低。在这样的电脑上使用轻量级 [Ubuntu MATE][11] 或 [Lubuntu][12] 系统会更好。 - #### 我可以在哪下载 Ubuntu 18.04? -一旦发布了 18.04,您可以从其网站获得 Ubuntu 18.04 的 ISO 镜像。您既可以直接官网下载,也能用种子下载。其他官方版本将在其官方网站上提供下载。 - +18.04 已经发布了,您可以从其网站获得 Ubuntu 18.04 的 ISO 镜像。您既可以直接官网下载,也能用种子下载。其他官方版本将在其官方网站上提供下载。 #### 我应该重新安装 Ubuntu 18.04 还是从 16.04/17.10 升级上来? @@ -106,7 +99,7 @@ Ubuntu 18.04 LTS 在 4 月 26 日发布。 所有基于 Ubuntu 的 Linux 发行 从现有版本升级到 18.04 是一个方便的选择。不过,就我个人而言,它仍然保留了旧版本的依赖包。重新安装还是比较干净。 -对于重新安装来说,我应该安装 Ubuntu 16.04 还是 Ubuntu 18.04? +#### 对于重新安装来说,我应该安装 Ubuntu 16.04 还是 Ubuntu 18.04? 如果您要在计算机上安装 Ubuntu,请尽量使用 Ubuntu 18.04 而不是 16.04。 @@ -116,7 +109,6 @@ Ubuntu 18.04 LTS 在 4 月 26 日发布。 所有基于 Ubuntu 的 Linux 发行 此外,许多应用程序开发人员将很快开始关注 Ubuntu 18.04。新创建的 PPA 可能仅在几个月内支持 18.04。所以使用 18.04 比 16.04 更好。 - #### 安装打印机-扫描仪驱动程序比使用 CLI 安装会更容易吗? 在打印机方面,我不是专家,所以我的观点是基于我在这方面有限的知识。大多数新打印机都支持 [IPP协议][14],因此它们应该在 Ubuntu 18.04 中能够获到很好的支持。 然而对较旧的打印机我则无法保证。 @@ -127,15 +119,14 @@ Ubuntu 18.04 LTS 在 4 月 26 日发布。 所有基于 Ubuntu 的 Linux 发行 #### Ubuntu 18.04 的系统要求? - 对于默认的 GNOME 版本,最好您应该有 [4 GB 的内存以便正常使用][15]。使用过去 8 年中发布的处理器也可以运行。但任何比这性能更差的硬件建议使用 [轻量级 Linux 发行版][16],例如 [Lubuntu][12]。 - - #### 有关 Ubuntu 18.04 的其问题? 如果还有其他疑问,请随时在下方评论区留言。如果您认为应将其他信息添加到列表中,请告诉我。 +(LCTT 译注:本文原文写于 Ubuntu 18.04 LTS 发布之前,译文翻译于发布之后,因此对部分内容做了修改。) + -------------------------------------------------------------------------------- via: https://itsfoss.com/ubuntu-18-04-faq/ @@ -143,12 +134,12 @@ via: https://itsfoss.com/ubuntu-18-04-faq/ 作者:[Abhishek Prakash][a] 选题:[lujun9972](https://github.com/lujun9972) 译者:[wyxplus](https://github.com/wyxplus) -校对:[校对者ID](https://github.com/校对者ID) +校对:[wxy](https://github.com/wxy) 本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 [a]:http://itsfoss.com/author/abhishek/ -[1]:https://itsfoss.com/ubuntu-18-04-release-features/ +[1]:https://linux.cn/article-9583-1.html [2]:https://4bds6hergc-flywheel.netdna-ssl.com/wp-content/uploads/2018/04/ubuntu-18-04-faq-800x450.png [3]:https://itsfoss.com/use-unity-ubuntu-17-10/ [4]:https://itsfoss.com/vanilla-gnome-ubuntu/ From 35b3bc2dfcb73acc66231a401d8b4fe001b45b17 Mon Sep 17 00:00:00 2001 From: "Xingyu.Wang" Date: Wed, 30 May 2018 05:39:31 +0800 Subject: [PATCH 07/14] PUB:20180424 Things You Should Know About Ubuntu 18.04.md @wyxplus https://linux.cn/article-9694-1.html --- .../20180424 Things You Should Know About Ubuntu 18.04.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {translated/tech => published}/20180424 Things You Should Know About Ubuntu 18.04.md (100%) diff --git a/translated/tech/20180424 Things You Should Know About Ubuntu 18.04.md b/published/20180424 Things You Should Know About Ubuntu 18.04.md similarity index 100% rename from translated/tech/20180424 Things You Should Know About Ubuntu 18.04.md rename to published/20180424 Things You Should Know About Ubuntu 18.04.md From ae00d25c0b6545de2f54a6805d44b371c1df533f Mon Sep 17 00:00:00 2001 From: "Xingyu.Wang" Date: Wed, 30 May 2018 05:58:38 +0800 Subject: [PATCH 08/14] PRF:20180131 Microservices vs. monolith How to choose.md @qhwdw --- ...icroservices vs. monolith How to choose.md | 66 +++++++------------ 1 file changed, 22 insertions(+), 44 deletions(-) diff --git a/translated/tech/20180131 Microservices vs. monolith How to choose.md b/translated/tech/20180131 Microservices vs. monolith How to choose.md index f6d389fab2..9f2d1b6984 100644 --- a/translated/tech/20180131 Microservices vs. monolith How to choose.md +++ b/translated/tech/20180131 Microservices vs. monolith How to choose.md @@ -1,13 +1,11 @@ 微服务 vs. 整体服务:如何选择 ============================================================ -### 任何一种架构都是有利有弊的,而能满足你组织的独特需要的决策才是正确的选择。 - +> 任何一种架构都是有利有弊的,而能满足你组织的独特需要的决策才是正确的选择。 ![Microservices vs. monolith: How to choose](https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/building_architecture_design.jpg?itok=lB_qYv-I "Microservices vs. monolith: How to choose") -Image by :  -Onasill ~ Bill Badzo on [Flickr][11]. [CC BY-NC-SA 2.0][12]. Modified by Opensource.com. +Image by : Onasill ~ Bill Badzo on [Flickr][11]. [CC BY-NC-SA 2.0][12]. Modified by Opensource.com. 对于许多初创公司来说,传统的知识认为,从单一整体架构开始,而不是使用微服务。但是,我们还有别的选择吗? @@ -17,37 +15,30 @@ Onasill ~ Bill Badzo on [Flickr][11]. [CC BY-NC-SA 2.0][12]. Modified by Open ### 理解范围 -更多有关微服务的内容 - -* [如何向你的 CEO 解释微服务][1] - -* [免费电子书:微服务 vs. 面向服务的架构][2] - -* [DevOps 确保微服务安全][3] - 首先,我们先来准确定义我们所谓的 “整体服务” 和 “微服务” 是什么。 -微服务是一种方法,它开发一个单一的应用程序来作为构成整体服务的小服务,每个小服务都运行在它自己的进程中,并且使用一个轻量级的机制进行通讯,通常是一个 HTTP 资源 API。这些服务都围绕业务能力来构建,并且可依赖全自动部署机制来独立部署。 +微服务是一种方法,它开发一个单一的应用程序来作为构成整体服务的小服务,每个小服务都运行在它自己的进程中,并且使用一个轻量级的机制进行通讯,通常是一个 HTTP 资源的 API。这些服务都围绕业务能力来构建,并且可依赖全自动部署机制来独立部署。 -一个整体应用程序是按单个的、统一的单元来构建,并且,通常情况下它是基于一个大量的代码来实现的。一般来说,一个整体服务是由三部分组成的:一个数据库、一个客户端用户界面(由 HTML 页面和/或运行在浏览器中的 JavaScript 组成)、以及一个服务器端应用程序。 +一个整体应用程序是按单个的、统一的单元来构建,并且,通常情况下它是基于一个大量的代码来实现的。一般来说,一个整体服务是由三部分组成的:数据库、客户端用户界面(由 HTML 页面和/或运行在浏览器中的 JavaScript 组成)、以及服务器端应用程序。 -“系统架构处于一个范围之中”,Zachary Crockett,[Particle][14] 的 CTO,在一次访谈中,他说,”在讨论微服务时,人们倾向于关注这个范围的一端:许多极小的应用程序给其它应用程序传递了过多的信息。在另一端,有一个巨大的整体服务做了太多的事情。在任何现实中的系统上,在这两个极端之间有很多合适的面向服务的架构“。 +“系统架构处于一个范围之中”,Zachary Crockett,[Particle][14] 的 CTO,在一次访谈中,他说,“在讨论微服务时,人们倾向于关注这个范围的一端:许多极小的应用程序给其它应用程序传递了过多的信息。在另一端,有一个巨大的整体服务做了太多的事情。在任何现实中的系统上,在这两个极端之间有很多合适的面向服务的架构”。 根据你的情况不同,不论是使用整体服务还是微服务都有很多很好的理由。 -"我们希望为每个服务使用最好的工具”,Julien Lemoine 说,他是 Algolia 的 CTO。 +“我们希望为每个服务使用最好的工具”,Julien Lemoine 说,他是 Algolia 的 CTO。 与很多人的想法正好相反,整体服务并不是过去遗留下来的过时的架构。在某些情况下,整体服务是非常理想的。我采访了 Steven Czerwinski 之后,更好地理解了这一点,他是 [Scaylr][15] 的工程主管,前谷歌员工。 “尽管我们在谷歌时有使用微服务的一些好的经验,我们现在 [在 Scalyr] 却使用的是整体服务的架构,因为一个整体服务架构意味着我们的工作量更少,我们只有两位工程师。“ 他解释说。(采访他时,Scaylr 正处于早期阶段) -但是,如果你的团队使用微服务的经验很丰富,并且你对你们的发展方向有明确的想法,微服务可能是一个很好的 替代者。 +但是,如果你的团队使用微服务的经验很丰富,并且你对你们的发展方向有明确的想法,微服务可能是一个很好的替代者。 -Julien Lemoine,[Algolia][16] 的 CTO,在这个问题上,他认为:”我们通常从使用微服务开始,主要目的是我们可以使用不同的技术来构建我们的服务,因为如下的两个主要原因: +Julien Lemoine,[Algolia][16] 的 CTO,在这个问题上,他认为:“我们通常从使用微服务开始,主要目的是我们可以使用不同的技术来构建我们的服务,因为如下的两个主要原因: -* 我们想为每个服务使用最好的工具。我们的搜索 API 是在底层做过高度优化的,而 C++ 是非常适合这项工作的。他说,在任何地方都使用 C++ 是一种生产力的浪费,尤其是在构建仪表板方面。 +* 我们想为每个服务使用最好的工具。我们的搜索 API 是在底层做过高度优化的,而 C++ 是非常适合这项工作的。他说,在任何其它地方都使用 C++ 是一种生产力的浪费,尤其是在构建仪表板方面。 +* 我们希望使用最好的人才,而只使用一种技术将极大地限制我们的选择。这就是为什么在公司中有不同语言的原因。 -* 我们希望使用最好的人才,而只使用一种技术将极大地限制我们的选择。这就是为什么在公司中有不同语言的原因。“ +” 如果你的团队已经准备好从一开始就使用微服务,这样你的组织从一开始就可以适应微服务环境的开发节奏。 @@ -57,36 +48,29 @@ Julien Lemoine,[Algolia][16] 的 CTO,在这个问题上,他认为:”我 ### 整体服务 -### 优点: +#### 优点: * **很少担心横向联系:** 大多数应用程序开发者都担心横向联系,比如,日志、速度限制、以及像审计跟踪和 DoS 防护这样的安全特性。当所有的东西都运行在同一个应用程序中时,通过组件钩子来处理这些关注点就非常容易了。 - * **运营开销很少:** 只需要为一个应用程序设置日志、监视、以及测试。一般情况下,部署也相对要简单。 - * **性能:** 一个整体的架构可能会有更好的性能,因为共享内存的访问速度要比进程间通讯(IPC)更快。 -### 缺点: - -* **紧耦合:** 整体服务的应用程序倾向于紧耦合,并且应用程序是整体进化,分离特定用途的服务是非常困难的,比如,独立扩展或者代码维护。 +#### 缺点: +* **紧耦合:** 整体服务的应用程序倾向于紧耦合,并且应用程序是整体进化的,分离特定用途的服务是非常困难的,比如,独立扩展或者代码维护。 * **理解起来很困难:** 当你想查看一个特定的服务或者控制器时,因为依赖、副作用、和其它的不可预见因素,整体架构理解起来更困难。 ### 微服务 -### 优点: +#### 优点: * **非常好组织:** 微服务架构一般很好组织它们,因为每个微服务都有一个特定的工作,并且还不用考虑其它组件的工作。 - * **解耦合:** 解耦合的服务是能够非常容易地进行重组织和重配置,以服务于不同的应用程序(比如,同时向 Web 客户端和公共 API 提供服务)。它们在一个大的集成系统中,也允许快速、独立分发单个部分。 - * **性能:** 根据组织的情况,微服务可以提供更好的性能,因为你可以分离热点服务,并根据其余应用程序的情况来扩展它们。 - * **更少的错误:** 微服务允许系统中的不同部分,在维护良好边界的前提下进行并行开发。这样将使连接不该被连接的部分变得更困难,比如,需要连接的那些紧耦合部分。 -### 缺点: +#### 缺点: * **跨每个服务的横向联系点:** 由于你构建了一个新的微服务架构,你可能会发现在设计时没有预料到的很多横向联系的问题。这也将导致需要每个横向联系点的独立模块(比如,测试)的开销增加,或者在其它服务层面因封装横向联系点,所导致的所有流量都需要路由。最终,即便是整体服务架构也倾向于通过横向联系点的外部服务层来路由流量,但是,如果使用整体架构,在项目更加成熟之前,也不过只是推迟了工作成本。 - * **更高的运营开销:** 微服务在它所属的虚拟机或容器上部署非常频繁,导致虚拟机争用激增。这些任务都是使用容器管理工具进行频繁的自动化部署的。 ### 决策时刻 @@ -109,7 +93,7 @@ David Strauss,[Pantheon][17] 的 CTO,他解释说:"[以前],你使用整 ### 评估业务风险 -技术力量雄厚的初创公司为追求较高的目标,可以考虑使用微服务。但是微服务可能会带来业务风险。Strauss 解释说,”许多团队一开始就过度构建他们的项目。每个人都认为,他们的公司会成为下一个 “独角兽”,因此,他们使用微服务构建任何一个东西,或者一些其它的高扩展性的基础设施。但是这通常是一种错误的做法“。Strauss 说,在那种情况下,他们认为需要扩大规模的领域往往并不是一开始真正需要扩展的领域,最后的结果是浪费了时间和努力。 +技术力量雄厚的初创公司为追求较高的目标,可以考虑使用微服务。但是微服务可能会带来业务风险。Strauss 解释说,“许多团队一开始就过度构建他们的项目。每个人都认为,他们的公司会成为下一个 ‘独角兽’,因此,他们使用微服务构建任何一个东西,或者一些其它的高扩展性的基础设施。但是这通常是一种错误的做法”。Strauss 说,在那种情况下,他们认为需要扩大规模的领域往往并不是一开始真正需要扩展的领域,最后的结果是浪费了时间和努力。 ### 态势感知 @@ -118,36 +102,30 @@ David Strauss,[Pantheon][17] 的 CTO,他解释说:"[以前],你使用整 #### 什么时候使用整体服务 * **你的团队还在创建阶段:** 你的团队很小 —— 也就是说,有 2 到 5 位成员 —— 还无法应对大范围、高成本的微服务架构。 - * **你正在构建的是一个未经证实的产品或者概念验证:** 如果你将一个全新的产品推向市场,随着时间的推移,它有可能会成功,而对于一个快速迭代的产品,整体架构是最合适的。这个提示也同样适用于概念验证,你的目标是尽可能快地学习,即便最终你可能会放弃它。 - * **你没有使用微服务的经验:** 除非你有合理的理由证明早期学习阶段的风险可控,否则,一个整体的架构更适用于一个没有经验的团队。 #### 什么时候开始使用微服务 * **你需要快速、独立的分发服务:** 微服务允许在一个大的集成系统中快速、独立分发单个部分。请注意,根据你的团队规模,获取与整体服务的比较优势,可能需要一些时间。 - * **你的平台中的某些部分需要更高效:** 如果你的业务要求集中处理 PB 级别的日志卷,你可能需要使用一个像 C++ 这样的更高效的语言来构建这个服务,尽管你的用户仪表板或许还是用 [Ruby on Rails][5] 构建的。 - * **计划扩展你的团队:** 使用微服务,将让你的团队从一开始就开发独立的小服务,而服务边界独立的团队更易于按需扩展。 要决定整体服务还是微服务更适合你的组织,要坦诚并正确认识自己的环境和能力。这将有助于你找到业务成长的最佳路径。 -### 主题 - - [微服务][21]、 [DevOps][22] - ### 关于作者 - [![](https://opensource.com/sites/default/files/styles/profile_pictures/public/pictures/profile_15.jpg?itok=EaSRMCN-)][18] jakelumetta - Jake 是 ButterCMS 的 CEO,它是一个 [API-first CMS][6]。他喜欢搅动出黄油双峰,以及构建让开发者工作更舒适的工具,喜欢他的更多内容,请在 Twitter 上关注 [@ButterCMS][7],订阅 [他的博客][8]。[关于他的更多信息][9] + [![](https://opensource.com/sites/default/files/styles/profile_pictures/public/pictures/profile_15.jpg?itok=EaSRMCN-)][18] + +jakelumetta - Jake 是 ButterCMS 的 CEO,它是一个 [API-first CMS][6]。他喜欢搅动出黄油双峰,以及构建让开发者工作更舒适的工具,喜欢他的更多内容,请在 Twitter 上关注 [@ButterCMS][7],订阅 [他的博客][8]。[关于他的更多信息][9]…… -------------------------------------------------------------------------------- via: https://opensource.com/article/18/1/how-choose-between-monolith-microservices -作者:[jakelumetta ][a] +作者:[jakelumetta][a] 译者:[qhwdw](https://github.com/qhwdw) -校对:[校对者ID](https://github.com/校对者ID) +校对:[wxy](https://github.com/wxy) 本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 From d2f0ab2f8b6c7165af43bdb06d9bb19810816f71 Mon Sep 17 00:00:00 2001 From: "Xingyu.Wang" Date: Wed, 30 May 2018 05:59:15 +0800 Subject: [PATCH 09/14] PUB:20180131 Microservices vs. monolith How to choose.md @qhwdw https://linux.cn/article-9695-1.html --- .../20180131 Microservices vs. monolith How to choose.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {translated/tech => published}/20180131 Microservices vs. monolith How to choose.md (100%) diff --git a/translated/tech/20180131 Microservices vs. monolith How to choose.md b/published/20180131 Microservices vs. monolith How to choose.md similarity index 100% rename from translated/tech/20180131 Microservices vs. monolith How to choose.md rename to published/20180131 Microservices vs. monolith How to choose.md From 8ad580cc38cc8d4fe3a1e18205b8f094ff90f2e4 Mon Sep 17 00:00:00 2001 From: geekpi Date: Wed, 30 May 2018 08:42:23 +0800 Subject: [PATCH 10/14] translated --- ...ck tip- quickly remove background noise.md | 55 ------------------- ...ck tip- quickly remove background noise.md | 53 ++++++++++++++++++ 2 files changed, 53 insertions(+), 55 deletions(-) delete mode 100644 sources/tech/20180521 Audacity quick tip- quickly remove background noise.md create mode 100644 translated/tech/20180521 Audacity quick tip- quickly remove background noise.md diff --git a/sources/tech/20180521 Audacity quick tip- quickly remove background noise.md b/sources/tech/20180521 Audacity quick tip- quickly remove background noise.md deleted file mode 100644 index c4943ba7ba..0000000000 --- a/sources/tech/20180521 Audacity quick tip- quickly remove background noise.md +++ /dev/null @@ -1,55 +0,0 @@ -translating---geekpi - -Audacity quick tip: quickly remove background noise -====== - -![](https://fedoramagazine.org/wp-content/uploads/2018/03/audacity-noise-816x345.png) -When recording sounds on a laptop — say for a simple first screencast — many users typically use the built-in microphone. However, these small microphones also capture a lot of background noise. In this quick tip, learn how to use [Audacity][1] in Fedora to quickly remove the background noise from audio files. - -### Installing Audacity - -Audacity is an application in Fedora for mixing, cutting, and editing audio files. It supports a wide range of formats out of the box on Fedora — including MP3 and OGG. Install Audacity from the Software application. - -![][2] - -If the terminal is more your speed, use the command: -``` -sudo dnf install audacity - -``` - -### Import your Audio, sample background noise - -After installing Audacity, open the application, and import your sound using the **File > Import** menu item. This example uses a [sound bite from freesound.org][3] to which noise was added: - -Next, take a sample of the background noise to be filtered out. With the tracks imported, select an area of the track that contains only the background noise. Then choose **Effect > Noise Reduction** from the menu, and press the **Get Noise Profile** button. - -![][4] - -### Filter the Noise - -Next, select the area of the track you want to filter the noise from. Do this either by selecting with the mouse, or **Ctrl + a** to select the entire track. Finally, open the **Effect > Noise Reduction** dialog again, and click OK to apply the filter. - -![][5] - -Additionally, play around with the settings until your tracks sound better. Here is the original file again, followed by the noise reduced track for comparison (using the default settings): - -https://ryanlerch.fedorapeople.org/sidebyside.ogg?_=2 - --------------------------------------------------------------------------------- - -via: https://fedoramagazine.org/audacity-quick-tip-quickly-remove-background-noise/ - -作者:[Ryan Lerch][a] -选题:[lujun9972](https://github.com/lujun9972) -译者:[译者ID](https://github.com/译者ID) -校对:[校对者ID](https://github.com/校对者ID) - -本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 - -[a]: https://fedoramagazine.org/introducing-flatpak/ -[1]:https://www.audacityteam.org/ -[2]:https://fedoramagazine.org/wp-content/uploads/2018/03/audacity-software.jpg -[3]:https://freesound.org/people/levinj/sounds/8323/ -[4]:https://fedoramagazine.org/wp-content/uploads/2018/03/select-noise-profile.gif -[5]:https://fedoramagazine.org/wp-content/uploads/2018/03/apply-filter.gif diff --git a/translated/tech/20180521 Audacity quick tip- quickly remove background noise.md b/translated/tech/20180521 Audacity quick tip- quickly remove background noise.md new file mode 100644 index 0000000000..0765199582 --- /dev/null +++ b/translated/tech/20180521 Audacity quick tip- quickly remove background noise.md @@ -0,0 +1,53 @@ +Audacity 快速指南:快速消除背景噪音 +====== + +![](https://fedoramagazine.org/wp-content/uploads/2018/03/audacity-noise-816x345.png) +当在笔记本电脑上录制声音时 - 比如首次简单地录屏 - 许多用户通常使用内置麦克风。但是,这些小型麦克风也会捕获很多背景噪音。在这个快速指南中,我们会学习如何使用 Fedora 中的 [Audacity][1] 快速移除音频文件中的背景噪音。 + +### 安装 Audacity + +Audacity 是 Fedora 中用于混合、剪切和编辑音频文件的程序。在 Fedora 上它支持各种开箱即用的格式 - 包括 MP3 和 OGG。从软件中心安装 Audacity。 + +![][2] + +如果你更喜欢终端,请使用以下命令: +``` +sudo dnf install audacity + +``` + +### 导入您的音频、样本背景噪音 + +安装 Audacity 后,打开程序,使用 **File > Import** 菜单项导入你的声音。这个例子使用了一个[来自 freesound.org 添加了噪音的声音][3]: + +接下来,采样要滤除的背景噪音。导入音轨后,选择仅包含背景噪音的音轨区域。然后从菜单中选择 **Effect > Noise Reduction**,然后按下 **Get Noise Profile** 按钮。 + +![][4] + +### 过滤噪音 + +接下来,选择你要过滤噪音的音轨区域。通过使用鼠标进行选择,或者按 **Ctrl + a** 来选择整个音轨。最后,再次打开 **Effect > Noise Reduction** 对话框,然后单击确定以应用滤镜。 + +![][5] + +此外,调整设置,直到你的音轨听起来更好。这里是原始文件,接下来是用于比较的降噪音轨(使用默认设置): + +https://ryanlerch.fedorapeople.org/sidebyside.ogg?_=2 + +-------------------------------------------------------------------------------- + +via: https://fedoramagazine.org/audacity-quick-tip-quickly-remove-background-noise/ + +作者:[Ryan Lerch][a] +选题:[lujun9972](https://github.com/lujun9972) +译者:[geekpi](https://github.com/geekpi) +校对:[校对者ID](https://github.com/校对者ID) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: https://fedoramagazine.org/introducing-flatpak/ +[1]:https://www.audacityteam.org/ +[2]:https://fedoramagazine.org/wp-content/uploads/2018/03/audacity-software.jpg +[3]:https://freesound.org/people/levinj/sounds/8323/ +[4]:https://fedoramagazine.org/wp-content/uploads/2018/03/select-noise-profile.gif +[5]:https://fedoramagazine.org/wp-content/uploads/2018/03/apply-filter.gif From 1995354eea17f0af59c8efefbc6a549f456fed13 Mon Sep 17 00:00:00 2001 From: geekpi Date: Wed, 30 May 2018 08:45:18 +0800 Subject: [PATCH 11/14] translating --- ...ing Stratis to manage Linux storage from the command line.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sources/tech/20180522 Using Stratis to manage Linux storage from the command line.md b/sources/tech/20180522 Using Stratis to manage Linux storage from the command line.md index 2bed178d85..9d1919c689 100644 --- a/sources/tech/20180522 Using Stratis to manage Linux storage from the command line.md +++ b/sources/tech/20180522 Using Stratis to manage Linux storage from the command line.md @@ -1,3 +1,5 @@ +translating----geekpi + Using Stratis to manage Linux storage from the command line ====== From d1438a6d55b886e8f810f478a0d1609b1c1f5ed8 Mon Sep 17 00:00:00 2001 From: qhwdw Date: Wed, 30 May 2018 15:32:36 +0800 Subject: [PATCH 12/14] Translated by qhwdw --- .../tech/20180418 Passwordless Auth Server.md | 214 +++++++++--------- 1 file changed, 107 insertions(+), 107 deletions(-) rename {sources => translated}/tech/20180418 Passwordless Auth Server.md (56%) diff --git a/sources/tech/20180418 Passwordless Auth Server.md b/translated/tech/20180418 Passwordless Auth Server.md similarity index 56% rename from sources/tech/20180418 Passwordless Auth Server.md rename to translated/tech/20180418 Passwordless Auth Server.md index 541d732212..50c8469080 100644 --- a/sources/tech/20180418 Passwordless Auth Server.md +++ b/translated/tech/20180418 Passwordless Auth Server.md @@ -1,45 +1,44 @@ -Translating by qhwdw -Passwordless Auth: Server +无密码验证:服务器 ============================================================ -Passwordless authentication allows logging in without a password, just an email. It’s a more secure way of doing than the classic email/password login. +无密码验证可以让你只输入一个 email 而无需输入密码即可登入系统。这是一种比传统的电子邮件/密码验证方式登入更安全的方法。 -I’ll show you how to code an HTTP API in [Go][6] that provides this service. +下面我将为你展示,如何在 [Go][6] 中实现一个 HTTP API 去提供这种服务。 -### Flow +### 业务流 -* User inputs his email. +* 用户输入他的电子邮件地址。 -* Server creates a temporal on-time-use code associated with the user (like a temporal password) and mails it to the user in form of a “magic link”. +* 服务器创建一个临时的一次性使用的代码(就像一个临时密码一样)关联到用户,然后给用户邮箱中发送一个“魔法链接”。 -* User clicks the magic link. +* 用户点击魔法链接。 -* Server extract the code from the magic link, fetch the user associated and redirects to the client with a new JWT. +* 服务器提取魔法链接中的代码,获取关联的用户,并且使用一个新的 JWT 重定向到客户端。 -* Client will use the JWT in every new request to authenticate the user. +* 在每次有新请求时,客户端使用 JWT 去验证用户。 -### Requisites +### 必需条件 -* Database: We’ll use an SQL database called [CockroachDB][1] for this. It’s much like postgres, but writen in Go. +* 数据库:我们为这个服务使用了一个叫 [CockroachDB][1] 的 SQL 数据库。它非常像 postgres,但它是用 Go 写的。 -* SMTP Server: To send mails we’ll use a third party mailing service. For development we’ll use [mailtrap][2]. Mailtrap sends all the mails to it’s inbox, so you don’t have to create multiple fake email accounts to test it. +* SMTP 服务器:我们将使用一个第三方的邮件服务器去发送邮件。开发的时我们使用 [mailtrap][2]。Mailtrap 发送所有的邮件到它的收件箱,因此,你在测试它们时不需要创建多个假冒邮件帐户。 -Install Go from [it’s page][7] and check your installation went ok with `go version`(1.10.1 atm). +从 [它的主页][7] 上安装 Go,然后使用 `go version`(1.10.1 atm)命令去检查它能否正常工作。 -Download CockroachDB from [it’s page][8], extract it and add it to your `PATH`. Check that all went ok with `cockroach version` (2.0 atm). +从 [它的主页][8] 上下载 CockroachDB,展开它并添加到你的 `PATH` 变量中。使用 `cockroach version`(2.0 atm)命令检查它能否正常工作。 -### Database Schema +### 数据库模式 -Now, create a new directory for the project inside `GOPATH` and start a new CockroachDB node with `cockroach start`: +现在,我们在 `GOPATH` 目录下为这个项目创建一个目录,然后使用 `cockroach start` 启动一个新的 CockroachDB 节点: ``` cockroach start --insecure --host 127.0.0.1 ``` -It will print some things, but check the SQL address line, it should said something like `postgresql://root@127.0.0.1:26257?sslmode=disable`. We’ll use this to connect to the database later. +它会输出一些内容,找到 SQL 地址行,它将显示像 `postgresql://root@127.0.0.1:26257?sslmode=disable` 这样的内容。稍后我们将使用它去连接到数据库。 -Create a `schema.sql` file with the following content. +使用如下的内容去创建一个 `schema.sql` 文件。 ``` DROP DATABASE IF EXISTS passwordless_demo CASCADE; @@ -63,28 +62,28 @@ INSERT INTO users (email, username) VALUES ``` -This script creates a database `passwordless_demo`, two tables: `users` and `verification_codes`, and inserts a fake user just to test it later. Each verification code is associated with a user and stores the creation date, useful to check if the code is expired or not. +这个脚本创建了一个名为 `passwordless_demo` 的数据库、两个名为 `users` 和 `verification_codes` 的表,以及为了稍后测试而插入的一些假冒用户。每个验证代码都与用户关联并保存代码创建数据,以用于去检查代码是否过期。 -To execute this script use `cockroach sql` in other terminal: +在另外的终端中使用 `cockroach sql` 命令去运行这个脚本: ``` cat schema.sql | cockroach sql --insecure ``` -### Environment Configuration +### 环境配置 -I want you to set two environment variables: `SMTP_USERNAME` and `SMTP_PASSWORD` that you can get from your mailtrap account. These two will be required by our program. +需要配置两个环境变量:`SMTP_USERNAME` 和 `SMTP_PASSWORD`,你可以从你的 mailtrap 帐户中获得它们。将在我们的程序中用到它们。 -### Go Dependencies +### Go 依赖 -For Go we’ll need the following packages: +我们需要下列的 Go 包: -* [github.com/lib/pq][3]: Postgres driver which CockroachDB uses. +* [github.com/lib/pq][3]:它是 CockroachDB 使用的 postgres 驱动 -* [github.com/matryer/way][4]: Router. +* [github.com/matryer/way][4]: 路由器 -* [github.com/dgrijalva/jwt-go][5]: JWT implementation. +* [github.com/dgrijalva/jwt-go][5]: JWT 实现 ``` go get -u github.com/lib/pq @@ -93,11 +92,11 @@ go get -u github.com/dgrijalva/jwt-go ``` -### Coding +### 代码 -### Init Function +### 初始化函数 -Create the `main.go` and start by getting some configuration from the environment inside the `init` function. +创建 `main.go` 并且通过 `init` 函数里的环境变量中取得一些配置来启动。 ``` var config struct { @@ -137,23 +136,23 @@ func env(key, fallbackValue string) string { ``` -* `appURL` will allow us to build the “magic link”. +* `appURL` 将去构建我们的 “魔法链接”。 -* `port` in which the HTTP server will start. +* `port` 将要启动的 HTTP 服务器。 -* `databaseURL` is the CockroachDB address, I added `/passwordless_demo` to the previous address to indicate the database name. +* `databaseURL` 是 CockroachDB 地址,我添加 `/passwordless_demo` 前面的数据库地址去表示数据库名字。 -* `jwtKey` used to sign JWTs. +* `jwtKey` 用于签名 JWTs。 -* `smtpAddr` is a join of `SMTP_HOST` + `SMTP_PORT`; we’ll use it to to send mails. +* `smtpAddr` 是 `SMTP_HOST` + `SMTP_PORT` 的联合;我们将使用它去发送邮件。 -* `smtpUsername` and `smtpPassword` are the two required vars. +* `smtpUsername` 和 `smtpPassword` 是两个必需的变量。 -* `smtpAuth` is also used to send mails. +* `smtpAuth` 也是用于发送邮件。 -The `env` function allow us to get an environment variable with a fallback value in case it doesn’t exist. +`env` 函数允许我们去获得环境变量,不存在时返回一个 fallback value。 -### Main Function +### 主函数 ``` var db *sql.DB @@ -181,7 +180,7 @@ func main() { ``` -First, it opens a database connection. Remember to load the driver. +首先,打开数据库连接。记得要加载驱动。 ``` import ( @@ -190,11 +189,11 @@ import ( ``` -Then, we create the router and define some endpoints. For the passwordless flow we use two endpoints: `/api/passwordless/start` mails the magic link and `/api/passwordless/verify_redirect` respond with the JWT. +然后,我们创建路由器并定义一些端点。对于无密码业务流来说,我们使用两个端点:`/api/passwordless/start` 发送魔法链接,和 `/api/passwordless/verify_redirect` 用 JWT 响应。 -Finally, we start the server. +最后,我们启动服务器。 -You can create empty handlers and middlewares to test that the server starts. +你可以创建空处理程序和中间件去测试服务器启动。 ``` func createUser(w http.ResponseWriter, r *http.Request) { @@ -227,7 +226,7 @@ func authRequired(next http.HandlerFunc) http.HandlerFunc { ``` -Now: +接下来: ``` go build @@ -235,11 +234,11 @@ go build ``` -I’m on a directory called “passwordless-demo”, but if yours is different, `go build` will create an executable with that name. If you didn’t close the previous cockroach node and you setted `SMTP_USERNAME` and `SMTP_PASSWORD` vars correctly, you should see `starting server at http://localhost/ 🚀` without errors. +我们在目录中有了一个 “passwordless-demo”,但是你的目录中可能与示例不一样,`go build` 将创建一个同名的可执行文件。如果你没有关闭前面的 cockroach 节点,并且你正确配置了 `SMTP_USERNAME` 和 `SMTP_PASSWORD` 变量,你将看到命令 `starting server at http://localhost/ 🚀` 没有错误输出。 -### JSON Required Middleware +### JSON 要求的中间件 -Endpoints that need to decode JSON from the request body need to make sure the request is of type `application/json`. Because that is a common thing, I decoupled it to a middleware. +端点需要从请求体中解码 JSON,因此要确保请求是 `application/json` 类型。因为它是一个通用的东西,我将它解耦到中间件。 ``` func jsonRequired(next http.HandlerFunc) http.HandlerFunc { @@ -256,11 +255,11 @@ func jsonRequired(next http.HandlerFunc) http.HandlerFunc { ``` -As easy as that. First it gets the request content type from the headers, then check if it starts with “application/json”, otherwise it early return with `415 Unsupported Media Type`. +实现很容易。首先它从请求头中获得内容的类型,然后检查它是否是以 “application/json” 开始,如果不是则以 `415 Unsupported Media Type` 提前返回。 -### Respond JSON Function +### 响应 JSON 函数 -Responding with JSON is also a common thing so I extracted it to a function. +以 JSON 响应是非常通用的做法,因此我把它提取到函数中。 ``` func respondJSON(w http.ResponseWriter, payload interface{}, code int) { @@ -284,11 +283,11 @@ func respondJSON(w http.ResponseWriter, payload interface{}, code int) { ``` -First, it does a type assertion for primitive types to wrap they in a `map`. Then it marshalls to JSON, sets the response content type and status code, and writes the JSON. In case the JSON marshalling fails, it respond with an internal error. +首先,对原始类型做一个类型判断,并将它们封装到一个 `map`。然后将它们编组到 JSON,设置响应内容类型和状态码,并写 JSON。如果 JSON 编组失败,则响应一个内部错误。 -### Respond Internal Error Function +### 响应内部错误的函数 -`respondInternalError` is a funcion that respond with `500 Internal Server Error`, but it also logs the error to the console. +`respondInternalError` 是一个响应 `500 Internal Server Error` 的函数,但是也同时将错误输出到控制台。 ``` func respondInternalError(w http.ResponseWriter, err error) { @@ -300,9 +299,9 @@ func respondInternalError(w http.ResponseWriter, err error) { ``` -### Create User Handler +### 创建用户处理程序 -I’ll start coding the `createUser` handler because is the more easy and REST-ish. +下面开始编写 `createUser` 处理程序,因为它非常容易并且是 REST 式的。 ``` type User struct { @@ -313,7 +312,7 @@ type User struct { ``` -The `User` type is just like the `users` table. +`User` 类型和 `users` 表相似。 ``` var ( @@ -323,9 +322,9 @@ var ( ``` -These regular expressions are to validate email and username respectively. These are very basic, feel free to adapt they as you need. +这些正则表达式是分别用于去验证电子邮件和用户名的。这些都很简单,可以根据你的需要随意去适配。 -Now, **inside** `createUser` function we’ll start by decoding the request body. +现在,在 `createUser` 函数内部,我们将开始解码请求体。 ``` var user User @@ -337,7 +336,7 @@ defer r.Body.Close() ``` -We create a JSON decoder using the request body and decode to a user pointer. In case of error we return with a `400 Bad Request`. Don’t forget to close the body reader. +我们将使用请求体去创建一个 JSON 解码器来解码出一个用户指针。如果发生错误则返回一个 `400 Bad Request`。不要忘记关闭请求体读取器。 ``` errs := make(map[string]string) @@ -358,7 +357,7 @@ if len(errs) != 0 { ``` -This is how I make validation; a simple `map` and check if `len(errs) != 0` to return with `422 Unprocessable Entity`. +这是我如何做验证;一个简单的 `map` 并检查如果 `len(errs) != 0`,则使用 `422 Unprocessable Entity` 去返回。 ``` err := db.QueryRowContext(r.Context(), ` @@ -381,18 +380,18 @@ if errPq, ok := err.(*pq.Error); ok && errPq.Code.Name() == "unique_violation" { ``` -This SQL query inserts a new user with the given email and username, and returns the auto generated id. Each `$` will be replaced by the next arguments passed to `QueryRowContext`. +这个 SQL 查询使用一个给定的 email 和用户名去插入一个新用户,并返回自动生成的 id,每个 `$` 将被接下来传递给 `QueryRowContext` 的参数替换掉。 -Because the `users` table had unique constraints on the `email` and `username`fields I check for the “unique_violation” error to return with `403 Forbidden` or I return with an internal error. +因为 `users` 表在 `email` 和 `username` 字段上有唯一性约束,因此我将检查 “unique_violation” 错误并返回 `403 Forbidden` 或者返回一个内部错误。 ``` respondJSON(w, user, http.StatusCreated) ``` -Finally I just respond with the created user. +最后使用创建的用户去响应。 -### Passwordless Start Handler +### 无密码验证开始部分的处理程序 ``` type PasswordlessStartRequest struct { @@ -402,14 +401,14 @@ type PasswordlessStartRequest struct { ``` -This struct holds the `passwordlessStart` request body. The email of the user who wants to log in. The redirect URI comes from the client (the app that will use our API) ex: `https://frontend.app/callback`. +这个结构体持有 `passwordlessStart` 的请求体。希望去登入的用户 email。来自客户端的重定向 URI(这个应用中将使用我们的 API)如:`https://frontend.app/callback`。 ``` var magicLinkTmpl = template.Must(template.ParseFiles("templates/magic-link.html")) ``` -We’ll use the golang template engine to build the mailing so I’ll need you to create a `magic-link.html` file inside a `templates` directory with a content like so: +我们将使用 golang 模板引擎去构建邮件,因此需要你在 `templates` 目录中,用如下的内容创建一个 `magic-link.html` 文件: ``` @@ -428,9 +427,9 @@ We’ll use the golang template engine to build the mailing so I’ll need you t ``` -This template is the mail we’ll send to the user with the magic link. Feel free to style it how you want. +这个模板是给用户发送魔法链接邮件用的。你可以根据你的需要去随意调整它。 -Now, **inside** `passwordlessStart` function: +现在, 进入 `passwordlessStart` 函数**内部**: ``` var input PasswordlessStartRequest @@ -442,7 +441,7 @@ defer r.Body.Close() ``` -First, we decode the request body like before. +首先,我们像前面一样解码请求体。 ``` errs := make(map[string]string) @@ -463,7 +462,7 @@ if len(errs) != 0 { ``` -For the redirect URI validation we use the golang URL parser and check that the URI is absolute. +我们使用 golang 的 URL 解析器去验证重定向 URI,检查那个 URI 是否为绝对地址。 ``` var verificationCode string @@ -482,7 +481,7 @@ if errPq, ok := err.(*pq.Error); ok && errPq.Code.Name() == "not_null_violation" ``` -This SQL query will insert a new verification code associated with a user with the given email and return the auto generated id. Because the user could not exist, that subquery can resolve to `NULL` which will fail the `NOT NULL`constraint on the `user_id` field so I do a check on that and return with `404 Not Found` in case or an internal error otherwise. +这个 SQL 查询将插入一个验证代码,这个代码通过给定的 email 关联到用户,并且返回一个自动生成的 id。因为有可能会出现用户不存在的情况,那样的话子查询可能解析为 `NULL`,这将导致在 `user_id` 字段上因违反 `NOT NULL` 约束而导致失败,因此需要对这种情况进行检查,如果用户不存在,则返回 `404 Not Found` 或者一个内部错误。 ``` q := make(url.Values) @@ -494,7 +493,7 @@ magicLink.RawQuery = q.Encode() ``` -Now, I build the magic link and set the `verification_code` and `redirect_uri`inside the query string. Ex: `http://localhost/api/passwordless/verify_redirect?verification_code=some_code&redirect_uri=https://frontend.app/callback`. +现在,构建魔法链接并设置查询字符串中的 `verification_code` 和 `redirect_uri` 的值。如:`http://localhost/api/passwordless/verify_redirect?verification_code=some_code&redirect_uri=https://frontend.app/callback`。 ``` var body bytes.Buffer @@ -506,7 +505,7 @@ if err := magicLinkTmpl.Execute(&body, data); err != nil { ``` -We’ll get the magic link template content saving it to a buffer. In case of error I return with an internal error. +我们将得到的魔法链接模板的内容保存到缓冲区中。如果发生错误则返回一个内部错误。 ``` to := mail.Address{Address: input.Email} @@ -517,16 +516,16 @@ if err := sendMail(to, "Magic Link", body.String()); err != nil { ``` -To mail the user I make use of `sendMail` function that I’ll code now. In case of error I return with an internal error. +现在来写给用户发邮件的 `sendMail` 函数。如果发生错误则返回一个内部错误。 ``` w.WriteHeader(http.StatusNoContent) ``` -Finally, I just set the response status code to `204 No Content`. The client doesn’t need more data than a success status code. +最后,设置响应状态码为 `204 No Content`。对于成功的状态码,客户端不需要很多数据。 -### Send Mail Function +### 发送邮件函数 ``` func sendMail(to mail.Address, subject, body string) error { @@ -557,18 +556,18 @@ func sendMail(to mail.Address, subject, body string) error { ``` -This function creates the structure of a basic HTML mail and sends it using the SMTP server. There is a lot of things you can customize of a mail, but I kept it simple. +这个函数创建一个基本的 HTML 邮件结构体并使用 SMTP 服务器去发送它。邮件的内容你可以随意定制,我喜欢使用比较简单的内容。 -### Passwordless Verify Redirect Handler +### 无密码验证重定向处理程序 ``` var rxUUID = regexp.MustCompile("^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$") ``` -First, this regular expression is to validate an UUID (the verification code). +首先,这个正则表达式去验证一个 UUID(验证代码)。 -Now, **inside** `passwordlessVerifyRedirect` function: +现在进入 `passwordlessVerifyRedirect` 函数 **内部**: ``` q := r.URL.Query() @@ -577,7 +576,7 @@ redirectURI := q.Get("redirect_uri") ``` -`/api/passwordless/verify_redirect` is a `GET` endpoint so we read data from the query string. +`/api/passwordless/verify_redirect` 是一个 `GET` 端点,以便于我们从查询字符串中读取数据。 ``` errs := make(map[string]string) @@ -600,7 +599,7 @@ if len(errs) != 0 { ``` -Pretty similar validation, but we store the parsed redirect URI into a `callback`variable. +类似的验证,我们保存解析后的重定向 URI 到一个 `callback` 变量中。 ``` var userID string @@ -619,7 +618,7 @@ if err := db.QueryRowContext(r.Context(), ` ``` -This SQL query deletes a verification code with the given id and makes sure it has been created no more than 15 minutes ago, it also returns the `user_id`associated. In case of no rows, means the code didn’t exist or it was expired so we respond with that, otherwise an internal error. +这个 SQL 查询通过给定的 id 去删除相应的验证代码,并且确保它创建之后时间不超过 15 分钟,它也返回关联的 `user_id`。如果没有检索到内容,意味着代码不存在或者已过期,我们返回一个响应信息,否则就返回一个内部错误。 ``` expiresAt := time.Now().Add(time.Hour * 24 * 60) @@ -634,7 +633,7 @@ if err != nil { ``` -This is how the JWT is created. We set an expiration date for the JWT within 60 days. Maybe you can give it less time (~2 weeks) and add a new endpoint to refresh tokens, but I didn’t want to add more complexity. +这些是如何去创建 JWT。我们为 JWT 设置一个 60 天的过期值,你也可以设置更短的时间(大约 2 周),并添加一个新端点去刷新令牌,但是不要搞的过于复杂。 ``` expiresAtB, err := expiresAt.MarshalText() @@ -649,24 +648,24 @@ callback.Fragment = f.Encode() ``` -We plan to redirect; you could use the query string to add the JWT, but I’ve seen that a hash fragment is more used. Ex: `https://frontend.app/callback#jwt=token_here&expires_at=some_date`. +我们去规划重定向;你可使用查询字符串去添加 JWT,但是更常见的是使用一个哈希片段。如:`https://frontend.app/callback#jwt=token_here&expires_at=some_date`. -The expiration date could be extracted from the JWT, but then the client will have to implement a JWT library to decode it, so to make the life easier I just added it there too. +过期日期可以从 JWT 中提取出来,但是这样做的话,就需要在客户端上实现一个 JWT 库来解码它,因此为了简化,我将它加到这里。 ``` http.Redirect(w, r, callback.String(), http.StatusFound) ``` -Finally we just redirect with a `302 Found`. +最后我们使用一个 `302 Found` 重定向。 * * * -The passwordless flow is completed. Now we just need to code the `getAuthUser`endpoint which is to get info about the current authenticated user. If you rememeber, this endpoint makes use of `authRequired` middleware. +无密码的工作流已经完成。现在需要去写 `getAuthUser` 端点的代码了,它用于获取当前验证用户的信息。你应该还记得,这个端点使用了 `authRequired` 中间件。 -### With Auth Middleware +### 使用 Auth 中间件 -Before coding the `authRequired` middleware, I’ll code one that doesn’t require authentication. I mean, if no JWT is passed, it just continues without authenticating the user. +在编写 `authRequired` 中间件之前,我将编写一个不需要验证的分支。目的是,如果没有传递 JWT,它将不去验证用户。 ``` type ContextKey int @@ -711,13 +710,13 @@ func withAuth(next http.HandlerFunc) http.HandlerFunc { ``` -The JWT will come in every request inside the “Authorization” header in the form of “Bearer ”. So if no token is present, we just pass to the next middleware. +JWT 将在每次请求时以 “Bearer ” 格式包含在 “Authorization” 头中。因此,如果没有提供令牌,我们将直接通过,进入接下来的中间件。 -We create a parser and parse the token. If fails, we return with `401 Unauthorized`. +我们创建一个解析器来解析令牌。如果解析失败则返回 `401 Unauthorized`。 -Then we extract the claims inside the JWT and add the `Subject` (which is the user ID) to the request context. +然后我们从 JWT 中提取出要求的内容,并添加 `Subject`(就是用户 ID)到需要的地方。 -### Auth Required Middleware +### Auth 需要的中间件 ``` func authRequired(next http.HandlerFunc) http.HandlerFunc { @@ -731,13 +730,14 @@ func authRequired(next http.HandlerFunc) http.HandlerFunc { }) } + ``` -Now, `authRequired` will make use of `withAuth` and will try to extract the authenticated user ID from the request context. If fails, it returns with `401 Unauthorized` otherwise continues. +现在,`authRequired` 将使用 `withAuth` 并从请求内容中提取出验证用户的 ID。如果提取失败,它将返回 `401 Unauthorized`,提取成功则继续下一步。 -### Get Auth User +### 获取 Auth 用户 -**Inside** `getAuthUser` handler: +在 `getAuthUser` 处理程序**内部**: ``` ctx := r.Context() @@ -756,11 +756,11 @@ respondJSON(w, user, http.StatusOK) ``` -First we extract the ID of the authenticated user from the request context, we use that to fetch the user. In case of no row returned, we send a `418 I'm a teapot` or an internal error otherwise. Lastly we just respond with the user 😊 +首先,我们从请求内容中提取验证用户的 ID,我们使用这个 ID 去获取用户。如果没有获取到内容,则发送一个 `418 I'm a teapot`,或者一个内部错误。最后,我们将用这个用户去响应 😊 -### Fetch User Function +### 获取 User 函数 -You saw a `fetchUser` function there. +下面你看到的是 `fetchUser` 函数。 ``` func fetchUser(ctx context.Context, id string) (User, error) { @@ -773,24 +773,24 @@ func fetchUser(ctx context.Context, id string) (User, error) { ``` -I decoupled it because fetching a user by ID is a common thing. +我将它解耦是因为通过 ID 来获取用户是个常做的事。 * * * -That’s all the code. Build it and test it yourself. You can try a live demo [here][9]. +以上就是全部的代码。你可以自己去构建它和测试它。[这里][9] 还有一个 demo 你可以试用一下。 -If you have problems about `Blocked script execution because the document's frame is sandboxed and the 'allow-scripts' permission is not set` after clicking the magic link on mailtrap, try doing a right click + “Open link in new tab”. This is a security thing where the mail content is [sandboxed][10]. I had this problem sometimes on `localhost`, but I think you should be fine once you deploy the server with `https://`. +如果你在 mailtrap 上点击之后出现有关 `脚本运行被拦截,因为文档的框架是沙箱化的,并且没有设置 'allow-scripts' 权限` 的问题,你可以尝试右键点击 “在新标签中打开链接“。这样做是安全的,因为邮件内容是 [沙箱化的][10]。我在 `localhost` 上有时也会出现这个问题,但是我认为你一旦以 `https://` 方式部署到服务器上应该不会出现这个问题了。 -Please leave any issues on the [GitHub repo][11] or feel free to send PRs 👍 +如果有任何问题,请在我的 [GitHub repo][11] 留言或者提交 PRs 👍 -I’ll write a second part for this post coding a client for the API. +以后,我将为这个 API 写一个客户端作为这篇文章的第二部分。 -------------------------------------------------------------------------------- via: https://nicolasparada.netlify.com/posts/passwordless-auth-server/ 作者:[Nicolás Parada ][a] -译者:[译者ID](https://github.com/译者ID) +译者:[qhwdw](https://github.com/qhwdw) 校对:[校对者ID](https://github.com/校对者ID) 本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 From 9dfa29dcc4035dc929f93ed8a79fc02606ec3a9f Mon Sep 17 00:00:00 2001 From: ChenYi <31087327+cyleft@users.noreply.github.com> Date: Wed, 30 May 2018 15:42:06 +0800 Subject: [PATCH 13/14] Update 20180522 How to Enable Click to Minimize On Ubuntu.md apply for translated --- .../tech/20180522 How to Enable Click to Minimize On Ubuntu.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sources/tech/20180522 How to Enable Click to Minimize On Ubuntu.md b/sources/tech/20180522 How to Enable Click to Minimize On Ubuntu.md index 50d68ad445..761138908d 100644 --- a/sources/tech/20180522 How to Enable Click to Minimize On Ubuntu.md +++ b/sources/tech/20180522 How to Enable Click to Minimize On Ubuntu.md @@ -1,3 +1,5 @@ +translated by cyleft + How to Enable Click to Minimize On Ubuntu ============================================================ From bafbce2be16ab620cedd8b0938b69e6993c7a921 Mon Sep 17 00:00:00 2001 From: songshunqiang Date: Wed, 30 May 2018 17:18:30 +0800 Subject: [PATCH 14/14] submit tech/20180315 Kubernetes distributed application deployment with sample Face Recognition App.md --- ...oyment with sample Face Recognition App.md | 1111 ---------------- ...oyment with sample Face Recognition App.md | 1113 +++++++++++++++++ 2 files changed, 1113 insertions(+), 1111 deletions(-) delete mode 100644 sources/tech/20180315 Kubernetes distributed application deployment with sample Face Recognition App.md create mode 100644 translated/tech/20180315 Kubernetes distributed application deployment with sample Face Recognition App.md diff --git a/sources/tech/20180315 Kubernetes distributed application deployment with sample Face Recognition App.md b/sources/tech/20180315 Kubernetes distributed application deployment with sample Face Recognition App.md deleted file mode 100644 index 83ef4b1835..0000000000 --- a/sources/tech/20180315 Kubernetes distributed application deployment with sample Face Recognition App.md +++ /dev/null @@ -1,1111 +0,0 @@ -pinewall translating - -Kubernetes distributed application deployment with sample Face Recognition App -============================================================ - -# Intro - - -Alright folks. Settle in. This is going to be a long, but hopefully, fun ride. - -I’m going to deploy a distributed application with [Kubernetes][5]. I was trying to write an app which I thought is as close to real-world as possible. But obviously I cut some corners because of time and energy constraints. - -My focus will be on Kubernetes and deployment. - -Shall we? - -# The Application - -### TL;DR - -![kube overview](https://skarlso.github.io/img/kube_overview.png) - -The application itself consists of six parts. The repository can be found here: [Kube Cluster Sample][6]. - -It is a face recognition service which identifies images of people, comparing them to known individuals. A simple frontend displays a table of these images and whom they belong to. This happens by sending a request to a [receiver][7]. The request contains a path to an image. The image could be located anywhere. The receiver stores this path in the DB (MySQL) and sends a processing request to a queue. The queue uses [NSQ][8]. The request contains the ID of the saved image. - -An [Image Processing][9] service is constantly monitoring the queue for jobs to do. The processing consists of the following steps: taking the ID, loading in the image and sending off the path of the image to a [face recognition][10] backend, written in Python, via [gRPC][11]. If the identification was successful, the backend returns the name of the image corresponding to that person. The image_processor then updates the image record with the person id and marks the image as processed successfully. If identification is unsuccessful the image is left as pending. If there was a failure during identification the image is flagged as failed. - -Failed images can be re-tried with a cron job, for example. - -So how does this all work? Let’s dive in. - -### Receiver - -The receiver service is the starting point of the process. It’s an API which receives a request in the following format: - -``` -curl -d '{"path":"/unknown_images/unknown0001.jpg"}' http://127.0.0.1:8000/image/post - -``` - -In this moment, receiver stores this path using a shared database cluster. The entity then will receive an ID from the database service. This application is based on the model where unique identification for Entity Objects is provided by the persistence layer. Once the ID is acquired, receiver will send a message to NSQ. The receiver’s job is done at this point. - -### Image Processor - -Here is where the fun begins. When Image Processor first runs it creates two Go routines. These are… - -### Consume - -This is an NSQ consumer. It has three jobs. First, it listens for messages on the queue. Second, when there is a message it appends the received ID to a thread safe slice of IDs that the second routine processes. Lastly it signals the second routine that there is work to do. It does that through [sync.Condition][12]. - -### ProcessImages - -This routine processes a slice of IDs until the slice is drained completely. Once the slice is drained the routine goes into suspend instead of sleep-waiting on a channel. The processing of a single ID is through the following steps in order: - -* Establish a gRPC connection to the Face Recognition service (explained under Face Recognition) - -* Retrieve the image record from the database - -* Setup two functions for the [Circuit Breaker][1] - * Function 1: The main function which does the RPC method call - - * Function 2: A health check for the Ping of the circuit breaker - -* Call Function 1 which sends the path of the image to the face recognition service. This path should be accessible by the face recognition service. Preferably something shared, like an NFS - -* If this call fails, update the image record as FAILEDPROCESSING - -* If it succeeds, an image name should come back which corresponds to a person in the db. It runs a joined SQL query which gets the corresponding person’s id - -* Update the Image record in the database with PROCESSED status and the ID of the person that image was identified as - -This service can be replicated, meaning, more than one could run at the same time. - -### Circuit Breaker - -In a system where replicating resources requires little to no effort, there still could be cases where, for example, the network goes down, or there are communication problems of any kind between two services. I implement a little circuit breaker around the gRPC calls for fun mostly. - -This is how it works: - -![kube circuit](https://skarlso.github.io/img/kube_circuit1.png) - -Once there are 5 unsuccessful calls to the service the circute breaker activates and doesn’t allow any more calls to go through. After a configured amount of time, it will send a health check to the service to see if it’s back up. If that still errors out, it increases the timeout. If not, it opens the circuit and allows traffic to proceed. - -### Front-End - -This is only a simplistic table view with Go’s own html/template used to render a list of images. - -### Face Recognition - -Here is where the identification magic is happening. I decided to make this a gRPC based service for the sole purpose of flexibility. I started writing it in Go, but decided that a Python implementation could be much sorter. In fact, not counting the gRPC code, the recognition part is about 7 lines of code. I’m using this fantastic library which has all the C bindings to OpenCV. [Face Recognition][13]. Having an API contract here means that I can change the implementation anytime as long as it adheres to the contract. - -Note that there is a great Go library that I was about to use, but they have yet to write the needed C bindings. It’s called [GoCV][14]. Go, check them out. They have some pretty amazing things, like real time camera feed processing with only a couple of lines of code. - -How the Python library works is simple in nature. Have a set of images about people you know and have a record for. In this case, I have a folder with a couple of images named, `hannibal_1.jpg, hannibal_2.jpg, gergely_1.jpg, john_doe.jpg`. In the database I have two tables named, `person, person_images`. They look like this: - -``` -+----+----------+ -| id | name | -+----+----------+ -| 1 | Gergely | -| 2 | John Doe | -| 3 | Hannibal | -+----+----------+ -+----+----------------+-----------+ -| id | image_name | person_id | -+----+----------------+-----------+ -| 1 | hannibal_1.jpg | 3 | -| 2 | hannibal_2.jpg | 3 | -+----+----------------+-----------+ - -``` - -The face recognition library returns the name of the image the unknown image matches to. After that, a simple joined query like this will return the person in question. - -``` -select person.name, person.id from person inner join person_images as pi on person.id = pi.person_id where image_name = 'hannibal_2.jpg'; - -``` - -The gRPC call returns the id of the person which is than used to update the image’s `person` column. - -### NSQ - -NSQ is a little Go based queue. It can be scaled and has a minimal footprint on the system. It has a lookup service which consumers use to receive messages and a daemon that senders use to send messages. - -NSQ’s philosophy is that the daemon should run with the sender application. That way, the sender sends to localhost only. But the daemon is connected to the lookup service and that’s how they achieve a global queue. - -This means that there are as many NSQ daemons deployed as there are senders. Because the daemon has a minuscule resource requirement, it won’t interfere with the requirements of the main application. - -### Configuration - -In order to be as flexible as possible and making use of Kubernetes’ ConfigSet, I’m using .env files in development to store configuration like the location of the database service or NSQ’s lookup address. In production, and that means the Kubernetes environment, I’ll use environment properties. - -### Conclusion for the Application - -And that’s all there is to the architecture of the application we are about to deploy. All of its components are changeable and only coupled through the database, a queue and gRPC. This is imperative when deploying a distributed application because of how updating mechanics work. I will cover that part in the Deployment section. - -# Deployment with Kubernetes - -### Basics - -What is Kubernetes? - -I’ll cover some basics here, although I won’t go too much into details as that would require a whole book like this one: [Kubernetes Up And Running][15]. Also, you can look at the documentation if you are daring enough: [Kubernetes Documentation][16]. - -Kubernetes is a containerized service and application manager. It scales easily, employs a swarm of containers but more importantly, it’s highly configurable via yaml based template files. People compare Kubernetes to Docker swarm, but Kubernetes does way more than that. For example, it’s container agnostic. You could use LXC with Kubernetes and it would work the same way you would use it with Docker. It provides a layer above managing a cluster of deployed services and applications. How? Let’s take a quick look at the building blocks of Kubernetes. - -In Kubernetes you describe a desired state of the application and Kubernetes will do what it can to reach that state. States could be something like, deployed, paused, replicated 2 times and so and so forth. - -One of the basics of Kubernetes is that it uses Labels and Annotations for all it’s components. Services, Deployments, ReplicaSets, DaemonSets, everyhting is labelled. Consider the following scenario. In order to identify what pod belongs to what application a labeled is used called `app: myapp`. Lets assume you have to containers of this application deployed. If you would remove the label `app` from one of the containers, Kubernetes would only detect one and thus would launch a new instance of `myapp`. - -### Kubernetes Cluster - -For Kuberenetes to work, a Kubernetes cluster needs to be present. Setting that up one might be a bit painful, but luckily help is there. Minikube sets up a cluster for us locally with one Node. And AWS has a beta service running in the form of a Kubernetes cluster where the only thing you need to do is request nodes and define your deployments. The Kubernetes cluster components are documented here: [Kubernetes Cluster Components][17]. - -### Nodes - -A Node is a worker machine. It can be anything from a vm to a physical machine, including all sorts of cloud provided vms. - -### Pods - -Pods are a logically grouped collection of containers. That means, one Pod can potentially house a multitude of containers. A Pod gets its own DNS and virtual IP address after it has been created so Kubernetes can load balancer traffic to it. You rarely have to deal with containers directly. Even when debugging, like looking at logs, you usually invoke `kubectl logs deployment/your-app -f` instead of looking at a specific container. Although it is possible with `-c container_name`. The `-f` does a tail on the log. - -### Deployments - -When creating any kind of resource in Kubernetes, it will use a Deployment in the background. A deployment describes a desired state of the current application. It’s an object you can use to update Pods or a Service to be in a different state; do an update, or rollout new version of your app. You don’t directly conrtol a ReplicaSet (described later) but control the deployment object which creates and manages a ReplicaSet. - -### Services - -By default a Pod will get an IP address. However, since Pods are a volatile thing in Kubernetes you’ll need something more permanent. A queue, mysql, or an internal API, a frontend; these need to be long running and behind a static, unchanging IP or preferably a DNS record. - -For this purpose, Kubernetes has Services for which you can define modes of accessibility. Load Balanced, simple IP or internal DNS. - -How does Kubernetes know if a service is running correctly? You can configure Health Checks and Availability Checks. A HealtCheck will check if a container is running but that doesn’t mean that your service is running. For that, you have the availability check which pings a different endpoint in your application. - -Since Services are pretty important, I recommend that you read up on them later here: [Services][18]. Fair warning, this is quiet dense. 24 A4 pages of networking, services and discovery. It’s also important to understand if you want to seriously use Kubernetes in production. - -### DNS / Service Discovery - -If you create a service in the cluster that service will get a DNS record in Kubernetes provided by special Kubernetes deployments called kube-proxy and kube-dns. These two provid service discover inside a cluster. If you have a mysql service running and set `clusterIP: none`, than everyone in the cluster can reach that service by pinging `mysql.default.svc.cluster.local`. Where: - -* `mysql` – is the name of the service - -* `default` – is the namespace name - -* `svc` – is services - -* `cluster.local` – is a local cluster domain - -The domain can be changed by using a custom definition. To access a service outside the cluster, a DNS provider has to be used and Nginx (for example) to bind an IP address to a record. The public IP address of a service can be queried with the following commands: - -* NodePort – `kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services mysql` - -* LoadBalancer – `kubectl get -o jsonpath="{.spec.ports[0].LoadBalancer}" services mysql` - -### Template Files - -Like Docker Compose, or TerraForm or other service management tools, Kubernetes also provides infrastructure describing templates. What that means is that you rarely have to do anything by hand. - -For example consider the following yaml template which describes an nginx Deployment: - -``` -apiVersion: apps/v1 -kind: Deployment #(1) -metadata: #(2) - name: nginx-deployment - labels: #(3) - app: nginx -spec: #(4) - replicas: 3 #(5) - selector: - matchLabels: - app: nginx - template: - metadata: - labels: - app: nginx - spec: - containers: #(6) - - name: nginx - image: nginx:1.7.9 - ports: - - containerPort: 80 - -``` - -This is a simple deployment where we do the following: - -* (1) Define the type of the template with kind - -* (2) Add metadata that will identify this deployment and every resource that it would create with a label (3) - -* (4) Then comes the spec which describes the desired state - * (5) For the nginx app have 3 replicas - - * (6) This is the template definition for the containers that this Pod will contain - - * nginx named container - - * nginx:1.7.9 image (docker in this case) - - * exposed ports - -### ReplicaSet - -A ReplicaSet is a low level replication manager. It ensures that the correct number of replicates are running for a application. However, Deployments are higher level and should always manage ReplicaSets. You rarely have to use ReplicaSets directly. Unless you have a fringe case where you want to control the specifics of replication. - -### DaemonSet - -Remember how I said Kubernetes is using Labels all the time? A DaemonSet is a controller that ensures that at daemonized application is always running on a node with a certain label. - -For example, you want all the nodes labelled with `logger` or `mission_critical` to run an logger / auditing service daemon. Then you create a DaemonSet and give it a node selector called `logger` or `mission_critical`. Kubernetes will look for a node that has that label and always ensure that it will have an instance of that daemon running on it. Thus everyone running on that node will have access to that daemon locally. - -In case of my application the NSQ daemon could be a DaemonSet. I would ensure it’s up on a node which has the receiver component running by labelling a node with `receiver` and specifying a DaemonSet with `receiver` application selector. - -The DaemonSet has all the benefits of the ReplicaSet. It’s scalable and Kubernetes manages it; which means, all life cycle events are handled by Kube enusring it never dies or if it dies it gets immediately replaced. - -### Scaling - -In Kubernetes it’s trivial to scale. The ReplicaSets take care of the number of instances of a Pod to run. Like you saw in the nginx deployment with the setting `replicas:3`. It’s up to us to write our application in a way that it allows Kubernetes to run multiple copies of it. - -Of course the settings are vast. You can specify that the replicates must run on different Nodes, or various waiting times on how long to wait for an instance to come up. You can read up more on this subject here: [Horizontal Scaling][19] and here: [Interactive Scaling with Kubernetes][20] and of course the details of a [ReplicaSet][21] which controls all the scaling made possible in Kubernetes. - -### Conclusion for Kubernetes - -It’s a convenient tool to handle container orchestration. Its unit of work are Pods and it has a layered architecture. The top level layer is Deployments through which you handle all other resources. It’s highly configurable. It provides an API for all calls you make, so potentionally, instead of running `kubectl` you can also write your own logic to send information to the Kubernetes API. - -It provides support for all major cloud providers natively by now and it’s completely open source. Feel free to contribute, check the code if you would like to have a deeper understanding on how it works: [Kubernetes on Github][22]. - -### Minikube - -I’m going to use [Minikube][23]. Minikube is a local kubernetes cluster simulator. It’s not great in simulating multiple nodes though, but for starting out and local play without any costs, it’s great. It uses a VM that can be fine tuned if necessary using VirtualBox and the likes. - -All the kube template files that I’ll be using are located here: [Kube files][24]. - -NOTE If, later on, you would like to play with scaling, but notice that the replicates are always in `Pending` state, remember, that minikube employs a single node only. It might not allow multiple replicas on the same node, or just plain ran out of resources to use. You can check available resources with the following command: - -``` -kubectl get nodes -o yaml - -``` - -### Building the containers - -Kubernetes supports most of the containers out there. I’m going to use Docker. For all the services I’ve built, there is a Dockerfile included in the repository. I encourage you to study them. Most of them are simple. For the go services I’m using a multi stage build that got recently introduced. The Go services are Alpine Linux based. The Face Recognition service is Python. NSQ and MySQL are using their own containers. - -### Context - -Kubernetes uses namespaces. If you don’t specify any it will use the `default` namespace. I’m going to permanently set a context to avoid polluting the default namespace. You do that like this: - -``` -❯ kubectl config set-context kube-face-cluster --namespace=face -Context "kube-face-cluster" created. - -``` - -You have to also start using the context once it’s created like so: - -``` -❯ kubectl config use-context kube-face-cluster -Switched to context "kube-face-cluster". - -``` - -After this, all `kubectl` commands will use the namespace `face`. - -### Deploying the Application - -Overview of Pods and Services: - -![kube deployed](https://skarlso.github.io/img/kube_deployed.png) - -### MySQL - -The first Service I’m going to deploy is my database. - -I’m using the Kubernetes example located here [Kube MySQL][25] which fits my needs. Note that this file is using a plain password for MYSQL_PASSWORD. I’m going to employ a vault described here [Kubernetes Secrets][26]. - -I’ve created a secret locally as described in that document using a secret yaml: - -``` -apiVersion: v1 -kind: Secret -metadata: - name: kube-face-secret -type: Opaque -data: - mysql_password: base64codehere - -``` - -The base64 code I created with the following commands: - -``` -echo -n "ubersecurepassword" | base64 - -``` - -And this is what you’ll see in my deployment yaml file: - -``` -... -- name: MYSQL_ROOT_PASSWORD - valueFrom: - secretKeyRef: - name: kube-face-secret - key: mysql_password -... - -``` - -One other thing worth mentioning. It’s using a volume to persist the database. The volume definition is as follows: - -``` -... - volumeMounts: - - name: mysql-persistent-storage - mountPath: /var/lib/mysql -... - volumes: - - name: mysql-persistent-storage - persistentVolumeClaim: - claimName: mysql-pv-claim -... - -``` - -`presistentVolumeClain` is the key here. This tells Kubernetes that this resource requires a persistent volume. How it’s provided is abstracted away from the user. You can be sure that Kubernetes will provide a volume that will always be there. Similar to Pods. To read up on the details check out this document: [Kubernetes Persistent Volumes][27]. - -Deploying the mysql Service is done with the following command: - -``` -kubectl apply -f mysql.yaml - -``` - -`apply` vs `create`. In short, `apply` is considered a declerative object configuration command while `create` is imperative. What that means for now is that create is usually for a one of task, like running something or creating a deployment. While, when using apply the user doesn’t define the action to be taken. That will be defined by Kubernetes based on the current status of the cluster. Thus, when there is no service called `mysql` and I’m calling `apply -f mysql.yaml` it will create the service. When running again, Kubernetes won’t do anything. But if I would run `create` again it would throw an error saying the service is already created. - -For more information checkout the following docs: [Kubernetes Object Management][28], [Imperative Configuration][29], [Declarative Configuration][30]. - -To see progress information, run: - -``` -# Describes the whole process -kubectl describe deployment mysql -# Shows only the pod -kubectl get pods -l app=mysql - -``` - -Output should be similar to this: - -``` -... - Type Status Reason - ---- ------ ------ - Available True MinimumReplicasAvailable - Progressing True NewReplicaSetAvailable -OldReplicaSets: -NewReplicaSet: mysql-55cd6b9f47 (1/1 replicas created) -... - -``` - -Or in case of `get pods`: - -``` -NAME READY STATUS RESTARTS AGE -mysql-78dbbd9c49-k6sdv 1/1 Running 0 18s - -``` - -To test the instance, run the following snippet: - -``` -kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -pyourpasswordhere - -``` - -GOTCHA: If you change the password now, it’s not enough to re-apply your yaml file to update the container. Since the DB is persisted, the password will not be changed. You have to delete the whole deployment with `kubectl delete -f mysql.yaml`. - -You should see the following when running a `show databases`. - -``` -If you don't see a command prompt, try pressing enter. -mysql> -mysql> -mysql> show databases; -+--------------------+ -| Database | -+--------------------+ -| information_schema | -| kube | -| mysql | -| performance_schema | -+--------------------+ -4 rows in set (0.00 sec) - -mysql> exit -Bye - -``` - -You’ll notice that I also mounted a file located here [Database Setup SQL][31] into the container. MySQL container automatically executed these. That file will bootstrap some data and the schema I’m going to use. - -The volume definition is as follows: - -``` - volumeMounts: - - name: mysql-persistent-storage - mountPath: /var/lib/mysql - - name: bootstrap-script - mountPath: /docker-entrypoint-initdb.d/database_setup.sql -volumes: -- name: mysql-persistent-storage - persistentVolumeClaim: - claimName: mysql-pv-claim -- name: bootstrap-script - hostPath: - path: /Users/hannibal/golang/src/github.com/Skarlso/kube-cluster-sample/database_setup.sql - type: File - -``` - -To check if the bootstrap script was successful run this: - -``` -~/golang/src/github.com/Skarlso/kube-cluster-sample/kube_files master* -❯ kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -uroot -pyourpasswordhere kube -If you don't see a command prompt, try pressing enter. - -mysql> show tables; -+----------------+ -| Tables_in_kube | -+----------------+ -| images | -| person | -| person_images | -+----------------+ -3 rows in set (0.00 sec) - -mysql> - -``` - -This concludes the database service setup. Logs for this service can be viewed with the following command: - -``` -kubectl logs deployment/mysql -f - -``` - -### NSQ Lookup - -The NSQ Lookup will run as an internal service. It doesn’t need access from the outside so I’m setting `clusterIP: None` which will tell Kubernetes that this service is a headless service. This means that it won’t be loadbalanced and it won’t be a single ip service. The DNS will be based upon service selectors. - -Our NSQ Lookup selector is: - -``` - selector: - matchLabels: - app: nsqlookup - -``` - -Thus, the internal DNS will look like this: `nsqlookup.default.svc.cluster.local`. - -Headless services are described in detail here: [Headless Service][32]. - -Basically it’s the same as MySQL just with slight modifications. As stated earlier, I’m using NSQ’s own Docker Image called `nsqio/nsq`. All nsq commands are there, so nsqd will also use this image just with a different command. For nsqlookupd the command is as follows: - -``` -command: ["/nsqlookupd"] -args: ["--broadcast-address=nsqlookup.default.svc.cluster.local"] - -``` - -What’s the `--broadcast-address` for, you might ask? By default, nsqlookup will use the `hostname` as broadcast address. Meaning, when the consumer runs a callback it will try to connect to something like `http://nsqlookup-234kf-asdf:4161/lookup?topics=image` which will not work of course. By setting the broadcast-address to the internal DNS that callback will be `http://nsqlookup.default.svc.cluster.local:4161/lookup?topic=images`. Which will work as expected. - -NSQ Lookup also requires two ports forwarded. One for broadcasting and one for nsqd daemon callback. These are exposed in the Dockerfile and then utilized in the kubernetes template like this: - -In the container template: - -``` - ports: - - containerPort: 4160 - hostPort: 4160 - - containerPort: 4161 - hostPort: 4161 - -``` - -In the service template: - -``` -spec: - ports: - - name: tcp - protocol: TCP - port: 4160 - targetPort: 4160 - - name: http - protocol: TCP - port: 4161 - targetPort: 4161 - -``` - -Names are required by kubernetes to distinguish between them. - -To create this service, I’m using the following command as before: - -``` -kubectl apply -f nsqlookup.yaml - -``` - -This concludes nsqlookupd. Two of the major players are in the sack. - -### Receiver - -This is a more complex one. The receiver will do three things. - -* It will create some deployments - -* It will create the nsq daemon - -* It will be public facing - -#### Deployments - -The first deployment it creates is it’s own. The receiver container is `skarlso/kube-receiver-alpine`. - -#### Nsq Daemon - -The receiver starts an nsq daemon. Like said earlier, the receiver runs an nsq with it-self. It does that so talking to it can happen locally and not over the network. By making receiver do this, it will end up on the same node as the receiver. - -NSQ daemon also needs some adjustments and parameters. - -``` - ports: - - containerPort: 4150 - hostPort: 4150 - - containerPort: 4151 - hostPort: 4151 - env: - - name: NSQLOOKUP_ADDRESS - value: nsqlookup.default.svc.cluster.local - - name: NSQ_BROADCAST_ADDRESS - value: nsqd.default.svc.cluster.local - command: ["/nsqd"] - args: ["--lookupd-tcp-address=$(NSQLOOKUP_ADDRESS):4160", "--broadcast-address=$(NSQ_BROADCAST_ADDRESS)"] - -``` - -You can see the lookup-tcp-address and the broadcast-address are set. Lookup tcp address is the DNS for the nsqlookupd service. And the broadcast address is necessary just like with nsqlookupd so the callbacks are working properly. - -#### Public facing - -Now, this is the first time I’m deploying a public facing service. There are two options. I could use a LoadBalancer because this API will be under heavy load. And if this would be deployed anywhere in production, then it should be a LoadBalancer. - -I’m doing this locally though with one node so something called a `NodePort` is enough. A `NodePort` exposes a service on each node’s IP at a static port. If not specified, it will assign a random port on the host between 30000-32767\. But it can also be configured to be a specific port, using `nodePort` in the yaml. To reach this service I will have to use `:`. If more than one node is configured a LoadBalancer can multiplex them to a single IP. - -For further information check out this document: [Publishing Services][33]. - -Putting this all together, we’ll get a receiver-service for which the template is as follows: - -``` -apiVersion: v1 -kind: Service -metadata: - name: receiver-service -spec: - ports: - - protocol: TCP - port: 8000 - targetPort: 8000 - selector: - app: receiver - type: NodePort - -``` - -For a fixed nodePort on 8000 a definition of `nodePort` must be provided as follows: - -``` -apiVersion: v1 -kind: Service -metadata: - name: receiver-service -spec: - ports: - - protocol: TCP - port: 8000 - targetPort: 8000 - selector: - app: receiver - type: NodePort - nodePort: 8000 - -``` - -### Image processor - -The Image Processor is where I’m handling passing off images to be identified. It should have access to nsqlookupd, mysql and the gRPC endpoint of the face recognition service deployed later. This is actually a boring service. In fact, it’s not a service at all. It doesn’t expose anything and thus it’s the first deployment only component. For brevity, here is the whole template: - -``` ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: image-processor-deployment -spec: - selector: - matchLabels: - app: image-processor - replicas: 1 - template: - metadata: - labels: - app: image-processor - spec: - containers: - - name: image-processor - image: skarlso/kube-processor-alpine:latest - env: - - name: MYSQL_CONNECTION - value: "mysql.default.svc.cluster.local" - - name: MYSQL_USERPASSWORD - valueFrom: - secretKeyRef: - name: kube-face-secret - key: mysql_userpassword - - name: MYSQL_PORT - # TIL: If this is 3306 without " kubectl throws an error. - value: "3306" - - name: MYSQL_DBNAME - value: kube - - name: NSQ_LOOKUP_ADDRESS - value: "nsqlookup.default.svc.cluster.local:4161" - - name: GRPC_ADDRESS - value: "face-recog.default.svc.cluster.local:50051" - -``` - -The only interesting points in this file are the multitude of environment properties that are used to configure the application. Note the nsqlookupd address and the grpc address. - -To create this deployment, run: - -``` -kubectl apply -f image_processor.yaml - -``` - -### Face - Recognition - -The face recognition service does have a service. It’s a simple one, only needed by image-processor. It’s template is as follows: - -``` -apiVersion: v1 -kind: Service -metadata: - name: face-recog -spec: - ports: - - protocol: TCP - port: 50051 - targetPort: 50051 - selector: - app: face-recog - clusterIP: None - -``` - -The more interesting part is that it requires two volumes. The two volumes are `known_people` and `unknown_people`. Can you guess what they will contain? Yep, images. The `known_people` volume contains all the images associated to the known people in the database. The `unknown_people` volume will contain all the new images. And that’s the path we will need to use when sending images from the receiver. That is, where ever the mount points to. Which in my case is `/unknown_people`. Basically the path needs to be one that the face recognition service can access. - -Now, with Kubernetes and Docker this is easy. It could be a mounted S3 or some kind of nfs or a local mount from host to guest. The possibilities are endless (around a dozen or so). I’m going to use a local mount for the sake of simplicity. - -Mounting a volume has two parts. First, the Dockerfile has to specify volumes: - -``` -VOLUME [ "/unknown_people", "/known_people" ] - -``` - -Second, the Kubernetes template as seen earlier with MySQL; the difference being `hostPath` instead of a claimed volume: - -``` - volumeMounts: - - name: known-people-storage - mountPath: /known_people - - name: unknown-people-storage - mountPath: /unknown_people - volumes: - - name: known-people-storage - hostPath: - path: /Users/hannibal/Temp/known_people - type: Directory - - name: unknown-people-storage - hostPath: - path: /Users/hannibal/Temp/ - type: Directory - -``` - -We also have to set the `known_people` folder config setting for face recognition. This is done via an environment property of course: - -``` - env: - - name: KNOWN_PEOPLE - value: "/known_people" - -``` - -Then the Python code will look up images like this: - -``` - known_people = os.getenv('KNOWN_PEOPLE', 'known_people') - print("Known people images location is: %s" % known_people) - images = self.image_files_in_folder(known_people) - -``` - -Where `image_files_in_folder` is: - -``` - def image_files_in_folder(self, folder): - return [os.path.join(folder, f) for f in os.listdir(folder) if re.match(r'.*\.(jpg|jpeg|png)', f, flags=re.I)] - -``` - -Neat. - -Now, if the receiver receives a request (and sends it off further the line) similar to the one below… - -``` -curl -d '{"path":"/unknown_people/unknown220.jpg"}' http://192.168.99.100:30251/image/post - -``` - -…it will look for an image called unknown220.jpg under `/unknown_people`; locate an image in the known_folder that corresponds to the person on the unknown image and return the name of the image that matched. - -Looking at logs you should see something like this: - -``` -# Receiver -❯ curl -d '{"path":"/unknown_people/unknown219.jpg"}' http://192.168.99.100:30251/image/post -got path: {Path:/unknown_people/unknown219.jpg} -image saved with id: 4 -image sent to nsq - -# Image Processor -2018/03/26 18:11:21 INF 1 [images/ch] querying nsqlookupd http://nsqlookup.default.svc.cluster.local:4161/lookup?topic=images -2018/03/26 18:11:59 Got a message: 4 -2018/03/26 18:11:59 Processing image id: 4 -2018/03/26 18:12:00 got person: Hannibal -2018/03/26 18:12:00 updating record with person id -2018/03/26 18:12:00 done - -``` - -And that concludes all of the services that we need to deploy with Kubernetes to get this application to work. - -### Frontend - -Last but not least, there is a small web-app which displays the information in the db for convenience. This is also a public facing service with the same parameters as the receiver’s service. - -It looks like this: - -![frontend](https://skarlso.github.io/img/kube-frontend.png) - -### Recap - -So what is the situation so far? I deployed a bunch of services all over the place. A recap off the commands I used: - -``` -kubectl apply -f mysql.yaml -kubectl apply -f nsqlookup.yaml -kubectl apply -f receiver.yaml -kubectl apply -f image_processor.yaml -kubectl apply -f face_recognition.yaml -kubectl apply -f frontend.yaml - -``` - -These could be in any order because the application does not allocate connections on start except for image_processor’s NSQ consumer. But that re-tries. - -Query-ing kube for running pods with `kubectl get pods` should show something like this: - -``` -❯ kubectl get pods -NAME READY STATUS RESTARTS AGE -face-recog-6bf449c6f-qg5tr 1/1 Running 0 1m -image-processor-deployment-6467468c9d-cvx6m 1/1 Running 0 31s -mysql-7d667c75f4-bwghw 1/1 Running 0 36s -nsqd-584954c44c-299dz 1/1 Running 0 26s -nsqlookup-7f5bdfcb87-jkdl7 1/1 Running 0 11s -receiver-deployment-5cb4797598-sf5ds 1/1 Running 0 26s - -``` - -Running `minikube service list`: - -``` -❯ minikube service list -|-------------|----------------------|-----------------------------| -| NAMESPACE | NAME | URL | -|-------------|----------------------|-----------------------------| -| default | face-recog | No node port | -| default | kubernetes | No node port | -| default | mysql | No node port | -| default | nsqd | No node port | -| default | nsqlookup | No node port | -| default | receiver-service | http://192.168.99.100:30251 | -| kube-system | kube-dns | No node port | -| kube-system | kubernetes-dashboard | http://192.168.99.100:30000 | -|-------------|----------------------|-----------------------------| - -``` - -### Rolling update - -What happens during a rolling update? - -![kube rotate](https://skarlso.github.io/img/kube_rotate.png) - -As it happens during software development, change is requested/needed to some parts of the application. What happens to our cluster if I would like to change one of it’s components without breaking the other? And also whilest maintaining backwards compatibility with no disruption to user experience. Thankfully Kubernetes can help with that. - -What I don’t like is that the API only handles one image at a time. There is no option to bulk upload. - -#### Code - -Right now, we have the following code segment dealing with a single image: - -``` -// PostImage handles a post of an image. Saves it to the database -// and sends it to NSQ for further processing. -func PostImage(w http.ResponseWriter, r *http.Request) { -... -} - -func main() { - router := mux.NewRouter() - router.HandleFunc("/image/post", PostImage).Methods("POST") - log.Fatal(http.ListenAndServe(":8000", router)) -} - -``` - -We have two options. Add a new endpoint with `/images/post` and make the client use that, or modify the existing one. - -The new client code has the advantage that it could fall back to submitting the old way if the new endpoint isn’t available. The old client code though doesn’t have this advantage so we can’t change the way our code works right now. Consider this. You have 90 servers. You do a slow paced rolling update. That will take out servers one step at a time doing an update. If an update lasts around a minute, that will take around one and a half hours to complete (not counting any parallel updates). - -During that time, some of your servers will run the new code and some will run the old one. Calls are load balanced, thus you have no control over what server is hit. If a client is trying to do a call the new way but hits an old server the client would fail. The client could try a fallback, but since you eliminated the old version it will not succeed unless it, by chance, hits a server with the new code (assuming no sticky sessions are set). - -Also, once all your servers are updated, an old client will not be able to use your service any longer at all. - -Now, you could argue that you don’t want to keep around old versions of your code forever. And that is true in some sense. That’s why, what we are going to do, is modify the old code, to simply call the new code with some slight augmentations. This way, old code is not kept around. Once all clients have been migrated, the code can simply be deleted without any problems. - -#### New Endpoint - -Let’s add a new route method: - -``` -... -router.HandleFunc("/images/post", PostImages).Methods("POST") -... - -``` - -And updating the old one to call the new one with a modified body like this: - -``` -// PostImage handles a post of an image. Saves it to the database -// and sends it to NSQ for further processing. -func PostImage(w http.ResponseWriter, r *http.Request) { - var p Path - err := json.NewDecoder(r.Body).Decode(&p) - if err != nil { - fmt.Fprintf(w, "got error while decoding body: %s", err) - return - } - fmt.Fprintf(w, "got path: %+v\n", p) - var ps Paths - paths := make([]Path, 0) - paths = append(paths, p) - ps.Paths = paths - var pathsJSON bytes.Buffer - err = json.NewEncoder(&pathsJSON).Encode(ps) - if err != nil { - fmt.Fprintf(w, "failed to encode paths: %s", err) - return - } - r.Body = ioutil.NopCloser(&pathsJSON) - r.ContentLength = int64(pathsJSON.Len()) - PostImages(w, r) -} - -``` - -Well, the naming could be better, but you should get the basic idea. I’m modifying the incoming single path by wrapping it into the new format and sending it over to the new end-point handler. And that’s it. There are a few more modifications, to check them out take a look at this PR: [Rolling Update Bulk Image Path PR][34]. - -Now, we can call the receiver in two ways: - -``` -# Single Path: -curl -d '{"path":"unknown4456.jpg"}' http://127.0.0.1:8000/image/post - -# Multiple Paths: -curl -d '{"paths":[{"path":"unknown4456.jpg"}]}' http://127.0.0.1:8000/images/post - -``` - -Here, the client is curl. Normally, if the client would be a service, I would modify it that in case the new end-point throws a 404 it would try the old one next. - -For brevity, I’m not modifying NSQ and the others to handle bulk image processing. They will still receive it one - by - one. I’ll leave that up to you as homework. ;) - -#### New Image - -To perform a rolling update, I must create a new image first from the receiver service. To do this, I’ll create a new image with a new tag, denoting a version v1.1. - -``` -docker build -t skarlso/kube-receiver-alpine:v1.1 . - -``` - -Once this is complete, we can begin rolling out the change. - -#### Rolling update - -In Kubernetes, you can configure your rolling update in multiple ways. - -##### Manual Update - -If, say, I was using a container version in my config file called `v1.0` than doing an update is simply calling: - -``` -kubectl rolling-update receiver --image:skarlso/kube-receiver-alpine:v1.1 - -``` - -If there is a problem during the rollout we can always rollback. - -``` -kubectl rolling-update receiver --rollback - -``` - -It will set back the previous version no fuss, no muss. - -##### Apply a new configuration file - -The problem with by-hand updates is always that they aren’t in source control. - -Consider this. Something changed, a couple of servers got updated, but nobody witnessed it. A new person comes along and does a change to the template and applys the template to the cluster. All the servers are updated, but suddenly, there is a service outage. - -Long story sort, the servers which got updated are wacked over because the template didn’t reflect what has been done by hand. That is bad. Don’t do that. - -The recommended way is to change the template to use the new version and than apply the template with the `apply` command. - -Kubernetes recommends that the Deployment handles the rollout with ReplicaSets. This means however, that there must be at least two replicates present for a rolling update. Otherwise the update won’t work (unless `maxUnavailable` is set to 1). I’m increasing the replica count in the yaml and I set the new image version for the receiver container. - -``` - replicas: 2 -... - spec: - containers: - - name: receiver - image: skarlso/kube-receiver-alpine:v1.1 -... - -``` - -Looking at the progress you should see something like this: - -``` -❯ kubectl rollout status deployment/receiver-deployment -Waiting for rollout to finish: 1 out of 2 new replicas have been updated... - -``` - -You can add in additional rollout configuration settings by specifying the `strategy` part of the template like this: - -``` - strategy: - type: RollingUpdate - rollingUpdate: - maxSurge: 1 - maxUnavailable: 0 - -``` - -Additional information on rolling update can be found in these documents: [Deployment Rolling Update][35], [Updating a Deployment][36], [Manage Deployments][37], [Rolling Update using ReplicaController][38]. - -NOTE MINIKUBE USERS: Since we are doing this on a local machine with one node and 1 replica of an application, we have to set `maxUnavailable` to `1`. Otherwise, Kubernetes won’t allow the update to happen and the new version will always be in `Pending` state since we aren’t allowing that at any given point in time there is a situation where no containers are present for `receiver` app. - -### Scaling - -Scaling is dead easy with Kubernetes. Since it’s managing the whole cluster, you basically, just have to put a number into the template of the desired replicas to use. - -This has been a great post so far but it’s getting too long. I’m planning on writing a follow-up where I will be truly scaling things up on AWS with multiple nodes and replicas. Stay tuned. - -### Cleanup - -``` -kubectl delete deployments --all -kubectl delete services -all - -``` - -# Final Words - -And that is it ladies and gentleman. We wrote, deployed, updated and scaled (well, not yet really) a distributed application with Kubernetes. - -Any questions, please feel free to chat in the comments below, I’m happy to answer. - -I hope you enjoyed reading this. I know, it’s quiet long and I was thinking of splitting it up, but having a cohesive, one page guide is sometimes useful and makes it easy to find something or save it for later read or even print as PDF. - -Thank you for reading, Gergely. - --------------------------------------------------------------------------------- - -via: https://skarlso.github.io/2018/03/15/kubernetes-distributed-application/ - -作者:[hannibal ][a] -译者:[译者ID](https://github.com/译者ID) -校对:[校对者ID](https://github.com/校对者ID) - -本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 - -[a]:https://github.com/Skarlso -[1]:https://skarlso.github.io/2018/03/15/kubernetes-distributed-application/#circuit-breaker -[2]:https://skarlso.github.io//categories/go -[3]:https://skarlso.github.io//categories/kubernetes -[4]:https://skarlso.github.io//categories/facerecognition -[5]:https://kubernetes.io/ -[6]:https://github.com/Skarlso/kube-cluster-sample -[7]:https://github.com/Skarlso/kube-cluster-sample/tree/master/receiver -[8]:http://nsq.io/ -[9]:https://github.com/Skarlso/kube-cluster-sample/tree/master/image_processor -[10]:https://github.com/Skarlso/kube-cluster-sample/tree/master/face_recognition -[11]:https://grpc.io/ -[12]:https://golang.org/pkg/sync/#Cond -[13]:https://github.com/ageitgey/face_recognition -[14]:https://gocv.io/ -[15]:http://shop.oreilly.com/product/0636920043874.do -[16]:https://kubernetes.io/docs/ -[17]:https://kubernetes.io/docs/concepts/overview/components/ -[18]:https://kubernetes.io/docs/concepts/services-networking/service/ -[19]:https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/ -[20]:https://kubernetes.io/docs/tutorials/kubernetes-basics/scale-interactive/ -[21]:https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/ -[22]:https://github.com/kubernetes/kubernetes -[23]:https://github.com/kubernetes/minikube/ -[24]:https://github.com/Skarlso/kube-cluster-sample/tree/master/kube_files -[25]:https://kubernetes.io/docs/tasks/run-application/run-single-instance-stateful-application/#deploy-mysql -[26]:https://kubernetes.io/docs/concepts/configuration/secret/ -[27]:https://kubernetes.io/docs/concepts/storage/persistent-volumes -[28]:https://kubernetes.io/docs/concepts/overview/object-management-kubectl/overview/ -[29]:https://kubernetes.io/docs/concepts/overview/object-management-kubectl/imperative-config/ -[30]:https://kubernetes.io/docs/concepts/overview/object-management-kubectl/declarative-config/ -[31]:https://github.com/Skarlso/kube-cluster-sample/blob/master/database_setup.sql -[32]:https://kubernetes.io/docs/concepts/services-networking/service/#headless-services -[33]:https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types -[34]:https://github.com/Skarlso/kube-cluster-sample/pull/1 -[35]:https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#rolling-back-a-deployment -[36]:https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#updating-a-deployment -[37]:https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#updating-your-application-without-a-service-outage -[38]:https://kubernetes.io/docs/tasks/run-application/rolling-update-replication-controller/ -[39]:https://skarlso.github.io/2018/03/15/kubernetes-distributed-application/ diff --git a/translated/tech/20180315 Kubernetes distributed application deployment with sample Face Recognition App.md b/translated/tech/20180315 Kubernetes distributed application deployment with sample Face Recognition App.md new file mode 100644 index 0000000000..a05307301a --- /dev/null +++ b/translated/tech/20180315 Kubernetes distributed application deployment with sample Face Recognition App.md @@ -0,0 +1,1113 @@ +Kubernetes 分布式应用部署实战 -- 以人脸识别应用为例 +============================================================ + +# 简介 + +伙计们,请做好准备,下面将是一段漫长的旅程,期望你能够乐在其中。 + +我将基于 [Kubernetes][5] 部署一个分布式应用。我曾试图编写一个尽可能真实的应用,但由于时间和精力有限,最终砍掉了很多细节。 + +我将聚焦 Kubernetes 及其部署。 + +让我们开始吧。 + +# 应用 + +### TL;DR + +![kube overview](https://skarlso.github.io/img/kube_overview.png) + +应用本身由 6 个组件构成。代码可以从如下链接中找到:[Kubenetes 集群示例][6]。 + +这是一个人脸识别服务,通过比较已知个人的图片,识别给定图片对应的个人。前端页面用表格形式简要的展示图片及对应的个人。具体而言,向 [接收器][6] 发送请求,请求包含指向一个图片的链接。图片可以位于任何位置。接受器将图片地址存储到数据库 (MySQL) 中,然后向队列发送处理请求,请求中包含已保存图片的 ID。这里我们使用 [NSQ][8] 建立队列。 + +[图片处理][9]服务一直监听处理请求队列,从中获取任务。处理过程包括如下几步:获取图片 ID,读取图片,通过 [gRPC][11] 将图片路径发送至 Python 编写的[人脸识别][10]后端。如果识别成功,后端给出图片对应个人的名字。图片处理器进而根据个人 ID 更新图片记录,将其标记为处理成功。如果识别不成功,图片被标记为待解决。如果图片识别过程中出现错误,图片被标记为失败。 + +标记为失败的图片可以通过计划任务等方式进行重试。 + +那么具体是如何工作的呢?我们深入探索一下。 + +### 接收器 + +接收器服务是整个流程的起点,通过如下形式的 API 接收请求: + +``` +curl -d '{"path":"/unknown_images/unknown0001.jpg"}' http://127.0.0.1:8000/image/post + +``` + +此时,接收器将路径path存储到共享数据库集群中,对应的条目包括数据库服务提供的 ID。本应用采用”持久层提供条目对象唯一标识“的模型。获得条目 ID 后,接收器向 NSQ 发送消息,至此接收器的工作完成。 + +### 图片处理器 + +从这里开始变得有趣起来。图片处理器首次运行时会创建两个 Go routines,具体为: + +### Consume + +这是一个 NSQ 消费者,需要完成三项任务。首先,监听队列中的消息。其次,当有新消息到达时,将对应的 ID 追加到一个线程安全的 ID 片段中,以供第二个 routine 处理。最后,告知第二个 routine 处理新任务,方法为 [sync.Condition][12]。 + +### ProcessImages + +该 routine 会处理指定 ID 片段,直到对应片段全部处理完成。当处理完一个片段后,该 routine 并不是在一个通道上睡眠等待,而是进入悬挂状态。对每个 ID,按如下步骤顺序处理: + +* 与人脸识别服务建立 gRPC 连接,其中人脸识别服务会在人脸识别部分进行介绍 + +* 从数据库获取图片对应的条目 + +* 为 [断路器][1] 准备两个函数 + * 函数 1: 用于 RPC 方法调用的主函数 + + * 函数 2: 基于 ping 的断路器健康检查 + +* 调用函数 1 将图片路径发送至人脸识别服务,其中路径应该是人脸识别服务可以访问的,最好是共享的,例如 NFS + +* 如果调用失败,将图片条目状态更新为 FAILEDPROCESSING + +* 如果调用成功,返回值是一个图片的名字,对应数据库中的一个个人。通过联合 SQL 查询,获取对应个人的 ID + +* 将数据库中的图片条目状态更新为 PROCESSED,更新图片被识别成的个人的 ID + +这个服务可以复制多份同时运行。 + +### 断路器 + +即使对于一个复制资源几乎没有开销的系统,也会有意外的情况发生,例如网络故障或任何两个服务之间的通信存在问题等。我在 gRPC 调用中实现了一个简单的断路器,这十分有趣。 + +下面给出工作原理: + +![kube circuit](https://skarlso.github.io/img/kube_circuit1.png) + +当出现 5 次不成功的服务调用时,断路器启动并阻断后续的调用请求。经过指定的时间后,它对服务进行健康检查并判断是否恢复。如果问题依然存在,等待时间会进一步增大。如果已经恢复,断路器停止对服务调用的阻断,允许请求流量通过。 + +### 前端 + +前端只包含一个极其简单的表格视图,通过 Go 自身的 html/模板显示一系列图片。 + +### 人脸识别 + +人脸识别是整个识别的关键点。仅因为追求灵活性,我将这个服务设计为基于 gRPC 的服务。最初我使用 Go 编写,但后续发现基于 Python 的实现更加适合。事实上,不算 gRPC 部分的代码,人脸识别部分仅有 7 行代码。我使用的[人脸识别][13]库极为出色,它包含 OpenCV 的全部 C 绑定。维护 API 标准意味着只要标准本身不变,实现可以任意改变。 + +注意:我曾经试图使用 [GoCV][14],这是一个极好的 Go 库,但欠缺所需的 C 绑定。推荐马上了解一下这个库,它会让你大吃一惊,例如编写若干行代码即可实现实时摄像处理。 + +这个 Python 库的工作方式本质上很简单。准备一些你认识的人的图片,把信息记录下来。对于我而言,我有一个图片文件夹,包含若干图片,名称分别为 `hannibal_1.jpg, hannibal_2.jpg, gergely_1.jpg, john_doe.jpg`。在数据库中,我使用两个表记录信息,分别为 `person, person_images`,具体如下: + +``` ++----+----------+ +| id | name | ++----+----------+ +| 1 | Gergely | +| 2 | John Doe | +| 3 | Hannibal | ++----+----------+ ++----+----------------+-----------+ +| id | image_name | person_id | ++----+----------------+-----------+ +| 1 | hannibal_1.jpg | 3 | +| 2 | hannibal_2.jpg | 3 | ++----+----------------+-----------+ + +``` +人脸识别库识别出未知图片后,返回图片的名字。我们接着使用类似下面的联合查询找到对应的个人。 + +``` +select person.name, person.id from person inner join person_images as pi on person.id = pi.person_id where image_name = 'hannibal_2.jpg'; + +``` + +gRPC 调用返回的个人 ID 用于更新图片的 `person` 列。 + +### NSQ + +NSQ 是 Go 编写的小规模队列,可扩展且占用系统内存较少。NSQ 包含一个查询服务,用于消费者接收消息;包含一个守护进程,用于发送消息。 + +在 NSQ 的设计理念中,消息发送程序应该与守护进程在同一台主机上,故发送程序仅需发送至 localhost。但守护进程与查询服务相连接,这使其构成了全局队列。 + +这意味着有多少 NSQ 守护进程就有多少对应的发送程序。但由于其资源消耗极小,不会影响主程序的资源使用。 + +### 配置 + +为了尽可能增加灵活性以及使用 Kubernetes 的 ConfigSet 特性,我在开发过程中使用 .env 文件记录配置信息,例如数据库服务的地址以及 NSQ 的查询地址。在生产环境或 Kubernetes 环境中,我将使用环境变量属性配置。 + +### 应用小结 + +这就是待部署应用的全部架构信息。应用的各个组件都是可变更的,他们之间仅通过数据库、消息队列和 gRPC 进行耦合。考虑到更新机制的原理,这是部署分布式应用所必须的;在部署部分我会继续分析。 + +# 使用 Kubernetes 部署应用 + +### 基础知识 + +Kubernetes 是什么? + +这里我会提到一些基础知识,但不会深入细节,细节可以用一本书的篇幅描述,例如 [Kubernetes 构建与运行][15]。另外,如果你愿意挑战自己,可以查看官方文档:[Kubernetes 文档][16]。 + +Kubernetes 是容器化服务及应用的管理器。它易于扩展,可以管理大量容器;更重要的是,可以通过基于 yaml 的模板文件高度灵活地进行配置。人们经常把 Kubernetes 比作 Docker Swarm,但 Kubernetes 的功能不仅仅如此。例如,Kubernetes 不关心底层容器实现,你可以使用 LXC 与 Kubernetes 的组合,效果与使用 Docker 一样好。Kubernetes 在管理容器的基础上,可以管理已部署的服务或应用集群。如何操作呢?让我们概览一下用于构成 Kubernetes 的模块。 + +在 Kubernetes 中,你给出期望的应用状态,Kubernetes 会尽其所能达到对应的状态。状态可以是已部署、已暂停,有 2 个副本等,以此类推。 + +Kubernetes 使用标签和注释标记组件,包括服务,部署,副本组,守护进程组等在内的全部组件都被标记。考虑如下场景,为了识别 pod 与 应用的对应关系,使用 `app: myapp` 标签。假设应用已部署 2 个容器,如果你移除其中一个容器的 `app` 标签,Kubernetes 只能识别到一个容器(隶属于应用),进而启动一个新的具有 `myapp` 标签的实例。 + +### Kubernetes 集群 + +要使用 Kubernetes,需要先搭建一个 Kubernetes 集群。搭建 Kubernetes 集群可能是一个痛苦的经历,但所幸有工具可以帮助我们。Minikube 为我们在本地搭建一个单节点集群。AWS 的一个 beta 服务工作方式类似于 Kubernetes 集群,你只需请求 Nodes 并定义你的部署即可。Kubernetes 集群组件的文档如下:[Kubernetes 集群组件][17]。 + +### 节点 (Nodes) + +节点是工作单位,形式可以是虚拟机、物理机,也可以是各种类型的云主机。 + +### Pods + +Pods 是本地容器组成的集合,即一个 Pod 中可能包含若干个容器。Pod 创建后具有自己的 DNS 和 虚拟 IP,这样 Kubernetes 可以对到达流量进行负载均衡。你几乎不需要直接和容器打交道;即使是调试的时候,例如查看日志,你通常调用 `kubectl logs deployment/your-app -f` 查看部署日志,而不是使用 `-c container_name` 查看具体某个容器的日志。`-f` 参数表示从日志尾部进行流式输出。 + +### 部署 (Deployments) + +在 Kubernetes 中创建任何类型的资源时,后台使用一个部署,它指定了资源的期望状态。使用部署对象,你可以将 Pod 或服务变更为另外的状态,也可以更新应用或上线新版本应用。你一般不会直接操作副本组 (后续会描述),而是通过部署对象创建并管理。 + +### 服务 (Services) + +默认情况下,Pod 会获取一个 IP 地址。但考虑到 Pod 是 Kubernetes 中的易失性组件,我们需要更加持久的组件。不论是队列,mysql,内部 API 或前端,都需要长期运行并使用保持不变的 IP 或 更佳的 DNS 记录。 + +为解决这个问题,Kubernetes 提供了服务组件,可以定义访问模式,支持的模式包括负载均衡,简单 IP 或 内部 DNS。 + +Kubernetes 如何获知服务运行正常呢?你可以配置健康性检查和可用性检查。健康性检查是指检查容器是否处于运行状态,但容器处于运行状态并不意味着服务运行正常。对此,你应该使用可用性检查,即请求应用的一个特别接口endpoint。 + +由于服务非常重要,推荐你找时间阅读以下文档:[服务][18]。严肃的说,需要阅读的东西很多,有 24 页 A4 纸的篇幅,涉及网络,服务及自动发现。这也有助于你决定是否真的打算在生产环境中使用 Kubernetes。 + +### DNS / 服务发现 + +在 Kubernetes 集群中创建服务后,该服务会从名为 kube-proxy 和 kube-dns 的特殊 Kubernetes 部署中获取一个 DNS 记录。他们两个用于提供集群内的服务发现。如果你有一个正在运行的 mysql 服务并配置 `clusterIP: no`,那么集群内部任何人都可以通过 `mysql.default.svc.cluster.local` 访问该服务,其中: + +* `mysql` – 服务的名称 + +* `default` – 命名空间的名称 + +* `svc` – 对应服务分类 + +* `cluster.local` – 本地集群的域名 + +可以使用自定义设置更改本地集群的域名。如果想让服务可以从集群外访问,需要使用 DNS 提供程序并使用例如 Nginx 将 IP 地址绑定至记录。服务对应的对外 IP 地址可以使用如下命令查询: + +* 节点端口方式 – `kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services mysql` + +* 负载均衡方式 – `kubectl get -o jsonpath="{.spec.ports[0].LoadBalancer}" services mysql` + +### 模板文件 + +类似 Docker Compose, TerraForm 或其它的服务管理工具,Kubernetes 也提供了基础设施描述模板。这意味着,你几乎不用手动操作。 + +以 Nginx 部署为例,查看下面的 yaml 模板: + +``` +apiVersion: apps/v1 +kind: Deployment #(1) +metadata: #(2) + name: nginx-deployment + labels: #(3) + app: nginx +spec: #(4) + replicas: 3 #(5) + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: #(6) + - name: nginx + image: nginx:1.7.9 + ports: + - containerPort: 80 + +``` + +在这个示例部署中,我们做了如下操作: + +* (1) 使用 kind 关键字定义模板类型 +* (2) 使用 metadata 关键字,增加该部署的识别信息,使用 labels 标记每个需要创建的资源 (3) +* (4) 然后使用 spec 关键字描述所需的状态 + * (5) nginx 应用需要 3 个副本 + * (6) Pod 中容器的模板定义部分 + * 容器名称为 nginx + * 容器模板为 nginx:1.7.9 (本例使用 Docker 镜像) + +### 副本组 (ReplicaSet) + +副本组是一个底层的副本管理器,用于保证运行正确数目的应用副本。相比而言,部署是更高层级的操作,应该用于管理副本组。除非你遇到特殊的情况,需要控制副本的特性,否则你几乎不需要直接操作副本组。 + +### 守护进程组 (DaemonSet) + +上面提到 Kubernetes 始终使用标签,还有印象吗?守护进程组是一个控制器,用于确保守护进程化的应用一直运行在具有特定标签的节点中。 + +例如,你将所有节点增加 `logger` 或 `mission_critical` 的标签,以便运行日志 / 审计服务的守护进程。接着,你创建一个守护进程组并使用 `logger` 或 `mission_critical` 节点选择器。Kubernetes 会查找具有该标签的节点,确保守护进程的实例一直运行在这些节点中。因而,节点中运行的所有进程都可以在节点内访问对应的守护进程。 + +以我的应用为例,NSQ 守护进程可以用守护进程组实现。具体而言,将对应节点增加 `recevier` 标签,创建一个守护进程组并配置 `receiver` 应用选择器,这样这些节点上就会一直运行接收者组件。 + +守护进程组具有副本组的全部优势,可扩展且由 Kubernetes 管理,意味着 Kubernetes 管理其全生命周期的事件,确保持续运行,即使出现故障,也会立即替换。 + +### 扩展 + +在 Kubernetes 中,扩展是稀松平常的事情。副本组负责 Pod 运行的实例数目。就像你在 nginx 部署那个示例中看到的那样,对应设置项 `replicas:3`。我们可以按应用所需,让 Kubernetes 运行多份应用副本。 + +当然,设置项有很多。你可以指定让多个副本运行在不同的节点上,也可以指定各种不同的应用启动等待时间。想要在这方面了解更多,可以阅读 [水平扩展][19] 和 [Kubernetes 中的交互式扩展][20];当然 [副本组][21] 的细节对你也有帮助,毕竟 Kubernetes 中的扩展功能都来自于该模块。 + +### Kubernetes 部分小结 + +Kubernetes 是容器编排的便捷工具,工作单元为 Pods,具有分层架构。最顶层是部署,用于操作其它资源,具有高度可配置性。对于你的每个命令调用,Kubernetes 提供了对应的 API,故理论上你可以编写自己的代码,向 Kubernetes API 发送数据,得到与 `kubectl` 命令同样的效果。 + +截至目前,Kubernetes 原生支持所有主流云服务供应商,而且完全开源。如果你愿意,可以贡献代码;如果你希望对工作原理有深入了解,可以查阅代码:[GitHub 上的 Kubernetes 项目][22]。 + +### Minikube + +接下来我会使用 [Minikube][23] 这款本地 Kubernetes 集群模拟器。它并不擅长模拟多节点集群,但可以很容易地给你提供本地学习环境,让你开始探索,这很棒。Minikube 基于可高度调优的虚拟机,由 VirtualBox 类似的虚拟化工具提供。 + +我用到的全部 Kubernetes 模板文件可以在这里找到:[Kubernetes 文件][24]。 + +注意:在你后续测试可扩展性时,会发现副本一直处于 `Pending` 状态,这是因为 minikube 集群中只有一个节点,不应该允许多副本运行在同一个节点上,否则明显只是耗尽了可用资源。使用如下命令可以查看可用资源: + +``` +kubectl get nodes -o yaml + +``` + +### 构建容器 + +Kubernetes 支持大多数现有的容器技术。我这里使用 Docker。每一个构建的服务容器,对应代码库中的一个 Dockerfile 文件。我推荐你仔细阅读它们,其中大多数都比较简单。对于 Go 服务,我采用了最近引入的多步构建的方式。Go 服务基于 Alpine Linux 镜像创建。人脸识别程序使用 Python,NSQ 和 MySQL 使用对应的容器。 + +### 上下文 + +Kubernetes 使用命名空间。如果你不额外指定命名空间,Kubernetes 会使用 `default` 命名空间。为避免污染默认命名空间,我会一直指定命名空间,具体操作如下: + +``` +❯ kubectl config set-context kube-face-cluster --namespace=face +Context "kube-face-cluster" created. + +``` + +创建上下文之后,应马上启用: + +``` +❯ kubectl config use-context kube-face-cluster +Switched to context "kube-face-cluster". + +``` +此后,所有 `kubectl` 命令都会使用 `face` 命名空间。 + +(译注:作者后续并没有使用 face 命名空间,模板文件中的命名空间仍为 default,可能 face 命名空间用于开发环境。如果希望使用 face 命令空间,需要将内部 DNS 地址中的 default 改成 face;如果只是测试,可以不执行这两条命令。) + +### 应用部署 + +Pods 和 服务概览: + +![kube deployed](https://skarlso.github.io/img/kube_deployed.png) + +### MySQL + +第一个要部署的服务是数据库。 + +按照 Kubernetes 的示例 [Kubenetes MySQL][25] 进行部署,即可以满足我的需求。注意:示例配置文件的 MYSQL_PASSWORD 字段使用了明文密码,我将使用 [Kubernetes Secrets][26] 对象以提高安全性。 + +我创建了一个 Secret 对象,对应的本地 yaml 文件如下: + +``` +apiVersion: v1 +kind: Secret +metadata: + name: kube-face-secret +type: Opaque +data: + mysql_password: base64codehere + mysql_userpassword: base64codehere + +``` + +其中 base64 编码通过如下命令生成: + +``` +echo -n "ubersecurepassword" | base64 +echo -n "root:ubersecurepassword" | base64 + +``` + +(LCTT 译注:secret yaml 文件中的 data 应该有两条,一条对应 mysql_password, 仅包含密码;另一条对应 mysql_userpassword,包含用户和密码。后文会用到 mysql_userpassword,但没有提及相应的生成) + +我的部署 yaml 对应部分如下: + +``` +... +- name: MYSQL_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: kube-face-secret + key: mysql_password +... + +``` + +另外值得一提的是,我使用卷将数据库持久化,卷对应的定义如下: + +``` +... + volumeMounts: + - name: mysql-persistent-storage + mountPath: /var/lib/mysql +... + volumes: + - name: mysql-persistent-storage + persistentVolumeClaim: + claimName: mysql-pv-claim +... + +``` + +其中 `presistentVolumeClain` 是关键,告知 Kubernetes 当前资源需要持久化存储。持久化存储的提供方式对用户透明。类似 Pods,如果想了解更多细节,参考文档:[Kubernetes 持久化存储][27]。 + +(LCTT 译注:使用 presistentVolumeClain 之前需要创建 presistentVolume,对于单节点可以使用本地存储,对于多节点需要使用共享存储,因为 Pod 可以能调度到任何一个节点) + +使用如下命令部署 MySQL 服务: + +``` +kubectl apply -f mysql.yaml + +``` + +这里比较一下 `create` 和 `apply`。`apply` 是一种宣告式declarative的对象配置命令,而 `create` 是命令式imperative的命令。当下我们需要知道的是,`create` 通常对应一项任务,例如运行某个组件或创建一个部署;相比而言,当我们使用 `apply` 的时候,用户并没有指定具体操作,Kubernetes 会根据集群目前的状态定义需要执行的操作。故如果不存在名为 `mysql` 的服务,当我执行 `apply -f mysql.yaml` 时,Kubernetes 会创建该服务。如果再次执行这个命令,Kubernetes 会忽略该命令。但如果我再次运行 `create`,Kubernetes 会报错,告知服务已经创建。 + +想了解更多信息,请阅读如下文档:[Kubernetes 对象管理][28],[命令式配置][29]和[宣告式配置][30]。 + +运行如下命令查看执行进度信息: + +``` +# 描述完整信息 +kubectl describe deployment mysql +# 仅描述 Pods 信息 +kubectl get pods -l app=mysql + +``` + +(第一个命令)输出示例如下: + +``` +... + Type Status Reason + ---- ------ ------ + Available True MinimumReplicasAvailable + Progressing True NewReplicaSetAvailable +OldReplicaSets: +NewReplicaSet: mysql-55cd6b9f47 (1/1 replicas created) +... + +``` + +对于 `get pods` 命令,输出示例如下: + +``` +NAME READY STATUS RESTARTS AGE +mysql-78dbbd9c49-k6sdv 1/1 Running 0 18s + +``` + +可以使用下面的命令测试数据库实例: + +``` +kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -pyourpasswordhere + +``` + +特别提醒:如果你在这里修改了密码,重新 apply 你的 yaml 文件并不能更新容器。因为数据库是持久化的,密码并不会改变。你需要先使用 `kubectl delete -f mysql.yaml` 命令删除整个部署。 + +运行 `show databases` 后,应该可以看到如下信息: + +``` +If you don't see a command prompt, try pressing enter. + +mysql> +mysql> +mysql> show databases; ++--------------------+ +| Database | ++--------------------+ +| information_schema | +| kube | +| mysql | +| performance_schema | ++--------------------+ +4 rows in set (0.00 sec) + +mysql> exit +Bye + +``` + +你会注意到,我还将一个[数据库初始化 SQL][31] 文件挂载到容器中,MySQL 容器会自动运行该文件,导入我将用到的部分数据和模式。 + +对应的卷定义如下: + +``` + volumeMounts: + - name: mysql-persistent-storage + mountPath: /var/lib/mysql + - name: bootstrap-script + mountPath: /docker-entrypoint-initdb.d/database_setup.sql +volumes: +- name: mysql-persistent-storage + persistentVolumeClaim: + claimName: mysql-pv-claim +- name: bootstrap-script + hostPath: + path: /Users/hannibal/golang/src/github.com/Skarlso/kube-cluster-sample/database_setup.sql + type: File + +``` + +(LCTT 译注:数据库初始化脚本需要改成对应的路径,如果是多节点,需要是共享存储中的路径。另外,作者给的 sql 文件似乎有误,person_images 表中的 person_id 列数字都小 1,作者默认 id 从 0 开始,但应该是从 1 开始) + +运行如下命令查看引导脚本是否正确执行: + +``` +~/golang/src/github.com/Skarlso/kube-cluster-sample/kube_files master* +❯ kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -uroot -pyourpasswordhere kube +If you don't see a command prompt, try pressing enter. + +mysql> show tables; ++----------------+ +| Tables_in_kube | ++----------------+ +| images | +| person | +| person_images | ++----------------+ +3 rows in set (0.00 sec) + +mysql> + +``` + +(LCTT 译注:上述代码块中的第一行是作者执行命令所在路径,执行第二行的命令无需在该目录中进行) + +上述操作完成了数据库服务的初始化。使用如下命令可以查看服务日志: + +``` +kubectl logs deployment/mysql -f + +``` + +### NSQ 查询 + +NSQ 查询将以内部服务的形式运行。由于不需要外部访问,这里使用 `clusterIP: None` 在 Kubernetes 中将其设置为无头服务headless service,意味着该服务不使用负载均衡模式,也不使用单独的服务 IP。DNS 将基于服务选择器selectors。 + +我们的 NSQ 查询服务对应的选择器为: + +``` + selector: + matchLabels: + app: nsqlookup + +``` + +那么,内部 DNS 对应的条目类似于:`nsqlookup.default.svc.cluster.local`。 + +无头服务的更多细节,可以参考:[无头服务][32]。 + +NSQ 服务与 MySQL 服务大同小异,只需要少许修改即可。如前所述,我将使用 NSQ 原生的 Docker 镜像,名称为 `nsqio/nsq`。镜像包含了全部的 nsq 命令,故 nsqd 也将使用该镜像,只是使用的命令不同。对于 nsqlookupd,命令如下: + +``` +command: ["/nsqlookupd"] +args: ["--broadcast-address=nsqlookup.default.svc.cluster.local"] + +``` + +你可能会疑惑,`--broadcast-address` 参数是做什么用的?默认情况下,nsqlookup 使用 `hostname` (LCTT 译注:这里是指容器的主机名,而不是 hostname 字符串本身)作为广播地址;这意味着,当用户运行回调时,回调试图访问的地址类似于 `http://nsqlookup-234kf-asdf:4161/lookup?topics=image`,但这显然不是我们期望的。将广播地址设置为内部 DNS 后,回调地址将是 `http://nsqlookup.default.svc.cluster.local:4161/lookup?topic=images`,这正是我们期望的。 + +NSQ 查询还需要转发两个端口,一个用于广播,另一个用于 nsqd 守护进程的回调。在 Dockerfile 中暴露相应端口,在 Kubernetes 模板中使用它们,类似如下: + +容器模板: + +``` + ports: + - containerPort: 4160 + hostPort: 4160 + - containerPort: 4161 + hostPort: 4161 + +``` + +服务模板: +``` +spec: + ports: + - name: main + protocol: TCP + port: 4160 + targetPort: 4160 + - name: secondary + protocol: TCP + port: 4161 + targetPort: 4161 + +``` + +端口名称是必须的,Kubernetes 基于名称进行区分。(LCTT 译注:端口名更新为作者 GitHub 对应文件中的名称) + +像之前那样,使用如下命令创建服务: + +``` +kubectl apply -f nsqlookup.yaml + +``` + +nsqlookupd 部分到此结束。截至目前,我们已经准备好两个主要的组件。 + +### 接收器 + +这部分略微复杂。接收器需要完成三项工作: + +* 创建一些部署 +* 创建 nsq 守护进程 +* 将本服务对外公开 + +#### 部署 + +第一个要创建的部署是接收器本身,容器镜像为 `skarlso/kube-receiver-alpine`。 + +#### NSQ 守护进程 + +接收器需要使用 NSQ 守护进程。如前所述,接收器在其内部运行一个 NSQ,这样与 nsq 的通信可以在本地进行,无需通过网络。为了让接收器可以这样操作,NSQ 需要与接收器部署在同一个节点上。 + +NSQ 守护进程也需要一些调整的参数配置: + +``` + ports: + - containerPort: 4150 + hostPort: 4150 + - containerPort: 4151 + hostPort: 4151 + env: + - name: NSQLOOKUP_ADDRESS + value: nsqlookup.default.svc.cluster.local + - name: NSQ_BROADCAST_ADDRESS + value: nsqd.default.svc.cluster.local + command: ["/nsqd"] + args: ["--lookupd-tcp-address=$(NSQLOOKUP_ADDRESS):4160", "--broadcast-address=$(NSQ_BROADCAST_ADDRESS)"] + +``` + +其中我们配置了 lookup-tcp-address 和 broadcast-address 参数。前者是 nslookup 服务的 DNS 地址,后者用于回调,就像 nsqlookupd 配置中那样。 + +#### 对外公开 + +下面即将创建第一个对外公开的服务。有两种方式可供选择。考虑到该 API 负载较高,可以使用负载均衡的方式。另外,如果希望将其部署到生产环境中的任选节点,也应该使用负载均衡方式。 + +但由于我使用的本地集群只有一个节点,那么使用 `节点端口` 的方式就足够了。`节点端口` 方式将服务暴露在对应节点的固定端口上。如果未指定端口,将从 30000-32767 数字范围内随机选其一个。也可以指定端口,可以在模板文件中使用 `nodePort` 设置即可。可以通过 `:` 访问该服务。如果使用多个节点,负载均衡可以将多个 IP 合并为一个 IP。 + +更多信息,请参考文档:[服务发布][33]。 + +结合上面的信息,我们定义了接收器服务,对应的模板如下: + +``` +apiVersion: v1 +kind: Service +metadata: + name: receiver-service +spec: + ports: + - protocol: TCP + port: 8000 + targetPort: 8000 + selector: + app: receiver + type: NodePort + +``` + +如果希望固定使用 8000 端口,需要增加 `nodePort` 配置,具体如下: + +``` +apiVersion: v1 +kind: Service +metadata: + name: receiver-service +spec: + ports: + - protocol: TCP + port: 8000 + targetPort: 8000 + selector: + app: receiver + type: NodePort + nodePort: 8000 + +``` + +(LCTT 译注:虽然作者没有写,但我们应该知道需要运行的部署命令 `kubectl apply -f receiver.yaml`。) + +### 图片处理器 + +图片处理器用于将图片传送至识别组件。它需要访问 nslookupd, mysql 以及后续部署的人脸识别服务的 gRPC 接口。事实上,这是一个无聊的服务,甚至其实并不是服务(LCTT 译注:第一个服务是指在整个架构中,图片处理器作为一个服务;第二个服务是指 Kubernetes 服务)。它并需要对外暴露端口,这是第一个只包含部署的组件。长话短说,下面是完整的模板: + +``` +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: image-processor-deployment +spec: + selector: + matchLabels: + app: image-processor + replicas: 1 + template: + metadata: + labels: + app: image-processor + spec: + containers: + - name: image-processor + image: skarlso/kube-processor-alpine:latest + env: + - name: MYSQL_CONNECTION + value: "mysql.default.svc.cluster.local" + - name: MYSQL_USERPASSWORD + valueFrom: + secretKeyRef: + name: kube-face-secret + key: mysql_userpassword + - name: MYSQL_PORT + # TIL: If this is 3306 without " kubectl throws an error. + value: "3306" + - name: MYSQL_DBNAME + value: kube + - name: NSQ_LOOKUP_ADDRESS + value: "nsqlookup.default.svc.cluster.local:4161" + - name: GRPC_ADDRESS + value: "face-recog.default.svc.cluster.local:50051" + +``` + +文件中唯一需要提到的是用于配置应用的多个环境变量属性,主要关注 nsqlookupd 地址 和 gRPC 地址。 + +运行如下命令完成部署: + +``` +kubectl apply -f image_processor.yaml + +``` + +### 人脸识别 + +人脸识别服务的确包含一个 Kubernetes 服务,具体而言是一个比较简单、仅供图片处理器使用的服务。模板如下: + +``` +apiVersion: v1 +kind: Service +metadata: + name: face-recog +spec: + ports: + - protocol: TCP + port: 50051 + targetPort: 50051 + selector: + app: face-recog + clusterIP: None + +``` + +更有趣的是,该服务涉及两个卷,分别为 `known_people` 和 `unknown_people`。你能猜到卷中包含什么内容吗?对,是图片。`known_people` 卷包含所有新图片,接收器收到图片后将图片发送至该卷对应的路径,即挂载点。在本例中,挂载点为 `/unknown_people`,人脸识别服务需要能够访问该路径。 + +对于 Kubernetes 和 Docker 而言,这很容易。卷可以使用挂载的 S3 或 某种 nfs,也可以是宿主机到虚拟机的本地挂载。可选方式有很多 (至少有一打那么多)。为简洁起见,我将使用本地挂载方式。 + +挂载卷分为两步。第一步,需要在 Dockerfile 中指定卷: + +``` +VOLUME [ "/unknown_people", "/known_people" ] + +``` + +第二步,就像之前为 MySQL Pod 挂载卷那样,需要在 Kubernetes 模板中配置;相比而言,这里使用 `hostPath`,而不是 MySQL 例子中的 `PersistentVolumeClaim`: + +``` + volumeMounts: + - name: known-people-storage + mountPath: /known_people + - name: unknown-people-storage + mountPath: /unknown_people + volumes: + - name: known-people-storage + hostPath: + path: /Users/hannibal/Temp/known_people + type: Directory + - name: unknown-people-storage + hostPath: + path: /Users/hannibal/Temp/ + type: Directory + +``` + +(LCTT 译注:对于多节点模式,由于人脸识别服务和接收器服务可能不在一个节点上,故需要使用共享存储而不是节点本地存储。另外,出于 Python 代码的逻辑,推荐保持两个文件夹的嵌套结构,即 known_people 作为子目录。) + +我们还需要为 `known_people` 文件夹做配置设置,用于人脸识别程序。当然,使用环境变量属性可以完成该设置: + +``` + env: + - name: KNOWN_PEOPLE + value: "/known_people" + +``` + +Python 代码按如下方式搜索图片: + +``` + known_people = os.getenv('KNOWN_PEOPLE', 'known_people') + print("Known people images location is: %s" % known_people) + images = self.image_files_in_folder(known_people) + +``` + +其中 `image_files_in_folder` 函数定义如下: + +``` + def image_files_in_folder(self, folder): + return [os.path.join(folder, f) for f in os.listdir(folder) if re.match(r'.*\.(jpg|jpeg|png)', f, flags=re.I)] + +``` + +看起来不错。 + +如果接收器现在收到一个类似下面的请求(接收器会后续将其发送出去): + +``` +curl -d '{"path":"/unknown_people/unknown220.jpg"}' http://192.168.99.100:30251/image/post + +``` + +图像处理器会在 `/unknown_people` 目录搜索名为 unknown220.jpg 的图片,接着在 known_foler 文件中找到 unknown220.jpg 对应个人的图片,最后返回匹配图片的名称。 + +查看日志,大致信息如下: + +``` +# 接收器 +❯ curl -d '{"path":"/unknown_people/unknown219.jpg"}' http://192.168.99.100:30251/image/post +got path: {Path:/unknown_people/unknown219.jpg} +image saved with id: 4 +image sent to nsq + +# 图片处理器 +2018/03/26 18:11:21 INF 1 [images/ch] querying nsqlookupd http://nsqlookup.default.svc.cluster.local:4161/lookup?topic=images +2018/03/26 18:11:59 Got a message: 4 +2018/03/26 18:11:59 Processing image id: 4 +2018/03/26 18:12:00 got person: Hannibal +2018/03/26 18:12:00 updating record with person id +2018/03/26 18:12:00 done + +``` + +我们已经使用 Kubernetes 部署了应用正常工作所需的全部服务。 + +### 前端 + +更进一步,可以使用简易的 Web 应用更好的显示数据库中的信息。这也是一个对外公开的服务,使用的参数可以参考接收器。 + +部署后效果如下: + +![frontend](https://skarlso.github.io/img/kube-frontend.png) + +### 回顾 + +到目前为止我们做了哪些操作呢?我一直在部署服务,用到的命令汇总如下: + +``` +kubectl apply -f mysql.yaml +kubectl apply -f nsqlookup.yaml +kubectl apply -f receiver.yaml +kubectl apply -f image_processor.yaml +kubectl apply -f face_recognition.yaml +kubectl apply -f frontend.yaml + +``` + +命令顺序可以打乱,因为除了图片处理器的 NSQ 消费者外的应用在启动时并不会建立连接,而且图片处理器的 NSQ 消费者会不断重试。 + +使用 `kubectl get pods` 查询正在运行的 Pods,示例如下: + +``` +❯ kubectl get pods +NAME READY STATUS RESTARTS AGE +face-recog-6bf449c6f-qg5tr 1/1 Running 0 1m +image-processor-deployment-6467468c9d-cvx6m 1/1 Running 0 31s +mysql-7d667c75f4-bwghw 1/1 Running 0 36s +nsqd-584954c44c-299dz 1/1 Running 0 26s +nsqlookup-7f5bdfcb87-jkdl7 1/1 Running 0 11s +receiver-deployment-5cb4797598-sf5ds 1/1 Running 0 26s + +``` + +运行 `minikube service list`: + +``` +❯ minikube service list +|-------------|----------------------|-----------------------------| +| NAMESPACE | NAME | URL | +|-------------|----------------------|-----------------------------| +| default | face-recog | No node port | +| default | kubernetes | No node port | +| default | mysql | No node port | +| default | nsqd | No node port | +| default | nsqlookup | No node port | +| default | receiver-service | http://192.168.99.100:30251 | +| kube-system | kube-dns | No node port | +| kube-system | kubernetes-dashboard | http://192.168.99.100:30000 | +|-------------|----------------------|-----------------------------| + +``` + +### 滚动更新 (Rolling Update) + +滚动更新过程中会发生什么呢? + +![kube rotate](https://skarlso.github.io/img/kube_rotate.png) + +在软件开发过程中,需要变更应用的部分组件是常有的事情。如果我希望在不影响其它组件的情况下变更一个组件,我们的集群会发生什么变化呢?我们还需要最大程度的保持向后兼容性,以免影响用户体验。谢天谢地,Kubernetes 可以帮我们做到这些。 + +目前的 API 一次只能处理一个图片,不能批量处理,对此我并不满意。 + +#### 代码 + +目前,我们使用下面的代码段处理单个图片的情形: + +``` +// PostImage 对图片提交做出响应,将图片信息保存到数据库中 +// 并将该信息发送给 NSQ 以供后续处理使用 +func PostImage(w http.ResponseWriter, r *http.Request) { +... +} + +func main() { + router := mux.NewRouter() + router.HandleFunc("/image/post", PostImage).Methods("POST") + log.Fatal(http.ListenAndServe(":8000", router)) +} + +``` + +我们有两种选择。一种是增加新接口 `/images/post` 给用户使用;另一种是在原接口基础上修改。 + +新版客户端有回退特性,在新接口不可用时回退使用旧接口。但旧版客户端没有这个特性,故我们不能马上修改代码逻辑。考虑如下场景,你有 90 台服务器,计划慢慢执行滚动更新,依次对各台服务器进行业务更新。如果一台服务需要大约 1 分钟更新业务,那么整体更新完成需要大约 1 个半小时的时间(不考虑并行更新的情形)。 + +更新过程中,一些服务器运行新代码,一些服务器运行旧代码。用户请求被负载均衡到各个节点,你无法控制请求到达哪台服务器。如果客户端的新接口请求被调度到运行旧代码的服务器,请求会失败;客户端可能会回退使用旧接口,(但由于我们已经修改旧接口,本质上仍然是调用新接口),故除非请求刚好到达到运行新代码的服务器,否则一直都会失败。这里我们假设不使用粘性会话sticky sessions。 + +而且,一旦所有服务器更新完毕,旧版客户端不再能够使用你的服务。 + +这里,你可能会说你并不需要保留旧代码;某些情况下,确实如此。因此,我们打算直接修改旧代码,让其通过少量参数调用新代码。这样操作操作相当于移除了旧代码。当所有客户端迁移完毕后,这部分代码也可以安全地删除。 + +#### 新的 Endpoint + +让我们添加新的路由方法: + +``` +... +router.HandleFunc("/images/post", PostImages).Methods("POST") +... + +``` + +更新旧的路由方法,使其调用新的路由方法,修改部分如下: + +``` +// PostImage 对图片提交做出响应,将图片信息保存到数据库中 +// 并将该信息发送给 NSQ 以供后续处理使用 +func PostImage(w http.ResponseWriter, r *http.Request) { + var p Path + err := json.NewDecoder(r.Body).Decode(&p) + if err != nil { + fmt.Fprintf(w, "got error while decoding body: %s", err) + return + } + fmt.Fprintf(w, "got path: %+v\n", p) + var ps Paths + paths := make([]Path, 0) + paths = append(paths, p) + ps.Paths = paths + var pathsJSON bytes.Buffer + err = json.NewEncoder(&pathsJSON).Encode(ps) + if err != nil { + fmt.Fprintf(w, "failed to encode paths: %s", err) + return + } + r.Body = ioutil.NopCloser(&pathsJSON) + r.ContentLength = int64(pathsJSON.Len()) + PostImages(w, r) +} + +``` + +当然,方法名可能容易混淆,但你应该能够理解我想表达的意思。我将请求中的单个路径封装成新方法所需格式,然后将其作为请求发送给新接口处理。仅此而已。在 [滚动更新批量图片 PR][34] 中可以找到更多的修改方式。 + +至此,我们使用两种方法调用接收器: + +``` +# 单路径模式 +curl -d '{"path":"unknown4456.jpg"}' http://127.0.0.1:8000/image/post + +# 多路径模式 +curl -d '{"paths":[{"path":"unknown4456.jpg"}]}' http://127.0.0.1:8000/images/post + +``` + +这里用到的客户端是 curl。一般而言,如果客户端本身是一个服务,我会做一些修改,在新接口返回 404 时继续尝试旧接口。 + +为了简洁,我不打算为 NSQ 和其它组件增加批量图片处理的能力。这些组件仍然是一次处理一个图片。这部分修改将留给你作为扩展内容。 :) + +#### 新镜像 + +为实现滚动更新,我首先需要为接收器服务创建一个新的镜像。新镜像使用新标签,告诉大家版本号为 v1.1。 + +``` +docker build -t skarlso/kube-receiver-alpine:v1.1 . + +``` + +新镜像创建后,我们可以开始滚动更新了。 + +#### 滚动更新 + +在 Kubernetes 中,可以使用多种方式完成滚动更新。 + +##### 手动更新 + +不妨假设在我配置文件中使用的容器版本为 `v1.0`,那么实现滚动更新只需运行如下命令: + +``` +kubectl rolling-update receiver --image:skarlso/kube-receiver-alpine:v1.1 + +``` + +如果滚动更新过程中出现问题,我们总是可以回滚: + +``` +kubectl rolling-update receiver --rollback + +``` + +容器将回滚到使用上一个版本镜像,操作简捷无烦恼。 + +##### 应用新的配置文件 + +手动更新的不足在于无法版本管理。 + +试想下面的场景。你使用手工更新的方式对若干个服务器进行滚动升级,但其它人并不知道这件事。之后,另外一个人修改了模板文件并将其应用到集群中,更新了全部服务器;更新过程中,突然发现服务不可用了。 + +长话短说,由于模板无法识别已经手动更新的服务器,这些服务器会按模板变更成错误的状态。这种做法很危险,千万不要这样做。 + +推荐的做法是,使用新版本信息更新模板文件,然后使用 `apply` 命令应用模板文件。 + +对于滚动扩展,Kubernetes 推荐通过部署结合副本组完成。但这意味着待滚动更新的应用至少有 2 个副本,否则无法完成 (除非将 `maxUnavailable` 设置为 1)。我在模板文件中增加了副本数量、设置了接收器容器的新镜像版本。 + +``` + replicas: 2 +... + spec: + containers: + - name: receiver + image: skarlso/kube-receiver-alpine:v1.1 +... + +``` + +更新过程中,你会看到如下信息: + +``` +❯ kubectl rollout status deployment/receiver-deployment +Waiting for rollout to finish: 1 out of 2 new replicas have been updated... + +``` + +通过在模板中增加 `strategy` 段,你可以增加更多的滚动扩展配置: + +``` + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + +``` + +关于滚动更新的更多信息,可以参考如下文档:[部署的滚动更新][35],[部署的更新][36], [部署的管理][37] 和 [使用副本控制器完成滚动更新][38]等。 + +MINIKUBE 用户需要注意:由于我们使用单个主机上使用单节点配置,应用只有 1 份副本,故需要将 `maxUnavailable` 设置为 `1`。否则 Kubernetes 会阻止更新,新版本会一直处于 `Pending` 状态;这是因为我们在任何时刻都不允许出现没有(正在运行的) `receiver` 容器的场景。 + +### 扩展 + +Kubernetes 让扩展成为相当容易的事情。由于 Kubernetes 管理整个集群,你仅需在模板文件中添加你需要的副本数目即可。 + +这篇文章已经比较全面了,但文章的长度也越来越长。我计划再写一篇后续文章,在 AWS 上使用多节点、多副本方式实现扩展。敬请期待。 + +### 清理环境 + +``` +kubectl delete deployments --all +kubectl delete services -all + +``` + +# 写在最后的话 + +各位看官,本文就写到这里了。我们在 Kubernetes 上编写、部署、更新和扩展(老实说,并没有实现)了一个分布式应用。 + +如果你有任何疑惑,请在下面的评论区留言交流,我很乐意回答相关问题。 + +希望阅读本文让你感到愉快。我知道,这是一篇相对长的文章,我也曾经考虑进行拆分;但整合在一起的单页教程也有其好处,例如利于搜索、保存页面或更进一步将页面打印为 PDF 文档。 + +Gergely 感谢你阅读本文。 + +-------------------------------------------------------------------------------- + +via: https://skarlso.github.io/2018/03/15/kubernetes-distributed-application/ + +作者:[hannibal ][a] +译者:[pinewall](https://github.com/pinewall) +校对:[校对者ID](https://github.com/校对者ID) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]:https://github.com/Skarlso +[1]:https://skarlso.github.io/2018/03/15/kubernetes-distributed-application/#circuit-breaker +[2]:https://skarlso.github.io//categories/go +[3]:https://skarlso.github.io//categories/kubernetes +[4]:https://skarlso.github.io//categories/facerecognition +[5]:https://kubernetes.io/ +[6]:https://github.com/Skarlso/kube-cluster-sample +[7]:https://github.com/Skarlso/kube-cluster-sample/tree/master/receiver +[8]:http://nsq.io/ +[9]:https://github.com/Skarlso/kube-cluster-sample/tree/master/image_processor +[10]:https://github.com/Skarlso/kube-cluster-sample/tree/master/face_recognition +[11]:https://grpc.io/ +[12]:https://golang.org/pkg/sync/#Cond +[13]:https://github.com/ageitgey/face_recognition +[14]:https://gocv.io/ +[15]:http://shop.oreilly.com/product/0636920043874.do +[16]:https://kubernetes.io/docs/ +[17]:https://kubernetes.io/docs/concepts/overview/components/ +[18]:https://kubernetes.io/docs/concepts/services-networking/service/ +[19]:https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/ +[20]:https://kubernetes.io/docs/tutorials/kubernetes-basics/scale-interactive/ +[21]:https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/ +[22]:https://github.com/kubernetes/kubernetes +[23]:https://github.com/kubernetes/minikube/ +[24]:https://github.com/Skarlso/kube-cluster-sample/tree/master/kube_files +[25]:https://kubernetes.io/docs/tasks/run-application/run-single-instance-stateful-application/#deploy-mysql +[26]:https://kubernetes.io/docs/concepts/configuration/secret/ +[27]:https://kubernetes.io/docs/concepts/storage/persistent-volumes +[28]:https://kubernetes.io/docs/concepts/overview/object-management-kubectl/overview/ +[29]:https://kubernetes.io/docs/concepts/overview/object-management-kubectl/imperative-config/ +[30]:https://kubernetes.io/docs/concepts/overview/object-management-kubectl/declarative-config/ +[31]:https://github.com/Skarlso/kube-cluster-sample/blob/master/database_setup.sql +[32]:https://kubernetes.io/docs/concepts/services-networking/service/#headless-services +[33]:https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types +[34]:https://github.com/Skarlso/kube-cluster-sample/pull/1 +[35]:https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#rolling-back-a-deployment +[36]:https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#updating-a-deployment +[37]:https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#updating-your-application-without-a-service-outage +[38]:https://kubernetes.io/docs/tasks/run-application/rolling-update-replication-controller/ +[39]:https://skarlso.github.io/2018/03/15/kubernetes-distributed-application/