OpenGL 与 Go 教程（三）实现游戏

Summary: 到目前为止，你应该懂得如何创建网格系统以及创建代表方格中每一个单元的格子阵列。现在可以开始把网格当作游戏面板实现康威生命游戏。

![](/data/attachment/album/201710/17/154427lifqhohyri5frun0.jpg)

* [第一节: Hello, OpenGL](/article-8933-1.html)
* [第二节: 绘制游戏面板](/article-8937-1.html)
* [第三节：实现游戏功能](/article-8969-1.html)

该教程的完整源代码可以从 [GitHub](https://github.com/KyleBanks/conways-gol) 上找到。

欢迎回到《OpenGL 与 Go 教程》！如果你还没有看过 [第一节](/article-8933-1.html) 和 [第二节](https://kylewbanks.com/blog/%5BPart%202:%20Drawing%20the%20Game%20Board%5D(/blog/tutorial-opengl-with-golang-part-2-drawing-the-game-board))，那就要回过头去看一看。

到目前为止，你应该懂得如何创建网格系统以及创建代表方格中每一个单元的格子阵列。现在可以开始把网格当作游戏面板实现<ruby> 康威生命游戏 <rt>  Conway's Game of Life </rt></ruby>。

开始吧！

### 实现康威生命游戏

康威生命游戏的其中一个要点是所有<ruby> 细胞 <rt>  cell </rt></ruby>必须同时基于当前细胞在面板中的状态确定下一个细胞的状态。也就是说如果细胞 `(X=3,Y=4)` 在计算过程中状态发生了改变，那么邻近的细胞 `(X=4,Y=4)` 必须基于 `(X=3,Y=4)` 的状态决定自己的状态变化，而不是基于自己现在的状态。简单的讲，这意味着我们必须遍历细胞，确定下一个细胞的状态，而在绘制之前不改变他们的当前状态，然后在下一次循环中我们将新状态应用到游戏里，依此循环往复。

为了完成这个功能，我们需要在 `cell` 结构体中添加两个布尔型变量：

```
type cell struct {
    drawable uint32

    alive     bool
    aliveNext bool

    x int
    y int
}

```

这里我们添加了 `alive` 和 `aliveNext`，前一个是细胞当前的专题，后一个是经过计算后下一回合的状态。

现在添加两个函数，我们会用它们来确定 cell 的状态：

```
// checkState 函数决定下一次游戏循环时的 cell 状态
func (c *cell) checkState(cells [][]*cell) {
    c.alive = c.aliveNext
    c.aliveNext = c.alive

    liveCount := c.liveNeighbors(cells)
    if c.alive {
        // 1. 当任何一个存活的 cell 的附近少于 2 个存活的 cell 时，该 cell 将会消亡，就像人口过少所导致的结果一样
        if liveCount < 2 {
            c.aliveNext = false
        }

        // 2. 当任何一个存活的 cell 的附近有 2 至 3 个存活的 cell 时，该 cell 在下一代中仍然存活。
        if liveCount == 2 || liveCount == 3 {
            c.aliveNext = true
        }

        // 3. 当任何一个存活的 cell 的附近多于 3 个存活的 cell 时，该 cell 将会消亡，就像人口过多所导致的结果一样
        if liveCount > 3 {
            c.aliveNext = false
        }
    } else {
        // 4. 任何一个消亡的 cell 附近刚好有 3 个存活的 cell，该 cell 会变为存活的状态，就像重生一样。
        if liveCount == 3 {
            c.aliveNext = true
        }
    }
}

// liveNeighbors 函数返回当前 cell 附近存活的 cell 数
func (c *cell) liveNeighbors(cells [][]*cell) int {
    var liveCount int
    add := func(x, y int) {
        // If we're at an edge, check the other side of the board.
        if x == len(cells) {
            x = 0
        } else if x == -1 {
            x = len(cells) - 1
        }
        if y == len(cells[x]) {
            y = 0
        } else if y == -1 {
            y = len(cells[x]) - 1
        }

        if cells[x][y].alive {
            liveCount++
        }
    }

    add(c.x-1, c.y)   // To the left
    add(c.x+1, c.y)   // To the right
    add(c.x, c.y+1)   // up
    add(c.x, c.y-1)   // down
    add(c.x-1, c.y+1) // top-left
    add(c.x+1, c.y+1) // top-right
    add(c.x-1, c.y-1) // bottom-left
    add(c.x+1, c.y-1) // bottom-right

    return liveCount
}

```

在 `checkState` 中我们设置当前状态（`alive`） 等于我们最近迭代结果（`aliveNext`）。接下来我们计数邻居数量，并根据游戏的规则来决定 `aliveNext` 状态。该规则是比较清晰的，而且我们在上面的代码当中也有说明，所以这里不再赘述。

更加值得注意的是 `liveNeighbors` 函数里，我们返回的是当前处于存活（`alive`）状态的细胞的邻居个数。我们定义了一个叫做 `add` 的内嵌函数，它会对 `X` 和 `Y` 坐标做一些重复性的验证。它所做的事情是检查我们传递的数字是否超出了范围——比如说，如果细胞 `(X=0,Y=5)` 想要验证它左边的细胞，它就得验证面板另一边的细胞 `(X=9,Y=5)`，Y 轴与之类似。

在 `add` 内嵌函数后面，我们给当前细胞附近的八个细胞分别调用 `add` 函数，示意如下：

```
[
    [-, -, -],
    [N, N, N],
    [N, C, N],
    [N, N, N],
    [-, -, -]
]

```

在该示意中，每一个叫做 N 的细胞是 C 的邻居。

现在是我们的 `main` 函数，这里我们执行核心游戏循环，调用每个细胞的 `checkState` 函数进行绘制：

```
func main() {
    ...

    for !window.ShouldClose() {
        for x := range cells {
            for _, c := range cells[x] {
                c.checkState(cells)
            }
        }

        draw(cells, window, program)
    }
}

```

现在我们的游戏逻辑全都设置好了，我们需要修改细胞绘制函数来跳过绘制不存活的细胞：

```
func (c *cell) draw() {
    if !c.alive {
            return
    }

    gl.BindVertexArray(c.drawable)
    gl.DrawArrays(gl.TRIANGLES, 0, int32(len(square)/3))
}

```

如果我们现在运行这个游戏，你将看到一个纯黑的屏幕，而不是我们辛苦工作后应该看到生命模拟。为什么呢？其实这正是模拟在工作。因为我们没有活着的细胞，所以就一个都不会绘制出来。

现在完善这个函数。回到 `makeCells` 函数，我们用 `0.0` 到 `1.0` 之间的一个随机数来设置游戏的初始状态。我们会定义一个大小为 `0.15` 的常量阈值，也就是说每个细胞都有 15% 的几率处于存活状态。

```
import (
    "math/rand"
    "time"
    ...
)

const (
    ...

    threshold = 0.15
)

func makeCells() [][]*cell {
    rand.Seed(time.Now().UnixNano())

    cells := make([][]*cell, rows, rows)
    for x := 0; x < rows; x++ {
        for y := 0; y < columns; y++ {
            c := newCell(x, y)

            c.alive = rand.Float64() < threshold
            c.aliveNext = c.alive

            cells[x] = append(cells[x], c)
        }
    }

    return cells
}

```

我们首先增加两个引入：随机（`math/rand`）和时间（`time`），并定义我们的常量阈值。然后在 `makeCells` 中我们使用当前时间作为随机种子，给每个游戏一个独特的起始状态。你也可也指定一个特定的种子值，来始终得到一个相同的游戏，这在你想重放某个有趣的模拟时很有用。

接下来在循环中，在用 `newCell` 函数创造一个新的细胞时，我们根据随机浮点数的大小设置它的存活状态，随机数在 `0.0` 到 `1.0` 之间，如果比阈值（`0.15`）小，就是存活状态。再次强调，这意味着每个细胞在开始时都有 15% 的几率是存活的。你可以修改数值大小，增加或者减少当前游戏中存活的细胞。我们还把 `aliveNext` 设成 `alive` 状态，否则在第一次迭代之后我们会发现一大片细胞消亡了，这是因为 `aliveNext` 将永远是 `false`。

现在继续运行它，你很有可能看到细胞们一闪而过，但你却无法理解这是为什么。原因可能在于你的电脑太快了，在你能够看清楚之前就运行了（甚至完成了）模拟过程。

让我们降低游戏速度，在主循环中引入一个帧率（FPS）限制：

```
const (
    ...

    fps = 2
)

func main() {
    ...

    for !window.ShouldClose() {
        t := time.Now()

        for x := range cells {
            for _, c := range cells[x] {
                c.checkState(cells)
            }
        }

        if err := draw(prog, window, cells); err != nil {
            panic(err)
        }

        time.Sleep(time.Second/time.Duration(fps) - time.Since(t))
    }
}

```

现在你能给看出一些图案了，尽管它变换的很慢。把 FPS 加到 10，把方格的尺寸加到 100x100，你就能看到更真实的模拟：

```
const (
    ...

    rows = 100
    columns = 100

    fps = 10

    ...
)

```

![ “Conway's Game of Life” - 示例游戏](/data/attachment/album/201710/17/154537s5hgt5z95e5gc55w.gif)

试着修改常量，看看它们是怎么影响模拟过程的 —— 这是你用 Go 语言写的第一个 OpenGL 程序，很酷吧？

### 进阶内容？

这是《OpenGL 与 Go 教程》的最后一节，但是这不意味着到此而止。这里有些新的挑战，能够增进你对 OpenGL （以及 Go）的理解。

1. 给每个细胞一种不同的颜色。
2. 让用户能够通过命令行参数指定格子尺寸、帧率、种子和阈值。在 GitHub 上的 [github.com/KyleBanks/conways-gol](https://github.com/KyleBanks/conways-gol) 里你可以看到一个已经实现的程序。
3. 把格子的形状变成其它更有意思的，比如六边形。
4. 用颜色表示细胞的状态 —— 比如，在第一帧把存活状态的格子设成绿色，如果它们存活了超过三帧的时间，就变成黄色。
5. 如果模拟过程结束了，就自动关闭窗口，也就是说所有细胞都消亡了，或者是最后两帧里没有格子的状态有改变。
6. 将着色器源代码放到单独的文件中，而不是把它们用字符串的形式放在 Go 的源代码中。

### 总结

希望这篇教程对想要入门 OpenGL （或者是 Go）的人有所帮助！这很有趣，因此我也希望理解学习它也很有趣。

正如我所说的，OpenGL 可能是非常恐怖的，但只要你开始着手了就不会太差。你只用制定一个个可达成的小目标，然后享受每一次成功，因为尽管 OpenGL 不会总像它看上去的那么难，但也肯定有些难懂的东西。我发现，当遇到一个难于理解用 go-gl 生成的代码的 OpenGL 问题时，你总是可以参考一下在网上更流行的当作教程的 C 语言代码，这很有用。通常 C 语言和 Go 语言的唯一区别是在 Go 中，gl 函数的前缀是 `gl.` 而不是 `gl`，常量的前缀是 `gl` 而不是 `GL_`。这可以极大地增加了你的绘制知识！

该教程的完整源代码可从 [GitHub](https://github.com/KyleBanks/conways-gol) 上获得。

### 回顾

这是 main.go 文件最终的内容：

```
package main

import (
    "fmt"
    "log"
    "math/rand"
    "runtime"
    "strings"
    "time"

    "github.com/go-gl/gl/v4.1-core/gl" // OR: github.com/go-gl/gl/v2.1/gl
    "github.com/go-gl/glfw/v3.2/glfw"
)

const (
    width  = 500
    height = 500

    vertexShaderSource = `
        #version 410
        in vec3 vp;
        void main() {
            gl_Position = vec4(vp, 1.0);
        }
    ` + "\x00"

    fragmentShaderSource = `
        #version 410
        out vec4 frag_colour;
        void main() {
            frag_colour = vec4(1, 1, 1, 1.0);
        }
    ` + "\x00"

    rows    = 100
    columns = 100

    threshold = 0.15
    fps       = 10
)

var (
    square = []float32{
        -0.5, 0.5, 0,
        -0.5, -0.5, 0,
        0.5, -0.5, 0,

        -0.5, 0.5, 0,
        0.5, 0.5, 0,
        0.5, -0.5, 0,
    }
)

type cell struct {
    drawable uint32

    alive     bool
    aliveNext bool

    x int
    y int
}

func main() {
    runtime.LockOSThread()

    window := initGlfw()
    defer glfw.Terminate()
    program := initOpenGL()

    cells := makeCells()
    for !window.ShouldClose() {
        t := time.Now()

        for x := range cells {
            for _, c := range cells[x] {
                c.checkState(cells)
            }
        }

        draw(cells, window, program)

        time.Sleep(time.Second/time.Duration(fps) - time.Since(t))
    }
}

func draw(cells [][]*cell, window *glfw.Window, program uint32) {
    gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
    gl.UseProgram(program)

    for x := range cells {
        for _, c := range cells[x] {
            c.draw()
        }
    }

    glfw.PollEvents()
    window.SwapBuffers()
}

func makeCells() [][]*cell {
    rand.Seed(time.Now().UnixNano())

    cells := make([][]*cell, rows, rows)
    for x := 0; x < rows; x++ {
        for y := 0; y < columns; y++ {
            c := newCell(x, y)

            c.alive = rand.Float64() < threshold
            c.aliveNext = c.alive

            cells[x] = append(cells[x], c)
        }
    }

    return cells
}
func newCell(x, y int) *cell {
    points := make([]float32, len(square), len(square))
    copy(points, square)

    for i := 0; i < len(points); i++ {
        var position float32
        var size float32
        switch i % 3 {
        case 0:
            size = 1.0 / float32(columns)
            position = float32(x) * size
        case 1:
            size = 1.0 / float32(rows)
            position = float32(y) * size
        default:
            continue
        }

        if points[i] < 0 {
            points[i] = (position * 2) - 1
        } else {
            points[i] = ((position + size) * 2) - 1
        }
    }

    return &cell{
        drawable: makeVao(points),

        x: x,
        y: y,
    }
}

func (c *cell) draw() {
    if !c.alive {
        return
    }

    gl.BindVertexArray(c.drawable)
    gl.DrawArrays(gl.TRIANGLES, 0, int32(len(square)/3))
}

// checkState 函数决定下一次游戏循环时的 cell 状态
func (c *cell) checkState(cells [][]*cell) {
    c.alive = c.aliveNext
    c.aliveNext = c.alive

    liveCount := c.liveNeighbors(cells)
    if c.alive {
        // 1. 当任何一个存活的 cell 的附近少于 2 个存活的 cell 时，该 cell 将会消亡，就像人口过少所导致的结果一样
        if liveCount < 2 {
            c.aliveNext = false
        }

        // 2. 当任何一个存活的 cell 的附近有 2 至 3 个存活的 cell 时，该 cell 在下一代中仍然存活。
        if liveCount == 2 || liveCount == 3 {
            c.aliveNext = true
        }

        // 3. 当任何一个存活的 cell 的附近多于 3 个存活的 cell 时，该 cell 将会消亡，就像人口过多所导致的结果一样
        if liveCount > 3 {
            c.aliveNext = false
        }
    } else {
        // 4. 任何一个消亡的 cell 附近刚好有 3 个存活的 cell，该 cell 会变为存活的状态，就像重生一样。
        if liveCount == 3 {
            c.aliveNext = true
        }
    }
}

// liveNeighbors 函数返回当前 cell 附近存活的 cell 数
func (c *cell) liveNeighbors(cells [][]*cell) int {
    var liveCount int
    add := func(x, y int) {
        // If we're at an edge, check the other side of the board.
        if x == len(cells) {
            x = 0
        } else if x == -1 {
            x = len(cells) - 1
        }
        if y == len(cells[x]) {
            y = 0
        } else if y == -1 {
            y = len(cells[x]) - 1
        }

        if cells[x][y].alive {
            liveCount++
        }
    }

    add(c.x-1, c.y)   // To the left
    add(c.x+1, c.y)   // To the right
    add(c.x, c.y+1)   // up
    add(c.x, c.y-1)   // down
    add(c.x-1, c.y+1) // top-left
    add(c.x+1, c.y+1) // top-right
    add(c.x-1, c.y-1) // bottom-left
    add(c.x+1, c.y-1) // bottom-right

    return liveCount
}

// initGlfw 初始化 glfw，返回一个可用的 Window
func initGlfw() *glfw.Window {
    if err := glfw.Init(); err != nil {
        panic(err)
    }
    glfw.WindowHint(glfw.Resizable, glfw.False)
    glfw.WindowHint(glfw.ContextVersionMajor, 4)
    glfw.WindowHint(glfw.ContextVersionMinor, 1)
    glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
    glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)

    window, err := glfw.CreateWindow(width, height, "Conway's Game of Life", nil, nil)
    if err != nil {
        panic(err)
    }
    window.MakeContextCurrent()

    return window
}

// initOpenGL 初始化 OpenGL 并返回一个已经编译好的着色器程序
func initOpenGL() uint32 {
    if err := gl.Init(); err != nil {
        panic(err)
    }
    version := gl.GoStr(gl.GetString(gl.VERSION))
    log.Println("OpenGL version", version)

    vertexShader, err := compileShader(vertexShaderSource, gl.VERTEX_SHADER)
    if err != nil {
        panic(err)
    }

    fragmentShader, err := compileShader(fragmentShaderSource, gl.FRAGMENT_SHADER)
    if err != nil {
        panic(err)
    }

    prog := gl.CreateProgram()
    gl.AttachShader(prog, vertexShader)
    gl.AttachShader(prog, fragmentShader)
    gl.LinkProgram(prog)
    return prog
}

// makeVao 初始化并从提供的点里面返回一个顶点数组
func makeVao(points []float32) uint32 {
    var vbo uint32
    gl.GenBuffers(1, &vbo)
    gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
    gl.BufferData(gl.ARRAY_BUFFER, 4*len(points), gl.Ptr(points), gl.STATIC_DRAW)

    var vao uint32
    gl.GenVertexArrays(1, &vao)
    gl.BindVertexArray(vao)
    gl.EnableVertexAttribArray(0)
    gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
    gl.VertexAttribPointer(0, 3, gl.FLOAT, false, 0, nil)

    return vao
}

func compileShader(source string, shaderType uint32) (uint32, error) {
    shader := gl.CreateShader(shaderType)

    csources, free := gl.Strs(source)
    gl.ShaderSource(shader, 1, csources, nil)
    free()
    gl.CompileShader(shader)

    var status int32
    gl.GetShaderiv(shader, gl.COMPILE_STATUS, &status)
    if status == gl.FALSE {
        var logLength int32
        gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, &logLength)

        log := strings.Repeat("\x00", int(logLength+1))
        gl.GetShaderInfoLog(shader, logLength, nil, gl.Str(log))

        return 0, fmt.Errorf("failed to compile %v: %v", source, log)
    }

    return shader, nil
}

```

请在 Twitter [@kylewbanks](https://twitter.com/kylewbanks) 告诉我这篇文章对你是否有帮助，或者在 Twitter 下方关注我以便及时获取最新文章！

---

via: <https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-3-implementing-the-game>

作者：[kylewbanks](https://twitter.com/kylewbanks) 译者：[GitFuture](https://github.com/GitFuture) 校对：[wxy](https://github.com/wxy)

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


Up：在几秒钟内部署无服务器应用程序

Summary: 为什么关注无服务器（serverless）？对于初学者来说，它可以节省成本，因为你可以按需付费，且只为你使用的付费。无服务器方式是自愈的，因为每个请求被隔离并被视作“无状态的”。最后，它可以无限轻松地扩展 —— 没有机器或集群要管理。部署你的代码就行了。

![](/data/attachment/album/201710/17/222344ghhnihphli9phphk.png)

去年，我[为 Up 规划了一份蓝图](https://medium.com/@tjholowaychuk/blueprints-for-up-1-5f8197179275)，其中描述了如何以最小的成本在 AWS 上为大多数构建块创建一个很棒的无服务器环境。这篇文章则是讨论了 [Up](https://github.com/apex/up) 的初始 alpha 版本。

为什么关注<ruby> 无服务器 <rt>  serverless </rt></ruby>？对于初学者来说，它可以节省成本，因为你可以按需付费，且只为你使用的付费。无服务器方式是自愈的，因为每个请求被隔离并被视作“无状态的”。最后，它可以无限轻松地扩展 —— 没有机器或集群要管理。部署你的代码就行了。

大约一个月前，我决定开始在 [apex/up](https://github.com/apex/up) 上开发它，并为动态 SVG 版本的 GitHub 用户投票功能写了第一个小型无服务器示例程序 [tj/gh-polls](https://github.com/tj/gh-polls)。它运行良好，成本低于每月 1 美元即可为数百万次投票服务，因此我会继续这个项目，看看我是否可以提供开源版本及商业的变体版本。

其长期的目标是提供“你自己的 Heroku” 的版本，支持许多平台。虽然平台即服务（PaaS）并不新鲜，但无服务器生态系统正在使这种方案日益萎缩。据说，AWS 和其他的供应商经常由于他们提供的灵活性而被人诟病用户体验。Up 将复杂性抽象出来，同时为你提供一个几乎无需运维的解决方案。

### 安装

你可以使用以下命令安装 Up，查看这篇[临时文档](https://github.com/apex/up/tree/master/docs)开始使用。或者如果你使用安装脚本，请下载[二进制版本](https://github.com/apex/up/releases)。（请记住，这个项目还在早期。）

```
curl -sfL https://raw.githubusercontent.com/apex/up/master/install.sh | sh

```

只需运行以下命令随时升级到最新版本：

```
up upgrade

```

你也可以通过 NPM 进行安装：

```
npm install -g up

```

### 功能

这个早期 alpha 版本提供什么功能？让我们来看看！请记住，Up 不是托管服务，因此你需要一个 AWS 帐户和 [AWS 凭证](https://github.com/apex/up/blob/master/docs/aws-credentials.md)。如果你对 AWS 不熟悉，你可能需要先停下来，直到熟悉流程。

我遇到的第一个问题是：up(1) 与 [apex(1)](https://github.com/apex/apex) 有何不同？Apex 专注于部署功能，用于管道和事件处理，而 Up 则侧重于应用程序、API 和静态站点，也就是单个可部署单元。Apex 不为你提供 API 网关、SSL 证书或 DNS，也不提供 URL 重写，脚本注入等。

#### 单命令无服务器应用程序

Up 可以让你使用单条命令部署应用程序、API 和静态站点。要创建一个应用程序，你只需要一个文件，在 Node.js 的情况下，`./app.js` 监听由 Up 提供的 `PORT'。请注意，如果你使用的是`package.json`，则会检测并使用`start`和`build` 脚本。

```
const http = require('http')
const { PORT = 3000 } = process.env

```

```
http.createServer((req, res) => {
  res.end('Hello World\n')
}).listen(PORT)

```

额外的[运行时环境](https://github.com/apex/up/blob/master/docs/runtimes.md)也支持开箱可用，例如用于 Golang 的 “main.go”，所以你可以在几秒钟内部署 Golang、Python、Crystal 或 Node.js 应用程序。

```
package main

import (
 "fmt"
 "log"
 "net/http"
 "os"
)

func main() {
 addr := ":" + os.Getenv("PORT")
 http.HandleFunc("/", hello)
 log.Fatal(http.ListenAndServe(addr, nil))
}

func hello(w http.ResponseWriter, r *http.Request) {
 fmt.Fprintln(w, "Hello World from Go")
}

```

要部署应用程序输入 `up` 来创建所需的资源并部署应用程序本身。这里没有模糊不清的地方，一旦它说“完成”了那就完成了，该应用程序立即可用 —— 没有远程构建过程。

![](/data/attachment/album/201710/17/222344sk5uk786q99646q1.png)

后续的部署将会更快，因为栈已被配置：

![](/data/attachment/album/201710/17/222345vai0n1r17n80orhl.png)

使用 `up url --open` 测试你的程序，以在浏览器中浏览它，`up url --copy` 可以将 URL 保存到剪贴板，或者可以尝试使用 curl：

```
curl `up url`
Hello World

```

要删除应用程序及其资源，只需输入 `up stack delete`：

![](/data/attachment/album/201710/17/222345tuj237q2paq0ubph.png)

例如，使用 `up staging` 或 `up production` 和 `up url --open production` 部署到预发布或生产环境。请注意，自定义域名尚不可用，[它们将很快可用](https://github.com/apex/up/issues/166)。之后，你还可以将版本“推广”到其他环境。

#### 反向代理

Up 的一个独特的功能是，它不仅仅是简单地部署代码，它将一个 Golang 反向代理放在应用程序的前面。这提供了许多功能，如 URL 重写、重定向、脚本注入等等，我们将在后面进一步介绍。

#### 基础设施即代码

在配置方面，Up 遵循现代最佳实践，因此对基础设施的更改都可以在部署之前预览，并且 IAM 策略的使用还可以限制开发人员访问以防止事故发生。一个附带的好处是它有助于自动记录你的基础设施。

以下是使用 Let's Encrypt 通过 AWS ACM 配置一些（虚拟）DNS 记录和免费 SSL 证书的示例。

```
{
  "name": "app",
  "dns": {
    "myapp.com": [
      {
        "name": "myapp.com",
        "type": "A",
        "ttl": 300,
        "value": ["35.161.83.243"]
      },
      {
        "name": "blog.myapp.com",
        "type": "CNAME",
        "ttl": 300,
        "value": ["34.209.172.67"]
      },
      {
        "name": "api.myapp.com",
        "type": "A",
        "ttl": 300,
        "value": ["54.187.185.18"]
      }
    ]
  },
  "certs": [
    {
      "domains": ["myapp.com", "*.myapp.com"]
    }
  ]
}

```

当你首次通过 `up` 部署应用程序时，需要所有的权限，它为你创建 API 网关、Lambda 函数、ACM 证书、Route53 DNS 记录等。

[ChangeSets](https://github.com/apex/up/issues/115) 尚未实现，但你能使用 `up stack plan` 预览进一步的更改，并使用 `up stack apply` 提交，这与 Terraform 非常相似。

详细信息请参阅[配置文档](https://github.com/apex/up/blob/master/docs/configuration.md)。

#### 全球部署

`regions` 数组可以指定应用程序的目标区域。例如，如果你只对单个地区感兴趣，请使用：

```
{
  "regions": ["us-west-2"]
}

```

如果你的客户集中在北美，你可能需要使用美国和加拿大所有地区：

```
{
  "regions": ["us-*", "ca-*"]
}

```

最后，你可以使用 AWS 目前支持的所有 14 个地区：

```
{
  "regions": ["*"]
}

```

多区域支持仍然是一个正在进行的工作，因为需要一些新的 AWS 功能来将它们结合在一起。

#### 静态文件服务

Up 默认支持静态文件服务，并带有 HTTP 缓存支持，因此你可以在应用程序前使用 CloudFront 或任何其他 CDN 来大大减少延迟。

当 `type` 为 `static` 时，默认情况下的工作目录是 `.`，但是你也可以提供一个 `static.dir`：

```
{
  "name": "app",
  "type": "static",
  "static": {
    "dir": "public"
  }
}

```

#### 构建钩子

构建钩子允许你在部署或执行其他操作时定义自定义操作。一个常见的例子是使用 Webpack 或 Browserify 捆绑 Node.js 应用程序，这大大减少了文件大小，因为 node 模块是*很大*的。

```
{
  "name": "app",
  "hooks": {
    "build": "browserify --node server.js > app.js",
    "clean": "rm app.js"
  }
}

```

#### 脚本和样式表插入

Up 允许你插入脚本和样式，无论是内联方式或声明路径。它甚至支持一些“罐头”脚本，用于 Google Analytics（分析）和 [Segment](https://segment.com/)，只需复制并粘贴你的写入密钥即可。

```
{
  "name": "site",
  "type": "static",
  "inject": {
    "head": [
      {
        "type": "segment",
        "value": "API_KEY"
      },
      {
        "type": "inline style",
        "file": "/css/primer.css"
      }
    ],
    "body": [
      {
        "type": "script",
        "value": "/app.js"
      }
    ]
  }
}

```

#### 重写和重定向

Up 通过 `redirects` 对象支持重定向和 URL 重写，该对象将路径模式映射到新位置。如果省略 `status` 参数（或值为 200），那么它是重写，否则是重定向。

```
{
  "name": "app",
  "type": "static",
  "redirects": {
    "/blog": {
      "location": "https://blog.apex.sh/",
      "status": 301
    },
    "/docs/:section/guides/:guide": {
      "location": "/help/:section/:guide",
      "status": 302
    },
    "/store/*": {
      "location": "/shop/:splat"
    }
  }
}

```

用于重写的常见情况是 SPA（单页面应用程序），你希望不管路径如何都提供 `index.html`，当然除非文件存在。

```
{
  "name": "app",
  "type": "static",
  "redirects": {
    "/*": {
      "location": "/",
      "status": 200
    }
  }
}

```

如果要强制实施该规则，无论文件是否存在，只需添加 `"force": true` 。

#### 环境变量

密码将在下一个版本中有，但是现在支持纯文本环境变量：

```
{
  "name": "api",
  "environment": {
    "API_FEATURE_FOO": "1",
    "API_FEATURE_BAR": "0"
  }
}

```

#### CORS 支持

[CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS) 支持允许你指定哪些（如果有的话）域可以从浏览器访问你的 API。如果你希望允许任何网站访问你的 API，只需启用它：

```
{
  "cors": {
    "enable": true
  }
}

```

你还可以自定义访问，例如仅将 API 访问限制为你的前端或 SPA。

```
{
  "cors": {
    "allowed_origins": ["https://myapp.com"],
    "allowed_methods": ["HEAD", "GET", "POST", "PUT", "DELETE"],
    "allowed_headers": ["Content-Type", "Authorization"]
  }
}

```

#### 日志

对于 $0.5/GB 的低价格，你可以使用 CloudWatch 日志进行结构化日志查询和跟踪。Up 实现了一种用于改进 CloudWatch 提供的自定义[查询语言](https://github.com/apex/up/blob/master/internal/logs/parser/grammar.peg)，专门用于查询结构化 JSON 日志。

![](/data/attachment/album/201710/17/222431kvicb1bzdcitbjct.png)

你可以查询现有日志：

```
up logs

```

跟踪在线日志：

```
up logs -f

```

或者对其中任一个进行过滤，例如只显示耗时超过 5 毫秒的 200 个 GET/HEAD 请求：

```
up logs 'method in ("GET", "HEAD") status = 200 duration >= 5'

```

![](/data/attachment/album/201710/17/222503akf3aga3rmggealk.png)

查询语言是非常灵活的，这里有更多来自于 `up help logs` 的例子

```
### 显示过去 5 分钟的日志
$ up logs

### 显示过去 30 分钟的日志
$ up logs -s 30m

### 显示过去 5 小时的日志
$ up logs -s 5h

### 实时显示日志
$ up logs -f

### 显示错误日志
$ up logs error

### 显示错误和致命错误日志
$ up logs 'error or fatal'

### 显示非 info 日志
$ up logs 'not info'

### 显示特定消息的日志
$ up logs 'message = "user login"'

### 显示超时 150ms 的 200 响应
$ up logs 'status = 200 duration > 150'

### 显示 4xx 和 5xx 响应
$ up logs 'status >= 400'

### 显示用户邮件包含 @apex.sh 的日志
$ up logs 'user.email contains "@apex.sh"'

### 显示用户邮件以 @apex.sh 结尾的日志
$ up logs 'user.email = "*@apex.sh"'

### 显示用户邮件以 tj@ 开始的日志
$ up logs 'user.email = "tj@*"'

### 显示路径 /tobi 和 /loki 下的错误日志
$ up logs 'error and (path = "/tobi" or path = "/loki")'

### 和上面一样，用 in 显示
$ up logs 'error and path in ("/tobi", "/loki")'

### 更复杂的查询方式
$ up logs 'method in ("POST", "PUT") ip = "207.*" status = 200 duration >= 50'

### 将 JSON 格式的错误日志发送给 jq 工具
$ up logs error | jq

```

请注意，`and` 关键字是暗含的，虽然你也可以使用它。

#### 冷启动时间

这是 AWS Lambda 平台的特性，但冷启动时间通常远远低于 1 秒，在未来，我计划提供一个选项来保持它们在线。

#### 配置验证

`up config` 命令输出解析后的配置，有默认值和推断的运行时设置 - 它也起到验证配置的双重目的，因为任何错误都会导致退出状态 > 0。

#### 崩溃恢复

使用 Up 作为反向代理的另一个好处是执行崩溃恢复 —— 在崩溃后重新启动服务器，并在将错误的响应发送给客户端之前重新尝试该请求。

例如，假设你的 Node.js 程序由于间歇性数据库问题而导致未捕获的异常崩溃，Up 可以在响应客户端之前重试该请求。之后这个行为会更加可定制。

#### 适合持续集成

很难说这是一个功能，但是感谢 Golang 相对较小和独立的二进制文件，你可以在一两秒中在 CI 中安装 Up。

#### HTTP/2

Up 通过 API 网关支持 HTTP/2，对于服务很多资源的应用和站点可以减少延迟。我将来会对许多平台进行更全面的测试，但是 Up 的延迟已经很好了：

![](/data/attachment/album/201710/17/222503as3skc93q3a9usup.jpg)

#### 错误页面

Up 提供了一个默认错误页面，如果你要提供支持电子邮件或调整颜色，你可以使用 `error_pages` 自定义。

```
{
  "name": "site",
  "type": "static",
  "error_pages": {
    "variables": {
      "support_email": "support@apex.sh",
      "color": "#228ae6"
    }
  }
}

```

默认情况下，它看上去像这样：

![](/data/attachment/album/201710/17/222539pb96vr7xvex1olvv.png)

如果你想提供自定义模板，你可以创建以下一个或多个文件。特定文件优先。

* `error.html` – 匹配任何 4xx 或 5xx
* `5xx.html` – 匹配任何 5xx 错误
* `4xx.html` – 匹配任何 4xx 错误
* `CODE.html` – 匹配一个特定的代码，如 404.html

查看[文档](https://github.com/apex/up/blob/master/docs/configuration.md#error-pages)阅读更多有关模板的信息。

### 伸缩和成本

你已经做了这么多，但是 Up 怎么伸缩？目前，API 网关和 AWS 是目标平台，因此你无需进行任何更改即可扩展，只需部署代码即可完成。你只需按需支付实际使用的数量并且无需人工干预。

AWS 每月免费提供 1,000,000 个请求，但你可以使用 [http://serverlesscalc.com](http://serverlesscalc.com/) 来插入预期流量。在未来 Up 将提供更多的平台，所以如果一个成本过高，你可以迁移到另一个！

### 未来

目前为止就这样了！它可能看起来不是很多，但它已经超过 10,000 行代码，并且我刚刚开始开发。看看这个问题队列，假设项目可持续发展，看看未来会有什么期待。

如果你发现这个免费版本有用，请考虑在 [OpenCollective](https://opencollective.com/apex-up) 上捐赠我，因为我没有任何工作。我将在短期内开发早期专业版，对早期用户有年费优惠。专业或企业版也将提供源码，因此可以进行内部修复和自定义。

---

via: <https://medium.freecodecamp.org/up-b3db1ca930ee>

作者：[TJ Holowaychuk](https://medium.freecodecamp.org/@tjholowaychuk?source=post_header_lockup) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

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


违反 GPL 究竟算不算合同违约，法官表示需要仔细审理……

Summary: 正在进行中的 GPL 合规案件 Artifex v. Hancom 近日产生了新的裁决，一项旨在提请简易判决的动议被法院驳回。FSF 表示，随着简易判决动议被否，本案继续向前推进，并将变得更为有趣。

![](/data/attachment/album/201710/17/231500t3ssjj1c3jr3ztkm.jpeg)

据自由软件基金会（FSF）报道，正在进行中的 GPL 合规案件 Artifex v. Hancom 近日产生了新的裁决，一项旨在提请简易判决的动议被法院驳回。

该案涉及一款遵循 GPL v3 及之后版本许可证的名为 Ghostscript 的软件，其来源于 Artifex 公司一个用于处理 PostScript、PDF 以及打印机的项目（GNU Ghostscript 是该项目的一个单独版本，并未涉及或牵连该案）。

FSF在之前的报道中[表示](https://www.fsf.org/blogs/licensing/motion-to-dismiss-denied-in-recent-gnu-gpl-case)：

>
> 在该诉讼中，基于 Hancom 公司对 Ghostscript 的使用情况，Artifex 公司提出了两项指控：（1）侵犯版权；以及（2）违反基于GPL许可证的合同。……虽然违反自由软件许可证将导致侵犯版权已经成为老生常谈，但是，违反 GPL 等许可证是否可以被视为违反合同，长期以来一直是许可专家们的讨论话题。
>
>
>

在之前的裁决中，本案的法官驳回了 Hancom 的撤案动议，让案件得以继续进行。现在诉讼已经到了下一步，其中包括一个主张 GPL 构成合同的<ruby> <a href="https://docs.justia.com/cases/federal/district-courts/california/candce/3:2016cv06982/305835/54">  简易判决动议 </a> <rp>  （ </rp> <rt>  Motion for summary judgment </rt> <rp>  ） </rp></ruby>，但是该动议日前也被法官驳回。在撤案动议中，法院假定所涉指控真实存在，并对所述指控是否真正提出了有效的合法权利主张做出了裁决。在简易判决动议中，法院会被要求去审视无可争议的事实，并确定该案件的结果是否如此明显，以至于不需要经过全面审判。类似动议虽然是例行的，但是如果通过了简易判决，意味着本案中合同理论的复苏问题依然存在（LCTT 译注：指是否被视为违法合同）。

Hancom 公司提出了一些否认 GPL 许可证构成合同的证据，其中一个特别有意思。Hancom 公司认为，如果 GPL 许可证构成合同的说法被接受，则只能考虑他们在初始违规日之前的损害赔偿。他们认为，由于违规造成了许可终止，合同也因此结束。法官指出：

>
> GPL 许可证表明，在使用 Ghostscript 传播软件的权利被终止后，被告的义务依然存在，……因为每次传递“<ruby> 被覆盖作品 <rp>  （ </rp> <rt>  covered work </rt> <rp>  ） </rp></ruby>”，都需要提供源代码或承诺提供源代码，每次被告分发使用 Ghostscript 的产品，可以说随之而来的义务就是提供源代码或承诺提供源代码。
>
>
>

法官还发现，在这一点上没有足够的证据来对该问题做出裁决。所以，FSF 表示，目前还无法进行过多的解读。但是，法官对于违规行为之后 GPL 在什么条件下继续存在的看法，是随着本案进行，该问题如何发展的重要线索。虽然，为了保护用户自由， GPL 许可证并不需要作为合同进行维护（它作为版权许可已经成功运行了几十年）。不过，类似的程序性裁决显示了有更多的证据表明，GPL 许可证作为合同的主张在法院不容易站住脚或者容易被击败的说法并非毫无根据的造谣。

FSF 表示，随着简易判决动议被否，本案继续向前推进，并将变得更为有趣。

---

译者简介：薛亮，集慧智佳知识产权咨询公司高级咨询师，擅长专利检索、专利分析、竞争对手跟踪、FTO 分析、开源软件知识产权风险分析，致力于为互联网企业、高科技公司提供知识产权咨询服务。


密码修改最佳实践

Summary: NIST 最近发表了四卷 SP800-63b 数字身份指南。除此之外，它还对密码提供三个重要的建议

![](/data/attachment/album/201710/18/094012qnxe6erzko5y55no.jpg)

NIST 最近发表了四卷 [SP800-63b 数字身份指南](http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-63b.pdf)。除此之外，它还对密码提供三个重要的建议：

1. 不要再纠结于复杂的密码规则。它们使密码难以记住。因为人为的复杂密码很难输入，因此增加了错误。它们[也没有很大帮助](https://www.wsj.com/articles/the-man-who-wrote-those-password-rules-has-a-new-tip-n3v-r-m1-d-1502124118)。最好让人们使用密码短语。
2. 停止密码到期。这是[我们以前使用计算机的一个老的想法](https://securingthehuman.sans.org/blog/2017/03/23/time-for-password-expiration-to-die)。如今不要让人改变密码，除非有泄密的迹象。
3. 让人们使用密码管理器。这就是处理我们所有密码的方式。

这些密码规则不能[让用户安全](http://ieeexplore.ieee.org/document/7676198/?reload=true)。让系统安全才是最重要的。

---

作者简介：

自从 2004 年以来，我一直在博客上写关于安全的文章，以及从 1998 年以来我的每月订阅中也有。我写书、文章和学术论文。目前我是 IBM Resilient 的首席技术官，哈佛伯克曼中心的研究员，EFF 的董事会成员。

---

via: <https://www.schneier.com/blog/archives/2017/10/changes_in_pass.html>

作者：[Bruce Schneier](https://www.schneier.com/blog/about/) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

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


Android 在物联网方面能否像在移动终端一样成功？

![](/data/attachment/album/201710/18/121752v0bllg0r7xa9mbvu.jpg)

### 我在 Android Things 上的最初 24 小时

正当我在开发一个基于 Android 的运行在树莓派 3 的物联网商业项目时，一些令人惊喜的事情发生了。谷歌发布了[Android Things](https://developer.android.com/things/index.html) 的第一个预览版本，他们的 SDK 专门（目前）针对 3 个 SBC（单板计算机） —— 树莓派 3、英特尔 Edison 和恩智浦 Pico。说我一直在挣扎似乎有些轻描淡写 —— 树莓派上甚至没有一个成功的 Android 移植版本，我们在理想丰满，但是实践漏洞百出的内测版本上叫苦不迭。其中一个问题，同时也是不可原谅的问题是，它不支持触摸屏，甚至连 [Element14](https://www.element14.com/community/docs/DOC-78156/l/raspberry-pi-7-touchscreen-display) 官方销售的也不支持。曾经我认为 Android 已经支持树莓派，更早时候 “[谷歌向 AOSP 项目发起提交](http://www.androidpolice.com/2016/05/24/google-is-preparing-to-add-the-raspberry-pi-3-to-aosp-it-will-apparently-become-an-officially-supported-device/)” 中提到过树莓派曾让所有人兴奋不已。所以当 2016 年 12 月 12 日谷歌发布 “Android Things” 及其 SDK 的时候，我马上闭门谢客，全身心地去研究了……

### 问题？

关于树莓派上的谷歌 Android 我遇到很多问题，我以前用 Android 做过许多开发，也做过一些树莓派项目，包括之前提到过的一个真正参与的。未来我会尝试解决这些问题，但是首先最重要的问题得到了解决 —— 有完整的 Android Studio 支持，树莓派成为你手里的另一个常规的 ADB 可寻址设备。好极了。Android Studio 强大而便利、十分易用的功能包括布局预览、调试系统、源码检查器、自动化测试等都可以真正的应用在 IoT 硬件上。这些好处怎么说都不过分。到目前为止，我在树莓派上的大部分工作都是通过 SSH 使用运行在树莓派上的编辑器（MC，如果你真的想知道）借助 Python 完成的。这是有效的，毫无疑问铁杆的 Pi/Python 粉丝或许会有更好的工作方式，而不是当前这种像极了 80 年代码农的软件开发模式。我的项目需要在控制树莓派的手机上编写 Android 软件，这真有点痛不欲生 —— 我使用 Android Studio 做“真正的” Android 开发，借助 SSH 做剩下的。但是有了“Android Things”之后，一切都结束了。

所有的示例代码都适用于这三种 SBC，树莓派只是其中之一。 `Build.DEVICE` 常量可以在运行时确定是哪一个，所以你会看到很多如下代码：

```
 public static String getGPIOForButton() {
     switch (Build.DEVICE) {
         case DEVICE_EDISON_ARDUINO:
             return "IO12";
         case DEVICE_EDISON:
             return "GP44";
         case DEVICE_RPI3:
             return "BCM21";
         case DEVICE_NXP:
             return "GPIO4_IO20";
         default:
             throw new IllegalStateException(“Unknown Build.DEVICE “ + Build.DEVICE);
     }
 }

```

我对 GPIO 处理有浓厚的兴趣。 由于我只熟悉树莓派，我只能假定其它 SBC 工作方式相同，GPIO 只是一组引脚，可以定义为输入/输出，是连接物理外部世界的主要接口。 基于 Linux 的树莓派操作系统通过 Python 中的读取和写入方法提供了完整和便捷的支持，但对于 Android，您必须使用 NDK 编写 C++ 驱动程序，并通过 JNI 在 Java 中与这些驱动程序对接。 不是那么困难，但需要在你的构建链中维护额外的一些东西。 树莓派还为 I2C 指定了 2 个引脚：时钟和数据，因此需要额外的工作来处理它们。I2C 是真正酷的总线寻址系统，它通过串行化将许多独立的数据引脚转换成一个。 所以这里的优势是 —— Android Things 已经帮你完成了所有这一切。 你只需要 `read()` 和 `write()` 你需要的任何 GPIO 引脚，I2C 同样容易：

```
public class HomeActivity extends Activity {
    // I2C Device Name
    private static final String I2C_DEVICE_NAME = ...;
    // I2C Slave Address
    private static final int I2C_ADDRESS = ...;

    private I2cDevice mDevice;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Attempt to access the I2C device
        try {
            PeripheralManagerService manager = new PeripheralManagerService();
            mDevice = manager.openI2cDevice(I2C_DEVICE_NAME, I2C_ADDRESS)
        } catch (IOException e) {
            Log.w(TAG, "Unable to access I2C device", e);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        if (mDevice != null) {
            try {
                mDevice.close();
                mDevice = null;
            } catch (IOException e) {
                Log.w(TAG, "Unable to close I2C device", e);
            }
        }
    }
}

```

### Android Things 基于 Android 的哪个版本？

看起来是 Android 7.0，这样很好，因为我们可以继承 Android 所有以前版本的平板设计 UI、优化，安全加固等。它也带来了一个有趣的问题 —— 与应用程序必须单独管理不同，未来的平台应如何更新升级？请记住，这些设备可能无法连接到互联网。我们可能不便于连接蜂窝 / WiFi ，即便之前这些连接能用，但是有时不那么可靠。

另一个担心是，Android Things 仅仅是一个名字不同的 Android 分支版本，大部分都是一样的，和已经发布的 Arduino 一样，更像是为了市场营销而出现，而不是作为操作系统。不过可以放心，实际上通过[样例](https://github.com/androidthings/sample-simpleui/blob/master/app/src/main/res/drawable/pinout_board_vert.xml)可以看到，其中一些样例甚至使用了 SVG 图形作为资源，而不是传统的基于位图的图形（当然也能轻松处理） —— 这是一个非常新的 Android 创新。

不可避免地，与 Android Things 相比，普通的 Android 会有些不同。例如，权限问题。因为 Android Things 为固定硬件设计，在构建好之后，用户通常不会在这种设备上安装应用，所以在一定程序上减轻了这个问题，尽管当设备要求权限时是个问题 —— 因为它们没有 UI。解决方案是当应用在安装时给予所有需要的权限。 通常，这些设备只有一个应用，并且该应用从设备上电的那一刻就开始运行。

![](/data/attachment/album/201710/18/121753qtj9tx37jjm17fj7.png)

### Brillo 怎么了？

Brillo 是谷歌以前的 IoT 操作系统的代号，听起来很像 Android Things 的前身。 实际上现在你仍然能看到很多提及 Brillo 的地方，特别是在 GitHub Android Things 源码的文件夹名字中。 然而，它已经不复存在了。新王已经登基！

### UI 指南？

谷歌针对 Android 智能手机和平板电脑应用发布了大量指南，例如屏幕按钮间距等。 当然，你最好在可行的情况下遵循这些，但这已经不是本文应该考虑的范畴了。 缺省情况下什么也没有 —— 应用程序作者决定一切，这包括顶部状态栏，底部导航栏 —— 绝对是一切。 多年来谷歌一直在告诉 Android 应用程序的作者们绝不要在屏幕上放置返回按钮，因为平台将提供一个，因为 Android Things [可能甚至没有 UI！](https://developer.android.com/things/sdk/index.html)。

### 智能手机上会有多少谷歌服务？

有一些，但不是所有。第一个预览版本没有蓝牙支持、没有 NFC，这两者都对物联网革命有重大贡献。 SBC 支持它们，所以我们应该不会等待太久。由于没有通知栏，因此不支持任何通知。没有地图。缺省没有软键盘，你必须自己安装一个键盘。由于没有 Play 商店，你只能艰难地通过 ADB 做这个和许多其他操作。

当为 Android Things 开发时，我试图为运行在手机上和树莓派上使用同一个 APK。这引发了一个错误，阻止它安装在除 Android Things 设备之外的任何设备：`com.google.android.things` 库不存在。 这有点用，因为只有 Android Things 设备需要这个，但它似乎是个限制，因为不仅智能手机或平板电脑上没有，连模拟器上也没有。似乎只能在物理 Android Things 设备上运行和测试您的 Android Things 应用程序……直到谷歌在 [G+ 谷歌的 IoT 开发人员社区](https://plus.google.com/+CarlWhalley/posts/4tF76pWEs1D)组中回答了我的问题，并提供了规避方案。但是，躲过初一，躲不过十五。

### 可以期待 Android Thing 生态演进到什么程度？

我期望看到移植更多传统的基于 Linux 服务器的应用程序，将 Android 限制在智能手机和平板电脑上没有意义。例如，Web 服务器突然变得非常有用。已经有一些了，但没有像重量级的 Apache 或 Nginx 的。物联网设备可以没有本地 UI，但通过浏览器管理它们当然是可行的，因此需要用这种方式呈现 Web 面板。类似的那些如雷贯耳的通讯应用程序 —— 它需要的仅是一个麦克风和扬声器，而且在理论上任何视频通话应用程序，如 Duo、Skype、FB 等都可行。这个演变能走多远目前只能猜测。会有 Play 商店吗？它们会展示广告吗？我们能够确保它们不会窥探我们，或被黑客控制它们么？从消费者的角度来看，物联网应该是具有触摸屏的网络连接设备，因为每个人都已经习惯于通过智能手机工作。

我还期望看到硬件的迅速发展 —— 特别是有更多的 SBC 拥有更低的成本。看看惊人的 5 美元树莓派 Zero，不幸的是，由于其有限的 CPU 和内存，几乎可以肯定不能运行 Android Things。多久之后像这样的设备才能运行 Android Things？这是很明显的，标杆已经设定，任何有追求的 SBC 制造商将瞄准 Android Things 的兼容性，规模经济也将波及到外围设备，如 23 美元的触摸屏。没人会购买不会播放 YouTube 的微波炉，你的洗碗机会在 eBay 上购买更多的清洁粉，因为它注意到你很少使用它……

然而，我不认为我们会过于冲昏头脑。了解一点 Android 架构有助于将其视为一个包罗万象的物联网操作系统。它仍然使用 Java，其垃圾回收机制导致的所有时序问题在过去几乎把它搞死。这仅仅是问题最少的部分。真正的实时操作系统依赖于可预测、准确和坚如磐石的时序，要么它就不能被用于“关键任务”。想想医疗应用、安全监视器，工业控制器等。使用 Android，如果宿主操作系统认为它需要，理论上可以在任何时候杀死您的活动/服务。这在手机上没那么糟糕 —— 用户可以重新启动应用程序，杀死其他应用程序，或重新启动手机。但心脏监视器就完全是另一码事。如果前台的活动/服务正在监视一个 GPIO 引脚，而这个信号没有被准确地处理，我们就完了。必须要做一些相当根本的改变让 Android 来支持这一点，到目前为止还没有迹象表明它已经在计划之中了。

### 这 24 小时

所以，回到我的项目。 我认为我会接管我已经完成和尽力能为的工作，等待不可避免的路障，并向 G+ 社区寻求帮助。 除了一些在非 Android Things 设备上如何运行程序的问题之外，没有其他问题。它运行得很好！ 这个项目也使用了一些奇怪的东西，如自定义字体、高精定时器 —— 所有这些都在 Android Studio 中完美地展现。对我而言，可以打满分 —— 至少我能够开始做出实际原型，而不只是视频和截图。

### 蓝图

今天的物联网操作系统环境看起来非常零碎。 显然没有市场领导者，尽管炒作之声沸反连天，物联网仍然在草创阶段。 谷歌 Android 物联网能否像它在移动端那样取得成功？现在 Android 在移动方面的主导地位几近达到 90％。我相信如果真的如此，Android Things 的推出正是重要的一步。

记住所有的关于开放和封闭软件的战争，它们主要发生在从不授权的苹果和一直担心免费还不够充分的谷歌之间。那个老梗又来了，因为让苹果推出一个免费的物联网操作系统的构想就像让他们免费赠送下一代 iPhone 一样遥不可及。

物联网操作系统游戏是开放的，大家机遇共享，不过这个时候，封闭派甚至不会公布它们的开发工具箱……

前往 [Developer Preview](https://developer.android.com/things/preview/index.html)网站，立即获取 Android Things SDK 的副本。

---

via: <https://medium.com/@carl.whalley/will-android-do-for-iot-what-it-did-for-mobile-c9ac79d06c#.hxva5aqi2>

作者：[Carl Whalley](https://medium.com/@carl.whalley) 译者：[firstadream](https://github.com/firstadream) 校对：[wxy](https://github.com/wxy)

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


NixOS Linux： 先配置后安装的 Linux

Summary: 配置是成功安装 NixOS 的关键。

配置是成功安装 NixOS 的关键。

![](/data/attachment/album/201710/18/232958ljxtr0rooxo77x4g.png)

>
> 配置是成功安装 NixOS 的关键。
>
>
>

我用 Linux 有些年头了。在这些年里我很有幸见证了开源的发展。各色各样的发行版在安装方面的努力，也是其中的一个比较独特的部分。以前，安装 Linux 是个最好让有技术的人来干的任务。现在，只要你会装软件，你就会安装 Linux。简单，并且，不是我吹，在吸引新用户方面效果拔群。事实上安装整个 Linux 操作系统都要比 Windows 用户安装个更新看起来要快一点。

但每一次，我都喜欢看到一些不同的东西——那些可以让我体验新鲜的东西。[NixOS](https://nixos.org/) 在这方面就做的别具一格。讲真，我原来也就把它当作另一个提供标准特性和 KDE Plasma 5 界面的 Linux 发行版。

好像也没什么不对。

[下载 ISO 映像](https://nixos.org/nixos/download.html)后，我启动了 [VirtualBox](https://www.virtualbox.org/wiki/Downloads) 并用下载的镜像创建了个新的虚拟机。VM 启动后，出来的是 Bash 的登录界面，界面上指导我用空密码去登录 root 账号，以及我该如何启动 GUI 显示管理器（图 1）。

![First contact](/data/attachment/album/201710/18/233052olz8hlgl11elidgf.jpg "First contact")

*图 1： 与 NixOS 的初次接触可能不是太和谐。*

“好吧”我这样想着，“打开看看吧！”

GUI 启动和运行时（KDE Plasma 5），我没找到喜闻乐见的“安装”按钮。原来，NixOS 是一个在安装前需要你配置的发行版，真有趣。那就让我们瞧瞧它是如何做到的吧！

### 安装前配置

你需要做的第一件事是建分区。由于 NixOS 安装程序不包含分区工具，你得用自带的 GParted （图 2）来创建一个 EXT4 分区。

![Partitioning](/data/attachment/album/201710/18/233053x8goeovs9j64twvp.jpg "Partitioning")

*图 2： 安装前对磁盘分区。*

创建好分区，然后用命令 `mount /dev/sdX /mnt` 挂载。（请自行替换 `sdX` 为你新创建的分区）。

你现在需要创建一个配置文件。命令如下：

```
nixos-generate-config --root /mnt

```

上面的命令会创建两个文件（存放在目录 `/mnt/etc/nixos` 中）：

* `configuration.nix` — 默认配置文件。
* `hardware-configuration.nix` — 硬件配置（无法编辑）

通过命令 `nano /mnt/etc/nixos/configuration.nix` 打开文件。其中有一些需要编辑的地方得注意。第一个改动便是设置启动选项。找到行：

```
# boot.loader.grub.device = "/dev/sda"; # 或 efi 时用 "nodev"

```

删除行首的 `#` 使该选项生效（确保 `/dev/sda` 与你新建的分区）。

通过配置文件，你可以设置时区和追加要安装的软件包。来看一个被注释掉的安装包的示例：

```
# List packages installed in system profile. To search by name, run:
# nix-env -aqP | grep wget
# environment.systemPackages = with pkgs; [
#     wget vim
# ];

```

如果你想要添加软件包，并在安装时安装它们，那就取消掉这段注释，并添加你需要的软件包。举个例子，比方说你要把 LibreOffice 加进去。示例详见下方：

```
# List packages installed in system profile. To search by name, run:
nix-env -aqP | grep wget
environment.systemPackages = with pkgs; [
    libreoffice wget vim
];

```

你可以通过输入命令 `nix-env -aqP | grep PACKAGENAME` 来寻找确切的包名（`PACKAGENAME` 为你想要找的软件包）。如果你不想输命令，你也可以检索 [NixOS 的软件包数据库](https://nixos.org/nixos/packages.html)。

在你把所有的软件包都添加完后，你还有件事儿需要做（如果你想要登录到桌面的话，我觉得你还得折腾下 KDE Plasma 5 桌面）。翻到配置文件的末尾并在最后的 `}` 符号前，追加如下内容：

```
services.xserver = {
    enable = true;
    displayManager.sddm.enable = true;
    desktopManager.plasma5.enable = true;
};

```

在 [NixOS 官方文件](https://nixos.org/nixos/manual/index.html#ch-configuration) 中，你能找到配置文件中更多的选项。保存并关掉配置文件。

### 安装

在你按照自己的需求完善好配置之后，使用命令（需要 root 权限） `nixos-install`。完成安装所需要的时间，会随着你加入的软件包多少有所区别。安装结束后，你可以使用命令重启系统，（重启之后）迎接你的就是 KDE Plasma 5 的登录管理界面了（图 3）。

![KDE Plasma 5](/data/attachment/album/201710/18/233054zcjhzbchci38ijgg.jpg "KDE Plasma 5")

*图 3： KDE Plasma 5 登录管理界面*

### 安装后

你要首先要做的两件事之一便是给 root 用户设个密码（通过输入命令 `passwd` 来修改默认的密码），以及添加一个标准用户。做法和其它的 Linux 发行版无二。用 root 用户登录，然后在终端输入命令：

```
useradd -m USER

```

将 `USER` 替换成你想要添加的用户名。然后通过下面的命令给用户设上密码：

```
passwd USER

```

同样的将 `USER` 替换成你添加的用户。

然后会有提示引导你填写并验证新密码。然后，你就能用标准用户登录 NixOS 啦。

NixOS 在你安装并运行后，你可以为系统添加新的软件包，但并非通过寻常的方式。如果你发现你需要安装些新东西，你得回到配置文件（位置就是 `/etc/nixos/` ），找到之前安装时添加软件包的位置，运行以下命令（需要 root 权限）：

```
nixos-rebuild switch

```

命令执行结束后，你就能使用新安装的软件包了。

### Enjoy NixOS

现在，NixOS 已经带着所有你想安装的软件和 KDE Plasma 5 桌面运行起来了。要知道，你所做的不仅仅只是安装了个 Linux 发行版，关键是你自定义出来的发行版非常符合你的需求。所以好好享受你的 NixOS 吧！

---

via: <https://www.linux.com/learn/intro-to-linux/2017/10/nixos-linux-lets-you-configure-your-os-installing>

作者：[JACK WALLEN](https://www.linux.com/users/jlwallen) 译者：[martin2011qi](https://github.com/martin2011qi) 校对：[wxy](https://github.com/wxy)

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


极客漫画：谁杀死了 MySQL? - 后记

不错不错，很有趣。

![Who Killed MySQL? - Epilogue](/data/attachment/album/201710/19/100254adq477kzbdlldbko.png)

这篇漫画意在讽刺 Oracle 收购太阳微系统公司之后，对收购来的资产一个个杀死，比如 MySQL，导致大多数发行版已经不使用 MySQL ，转向它的分支 MariaDB，在此之后，Oracle 还放弃了“不赚钱的” Java EE。

然后之后，是以对开源不友善而著名的微软前总裁巴尔默——虽然现在新总裁纳德拉上台之后，微软公司乃至巴尔默都对开源的态度发生了一百八十度大转弯。

本篇漫画中涉及的一些技术方面的隐喻有：

* SIGKILL，是一种 UNIX/Posix 信号，用于结束一个进程，不可捕获。关于 SIGKILL ，可以看看[这篇漫画](/article-8791-1.html)。
* PIPE，是 UNIX/Posix 中的一种进程通讯机制，数据可以通过管道进行传输，进行进程间通讯。此处引申为一个管道、通道，从漫画中可以看到， Java 439 进程感觉不妙的时候，其脚下已经有些塌陷的痕迹，而接着变成了一个通道——然后它就掉进去死了。
* ulimit，是 UNIX/Linux 中用于限制资源分配的命令，可以设置系统可以分配的文件句柄数等，像 Apache 之类的服务需要足够的句柄数才能提供更高的连接数。此处，头上顶着羽毛的 Apache 表示，它要占用一些 ulimit 分配的资源（去工作了）。

---

via: <http://turnoff.us/geek/who-killed-mysql-epilogue/>

作者：[Daniel Stori](http://turnoff.us/about/) 译者&点评：[ItsLucas](https://github.com/ItsLucas) 校对&合成：[wxy](https://github.com/wxy)

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


OpenMessaging：构建一个分布式消息分发的开放标准

通过在云计算、大数据和标准 API 上的企业及社区的协作，我很高兴 OpenMessaging 项目进入 Linux 基金会。OpenMessaging 社区的目标是为分布式消息分发创建全球采用的、供应商中立的和开放标准，可以部署在云端、内部和混合云情景中。

![](/data/attachment/album/201710/19/185250rw8xo3kmtp5bb323.jpg)

阿里巴巴、雅虎、滴滴和 Streamlio 是该项目的创始贡献者。Linux 基金会已与这个初始项目社区合作来建立一个治理模式和结构，以实现运作在消息 API 标准上的生态系统的长期受益。

由于越来越多的公司和开发者迈向<ruby> 云原生应用 <rt>  cloud native application </rt></ruby>，消息式应用和流式应用的扩展面临的挑战也在不断发展。这包括平台之间的互操作性问题，<ruby> <a href="https://en.wikipedia.org/wiki/Wire_protocol">  线路级协议 </a> <rt>  wire-level protocol </rt></ruby>之间缺乏兼容性以及系统间缺乏标准的基准测试。

特别是当数据跨不同的消息平台和流平台进行传输时会出现兼容性问题，这意味着额外的工作和维护成本。现有解决方案缺乏负载平衡、容错、管理、安全性和流功能的标准化指南。目前的系统不能满足现代面向云的消息应用和流应用的需求。这可能导致开发人员额外的工作，并且难以或不可能满足物联网、边缘计算、智能城市等方面的尖端业务需求。

OpenMessaging 的贡献者正在寻求通过以下方式改进分布式消息分发：

* 为分布式消息分发创建一个面向全球、面向云、供应商中立的行业标准
* 促进用于测试应用程序的标准基准发展
* 支持平台独立
* 以可伸缩性、灵活性、隔离和安全性为目标的云数据的流和消息分发要求
* 培育不断发展的开发贡献者社区

你可以在这了解有关新项目的更多信息以及如何参与： [http://openmessaging.cloud](http://openmessaging.cloud/)。

这些是支持 OpenMessaging 的一些组织：

>
> “我们多年来一直专注于消息分发和流领域，在此期间，我们探索了 Corba 通知、JMS 和其它标准，来试图解决我们最严格的业务需求。阿里巴巴在评估了可用的替代品后，选择创建一个新的面向云的消息分发标准 OpenMessaging，这是一个供应商中立，且语言无关的标准，并为金融、电子商务、物联网和大数据等领域提供了行业指南。此外，它目地在于跨异构系统和平台间开发消息分发和流应用。我们希望它可以是开放、简单、可扩展和可互操作的。另外，我们要根据这个标准建立一个生态系统，如基准测试、计算和各种连接器。我们希望有新的贡献，并希望大家能够共同努力，推动 OpenMessaging 标准的发展。”
>
>
> ——阿里巴巴高级架构师，Apache RocketMQ 的联合创始人，以及 OpenMessaging 的原始发起人 Von Gosling
>
>
>

>
> “随着应用程序消息的复杂性和规模的不断扩大，缺乏标准的接口为开发人员和组织带来了复杂性和灵活性的障碍。Streamlio 很高兴与其他领导者合作推出 OpenMessaging 标准倡议来给客户一个轻松使用高性能、低延迟的消息传递解决方案，如 Apache Pulsar，它提供了企业所需的耐用性、一致性和可用性。“
>
>
> —— Streamlio 的软件工程师、Apache Pulsar 的联合创始人以及 Apache BookKeeper PMC 的成员 Matteo Merli
>
>
>

>
> “Oath（Verizon 旗下领先的媒体和技术品牌，包括雅虎和 AOL）支持开放，协作的举措，并且很乐意加入 OpenMessaging 项目。”
>
>
> —— Joe Francis，核心平台总监
>
>
>

>
> “在滴滴中，我们定义了一组私有的生产者 API 和消费者 API 来隐藏开源的 MQ（如 Apache Kafka、Apache RocketMQ 等）之间的差异，并提供额外的自定义功能。我们计划将这些发布到开源社区。到目前为止，我们已经积累了很多关于 MQ 和 API 统一的经验，并愿意在 OpenMessaging 中与其它 API 一起构建 API 的共同标准。我们真诚地认为，统一和广泛接受的 API 标准可以使 MQ 技术和依赖于它的应用程序受益。”
>
>
> —— 滴滴的架构师 Neil Qi
>
>
>

>
> “有许多不同的开源消息分发解决方案，包括 Apache ActiveMQ、Apache RocketMQ、Apache Pulsar 和 Apache Kafka。缺乏行业级的可扩展消息分发标准使得评估合适的解决方案变得困难。我们很高兴能够与多个开源项目共同努力，共同确定可扩展的开放消息规范。 Apache BookKeeper 已成功在雅虎（通过 Apache Pulsar）和 Twitter（通过 Apache DistributedLog）的生产环境中部署，它作为其企业级消息系统的持久化、高性能、低延迟存储基础。我们很高兴加入 OpenMessaging 帮助其它项目解决诸如低延迟持久化、一致性和可用性等在消息分发方案中的常见问题。”
>
>
> —— Streamlio 的联合创始人、Apache BookKeeper 的 PMC 主席、Apache DistributedLog 的联合创造者， Sijie Guo
>
>
>

---

via: <https://www.linuxfoundation.org/blog/building-open-standard-distributed-messaging-introducing-openmessaging/>

作者：[Mike Dolan](https://www.linuxfoundation.org/author/mdolan/) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

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


Ubuntu 17.10（AA）发布

![](/data/attachment/album/201710/19/231242bhki55gchkis1cv5.png)

今天，Canonical 公司发布了 Ubuntu 17.10，这个版本不是 LTS 版本，因此其支持期只有 9 个月，支持到 2018 年 7 月。

Ubuntu 17.10 的代号是 Artful Aardvark （巧豚）。正如大家知道的，Ubuntu 发行版的代号是由两个单词组成的，分别是一个形容词和一个濒危动物名。从第四个版本开始，就按照字母顺序，从 D 开始逐个使用。这是 Ubnutu 发布了 26 个主版本以来，唯二剩下没用过的字母（另外一个是 C，最开始的三个版本没有按字母顺序来，分别是 W、H、B）。

![Artful Aardvark （巧豚）](/data/attachment/album/201710/19/231556xi8bbdoow6ffvvt7.jpg)

### 重大变化

这次的 Ubuntu 17.10 虽然不是重要的 LTS 版本，连 Ubuntu 官网的介绍中都将其视作是明年的 Ubuntu 18.04 LTS 的前奏，但是这个版本其实还是有几个处女式创新：

* 这是第一个放弃 32 位支持的主版本，不过 17.10 的官方风味版本还会继续支持 32 位。
* 这是七年来 Ubuntu 主版本第一次放弃 Unity 用户界面，改投 GNOME 怀抱，目前采用的是最新的 GNOME 3.26.1。
* 这是第一次默认使用 Wayland 显示服务器，而 X.Org 显示服务器则是可选的。当然如果你的机器不支持 Wayland ，会自动回退到 X.Org。
* 默认不再安装 Python 2，Python 3 更新到了 3.6。

我们可以先看一下官方的宣传视频：

### 新的变化

除了上述重大改变之外，Ubuntu 17.10 还有这些新改变：

**桌面版**

* 新的由 GNOME 带来的 Caribou 屏幕键盘替代了原来的 Onboard，铺平了将来的触摸屏体验之路。
* 相同的用户体验。虽然更换了 GNOME，但是从桌面布局到键盘快捷键均保持了一致，这要感谢那些主题和扩展。
* 熟悉的 dock。从 11.04 开始，dock 就是 Ubuntu 发行版的特色，在这个版本中，它还继续在那里，不增不减——不过现在它可以随意移动到左边、右边和底部。
* Ubuntu 17.10 使用 Linux kernel 4.13。
* 交换分区现在改成了交换文件，这便于你随时根据系统需求伸缩，也可以很方便的安装到各种机器上。
* 由于默认使用 GNOME 桌面系统，GDM 也替换 LightDM 成为了默认的显示管理器。并且现在登录屏使用 1 号虚拟终端，而不是之前的 7 号虚拟终端。
* 七年来，窗口控制按钮首次从左边回到了右边。
* 免驱动打印现在支持 [IPP Everywhere](http://www.pwg.org/dynamo/eveprinters.php)、 [Apple AirPrint](https://support.apple.com/en-us/HT201311)、 [Wi-Fi Direct](https://www.wi-fi.org/discover-wi-fi/wi-fi-direct) 和 [Mopria](http://mopria.org/) 设备。
* “设置”应用重新进行了设计。
* “系统日志” 被替换为“日志”，这是来自 systemd journal 的日志查看器。
* 官方风味版的 Ubuntu GNOME 停止更新，因为现在 Ubuntu 使用的就是 GNOME 。
* 但是如果你想试试更上游的 GNOME，你可以安装 gnome-session，并从登录屏幕选择 GNOME 。如果你喜欢的话，也可以安装 vanilla-gnome-desktop 基础包来获得更多的 GNOME 核心应用。

**服务器版**

* qemu 从 2.8 更新到了 2.10。需要注意的是，默认增加并启用了镜像锁定功能，一般来说它会更安全，但是在有些旧场景下会有问题。
* libvirt 更新到了 3.6。
* DPDK 更新到了最新的稳定版本 17.05.2，这使得它可以与 Open vSwitch 2.8 集成。Open vSwitch 更新到了 2.8，需要注意是的，从 2.7 开始，你就需要通过 dpdk-devargs 指定 dpdk 设备了。
* DNS 服务器 Bind9 更新包括了 2017 年 7 月 11 日新发布的<ruby> 密钥签名密钥 <rp>  （ </rp> <rt>  Key Signing Key </rt> <rp>  ） </rp></ruby>（KSK），从 2017 年 10 月 11 日开始，该密钥将用于签名根区密钥，根区密钥用于签名实际的根区。已运行的 Bind 9 会根据 RFC 5011 自动更新其锚点密钥，而 [2017 年 10 月 11 日回滚事件](https://www.icann.org/resources/pages/ksk-rollover/)之后新安装的 Bind 9 将需要这个包或手动更新密钥。

更多变化，可以参考[发行公告](https://wiki.ubuntu.com/ArtfulAardvark/ReleaseNotes)。

### 下载

* Ubuntu 桌面版和服务器版 <http://releases.ubuntu.com/17.10/>
* 其它架构的 Ubuntu 服务器 <http://cdimage.ubuntu.com/ubuntu/releases/17.10/release/>
* Ubuntu 云镜像 <http://cloud-images.ubuntu.com/daily/server/artful/current/>
* Ubuntu Netboot <http://cdimage.ubuntu.com/netboot/17.10/>
* Kubuntu <http://cdimage.ubuntu.com/kubuntu/releases/17.10/release/>
* Lubuntu <http://cdimage.ubuntu.com/lubuntu/releases/17.10/release/>
* Ubuntu Budgie <http://cdimage.ubuntu.com/ubuntu-budgie/releases/17.10/release/>
* 优麒麟 <http://cdimage.ubuntu.com/ubuntukylin/releases/17.10/release/>
* Ubuntu MATE <https://ubuntu-mate.org/download/>
* Ubuntu Studio <http://cdimage.ubuntu.com/ubuntustudio/releases/17.10/release/>
* Xubuntu <http://cdimage.ubuntu.com/xubuntu/releases/17.10/release/>

### 从 17.04 升级

如果你希望使用长期支持版本，建议你安装 Ubuntu 16.04 LTS。如果你在使用 Ubuntu 17.04，你可以按以下方式升级到最新版本。

桌面系统升级：

* 在系统设置中打开“软件与更新”。
* 选择“更新”选项卡。
* 设置下拉菜单“有新的 Ubuntu 版本时提醒我”为“任何新版本”。
* 按下 `Alt+F2`快捷键，并在命令行窗口输入 `update-manager -c`。
* 更新管理器将打开并提示你“新的发行版 17.10 已经可用”。如果不工作，你可以运行 `/usr/lib/ubuntu-release-upgrader/check-new-release-gtk`。
* 点击“升级”并按屏幕提示进行。

服务器系统升级：

* 如果没有安装的话，请先安装 `update-manager-core` 软件包。
* 确保 `/etc/update-manager/release-upgrades` 中的 `Prompt` 设置为 `normal`。
* 在命令行运行 `sudo do-release-upgrade` 启动升级管理器。
* 按屏幕提示进行。


Docker 快速入门之 Dockerfile

![docker](/data/attachment/album/201710/20/222828ouudmzdmouzrfmyd.jpg)

在[之前的文章](/article-8932-1.html)中我们提到可以通过容器创建一个我们自定义过的镜像，那么我们是否可以直接通过基础的镜像直接自定义镜像呢？答案当然是可以的，在 Docker 中我们可以从名为 `Dockerfile` 的文件中读取指令并且自动构建镜像。在本文中，将介绍 Dockerfile 的基本语法以及基本知识。

### 1、Dockerfile 是什么？

Dockerfile 其实是一份文本文档，里面包含了用户可以用来操作镜像的一些指令。通过顺序执行这些指令，最后得到一个自定义的镜像，这有点类似于我们的 shell 脚本。

### 2、Dockerfile 示例

接下来先看一个 Dockerfile 示例：

```
FROM centos
LABEL maintainer="Locez <locez@locez.com>"
ENV TEST="This is a test env"
COPY nginx.repo /etc/yum.repos.d/nginx.repo
RUN yum update -y && \
        yum install -y nginx
COPY nginx.conf /etc/nginx/nginx.conf
COPY index.html /usr/share/nginx/html/index.html
COPY index_files/ /usr/share/nginx/html/index_files/
EXPOSE 80
CMD ["/usr/sbin/nginx","-g","daemon off;"]

```

在上面我们可以看到 Dockerfile 中的一些指令，通过名称我们也可以猜到这些指令大概是干嘛的，其中有一些对文件的操作，因此我们先来看看用于存放 Dockerfile 的这个目录的目录结构:

```
# tree .
.
├── Dockerfile
├── index_files
│   ├── 145049z4og8xyjhx4xy8go.jpg
│   ├── 222746e5vh38d7ey3leyps.jpg
│   ├── 88x31.png
│   ├── archlinux-splash.png
│   ├── bdshare.css
│   ├── Best-Linux-Markdown-Editors.png
│   ├── core.js
│   ├── docker-icon.jpg
│   ├── hadoop-pic1.png
│   ├── jquery_002.js
│   ├── jquery.css
│   ├── jquery.js
│   ├── MathJax.js
│   ├── pic.gif
│   ├── raspberrypiraspberry-pi-logo.jpg
│   ├── script.js
│   ├── scrollup.png
│   ├── share.js
│   ├── style.css
│   └── z_stat.js
├── index.html
├── nginx.conf
└── nginx.repo

1 directory, 24 files

```

### 构建镜像

在当前目录下执行以下命令构建镜像:

```
# docker build -t locez/nginx .
Sending build context to Docker daemon 1.851 MB
Step 1/10 : FROM centos
 ---> 196e0ce0c9fb
Step 2/10 : LABEL maintainer "Locez <locez@locez.com>"
 ---> Using cache
 ---> 9bba3042bcdb
Step 3/10 : ENV TEST "This is a test env"
 ---> Using cache
 ---> c0ffe95ea0c5
Step 4/10 : COPY nginx.repo /etc/yum.repos.d/nginx.repo
 ---> Using cache
 ---> bb6ee4c30d56
Step 5/10 : RUN yum update -y &&        yum install -y nginx
 ---> Using cache
 ---> 6d46b41099c3
Step 6/10 : COPY nginx.conf /etc/nginx/nginx.conf
 ---> Using cache
 ---> cfe908390aae
Step 7/10 : COPY index.html /usr/share/nginx/html/index.html
 ---> Using cache
 ---> 21729476079d
Step 8/10 : COPY index_files/ /usr/share/nginx/html/index_files/
 ---> Using cache
 ---> 662f06ec7b46
Step 9/10 : EXPOSE 80
 ---> Using cache
 ---> 30db5a889d0a
Step 10/10 : CMD /usr/sbin/nginx -g daemon off;
 ---> Using cache
 ---> d29b9d4036d2
Successfully built d29b9d4036d2

```

然后用该镜像启动容器：

```
# docker run -d -it --rm --name test-nginx -p 8080:80 locez/nginx
e06fd991ca1b202e08cf1578f8046355fcbba10dd9a90e11d43282f3a1e36d29

```

用浏览器访问 `http://localhost:8080/` 即可看到部署的内容。

### 3 Dockerfile 指令解释

Dockerfile 支持 `FROM`、 `RUN`、 `CMD`、 `LABEL`、 `EXPOSE`、 `ENV`、 `ADD`、 `COPY`、 `ENTRYPOINT`、 `VOLUME`、 `USER`、 `WORKDIR`、 `ARG`、 `ONBUILD`、 `SHELL` 等指令，这里只选择常用的几个进行讲解，可结合上面的示例进行理解。其它的请自行查阅官方文档。

#### 3.1 FROM

`FROM` 指令用于指定要操作的基础镜像，因为在我们构建我们自己的镜像的时候需要一个基础镜像。 语法：

```
FROM <image> [AS <name>]
FROM <image>[:<tag>] [AS <name>]

```

其中 `[AS <name>]` 为指定一个名称，在一个 Dockerfile 中多次使用 `FROM` 时如有需要，可用 `COPY --from=<name|index>` 语法进行复制。

#### 3.2 RUN

`RUN` 指令用于执行命令，并且是在新的一层上执行，并把执行后的结果提交，也就是生成新的一层。基于这个问题，我们在使用 `RUN` 指令时应该尽可能的把要执行的命令一次写完，以减少最后生成的镜像的层数。 语法：

```
RUN <command>
RUN ["executable", "param1", "param2"]

```

#### 3.3 CMD

`CMD` 指令用于给容器启动时指定一个用于执行的命令，例如上例中的 nginx 启动命令。 语法：

```
CMD ["executable","param1","param2"]
CMD ["param1","param2"] ### 用于给 ENTRYPOINT 指令提供默认参数
CMD command param1 param2

```

#### 3.4 LABEL

`LABEL` 指令用于为镜像指定标签，可用 `docker inspect` 命令查看。可用来代替被舍弃的 `MAINTAINER` 命令。 语法：

```
LABEL <key>=<value> <key>=<value> <key>=<value> ...

```

#### 3.5 EXPOSE

`EXPOSE` 指令用于告诉 Docker 容器监听的特殊端口，但是此时端口还没有暴露给 host ，只有当在运行一个容器显式用参数 `-p` 或者 `-P` 的时候才会暴露端口。 语法：

```
EXPOSE <port> [<port>/<protocol>...]

```

#### 3.6 ENV

`ENV` 指令用于设定环境变量。 语法：

```
ENV <key> <value>
ENV <key>=<value> ...

```

#### 3.7 ADD

`ADD` 指令用于复制新文件，目录，远程文件到容器中。其中 `<src>` 可以为文件，目录，URL，若为可解压文件，在复制后会解压。 语法：

```
ADD <src>... <dest>
ADD ["<src>",... "<dest>"]

```

#### 3.8 COPY

`COPY` 指令与 `ADD` 指令非常相似，但 `COPY` 比较直观且简单，它只支持本地的文件以及目录的复制，不像 `ADD` 指令可以远程获取文件并解压。 语法：

```
COPY <src>... <dest>
COPY ["<src>",... "<dest>"]

```

#### 3.9 ENTRYPOINT

`ENTRYPOINT` 指令也跟 `CMD` 指令相似，用于指定容器启动时执行的命令。当使用 `ENTRYPOINT` 指令时，可用 `CMD` 命令配合，这样在启动容器时，可以对 `CMD` 指令写入的参数进行覆盖。 语法：

```
ENTRYPOINT ["executable", "param1", "param2"]

```

例子：

```
ENTRYPOINT ["top","-b"]
CMD ["-c"]

```

上面的 `-c` 参数可以在启动时覆盖 `docker run -it --rm --name test top -H`。 如果要覆盖 `ENTRYPOINT` 指令则用 `--entrypoint` 参数启动容器。

#### 3.10 VOLUME

`VOLUME` 指令用于为容器创建一个挂载点，这个挂载点可以用来挂载 `本地文件/文件夹` 也可以用来挂载 `数据卷`。其中若在启动一个新容器时没有指定挂载目录，则会自动创建一个数据卷，当容器被销毁时，数据卷如果没有被其它容器引用则会被删除。 语法：

```
VOLUME ["/data1","/data2"]

```

#### 3.11 USER

`USER` 指令用于设置执行 `RUN`, `CMD`, `ENTRYPOINT` 等指令的用户以及用户组。默认为 `root` 用户。 语法：

```
USER <user>[:<group>]

```

#### 3.12 WORKDIR

`WORKDIR` 指令用于设置 `RUN`, `CMD`, `ENTRYPOINT`, `COPY`, `ADD` 等指令的工作目录。 语法：

```
WORKDIR /path/to/workdir

```

### 4 总结

---

本文从一个具体的例子出发，讲述了如何利用 Dockerfile 构建镜像，然后解释了 Dockerfile 文件中的指令的语法，有关更多内容可访问官方文档。

### 5 参考资料

---

* [Dockerfile reference](https://docs.docker.com/engine/reference/builder/)


Ciao：云集成高级编排器

Summary: Ciao  是一个新的负载调度程序，用来解决当前云操作系统项目的局限性

Ciao  是一个新的负载调度程序，用来解决当前云操作系统项目的局限性

![](/data/attachment/album/201710/20/233813xercyqzsgylylufj.jpeg)

<ruby> 云集成高级编排器 <rt>  Cloud Integrated Advanced Orchestrator </rt></ruby> （Ciao） 是一个新的负载调度程序，用来解决当前云操作系统项目的局限性。Ciao 提供了一个轻量级，完全基于 TLS 的最小配置。它是 工作量无关的、易于更新、具有优化速度的调度程序，目前已针对 OpenStack 进行了优化。

其设计决策和创新方法在对安全性、可扩展性、可用性和可部署性的要求下进行：

* **可扩展性：** 初始设计目标是伸缩超过 5,000 个节点。因此，调度器架构用新的形式实现：
	+ 在 ciao 中，决策制定是去中心化的。它基于拉取模型，允许计算节点从调度代理请求作业。调度程序总能知道启动器的容量，而不要求进行数据更新，并且将调度决策时间保持在最小。启动器异步向调度程序发送容量。
	+ 持久化状态跟踪与调度程序决策制定相分离，它让调度程序保持轻量级。这种分离增加了可靠性、可扩展性和性能。结果是调度程序让出了权限并且这不是瓶颈。
* **可用性：** 虚拟机、容器和裸机集成到一个调度器中。所有的负载都被视为平等公民。为了更易于使用，网络通过一个组件间最小化的异步协议进行简化，只需要最少的配置。Ciao 还包括一个新的、简单的 UI。所有的这些功能都集成到一起来简化安装、配置、维护和操作。
* **轻松部署：** 升级应该是预期操作，而不是例外情况。这种新的去中心化状态的体系结构能够无缝升级。为了确保基础设施（例如 OpenStack）始终是最新的，它实现了持续集成/持续交付（CI/CD）模型。Ciao 的设计使得它可以立即杀死任何 Ciao 组件，更换它，并重新启动它，对可用性影响最小。
* **安全性是必需的：** 与调度程序的连接总是加密的：默认情况下 SSL 是打开的，而不是关闭的。加密是从端到端：所有外部连接都需要 HTTPS，组件之间的内部通信是基于 TLS 的。网络支持的一体化保障了租户分离。

初步结果证明是显著的：在 65 秒内启动一万个 Docker 容器和五千个虚拟机。进一步优化还在进行。

* 文档：<https://clearlinux.org/documentation/ciao/ciao.html>
* Github 链接： [https://github.com/01org/ciao(link is external)](https://github.com/01org/ciao)
* 邮件列表链接： <https://lists.clearlinux.org/mailman/listinfo/ciao-devel>

---

via: <https://clearlinux.org/ciao>

作者：[ciao](https://clearlinux.org/ciao) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

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


如何像 NASA 顶级程序员一样编程 —— 10 条重要原则

![rules of coding nasa](/data/attachment/album/201710/21/232332dxjaxwomx2xjmxbt.png)

>
> 引言： 你知道 NASA 顶级程序员如何编写关键任务代码么？为了确保代码更清楚、更安全、且更容易理解，NASA 的喷气推进实验室制定了 10 条编码规则。
>
>
>

NASA 的开发者是编程界最有挑战性的工作之一。他们编写代码并将开发安全的关键任务应用程序作为其主要关注点。

在这种情形下，遵守一些严格的编码规则是重要的。这些规则覆盖软件开发的多个方面，例如软件应该如何编码、应该使用哪些语言特性等。

尽管很难就一个好的编码标准达成共识，NASA 的喷气推进实验室（JPL）遵守一个[编码规则](http://pixelscommander.com/wp-content/uploads/2014/12/P10.pdf)，其名为“十的次方：开发安全的关键代码的规则”。

由于 JPL 长期使用 C 语言，这个规则主要是针对于 C 程序语言编写。但是这些规则也可以很容地应用到其它的程序语言。

该规则由 JPL 的首席科学家 Gerard J. Holzmann 制定，这些严格的编码规则主要是聚焦于安全。

NASA 的 10 条编写关键任务代码的规则：

1. 限制所有代码为极为简单的控制流结构 — 不用 `goto` 语句、`setjmp` 或 `longjmp` 结构，不用间接或直接的递归调用。
2. 所有循环必须有一个固定的上限值。必须可以被某个检测工具静态证实，该循环不能达到预置的迭代上限值。如果该上限值不能被静态证实，那么可以认为违背该原则。
3. 在初始化后不要使用动态内存分配。
4. 如果一个语句一行、一个声明一行的标准格式来参考，那么函数的长度不应该比超过一张纸。通常这意味着每个函数的代码行不能超过 60。
5. 代码中断言的密度平均低至每个函数 2 个断言。断言被用于检测那些在实际执行中不可能发生的情况。断言必须没有副作用，并应该定义为布尔测试。当一个断言失败时，应该执行一个明确的恢复动作，例如，把错误情况返回给执行该断言失败的函数调用者。对于静态工具来说，任何能被静态工具证实其永远不会失败或永远不能触发的断言违反了该规则（例如，通过增加无用的 `assert(true)` 语句是不可能满足这个规则的）。
6. 必须在最小的范围内声明数据对象。
7. 非 void 函数的返回值在每次函数调用时都必须检查，且在每个函数内其参数的有效性必须进行检查。
8. 预处理器的使用仅限制于包含头文件和简单的宏定义。符号拼接、可变参数列表（省略号）和递归宏调用都是不允许的。所有的宏必须能够扩展为完整的语法单元。条件编译指令的使用通常是晦涩的，但也不总是能够避免。这意味着即使在一个大的软件开发中超过一两个条件编译指令也要有充足的理由，这超出了避免多次包含头文件的标准做法。每次在代码中这样做的时候必须有基于工具的检查器进行标记，并有充足的理由。
9. 应该限制指针的使用。特别是不应该有超过一级的解除指针引用。解除指针引用操作不可以隐含在宏定义或类型声明中。还有，不允许使用函数指针。
10. 从开发的第一天起，必须在编译器开启最高级别警告选项的条件下对代码进行编译。在此设置之下，代码必须零警告编译通过。代码必须利用源代码静态分析工具每天至少检查一次或更多次，且零警告通过。

关于这些规则，NASA 是这么评价的：

>
> 这些规则就像汽车中的安全带一样，刚开始你可能感到有一点不适，但是一段时间后就会养成习惯，你会无法想象不使用它们的日子。
>
>
>

此文是否对你有帮助？不要忘了在下面的评论区写下你的反馈。

---

作者简介：

Adarsh Verma 是 Fossbytes 的共同创始人，他是一个令人尊敬的企业家，他一直对开源、技术突破和完全保持密切关注。可以通过邮件联系他 — [adarsh.verma@fossbytes.com](mailto:adarsh.verma@fossbytes.com)

---

via: <https://fossbytes.com/nasa-coding-programming-rules-critical/>

作者：[Adarsh Verma](https://fossbytes.com/author/adarsh/) 译者：[penghuster](https://github.com/penghuster) 校对：[wxy](https://github.com/wxy)

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


Grafeas：旨在更好地审计容器

>
> Google 的 Grafeas 为容器的元数据提供了一个从镜像、构建细节到安全漏洞的通用 API。
>
>
>

![](/data/attachment/album/201710/22/102612x2u8zq2jcewttuwu.jpeg)

我们运行的软件从来没有比今天更难获得。它分散在本地部署和云服务之间，由不知到有多少的开源组件构建而成，以快速的时间表交付，因此保证安全和质量变成了一个挑战。

最终的结果是软件难以审计、推断、安全化和管理。困难的不只是知道 VM 或容器是用什么构建的, 而是由谁来添加、删除或更改的。[Grafeas](http://grafeas.io/) 最初由 Google 设计，旨在使这些问题更容易解决。

### 什么是 Grafeas?

Grafeas 是一个定义软件组件的元数据 API 的开源项目。旨在提供一个统一的元数据模式，允许 VM、容器、JAR 文件和其他软件<ruby> 工件 <rt>  artifact </rt></ruby>描述自己的运行环境以及管理它们的用户。目标是允许像在给定环境中使用的软件一样的审计，以及对该软件所做的更改的审计，并以一致和可靠的方式进行。

Grafeas提供两种格式的元数据 API —— 备注和事件：

* <ruby> 备注 <rt>  note </rt></ruby>是有关软件工件的某些方面的细节。可以是已知软件漏洞的描述，有关如何构建软件的详细信息（构建器版本、校验和等），部署历史等。
* <ruby> 事件 <rt>  occurrence </rt></ruby>是备注的实例，包含了它们创建的地方和方式的细节。例如，已知软件漏洞的详细信息可能会有描述哪个漏洞扫描程序检测到它的情况、何时被检测到的事件信息，以及该漏洞是否被解决。

备注和事件都存储在仓库中。每个备注和事件都使用标识符进行跟踪，该标识符区分它并使其唯一。

Grafeas 规范包括备注类型的几个基本模式。例如，软件包漏洞模式描述了如何存储 CVE 或漏洞描述的备注信息。现在没有接受新模式类型的正式流程，但是[这已经在计划](https://github.com/Grafeas/Grafeas/issues/38)创建这样一个流程。

### Grafeas 客户端和第三方支持

现在，Grafeas 主要作为规范和参考形式存在，它在 [GitHub 上提供](https://github.com/grafeas/grafeas)。 [Go](https://github.com/Grafeas/client-go)、[Python](https://github.com/Grafeas/client-python) 和 [Java](https://github.com/Grafeas/client-java) 的客户端都可以[用 Swagger 生成](https://www.infoworld.com/article/2902750/application-development/manage-apis-with-swagger.html)，所以其他语言的客户端也应该不难写出来。

Google 计划让 Grafeas 广泛使用的主要方式是通过 Kubernetes。 Kubernetes 的一个名为 Kritis 的策略引擎，可以根据 Grafeas 元数据对容器采取措施。

除 Google 之外的几家公司已经宣布计划将 Grafeas 的支持添加到现有产品中。例如，CoreOS 正在考察 Grafeas 如何与 Tectonic 集成，[Red Hat](https://www.redhat.com/en/blog/red-hat-google-cloud-and-other-industry-leaders-join-together-standardize-kubernetes-service-component-auditing-and-policy-enforcement) 和 [IBM](https://developer.ibm.com/dwblog/2017/grafeas/) 都计划在其容器产品和服务中添加 Grafeas 集成。

---

via: <https://www.infoworld.com/article/3230462/security/what-is-grafeas-better-auditing-for-containers.html>

作者：[Serdar Yegulalp](https://www.infoworld.com/author/Serdar-Yegulalp/) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

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


Genymotion vs Android 模拟器

>
> Android 模拟器是否改善到足以取代 Genymotion
>
>
>

![](/data/attachment/album/201710/22/110139c2uu85s5dgusbdkc.jpg)

一直以来有关于选择 android 模拟器或者 Genymotion 的争论，我看到很多讨论最后以赞成 Genymotion 而告终。我根据我周围最常见的情况收集了一些数据，基于此，我将连同 Genymotion 全面评估 android 模拟器。

结论剧透：**配置正确时，Android 模拟器比 Genymotion 快。**

使用带 Google API 的 x86（32位）镜像、3GB RAM、四核CPU。

>
> * 哈，很高兴我们知道了最终结果
> * 现在，让我们深入
>
>
>

免责声明：我已经测试了我看到的一般情况，即运行测试。所有的基准测试都是在 2015 年中期的 MacBook Pro 上完成的。无论何时我提及 Genymotion 指的都是 Genymotion Desktop。他们还有其他产品，如 Genymotion on Cloud＆Genymotion on Demand，但这里没有考虑。我不是说 Genymotion 是不合适的，但运行测试比某些 Android 模拟器慢。

关于这个问题的一点背景，然后我们将转到具体内容上去。

*过去：我有一些基准测试，继续下去。*

很久以前，Android 模拟器是唯一的选择。但是它们太慢了，这是架构改变的原因。对于在 x86 机器上运行的 ARM 模拟器，你能期待什么？每个指令都必须从 ARM 转换为 x86 架构，这使得它的速度非常慢。

随之而来的是 Android 的 x86 镜像，随着它们摆脱了 ARM 到 x86 平台转化，速度更快了。现在，你可以在 x86 机器上运行 x86 Android 模拟器。

>
> * *问题解决了！！！*
> * 没有！
>
>
>

Android 模拟器仍然比人们想要的慢。随后出现了 Genymotion，这是一个在 virtual box 中运行的 Android 虚拟机。与在 qemu 上运行的普通老式 android 模拟器相比，它相当稳定和快速。

我们来看看今天的情况。

我在持续集成的基础设施上和我的开发机器上使用 Genymotion。我手头的任务是摆脱持续集成基础设施和开发机器上使用 Genymotion。

>
> * 你问为什么？
> * 授权费钱。
>
>
>

在快速看了一下以后，这似乎是一个愚蠢的举动，因为 Android 模拟器的速度很慢而且有 bug，它们看起来适得其反，但是当你深入的时候，你会发现 Android 模拟器是优越的。

我们的情况是对它们进行集成测试（主要是 espresso）。我们的应用程序中只有 1100 多个测试，Genymotion 需要大约 23 分钟才能运行所有测试。

在 Genymotion 中我们面临的另一些问题是：

* 有限的命令行工具（[GMTool](https://docs.genymotion.com/Content/04_Tools/GMTool/GMTool.htm)）。
* 由于内存问题，它们需要定期重新启动。这是一个手动任务，想象在配有许多机器的持续集成基础设施上进行这些会怎样。

**进入 Android 模拟器**

首先是尝试在它给你这么多的选择中设置一个，这会让你会觉得你在赛百味餐厅一样。最大的问题是 x86 或 x86\_64 以及是否有 Google API。

我用这些组合做了一些研究和基准测试，这是我们所想到的。

鼓声……

>
> * 比赛的获胜者是带 Google API 的 x86
> * 但是如何胜利的？为什么？
>
>
>

嗯，我会告诉你每一个问题。

x86\_64 比 x86 慢

>
> * 你问慢多少。
> * 28.2% 多！！！
>
>
>

使用 Google API 的模拟器更加稳定，没有它们容易崩溃。

这使我们得出结论：最好的是带 Google API 的x86。

在我们抛弃 Genymotion 开始使用模拟器之前。有下面几点重要的细节。

* 我使用的是带 Google API 的 Nexus 5 镜像。
* 我注意到，给模拟器较少的内存会造成了很多 Google API 崩溃。所以为模拟器设定了 3GB 的 RAM。
* 模拟器有四核。
* HAXM 安装在主机上。

**基准测试的时候到了**

![Genymotion and Android Emulator Espresso Benchmark](/data/attachment/album/201710/22/110217by598s9sp0s0519e.png)

![Linpack](/data/attachment/album/201710/22/110218kmkrgffoo07k8kfz.png)

![Geekbench 4](/data/attachment/album/201710/22/110218xd7bue7u8sy1bvtb.png)

从基准测试上你可以看到除了 Geekbench4，Android 模拟器都击败了 Genymotion，我感觉更像是virtual box 击败了 qemu。

>
> 欢呼模拟器之王
>
>
>

我们现在有更快的测试执行时间、更好的命令行工具。最新的 [Android 模拟器](https://developer.android.com/studio/releases/emulator.html)创下的新的记录。更快的启动时间之类。

Goolgle 一直努力让

>
> Android 模拟器变得更好
>
>
>

如果你没有在使用 Android 模拟器。我建议你重新试下，可以节省一些钱。

我尝试的另一个但是没有成功的方案是在 AWS 上运行 [Android-x86](http://www.android-x86.org/) 镜像。我能够在 vSphere ESXi Hypervisor 中运行它，但不能在 AWS 或任何其他云平台上运行它。如果有人知道原因，请在下面评论。

PS：[VMWare 现在可以在 AWS 上使用](https://aws.amazon.com/vmware/)，在 AWS 上使用 [Android-x86](http://www.android-x86.org/) 毕竟是有可能的。

---

作者简介：

嗨，我的名字是 Sumit Gupta。我是来自印度古尔冈的软件/应用/网页开发人员。我做这个是因为我喜欢技术，并且一直迷恋它。我已经工作了 3 年以上，但我还是有很多要学习。他们不是说如果你有知识，让别人点亮他们的蜡烛。

当在编译时，我阅读很多文章，或者听音乐。

如果你想联系，下面是我的社交信息和 [email](thesumitgupta@outlook.com)。

---

via: <https://www.plightofbyte.com/android/2017/09/03/genymotion-vs-android-emulator/>

作者：[Sumit Gupta](https://www.plightofbyte.com/about-me) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

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