2017年3月

本文作者:

A. Jesse Jiryu Davis 是纽约 MongoDB 的工程师。他编写了异步 MongoDB Python 驱动程序 Motor,也是 MongoDB C 驱动程序的开发领袖和 PyMongo 团队成员。 他也为 asyncio 和 Tornado 做了贡献,在 http://emptysqua.re 上写作。

Guido van Rossum 是主流编程语言 Python 的创造者,Python 社区称他为 BDFL ( 仁慈的终生大独裁者 Benevolent Dictator For Life )——这是一个来自 Monty Python 短剧的称号。他的主页是 http://www.python.org/~guido/

介绍

经典的计算机科学强调高效的算法,尽可能快地完成计算。但是很多网络程序的时间并不是消耗在计算上,而是在等待许多慢速的连接或者低频事件的发生。这些程序暴露出一个新的挑战:如何高效的等待大量网络事件。一个现代的解决方案是异步 I/O。

这一章我们将实现一个简单的网络爬虫。这个爬虫只是一个原型式的异步应用,因为它等待许多响应而只做少量的计算。一次爬的网页越多,它就能越快的完成任务。如果它为每个动态的请求启动一个线程的话,随着并发请求数量的增加,它会在耗尽套接字之前,耗尽内存或者线程相关的资源。使用异步 I/O 可以避免这个的问题。

我们将分三个阶段展示这个例子。首先,我们会实现一个事件循环并用这个事件循环和回调来勾画出一只网络爬虫。它很有效,但是当把它扩展成更复杂的问题时,就会导致无法管理的混乱代码。然后,由于 Python 的协程不仅有效而且可扩展,我们将用 Python 的生成器函数实现一个简单的协程。在最后一个阶段,我们将使用 Python 标准库“asyncio”中功能完整的协程, 并通过异步队列完成这个网络爬虫。(在 PyCon 2013 上,Guido 介绍了标准的 asyncio 库,当时称之为“Tulip”。)

任务

网络爬虫寻找并下载一个网站上的所有网页,也许还会把它们存档,为它们建立索引。从根 URL 开始,它获取每个网页,解析出没有遇到过的链接加到队列中。当网页没有未见到过的链接并且队列为空时,它便停止运行。

我们可以通过同时下载大量的网页来加快这一过程。当爬虫发现新的链接,它使用一个新的套接字并行的处理这个新链接,解析响应,添加新链接到队列。当并发很大时,可能会导致性能下降,所以我们会限制并发的数量,在队列保留那些未处理的链接,直到一些正在执行的任务完成。

传统方式

怎么使一个爬虫并发?传统的做法是创建一个线程池,每个线程使用一个套接字在一段时间内负责一个网页的下载。比如,下载 xkcd.com 网站的一个网页:

def fetch(url):
    sock = socket.socket()
    sock.connect(('xkcd.com', 80))
    request = 'GET {} HTTP/1.0
Host: xkcd.com

'.format(url)
    sock.send(request.encode('ascii'))
    response = b''
    chunk = sock.recv(4096)
    while chunk:
        response += chunk
        chunk = sock.recv(4096)

    # Page is now downloaded.
    links = parse_links(response)
    q.add(links)

套接字操作默认是阻塞的:当一个线程调用一个类似 connectrecv 方法时,它会阻塞,直到操作完成。(即使是 send 也能被阻塞,比如接收端在接受外发消息时缓慢而系统的外发数据缓存已经满了的情况下)因此,为了同一时间内下载多个网页,我们需要很多线程。一个复杂的应用会通过线程池保持空闲的线程来分摊创建线程的开销。同样的做法也适用于套接字,使用连接池。

到目前为止,使用线程的是成本昂贵的,操作系统对一个进程、一个用户、一台机器能使用线程做了不同的硬性限制。在 作者 Jesse 的系统中,一个 Python 线程需要 50K 的内存,开启上万个线程就会失败。每个线程的开销和系统的限制就是这种方式的瓶颈所在。

在 Dan Kegel 那一篇很有影响力的文章“The C10K problem”中,它提出了多线程方式在 I/O 并发上的局限性。他在开始写道,

网络服务器到了要同时处理成千上万的客户的时代了,你不这样认为么?毕竟,现在网络规模很大了。

Kegel 在 1999 年创造出“C10K”这个术语。一万个连接在今天看来还是可接受的,但是问题依然存在,只不过大小不同。回到那时候,对于 C10K 问题,每个连接启一个线程是不切实际的。现在这个限制已经成指数级增长。确实,我们的玩具网络爬虫使用线程也可以工作的很好。但是,对于有着千万级连接的大规模应用来说,限制依然存在:它会消耗掉所有线程,即使套接字还够用。那么我们该如何解决这个问题?

异步

异步 I/O 框架在一个线程中完成并发操作。让我们看看这是怎么做到的。

异步框架使用非阻塞套接字。异步爬虫中,我们在发起到服务器的连接前把套接字设为非阻塞:

sock = socket.socket()
sock.setblocking(False)
try:
    sock.connect(('xkcd.com', 80))
except BlockingIOError:
    pass

对一个非阻塞套接字调用 connect 方法会立即抛出异常,即使它可以正常工作。这个异常复现了底层 C 语言函数令人厌烦的行为,它把 errno 设置为 EINPROGRESS,告诉你操作已经开始。

现在我们的爬虫需要一种知道连接何时建立的方法,这样它才能发送 HTTP 请求。我们可以简单地使用循环来重试:

request = 'GET {} HTTP/1.0
Host: xkcd.com

'.format(url)
encoded = request.encode('ascii')

while True:
    try:
        sock.send(encoded)
        break  # Done.
    except OSError as e:
        pass

print('sent')

这种方法不仅消耗 CPU,也不能有效的等待多个套接字。在远古时代,BSD Unix 的解决方法是 select,这是一个 C 函数,它在一个或一组非阻塞套接字上等待事件发生。现在,互联网应用大量连接的需求,导致 selectpoll 所代替,在 BSD 上的实现是 kqueue ,在 Linux 上是 epoll。它们的 API 和 select 相似,但在大数量的连接中也能有较好的性能。

