Merge pull request #1 from LCTT/master

update
This commit is contained in:
amwps290 2018-06-15 15:19:08 +08:00 committed by GitHub
commit ecb7ecdbc2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 172 additions and 180 deletions

View File

@ -0,0 +1,107 @@
底层 Linux 容器运行时之发展史
======
> “容器运行时”是一个被过度使用的名词。
![](https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/running-containers-two-ship-container-beach.png?itok=wr4zJC6p)
在 Red Hat我们乐意这么说“容器即 LinuxLinux 即容器”。下面解释一下这种说法。传统的容器是操作系统中的进程,通常具有如下 3 个特性:
1. 资源限制
当你在系统中运行多个容器时,你肯定不希望某个容器独占系统资源,所以我们需要使用资源约束来控制 CPU、内存和网络带宽等资源。Linux 内核提供了 cgroup 特性,可以通过配置控制容器进程的资源使用。
2. 安全性配置
一般而言,你不希望你的容器可以攻击其它容器或甚至攻击宿主机系统。我们使用了 Linux 内核的若干特性建立安全隔离,相关特性包括 SELinux、seccomp 和 capabilities。
LCTT 译注:从 2.2 版本内核开始Linux 将特权从超级用户中分离,产生了一系列可以单独启用或关闭的 capabilities
3. 虚拟隔离
容器外的任何进程对于容器而言都应该不可见。容器应该使用独立的网络。不同的容器对应的进程应该都可以绑定 80 端口。每个容器的<ruby>内核映像<rt>image</rt></ruby><ruby>根文件系统<rt>rootfs</rt>rootfs都应该相互独立。在 Linux 中,我们使用内核的<ruby>名字空间<rt>namespace</rt></ruby>特性提供<ruby>虚拟隔离<rt>virtual separation</rt></ruby>
那么,具有安全性配置并且在 cgroup 和名字空间下运行的进程都可以称为容器。查看一下 Red Hat Enterprise Linux 7 操作系统中的 PID 1 的进程 systemd你会发现 systemd 运行在一个 cgroup 下。
```
# tail -1 /proc/1/cgroup
1:name=systemd:/
```
`ps` 命令让我们看到 systemd 进程具有 SELinux 标签:
```
# ps -eZ | grep systemd
system_u:system_r:init_t:s0             1 ?     00:00:48 systemd
```
以及 capabilities
```
# grep Cap /proc/1/status
...
CapEff: 0000001fffffffff
CapBnd: 0000001fffffffff
CapBnd:    0000003fffffffff
```
最后,查看 `/proc/1/ns` 子目录,你会发现 systemd 运行所在的名字空间。
```
ls -l /proc/1/ns
lrwxrwxrwx. 1 root root 0 Jan 11 11:46 mnt -> mnt:[4026531840]
lrwxrwxrwx. 1 root root 0 Jan 11 11:46 net -> net:[4026532009]
lrwxrwxrwx. 1 root root 0 Jan 11 11:46 pid -> pid:[4026531836]
...
```
如果 PID 1 进程(实际上每个系统进程)具有资源约束、安全性配置和名字空间,那么我可以说系统上的每一个进程都运行在容器中。
容器运行时工具也不过是修改了资源约束、安全性配置和名字空间,然后 Linux 内核运行起进程。容器启动后,容器运行时可以在容器内监控 PID 1 进程,也可以监控容器的标准输入/输出,从而进行容器进程的生命周期管理。
### 容器运行时
你可能自言自语道“哦systemd 看起来很像一个容器运行时”。经过若干次关于“为何容器运行时不使用 `systemd-nspawn` 工具来启动容器”的邮件讨论后,我认为值得讨论一下容器运行时及其发展史。
[Docker][1] 通常被称为容器运行时,但“<ruby>容器运行时<rt>container runtime</rt></ruby>”是一个被过度使用的词语。当用户提到“容器运行时”,他们其实提到的是为开发人员提供便利的<ruby>上层<rt>high-level</rt></ruby>工具,包括 Docker[CRI-O][2] 和 [RKT][3]。这些工具都是基于 API 的,涉及操作包括从容器仓库拉取容器镜像、配置存储和启动容器等。启动容器通常涉及一个特殊工具,用于配置内核如何运行容器,这类工具也被称为“容器运行时”,下文中我将称其为“底层容器运行时”以作区分。像 Docker、CRI-O 这样的守护进程及形如 [Podman][4]、[Buildah][5] 的命令行工具,似乎更应该被称为“容器管理器”。
早期版本的 Docker 使用 `lxc` 工具集启动容器,该工具出现在 `systemd-nspawn` 之前。Red Hat 最初试图将 [libvirt][6] `libvirt-lxc`)集成到 Docker 中替代 `lxc` 工具,因为 RHEL 并不支持 `lxc`。`libvirt-lxc` 也没有使用 `systemd-nspawn`,在那时 systemd 团队仅将 `systemd-nspawn` 视为测试工具,不适用于生产环境。
与此同时,包括我的 Red Hat 团队部分成员在内的<ruby>上游<rt>upstream</rt></ruby> Docker 开发者,认为应该采用 golang 原生的方式启动容器,而不是调用外部应用。他们的工作促成了 libcontainer 这个 golang 原生库用于启动容器。Red Hat 工程师更看好该库的发展前景,放弃了 `libvirt-lxc`
后来成立 <ruby>[开放容器组织][7]<rt>Open Container Initiative</rt></ruby>OCI的部分原因就是人们希望用其它方式启动容器。传统的基于名字空间隔离的容器已经家喻户晓但人们也有<ruby>虚拟机级别隔离<rt>virtual machine-level isolation</rt></ruby>的需求。Intel 和 [Hyper.sh][8] 正致力于开发基于 KVM 隔离的容器Microsoft 致力于开发基于 Windows 的容器。OCI 希望有一份定义容器的标准规范,因而产生了 [OCI <ruby>运行时规范<rt>Runtime Specification</rt></ruby>][9]。
OCI 运行时规范定义了一个 JSON 文件格式,用于描述要运行的二进制,如何容器化以及容器根文件系统的位置。一些工具用于生成符合标准规范的 JSON 文件,另外的工具用于解析 JSON 文件并在该根文件系统rootfs上运行容器。Docker 的部分代码被抽取出来构成了 libcontainer 项目,该项目被贡献给 OCI。上游 Docker 工程师及我们自己的工程师创建了一个新的前端工具,用于解析符合 OCI 运行时规范的 JSON 文件,然后与 libcontainer 交互以便启动容器。这个前端工具就是 [runc][10],也被贡献给 OCI。虽然 `runc` 可以解析 OCI JSON 文件,但用户需要自行生成这些文件。此后,`runc` 也成为了最流行的底层容器运行时,基本所有的容器管理工具都支持 `runc`,包括 CRI-O、Docker、Buildah、Podman 和 [Cloud Foundry Garden][11] 等。此后,其它工具的实现也参照 OCI 运行时规范,以便可以运行 OCI 兼容的容器。
[Clear Containers][12] 和 Hyper.sh 的 `runV` 工具都是参照 OCI 运行时规范运行基于 KVM 的容器,二者将其各自工作合并到一个名为 [Kata][12] 的新项目中。在去年Oracle 创建了一个示例版本的 OCI 运行时工具,名为 [RailCar][13],使用 Rust 语言编写。但该 GitHub 项目已经两个月没有更新了故无法判断是否仍在开发。几年前Vincent Batts 试图创建一个名为 [nspawn-oci][14] 的工具,用于解析 OCI 运行时规范文件并启动 `systemd-nspawn`;但似乎没有引起大家的注意,而且也不是原生的实现。
如果有开发者希望实现一个原生的 `systemd-nspawn --oci OCI-SPEC.json` 并让 systemd 团队认可和提供支持那么CRI-O、Docker 和 Podman 等容器管理工具将可以像使用 `runc` 和 Clear Container/runV [Kata][15] 那样使用这个新的底层运行时。(目前我的团队没有人参与这方面的工作。)
总结如下,在 3-4 年前,上游开发者打算编写一个底层的 golang 工具用于启动容器,最终这个工具就是 `runc`。那时开发者有一个使用 C 编写的 `lxc` 工具,在 `runc` 开发后,他们很快转向 `runc`。我很确信,当决定构建 libcontainer 时,他们对 `systemd-nspawn` 或其它非原生(即不使用 golang的运行 namespaces 隔离的容器的方式都不感兴趣。
--------------------------------------------------------------------------------
via: https://opensource.com/article/18/1/history-low-level-container-runtimes
作者:[Daniel Walsh][a]
译者:[pinewall](https://github.com/pinewall)
校对:[wxy](https://github.com/wxy)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]:https://opensource.com/users/rhatdan
[1]:https://github.com/docker
[2]:https://github.com/kubernetes-incubator/cri-o
[3]:https://github.com/rkt/rkt
[4]:https://github.com/projectatomic/libpod/tree/master/cmd/podman
[5]:https://github.com/projectatomic/buildah
[6]:https://libvirt.org/
[7]:https://www.opencontainers.org/
[8]:https://www.hyper.sh/
[9]:https://github.com/opencontainers/runtime-spec
[10]:https://github.com/opencontainers/runc
[11]:https://github.com/cloudfoundry/garden
[12]:https://clearlinux.org/containers
[13]:https://github.com/oracle/railcar
[14]:https://github.com/vbatts/nspawn-oci
[15]:https://github.com/kata-containers

View File

@ -5,27 +5,22 @@
下面我将为你展示,如何在 [Go][6] 中实现一个 HTTP API 去提供这种服务。
### 业务
### 流
* 用户输入他的电子邮件地址。
* 服务器创建一个临时的一次性使用的代码(就像一个临时密码一样)关联到用户,然后给用户邮箱中发送一个“魔法链接”。
* 用户点击魔法链接。
* 服务器提取魔法链接中的代码,获取关联的用户,并且使用一个新的 JWT 重定向到客户端。
* 在每次有新请求时,客户端使用 JWT 去验证用户。
### 必需条件
* 数据库:我们为这个服务使用了一个叫 [CockroachDB][1] 的 SQL 数据库。它非常像 postgres但它是用 Go 写的。
* SMTP 服务器:我们将使用一个第三方的邮件服务器去发送邮件。开发的时我们使用 [mailtrap][2]。Mailtrap 发送所有的邮件到它的收件箱,因此,你在测试时不需要创建多个假邮件帐户。
* SMTP 服务器:我们将使用一个第三方的邮件服务器去发送邮件。开发的时我们使用 [mailtrap][2]。Mailtrap 发送所有的邮件到它的收件箱,因此,你在测试它们时不需要创建多个假冒邮件帐户
从 [Go 的主页][7] 上安装它,然后使用 `go version`1.10.1 atm命令去检查它能否正常工作
从 [它的主页][7] 上安装 Go然后使用 `go version`1.10.1 atm命令去检查它能否正常工作。
从 [它的主页][8] 上下载 CockroachDB展开它并添加到你的 `PATH` 变量中。使用 `cockroach version`2.0 atm命令检查它能否正常工作。
从 [CockroachDB 的主页][8] 上下载它,展开它并添加到你的 `PATH` 变量中。使用 `cockroach version`2.0 atm命令检查它能否正常工作。
### 数据库模式
@ -33,7 +28,6 @@
```
cockroach start --insecure --host 127.0.0.1
```
它会输出一些内容,找到 SQL 地址行,它将显示像 `postgresql://root@127.0.0.1:26257?sslmode=disable` 这样的内容。稍后我们将使用它去连接到数据库。
@ -62,7 +56,7 @@ INSERT INTO users (email, username) VALUES
```
这个脚本创建了一个名为 `passwordless_demo` 的数据库、两个名为 `users` 和 `verification_codes` 的表,以及为了稍后测试而插入的一些假用户。每个验证代码都与用户关联并保存代码创建数据,以用于去检查代码是否过期。
这个脚本创建了一个名为 `passwordless_demo` 的数据库、两个名为 `users` 和 `verification_codes` 的表,以及为了稍后测试而插入的一些假用户。每个验证代码都与用户关联并保存创建时间,以用于去检查验证代码是否过期。
在另外的终端中使用 `cockroach sql` 命令去运行这个脚本:
@ -80,9 +74,7 @@ cat schema.sql | cockroach sql --insecure
我们需要下列的 Go 包:
* [github.com/lib/pq][3]:它是 CockroachDB 使用的 postgres 驱动
* [github.com/matryer/way][4]: 路由器
* [github.com/dgrijalva/jwt-go][5]: JWT 实现
```
@ -94,7 +86,7 @@ go get -u github.com/dgrijalva/jwt-go
### 代码
### 初始化函数
#### 初始化函数
创建 `main.go` 并且通过 `init` 函数里的环境变量中取得一些配置来启动。
@ -137,22 +129,16 @@ func env(key, fallbackValue string) string {
```
* `appURL` 将去构建我们的 “魔法链接”。
* `port` 将要启动的 HTTP 服务器。
* `databaseURL` 是 CockroachDB 地址,我添加 `/passwordless_demo` 前面的数据库地址去表示数据库名字。
* `jwtKey` 用于签名 JWTs。
* `jwtKey` 用于签名 JWT。
* `smtpAddr` 是 `SMTP_HOST` + `SMTP_PORT` 的联合;我们将使用它去发送邮件。
* `smtpUsername` 和 `smtpPassword` 是两个必需的变量。
* `smtpAuth` 也是用于发送邮件。
`env` 函数允许我们去获得环境变量,不存在时返回一个 fallback value
`env` 函数允许我们去获得环境变量,不存在时返回一个回退值
### 主函数
#### 主函数
```
var db *sql.DB
@ -189,7 +175,7 @@ import (
```
然后,我们创建路由器并定义一些端点。对于无密码业务流来说,我们使用两个端点:`/api/passwordless/start` 发送魔法链接,和 `/api/passwordless/verify_redirect` 用 JWT 响应。
然后,我们创建路由器并定义一些端点。对于无密码流来说,我们使用两个端点:`/api/passwordless/start` 发送魔法链接,和 `/api/passwordless/verify_redirect` 用 JWT 响应。
最后,我们启动服务器。
@ -236,7 +222,7 @@ go build
我们在目录中有了一个 “passwordless-demo”但是你的目录中可能与示例不一样`go build` 将创建一个同名的可执行文件。如果你没有关闭前面的 cockroach 节点,并且你正确配置了 `SMTP_USERNAME` 和 `SMTP_PASSWORD` 变量,你将看到命令 `starting server at http://localhost/ 🚀` 没有错误输出。
### JSON 要求的中间件
#### 请求 JSON 的中间件
端点需要从请求体中解码 JSON因此要确保请求是 `application/json` 类型。因为它是一个通用的东西,我将它解耦到中间件。
@ -257,7 +243,7 @@ func jsonRequired(next http.HandlerFunc) http.HandlerFunc {
实现很容易。首先它从请求头中获得内容的类型,然后检查它是否是以 “application/json” 开始,如果不是则以 `415 Unsupported Media Type` 提前返回。
### 响应 JSON 函数
#### 响应 JSON 函数
以 JSON 响应是非常通用的做法,因此我把它提取到函数中。
@ -285,7 +271,7 @@ func respondJSON(w http.ResponseWriter, payload interface{}, code int) {
首先,对原始类型做一个类型判断,并将它们封装到一个 `map`。然后将它们编组到 JSON设置响应内容类型和状态码并写 JSON。如果 JSON 编组失败,则响应一个内部错误。
### 响应内部错误的函数
#### 响应内部错误的函数
`respondInternalError` 是一个响应 `500 Internal Server Error` 的函数,但是也同时将错误输出到控制台。
@ -299,7 +285,7 @@ func respondInternalError(w http.ResponseWriter, err error) {
```
### 创建用户处理程序
#### 创建用户处理程序
下面开始编写 `createUser` 处理程序,因为它非常容易并且是 REST 式的。
@ -391,7 +377,7 @@ respondJSON(w, user, http.StatusCreated)
最后使用创建的用户去响应。
### 无密码验证开始部分的处理程序
#### 无密码验证开始部分的处理程序
```
type PasswordlessStartRequest struct {
@ -401,7 +387,7 @@ type PasswordlessStartRequest struct {
```
这个结构体持有 `passwordlessStart` 的请求体。希望去登入的用户 email。来自客户端的重定向 URI这个应用中将使用我们的 API`https://frontend.app/callback`。
这个结构体含有 `passwordlessStart` 的请求体:希望去登入的用户 email、来自客户端的重定向 URI这个应用中将使用我们的 API`https://frontend.app/callback`。
```
var magicLinkTmpl = template.Must(template.ParseFiles("templates/magic-link.html"))
@ -429,7 +415,7 @@ var magicLinkTmpl = template.Must(template.ParseFiles("templates/magic-link.html
这个模板是给用户发送魔法链接邮件用的。你可以根据你的需要去随意调整它。
现在, 进入 `passwordlessStart` 函数**内部**
现在, 进入 `passwordlessStart` 函数内部:
```
var input PasswordlessStartRequest
@ -525,7 +511,7 @@ w.WriteHeader(http.StatusNoContent)
最后,设置响应状态码为 `204 No Content`。对于成功的状态码,客户端不需要很多数据。
### 发送邮件函数
#### 发送邮件函数
```
func sendMail(to mail.Address, subject, body string) error {
@ -558,16 +544,16 @@ func sendMail(to mail.Address, subject, body string) error {
这个函数创建一个基本的 HTML 邮件结构体并使用 SMTP 服务器去发送它。邮件的内容你可以随意定制,我喜欢使用比较简单的内容。
### 无密码验证重定向处理程序
#### 无密码验证重定向处理程序
```
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}$")
```
首先,这个正则表达式去验证一个 UUID验证代码
首先,这个正则表达式去验证一个 UUID验证代码)。
现在进入 `passwordlessVerifyRedirect` 函数 **内部**
现在进入 `passwordlessVerifyRedirect` 函数内部:
```
q := r.URL.Query()
@ -661,23 +647,19 @@ http.Redirect(w, r, callback.String(), http.StatusFound)
* * *
无密码的工作流已经完成。现在需要去写 `getAuthUser` 端点的代码了,它用于获取当前验证用户的信息。你应该还记得,这个端点使用了 `authRequired` 中间件。
无密码的流已经完成。现在需要去写 `getAuthUser` 端点的代码了,它用于获取当前验证用户的信息。你应该还记得,这个端点使用了 `guard` 中间件。
### 使用 Auth 中间件
#### 使用 Auth 中间件
在编写 `authRequired` 中间件之前,我将编写一个不需要验证的分支。目的是,如果没有传递 JWT它将不去验证用户。
在编写 `guard` 中间件之前,我将编写一个不需要验证的分支。目的是,如果没有传递 JWT它将不去验证用户。
```
type ContextKey int
const (
keyAuthUserID ContextKey = iota
)
func jwtKeyFunc(*jwt.Token) (interface{}, error) {
return config.jwtKey, nil
type ContextKey struct {
Name string
}
var keyAuthUserID = ContextKey{"auth_user_id"}
func withAuth(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
a := r.Header.Get("Authorization")
@ -689,7 +671,11 @@ func withAuth(next http.HandlerFunc) http.HandlerFunc {
tokenString := a[7:]
p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}}
token, err := p.ParseWithClaims(tokenString, &jwt.StandardClaims{}, jwtKeyFunc)
token, err := p.ParseWithClaims(
tokenString,
&jwt.StandardClaims{},
func (*jwt.Token) (interface{}, error) { return config.jwtKey, nil },
)
if err != nil {
respondJSON(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
@ -707,19 +693,18 @@ func withAuth(next http.HandlerFunc) http.HandlerFunc {
next(w, r.WithContext(ctx))
}
}
```
JWT 将在每次请求时以 “Bearer <token_here>” 格式包含在 “Authorization” 头中。因此,如果没有提供令牌,我们将直接通过,进入接下来的中间件。
JWT 将在每次请求时以 `Bearer <token_here>` 格式包含在 `Authorization` 头中。因此,如果没有提供令牌,我们将直接通过,进入接下来的中间件。
我们创建一个解析器来解析令牌。如果解析失败则返回 `401 Unauthorized`
然后我们从 JWT 中提取出要求的内容,并添加 `Subject`(就是用户 ID到需要的地方。
### Auth 需要的中间件
#### Guard 中间件
```
func authRequired(next http.HandlerFunc) http.HandlerFunc {
func guard(next http.HandlerFunc) http.HandlerFunc {
return withAuth(func(w http.ResponseWriter, r *http.Request) {
_, ok := r.Context().Value(keyAuthUserID).(string)
if !ok {
@ -729,15 +714,13 @@ func authRequired(next http.HandlerFunc) http.HandlerFunc {
next(w, r)
})
}
```
现在,`authRequired` 将使用 `withAuth` 并从请求内容中提取出验证用户的 ID。如果提取失败它将返回 `401 Unauthorized`,提取成功则继续下一步。
现在,`guard` 将使用 `withAuth` 并从请求内容中提取出验证用户的 ID。如果提取失败它将返回 `401 Unauthorized`,提取成功则继续下一步。
### 获取 Auth 用户
#### 获取 Auth 用户
在 `getAuthUser` 处理程序**内部**
在 `getAuthUser` 处理程序内部:
```
ctx := r.Context()
@ -756,9 +739,9 @@ respondJSON(w, user, http.StatusOK)
```
首先,我们从请求内容中提取验证用户的 ID我们使用这个 ID 去获取用户。如果没有获取到内容,则发送一个 `418 I'm a teapot`,或者一个内部错误。最后,我们将用这个用户去响应 😊
首先,我们从请求内容中提取验证用户的 ID我们使用这个 ID 去获取用户。如果没有获取到内容,则发送一个 `418 I'm a teapot`,或者一个内部错误。最后,我们将用这个用户去响应
### 获取 User 函数
#### 获取 User 函数
下面你看到的是 `fetchUser` 函数。
@ -783,15 +766,15 @@ func fetchUser(ctx context.Context, id string) (User, error) {
如果有任何问题,请在我的 [GitHub repo][11] 留言或者提交 PRs 👍
以后,我为这个 API 写一个客户端作为这篇文章的第二部分。
以后,我为这个 API 写一个客户端作为这篇文章的[第二部分][13]
--------------------------------------------------------------------------------
via: https://nicolasparada.netlify.com/posts/passwordless-auth-server/
作者:[Nicolás Parada ][a]
作者:[Nicolás Parada][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/) 荣誉推出
@ -808,3 +791,4 @@ via: https://nicolasparada.netlify.com/posts/passwordless-auth-server/
[10]:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox
[11]:https://github.com/nicolasparada/go-passwordless-demo
[12]:https://twitter.com/intent/retweet?tweet_id=986602458716803074
[13]:https://nicolasparada.netlify.com/posts/passwordless-auth-client/

View File

@ -1,3 +1,5 @@
translating---geekpi
How To Run A Command For A Specific Time In Linux
======
![](https://www.ostechnix.com/wp-content/uploads/2018/02/Run-A-Command-For-A-Specific-Time-In-Linux-1-720x340.png)

View File

@ -1,100 +0,0 @@
底层 Linux 容器运行时的发展史
======
![](https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/running-containers-two-ship-container-beach.png?itok=wr4zJC6p)
在 Red Hat我们乐意这么说“容器就是 LinuxLinux 就是容器”。下面解释一下这种说法。传统的容器是操作系统中的进程,通常具有如下 3 个特性:
### 1\. 资源限制
当你在系统中运行多个容器时,你肯定不希望某个容器独占系统资源,所以我们需要使用资源约束来控制 CPU、内存和网络带宽等资源。Linux 内核提供了 cgroups 特性,可以通过配置控制容器进程的资源使用。
### 2\. 安全性配置
一般而言,你不希望你的容器可以攻击其它容器或甚至攻击的你的主机系统。我们使用了 Linux 内核的若干特性建立安全隔离,相关特性包括 SELinux、seccomp 和 capabilities。
LCTT 译注:从 2.2 版本内核开始Linux 将特权从超级用户中分离,产生了一系列可以单独启用或关闭的 capabilities
### 3\. 虚拟隔离
容器外的任何进程对于容器而言都应该不可见。容器应该使用独立的网络。不同的容器对应的进程都应该可以绑定 80 端口。每个容器的<ruby>内核映像<rt>image</rt></ruby><ruby>根文件系统<rt>rootfs</rt>都应该相互独立。在 Linux 中,我们使用内核 namespaces 特性提供<ruby>虚拟隔离<rt>virtual separation</rt></ruby>
那么,具有安全性配置并且在 cgroup 和 namespace 下运行的进程都可以称为容器。查看一下 Red Hat Enterprise Linux 7 操作系统中的 PID 1 的进程 systemd你会发现 systemd 运行在一个 cgroup 下。
```
# tail -1 /proc/1/cgroup
1:name=systemd:/
```
`ps` 命令让我们看到 systemd 进程具有 SELinux 标签:
```
# ps -eZ | grep systemd
system_u:system_r:init_t:s0             1 ?     00:00:48 systemd
```
以及 capabilities
```
# grep Cap /proc/1/status
...
CapEff: 0000001fffffffff
CapBnd: 0000001fffffffff
CapBnd:    0000003fffffffff
```
最后,查看 `/proc/1/ns` 子目录,你会发现 systemd 运行所在的 namespace。
```
ls -l /proc/1/ns
lrwxrwxrwx. 1 root root 0 Jan 11 11:46 mnt -> mnt:[4026531840]
lrwxrwxrwx. 1 root root 0 Jan 11 11:46 net -> net:[4026532009]
lrwxrwxrwx. 1 root root 0 Jan 11 11:46 pid -> pid:[4026531836]
...
```
如果 PID 1 进程(实际上每个系统进程)具有资源约束、安全性配置和 namespace那么我想说系统上的每一个进程都运行在容器中。
容器运行时工具也不过是修改了资源约束、安全性配置和 namespace然后 Linux 内核运行起进程。容器启动后,容器运行时可以在容器内监控 PID 1 进程,也可以监控容器的标准输入输出,从而进行容器进程的生命周期管理。
### 容器运行时
你可能自言自语道“哦systemd 看起来很像一个容器运行时”。经过若干次关于“为何容器运行时不使用 `systemd-nspawn` 工具启动容器”的邮件讨论后,我认为值得讨论一下容器运行时及其发展史。
[Docker][1] 通常被称为容器运行时,但“容器运行时”是一个被过度使用的词语。当用户提到“容器运行时”,他们其实提到的是为开发人员提供便利的<ruby>上层<rt>high-level</rt></ruby>工具,包括 Docker[CRI-O][2] 和 [RKT][3]。这些工具都是基于 API 的,涉及操作包括从容器仓库拉取容器镜像、配置存储和启动容器等。启动容器通常涉及一个特殊工具,用于配置内核如何运行容器,这类工具也被称为“容器运行时”,下文中我将称其为“底层容器运行时”以作区分。像 Docker、CRI-O 这样的守护进程及形如 [Podman][4]、[Buildah][5] 的命令行工具,似乎更应该被称为“容器管理器”。
早期版本的 Docker 使用 `lxc` 工具集启动容器,该工具出现在 `systemd-nspawn` 之前。Red Hat 最初试图将 `[libvirt][6]` (`libvirt-lxc`) 集成到 Docker 中替代 `lxc` 工具,因为 RHEL 并不支持 `lxc`。`libvirt-lxc` 也没有使用 `systemd-nspawn`,在那时 systemd 团队仅将 `systemd-nspawn` 视为测试工具,不适用于生产环境。
与此同时,包括我 Red Hat 团队部分成员在内的<ruby>上游<rt>upstream</rt></ruby> Docker 开发者,认为应该采用 golang 原生的方式启动容器,而不是调用外部应用。他们的工作促成了 libcontainer 这个 golang 原生库用于启动容器。Red Hat 工程师更看好该库的发展前景,放弃了 `libvirt-lxc`
后来成立 [<ruby>开放容器组织<rt>Open Container Initiative</rt></ruby>][7] (OCI) 的部分原因就是人们希望用其它方式启动容器。传统的基于 namespaces 隔离的容器已经家喻户晓,但人们也有<ruby>虚拟机级别隔离<rt>virtual machine-level isolation</rt></ruby>的需求。Intel 和 [Hyper.sh][8] 正致力于开发基于 KVM 隔离的容器Microsoft 致力于开发基于 Windows 的容器。OCI 希望有一份定义容器的标准规范,因而产生了 [OCI <ruby>运行时规范<rt>Runtime Specification</rt></ruby>][9]。
OCI 运行时规范定义了一个 JSON 文件格式,用于描述要运行的二进制,如何容器化以及容器根文件系统的位置。一些工具用于生成符合标准规范的 JSON 文件,另外的工具用于解析 JSON 文件并在根文件系统上运行容器。Docker 的部分代码被抽取出来构成了 libcontainer 项目,该项目被贡献给 OCI。上游 Docker 工程师及我们自己的工程师创建了一个新的前端工具,用于解析符合 OCI 运行时规范的 JSON 文件,然后与 libcontainer 交互以便启动容器。这个前端工具就是 `[runc][10]`,也被贡献给 OCI。虽然 `runc` 可以解析 OCI JSON 文件,但用户需要自行生成这些文件。此后,`runc` 也成为了最流行的底层容器运行时,基本所有的容器管理工具都支持 `runc`,包括 CRI-ODockerBuildahPodman 和 [Cloud Foundry Garden][11] 等。此后,其它工具的实现也参照 OCI 运行时规范,以便可以运行 OCI 兼容的容器。
[Clear Containers][12] 和 Hyper.sh 的 `runV` 工具都是参照 OCI 运行时规范运行基于 KVM 的容器,二者将其各自工作合并到一个名为 [Kata][12] 的新项目中。在去年Oracle 创建了一个示例版本的 OCI 运行时工具,名为 [RailCar][13],使用 Rust 语言编写。但该 GitHub 项目已经两个月没有更新了故无法判断是否仍在开发。几年前Vincent Batts 试图创建一个名为 `[nspawn-oci][14]` 的工具,用于解析 OCI 运行时规范文件并启动 `systemd-nspawn`;但似乎没有引起大家的注意,而且也不是原生的实现。
如果有开发者希望实现一个原生的 `systemd-nspawn --oci OCI-SPEC.json` 并让 systemd 团队认可和提供支持那么CRI-ODocker 和 Podman 等容器管理工具将可以像使用 `runc` 和 Clear Container/runV ([Kata][15]) 那样使用这个新的底层运行时。(目前我的团队没有人参与这方面的工作。)
总结如下,在 3-4 年前,上游开发者打算编写一个底层的 golong 工具用于启动容器,最终这个工具就是 `runc`。那时开发者使用 C 编写的 `lxc` 工具,在 `runc` 开发后,他们很快转向 `runc`。我很确信,当决定构建 libcontainer 时,他们对 `systemd-nspawn` 或其它非原生(即不使用 golong的运行 namespaces 隔离的容器的方式都不感兴趣。
--------------------------------------------------------------------------------
via: https://opensource.com/article/18/1/history-low-level-container-runtimes
作者:[Daniel Walsh][a]
译者:[pinewall](https://github.com/pinewall)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]:https://opensource.com/users/rhatdan
[1]:https://github.com/docker
[2]:https://github.com/kubernetes-incubator/cri-o
[3]:https://github.com/rkt/rkt
[4]:https://github.com/projectatomic/libpod/tree/master/cmd/podman
[5]:https://github.com/projectatomic/buildah
[6]:https://libvirt.org/
[7]:https://www.opencontainers.org/
[8]:https://www.hyper.sh/
[9]:https://github.com/opencontainers/runtime-spec
[10]:https://github.com/opencontainers/runc
[11]:https://github.com/cloudfoundry/garden
[12]:https://clearlinux.org/containers
[13]:https://github.com/oracle/railcar
[14]:https://github.com/vbatts/nspawn-oci
[15]:https://github.com/kata-containers

View File

@ -1,21 +1,20 @@
translating---geekpi
How To Test A Package Without Installing It In Linux
如何在 Linux 中不安装软测试一个软件包
======
![](https://www.ostechnix.com/wp-content/uploads/2018/06/nix-720x340.png)
For some reason, you might want to test a package before installing it in your Linux system. If so, youre lucky! Today, I will show you how to do it in Linux using **Nix** package manager. One of the notable feature of Nix package manager is it allows the users to test the packages without having to install them first. This can be helpful when you want to use a particular application temporarily.
出于某种原因,你可能需要在将软件包安装到你的 Linux 系统之前对其进行测试。如果是这样,你很幸运!今天,我将向你展示如何在 Linux 中使用 **Nix** 包管理器来实现。Nix 包管理器的一个显著特性是它允许用户测试软件包而无需先安装它们。当你想要临时使用特定的程序时,这会很有帮助。
### Test A Package Without Installing It In Linux
Make sure you have installed Nix package manager first. If you havent installed it yet, refer the following guide.
### 测试一个软件包而不在 Linux 中安装它
For instance, let us say you want to test your C++ code. You dont have to install GCC. Just run the following command:
确保你先安装了 Nix 包管理器。如果尚未安装,请参阅以下指南。
例如,假设你想测试你的 C++ 代码。你不必安装 GCC。只需运行以下命令
```
$ nix-shell -p gcc
```
This command builds or downloads gcc package and its dependencies, then drops you into a Bash shell where the **gcc** command is present, all without affecting your normal environment.
该命令会构建或下载 gcc 软件包及其依赖项,然后将其放入一个存在 **gcc** 命令的 Bash shell 中,所有这些都不会影响正常环境。
```
LANGUAGE = (unset),
LC_ALL = (unset),
@ -55,7 +54,7 @@ Dload Upload Total Spent Left Speed
```
Check the GCC version:
检查GCC版本
```
[nix-shell:~]$ gcc -v
Using built-in specs.
@ -68,46 +67,46 @@ gcc version 5.4.0 (GCC)
```
Now, go ahead and test the code. Once you are done, type **exit** to return back to your console.
现在,继续并测试代码。完成后,输入 **exit** 返回到控制台。
```
[nix-shell:~]$ exit
exit
```
Once you are exit from the nix-shell, you cant use GCC.
一旦你从 nix-shell 中退出,你就不能使用 GCC。
Here is another example.
这是另一个例子。
```
$ nix-shell -p hello
```
This builds or downloads GNU Hello and its dependencies, then drops you into a Bash shell where the **hello** command is present, all without affecting your normal environment:
这会构建或下载 GNU Hello 和它的依赖关系,然后将其放入 **hello** 命令所在的 Bash shell 中,所有这些都不会影响你的正常环境:
```
[nix-shell:~]$ hello
Hello, world!
```
Type exit to return back to the console.
输入 exit 返回到控制台。
```
[nix-shell:~]$ exit
```
Now test if hello program is available or not.
现在测试你的 hello 程序是否可用。
```
$ hello
hello: command not found
```
For more details about Nix package manager, refer the following guide.
有关 Nix 包管理器的更多详细信息,请参阅以下指南。
Hope this helps! More good stuffs to come. Stay tuned!!
希望本篇对你有帮助!还会有更好的东西。敬请关注!!
Cheers!
干杯!
@ -117,7 +116,7 @@ via: https://www.ostechnix.com/how-to-test-a-package-without-installing-it-in-li
作者:[SK][a]
选题:[lujun9972](https://github.com/lujun9972)
译者:[译者ID](https://github.com/译者ID)
译者:[geekpi](https://github.com/geekpi)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出