Python 3.4 的 DefaultSelector 会使用你系统上最好的 select 类函数。要注册一个网络 I/O 事件的提醒,我们会创建一个非阻塞套接字,并使用默认 selector 注册它。

from selectors import DefaultSelector, EVENT_WRITE

selector = DefaultSelector()

sock = socket.socket()
sock.setblocking(False)
try:
    sock.connect(('xkcd.com', 80))
except BlockingIOError:
    pass

def connected():
    selector.unregister(sock.fileno())
    print('connected!')

selector.register(sock.fileno(), EVENT_WRITE, connected)

我们不理会这个伪造的错误,调用 selector.register,传递套接字文件描述符和一个表示我们想要监听什么事件的常量表达式。为了当连接建立时收到提醒,我们使用 EVENT_WRITE :它表示什么时候这个套接字可写。我们还传递了一个 Python 函数 connected,当对应事件发生时被调用。这样的函数被称为回调

在一个循环中,selector 接收到 I/O 提醒时我们处理它们。

def loop():
    while True:
        events = selector.select()
        for event_key, event_mask in events:
            callback = event_key.data
            callback()

connected 回调函数被保存在 event_key.data 中,一旦这个非阻塞套接字建立连接,它就会被取出来执行。

不像我们前面那个快速轮转的循环,这里的 select 调用会暂停,等待下一个 I/O 事件,接着执行等待这些事件的回调函数。没有完成的操作会保持挂起,直到进到下一个事件循环时执行。

到目前为止我们展现了什么?我们展示了如何开始一个 I/O 操作和当操作准备好时调用回调函数。异步框架,它在单线程中执行并发操作,其建立在两个功能之上,非阻塞套接字和事件循环。

我们这里达成了“ 并发性 concurrency ”,但不是传统意义上的“ 并行性 parallelism ”。也就是说,我们构建了一个可以进行重叠 I/O 的微小系统,它可以在其它操作还在进行的时候就开始一个新的操作。它实际上并没有利用多核来并行执行计算。这个系统是用于解决 I/O 密集 I/O-bound 问题的,而不是解决 CPU 密集 CPU-bound 问题的。(Python 的全局解释器锁禁止在一个进程中以任何方式并行执行 Python 代码。在 Python 中并行化 CPU 密集的算法需要多个进程,或者以将该代码移植为 C 语言并行版本。但是这是另外一个话题了。)

所以,我们的事件循环在并发 I/O 上是有效的,因为它并不用为每个连接拨付线程资源。但是在我们开始前,我们需要澄清一个常见的误解:异步比多线程快。通常并不是这样的,事实上,在 Python 中,在处理少量非常活跃的连接时,像我们这样的事件循环是慢于多线程的。在运行时环境中是没有全局解释器锁的,在同样的负载下线程会执行的更好。异步 I/O 真正适用于事件很少、有许多缓慢或睡眠的连接的应用程序。(Jesse 在“什么是异步,它如何工作,什么时候该用它?”一文中指出了异步所适用和不适用的场景。Mike Bayer 在“异步 Python 和数据库”一文中比较了不同负载情况下异步 I/O 和多线程的不同。)

回调

用我们刚刚建立的异步框架,怎么才能完成一个网络爬虫?即使是一个简单的网页下载程序也是很难写的。

首先,我们有一个尚未获取的 URL 集合,和一个已经解析过的 URL 集合。

urls_todo = set(['/'])
seen_urls = set(['/'])

seen_urls 集合包括 urls_todo 和已经完成的 URL。用根 URL / 初始化它们。

获取一个网页需要一系列的回调。在套接字连接建立时会触发 connected 回调,它向服务器发送一个 GET 请求。但是它要等待响应,所以我们需要注册另一个回调函数;当该回调被调用,它仍然不能读取到完整的请求时,就会再一次注册回调,如此反复。

让我们把这些回调放在一个 Fetcher 对象中,它需要一个 URL,一个套接字,还需要一个地方保存返回的字节:

class Fetcher:
    def __init__(self, url):
        self.response = b''  # Empty array of bytes.
        self.url = url
        self.sock = None

我们的入口点在 Fetcher.fetch

    # Method on Fetcher class.
    def fetch(self):
        self.sock = socket.socket()
        self.sock.setblocking(False)
        try:
            self.sock.connect(('xkcd.com', 80))
        except BlockingIOError:
            pass

        # Register next callback.
        selector.register(self.sock.fileno(),
                          EVENT_WRITE,
                          self.connected)

fetch 方法从连接一个套接字开始。但是要注意这个方法在连接建立前就返回了。它必须将控制返回到事件循环中等待连接建立。为了理解为什么要这样做,假设我们程序的整体结构如下:

# Begin fetching http://xkcd.com/353/
fetcher = Fetcher('/353/')
fetcher.fetch()

while True:
    events = selector.select()
    for event_key, event_mask in events:
        callback = event_key.data
        callback(event_key, event_mask)

当调用 select 函数后,所有的事件提醒才会在事件循环中处理,所以 fetch 必须把控制权交给事件循环,这样我们的程序才能知道什么时候连接已建立,接着循环调用 connected 回调,它已经在上面的 fetch 方法中注册过。

这里是我们的 connected 方法的实现:

    # Method on Fetcher class.
    def connected(self, key, mask):
        print('connected!')
        selector.unregister(key.fd)
        request = 'GET {} HTTP/1.0
Host: xkcd.com

'.format(self.url)
        self.sock.send(request.encode('ascii'))

        # Register the next callback.
        selector.register(key.fd,
                          EVENT_READ,
                          self.read_response)

这个方法发送一个 GET 请求。一个真正的应用会检查 send 的返回值,以防所有的信息没能一次发送出去。但是我们的请求很小,应用也不复杂。它只是简单的调用 send,然后等待响应。当然,它必须注册另一个回调并把控制权交给事件循环。接下来也是最后一个回调函数 read_response,它处理服务器的响应:

    # Method on Fetcher class.
    def read_response(self, key, mask):
        global stopped

        chunk = self.sock.recv(4096)  # 4k chunk size.
        if chunk:
            self.response += chunk
        else:
            selector.unregister(key.fd)  # Done reading.
            links = self.parse_links()

            # Python set-logic:
            for link in links.difference(seen_urls):
                urls_todo.add(link)
                Fetcher(link).fetch()  # <- New Fetcher.

            seen_urls.update(links)
            urls_todo.remove(self.url)
            if not urls_todo:
                stopped = True

这个回调在每次 selector 发现套接字可读时被调用,可读有两种情况:套接字接受到数据或它被关闭。

这个回调函数从套接字读取 4K 数据。如果不到 4k,那么有多少读多少。如果比 4K 多,chunk 中只包 4K 数据并且这个套接字保持可读,这样在事件循环的下一个周期,会再次回到这个回调函数。当响应完成时,服务器关闭这个套接字,chunk 为空。

这里没有展示的 parse_links 方法,它返回一个 URL 集合。我们为每个新的 URL 启动一个 fetcher。注意一个使用异步回调方式编程的好处:我们不需要为共享数据加锁,比如我们往 seen_urls 增加新链接时。这是一种非抢占式的多任务,它不会在我们代码中的任意一个地方被打断。

我们增加了一个全局变量 stopped,用它来控制这个循环:

stopped = False

def loop():
    while not stopped:
        events = selector.select()
        for event_key, event_mask in events:
            callback = event_key.data
            callback()

一旦所有的网页被下载下来,fetcher 停止这个事件循环,程序退出。

这个例子让异步编程的一个问题明显的暴露出来:意大利面代码。

我们需要某种方式来表达一系列的计算和 I/O 操作,并且能够调度多个这样的系列操作让它们并发的执行。但是,没有线程你不能把这一系列操作写在一个函数中:当函数开始一个 I/O 操作,它明确的把未来所需的状态保存下来,然后返回。你需要考虑如何写这个状态保存的代码。

让我们来解释下这到底是什么意思。先来看一下在线程中使用通常的阻塞套接字来获取一个网页时是多么简单。

# Blocking version.
def fetch(url):
    sock = socket.socket()
    sock.connect(('xkcd.com', 80))
    request = 'GET {} HTTP/1.0
Host: xkcd.com

'.format(url)
    sock.send(request.encode('ascii'))
    response = b''
    chunk = sock.recv(4096)
    while chunk:
        response += chunk
        chunk = sock.recv(4096)

    # Page is now downloaded.
    links = parse_links(response)
    q.add(links)

在一个套接字操作和下一个操作之间这个函数到底记住了什么状态?它有一个套接字,一个 URL 和一个可增长的 response。运行在线程中的函数使用编程语言的基本功能来在栈中的局部变量保存这些临时状态。这样的函数也有一个“continuation”——它会在 I/O 结束后执行这些代码。运行时环境通过线程的指令指针来记住这个 continuation。你不必考虑怎么在 I/O 操作后恢复局部变量和这个 continuation。语言本身的特性帮你解决。

但是用一个基于回调的异步框架时,这些语言特性不能提供一点帮助。当等待 I/O 操作时,一个函数必须明确的保存它的状态,因为它会在 I/O 操作完成之前返回并清除栈帧。在我们基于回调的例子中,作为局部变量的替代,我们把 sockresponse 作为 Fetcher 实例 self 的属性来存储。而作为指令指针的替代,它通过注册 connectedread_response 回调来保存它的 continuation。随着应用功能的增长,我们需要手动保存的回调的复杂性也会增加。如此繁复的记账式工作会让编码者感到头痛。

更糟糕的是,当我们的回调函数抛出异常会发生什么?假设我们没有写好 parse_links 方法,它在解析 HTML 时抛出异常:

Traceback (most recent call last):
  File "loop-with-callbacks.py", line 111, in <module>
    loop()
  File "loop-with-callbacks.py", line 106, in loop
    callback(event_key, event_mask)
  File "loop-with-callbacks.py", line 51, in read_response
    links = self.parse_links()
  File "loop-with-callbacks.py", line 67, in parse_links
    raise Exception('parse error')
Exception: parse error

这个堆栈回溯只能显示出事件循环调用了一个回调。我们不知道是什么导致了这个错误。这条链的两边都被破坏:不知道从哪来也不知到哪去。这种丢失上下文的现象被称为“ 堆栈撕裂 stack ripping ”,经常会导致无法分析原因。它还会阻止我们为回调链设置异常处理,即那种用“try / except”块封装函数调用及其调用树。(对于这个问题的更复杂的解决方案,参见 http://www.tornadoweb.org/en/stable/stack_context.html

所以,除了关于多线程和异步哪个更高效的长期争议之外,还有一个关于这两者之间的争论:谁更容易跪了。如果在同步上出现失误,线程更容易出现数据竞争的问题,而回调因为" 堆栈撕裂 stack ripping "问题而非常难于调试。

(题图素材来自:ruth-tay.deviantart.com


via: http://aosabook.org/en/500L/pages/a-web-crawler-with-asyncio-coroutines.html

作者:A. Jesse Jiryu Davis , Guido van Rossum 译者:qingyunha 校对:wxy

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

我这个月在写一些更加长的文章,所以你们可以在几周后再来看看。本月,我想简要地提下我自己一直在玩的一个很棒的 R 库。

我的一个亲密朋友最近在用 R 编写东西。我一直都对它很感兴趣,也一直在试图挤时间,学习更多关于 R 的知识以及可用它做的事情。探索 R 的超强数字处理能力对我而言有些困难,因为我并不如我朋友那样有一个数学头脑。我进展有点慢,但我一直试图将它与我在其他领域的经验联系起来,我甚至开始考虑非常简单的 web 程序。

Shiny 是一个来自 RStudio 的工具包,它让创建 web 程序变得更容易。它能从 R 控制台轻松安装,只需要一行,就可以加载好最新的稳定版本来使用。这里有一个很棒的教程,它可以在前面课程基础上,带着你理解应用架设的概念。 Shiny 的授权是 GPLv3,源代码可以在 GitHub 上获得。

这是一个用 Shiny 写的简单的小 web 程序:

library(shiny)

server <- function(input, output, session) {
    observe({
        myText <- paste("Value above is: ", input$textIn)
        updateTextInput(session, "textOut", value=myText)
    })
}

ui <- basicPage(
    h3("My very own sample application!"),
    textInput("textIn", "Input goes here, please."),
    textInput("textOut", "Results will be printed in this box")
)

shinyApp(ui = ui, server = server)

当你在输入框中输入文字时,它会被复制到输出框中提示语后。这并没有什么奇特的,但它向你展示了一个 Shiny 程序的基本结构。“server”部分允许你处理所有后端工作,如计算、数据库检索或程序需要发生的任何其他操作。“ui”部分定义了接口,它可以根据需要变得简单或复杂。

包括在 Shiny 中的 Bootstrap 有了大量样式和主题,所以在学习了一点后,就能用 R 创建大量功能丰富的 web 程序。使用附加包可以将功能扩展到更高级的 JavaScript 程序、模板等。

有几种方式处理 Shiny 的后端工作。如果你只是在本地运行你的程序,加载库就能做到。对于想要发布到网络上的程序,你可以在 RStudio 的 Shiny 网站上共享它们,运行开源版本的 Shiny 服务器,或通过按年订阅服务从 RStudio 处购买 Shiny Server Pro。

经验丰富的 R 大牛可能已经知道 Shiny 了;它已经存在大约几年了。对于像我这样来自一个完全不同的编程语言,并且希望学习一点 R 的人来说,它是相当有帮助的。


作者简介:

D Ruth Bavousett - D Ruth Bavousett 作为一名系统管理员和软件开发人员已经很长时间了,她的专业生涯开始于 VAX 11/780。在她的职业生涯(迄今为止)中,她花费了大量的时间在满足库的需求上,她自 2008 年以来一直是 Koha 开源库自动化套件的贡献者. Ruth 目前在休斯敦的 cPanel 任 Perl 开发人员,他也作为首席员工效力于双猫公司。


via: https://opensource.com/article/17/1/writing-new-web-apps-shiny

作者:D Ruth Bavousett 译者:geekpi 校对:jasminepeng

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

这是 LXD 2.0 系列介绍文章的第九篇。

  1. LXD 入门
  2. 安装与配置
  3. 你的第一个 LXD 容器
  4. 资源控制
  5. 镜像管理
  6. 远程主机及容器迁移
  7. LXD 中的 Docker
  8. LXD 中的 LXD
  9. 实时迁移
  10. LXD 和 Juju
  11. LXD 和 OpenStack
  12. 调试,及给 LXD 做贡献

介绍

LXD 2.0 中的有一个尽管是实验性质的但非常令人兴奋的功能,那就是支持容器检查点和恢复。

简单地说,检查点/恢复意味着正在运行的容器状态可以被序列化到磁盘,要么可以作为同一主机上的有状态快照,要么放到另一主机上相当于实时迁移。

要求

要使用容器实时迁移和有状态快照,你需要以下条件:

  • 一个非常新的 Linux 内核,4.4 或更高版本。
  • CRIU 2.0,可能需要一些 cherry-pick 的提交,具体取决于你确切的内核配置。
  • 直接在主机上运行 LXD。 不能在容器嵌套下使用这些功能。
  • 对于迁移,目标主机必须至少实现源主机的指令集,目标主机内核必须至少提供与源主机相同的系统调用,并且在源主机上挂载的任何内核文件系统也必须可挂载到目标主机上。

Ubuntu 16.04 LTS 已经提供了所有需要的依赖,在这种情况下,您只需要安装 CRIU 本身:

apt install criu

使用 CRIU

有状态快照

一个普通的快照看上去像这样:

stgraber@dakara:~$ lxc snapshot c1 first
stgraber@dakara:~$ lxc info c1 | grep first
 first (taken at 2016/04/25 19:35 UTC) (stateless)

一个有状态快照看上去像这样:

stgraber@dakara:~$ lxc snapshot c1 second --stateful
stgraber@dakara:~$ lxc info c1 | grep second
 second (taken at 2016/04/25 19:36 UTC) (stateful)

这意味着所有容器运行时状态都被序列化到磁盘并且作为了快照的一部分。可以像你还原无状态快照那样还原一个有状态快照:

stgraber@dakara:~$ lxc restore c1 second
stgraber@dakara:~$

有状态快照的停止/启动

比方说你由于升级内核或者其他类似的维护而需要重启机器。与其等待重启后启动所有的容器,你可以:

stgraber@dakara:~$ lxc stop c1 --stateful

容器状态将会写入到磁盘,会在下次启动时读取。

你甚至可以看到像下面那样的状态:

root@dakara:~# tree /var/lib/lxd/containers/c1/rootfs/state/
/var/lib/lxd/containers/c1/rootfs/state/
├── cgroup.img
├── core-101.img
├── core-102.img
├── core-107.img
├── core-108.img
├── core-109.img
├── core-113.img
├── core-114.img
├── core-122.img
├── core-125.img
├── core-126.img
├── core-127.img
├── core-183.img
├── core-1.img
├── core-245.img
├── core-246.img
├── core-50.img
├── core-52.img
├── core-95.img
├── core-96.img
├── core-97.img
├── core-98.img
├── dump.log
├── eventfd.img
├── eventpoll.img
├── fdinfo-10.img
├── fdinfo-11.img
├── fdinfo-12.img
├── fdinfo-13.img
├── fdinfo-14.img
├── fdinfo-2.img
├── fdinfo-3.img
├── fdinfo-4.img
├── fdinfo-5.img
├── fdinfo-6.img
├── fdinfo-7.img
├── fdinfo-8.img
├── fdinfo-9.img
├── fifo-data.img
├── fifo.img
├── filelocks.img
├── fs-101.img
├── fs-113.img
├── fs-122.img
├── fs-183.img
├── fs-1.img
├── fs-245.img
├── fs-246.img
├── fs-50.img
├── fs-52.img
├── fs-95.img
├── fs-96.img
├── fs-97.img
├── fs-98.img
├── ids-101.img
├── ids-113.img
├── ids-122.img
├── ids-183.img
├── ids-1.img
├── ids-245.img
├── ids-246.img
├── ids-50.img
├── ids-52.img
├── ids-95.img
├── ids-96.img
├── ids-97.img
├── ids-98.img
├── ifaddr-9.img
├── inetsk.img
├── inotify.img
├── inventory.img
├── ip6tables-9.img
├── ipcns-var-10.img
├── iptables-9.img
├── mm-101.img
├── mm-113.img
├── mm-122.img
├── mm-183.img
├── mm-1.img
├── mm-245.img
├── mm-246.img
├── mm-50.img
├── mm-52.img
├── mm-95.img
├── mm-96.img
├── mm-97.img
├── mm-98.img
├── mountpoints-12.img
├── netdev-9.img
├── netlinksk.img
├── netns-9.img
├── netns-ct-9.img
├── netns-exp-9.img
├── packetsk.img
├── pagemap-101.img
├── pagemap-113.img
├── pagemap-122.img
├── pagemap-183.img
├── pagemap-1.img
├── pagemap-245.img
├── pagemap-246.img
├── pagemap-50.img
├── pagemap-52.img
├── pagemap-95.img
├── pagemap-96.img
├── pagemap-97.img
├── pagemap-98.img
├── pages-10.img
├── pages-11.img
├── pages-12.img
├── pages-13.img
├── pages-1.img
├── pages-2.img
├── pages-3.img
├── pages-4.img
├── pages-5.img
├── pages-6.img
├── pages-7.img
├── pages-8.img
├── pages-9.img
├── pipes-data.img
├── pipes.img
├── pstree.img
├── reg-files.img
├── remap-fpath.img
├── route6-9.img
├── route-9.img
├── rule-9.img
├── seccomp.img
├── sigacts-101.img
├── sigacts-113.img
├── sigacts-122.img
├── sigacts-183.img
├── sigacts-1.img
├── sigacts-245.img
├── sigacts-246.img
├── sigacts-50.img
├── sigacts-52.img
├── sigacts-95.img
├── sigacts-96.img
├── sigacts-97.img
├── sigacts-98.img
├── signalfd.img
├── stats-dump
├── timerfd.img
├── tmpfs-dev-104.tar.gz.img
├── tmpfs-dev-109.tar.gz.img
├── tmpfs-dev-110.tar.gz.img
├── tmpfs-dev-112.tar.gz.img
├── tmpfs-dev-114.tar.gz.img
├── tty.info
├── unixsk.img
├── userns-13.img
└── utsns-11.img

0 directories, 154 files

还原容器也很简单:

stgraber@dakara:~$ lxc start c1

实时迁移

实时迁移基本上与上面的有状态快照的停止/启动相同,除了容器目录和配置被移动到另一台机器上。

stgraber@dakara:~$ lxc list c1
+------+---------+-----------------------+----------------------------------------------+------------+-----------+
| NAME |  STATE  |          IPV4         |                     IPV6                     |    TYPE    | SNAPSHOTS |
+------+---------+-----------------------+----------------------------------------------+------------+-----------+
| c1   | RUNNING | 10.178.150.197 (eth0) | 2001:470:b368:4242:216:3eff:fe19:27b0 (eth0) | PERSISTENT | 2         |
+------+---------+-----------------------+----------------------------------------------+------------+-----------+

stgraber@dakara:~$ lxc list s-tollana:
+------+-------+------+------+------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------+-------+------+------+------+-----------+

stgraber@dakara:~$ lxc move c1 s-tollana:

stgraber@dakara:~$ lxc list c1
+------+-------+------+------+------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------+-------+------+------+------+-----------+

stgraber@dakara:~$ lxc list s-tollana:
+------+---------+-----------------------+----------------------------------------------+------------+-----------+
| NAME |  STATE  |          IPV4         |                     IPV6                     |    TYPE    | SNAPSHOTS |
+------+---------+-----------------------+----------------------------------------------+------------+-----------+
| c1   | RUNNING | 10.178.150.197 (eth0) | 2001:470:b368:4242:216:3eff:fe19:27b0 (eth0) | PERSISTENT | 2         |
+------+---------+-----------------------+----------------------------------------------+------------+-----------+

限制

正如我之前说的,容器的检查点/恢复还是非常新的功能,我们还在努力地开发这个功能、修复已知的问题。我们确实需要更多的人来尝试这个功能,并给我们反馈,但我不建议在生产中使用这个功能。

我们跟踪的问题列表在 Launchpad上

我们估计在带有 CRIU 的 Ubuntu 16.04 上带有几个服务的基本的 Ubuntu 容器能够正常工作。然而在更复杂的容器、使用了设备直通、复杂的网络服务或特殊的存储配置下可能会失败。

要是有问题,CRIU 会尽可能地在转储时失败,而不是在恢复时。在这种情况下,源容器将继续运行,快照或迁移将会失败,并生成一个日志文件用于调试。

在极少数情况下,CRIU 无法恢复容器,在这种情况下,源容器仍然存在但将被停止,并且必须手动重新启动。

发送 bug 报告

我们正在跟踪 Launchpad 上关于 CRIU Ubuntu 软件包的检查点/恢复相关的错误。大多数修复 bug 工作是在上游的 CRIU 或 Linux 内核上进行,但是这种方式我们更容易跟踪。

要提交新的 bug 报告,请看这里。

请务必包括:

  • 你运行的命令和显示给你的错误消息
  • lxc info 的输出(*)
  • lxc info <container name>的输出
  • lxc config show -expanded <container name> 的输出
  • dmesg(*)的输出
  • /proc/self/mountinfo 的输出(*)
  • lxc exec <container name> - cat /proc/self/mountinfo 的输出
  • uname -a(*)的输出
  • /var/log/lxd.log(*)的内容
  • /etc/default/lxd-bridge(*)的内容
  • /var/log/lxd/<container name>/ 的 tarball(*)

如果报告迁移错误,而不是状态快照或有状态停止的错误,请将上面所有含有(*)标记的源与目标主机的信息发来。

额外信息

CRIU 的网站在: https://criu.org

LXD 的主站在: https://linuxcontainers.org/lxd

LXD 的 GitHub 仓库: https://github.com/lxc/lxd

LXD 的邮件列表: https://lists.linuxcontainers.org

LXD 的 IRC 频道: #lxcontainers on irc.freenode.net


作者简介:我是 Stéphane Graber。我是 LXC 和 LXD 项目的领导者,目前在加拿大魁北克蒙特利尔的家所在的Canonical 有限公司担任 LXD 的技术主管。


via: https://stgraber.org/2016/04/25/lxd-2-0-live-migration-912/

作者:Stéphane Graber 译者:geekpi 校对:wxy

本文由 LCTT 组织翻译,Linux中国 荣誉推出

remote-atomic-docker

来自 Atomic 项目 的 Atomic 主机是一个轻量级的容器基于的操作系统,它可以运行 Linux 容器。它已被优化为用作云环境的容器运行时系统。例如,它可以托管 Docker 守护进程和容器。有时,你可能需要在该主机上运行 docker 命令,并从其他地方管理服务器。本文介绍如何远程访问 Fedora Atomic 主机(你可以在这里下载到它)上的 Docker 守护进程。整个过程由 Ansible 自动完成 - 在涉及到自动化的一切上,这真是一个伟大的工具!

安全备忘录

由于我们通过网络连接,所以我们使用 TLS 保护 Docker 守护进程。此过程需要客户端证书和服务器证书。OpenSSL 包用于创建用于建立 TLS 连接的证书密钥。这里,Atomic 主机运行守护程序,我们的本地的 Fedora Workstation 充当客户端。

在你按照这些步骤进行之前,请注意,任何在客户端上可以访问 TLS 证书的进程在服务器上具有完全的 root 访问权限。 因此,客户端可以在服务器上做任何它想做的事情。我们需要仅向可信任的特定客户端主机授予证书访问权限。你应该将客户端证书仅复制到完全由你控制的客户端主机。但即使在这种情况下,客户端机器的安全也至关重要。

不过,此方法只是远程访问守护程序的一种方法。编排工具通常提供更安全的控制。下面的简单方法适用于个人实验,可能不适合开放式网络。

获取 Ansible role

Chris Houseknecht 写了一个 Ansible role,它会创造所需的所有证书。这样,你不需要手动运行 openssl 命令了。 这些在 Ansible role 仓库中提供。将它克隆到你当前的工作主机。

$ mkdir docker-remote-access
$ cd docker-remote-access
$ git clone https://github.com/ansible/role-secure-docker-daemon.git

创建配置文件

接下来,你必须创建 Ansible 配置文件、 清单 inventory 剧本 playbook 文件以设置客户端和守护进程。以下说明在 Atomic 主机上创建客户端和服务器证书。然后,获取客户端证书到本地。最后,它们会配置守护进程以及客户端,使它们能彼此交互。

这里是你需要的目录结构。如下所示,创建下面的每个文件。

$ tree docker-remote-access/
docker-remote-access/
├── ansible.cfg
├── inventory
├── remote-access.yml
└── role-secure-docker-daemon

ansible.cfg

$ vim ansible.cfg
[defaults]
inventory=inventory

清单文件(inventory):

$ vim inventory
[daemonhost]
'IP_OF_ATOMIC_HOST' ansible_ssh_private_key_file='PRIVATE_KEY_FILE'

将清单文件(inventory) 中的 IP_OF_ATOMIC_HOST 替换为 Atomic 主机的 IP。将 PRIVATE_KEY_FILE 替换为本地系统上的 SSH 私钥文件的位置。

剧本文件(remote-access.yml):

$ vim remote-access.yml
- name: Docker Client Set up
  hosts: daemonhost
  gather_facts: no
  tasks:
    - name: Make ~/.docker directory for docker certs
      local_action: file path='~/.docker' state='directory'

    - name: Add Environment variables to ~/.bashrc
      local_action: lineinfile dest='~/.bashrc' line='export DOCKER_TLS_VERIFY=1\nexport DOCKER_CERT_PATH=~/.docker/\nexport DOCKER_HOST=tcp://{{ inventory_hostname }}:2376\n' state='present'

    - name: Source ~/.bashrc file
      local_action: shell source ~/.bashrc

- name: Docker Daemon Set up
  hosts: daemonhost
  gather_facts: no
  remote_user: fedora
  become: yes
  become_method: sudo
  become_user: root
  roles:
    - role: role-secure-docker-daemon
      dds_host: "{{ inventory_hostname }}"
      dds_server_cert_path: /etc/docker
      dds_restart_docker: no
  tasks:
    - name: fetch ca.pem from daemon host
      fetch:
        src: /root/.docker/ca.pem
        dest: ~/.docker/
        fail_on_missing: yes
        flat: yes
    - name: fetch cert.pem from daemon host
      fetch:
        src: /root/.docker/cert.pem
        dest: ~/.docker/
        fail_on_missing: yes
        flat: yes
    - name: fetch key.pem from daemon host
      fetch:
        src: /root/.docker/key.pem
        dest: ~/.docker/
        fail_on_missing: yes
        flat: yes
    - name: Remove Environment variable OPTIONS from /etc/sysconfig/docker
      lineinfile:
        dest: /etc/sysconfig/docker
        regexp: '^OPTIONS'
        state: absent

    - name: Modify Environment variable OPTIONS in /etc/sysconfig/docker
      lineinfile:
        dest: /etc/sysconfig/docker
        line: "OPTIONS='--selinux-enabled --log-driver=journald --tlsverify --tlscacert=/etc/docker/ca.pem --tlscert=/etc/docker/server-cert.pem --tlskey=/etc/docker/server-key.pem -H=0.0.0.0:2376 -H=unix:///var/run/docker.sock'"
        state: present

    - name: Remove client certs from daemon host
      file:
        path: /root/.docker
        state: absent

    - name: Reload Docker daemon
      command: systemctl daemon-reload
    - name: Restart Docker daemon
      command: systemctl restart docker.service

访问 Atomic 主机

现在运行 Ansible 剧本:

$ ansible-playbook remote-access.yml

确保 tcp 端口 2376 在你的 Atomic 主机上打开了。如果你在使用 Openstack,请在安全规则中添加 TCP 端口 2376。 如果你使用 AWS,请将其添加到你的安全组。

现在,在你的工作站上作为普通用户运行的 docker 命令与 Atomic 主机的守护进程通信,并在那里执行命令。你不需要手动 ssh 或在 Atomic 主机上发出命令。这可以让你远程、轻松、安全地启动容器化应用程序。

如果你想克隆 Ansible 剧本和配置文件,这里是 git 仓库

docker-daemon


via: https://fedoramagazine.org/use-docker-remotely-atomic-host/

作者:Trishna Guha 译者:geekpi 校对:wxy

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

Eclipse 基金会使用 Tuleap 取代了 Bugzilla。

Tuleap 是一个独特的开源项目管理工具,目前发展势头很好,现在,每个月它会出一个大版本。它还被列在 2015 年五大开源项目管理工具2016 年十一个名列前茅项目管理工具中。

Manuel Vacelet 是开发 Tuleap 项目的 Enalean 公司的联合创始人和 CTO,他说:“Tuleap 是一个完整用于托管软件项目的 GPLv2 平台,它提供了一个集中化的平台,在这里,团队可以找到他们所需的所有工具,追踪他们软件项目的生命周期。他们可以找到项目管理(Scrum、看板、瀑布、混合等等)、源码控制(git 和 svn)和代码审查(pull 请求和 gerrit)、持续集成、问题跟踪、wiki 和文档等的支持。”

在这次采访中,我会和 Manuel 讨论如何开始使用它,以及如何以开源方式管理 Tuleap。

Nitish Tiwari(以下简称 NT): 为什么 Tuleap 项目很重要?

Manuel Vacelet(以下简称 MV): Tuleap 很重要是因为我们坚信一个成功的(软件)项目必须涉及所有利益相关者:开发人员、项目经理、QA、客户和用户。

很久以前,我还是一个 SourceForge 衍生项目的实习生(当时 SourceForge 还是一个自由开源项目),几年后它变成了 Tuleap。 我的第一个贡献是将 PhpWiki 集成到该工具中(不要告诉任何人,代码写的很糟)。

现在,我很高兴作为首席技术官和产品负责人在 Enalean 工作,该公司是 Tuleap 项目的主要贡献公司。

NT:让我们聊聊技术方面。

MV: Tuleap 核心系统是基于 LAMP 并且架构于 CentOS 之上。如今的开发栈是 AngularJS (v1)、REST 后端(PHP)、基于 NodeJS 的实时推送服务器。但如果你想成为一名 Tuleap 全栈开发人员,你还将需要接触 bash、Perl、Python、Docker、Make 等等。

说到技术方面,需要重点强调的 Tuleap 的一个显著特征是它的可扩展性。一个运行在单服务器上的 Tuleap 单一实例、并且没有复杂的 IT 架构,可以处理超过 10000 人的访问。

NT:给我们说下该项目的用户和社区。有谁参与?他们如何使用这个工具?

MV: 用户非常多样化。从使用 Tuleap 跟踪他们的项目进度并管理他们的源代码的小型初创公司,到非常大的公司,如法国电信运营商 Orange,它为超过 17000 用户部署了它,并托管了 5000 个项目。

许多用户依靠 Tuleap 来促进敏捷项目并跟踪其进度。开发人员和客户共享同一个工作区。客户不需要学习如何使用 GitHub,也不需要开发人员做额外的工作,就可以将其工作转换到“客户可访问”平台。

今年, Eclipse 基金会使用 Tuleap 取代了 Bugzilla。

印度电子信息技术部使用 Tuleap 创建了印度政府开放电子政务的开放式协作开发平台。

Tuleap 有许多种不同的使用方式和配置。有些人使用它作为 Drupal 客户门户网站的后端; 它们通过 REST API 插入到 Tuleap 中以管理 bug 和服务请求。

甚至一些建筑师也使用它来管理他们的工作进度和 AutoCAD 文件。

NT:Tuleap 是否做了一些特别的事,使社区更安全,更多样化?

MV: 我们还没有创建“行为准则”;本社区非常平和而欢迎新人,但我们有计划这样做。Tuleap 的开发人员和贡献者来自不同的国家(例如加拿大、突尼斯、法国)。而且 35% 的活跃开发者和贡献者是女性。

NT:由社区提议的 Tuleap 功能的百分比是多少?

MV: 几乎 100% 的功能是由社区驱动的。

这是 Enalean 的关键挑战之一:找到一种商业模式,使我们能以正确的方式做开源软件。对我们来说,“开放核心”模式(其中应用程序的核心是开放的,但有趣和有用的部分是封闭源的)不是正确的方法,因为你最终还是要依赖闭源。因此,我们发明了 OpenRoadmap,这种方式是我们从社区和最终用户那里收集需求,并找公司来为此买单。


作者简介:

Nitish 是一名专业的软件开发人员并对开源有热情。作为一本基于 Linux 的杂志的技术作者,他会尝试新的开源工具。他喜欢阅读和探索任何开源相关的事情。在他的空闲时间,他喜欢读励志书。他目前正在构建 DevUp - 一个让开发人员以真正的方式连接所有工具和拥抱 DevOps 的平台。你可以在 Twitter 上关注他 @tiwari\_nitish。


via: https://opensource.com/article/17/1/interview-Tuleap-project

作者:Nitish Tiwari 译者:geekpi 校对:jamsinepeng

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

作为程序员,我们虽然不像港台剧那样处处用中文混着英文说话,但是在日常的工作学习中还是会大量接触到各种英文单词和缩写。除了一些很普通的英文单词由于某种神秘不可知的原因被屡屡错读之外,计算机世界是变化的如此之快,也经常出现一些新的缩写令非英语母语的中国程序员在懵圈之后随便选个姿势去读。

我猜测,或许是为了将广告野生程序员从这种尴尬之中解脱出来,石墨文档在 GitHub 上做了一个有趣的仓库,专门收集了许多中国程序员容易发音错误的单词。

单词正确发音错误发音
access ✓ ['ækses]✗ [ək'ses]
Angular ✓ ['æŋgjʊlə]✗ ['æŋɡələ; 'æŋdʒʌlə]
AJAX ✓ ['eidʒæks]✗ [ə'dʒʌks]
Apache ✓ [ə'pætʃɪ]✗ [ʌpʌtʃ]
app ✓ [æp]
archive ✓ ['ɑːkaɪv]✗ ['ətʃɪv]
array ✓ [ə'rei]✗ [æ'rei]
avatar ✓ ['ævətɑː]✗ [ə'vʌtɑ]
Azure ✓ ['æʒə]✗ [ˈæzʊʒə]
cache ✓ [kæʃ]✗ [kætʃ]
deque ✓ ['dek]✗ [di'kju]
digest ✓ ['dɑɪdʒɛst]✗ ['dɪgɛst]
Django ✓ [ˈdʒæŋɡoʊ]✗ [diˈdʒæŋɡoʊ]
doc ✓ [dɒk]✗ [daʊk]
facade ✓ [fə'sɑːd]]✗ ['feikeid]
Git ✓ [ɡɪt]✗ [dʒɪt; jɪt]
GNU ✓ [gnu:]
GUI ✓ [ˈɡui]
height ✓ [haɪt]✗ [heɪt]
hidden ✓ ['hɪdn]✗ ['haɪdn]
image ✓ ['ɪmɪdʒ]✗ [ɪ'meɪdʒ]
integer ✓ ['ɪntɪdʒə]✗ [ˈɪntaɪgə]
issue ✓ ['ɪʃuː]✗ [ˈaɪʃuː]
Java ✓ ['dʒɑːvə]✗ ['dʒɑːvɑː]
jpg(jpeg) ✓ ['dʒeɪpeɡ][ˈdʒeɪˈpi:ˈdʒiː]
Linux ✓ ['lɪnəks]✗ [ˈlɪnʌks; ˈlɪnjuːks]
main ✓ [meɪn]✗ [mɪn]
margin ✓ ['mɑːdʒɪn]✗ ['mʌgɪn]
maven ✓ ['meɪvn]✗ ['maːvn]
module ✓ ['mɒdjuːl]✗ ['məʊdl]
nginx✓ Engine X
null ✓ [nʌl]✗ [naʊ]
OS X✓ OS ten
parameter ✓ [pə'ræmɪtə]✗ ['pærəmɪtə]
putty ✓ [ˈpʌti]✗ [ˈpuːti]
query ✓ ['kwɪəri]✗ ['kwaɪri]
Qt ✓ [kjuːt]
resolved ✓ [rɪ'zɒlvd]✗ [rɪ'səʊvd]
retina ✓ ['retɪnə]✗ [ri'tina]
san jose ✓ [sænhəu'zei]✗ [sæn'ju:s]
safari ✓ [sə'fɑːrɪ]✗ [sæfərɪ]
scheme ✓ [skiːm]✗ [s'kæmə]
sudo✓ ['suːduː]
suite ✓ [swiːt]✗ [sjuːt]
typical ✓ ['tɪpɪkl]✗ ['taɪpɪkəl]
Ubuntu ✓ [ʊ'bʊntʊ]✗ [juː'bʊntʊ]
variable ✓ ['veəriəbl]✗ [və'raiəbl]
vue ✓ [v'ju:]✗ [v'ju:i]
width ✓ [wɪdθ]✗ [waɪdθ]
YouTube ✓ ['juː'tjuːb]✗ ['juː'tʊbɪ]

本着简单的原则,又为了避免程序员们出现选择困难症,“正确音标”采用了最接近有道词典音频的英式 DJ 音标,不代表其唯一性。专业在线英语词典请参考知乎链接:在线英语词典哪个比较好?

参考资料

  1. https://www.zhihu.com/question/19739907
  2. https://www.v2ex.com/t/131094
  3. https://www.v2ex.com/t/309350
  4. https://www.v2ex.com/t/63781
  5. https://www.v2ex.com/t/246033
  6. https://www.v2ex.com/t/342087

此外,还有一位 lexrus 同学根据此库的数据,做了一个开源的 iOS 应用,这个应用的名字非常绝妙,叫做“花灰”——请捋直了舌头跟我念:H-U-A-花,H-U-I-灰,花灰。

作者说道:

这个小 App 用 UITableView 陈列了一些中国程序员经常读错的单词,用 AVFoundation 实现美音和英音的朗读,用 Playground 同步单词库。功能异常简陋,实现非常粗暴,仅供 iOS 程序员自行编译,纠正发音使用,懒得提交 App Store。

可从以下视频中略窥一斑,做的还是不错的,不过如果你的 iPhone 没越狱,显然是没法用的——或许你可以去作者的 issues 页面请求他上架?:-D

那么你有几个读错的呢?欢迎发表评论,也欢迎去提交你发现易读错的计算机术语和缩写。

有同学表示,读对还不行,你经常用的一些单词术语,或许并不是你以为的那个意思。不信?你去看看“程序员眼中的英语单词”和它原本的意思差别有多大就知道了~