mirror of
https://github.com/LCTT/TranslateProject.git
synced 2025-03-06 01:20:12 +08:00
Merge remote-tracking branch 'LCTT/master'
This commit is contained in:
commit
3cc064ec2d
@ -9,7 +9,8 @@ Caffeinated 6.828:实验 1:PC 的引导过程
|
||||
|
||||
本课程中你需要的文件和接下来的实验任务所需要的文件都是通过使用 [Git][1] 版本控制系统来分发的。学习更多关于 Git 的知识,请查看 [Git 用户手册][2],或者,如果你熟悉其它的版本控制系统,这个 [面向 CS 的 Git 概述][3] 可能对你有帮助。
|
||||
|
||||
本课程在 Git 仓库中的地址是 `https://exokernel.scripts.mit.edu/joslab.git`。在你的 Athena 帐户中安装文件,你需要运行如下的命令去克隆课程仓库。你也可以使用 `ssh -X athena.dialup.mit.edu` 去登入到一个公共的 Athena 主机。
|
||||
本课程在 Git 仓库中的地址是 https://exokernel.scripts.mit.edu/joslab.git 。在你的 Athena 帐户中安装文件,你需要运行如下的命令去克隆课程仓库。你也可以使用 `ssh -X athena.dialup.mit.edu` 去登入到一个公共的 Athena 主机。
|
||||
|
||||
```
|
||||
athena% mkdir ~/6.828
|
||||
athena% cd ~/6.828
|
||||
@ -18,16 +19,15 @@ athena% git clone https://exokernel.scripts.mit.edu/joslab.git lab
|
||||
Cloning into lab...
|
||||
athena% cd lab
|
||||
athena%
|
||||
|
||||
```
|
||||
|
||||
Git 可以帮你跟踪代码中的变化。比如,如果你完成了一个练习,想在你的进度中打一个检查点,你可以运行如下的命令去提交你的变更:
|
||||
|
||||
```
|
||||
athena% git commit -am 'my solution for lab1 exercise 9'
|
||||
Created commit 60d2135: my solution for lab1 exercise 9
|
||||
1 files changed, 1 insertions(+), 0 deletions(-)
|
||||
athena%
|
||||
|
||||
```
|
||||
|
||||
你可以使用 `git diff` 命令跟踪你的变更。运行 `git diff` 将显示你的代码自最后一次提交之后的变更,而 `git diff origin/lab1` 将显示这个实验相对于初始代码的变更。在这里,`origin/lab1` 是为了完成这个作业,从我们的服务器上下载的初始代码在 Git 分支上的名字。
|
||||
@ -40,9 +40,10 @@ athena%
|
||||
|
||||
我们为了你便于做实验,为你使用了不同的 Git 仓库。做实验用的仓库位于一个 SSH 服务器后面。你可以拥有你自己的实验仓库,其他的任何同学都不可访问你的这个仓库。为了通过 SSH 服务器的认证,你必须有一对 RSA 密钥,并让服务器知道你的公钥。
|
||||
|
||||
实验代码同时还带有一个脚本,它可以帮你设置如何访问你的实验仓库。在运行这个脚本之前,你必须在我们的 [submission web 界面][5] 上有一个帐户。在登陆页面上,输入你的 Athena 用户名,然后点击 "Mail me my password"。在你的邮箱中将马上接收到一封包含有你的 `6.828` 课程密码的邮件。注意,每次你点击这个按钮的时候,系统将随机给你分配一个新密码。
|
||||
实验代码同时还带有一个脚本,它可以帮你设置如何访问你的实验仓库。在运行这个脚本之前,你必须在我们的 [submission web 界面][5] 上有一个帐户。在登陆页面上,输入你的 Athena 用户名,然后点击 “Mail me my password”。在你的邮箱中将马上接收到一封包含有你的 `6.828` 课程密码的邮件。注意,每次你点击这个按钮的时候,系统将随机给你分配一个新密码。
|
||||
|
||||
现在,你已经有了你的 `6.828` 密码,在 `lab` 目录下,运行如下的命令去配置实践仓库:
|
||||
|
||||
```
|
||||
athena% make handin-prep
|
||||
Using public key from ~/.ssh/id_rsa:
|
||||
@ -59,10 +60,10 @@ Setting up hand-in Git repository...
|
||||
Adding remote repository ssh://josgit@exokernel.mit.edu/joslab.git as 'handin'.
|
||||
Done! Use 'make handin' to submit your lab code.
|
||||
athena%
|
||||
|
||||
```
|
||||
|
||||
如果你没有 RSA 密钥对,这个脚本可能会询问你是否生成一个新的密钥对:
|
||||
|
||||
```
|
||||
athena% make handin-prep
|
||||
SSH key file ~/.ssh/id_rsa does not exists, generate one? [Y/n] Y
|
||||
@ -85,6 +86,7 @@ athena%
|
||||
```
|
||||
|
||||
当你开始动手做实验时,在 `lab` 目录下,输入 `make handin` 去使用 git 做第一次提交。后面将运行 `git push handin HEAD`,它将推送当前分支到远程 `handin` 仓库的同名分支上。
|
||||
|
||||
```
|
||||
athena% git commit -am "ready to submit my lab"
|
||||
[lab1 c2e3c8b] ready to submit my lab
|
||||
@ -136,6 +138,7 @@ athena%
|
||||
在 6.828 中,我们将使用 [QEMU 仿真器][12],它是一个现代化的并且速度非常快的仿真器。虽然 QEMU 内置的监视功能提供了有限的调试支持,但是,QEMU 也可以做为 [GNU 调试器][13] (GDB) 的远程调试目标,我们在这个实验中将使用它来一步一步完成引导过程。
|
||||
|
||||
在开始之前,按照前面 “软件安装“ 中在 Athena 主机上描述的步骤,提取实验 1 的文件到你自己的目录中,然后,在 `lab` 目录中输入 `make`(如果是 BSD 的系统,是输入 `gmake` )来构建最小的 6.828 引导加载器和用于启动的内核。(把在这里我们运行的这些代码称为 ”内核“ 有点夸大,但是,通过这个学期的课程,我们将把这些代码充实起来,成为真正的 ”内核“)
|
||||
|
||||
```
|
||||
athena% cd lab
|
||||
athena% make
|
||||
@ -156,14 +159,16 @@ boot block is 414 bytes (max 510)
|
||||
|
||||
```
|
||||
|
||||
(如果你看到有类似 ”undefined reference to `__udivdi3'" 这样的错误,可能是因为你的电脑上没有 32 位的 ”gcc multilib“。如果你运行在 Debian 或者 Ubuntu,你可以尝试去安装 ”gcc-multilib“ 包。)
|
||||
(如果你看到有类似 ”undefined reference to `__udivdi3'” 这样的错误,可能是因为你的电脑上没有 32 位的 “gcc multilib”。如果你运行在 Debian 或者 Ubuntu,你可以尝试去安装 “gcc-multilib” 包。)
|
||||
|
||||
现在,你可以去运行 QEMU 了,并将上面创建的 `obj/kern/kernel.img` 文件提供给它,以作为仿真 PC 的 “虚拟硬盘”,这个虚拟硬盘中包含了我们的引导加载器(`obj/boot/boot`) 和我们的内核(`obj/kernel`)。
|
||||
|
||||
```
|
||||
athena% make qemu
|
||||
```
|
||||
|
||||
运行 QEMU 时需要使用选项去设置硬盘,以及指示串行端口输出到终端。在 QEMU 窗口中将出现一些文本内容:
|
||||
|
||||
```
|
||||
Booting from Hard Disk...
|
||||
6828 decimal is XXX octal!
|
||||
@ -184,9 +189,10 @@ Type 'help' for a list of commands.
|
||||
K>
|
||||
```
|
||||
|
||||
在 '`Booting from Hard Disk...`' 之后的内容,就是由我们的基本 JOS 内核输出的:`K>` 是包含在我们的内核中的小型监听器或者交互式控制程序的提示符。内核输出的这些行也会出现在你运行 QEMU 的普通 shell 窗口中。这是因为测试和实验分级的原因,我们配置了 JOS 的内核,使它将控制台输出不仅写入到虚拟的 VGA 显示器(就是 QEMU 窗口),也写入到仿真 PC 的虚拟串口上,QEMU 会将虚拟串口上的信息转发到它的标准输出上。同样,JOS 内核也将接收来自键盘和串口的输入,因此,你既可以从 VGA 显示窗口中输入命令,也可以从运行 QEMU 的终端窗口中输入命令。或者,你可以通过运行 `make qemu-nox` 来取消虚拟 VGA 的输出,只使用串行控制台来输出。如果你是通过 SSH 拨号连接到 Athena 主机,这样可能更方便。
|
||||
在 `Booting from Hard Disk...` 之后的内容,就是由我们的基本 JOS 内核输出的:`K>` 是包含在我们的内核中的小型监听器或者交互式控制程序的提示符。内核输出的这些行也会出现在你运行 QEMU 的普通 shell 窗口中。这是因为测试和实验分级的原因,我们配置了 JOS 的内核,使它将控制台输出不仅写入到虚拟的 VGA 显示器(就是 QEMU 窗口),也写入到仿真 PC 的虚拟串口上,QEMU 会将虚拟串口上的信息转发到它的标准输出上。同样,JOS 内核也将接收来自键盘和串口的输入,因此,你既可以从 VGA 显示窗口中输入命令,也可以从运行 QEMU 的终端窗口中输入命令。或者,你可以通过运行 `make qemu-nox` 来取消虚拟 VGA 的输出,只使用串行控制台来输出。如果你是通过 SSH 拨号连接到 Athena 主机,这样可能更方便。
|
||||
|
||||
在这里有两个可以用来监视内核的命令,它们是 `help` 和 `kerninfo`。
|
||||
|
||||
```
|
||||
K> help
|
||||
help - display this list of commands
|
||||
@ -206,7 +212,8 @@ K>
|
||||
|
||||
#### PC 的物理地址空间
|
||||
|
||||
我们现在将更深入去了解 ”关于 PC 是如何启动“ 的更多细节。一台 PC 的物理地址空间是硬编码为如下的布局:
|
||||
我们现在将更深入去了解 “关于 PC 是如何启动” 的更多细节。一台 PC 的物理地址空间是硬编码为如下的布局:
|
||||
|
||||
```
|
||||
+------------------+ <- 0xFFFFFFFF (4GB)
|
||||
| 32-bit |
|
||||
@ -244,15 +251,16 @@ K>
|
||||
|
||||
从 `0x000A0000` 到 `0x000FFFFF` 的 384 KB 的区域是为特定硬件保留的区域,比如,视频显示缓冲和保存在非易失存储中的固件。这个保留区域中最重要的部分是基本输入/输出系统(BIOS),它位于从 `0x000F0000` 到 `0x000FFFFF` 之间的 64KB 大小的区域。在早期的 PC 中,BIOS 在真正的只读存储(ROM)中,但是,现在的 PC 的 BIOS 都保存在可更新的 FLASH 存储中。BIOS 负责执行基本系统初始化工作,比如,激活视频卡和检查已安装的内存数量。这个初始化工作完成之后,BIOS 从相关位置加载操作系统,比如从软盘、硬盘、CD-ROM、或者网络,然后将机器的控制权传递给操作系统。
|
||||
|
||||
当 Intel 最终在 80286 和 80386 处理器上 “打破了 1MB 限制” 之后,这两个处理器各自支持 16MB 和 4GB 物理地址空间,尽管如此,为了确保向下兼容现存软件,PC 架构还是保留着 1 MB 以内物理地址空间的原始布局。因此,现代 PC 的物理内存,在 `0x000A0000` 和 `0x00100000` 之间有一个 "黑洞区域“,将内存分割为 ”低位“ 或者 ”传统内存“ 区域(前 640 KB)和 ”扩展内存“(其它的部分)。除此之外,在 PC 的 32 位物理地址空间顶部之上的一些空间,在全部的物理内存上面,现在一般都由 BIOS 保留给 32 位的 PCI 设备使用。
|
||||
当 Intel 最终在 80286 和 80386 处理器上 “打破了 1MB 限制” 之后,这两个处理器各自支持 16MB 和 4GB 物理地址空间,尽管如此,为了确保向下兼容现存软件,PC 架构还是保留着 1 MB 以内物理地址空间的原始布局。因此,现代 PC 的物理内存,在 `0x000A0000` 和 `0x00100000` 之间有一个 “黑洞区域”,将内存分割为 “低位” 或者 “传统内存” 区域(前 640 KB)和 “扩展内存”(其它的部分)。除此之外,在 PC 的 32 位物理地址空间顶部之上的一些空间,在全部的物理内存上面,现在一般都由 BIOS 保留给 32 位的 PCI 设备使用。
|
||||
|
||||
最新的 x86 处理器可以支持超过 4GB 的物理地址空间,因此,RAM 可以进一步扩展到 `0xFFFFFFFF` 之上。在这种情况下,BIOS 必须在 32 位可寻址空间顶部之上的系统 RAM 上,设置第二个 ”黑洞区域“,以便于为这些 32 位的设备映射留下空间。因为 JOS 设计的限制,它仅可以使用 PC 物理内存的前 256 MB,因此,我们将假设所有的 PC ”仅仅“ 拥有 32 位物理地址空间。但是处理复杂的物理地址空间和其它部分的硬件系统,将涉及到许多年前操作系统开发所遇到的实际挑战之一。
|
||||
最新的 x86 处理器可以支持超过 4GB 的物理地址空间,因此,RAM 可以进一步扩展到 `0xFFFFFFFF` 之上。在这种情况下,BIOS 必须在 32 位可寻址空间顶部之上的系统 RAM 上,设置第二个 “黑洞区域”,以便于为这些 32 位的设备映射留下空间。因为 JOS 设计的限制,它仅可以使用 PC 物理内存的前 256 MB,因此,我们将假设所有的 PC “仅仅” 拥有 32 位物理地址空间。但是处理复杂的物理地址空间和其它部分的硬件系统,将涉及到许多年前操作系统开发所遇到的实际挑战之一。
|
||||
|
||||
#### ROM BIOS
|
||||
|
||||
在实验的这一部分中,你将使用 QEMU 的调试功能去研究 IA-32 相关的计算机是如何引导的。
|
||||
|
||||
打开两个终端窗口,在其中一个中,输入 `make qemu-gdb`(或者 `make qemu-nox-gdb`),这将启动 QEMU,但是处理器在运行第一个指令之前将停止 QEMU,以等待来自 GDB 的调试连接。在第二个终端窗口中,从相同的目录中运行 `make`,以及运行 `make gdb`。你将看到如下的输出。
|
||||
|
||||
```
|
||||
athena% make gdb
|
||||
GNU gdb (GDB) 6.8-debian
|
||||
@ -274,6 +282,7 @@ The target architecture is assumed to be i8086
|
||||
`make gdb` 的运行目标是一个称为 `.gdbrc` 的脚本,它设置了 GDB 在早期引导期间调试所用到的 16 位代码,并且将它指向到正在监听的 QEMU 上。
|
||||
|
||||
下列行:
|
||||
|
||||
```
|
||||
[f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b
|
||||
```
|
||||
@ -284,11 +293,10 @@ The target architecture is assumed to be i8086
|
||||
* PC 使用 `CS = 0xf000` 和 `IP = 0xfff0` 开始运行。
|
||||
* 运行的第一个指令是一个 `jmp` 指令,它跳转段地址 `CS = 0xf000` 和 `IP = 0xe05b`。
|
||||
|
||||
为什么 QEMU 是这样开始的呢?这是因为 Intel 设计的 8088 处理器是这样做的,这个处理器是 IBM 最早用在他们的 PC 上的处理器。因为在一台 PC 中,BIOS 是硬编码在物理地址范围 `0x000f0000-0x000fffff` 中的,这样的设计确保了在机器接通电源或者任何系统重启之后,BIOS 总是能够首先控制机器 —— 这是至关重要的,因为机器接通电源之后,在机器的内存中没有处理器可以运行的任何软件。QEMU 仿真器有它自己的 BIOS,它的位置在处理器的模拟地址空间中。在处理器复位之后,(模拟的)处理器进入了实模式,然后设置 `CS` 为 `0xf000` 、`IP` 为 `0xfff0`,所以,运行开始于那个(`CS:IP`)段地址。那么,段地址 `0xf000:fff0` 是如何转到物理地址的呢?
|
||||
|
||||
在回答这个问题之前,我们需要了解有关实模式地址的知识。在实模式(PC 启动之后就处于实模式)中,物理地址是根据这个公式去转换的:物理地址 = 16 * 段地址 + 偏移。因此,当 PC 设置 `CS` 为 `0xf000` 、`IP` 为 `0xfff0` 之后,物理地址指向到:
|
||||
|
||||
为什么 QEMU 是这样开始的呢?这是因为 Intel 设计的 8088 处理器是这样做的,这个处理器是 IBM 最早用在他们的 PC 上的处理器。因为在一台 PC 中,BIOS 是硬编码在物理地址范围 `0x000f0000-0x000fffff` 中的,这样的设计确保了在机器接通电源或者任何系统重启之后,BIOS 总是能够首先控制机器 —— 这是至关重要的,因为机器接通电源之后,在机器的内存中没有处理器可以运行的任何软件。QEMU 仿真器有它自己的 BIOS,它的位置在处理器的模拟地址空间中。在处理器复位之后,(模拟的)处理器进入了实模式,然后设置 CS 为 `0xf000` 、IP 为 `0xfff0`,所以,运行开始于那个(CS:IP)段地址。那么,段地址 0xf000:fff0 是如何转到物理地址的呢?
|
||||
|
||||
在回答这个问题之前,我们需要了解有关实模式地址的知识。在实模式(PC 启动之后就处于实模式)中,物理地址是根据这个公式去转换的:物理地址 = 16 * 段地址 + 偏移。因此,当 PC 设置 CS 为 `0xf000` 、IP 为 `0xfff0` 之后,物理地址指向到:
|
||||
```
|
||||
16 * 0xf000 + 0xfff0 # in hex multiplication by 16 is
|
||||
= 0xf0000 + 0xfff0 # easy--just append a 0.
|
||||
@ -302,22 +310,20 @@ The target architecture is assumed to be i8086
|
||||
>
|
||||
> 使用 GDB 的 `si`(步进指令)指令去跟踪进入到 ROM BIOS 的更多指令,然后尝试猜测它可能会做什么。你可能需要去查看 [Phil Storrs I/O 端口描述][14],以及在 [6.828 参考资料页面][8] 上的其它资料。不需要了解所有的细节 —— 只要搞明白 BIOS 首先要做什么就可以了。
|
||||
|
||||
当 BIOS 运行后,它将设置一个中断描述符表和初始化各种设备,比如, VGA 显示。在这时,你在 QEMU 窗口中将出现 "`Starting SeaBIOS`" 的信息。
|
||||
当 BIOS 运行后,它将设置一个中断描述符表和初始化各种设备,比如, VGA 显示。在这时,你在 QEMU 窗口中将出现 `Starting SeaBIOS` 的信息。
|
||||
|
||||
在初始化 PCI 产品线和 BIOS 知道的所有重要设备之后,它将搜索可引导设备,比如,一个软盘、硬盘、或者 CD-ROM。最后,当它找到可引导磁盘之后,BIOS 从可引导硬盘上读取引导加载器,然后将控制权交给它。
|
||||
|
||||
### 第二部分:引导加载器
|
||||
|
||||
在 PC 的软盘和硬盘中,将它们分割成 512 字节大小的区域,每个区域称为一个扇区。一个扇区就是磁盘的最小转存单元:每个读或写操作都必须是一个或多个扇区大小,并且按扇区边界进行对齐。如果磁盘是可引导盘,第一个扇区则为引导扇区,因为,第一个扇区中驻留有引导加载器的代码。当 BIOS 找到一个可引导软盘或者硬盘时,它将 512 字节的引导扇区加载进物理地址为 0x7c00 到 `0x7dff` 的内存中,然后使用一个 `jmp` 指令设置 CS:IP 为 `0000:7c00`,并传递控制权到引导加载器。与 BIOS 加载地址一样,这些地址是任意的 —— 但是它们对于 PC 来说是固定的,并且是标准化的。
|
||||
在 PC 的软盘和硬盘中,将它们分割成 512 字节大小的区域,每个区域称为一个扇区。一个扇区就是磁盘的最小转存单元:每个读或写操作都必须是一个或多个扇区大小,并且按扇区边界进行对齐。如果磁盘是可引导盘,第一个扇区则为引导扇区,因为,第一个扇区中驻留有引导加载器的代码。当 BIOS 找到一个可引导软盘或者硬盘时,它将 512 字节的引导扇区加载进物理地址为 `0x7c00` 到 `0x7dff` 的内存中,然后使用一个 `jmp` 指令设置 `CS:IP` 为 `0000:7c00`,并传递控制权到引导加载器。与 BIOS 加载地址一样,这些地址是任意的 —— 但是它们对于 PC 来说是固定的,并且是标准化的。
|
||||
|
||||
后来,随着 PC 的技术进步,它们可以从 CD-ROM 中引导,因此,PC 架构师趁机对引导过程进行轻微的调整。最后的结果使现代的 BIOS 从 CD-ROM 中引导的过程更复杂(并且功能更强大)。CD-ROM 使用 2048 字节大小的扇区,而不是 512 字节的扇区,并且,BIOS 在传递控制权之前,可以从磁盘上加载更大的(不止是一个扇区)引导镜像到内存中。更多内容,请查看 ["El Torito" 可引导 CD-ROM 格式规范][15]。
|
||||
后来,随着 PC 的技术进步,它们可以从 CD-ROM 中引导,因此,PC 架构师趁机对引导过程进行轻微的调整。最后的结果使现代的 BIOS 从 CD-ROM 中引导的过程更复杂(并且功能更强大)。CD-ROM 使用 2048 字节大小的扇区,而不是 512 字节的扇区,并且,BIOS 在传递控制权之前,可以从磁盘上加载更大的(不止是一个扇区)引导镜像到内存中。更多内容,请查看 [“El Torito” 可引导 CD-ROM 格式规范][15]。
|
||||
|
||||
不过对于 6.828,我们将使用传统的硬盘引导机制,意味着我们的引导加载器必须小于 512 字节。引导加载器是由一个汇编源文件 `boot/boot.S` 和一个 C 源文件 `boot/main.c` 构成,仔细研究这些源文件可以让你彻底理解引导加载器都做了些什么。引导加载器必须要做两件主要的事情:
|
||||
|
||||
1. 第一,引导加载器将处理器从实模式切换到 32 位保护模式,因为只有在 32 位保护模式中,软件才能够访问处理器中 1 MB 以上的物理地址空间。关于保护模式将在 [PC 汇编语言][6] 的 1.2.7 和 1.2.8 节中详细描述,更详细的内容请参阅 Intel 架构手册。在这里,你只要理解在保护模式中段地址(段基地址:偏移量)与物理地址转换的差别就可以了,并且转换后的偏移是 32 位而不是 16 位。
|
||||
2. 第二,引导加载器通过 x86 的专用 I/O 指令直接访问 IDE 磁盘设备寄存器,从硬盘中读取内核。如果你想去更好地了解在这里说的专用 I/O 指令,请查看 [6.828 参考页面][8] 上的 ”IDE 硬盘控制器“ 章节。你不用学习太多的专用设备编程方面的内容:在实践中,写设备驱动程序是操作系统开发中的非常重要的部分,但是,从概念或者架构的角度看,它也是最让人乏味的部分。
|
||||
|
||||
|
||||
1. 第一、引导加载器将处理器从实模式切换到 32 位保护模式,因为只有在 32 位保护模式中,软件才能够访问处理器中 1 MB 以上的物理地址空间。关于保护模式将在 [PC 汇编语言][6] 的 1.2.7 和 1.2.8 节中详细描述,更详细的内容请参阅 Intel 架构手册。在这里,你只要理解在保护模式中段地址(段基地址:偏移量)与物理地址转换的差别就可以了,并且转换后的偏移是 32 位而不是 16 位。
|
||||
2. 第二、引导加载器通过 x86 的专用 I/O 指令直接访问 IDE 磁盘设备寄存器,从硬盘中读取内核。如果你想去更好地了解在这里说的专用 I/O 指令,请查看 [6.828 参考页面][8] 上的 “IDE 硬盘控制器” 章节。你不用学习太多的专用设备编程方面的内容:在实践中,写设备驱动程序是操作系统开发中的非常重要的部分,但是,从概念或者架构的角度看,它也是最让人乏味的部分。
|
||||
|
||||
理解了引导加载器源代码之后,我们来看一下 `obj/boot/boot.asm` 文件。这个文件是在引导加载器编译过程中,由我们的 GNUmakefile 创建的引导加载器的反汇编文件。这个反汇编文件让我们可以更容易地看到引导加载器代码所处的物理内存位置,并且也可以更容易地跟踪在 GDB 中步进的引导加载器发生了什么事情。同样的,`obj/kern/kernel.asm` 文件中包含了 JOS 内核的一个反汇编,它也经常被用于内核调试。
|
||||
|
||||
@ -340,8 +346,6 @@ The target architecture is assumed to be i8086
|
||||
* 内核的第一个指令在哪里?
|
||||
* 为从硬盘上获取完整的内核,引导加载器如何决定有多少扇区必须被读入?在哪里能找到这些信息?
|
||||
|
||||
|
||||
|
||||
#### 加载内核
|
||||
|
||||
我们现在来进一步查看引导加载器在 `boot/main.c` 中的 C 语言部分的详细细节。在继续之前,我们先停下来回顾一下 C 语言编程的基础知识。
|
||||
@ -350,7 +354,7 @@ The target architecture is assumed to be i8086
|
||||
>
|
||||
> 下载 [pointers.c][17] 的源代码,运行它,然后确保你理解了输出值的来源的所有内容。尤其是,确保你理解了第 1 行和第 6 行的指针地址的来源、第 2 行到第 4 行的值是如何得到的、以及为什么第 5 行指向的值表面上看像是错误的。
|
||||
>
|
||||
> 如果你对指针的使用不熟悉,Brian Kernighan 和 Dennis Ritchie(就是大家知道的 'K&R')写的《C Programming Language》是一个非常好的参考书。同学们可以去买这本书(这里是 [Amazon 购买链接][18]),或者在 [MIT 的图书馆有 7 个拷贝][19] 中找到其中一个。在 [SIPB Office][20] 也有三个拷贝可以细读。
|
||||
> 如果你对指针的使用不熟悉,Brian Kernighan 和 Dennis Ritchie(就是大家知道的 “K&R”)写的《C Programming Language》是一个非常好的参考书。同学们可以去买这本书(这里是 [Amazon 购买链接][18]),或者在 [MIT 的图书馆的 7 个副本][19] 中找到其中一个。在 [SIPB Office][20] 也有三个副本可以细读。
|
||||
>
|
||||
> 在课程阅读中,[Ted Jensen 写的教程][21] 可以使用,它大量引用了 K&R 的内容。
|
||||
>
|
||||
@ -368,11 +372,10 @@ The target architecture is assumed to be i8086
|
||||
* `.rodata`:只读数据,比如,由 C 编译器生成的 ASCII 字符串常量。(然而我们并不需要操心设置硬件去禁止写入它)
|
||||
* `.data`:保持在程序的初始化数据中的数据节,比如,初始化声明所需要的全局变量,比如,像 `int x = 5;`。
|
||||
|
||||
|
||||
|
||||
当链接器计算程序的内存布局的时候,它为未初始化的全局变量保留一些空间,比如,`int x;`,在内存中的被称为 `.bss` 的节后面会马上跟着一个 `.data`。C 规定 "未初始化的" 全局变量以一个 0 值开始。因此,在 ELF 二进制中 `.bss` 中并不存储内容;而是,链接器只记录地址和`.bss` 节的大小。加载器或者程序自身必须在 `.bss` 节中写入 0。
|
||||
|
||||
通过输入如下的命令来检查在内核中可运行的所有节的名字、大小、以及链接地址的列表:
|
||||
|
||||
```
|
||||
athena% i386-jos-elf-objdump -h obj/kern/kernel
|
||||
```
|
||||
@ -381,7 +384,7 @@ athena% i386-jos-elf-objdump -h obj/kern/kernel
|
||||
|
||||
你将看到更多的节,而不仅是上面列出的那几个,但是,其它的那些节对于我们的实验目标来说并不重要。其它的那些节中大多数都是为了保留调试信息,它们一般包含在程序的可执行文件中,但是,这些节并不会被程序加载器加载到内存中。
|
||||
|
||||
我们需要特别注意 `.text` 节中的 "VMA"(或者链接地址)和 "LMA"(或者加载地址)。一个节的加载地址是那个节加载到内存中的地址。在 ELF 对象中,它保存在 `ph->p_pa` 域(在本案例中,它实际上是物理地址,不过 ELF 规范在这个域的意义方面规定的很模糊)。
|
||||
我们需要特别注意 `.text` 节中的 VMA(或者链接地址)和 LMA(或者加载地址)。一个节的加载地址是那个节加载到内存中的地址。在 ELF 对象中,它保存在 `ph->p_pa` 域(在本案例中,它实际上是物理地址,不过 ELF 规范在这个域的意义方面规定的很模糊)。
|
||||
|
||||
一个节的链接地址是这个节打算在内存中运行时的地址。链接器在二进制代码中以变量的方式去编码这个链接地址,比如,当代码需要全局变量的地址时,如果二进制代码从一个未链接的地址去运行,结果将是无法运行。(它一般是去生成一个不包含任何一个绝对地址的、与位置无关的代码。现在的共享库大量使用的就是这种方法,但这是以性能和复杂性为代价的,所以,我们在 6.828 中不使用这种方法。)
|
||||
|
||||
@ -394,7 +397,7 @@ BIOS 加载引导扇区到内存中的 0x7c00 地址,因此,这就是引导
|
||||
|
||||
> **练习 5**
|
||||
>
|
||||
> 如果你得到一个错误的引导加载器链接地址,通过再次跟踪引导加载器的前几个指令,你将会发现第一个指令会 ”中断“ 或者出错。然后在 `boot/Makefrag` 修改链接地址来修复错误,运行 `make clean`,使用 `make` 重新编译,然后再次跟踪引导加载器去查看会发生什么事情。不要忘了改回正确的链接地址,然后再次 `make clean`!
|
||||
> 如果你得到一个错误的引导加载器链接地址,通过再次跟踪引导加载器的前几个指令,你将会发现第一个指令会 “中断” 或者出错。然后在 `boot/Makefrag` 修改链接地址来修复错误,运行 `make clean`,使用 `make` 重新编译,然后再次跟踪引导加载器去查看会发生什么事情。不要忘了改回正确的链接地址,然后再次 `make clean`!
|
||||
|
||||
我们继续来看内核的加载和链接地址。与引导加载器不同,这里有两个不同的地址:内核告诉引导加载器加载它到内存的低位地址(小于 1 MB 的地址),但是它期望在一个高位地址来运行。我们将在下一节中深入研究它是如何实现的。
|
||||
|
||||
@ -407,9 +410,9 @@ athena% i386-jos-elf-objdump -f obj/kern/kernel
|
||||
|
||||
> **练习 6**
|
||||
>
|
||||
> 我们可以使用 GDB 的 `x` 命令去检查内存。[GDB 手册][23] 上讲的非常详细,但是现在,我们知道命令 `x/Nx ADDR` 是输出地址 `ADDR` 上 `N` 个词(word~~,致校对,word 有“单词”、“命令”、“消息”、“结论” 等意思,到底哪个更适合,我个人认为根据上下文,”单词“ 更适合一些,这里推送的 ”词“ 可能是指令,也可能是数据~~)就够了。(注意在命令中所有的 `x` 都是小写。)警告:词(word)的多少并没有一个普遍的标准。在 GNU 汇编中,一个词(word)是两个字节(在 xorw 中的 'w',它在这个词中就是 2 个字节)。
|
||||
> 我们可以使用 GDB 的 `x` 命令去检查内存。[GDB 手册][23] 上讲的非常详细,但是现在,我们知道命令 `x/Nx ADDR` 是输出地址 `ADDR` 上 `N` 个<ruby>词<rt>word</rt></ruby>就够了。(注意在命令中所有的 `x` 都是小写。)警告:<ruby>词<rt>word</rt></ruby>的多少并没有一个普遍的标准。在 GNU 汇编中,一个<ruby>词<rt>word</rt></ruby>是两个字节(在 xorw 中的 'w',它在这个词中就是 2 个字节)。
|
||||
|
||||
重置机器(退出 QEMU/GDB 然后再次启动它们)。检查内存中在 `0x00100000` 地址上的 8 个词(word),输出 BIOS 上的引导加载器入口,然后再次找出引导载器上的内核的入口。为什么它们不一样?在第二个断点上有什么内容?(你并不用真的在 QEMU 上去回答这个问题,只需要思考就可以。)
|
||||
重置机器(退出 QEMU/GDB 然后再次启动它们)。检查内存中在 `0x00100000` 地址上的 8 个词,输出 BIOS 上的引导加载器入口,然后再次找出引导载器上的内核的入口。为什么它们不一样?在第二个断点上有什么内容?(你并不用真的在 QEMU 上去回答这个问题,只需要思考就可以。)
|
||||
|
||||
### 第三部分:内核
|
||||
|
||||
@ -435,7 +438,7 @@ athena% i386-jos-elf-objdump -f obj/kern/kernel
|
||||
|
||||
#### 格式化控制台的输出
|
||||
|
||||
大多数人认为像 `printf()` 这样的函数是天生就有的,有时甚至认为这是 C 语言的 ”原语“。但是在操作系统的内核中,我们需要自己去实现所有的 I/O。
|
||||
大多数人认为像 `printf()` 这样的函数是天生就有的,有时甚至认为这是 C 语言的 “原语”。但是在操作系统的内核中,我们需要自己去实现所有的 I/O。
|
||||
|
||||
通过阅读 `kern/printf.c`、`lib/printfmt.c`、以及 `kern/console.c`,确保你理解了它们之间的关系。在后面的实验中,你将会明白为什么 `printfmt.c` 是位于单独的 `lib` 目录中。
|
||||
|
||||
@ -473,15 +476,15 @@ athena% i386-jos-elf-objdump -f obj/kern/kernel
|
||||
>
|
||||
> 4. 运行下列代码:
|
||||
>
|
||||
> ```
|
||||
> ```
|
||||
> unsigned int i = 0x00646c72;
|
||||
> cprintf("H%x Wo%s", 57616, &i);
|
||||
> ```
|
||||
> 输出是什么?解释如何在前面的练习中一步一步实现这个输出。这是一个 [ASCII 表][24],它是一个字节到字符串的映射表。
|
||||
> 输出是什么?解释如何在前面的练习中一步一步实现这个输出。这是一个 [ASCII 表][24],它是一个字节到字符串的映射表。
|
||||
>
|
||||
> 这个输出取决于 x86 是小端法这一事实。如果这个 x86 采用大端法格式,你怎么去设置 `i`,以产生相同的输出?你需要将 `57616` 改变为一个不同值吗?
|
||||
> 这个输出取决于 x86 是小端法这一事实。如果这个 x86 采用大端法格式,你怎么去设置 `i`,以产生相同的输出?你需要将 `57616` 改变为一个不同值吗?
|
||||
>
|
||||
> [这是小端法和大端法的描述][25] 和 [一个更古怪的描述][26]。
|
||||
> [这是小端法和大端法的描述][25] 和 [一个更古怪的描述][26]。
|
||||
>
|
||||
> 5. 在下列代码中,`y=` 会输出什么?(注意:这个问题没有确切值)为什么会发生这种情况?
|
||||
> `cprintf("x=%d y=%d", 3);`
|
||||
@ -518,7 +521,7 @@ Stack backtrace:
|
||||
|
||||
输出的第一行列出了当前运行的函数,名字为 `mon_backtrace`,就是它自己,第二行列出了被 `mon_backtrace` 调用的函数,第三行列出了另一个被调用的函数,依次类推。你可以输出所有未完成的栈帧。通过研究 `kern/entry.S`,你可以发现,有一个很容易的方法告诉你何时停止。
|
||||
|
||||
在每一行中,`ebp` 表示了那个函数进入栈的基指针:即,栈指针的位置,它就是函数进入之后,函数的前序代码设置的基指针。`eip` 值列出的是函数的返回指令指针:当函数返回时,指令地址将控制返回。返回指令指针一般指向 `call` 指令之后的指令(想一想为什么?)。在 `args` 之后列出的五个十六进制值是在问题中传递给函数的前五个参数。当然,如果函数调用时传递的参数少于五个,那么,在这里就不会列出全部五个值了。(为什么跟踪回溯代码不能检测到调用时实际上传递了多少个参数?如何去修复这个 ”缺陷“?)
|
||||
在每一行中,`ebp` 表示了那个函数进入栈的基指针:即,栈指针的位置,它就是函数进入之后,函数的前序代码设置的基指针。`eip` 值列出的是函数的返回指令指针:当函数返回时,指令地址将控制返回。返回指令指针一般指向 `call` 指令之后的指令(想一想为什么?)。在 `args` 之后列出的五个十六进制值是在问题中传递给函数的前五个参数。当然,如果函数调用时传递的参数少于五个,那么,在这里就不会列出全部五个值了。(为什么跟踪回溯代码不能检测到调用时实际上传递了多少个参数?如何去修复这个 “缺陷”?)
|
||||
|
||||
下面是在阅读 K&R 的书中的第 5 章中的一些关键点,为了接下来的练习和将来的实验,你应该记住它们。
|
||||
|
||||
@ -526,8 +529,6 @@ Stack backtrace:
|
||||
* `p[i]` 的定义与 `*(p+i)` 定义是相同的,都反映了在内存中由 `p` 指向的第 `i` 个对象。当对象大于一个字节时,上面的加法规则可以使这个定义正常工作。
|
||||
* `&p[i]` 与 `(p+i)` 是相同的,获取在内存中由 p 指向的第 `i` 个对象的地址。
|
||||
|
||||
|
||||
|
||||
虽然大多数 C 程序不需要在指针和整数之间转换,但是操作系统经常做这种转换。不论何时,当你看到一个涉及内存地址的加法时,你要问你自己,你到底是要做一个整数加法还是一个指针加法,以确保做完加法后的值是正确的,而不是相乘后的结果。
|
||||
|
||||
> **练 11**
|
||||
@ -545,16 +546,16 @@ Stack backtrace:
|
||||
在 `debuginfo_eip` 中,`__STAB_*` 来自哪里?这个问题的答案很长;为帮助你找到答案,下面是你需要做的一些事情:
|
||||
|
||||
* 在 `kern/kernel.ld` 文件中查找 `__STAB_*`
|
||||
* 运行 i386-jos-elf-objdump -h obj/kern/kernel
|
||||
* 运行 i386-jos-elf-objdump -G obj/kern/kernel
|
||||
* 运行 i386-jos-elf-gcc -pipe -nostdinc -O2 -fno-builtin -I. -MD -Wall -Wno-format -DJOS_KERNEL -gstabs -c -S kern/init.c, and look at init.s。
|
||||
* 运行 `i386-jos-elf-objdump -h obj/kern/kernel`
|
||||
* 运行 `i386-jos-elf-objdump -G obj/kern/kernel`
|
||||
* 运行 `i386-jos-elf-gcc -pipe -nostdinc -O2 -fno-builtin -I. -MD -Wall -Wno-format -DJOS_KERNEL -gstabs -c -S kern/init.c, and look at init.s`。
|
||||
* 如果引导加载器在加载二进制内核时,将符号表作为内核的一部分加载进内存中,那么,去查看它。
|
||||
|
||||
|
||||
|
||||
通过在 `stab_binsearch` 中插入调用,可以完成在 `debuginfo_eip` 中通过地址找到行号的功能。
|
||||
|
||||
在内核监视中添加一个 `backtrace` 命令,扩展你实现的 `mon_backtrace` 的功能,通过调用 `debuginfo_eip`,然后以下面的格式来输出每个栈帧行:
|
||||
|
||||
```
|
||||
K> backtrace
|
||||
Stack backtrace:
|
||||
@ -571,7 +572,7 @@ K>
|
||||
|
||||
为防止评级脚本引起混乱,应该将文件和函数名输出在单独的行上。
|
||||
|
||||
提示:`printf` 格式的字符串提供一个易用(尽管有些难理解)的方式去输出非空终止(non-null-terminated)字符串,就像在 STABS 表中的这些一样。`printf("%.*s", length, string)` 输出 `string` 中的最多 `length` 个字符。查阅 `printf` 的 man 页面去搞清楚为什么这样工作。
|
||||
提示:`printf` 格式的字符串提供一个易用(尽管有些难理解)的方式去输出<ruby>非空终止<rt>non-null-terminated</rt></ruby>字符串,就像在 STABS 表中的这些一样。`printf("%.*s", length, string)` 输出 `string` 中的最多 `length` 个字符。查阅 `printf` 的 man 页面去搞清楚为什么这样工作。
|
||||
|
||||
你可以从 `backtrace` 中找到那些没有的功能。比如,你或者可能看到一个到 `monitor()` 的调用,但是没有到 `runcmd()` 中。这是因为编译器的行内(in-lines)函数调用。其它的优化可能导致你看到一些意外的行号。如果你从 `GNUMakefile` 删除 `-O2` 参数,`backtraces` 可能会更有意义(但是你的内核将运行的更慢)。
|
||||
|
||||
@ -583,7 +584,7 @@ via: https://sipb.mit.edu/iap/6.828/lab/lab1/
|
||||
|
||||
作者:[mit][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/) 荣誉推出
|
||||
|
@ -5,44 +5,52 @@
|
||||
作为程序员,你经常会发现自己是某些网站的永久访问者。它们可以是教程、参考或论坛。因此,在这篇文章中,让我们看看给程序员的最佳网站。
|
||||
|
||||
### W3Schools
|
||||
W3Schools 是为初学者和有经验的 Web 开发人员学习各种编程语言的最佳网站之一。你可以学习 HTML5、CSS3、PHP、 JavaScript、ASP 等。
|
||||
|
||||
[W3Schools][10] 是为初学者和有经验的 Web 开发人员学习各种编程语言的最佳网站之一。你可以学习 HTML5、CSS3、PHP、 JavaScript、ASP 等。
|
||||
|
||||
更重要的是,该网站为网页开发人员提供了大量资源和参考资料。
|
||||
|
||||
[![w3schools logo][2]][3]
|
||||
|
||||
你可以快速浏览各种关键字及其功能。该网站非常具有互动性,它允许你在网站本身的嵌入式编辑器中尝试和练习代码。该网站是你作为网页开发人员经常访问的少数网站之一。
|
||||
你可以快速浏览各种关键字及其功能。该网站非常具有互动性,它允许你在网站本身的嵌入式编辑器中尝试和练习代码。该网站是你作为网页开发人员少数需要经常访问的网站之一。
|
||||
|
||||
(LCTT 译注:有一个国内网站 www.w3school.com.cn 提供类似的中文内容,但二者似无关系。)
|
||||
|
||||
### GeeksforGeeks
|
||||
GeeksforGeeks 是一个主要专注于计算机科学的网站。它有大量的算法,解决方案和编程问题。
|
||||
|
||||
[GeeksforGeeks][11] 是一个主要专注于计算机科学的网站。它有大量的算法,解决方案和编程问题。
|
||||
|
||||
[![geeksforgeeks programming support][4]][5]
|
||||
|
||||
该网站也有很多面试中经常问到的问题。由于该网站更多地涉及计算机科学,因此你可以找到很多编程问题在大多数著名语言下的解决方案。
|
||||
|
||||
### TutorialsPoint
|
||||
一个学习任何东西的地方。TutorialsPoint 有一些又好又简单的教程,它可以教你任何编程语言。我真的很喜欢这个网站,它不仅限于通用编程语言。
|
||||
|
||||
一个学习任何东西的地方。[TutorialsPoint][12] 有一些又好又简单的教程,它可以教你任何编程语言。我真的很喜欢这个网站,它不仅限于通用编程语言。
|
||||
|
||||

|
||||
|
||||
你可以在这里上找到几乎所有语言框架的教程。
|
||||
|
||||
### StackOverflow
|
||||
你可能已经知道 StackOverflow 是遇到程序员的地方。你在代码中遇到问题,只要在 StackOverflow 问一个问题,来自互联网的程序员将会在那里帮助你。
|
||||
|
||||
你可能已经知道 [StackOverflow][13] 是遇到程序员的地方。你在代码中遇到问题,只要在 StackOverflow 问一个问题,来自互联网的程序员将会在那里帮助你。
|
||||
|
||||
[![stackoverflow linux programming website][6]][7]
|
||||
|
||||
关于 StackOverflow 最好的部分是几乎所有的问题都得到了答案。你可能会从其他程序员的几个不同观点获得答案。
|
||||
关于 StackOverflow 最好的是几乎所有的问题都得到了答案。你可能会从其他程序员的几个不同观点获得答案。
|
||||
|
||||
### HackerRank
|
||||
HackerRank 是一个你可以参与各种编码竞赛并检测你的竞争能力的网站。
|
||||
|
||||
[HackerRank][14] 是一个你可以参与各种编码竞赛并检测你的竞争能力的网站。
|
||||
|
||||
[![hackerrank programming forums][8]][9]
|
||||
|
||||
这里有以各种编程语言举办的各种比赛,赢得比赛将增加你的分数。这个分数可以让你处于最高级别,并增加你获得一些软件公司注意的机会。
|
||||
|
||||
### Codebeautify
|
||||
由于我们是程序员,所以美不是我们所关心的。很多时候,我们的代码很难被其他人阅读。Codebeautify 可以使你的代码易于阅读。
|
||||
|
||||
由于我们是程序员,所以美不是我们所关心的。很多时候,我们的代码很难被其他人阅读。[Codebeautify][15] 可以使你的代码易于阅读。
|
||||
|
||||

|
||||
|
||||
@ -58,7 +66,7 @@ via: http://www.theitstuff.com/best-websites-programmers
|
||||
作者:[Rishabh Kandari][a]
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
@ -72,3 +80,9 @@ via: http://www.theitstuff.com/best-websites-programmers
|
||||
[7]:http://www.theitstuff.com/wp-content/uploads/2017/12/stackoverflow-linux-programming-website.png
|
||||
[8]:http://www.theitstuff.com/wp-content/uploads/2017/12/hackerrank-programming-forums-550x118.png
|
||||
[9]:http://www.theitstuff.com/wp-content/uploads/2017/12/hackerrank-programming-forums.png
|
||||
[10]:https://www.w3schools.com/
|
||||
[11]:http://www.geeksforgeeks.org/
|
||||
[12]:https://www.tutorialspoint.com/
|
||||
[13]:https://stackoverflow.com/
|
||||
[14]:https://www.hackerrank.com/
|
||||
[15]:https://codebeautify.org/
|
635
published/20180416 Running Jenkins builds in containers.md
Normal file
635
published/20180416 Running Jenkins builds in containers.md
Normal file
@ -0,0 +1,635 @@
|
||||
完全指南:在容器中运行 Jenkins 构建
|
||||
======
|
||||
|
||||
> 容器应用程序平台能够动态地启动具有资源限制的独立容器,从而改变了运行 CI/CD 任务的方式。
|
||||
|
||||

|
||||
|
||||
现今,由于 [Docker][1] 和 [Kubernetes][2](K8S)提供了可扩展、可管理的应用平台,将应用运行在容器中的实践已经被企业广泛接受。近些年势头很猛的[微服务架构][3]也很适合用容器实现。
|
||||
|
||||
容器应用平台可以动态启动指定资源配额、互相隔离的容器,这是其最主要的优势之一。让我们看看这会对我们运行<ruby>持续集成/持续部署<rt>continuous integration/continuous development</rt></ruby>(CI/CD)任务的方式产生怎样的改变。
|
||||
|
||||
构建并打包应用需要一定的环境,要求能够下载源代码、使用相关依赖及已经安装构建工具。作为构建的一部分,运行单元及组件测试可能会用到本地端口或需要运行第三方应用(如数据库及消息中间件等)。另外,我们一般定制化多台构建服务器,每台执行一种指定类型的构建任务。为方便测试,我们维护一些实例专门用于运行第三方应用(或者试图在构建服务器上启动这些第三方应用),避免并行运行构建任务导致结果互相干扰。为 CI/CD 环境定制化构建服务器是一项繁琐的工作,而且随着开发团队使用的开发平台或其版本变更,会需要大量的构建服务器用于不同的任务。
|
||||
|
||||
一旦我们有了容器管理平台(自建或在云端),将资源密集型的 CI/CD 任务在动态生成的容器中执行是比较合理的。在这种方案中,每个构建任务运行在独立启动并配置的构建环境中。构建过程中,构建任务的测试环节可以任意使用隔离环境中的可用资源;此外,我们也可以在辅助容器中启动一个第三方应用,只在构建任务生命周期中为测试提供服务。
|
||||
|
||||
听上去不错,让我们在现实环境中实践一下。
|
||||
|
||||
注:本文基于现实中已有的解决方案,即一个在 [Red Hat OpenShift][4] v3.7 集群上运行的项目。OpenShift 是企业级的 Kubernetes 版本,故这些实践也适用于 K8S 集群。如果愿意尝试,可以下载 [Red Hat CDK][5],运行 `jenkins-ephemeral` 或 `jenkins-persistent` [模板][6]在 OpenShift 上创建定制化好的 Jenkins 管理节点。
|
||||
|
||||
### 解决方案概述
|
||||
|
||||
在 OpenShift 容器中执行 CI/CD 任务(构建和测试等) 的方案基于[分布式 Jenkins 构建][7],具体如下:
|
||||
|
||||
* 我们需要一个 Jenkins 主节点;可以运行在集群中,也可以是外部提供
|
||||
* 支持 Jenkins 特性和插件,以便已有项目仍可使用
|
||||
* 可以用 Jenkins GUI 配置、运行任务或查看任务输出
|
||||
* 如果你愿意编码,也可以使用 [Jenkins Pipeline][8]
|
||||
|
||||
从技术角度来看,运行任务的动态容器是 Jenkins 代理节点。当构建启动时,首先是一个新节点启动,通过 Jenkins 主节点的 JNLP(5000 端口) 告知就绪状态。在代理节点启动并提取构建任务之前,构建任务处于排队状态。就像通常 Jenkins 代理服务器那样,构建输出会送达主节点;不同的是,构建完成后代理节点容器会自动关闭。
|
||||
|
||||

|
||||
|
||||
不同类型的构建任务(例如 Java、 NodeJS、 Python等)对应不同的代理节点。这并不新奇,之前也是使用标签来限制哪些代理节点可以运行指定的构建任务。启动用于构建任务的 Jenkins 代理节点容器需要配置参数,具体如下:
|
||||
|
||||
* 用于启动容器的 Docker 镜像
|
||||
* 资源限制
|
||||
* 环境变量
|
||||
* 挂载的卷
|
||||
|
||||
这里用到的关键组件是 [Jenkins Kubernetes 插件][9]。该插件(通过使用一个服务账号) 与 K8S 集群交互,可以启动和关闭代理节点。在插件的配置管理中,多种代理节点类型表现为多种 Kubernetes pod 模板,它们通过项目标签对应。
|
||||
|
||||
这些[代理节点镜像][10]以开箱即用的方式提供(也有 [CentOS7][11] 系统的版本):
|
||||
|
||||
* [jenkins-slave-base-rhel7][12]:基础镜像,启动与 Jenkins 主节点连接的代理节点;其中 Java 堆大小根据容器内容设置
|
||||
* [jenkins-slave-maven-rhel7][13]:用于 Maven 和 Gradle 构建的镜像(从基础镜像扩展)
|
||||
* [jenkins-slave-nodejs-rhel7][14]:包含 NodeJS4 工具的镜像(从基础镜像扩展)
|
||||
|
||||
注意:本解决方案与 OpenShift 中的 [Source-to-Image(S2I)][15] 构建无关,虽然后者也可以用于某些特定的 CI/CD 任务。
|
||||
|
||||
### 入门学习资料
|
||||
|
||||
有很多不错的博客和文档介绍了如何在 OpenShift 上执行 Jenkins 构建。不妨从下面这些开始:
|
||||
|
||||
* [OpenShift Jenkins][29] 镜像文档及 [源代码][30]
|
||||
* 网络播客:[基于 OpenShift 的 CI/CD][31]
|
||||
* [外部 Jenkins 集成][32] 剧本
|
||||
|
||||
阅读这些博客和文档有助于完整的理解本解决方案。在本文中,我们主要关注具体实践中遇到的各类问题。
|
||||
|
||||
### 构建我的应用
|
||||
|
||||
作为[示例项目][16],我们选取了包含如下构建步骤的 Java 项目:
|
||||
|
||||
* **代码源:** 从一个 Git 代码库中获取项目代码
|
||||
* **使用 Maven 编译:** 依赖可从内部仓库获取,(不妨使用 Apache Nexus) 镜像自外部 Maven 仓库
|
||||
* **发布成品:** 将编译好的 JAR 上传至内部仓库
|
||||
|
||||
在 CI/CD 过程中,我们需要与 Git 和 Nexus 交互,故 Jenkins 任务需要能够访问这些系统。这要求参数配置和已存储凭证可以在下列位置进行管理:
|
||||
|
||||
* **在 Jenkins 中:** 我们可以在 Jenkins 中添加凭证,通过 Git 插件能够对项目添加和使用文件(使用容器不会改变操作)
|
||||
* **在 OpenShift 中:** 使用 ConfigMap 和 Secret 对象,以文件或环境变量的形式附加到 Jenkins 代理容器中
|
||||
* **在高度定制化的 Docker 容器中:** 镜像是定制化的,已包含完成特定类型构建的全部特性;从一个代理镜像进行扩展即可得到。
|
||||
|
||||
你可以按自己的喜好选择一种实现方式,甚至你最终可能混用多种实现方式。下面我们采用第二种实现方式,即首选在 OpenShift 中管理参数配置。使用 Kubernetes 插件配置来定制化 Maven 代理容器,包括设置环境变量和映射文件等。
|
||||
|
||||
注意:对于 Kubernetes 插件 v1.0 版,由于 [bug][17],在 UI 界面增加环境变量并不生效。可以升级插件,或(作为变通方案) 直接修改 `config.xml` 文件并重启 Jenkins。
|
||||
|
||||
### 从 Git 获取源代码
|
||||
|
||||
从公共 Git 仓库获取源代码很容易。但对于私有 Git 仓库,不仅需要认证操作,客户端还需要信任服务器以便建立安全连接。一般而言,通过两种协议获取源代码:
|
||||
|
||||
* HTTPS:验证通过用户名/密码完成。Git 服务器的 SSL 证书必须被代理节点信任,这仅在证书被自建 CA 签名时才需要特别注意。
|
||||
|
||||
```
|
||||
git clone https://git.mycompany.com:443/myapplication.git
|
||||
```
|
||||
* SSH:验证通过私钥完成。如果服务器的公钥指纹出现在 `known_hosts` 文件中,那么该服务器是被信任的。
|
||||
|
||||
```
|
||||
git clone ssh://git@git.mycompany.com:22/myapplication.git
|
||||
```
|
||||
|
||||
对于手动操作,使用用户名/密码通过 HTTP 方式下载源代码是可行的;但对于自动构建而言,SSH 是更佳的选择。
|
||||
|
||||
#### 通过 SSH 方式使用 Git
|
||||
|
||||
要通过 SSH 方式下载源代码,我们需要保证代理容器与 Git 的 SSH 端口之间可以建立 SSH 连接。首先,我们需要创建一个私钥-公钥对。使用如下命令生成:
|
||||
|
||||
```
|
||||
ssh keygen -t rsa -b 2048 -f my-git-ssh -N ''
|
||||
```
|
||||
|
||||
命令生成的私钥位于 `my-git-ssh` 文件中(口令为空),对应的公钥位于 `my-git-ssh.pub` 文件中。将公钥添加至 Git 服务器的对应用户下(推荐使用“服务账号”);网页界面一般支持公钥上传。为建立 SSH 连接,我们还需要在代理容器上配置两个文件:
|
||||
|
||||
* 私钥文件位于 `~/.ssh/id_rsa`
|
||||
* 服务器的公钥位于 `~/.ssh/known_hosts`。要实现这一点,运行 `ssh git.mycompany.com` 并接受服务器指纹,系统会在 `~/.ssh/known_hosts` 文件中增加一行。这样需求得到了满足。
|
||||
|
||||
将 `id_rsa` 对应的私钥和 `known_hosts` 对应的公钥保存到一个 OpenShift 的 secret(或 ConfigMap) 对象中。
|
||||
|
||||
```
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: mygit-ssh
|
||||
stringData:
|
||||
id_rsa: |-
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
...
|
||||
-----END RSA PRIVATE KEY-----
|
||||
known_hosts: |-
|
||||
git.mycompany.com ecdsa-sha2-nistp256 AAA...
|
||||
```
|
||||
|
||||
在 Kubernetes 插件中将 secret 对象配置为卷,挂载到 `/home/jenkins/.ssh/`,供 Maven pod 使用。secret 中的每个对象对应挂载目录的一个文件,文件名与 key 名称相符。我们可以使用 UI(管理 Jenkins / 配置 / 云 / Kubernetes),也可以直接编辑 Jenkins 配置文件 `/var/lib/jenkins/config.xml`:
|
||||
|
||||
```
|
||||
<org.csanchez.jenkins.plugins.kubernetes.PodTemplate>
|
||||
<name>maven</name>
|
||||
...
|
||||
<volumes>
|
||||
<org.csanchez.jenkins.plugins.kubernetes.volumes.SecretVolume>
|
||||
<mountPath>/home/jenkins/.ssh</mountPath>
|
||||
<secretName>mygit-ssh</secretName>
|
||||
</org.csanchez.jenkins.plugins.kubernetes.volumes.SecretVolume>
|
||||
</volumes>
|
||||
```
|
||||
|
||||
此时,在代理节点上运行的任务应该可以通过 SSH 方式从 Git 代码库获取源代码。
|
||||
|
||||
注:我们也可以在 `~/.ssh/config` 文件中自定义 SSH 连接。例如,如果你不想处理 `known_hosts` 或私钥位于其它挂载目录中:
|
||||
|
||||
```
|
||||
Host git.mycompany.com
|
||||
StrictHostKeyChecking no
|
||||
IdentityFile /home/jenkins/.config/git-secret/ssh-privatekey
|
||||
```
|
||||
|
||||
#### 通过 HTTP 方式使用 Git
|
||||
|
||||
如果你选择使用 HTTP 方式下载,在指定的 [Git-credential-store][18] 文件中添加用户名/密码:
|
||||
|
||||
* 例如,在一个 OpenShift secret 对象中增加 `/home/jenkins/.config/git-secret/credentials` 文件对应,其中每个站点对应文件中的一行:
|
||||
|
||||
```
|
||||
https://username:password@git.mycompany.com
|
||||
https://user:pass@github.com
|
||||
```
|
||||
|
||||
* 在 [git-config][19] 配置中启用该文件,其中配置文件默认路径为 `/home/jenkins/.config/git/config`:
|
||||
|
||||
```
|
||||
[credential]
|
||||
helper = store --file=/home/jenkins/.config/git-secret/credentials
|
||||
```
|
||||
|
||||
如果 Git 服务使用了自有 CA 签名的证书,为代理容器设置环境变量 `GIT_SSL_NO_VERIFY=true` 是最便捷的方式。更恰当的解决方案包括如下两步:
|
||||
|
||||
* 利用 ConfigMap 将自有 CA 的公钥映射到一个路径下的文件中,例如 `/usr/ca/myTrustedCA.pem`)。
|
||||
* 通过环境变量 `GIT_SSL_CAINFO=/usr/ca/myTrustedCA.pem` 或上面提到的 `git-config` 文件的方式,将证书路径告知 Git。
|
||||
|
||||
```
|
||||
[http "https://git.mycompany.com"]
|
||||
sslCAInfo = /usr/ca/myTrustedCA.pem
|
||||
```
|
||||
|
||||
注:在 OpenShift v3.7 及早期版本中,ConfigMap 及 secret 的挂载点之间[不能相互覆盖][20],故我们不能同时映射 `/home/jenkins` 和 `/home/jenkins/dir`。因此,上面的代码中并没有使用常见的文件路径。预计 OpenShift v3.9 版本会修复这个问题。
|
||||
|
||||
### Maven
|
||||
|
||||
要完成 Maven 构建,一般需要完成如下两步:
|
||||
|
||||
* 建立一个社区 Maven 库(例如 Apache Nexus),充当外部库的代理。将其当作镜像使用。
|
||||
* 这个内部库可能提供 HTTPS 服务,其中使用自建 CA 签名的证书。
|
||||
|
||||
对于容器中运行构建的实践而言,使用内部 Maven 库是非常关键的,因为容器启动后并没有本地库或缓存,这导致每次构建时 Maven 都下载全部的 Jar 文件。在本地网络使用内部代理库下载明显快于从因特网下载。
|
||||
|
||||
[Maven Jenkins 代理][13]镜像允许配置环境变量,指定代理的 URL。在 Kubernetes 插件的容器模板中设置如下:
|
||||
|
||||
```
|
||||
MAVEN_MIRROR_URL=https://nexus.mycompany.com/repository/maven-public
|
||||
```
|
||||
|
||||
构建好的成品(JAR) 也应该保存到库中,可以是上面提到的用于提供依赖的镜像库,也可以是其它库。Maven 完成 `deploy` 操作需要在 `pom.xml` 的[分发管理][21] 下配置库 URL,这与代理镜像无关。
|
||||
|
||||
```
|
||||
<project ...>
|
||||
<distributionManagement>
|
||||
<snapshotRepository>
|
||||
<id>mynexus</id>
|
||||
<url>https://nexus.mycompany.com/repository/maven-snapshots/</url>
|
||||
</snapshotRepository>
|
||||
<repository>
|
||||
<id>mynexus</id>
|
||||
<url>https://nexus.mycompany.com/repository/maven-releases/</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
```
|
||||
|
||||
上传成品可能涉及认证。在这种情况下,在 `settings.xml` 中配置的用户名/密码要与 `pom.xml` 文件中的对应的服务器 `id` 下的设置匹配。我们可以使用 OpenShift secret 将包含 URL、用户名和密码的完整 `settings.xml` 映射到 Maven Jenkins 代理容器中。另外,也可以使用环境变量。具体如下:
|
||||
|
||||
* 利用 secret 为容器添加环境变量:
|
||||
|
||||
```
|
||||
MAVEN_SERVER_USERNAME=admin
|
||||
MAVEN_SERVER_PASSWORD=admin123
|
||||
```
|
||||
|
||||
* 利用 config map 将 `settings.xml` 挂载至 `/home/jenkins/.m2/settings.xml`:
|
||||
|
||||
```
|
||||
<settings ...>
|
||||
<mirrors>
|
||||
<mirror>
|
||||
<mirrorOf>external:*</mirrorOf>
|
||||
<url>${env.MAVEN_MIRROR_URL}</url>
|
||||
<id>mirror</id>
|
||||
</mirror>
|
||||
</mirrors>
|
||||
<servers>
|
||||
<server>
|
||||
<id>mynexus</id>
|
||||
<username>${env.MAVEN_SERVER_USERNAME}</username>
|
||||
<password>${env.MAVEN_SERVER_PASSWORD}</password>
|
||||
</server>
|
||||
</servers>
|
||||
</settings>
|
||||
```
|
||||
|
||||
禁用交互模式(即,使用批处理模式) 可以忽略下载日志,一种方式是在 Maven 命令中增加 `-B` 参数,另一种方式是在 `settings.xml` 配置文件中增加 `<interactiveMode>false</interactiveMode>` 配置。
|
||||
|
||||
如果 Maven 库的 HTTPS 服务使用自建 CA 签名的证书,我们需要使用 [keytool][22] 工具创建一个将 CA 公钥添加至信任列表的 Java KeyStore。在 OpenShift 中使用 ConfigMap 将这个 Keystore 上传。使用 `oc` 命令基于文件创建一个 ConfigMap:
|
||||
|
||||
```
|
||||
oc create configmap maven-settings --from-file=settings.xml=settings.xml --from-
|
||||
file=myTruststore.jks=myTruststore.jks
|
||||
```
|
||||
|
||||
将这个 ConfigMap 挂载至 Jenkins 代理容器。在本例中我们使用 `/home/jenkins/.m2` 目录,但这仅仅是因为配置文件 `settings.xml` 也对应这个 ConfigMap。KeyStore 可以放置在任意路径下。
|
||||
|
||||
接着在容器环境变量 `MAVEN_OPTS` 中设置 Java 参数,以便让 Maven 对应的 Java 进程使用该文件:
|
||||
|
||||
```
|
||||
MAVEN_OPTS=
|
||||
-Djavax.net.ssl.trustStore=/home/jenkins/.m2/myTruststore.jks
|
||||
-Djavax.net.ssl.trustStorePassword=changeit
|
||||
```
|
||||
|
||||
### 内存使用量
|
||||
|
||||
这可能是最重要的一部分设置,如果没有正确的设置最大内存,我们会遇到间歇性构建失败,虽然每个组件都似乎工作正常。
|
||||
|
||||
如果没有在 Java 命令行中设置堆大小,在容器中运行 Java 可能导致高内存使用量的报错。JVM [可以利用全部的宿主机内存][23],而不是使用容器内存现在并相应设置[默认的堆大小][24]。这通常会超过容器的内存资源总额,故当 Java 进程为堆分配过多内存时,OpenShift 会直接杀掉容器。
|
||||
|
||||
虽然 `jenkins-slave-base` 镜像包含一个内建[脚本设置堆最大为][25]容器内存的一半(可以通过环境变量 `CONTAINER_HEAP_PERCENT=0.50` 修改),但这只适用于 Jenkins 代理节点中的 Java 进程。在 Maven 构建中,还有其它重要的 Java 进程运行:
|
||||
|
||||
* `mvn` 命令本身就是一个 Java 工具。
|
||||
* [Maven Surefire 插件][26] 默认派生一个 JVM 用于运行单元测试。
|
||||
|
||||
总结一下,容器中同时运行着三个重要的 Java 进程,预估内存使用量以避免 pod 被误杀是很重要的。每个进程都有不同的方式设置 JVM 参数:
|
||||
|
||||
* 我们在上面提到了 Jenkins 代理容器堆最大值的计算方法,但我们显然不应该让代理容器使用如此大的堆,毕竟还有两个 JVM 需要使用内存。对于 Jenkins 代理容器,可以设置 `JAVA_OPTS`。
|
||||
* `mvn` 工具被 Jenkins 任务调用。设置 `MAVEN_OPTS` 可以用于自定义这类 Java 进程。
|
||||
* Maven `surefire` 插件滋生的用于单元测试的 JVM 可以通过 Maven [argLine][27] 属性自定义。可以在 `pom.xml` 或 `settings.xml` 的某个配置文件中设置,也可以直接在 `maven` 命令参数 `MAVEN_OPS` 中增加 `-DargLine=…`。
|
||||
|
||||
|
||||
下面例子给出 Maven 代理容器环境变量设置方法:
|
||||
|
||||
```
|
||||
JAVA_OPTS=-Xms64m -Xmx64m
|
||||
MAVEN_OPTS=-Xms128m -Xmx128m -DargLine=${env.SUREFIRE_OPTS}
|
||||
SUREFIRE_OPTS=-Xms256m -Xmx256m
|
||||
```
|
||||
|
||||
我们的测试环境是具有 1024Mi 内存限额的代理容器,使用上述参数可以正常构建一个 SpringBoot 应用并进行单元测试。测试环境使用的资源相对较小,对于复杂的 Maven 项目和对应的单元测试,我们需要更大的堆大小及更大的容器内存限额。
|
||||
|
||||
注:Java8 进程的实际内存使用量包括“堆大小 + 元数据 + 堆外内存”,因此内存使用量会明显高于设置的最大堆大小。在我们上面的测试环境中,三个 Java 进程使用了超过 900Mi 的内存。可以在容器内查看进程的 RSS 内存使用情况,命令如下:`ps -e -o pid,user,rss,comm,args`。
|
||||
|
||||
Jenkins 代理镜像同时安装了 JDK 64 位和 32 位版本。对于 `mvn` 和 `surefire`,默认使用 64 位版本 JVM。为减低内存使用量,只要 `-Xmx` 不超过 1.5 GB,强制使用 32 位 JVM 都是有意义的。
|
||||
|
||||
```
|
||||
JAVA_HOME=/usr/lib/jvm/Java-1.8.0-openjdk-1.8.0.161–0.b14.el7_4.i386
|
||||
```
|
||||
|
||||
注意到我们可以在 `JAVA_TOOL_OPTIONS` 环境变量中设置 Java 参数,每个 JVM 启动时都会读取该参数。`JAVA_OPTS` 和 `MAVEN_OPTS` 中的参数会覆盖 `JAVA_TOOL_OPTIONS` 中的对应值,故我们可以不使用 `argLine`,实现对 Java 进程同样的堆配置:
|
||||
|
||||
```
|
||||
JAVA_OPTS=-Xms64m -Xmx64m
|
||||
MAVEN_OPTS=-Xms128m -Xmx128m
|
||||
JAVA_TOOL_OPTIONS=-Xms256m -Xmx256m
|
||||
```
|
||||
|
||||
但缺点是每个 JVM 的日志中都会显示 `Picked up JAVA_TOOL_OPTIONS:`,这可能让人感到迷惑。
|
||||
|
||||
### Jenkins 流水线
|
||||
|
||||
完成上述配置,我们应该已经可以完成一次成功的构建。我们可以获取源代码,下载依赖,运行单元测试并将成品上传到我们的库中。我们可以通过创建一个 Jenkins 流水线项目来完成上述操作。
|
||||
|
||||
```
|
||||
pipeline {
|
||||
/* Which container to bring up for the build. Pick one of the templates configured in Kubernetes plugin. */
|
||||
agent {
|
||||
label 'maven'
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Pull Source') {
|
||||
steps {
|
||||
git url: 'ssh://git@git.mycompany.com:22/myapplication.git', branch: 'master'
|
||||
}
|
||||
}
|
||||
stage('Unit Tests') {
|
||||
steps {
|
||||
sh 'mvn test'
|
||||
}
|
||||
}
|
||||
stage('Deploy to Nexus') {
|
||||
steps {
|
||||
sh 'mvn deploy -DskipTests'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
当然,对应真实项目,CI/CD 流水线不仅仅完成 Maven 构建,还可以部署到开发环境,运行集成测试,提升至更接近于生产的环境等。上面给出的学习资料中有执行这些操作的案例。
|
||||
|
||||
### 多容器
|
||||
|
||||
一个 pod 可以运行多个容器,每个容器有单独的资源限制。这些容器共享网络接口,故我们可以从 `localhost` 访问已启动的服务,但我们需要考虑端口冲突的问题。在一个 Kubernetes pod 模板中,每个容器的环境变量是单独设置的,但挂载的卷是统一的。
|
||||
|
||||
当一个外部服务需要单元测试且嵌入式方案无法工作(例如,数据库、消息中间件等) 时,可以启动多个容器。在这种情况下,第二个容器会随着 Jenkins 代理容器启停。
|
||||
|
||||
查看 Jenkins `config.xml` 片段,其中我们启动了一个辅助的 `httpbin` 服务用于 Maven 构建:
|
||||
|
||||
```
|
||||
<org.csanchez.jenkins.plugins.kubernetes.PodTemplate>
|
||||
<name>maven</name>
|
||||
<volumes>
|
||||
...
|
||||
</volumes>
|
||||
<containers>
|
||||
<org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate>
|
||||
<name>jnlp</name>
|
||||
<image>registry.access.redhat.com/openshift3/jenkins-slave-maven-rhel7:v3.7</image>
|
||||
<resourceLimitCpu>500m</resourceLimitCpu>
|
||||
<resourceLimitMemory>1024Mi</resourceLimitMemory>
|
||||
<envVars>
|
||||
...
|
||||
</envVars>
|
||||
...
|
||||
</org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate>
|
||||
<org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate>
|
||||
<name>httpbin</name>
|
||||
<image>citizenstig/httpbin</image>
|
||||
<resourceLimitCpu></resourceLimitCpu>
|
||||
<resourceLimitMemory>256Mi</resourceLimitMemory>
|
||||
<envVars/>
|
||||
...
|
||||
</org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate>
|
||||
</containers>
|
||||
<envVars/>
|
||||
</org.csanchez.jenkins.plugins.kubernetes.PodTemplate>
|
||||
```
|
||||
|
||||
### 总结
|
||||
|
||||
作为总结,我们查看上面已描述配置的 Jenkins `config.xml` 对应创建的 OpenShift 资源以及 Kubernetes 插件的配置。
|
||||
|
||||
```
|
||||
apiVersion: v1
|
||||
kind: List
|
||||
metadata: {}
|
||||
items:
|
||||
- apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: git-config
|
||||
data:
|
||||
config: |
|
||||
[credential]
|
||||
helper = store --file=/home/jenkins/.config/git-secret/credentials
|
||||
[http "http://git.mycompany.com"]
|
||||
sslCAInfo = /home/jenkins/.config/git/myTrustedCA.pem
|
||||
myTrustedCA.pem: |-
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDVzCCAj+gAwIBAgIJAN0sC...
|
||||
-----END CERTIFICATE-----
|
||||
- apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: git-secret
|
||||
stringData:
|
||||
ssh-privatekey: |-
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
...
|
||||
-----END RSA PRIVATE KEY-----
|
||||
credentials: |-
|
||||
https://username:password@git.mycompany.com
|
||||
https://user:pass@github.com
|
||||
- apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: git-ssh
|
||||
data:
|
||||
config: |-
|
||||
Host git.mycompany.com
|
||||
StrictHostKeyChecking yes
|
||||
IdentityFile /home/jenkins/.config/git-secret/ssh-privatekey
|
||||
known_hosts: '[git.mycompany.com]:22 ecdsa-sha2-nistp256 AAAdn7...'
|
||||
- apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: maven-secret
|
||||
stringData:
|
||||
username: admin
|
||||
password: admin123
|
||||
```
|
||||
|
||||
基于文件创建另一个 ConfigMap:
|
||||
|
||||
```
|
||||
oc create configmap maven-settings --from-file=settings.xml=settings.xml
|
||||
--from-file=myTruststore.jks=myTruststore.jks
|
||||
```
|
||||
|
||||
Kubernetes 插件配置如下:
|
||||
|
||||
```
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<hudson>
|
||||
...
|
||||
<clouds>
|
||||
<org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud plugin="kubernetes@1.0">
|
||||
<name>openshift</name>
|
||||
<defaultsProviderTemplate></defaultsProviderTemplate>
|
||||
<templates>
|
||||
<org.csanchez.jenkins.plugins.kubernetes.PodTemplate>
|
||||
<inheritFrom></inheritFrom>
|
||||
<name>maven</name>
|
||||
<namespace></namespace>
|
||||
<privileged>false</privileged>
|
||||
<alwaysPullImage>false</alwaysPullImage>
|
||||
<instanceCap>2147483647</instanceCap>
|
||||
<slaveConnectTimeout>100</slaveConnectTimeout>
|
||||
<idleMinutes>0</idleMinutes>
|
||||
<label>maven</label>
|
||||
<serviceAccount>jenkins37</serviceAccount>
|
||||
<nodeSelector></nodeSelector>
|
||||
<nodeUsageMode>NORMAL</nodeUsageMode>
|
||||
<customWorkspaceVolumeEnabled>false</customWorkspaceVolumeEnabled>
|
||||
<workspaceVolume class="org.csanchez.jenkins.plugins.kubernetes.volumes.workspace.EmptyDirWorkspaceVolume">
|
||||
<memory>false</memory>
|
||||
</workspaceVolume>
|
||||
<volumes>
|
||||
<org.csanchez.jenkins.plugins.kubernetes.volumes.SecretVolume>
|
||||
<mountPath>/home/jenkins/.config/git-secret</mountPath>
|
||||
<secretName>git-secret</secretName>
|
||||
</org.csanchez.jenkins.plugins.kubernetes.volumes.SecretVolume>
|
||||
<org.csanchez.jenkins.plugins.kubernetes.volumes.ConfigMapVolume>
|
||||
<mountPath>/home/jenkins/.ssh</mountPath>
|
||||
<configMapName>git-ssh</configMapName>
|
||||
</org.csanchez.jenkins.plugins.kubernetes.volumes.ConfigMapVolume>
|
||||
<org.csanchez.jenkins.plugins.kubernetes.volumes.ConfigMapVolume>
|
||||
<mountPath>/home/jenkins/.config/git</mountPath>
|
||||
<configMapName>git-config</configMapName>
|
||||
</org.csanchez.jenkins.plugins.kubernetes.volumes.ConfigMapVolume>
|
||||
<org.csanchez.jenkins.plugins.kubernetes.volumes.ConfigMapVolume>
|
||||
<mountPath>/home/jenkins/.m2</mountPath>
|
||||
<configMapName>maven-settings</configMapName>
|
||||
</org.csanchez.jenkins.plugins.kubernetes.volumes.ConfigMapVolume>
|
||||
</volumes>
|
||||
<containers>
|
||||
<org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate>
|
||||
<name>jnlp</name>
|
||||
<image>registry.access.redhat.com/openshift3/jenkins-slave-maven-rhel7:v3.7</image>
|
||||
<privileged>false</privileged>
|
||||
<alwaysPullImage>false</alwaysPullImage>
|
||||
<workingDir>/tmp</workingDir>
|
||||
<command></command>
|
||||
<args>${computer.jnlpmac} ${computer.name}</args>
|
||||
<ttyEnabled>false</ttyEnabled>
|
||||
<resourceRequestCpu>500m</resourceRequestCpu>
|
||||
<resourceRequestMemory>1024Mi</resourceRequestMemory>
|
||||
<resourceLimitCpu>500m</resourceLimitCpu>
|
||||
<resourceLimitMemory>1024Mi</resourceLimitMemory>
|
||||
<envVars>
|
||||
<org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar>
|
||||
<key>JAVA_HOME</key>
|
||||
<value>/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.161-0.b14.el7_4.i386</value>
|
||||
</org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar>
|
||||
<org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar>
|
||||
<key>JAVA_OPTS</key>
|
||||
<value>-Xms64m -Xmx64m</value>
|
||||
</org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar>
|
||||
<org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar>
|
||||
<key>MAVEN_OPTS</key>
|
||||
<value>-Xms128m -Xmx128m -DargLine=${env.SUREFIRE_OPTS} -Djavax.net.ssl.trustStore=/home/jenkins/.m2/myTruststore.jks -Djavax.net.ssl.trustStorePassword=changeit</value>
|
||||
</org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar>
|
||||
<org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar>
|
||||
<key>SUREFIRE_OPTS</key>
|
||||
<value>-Xms256m -Xmx256m</value>
|
||||
</org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar>
|
||||
<org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar>
|
||||
<key>MAVEN_MIRROR_URL</key>
|
||||
<value>https://nexus.mycompany.com/repository/maven-public</value>
|
||||
</org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar>
|
||||
<org.csanchez.jenkins.plugins.kubernetes.model.SecretEnvVar>
|
||||
<key>MAVEN_SERVER_USERNAME</key>
|
||||
<secretName>maven-secret</secretName>
|
||||
<secretKey>username</secretKey>
|
||||
</org.csanchez.jenkins.plugins.kubernetes.model.SecretEnvVar>
|
||||
<org.csanchez.jenkins.plugins.kubernetes.model.SecretEnvVar>
|
||||
<key>MAVEN_SERVER_PASSWORD</key>
|
||||
<secretName>maven-secret</secretName>
|
||||
<secretKey>password</secretKey>
|
||||
</org.csanchez.jenkins.plugins.kubernetes.model.SecretEnvVar>
|
||||
</envVars>
|
||||
<ports/>
|
||||
<livenessProbe>
|
||||
<execArgs></execArgs>
|
||||
<timeoutSeconds>0</timeoutSeconds>
|
||||
<initialDelaySeconds>0</initialDelaySeconds>
|
||||
<failureThreshold>0</failureThreshold>
|
||||
<periodSeconds>0</periodSeconds>
|
||||
<successThreshold>0</successThreshold>
|
||||
</livenessProbe>
|
||||
</org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate>
|
||||
<org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate>
|
||||
<name>httpbin</name>
|
||||
<image>citizenstig/httpbin</image>
|
||||
<privileged>false</privileged>
|
||||
<alwaysPullImage>false</alwaysPullImage>
|
||||
<workingDir></workingDir>
|
||||
<command>/run.sh</command>
|
||||
<args></args>
|
||||
<ttyEnabled>false</ttyEnabled>
|
||||
<resourceRequestCpu></resourceRequestCpu>
|
||||
<resourceRequestMemory>256Mi</resourceRequestMemory>
|
||||
<resourceLimitCpu></resourceLimitCpu>
|
||||
<resourceLimitMemory>256Mi</resourceLimitMemory>
|
||||
<envVars/>
|
||||
<ports/>
|
||||
<livenessProbe>
|
||||
<execArgs></execArgs>
|
||||
<timeoutSeconds>0</timeoutSeconds>
|
||||
<initialDelaySeconds>0</initialDelaySeconds>
|
||||
<failureThreshold>0</failureThreshold>
|
||||
<periodSeconds>0</periodSeconds>
|
||||
<successThreshold>0</successThreshold>
|
||||
</livenessProbe>
|
||||
</org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate>
|
||||
</containers>
|
||||
<envVars/>
|
||||
<annotations/>
|
||||
<imagePullSecrets/>
|
||||
</org.csanchez.jenkins.plugins.kubernetes.PodTemplate>
|
||||
</templates>
|
||||
<serverUrl>https://172.30.0.1:443</serverUrl>
|
||||
<serverCertificate>-----BEGIN CERTIFICATE-----
|
||||
MIIC6jCC...
|
||||
-----END CERTIFICATE-----</serverCertificate>
|
||||
<skipTlsVerify>false</skipTlsVerify>
|
||||
<namespace>first</namespace>
|
||||
<jenkinsUrl>http://jenkins.cicd.svc:80</jenkinsUrl>
|
||||
<jenkinsTunnel>jenkins-jnlp.cicd.svc:50000</jenkinsTunnel>
|
||||
<credentialsId>1a12dfa4-7fc5-47a7-aa17-cc56572a41c7</credentialsId>
|
||||
<containerCap>10</containerCap>
|
||||
<retentionTimeout>5</retentionTimeout>
|
||||
<connectTimeout>0</connectTimeout>
|
||||
<readTimeout>0</readTimeout>
|
||||
<maxRequestsPerHost>32</maxRequestsPerHost>
|
||||
</org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud>
|
||||
</clouds>
|
||||
|
||||
</hudson>
|
||||
```
|
||||
|
||||
尝试愉快的构建吧!
|
||||
|
||||
原文发表于 [ITNext][28],已获得翻版授权。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/18/4/running-jenkins-builds-containers
|
||||
|
||||
作者:[Balazs Szeti][a]
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
译者:[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/bszeti
|
||||
[1]:https://opensource.com/resources/what-docker
|
||||
[2]:https://opensource.com/resources/what-is-kubernetes
|
||||
[3]:https://martinfowler.com/articles/microservices.html
|
||||
[4]:https://www.openshift.com/
|
||||
[5]:https://developers.redhat.com/products/cdk/overview/
|
||||
[6]:https://github.com/openshift/origin/tree/master/examples/jenkins
|
||||
[7]:https://wiki.jenkins.io/display/JENKINS/Distributed+builds
|
||||
[8]:https://jenkins.io/doc/book/pipeline/
|
||||
[9]:https://github.com/jenkinsci/kubernetes-plugin
|
||||
[10]:https://access.redhat.com/containers/#/search/jenkins%2520slave
|
||||
[11]:https://hub.docker.com/search/?isAutomated=0&isOfficial=0&page=1&pullCount=0&q=openshift+jenkins+slave+&starCount=0
|
||||
[12]:https://github.com/openshift/jenkins/tree/master/slave-base
|
||||
[13]:https://github.com/openshift/jenkins/tree/master/slave-maven
|
||||
[14]:https://github.com/openshift/jenkins/tree/master/slave-nodejs
|
||||
[15]:https://docs.openshift.com/container-platform/3.7/architecture/core_concepts/builds_and_image_streams.html#source-build
|
||||
[16]:https://github.com/bszeti/camel-springboot/tree/master/camel-rest-complex
|
||||
[17]:https://issues.jenkins-ci.org/browse/JENKINS-47112
|
||||
[18]:https://git-scm.com/docs/git-credential-store/1.8.2
|
||||
[19]:https://git-scm.com/docs/git-config/1.8.2
|
||||
[20]:https://bugzilla.redhat.com/show_bug.cgi?id=1430322
|
||||
[21]:https://maven.apache.org/pom.html#Distribution_Management
|
||||
[22]:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html
|
||||
[23]:https://developers.redhat.com/blog/2017/03/14/java-inside-docker/
|
||||
[24]:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/parallel.html#default_heap_size
|
||||
[25]:https://github.com/openshift/jenkins/blob/master/slave-base/contrib/bin/run-jnlp-client
|
||||
[26]:http://maven.apache.org/surefire/maven-surefire-plugin/examples/fork-options-and-parallel-execution.html
|
||||
[27]:http://maven.apache.org/surefire/maven-surefire-plugin/test-mojo.html#argLine
|
||||
[28]:https://itnext.io/running-jenkins-builds-in-containers-458e90ff2a7b
|
||||
[29]:https://docs.openshift.com/container-platform/3.7/using_images/other_images/jenkins.html
|
||||
[30]:https://github.com/openshift/jenkins
|
||||
[31]:https://blog.openshift.com/cicd-with-openshift/
|
||||
[32]:http://v1.uncontained.io/playbooks/continuous_delivery/external-jenkins-integration.html
|
@ -1,67 +0,0 @@
|
||||
pinewall translating
|
||||
|
||||
What Stratis learned from ZFS, Btrfs, and Linux Volume Manager | Opensource.com
|
||||
======
|
||||

|
||||
|
||||
As discussed in [Part 1][1] of this series, Stratis is a volume-managing filesystem (VMF) with functionality similar to [ZFS][2] and [Btrfs][3]. In designing Stratis, we studied the choices that developers of existing solutions made.
|
||||
|
||||
### Why not adopt an existing solution?
|
||||
|
||||
The reasons vary. First, let's consider [ZFS][2]. Originally developed by Sun Microsystems for Solaris (now owned by Oracle), ZFS has been ported to Linux. However, its [CDDL][4]-licensed code cannot be merged into the [GPL][5]-licensed Linux source tree. Whether CDDL and GPLv2 are truly incompatible is a subject for debate, but the uncertainty is enough to make enterprise Linux vendors unwilling to adopt and support it.
|
||||
|
||||
[Btrfs][3] is also well-established and has no licensing issues. For years it was the "Chosen One" for many users, but it just hasn't yet gotten to where it needs to be in terms of stability and features.
|
||||
|
||||
So, fuelled by a desire to improve the status quo and frustration with existing options, Stratis was conceived.
|
||||
|
||||
### How Stratis is different
|
||||
|
||||
One thing that ZFS and Btrfs have clearly shown is that writing a VMF as an in-kernel filesystem takes a tremendous amount of work and time to work out the bugs and stabilize. It's essential to get right when it comes to precious data. Starting from scratch and taking the same approach with Stratis would probably take a decade as well, which was not acceptable.
|
||||
|
||||
Instead, Stratis chose to use some of the Linux kernel's other existing capabilities: The [device mapper][6] subsystem, which is most notably used by LVM to provide RAID, thin-provisioning, and other features on top of block devices; and the well-tested and high-performance [XFS][7] filesystem. Stratis builds its pool using layers of existing technology, with the goal of managing them to appear as a seamless whole to the user.
|
||||
|
||||
### What Stratis learned from ZFS
|
||||
|
||||
For many users, ZFS set the expectations for what a next-generation filesystem should be. Reading comments online from people talking about ZFS helped set Stratis's initial development goals. ZFS's design also implicitly highlighted things to avoid. For example, ZFS requires an "import" step when attaching a pool created on another system. There are a few reasons for this, and each reason was likely an issue that Stratis had to solve, either by taking the same approach or a different one.
|
||||
|
||||
One thing we didn't like about ZFS was that it has some restrictions on adding new hard drives or replacing existing drives with bigger ones, especially if the pool is configured for redundancy. Of course, there is a reason for this, but we thought it was an area we could improve.
|
||||
|
||||
Finally, using ZFS's tools at the command line, once learned, is a good experience. We wanted to have that same feeling with Stratis's command-line tool, and we also liked the tool's tendency to use positional parameters and limit the amount of typing required for each command.
|
||||
|
||||
### What Stratis learned from Btrfs
|
||||
|
||||
One thing we liked about Btrfs was the single command-line tool, with positional subcommands. Btrfs also treats redundancy (Btrfs profiles) as a property of the pool, which seems easier to understand than ZFS's approach and allows drives to be added and even removed.
|
||||
|
||||
Finally, looking at the features that both ZFS and Btrfs offer, such as snapshot implementations and send/receive support, helped determine which features Stratis should include.
|
||||
|
||||
### What Stratis learned from LVM
|
||||
|
||||
From the early design stages of Stratis, we studied LVM extensively. LVM is currently the most significant user of the Linux device mapper (DM) subsystem—in fact, DM is maintained by the LVM core team. We examined it both from the possibility of actually using LVM as a layer of Stratis and an example of using DM, which Stratis could do directly with LVM as a peer. We looked at LVM's on-disk metadata format (along with ZFS's and XFS's) for inspiration in defining Stratis's on-disk metadata format.
|
||||
|
||||
Among the listed projects, LVM shares the most in common with Stratis internally, because they both use DM. However, from a usage standpoint, LVM is much more transparent about its inner workings. This gives expert users a great deal of control and options for precisely configuring the volume group (pool) layout in a way that Stratis doesn't.
|
||||
|
||||
### A diversity of solutions
|
||||
|
||||
One great thing about working on free software and open source is that there are no irreplaceable components. Every part—even the kernel—is open for view, modification, and even replacement if the current software isn't meeting users' needs. A new project doesn't need to end an existing one if there is enough support for both to be sustained in parallel.
|
||||
|
||||
Stratis is an attempt to better meet some users' needs for local storage management—those looking for a no-hassle, easy-to-use, powerful solution. This means making design choices that might not be right for all users. Alternatives make tough choices possible since users have other options. All users ultimately benefit from their ability to use whichever tool works best for them.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/18/4/stratis-lessons-learned
|
||||
|
||||
作者:[Andy Grover][a]
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://opensource.com/users/agrover
|
||||
[1]:https://opensource.com/article/18/4/stratis-easy-use-local-storage-management-linux
|
||||
[2]:https://en.wikipedia.org/wiki/ZFS
|
||||
[3]:https://en.wikipedia.org/wiki/Btrfs
|
||||
[4]:https://en.wikipedia.org/wiki/Common_Development_and_Distribution_License
|
||||
[5]:https://en.wikipedia.org/wiki/GNU_General_Public_License
|
||||
[6]:https://en.wikipedia.org/wiki/Device_mapper
|
||||
[7]:https://en.wikipedia.org/wiki/XFS
|
@ -1,3 +1,5 @@
|
||||
pinewall translating
|
||||
|
||||
An introduction to cryptography and public key infrastructure
|
||||
======
|
||||

|
||||
|
@ -1,122 +0,0 @@
|
||||
translating----geekpi
|
||||
|
||||
Vim-plug : A Minimalist Vim Plugin Manager
|
||||
======
|
||||
|
||||

|
||||
|
||||
When there were no plugin managers, the Vim users had to manually download the Plugins distributed as tarballs and extract them in a directory called **~/.vim**. It was OK for few plugins. When they installed more plugins, it became a mess. All plugin files scattered in a single directory and the users couldn’t find which file belongs to which plugin. Further more, they could not find which file they should remove to uninstall a plugin. There is where Vim plugin managers comes in handy. The plugin managers saves the files of installed plugins in separate directory, so it is became very easy to manage all plugins. We already wrote about [**Vundle**][1] few months ago. Today, we will see yet another Vim plugin manager named **“Vim-plug”**.
|
||||
|
||||
Vim-plug is a free, open source, very fast, minimalist vim plugin manager. It can install or update plugins in parallel. You can also rollback the updates. It creates shallow clones to minimize disk space usage and download time. It supports on-demand plugin loading for faster startup time. Other notable features are branch/tag/commit support, post-update hooks, support for externally managed plugins etc.
|
||||
|
||||
### Vim-plug : A Minimalist Vim Plugin Manager
|
||||
|
||||
#### **Installation**
|
||||
|
||||
It is very easier to setup and use. All you have to do is to open your Terminal and run the following command:
|
||||
```
|
||||
$ curl -fLo ~/.vim/autoload/plug.vim --create-dirs https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
|
||||
|
||||
```
|
||||
|
||||
The Neovim users can install Vim-plug using the following command:
|
||||
```
|
||||
$ curl -fLo ~/.config/nvim/autoload/plug.vim --create-dirs https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
|
||||
|
||||
```
|
||||
|
||||
#### Usage
|
||||
|
||||
**Installing Plugins**
|
||||
|
||||
To install plugins, you must first declare them in Vim configuration file as shown below. The configuration file for ordinary Vim is **~/.vimrc** and the config file for Neovim is **~/.config/nvim/init.vim**. Please remember that when you declare the plugins in configuration file, the list should start with **call plug#begin(PLUGIN_DIRECTORY)** and end with call **plug#end()**.
|
||||
|
||||
For example, let us install “lightline.vim” plugin. To do so, add the following lines on top of your **~/.vimrc** file.
|
||||
```
|
||||
call plug#begin('~/.vim/plugged')
|
||||
Plug 'itchyny/lightline.vim'
|
||||
call plug#end()
|
||||
|
||||
```
|
||||
|
||||
After adding the above lines in vim configuration file, reload by entering the following:
|
||||
```
|
||||
:source ~/.vimrc
|
||||
|
||||
```
|
||||
|
||||
Or, simply reload the Vim editor.
|
||||
|
||||
Now, open vim editor:
|
||||
```
|
||||
$ vim
|
||||
|
||||
```
|
||||
|
||||
Check the status using command:
|
||||
```
|
||||
:PlugStatus
|
||||
|
||||
```
|
||||
|
||||
And type following command and hit ENTER to install the plugins that you have declared in the config file earlier.
|
||||
```
|
||||
:PlugInstall
|
||||
|
||||
```
|
||||
|
||||
**Update Plugins**
|
||||
|
||||
To update plugins, run:
|
||||
```
|
||||
:PlugUpdate
|
||||
|
||||
```
|
||||
|
||||
After updating the plugins, press **d** to review the changes. Or, you can do it later by typing **:PlugDiff**.
|
||||
|
||||
**Review Plugins**
|
||||
|
||||
Some times, the updated plugins may have new bugs or no longer work correctly. To fix this, you can simply rollback the problematic plugins. Type **:PlugDiff** command and hit ENTER to review the changes from the last **:PlugUpdate** and roll each plugin back to the previous state before the update by pressing **X** on each paragraph.
|
||||
|
||||
**Removing Plugins**
|
||||
|
||||
To remove a plugin delete or comment out the **plug** commands that you have added earlier in your vim configuration file. Then, run **:source ~/.vimrc** or restart Vim editor. Finally, run the following command to uninstall the plugins:
|
||||
```
|
||||
:PlugClean
|
||||
|
||||
```
|
||||
|
||||
This command will delete all undeclared plugins in your vim config file.
|
||||
|
||||
**Upgrade Vim-plug**
|
||||
|
||||
To upgrade vim-plug itself, type:
|
||||
```
|
||||
:PlugUpgrade
|
||||
|
||||
```
|
||||
|
||||
As you can see, managing plugins using Vim-plug is not a big deal. It simplifies the plugin management a lot easier. Now go and find out your favorite plugins and install them using Vim-plug.
|
||||
|
||||
**Suggested read:**
|
||||
|
||||
And, that’s all for now. I will be soon here with another interesting topic. Until then, stay tuned with OSTechNix.
|
||||
|
||||
Cheers!
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://www.ostechnix.com/vim-plug-a-minimalist-vim-plugin-manager/
|
||||
|
||||
作者:[SK][a]
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://www.ostechnix.com/author/sk/
|
||||
[1]:https://www.ostechnix.com/manage-vim-plugins-using-vundle-linux/
|
@ -1,3 +1,5 @@
|
||||
translating----geekpi
|
||||
|
||||
A friendly alternative to the find tool in Linux
|
||||
======
|
||||

|
||||
|
@ -1,3 +1,5 @@
|
||||
pinewall translating
|
||||
|
||||
Getting started with Buildah
|
||||
======
|
||||

|
||||
|
@ -1,3 +1,5 @@
|
||||
pinewall translating
|
||||
|
||||
Using MQTT to send and receive data for your next project
|
||||
======
|
||||
|
||||
|
@ -1,941 +0,0 @@
|
||||
在容器中运行 Jenkins 构建
|
||||
======
|
||||
|
||||

|
||||
由于 [Docker][1] 和 [Kubernetes][2] (K8s) 目前提供了可扩展、可管理的应用平台,将应用运行在容器中的实践已经被企业广泛接受。近些年势头很猛的[微服务架构][3]也很适合用容器实现。
|
||||
|
||||
容器应用平台可以动态启动指定资源配额、互相隔离的容器,这是其最主要的优势之一。让我们看看这会对我们运行持续集成/持续部署 (continuous integration/continuous development, CI/CD) 任务的方式产生怎样的改变。
|
||||
|
||||
构建并打包应用需要一定的环境,要求能够下载源代码、使用相关依赖及已经安装构建工具。作为构建的一部分,运行单元及组件测试可能会用到本地端口或需要运行第三方应用 (例如数据库及消息中间件等)。另外,我们一般定制化多台构建服务器,每台执行一种指定类型的构建任务。为方便测试,我们维护一些实例专门用于运行第三方应用 (或者试图在构建服务器上启动这些第三方应用),避免并行运行构建任务防止结果互相干扰。为 CI/CD 环境定制化构建服务器是一项繁琐的工作,而且随着开发团队使用的开发平台或其版本变更,所需构建服务器的数量也会变更。
|
||||
|
||||
一旦我们有了容器管理平台 (自建或在云端),将资源密集型的 CI/CD 任务在动态生成的容器中执行是比较合理的。在这种方案中,每个构建任务运行在独立启动并配置的构建环境中。构建过程中,构建任务的测试环节可以任意使用隔离环境中的可用资源;此外,我们也可以在辅助容器中启动一个第三方应用,只在构建任务生命周期中为测试提供服务。
|
||||
|
||||
听上去不错,让我们在现实环境中实践一下。
|
||||
|
||||
注:本文基于现实中已有的解决方案,即在 [Red Hat OpenShift][4] v3.7 集群上运行项目。OpenShift 是企业就绪版本的 Kubernetes,故这些实践也适用于 K8s 集群。如果愿意尝试,可以下载 [Red Hat CDK][5],运行 `jenkins-ephemeral` 或 `jenkins-persistent` [模板][6]在 OpenShift 上创建定制化好的 Jenkins 管理节点。
|
||||
|
||||
### 解决方案概述
|
||||
|
||||
在 OpenShift 容器中执行 CI/CD 任务 (构建和测试等) 的方案基于[分布式 Jenkins 构建][7],具体如下:
|
||||
* 我们需要一个 Jenkins 主节点;可以运行在集群中,也可以是外部提供
|
||||
* 支持 Jenkins 特性和插件,故已有项目仍可使用
|
||||
* 可以用 Jenkins GUI 配置、运行任务或查看任务输出
|
||||
* 如果你愿意编码,也可以使用 [Jenkins Pipeline][8]
|
||||
|
||||
从技术角度来看,运行任务的动态容器是 Jenkins 代理节点。当构建启动时,首先是一个新节点启动,通过 Jenkins 主节点的 JNLP (5000 端口) 告知就绪状态。在代理节点启动并提取构建任务之前,构建任务处于排队状态。就像通常 Jenkins 代理服务器那样,构建输出会送达主节点;不同的是,构建完成后代理节点容器会自动关闭。
|
||||
|
||||

|
||||
|
||||
不同类型的构建任务 (例如 Java, NodeJS, Python等) 对应不同的代理节点。这并不新奇,之前也是使用标签来限制哪些代理节点可以运行指定的构建任务。启动用于构建任务的 Jenkins 代理节点容器需要配置参数,具体如下:
|
||||
* 用于启动容器的 Docker 镜像
|
||||
* 资源限制
|
||||
* 环境变量
|
||||
* 挂载卷
|
||||
|
||||
这里用到的关键组件是 [Jenkins Kubernetes 插件][9]。该插件 (通过使用一个服务账号) 与 K8s 集群交互,可以启动和关闭代理节点。在插件的配置管理中,多种代理节点类型表现为多种Kubernetes pod 模板,它们通过项目标签对应。
|
||||
|
||||
这些[代理节点镜像][10]以开箱即用的方式提供 (也有 [CentOS7][11] 系统的版本):
|
||||
* [jenkins-slave-base-rhel7][12]:基础镜像,启动与 Jenkins 主节点连接的代理节点;其中 Java 堆大小根据容器内容设置
|
||||
* [jenkins-slave-maven-rhel7][13]:用于 Maven 和 Gradle 构建的镜像 (从基础镜像扩展)
|
||||
* [jenkins-slave-nodejs-rhel7][14]:包含 NodeJS4 工具的镜像 (从基础镜像扩展)
|
||||
|
||||
注意:本解决方案与 OpenShift 中的 [Source-to-Image (S2I)][15] 构建不同,虽然后者也可以用于某些特定的 CI/CD 任务。
|
||||
|
||||
### 入门学习资料
|
||||
|
||||
有很多不错的博客和文档介绍了如何在 OpenShift 上执行 Jenkins 构建。不妨从下面这些开始:
|
||||
* [OpenShift Jenkins][29] 镜像文档及 [源代码][30]
|
||||
* 网络直播[基于 OpenShift 的 CI/CD][31]
|
||||
* [外部 Jenkins 集成][32] playbook
|
||||
|
||||
阅读这些博客和文档有助于完整的理解本解决方案。在本文中,我们主要关注具体实践中遇到的各类问题。
|
||||
|
||||
### 构建我的应用
|
||||
|
||||
作为[示例项目][16],我们选取了包含如下构建步骤的 Java 项目:
|
||||
|
||||
* **代码源:** 从一个Git代码库中获取项目代码
|
||||
* **使用 Maven 编译:** 依赖可从内部仓库获取,(不妨使用 Apache Nexus) 从外部 Maven 仓库镜像
|
||||
* **发布 artifact:** 将编译好的 JAR 上传至内部仓库
|
||||
|
||||
在 CI/CD 过程中,我们需要与 Git 和 Nexus 交互,故 Jenkins 任务需要能够访问这些系统。这涉及参数配置和已存储凭证,可以在下列位置进行存放及管理:
|
||||
* **在 Jenkins 中:** 我们可以在 Jenkins 中添加凭证,通过 Git 插件获取项目代码 (使用容器不会改变操作)
|
||||
* **在 OpenShift 中:** 使用 ConfigMap 和 Secret 对象,以文件或环境变量的形式附加到 Jenkins 代理容器中
|
||||
* **在高度定制化的 Docker 容器中:** 镜像是定制化的,已包含完成特定类型构建的全部特性;从一个代理镜像进行扩展即可得到。
|
||||
|
||||
你可以按自己的喜好选择一种实现方式,甚至你最终可能混用多种实现方式。下面我们采用第二种实现方式,即首选在 OpenShift 中管理参数配置。使用 Kubernetes 插件配置来定制化 Maven 代理容器,包括设置环境变量和映射文件等。
|
||||
|
||||
注意:对于 Kubernetes 插件 v1.0 版,由于 [bug][17],在 UI 界面增加环境变量并不生效。可以升级插件,或 (作为变通方案) 直接修改 `config.xml` 文件并重启 Jenkins。
|
||||
|
||||
### 从 Git 获取源代码
|
||||
|
||||
从公共 Git 仓库获取源代码很容易。但对于私有 Git 仓库,不仅需要认证操作,客户端还需要信任服务器以便建立安全连接。一般而言,通过两种协议获取源代码:
|
||||
* HTTPS:验证通过用户名/密码完成。Git 服务器的 SSL 证书必须被代理节点信任,这仅在证书被自建 CA 签名时才需要特别关注。
|
||||
|
||||
|
||||
```
|
||||
git clone https://git.mycompany.com:443/myapplication.git
|
||||
|
||||
```
|
||||
|
||||
* SSH:验证通过私钥完成。如果服务器的公钥指纹出现在 `known_hosts` 文件中,那么该服务器是被信任的。
|
||||
|
||||
|
||||
```
|
||||
git clone ssh://git@git.mycompany.com:22/myapplication.git
|
||||
|
||||
```
|
||||
|
||||
对于手动操作,使用用户名/密码通过 HTTP 方式下载源代码是可行的;但对于自动构建而言,SSH 是更佳的选择。
|
||||
|
||||
#### 通过 SSH 方式使用 Git
|
||||
|
||||
要通过 SSH 方式下载源代码,我们需要保证代理容器与 Git 的 SSH 端口之间可以建立 SSH 连接。首先,我们需要创建一个私钥-公钥对。使用如下命令生成:
|
||||
```
|
||||
ssh keygen -t rsa -b 2048 -f my-git-ssh -N ''
|
||||
|
||||
```
|
||||
|
||||
命令生成的私钥位于 `my-git-ssh` 文件中 (无密码口令),对应的公钥位于 `my-git-ssh.pub` 文件中。将公钥添加至 Git 服务器的对应用户下 (推荐使用服务账号);网页界面一般支持公钥上传。为建立 SSH 连接,我们还需要在代理容器上配置两个文件:
|
||||
|
||||
* 私钥文件位于 `~/.ssh/id_rsa`
|
||||
* 服务器的公钥位于 `~/.ssh/known_hosts`。要实现这一点,运行 `ssh git.mycompany.com` 并接受服务器指纹,系统会在 `~/.ssh/known_hosts` 文件中增加一行。这样需求得到了满足。
|
||||
|
||||
|
||||
将 `id_rsa` 对应的私钥和 `known_hosts` 对应的公钥保存到一个 OpenShift secret (或 config map) 对象中。
|
||||
```
|
||||
apiVersion: v1
|
||||
|
||||
kind: Secret
|
||||
|
||||
metadata:
|
||||
|
||||
name: mygit-ssh
|
||||
|
||||
stringData:
|
||||
|
||||
id_rsa: |-
|
||||
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
|
||||
...
|
||||
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
||||
known_hosts: |-
|
||||
|
||||
git.mycompany.com ecdsa-sha2-nistp256 AAA...
|
||||
|
||||
```
|
||||
|
||||
在 Kubernetes 插件中将 secret 对象配置为卷,挂载到 `/home/jenkins/.ssh/`,供 Maven pod 使用。secret 中的每个对象对应挂载目录的一个文件,文件名与 key 名称相符。我们可以使用 UI (管理 Jenkins / 配置 / 云 / Kubernetes),也可以直接编辑 Jenkins 配置文件 `/var/lib/jenkins/config.xml`:
|
||||
```
|
||||
<org.csanchez.jenkins.plugins.kubernetes.PodTemplate>
|
||||
|
||||
<name>maven</name>
|
||||
|
||||
...
|
||||
|
||||
<volumes>
|
||||
|
||||
<org.csanchez.jenkins.plugins.kubernetes.volumes.SecretVolume>
|
||||
|
||||
<mountPath>/home/jenkins/.ssh</mountPath>
|
||||
|
||||
<secretName>mygit-ssh</secretName>
|
||||
|
||||
</org.csanchez.jenkins.plugins.kubernetes.volumes.SecretVolume>
|
||||
|
||||
</volumes>
|
||||
|
||||
```
|
||||
|
||||
此时,在代理节点上运行的任务应该可以通过 SSH 方式从 Git 代码库获取源代码。
|
||||
|
||||
注:我们也可以在 `~/.ssh/config` 文件中自定义 SSH 连接。例如,如果你不想处理 `known_hosts` 或 私钥位于其它挂载目录中:
|
||||
```
|
||||
Host git.mycompany.com
|
||||
|
||||
StrictHostKeyChecking no
|
||||
|
||||
IdentityFile /home/jenkins/.config/git-secret/ssh-privatekey
|
||||
|
||||
```
|
||||
|
||||
#### 通过 HTTP 方式使用 Git
|
||||
|
||||
如果你选择使用 HTTP 方式下载,在指定的 [Git-credential-store][18] 文件中添加用户名/密码:
|
||||
|
||||
* 例如,在一个 OpenShift secret 对象中增加 `/home/jenkins/.config/git-secret/credentials` 文件对应,其中每个站点对应文件中的一行:
|
||||
|
||||
|
||||
```
|
||||
https://username:password@git.mycompany.com
|
||||
|
||||
https://user:pass@github.com
|
||||
|
||||
```
|
||||
|
||||
* 在 [git-config][19] 配置中启用该文件,其中配置文件默认路径为 `/home/jenkins/.config/git/config`:
|
||||
|
||||
|
||||
```
|
||||
[credential]
|
||||
|
||||
helper = store --file=/home/jenkins/.config/git-secret/credentials
|
||||
|
||||
```
|
||||
如果 Git 服务使用了自有 CA 签名的证书,为代理容器设置环境变量 `GIT_SSL_NO_VERIFY=true` 是最便捷的方式。更恰当的解决方案包括如下两步:
|
||||
|
||||
* 利用 config map 将自有 CA 的公钥映射到一个路径下的文件中,例如 `/usr/ca/myTrustedCA.pem`)。
|
||||
* 通过环境变量 `GIT_SSL_CAINFO=/usr/ca/myTrustedCA.pem` 或上面提到的 `git-config` 文件的方式,将证书路径告知 Git。
|
||||
|
||||
|
||||
```
|
||||
[http "https://git.mycompany.com"]
|
||||
|
||||
sslCAInfo = /usr/ca/myTrustedCA.pem
|
||||
|
||||
```
|
||||
|
||||
注:在 OpenShift v3.7 及早期版本中,config map 及 secret 的挂载点之间[不能相互覆盖][20],故我们不能同时映射 `/home/jenkins` 和 `/home/jenkins/dir`。因此,上面的代码中并没有使用常见的文件路径。预计 OpenShift v3.9 版本会修复这个问题。
|
||||
|
||||
### Maven
|
||||
|
||||
要完成 Maven 构建,一般需要完成如下两步:
|
||||
|
||||
* 建立一个社区 Maven 库 (例如 Apache Nexus),充当外部库的代理。将其当作镜像使用。
|
||||
* 这个内部库可能提供 HTTPS 服务,其中使用自建 CA 签名的证书。
|
||||
|
||||
|
||||
对于容器中运行构建的实践而言,使用内部 Maven 库是非常关键的,因为容器启动后并没有本地库或缓存,这导致每次构建时 Maven 都下载全部的 Jar 文件。在本地网络使用内部代理库下载明显快于从因特网下载。
|
||||
|
||||
[Maven Jenkins 代理][13]镜像允许配置环境变量,指定代理的 URL。在 Kubernetes 插件的容器模板中设置如下:
|
||||
```
|
||||
MAVEN_MIRROR_URL=https://nexus.mycompany.com/repository/maven-public
|
||||
|
||||
```
|
||||
|
||||
构建好的 artifacts (JARs) 也应该保存到库中,可以是上面提到的用于提供依赖的镜像库,也可以是其它库。Maven 完成 `deploy` 操作需要在 `pom.xml` 的[分发管理][21] 下配置库 URL,这与代理镜像无关。
|
||||
```
|
||||
<project ...>
|
||||
|
||||
<distributionManagement>
|
||||
|
||||
<snapshotRepository>
|
||||
|
||||
<id>mynexus</id>
|
||||
|
||||
<url>https://nexus.mycompany.com/repository/maven-snapshots/</url>
|
||||
|
||||
</snapshotRepository>
|
||||
|
||||
<repository>
|
||||
|
||||
<id>mynexus</id>
|
||||
|
||||
<url>https://nexus.mycompany.com/repository/maven-releases/</url>
|
||||
|
||||
</repository>
|
||||
|
||||
</distributionManagement>
|
||||
|
||||
```
|
||||
|
||||
上传 artifact 可能涉及认证。在这种情况下,需要在 `settings.xml` 中配置用户名/密码,其中 server ID 要与 `pom.xml` 文件中的 server ID 对应。我们可以使用 OpenShift secret 将包含 URL、用户名和密码的完整 `settings.xml` 映射到 Maven Jenkins 代理容器中。另外,也可以使用环境变量。具体如下:
|
||||
|
||||
* 利用 secret 为容器添加环境变量:
|
||||
|
||||
|
||||
```
|
||||
MAVEN_SERVER_USERNAME=admin
|
||||
|
||||
MAVEN_SERVER_PASSWORD=admin123
|
||||
|
||||
```
|
||||
|
||||
* 利用 config map 将 `settings.xml` 挂载至 `/home/jenkins/.m2/settings.xml`:
|
||||
|
||||
|
||||
```
|
||||
<settings ...>
|
||||
|
||||
<mirrors>
|
||||
|
||||
<mirror>
|
||||
|
||||
<mirrorOf>external:*</mirrorOf>
|
||||
|
||||
<url>${env.MAVEN_MIRROR_URL}</url>
|
||||
|
||||
<id>mirror</id>
|
||||
|
||||
</mirror>
|
||||
|
||||
</mirrors>
|
||||
|
||||
<servers>
|
||||
|
||||
<server>
|
||||
|
||||
<id>mynexus</id>
|
||||
|
||||
<username>${env.MAVEN_SERVER_USERNAME}</username>
|
||||
|
||||
<password>${env.MAVEN_SERVER_PASSWORD}</password>
|
||||
|
||||
</server>
|
||||
|
||||
</servers>
|
||||
|
||||
</settings>
|
||||
|
||||
```
|
||||
|
||||
禁用交互模式 (即使用批处理模式) 可以忽略下载日志,一种方式是在 Maven 命令中增加 `-B` 参数,另一种方式是在 `settings.xml` 配置文件中增加 `<interactiveMode>false</interactiveMode>` 配置。
|
||||
|
||||
如果 Maven 库的 HTTPS 服务使用自建 CA 签名的证书,我们需要使用 [keytool][22] 工具创建一个将 CA 公钥添加至信任列表的 Java KeyStore。在 OpenShift 中使用 config map 将这个 Keystore 上传。使用 `oc` 命令基于文件创建一个 config map:
|
||||
```
|
||||
oc create configmap maven-settings --from-file=settings.xml=settings.xml --from-
|
||||
|
||||
file=myTruststore.jks=myTruststore.jks
|
||||
|
||||
```
|
||||
|
||||
将这个 config map 挂载至 Jenkins 代理容器。在本例中我们使用 `/home/jenkins/.m2` 目录,但这仅仅是因为配置文件 `settings.xml` 也对应这个 config map,KeyStore 可以放置在任意路径下。
|
||||
|
||||
接着在容器环境变量 `MAVEN_OPTS` 中设置 Java 参数,以便让 Maven 对应的 Java 进程使用该文件:
|
||||
```
|
||||
MAVEN_OPTS=
|
||||
|
||||
-Djavax.net.ssl.trustStore=/home/jenkins/.m2/myTruststore.jks
|
||||
|
||||
-Djavax.net.ssl.trustStorePassword=changeit
|
||||
|
||||
```
|
||||
|
||||
### 内存使用量
|
||||
|
||||
这可能是最重要的一部分设置,如果没有正确的设置最大内存,我们会遇到间歇性构建失败,虽然每个组件都似乎工作正常。
|
||||
|
||||
如果没有在 Java 命令行中设置堆大小,在容器中运行 Java 可能导致高内存使用量的报错。JVM [可以利用全部的主机内存][23],因而使用[默认的堆大小限制][24]。这通常会超过容器的内存资源总额,故当 Java 进程为堆分配过多内存时,OpenShift 会直接杀掉容器。
|
||||
|
||||
虽然 `jenkins` `-slave-base` 镜像包含一个内建[脚本设置堆最大为][25]容器内存的一半 (可以通过环境变量 `CONTAINER_HEAP_PERCENT=0.50` 修改),但这只适用于 Jenkins 代理节点中的 Java 进程。在 Maven 构建中,还有其它重要的 Java 进程运行:
|
||||
|
||||
* `mvn` 命令本身就是一个 Java 工具。
|
||||
* [Maven Surefire 插件][26] 按默认参数派生的 JVM 用于运行单元测试。
|
||||
|
||||
|
||||
总结一下,容器中同时运行着三个重要的 Java 进程,预估内存使用量以避免 pod 被误杀是很重要的。每个进程都有不同的方式设置 JVM 参数:
|
||||
|
||||
* 我们在上面提到了 Jenkins 代理容器堆最大值的计算方法,但我们显然不应该让代理容器使用如此大的堆,毕竟还有两个 JVM 需要使用内存。对于 Jenkins 代理容器,可以设置 `JAVA_OPTS`。
|
||||
* `mvn` 工具被 Jenkins 任务调用。设置 `JAVA_OPTS` 可以用于自定义这类 Java 进程。
|
||||
* Maven `surefire` 插件派生的用于单元测试的 JVM 可以通过 Maven [argLine][27] 属性自定义。可以在 `pom.xml` 或 `settings.xml` 的某个配置文件中设置,也可以直接在 `maven` 命令参数 `MAVEN_OPS` 中增加 `-DargLine=…`。
|
||||
|
||||
|
||||
下面例子给出 Maven 代理容器环境变量设置方法:
|
||||
```
|
||||
JAVA_OPTS=-Xms64m -Xmx64m
|
||||
|
||||
MAVEN_OPTS=-Xms128m -Xmx128m -DargLine=${env.SUREFIRE_OPTS}
|
||||
|
||||
SUREFIRE_OPTS=-Xms256m -Xmx256m
|
||||
|
||||
```
|
||||
|
||||
我们的测试环境是具有 1024Mi 内存限额的代理容器,使用上述参数可以正常构建一个 SpringBoot 应用并进行单元测试。测试环境使用的资源相对较小,对于复杂的 Maven 项目和对应的单元测试,我们需要更大的堆大小及更大的容器内存限额。
|
||||
|
||||
注:Java8 进程的实际内存使用量包括 `堆大小 + 元数据 + 堆外内存`,因此内存使用量会明显高于设置的最大堆大小。在我们上面的测试环境中,三个 Java 进程使用了超过 900Mi 的内存。可以在容器内查看进程的 RSS 内存使用情况,命令如下:`ps -e -o ``pid``,user``,``rss``,comm``,args`。
|
||||
|
||||
Jenkins 代理镜像同时安装了 JDK 64 位和 32 位版本。对于 `mvn` 和 `surefire`,默认使用 64 位版本 JVM。为减低内存使用量,只要 `-Xmx` 不超过 1.5 GB,强制使用 32 位 JVM 都是有意义的。
|
||||
```
|
||||
JAVA_HOME=/usr/lib/jvm/Java-1.8.0-openjdk-1.8.0.161–0.b14.el7_4.i386
|
||||
|
||||
```
|
||||
|
||||
注意到我们可以在 `JAVA_TOOL_OPTIONS` 环境变量中设置 Java 参数,每个 JVM 启动时都会读取该参数。`JAVA_OPTS` 和 `MAVEN_OPTS` 中的参数会覆盖 `JAVA_TOOL_OPTIONS` 中的对应值,故我们可以不使用 `argLine`,实现对 Java 进程同样的堆配置:
|
||||
```
|
||||
JAVA_OPTS=-Xms64m -Xmx64m
|
||||
|
||||
MAVEN_OPTS=-Xms128m -Xmx128m
|
||||
|
||||
JAVA_TOOL_OPTIONS=-Xms256m -Xmx256m
|
||||
|
||||
```
|
||||
|
||||
但缺点是每个 JVM 的日志中都会显示 `Picked up JAVA_TOOL_OPTIONS:`,这可能让人感到迷惑。
|
||||
|
||||
### Jenkins 流水线
|
||||
|
||||
完成上述配置,我们应该已经可以完成一次成功的构建。我们可以获取源代码,下载依赖,运行单元测试并将 artifact 上传到我们的库中。我们可以通过创建一个 Jenkins 流水线项目来完成上述操作。
|
||||
```
|
||||
pipeline {
|
||||
|
||||
/bin /boot /dev /etc /home /lib /lib64 /lost+found /media /mnt /opt /proc /root /run /sbin /srv /sys /tmp /usr /var Which container to bring up for the build. Pick one of the templates configured in Kubernetes plugin. */
|
||||
|
||||
agent {
|
||||
|
||||
label 'maven'
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
stages {
|
||||
|
||||
stage('Pull Source') {
|
||||
|
||||
steps {
|
||||
|
||||
git url: 'ssh://git@git.mycompany.com:22/myapplication.git', branch: 'master'
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
stage('Unit Tests') {
|
||||
|
||||
steps {
|
||||
|
||||
sh 'mvn test'
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
stage('Deploy to Nexus') {
|
||||
|
||||
steps {
|
||||
|
||||
sh 'mvn deploy -DskipTests'
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
当然,对应真实项目,CI/CD 流水线不仅仅完成 Maven 构建,还可以部署到开发环境,运行集成测试,提升至更接近于生产的环境等。上面给出的学习资料中有执行这些操作的案例。
|
||||
|
||||
### 多容器
|
||||
|
||||
一个 pod 可以运行多个容器,每个容器有单独的资源限制。这些容器共享网络接口,故我们可以从 `localhost` 访问已启动的服务,但我们需要考虑端口冲突的问题。在一个 Kubernetes pod 模板中,每个容器的环境变量是单独设置的,但挂载的卷是统一的。
|
||||
|
||||
当一个外部服务需要单元测试且嵌入式方案无法工作 (例如,数据库、消息中间件等) 时,可以启动多个容器。在这种情况下,第二个容器会随着 Jenkins 代理容器启停。
|
||||
|
||||
查看 Jenkins `config.xml` 片段,其中我们启动了一个辅助的 `httpbin` 服务用于 Maven 构建:
|
||||
```
|
||||
<org.csanchez.jenkins.plugins.kubernetes.PodTemplate>
|
||||
|
||||
<name>maven</name>
|
||||
|
||||
<volumes>
|
||||
|
||||
...
|
||||
|
||||
</volumes>
|
||||
|
||||
<containers>
|
||||
|
||||
<org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate>
|
||||
|
||||
<name>jnlp</name>
|
||||
|
||||
<image>registry.access.redhat.com/openshift3/jenkins-slave-maven-rhel7:v3.7</image>
|
||||
|
||||
<resourceLimitCpu>500m</resourceLimitCpu>
|
||||
|
||||
<resourceLimitMemory>1024Mi</resourceLimitMemory>
|
||||
|
||||
<envVars>
|
||||
|
||||
...
|
||||
|
||||
</envVars>
|
||||
|
||||
...
|
||||
|
||||
</org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate>
|
||||
|
||||
<org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate>
|
||||
|
||||
<name>httpbin</name>
|
||||
|
||||
<image>citizenstig/httpbin</image>
|
||||
|
||||
<resourceLimitCpu></resourceLimitCpu>
|
||||
|
||||
<resourceLimitMemory>256Mi</resourceLimitMemory>
|
||||
|
||||
<envVars/>
|
||||
|
||||
...
|
||||
|
||||
</org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate>
|
||||
|
||||
</containers>
|
||||
|
||||
<envVars/>
|
||||
|
||||
</org.csanchez.jenkins.plugins.kubernetes.PodTemplate>
|
||||
|
||||
```
|
||||
|
||||
### 总结
|
||||
|
||||
作为总结,我们查看上面已描述配置的 Jenkins `config.xml` 对应创建的 OpenShift 资源以及 Kubernetes 插件的配置。
|
||||
```
|
||||
apiVersion: v1
|
||||
|
||||
kind: List
|
||||
|
||||
metadata: {}
|
||||
|
||||
items:
|
||||
|
||||
- apiVersion: v1
|
||||
|
||||
kind: ConfigMap
|
||||
|
||||
metadata:
|
||||
|
||||
name: git-config
|
||||
|
||||
data:
|
||||
|
||||
config: |
|
||||
|
||||
[credential]
|
||||
|
||||
helper = store --file=/home/jenkins/.config/git-secret/credentials
|
||||
|
||||
[http "http://git.mycompany.com"]
|
||||
|
||||
sslCAInfo = /home/jenkins/.config/git/myTrustedCA.pem
|
||||
|
||||
myTrustedCA.pem: |-
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
|
||||
MIIDVzCCAj+gAwIBAgIJAN0sC...
|
||||
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
- apiVersion: v1
|
||||
|
||||
kind: Secret
|
||||
|
||||
metadata:
|
||||
|
||||
name: git-secret
|
||||
|
||||
stringData:
|
||||
|
||||
ssh-privatekey: |-
|
||||
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
|
||||
...
|
||||
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
||||
credentials: |-
|
||||
|
||||
https://username:password@git.mycompany.com
|
||||
|
||||
https://user:pass@github.com
|
||||
|
||||
- apiVersion: v1
|
||||
|
||||
kind: ConfigMap
|
||||
|
||||
metadata:
|
||||
|
||||
name: git-ssh
|
||||
|
||||
data:
|
||||
|
||||
config: |-
|
||||
|
||||
Host git.mycompany.com
|
||||
|
||||
StrictHostKeyChecking yes
|
||||
|
||||
IdentityFile /home/jenkins/.config/git-secret/ssh-privatekey
|
||||
|
||||
known_hosts: '[git.mycompany.com]:22 ecdsa-sha2-nistp256 AAAdn7...'
|
||||
|
||||
- apiVersion: v1
|
||||
|
||||
kind: Secret
|
||||
|
||||
metadata:
|
||||
|
||||
name: maven-secret
|
||||
|
||||
stringData:
|
||||
|
||||
username: admin
|
||||
|
||||
password: admin123
|
||||
|
||||
```
|
||||
|
||||
基于文件创建另一个 config map:
|
||||
```
|
||||
oc create configmap maven-settings --from-file=settings.xml=settings.xml
|
||||
|
||||
--from-file=myTruststore.jks=myTruststore.jks
|
||||
|
||||
```
|
||||
|
||||
Kubernetes 插件配置如下:
|
||||
```
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
|
||||
<hudson>
|
||||
|
||||
...
|
||||
|
||||
<clouds>
|
||||
|
||||
<org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud plugin="kubernetes@1.0">
|
||||
|
||||
<name>openshift</name>
|
||||
|
||||
<defaultsProviderTemplate></defaultsProviderTemplate>
|
||||
|
||||
<templates>
|
||||
|
||||
<org.csanchez.jenkins.plugins.kubernetes.PodTemplate>
|
||||
|
||||
<inheritFrom></inheritFrom>
|
||||
|
||||
<name>maven</name>
|
||||
|
||||
<namespace></namespace>
|
||||
|
||||
<privileged>false</privileged>
|
||||
|
||||
<alwaysPullImage>false</alwaysPullImage>
|
||||
|
||||
<instanceCap>2147483647</instanceCap>
|
||||
|
||||
<slaveConnectTimeout>100</slaveConnectTimeout>
|
||||
|
||||
<idleMinutes>0</idleMinutes>
|
||||
|
||||
<label>maven</label>
|
||||
|
||||
<serviceAccount>jenkins37</serviceAccount>
|
||||
|
||||
<nodeSelector></nodeSelector>
|
||||
|
||||
<nodeUsageMode>NORMAL</nodeUsageMode>
|
||||
|
||||
<customWorkspaceVolumeEnabled>false</customWorkspaceVolumeEnabled>
|
||||
|
||||
<workspaceVolume class="org.csanchez.jenkins.plugins.kubernetes.volumes.workspace.EmptyDirWorkspaceVolume">
|
||||
|
||||
<memory>false</memory>
|
||||
|
||||
</workspaceVolume>
|
||||
|
||||
<volumes>
|
||||
|
||||
<org.csanchez.jenkins.plugins.kubernetes.volumes.SecretVolume>
|
||||
|
||||
<mountPath>/home/jenkins/.config/git-secret</mountPath>
|
||||
|
||||
<secretName>git-secret</secretName>
|
||||
|
||||
</org.csanchez.jenkins.plugins.kubernetes.volumes.SecretVolume>
|
||||
|
||||
<org.csanchez.jenkins.plugins.kubernetes.volumes.ConfigMapVolume>
|
||||
|
||||
<mountPath>/home/jenkins/.ssh</mountPath>
|
||||
|
||||
<configMapName>git-ssh</configMapName>
|
||||
|
||||
</org.csanchez.jenkins.plugins.kubernetes.volumes.ConfigMapVolume>
|
||||
|
||||
<org.csanchez.jenkins.plugins.kubernetes.volumes.ConfigMapVolume>
|
||||
|
||||
<mountPath>/home/jenkins/.config/git</mountPath>
|
||||
|
||||
<configMapName>git-config</configMapName>
|
||||
|
||||
</org.csanchez.jenkins.plugins.kubernetes.volumes.ConfigMapVolume>
|
||||
|
||||
<org.csanchez.jenkins.plugins.kubernetes.volumes.ConfigMapVolume>
|
||||
|
||||
<mountPath>/home/jenkins/.m2</mountPath>
|
||||
|
||||
<configMapName>maven-settings</configMapName>
|
||||
|
||||
</org.csanchez.jenkins.plugins.kubernetes.volumes.ConfigMapVolume>
|
||||
|
||||
</volumes>
|
||||
|
||||
<containers>
|
||||
|
||||
<org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate>
|
||||
|
||||
<name>jnlp</name>
|
||||
|
||||
<image>registry.access.redhat.com/openshift3/jenkins-slave-maven-rhel7:v3.7</image>
|
||||
|
||||
<privileged>false</privileged>
|
||||
|
||||
<alwaysPullImage>false</alwaysPullImage>
|
||||
|
||||
<workingDir>/tmp</workingDir>
|
||||
|
||||
<command></command>
|
||||
|
||||
<args>${computer.jnlpmac} ${computer.name}</args>
|
||||
|
||||
<ttyEnabled>false</ttyEnabled>
|
||||
|
||||
<resourceRequestCpu>500m</resourceRequestCpu>
|
||||
|
||||
<resourceRequestMemory>1024Mi</resourceRequestMemory>
|
||||
|
||||
<resourceLimitCpu>500m</resourceLimitCpu>
|
||||
|
||||
<resourceLimitMemory>1024Mi</resourceLimitMemory>
|
||||
|
||||
<envVars>
|
||||
|
||||
<org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar>
|
||||
|
||||
<key>JAVA_HOME</key>
|
||||
|
||||
<value>/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.161-0.b14.el7_4.i386</value>
|
||||
|
||||
</org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar>
|
||||
|
||||
<org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar>
|
||||
|
||||
<key>JAVA_OPTS</key>
|
||||
|
||||
<value>-Xms64m -Xmx64m</value>
|
||||
|
||||
</org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar>
|
||||
|
||||
<org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar>
|
||||
|
||||
<key>MAVEN_OPTS</key>
|
||||
|
||||
<value>-Xms128m -Xmx128m -DargLine=${env.SUREFIRE_OPTS} -Djavax.net.ssl.trustStore=/home/jenkins/.m2/myTruststore.jks -Djavax.net.ssl.trustStorePassword=changeit</value>
|
||||
|
||||
</org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar>
|
||||
|
||||
<org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar>
|
||||
|
||||
<key>SUREFIRE_OPTS</key>
|
||||
|
||||
<value>-Xms256m -Xmx256m</value>
|
||||
|
||||
</org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar>
|
||||
|
||||
<org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar>
|
||||
|
||||
<key>MAVEN_MIRROR_URL</key>
|
||||
|
||||
<value>https://nexus.mycompany.com/repository/maven-public</value>
|
||||
|
||||
</org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar>
|
||||
|
||||
<org.csanchez.jenkins.plugins.kubernetes.model.SecretEnvVar>
|
||||
|
||||
<key>MAVEN_SERVER_USERNAME</key>
|
||||
|
||||
<secretName>maven-secret</secretName>
|
||||
|
||||
<secretKey>username</secretKey>
|
||||
|
||||
</org.csanchez.jenkins.plugins.kubernetes.model.SecretEnvVar>
|
||||
|
||||
<org.csanchez.jenkins.plugins.kubernetes.model.SecretEnvVar>
|
||||
|
||||
<key>MAVEN_SERVER_PASSWORD</key>
|
||||
|
||||
<secretName>maven-secret</secretName>
|
||||
|
||||
<secretKey>password</secretKey>
|
||||
|
||||
</org.csanchez.jenkins.plugins.kubernetes.model.SecretEnvVar>
|
||||
|
||||
</envVars>
|
||||
|
||||
<ports/>
|
||||
|
||||
<livenessProbe>
|
||||
|
||||
<execArgs></execArgs>
|
||||
|
||||
<timeoutSeconds>0</timeoutSeconds>
|
||||
|
||||
<initialDelaySeconds>0</initialDelaySeconds>
|
||||
|
||||
<failureThreshold>0</failureThreshold>
|
||||
|
||||
<periodSeconds>0</periodSeconds>
|
||||
|
||||
<successThreshold>0</successThreshold>
|
||||
|
||||
</livenessProbe>
|
||||
|
||||
</org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate>
|
||||
|
||||
<org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate>
|
||||
|
||||
<name>httpbin</name>
|
||||
|
||||
<image>citizenstig/httpbin</image>
|
||||
|
||||
<privileged>false</privileged>
|
||||
|
||||
<alwaysPullImage>false</alwaysPullImage>
|
||||
|
||||
<workingDir></workingDir>
|
||||
|
||||
<command>/run.sh</command>
|
||||
|
||||
<args></args>
|
||||
|
||||
<ttyEnabled>false</ttyEnabled>
|
||||
|
||||
<resourceRequestCpu></resourceRequestCpu>
|
||||
|
||||
<resourceRequestMemory>256Mi</resourceRequestMemory>
|
||||
|
||||
<resourceLimitCpu></resourceLimitCpu>
|
||||
|
||||
<resourceLimitMemory>256Mi</resourceLimitMemory>
|
||||
|
||||
<envVars/>
|
||||
|
||||
<ports/>
|
||||
|
||||
<livenessProbe>
|
||||
|
||||
<execArgs></execArgs>
|
||||
|
||||
<timeoutSeconds>0</timeoutSeconds>
|
||||
|
||||
<initialDelaySeconds>0</initialDelaySeconds>
|
||||
|
||||
<failureThreshold>0</failureThreshold>
|
||||
|
||||
<periodSeconds>0</periodSeconds>
|
||||
|
||||
<successThreshold>0</successThreshold>
|
||||
|
||||
</livenessProbe>
|
||||
|
||||
</org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate>
|
||||
|
||||
</containers>
|
||||
|
||||
<envVars/>
|
||||
|
||||
<annotations/>
|
||||
|
||||
<imagePullSecrets/>
|
||||
|
||||
</org.csanchez.jenkins.plugins.kubernetes.PodTemplate>
|
||||
|
||||
</templates>
|
||||
|
||||
<serverUrl>https://172.30.0.1:443</serverUrl>
|
||||
|
||||
<serverCertificate>-----BEGIN CERTIFICATE-----
|
||||
|
||||
MIIC6jCC...
|
||||
|
||||
-----END CERTIFICATE-----</serverCertificate>
|
||||
|
||||
<skipTlsVerify>false</skipTlsVerify>
|
||||
|
||||
<namespace>first</namespace>
|
||||
|
||||
<jenkinsUrl>http://jenkins.cicd.svc:80</jenkinsUrl>
|
||||
|
||||
<jenkinsTunnel>jenkins-jnlp.cicd.svc:50000</jenkinsTunnel>
|
||||
|
||||
<credentialsId>1a12dfa4-7fc5-47a7-aa17-cc56572a41c7</credentialsId>
|
||||
|
||||
<containerCap>10</containerCap>
|
||||
|
||||
<retentionTimeout>5</retentionTimeout>
|
||||
|
||||
<connectTimeout>0</connectTimeout>
|
||||
|
||||
<readTimeout>0</readTimeout>
|
||||
|
||||
<maxRequestsPerHost>32</maxRequestsPerHost>
|
||||
|
||||
</org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud>
|
||||
|
||||
</clouds>
|
||||
|
||||
|
||||
|
||||
</hudson>
|
||||
|
||||
```
|
||||
|
||||
尝试愉快的构建吧!
|
||||
|
||||
原文发表于 [ITNext][28],已获得翻版授权。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/18/4/running-jenkins-builds-containers
|
||||
|
||||
作者:[Balazs Szeti][a]
|
||||
译者:[pinewall](https://github.com/pinewall)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://opensource.com/users/bszeti
|
||||
[1]:https://opensource.com/resources/what-docker
|
||||
[2]:https://opensource.com/resources/what-is-kubernetes
|
||||
[3]:https://martinfowler.com/articles/microservices.html
|
||||
[4]:https://www.openshift.com/
|
||||
[5]:https://developers.redhat.com/products/cdk/overview/
|
||||
[6]:https://github.com/openshift/origin/tree/master/examples/jenkins
|
||||
[7]:https://wiki.jenkins.io/display/JENKINS/Distributed+builds
|
||||
[8]:https://jenkins.io/doc/book/pipeline/
|
||||
[9]:https://github.com/jenkinsci/kubernetes-plugin
|
||||
[10]:https://access.redhat.com/containers/#/search/jenkins%2520slave
|
||||
[11]:https://hub.docker.com/search/?isAutomated=0&isOfficial=0&page=1&pullCount=0&q=openshift+jenkins+slave+&starCount=0
|
||||
[12]:https://github.com/openshift/jenkins/tree/master/slave-base
|
||||
[13]:https://github.com/openshift/jenkins/tree/master/slave-maven
|
||||
[14]:https://github.com/openshift/jenkins/tree/master/slave-nodejs
|
||||
[15]:https://docs.openshift.com/container-platform/3.7/architecture/core_concepts/builds_and_image_streams.html#source-build
|
||||
[16]:https://github.com/bszeti/camel-springboot/tree/master/camel-rest-complex
|
||||
[17]:https://issues.jenkins-ci.org/browse/JENKINS-47112
|
||||
[18]:https://git-scm.com/docs/git-credential-store/1.8.2
|
||||
[19]:https://git-scm.com/docs/git-config/1.8.2
|
||||
[20]:https://bugzilla.redhat.com/show_bug.cgi?id=1430322
|
||||
[21]:https://maven.apache.org/pom.html#Distribution_Management
|
||||
[22]:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html
|
||||
[23]:https://developers.redhat.com/blog/2017/03/14/java-inside-docker/
|
||||
[24]:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/parallel.html#default_heap_size
|
||||
[25]:https://github.com/openshift/jenkins/blob/master/slave-base/contrib/bin/run-jnlp-client
|
||||
[26]:http://maven.apache.org/surefire/maven-surefire-plugin/examples/fork-options-and-parallel-execution.html
|
||||
[27]:http://maven.apache.org/surefire/maven-surefire-plugin/test-mojo.html#argLine
|
||||
[28]:https://itnext.io/running-jenkins-builds-in-containers-458e90ff2a7b
|
||||
[29]:https://docs.openshift.com/container-platform/3.7/using_images/other_images/jenkins.html
|
||||
[30]:https://github.com/openshift/jenkins
|
||||
[31]:https://blog.openshift.com/cicd-with-openshift/
|
||||
[32]:http://v1.uncontained.io/playbooks/continuous_delivery/external-jenkins-integration.html
|
@ -0,0 +1,69 @@
|
||||
Stratis 从 ZFS, Btrfs 和 LVM 学到哪些
|
||||
======
|
||||

|
||||
|
||||
在本系列[第一部分][1]中提到,Stratis 是一个<ruby>卷管理文件系统<rt>volume-managing filesystem, VMF</rt></ruby>,功能特性类似于 [ZFS][2] 和 [Btrfs][3]。在设计 Stratis 过程中,我们研究了已有解决方案开发者做出的取舍。
|
||||
|
||||
### 为何不使用已有解决方案
|
||||
|
||||
理由千差万别。先说说 [ZFS][2],它最初由 Sun Microsystems 为 Solaris (目前为 Oracle 所有)开发,后移植到 Linux。但 [CDDL][4] 协议授权的代码无法合并到 [GPL][5] 协议授权的 Linux 源码树中。CDDL 与 GPLv2 是否真的不兼容有待讨论,但这种不确定性足以打消企业级 Linux 供应商采用并支持 ZFS 的积极性。
|
||||
|
||||
[Btrfs][3] 发展也很好,没有授权问题。它已经多年被很多用户列为“最佳文件系统”,但在稳定性和功能特性方面仍有待提高。
|
||||
|
||||
我们希望打破现状,解决已有方案的种种问题,这种渴望促成了 Stratis。
|
||||
|
||||
### Stratis 如何与众不同
|
||||
|
||||
ZFS 和 Btrfs 让我们知道一件事情,即编写一个内核支持的 VMF 文件系统需要花费极大的时间和精力,以便消除漏洞、增强稳定性。涉及核心数据时,提供正确性保证是必要的。如果 Stratis 也采用这种方案并从零开始的话,开发工作也需要十数年,这是无法接受的。
|
||||
|
||||
相反地,Stratis 采用 Linux 内核的其它一些已有特性:[device mapper][6] 子系统以及久经考验的高性能文件系统 [XFS][7],其中前者被 LVM 用于提供 RAID、精简配置和其它块设备特性而广为人知。Stratis 将已有技术作为(技术架构中的)层来创建存储池,目标是通过集成为用户提供一个看似无缝的整体。
|
||||
|
||||
### Stratis 从 ZFS 学到哪些
|
||||
|
||||
对很多用户而言,ZFS 影响了他们对下一代文件系统的预期。通过查看人们在互联网上关于 ZFS 的讨论,我们设定了 Stratis 的最初开发目标。ZFS 的设计思路也潜在地为我们指明应该避免哪些东西。例如,当挂载一个在其它主机上创建的存储池时,ZFS 需要一个“<ruby>导入<rt>import</rt></ruby>”步骤。这样做出于某些原因,但每一种原因都似乎是 Stratis 需要解决的问题,无论是否采用同样的实现方式。
|
||||
|
||||
对于增加新硬盘或将已有硬盘替换为更大容量的硬盘,ZFS 有一些限制,尤其是存储池做了冗余配置的时候,这一点让我们不太满意。当然,这么设计也是有其原因的,但我们更愿意将其视为可以改进的空间。
|
||||
|
||||
最后,一旦掌握了 ZFS 的命令行工具,用户体验很好。我们希望让 Stratis 的命令行工具能够保持这种体验;同时,我们也很喜欢 ZFS 命令行工具的发展趋势,包括使用<ruby>必选参数<rt>positional parameters</rt></ruby>和控制每个命令需要的键盘输入量。
|
||||
|
||||
(LCTT 译注:位置参数来自脚本,$n 代表第 n 个参数)
|
||||
|
||||
### Stratis 从 Btrfs 学到哪些
|
||||
|
||||
Btrfs 让我们满意的一点是,有单一的包含必选子命令的命令行工具。Btrfs 也将冗余(选择对应的 Btrfs profiles)视为存储池的特性之一。而且和 ZFS 相比实现方式更好理解,也允许增加甚至移除硬盘。
|
||||
|
||||
(LCTT 译注:Btrfs profiles 包括 single/DUP 和 各种 RAID 等类型)
|
||||
|
||||
最后,通过了解 ZFS 和 Btrfs 共有的特性,例如快照的实现、对发送/接收的支持,让我们更好的抉择 Stratis 应该包括的特性。
|
||||
|
||||
### Stratis 从 LVM 学到哪些
|
||||
|
||||
在 Stratis 设计阶段早期,我们仔细研究了 LVM。LVM 目前是 Linux device mapper (DM) 最主要的使用者;事实上,DM 就是由 LVM 的核心开发团队维护的。我们研究了将 LVM 真的作为 Stratis 其中一层的可能性,也使用 DM 做了实验,其中 Stratis 可以作为<ruby>对等角色<rt>peer</rt></ruby>直接与 LVM 打交道。我们参考了 LVM 的<ruby>磁盘元数据格式<rt>on-disk metadata format</rt></ruby>(也结合 ZFS 和 XFS 的相应格式),获取灵感并定义了 Stratis 的磁盘元数据格式。
|
||||
|
||||
在提到的项目中,LVM 与 Stratis 内在地有最多的共性,毕竟它们都使用 DM。不过从使用的角度来看,LVM 内在工作更加透明,为专业用户提供相当多的控制和选项,使其可以精确配置<ruby>卷组<rt>volume group</rt></ruby>(存储池)的<ruby>布局<rt>layout</rt></ruby>;但 Stratis 不采用这种方式。
|
||||
|
||||
### 多种多样的解决方案
|
||||
|
||||
基于自由和开源软件工作的明显好处在于,没有什么组件是不可替代的。包括内核在内的每个组成部分都是开源的,可以查看修改源代码,如果当前的软件不能满足用户需求可以用其它软件替换。新项目产生不一定意味着旧项目的终结,只要都有足够的(社区)支持,两者可以并行存在。
|
||||
|
||||
对于寻找一个不存在争议、简单易用、强大的本地存储管理解决方案的人而言,Stratis 是更好满足其需求的一种尝试。这意味着一种设计思路所做的抉择不一定对所有用户适用。考虑到用户的其它需求,另一种设计思路可能需要艰难的做出抉择。所有用户可以选择最适合其的工作的工具并从这种自由选择中受益。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/18/4/stratis-lessons-learned
|
||||
|
||||
作者:[Andy Grover][a]
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
译者:[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/agrover
|
||||
[1]:https://opensource.com/article/18/4/stratis-easy-use-local-storage-management-linux
|
||||
[2]:https://en.wikipedia.org/wiki/ZFS
|
||||
[3]:https://en.wikipedia.org/wiki/Btrfs
|
||||
[4]:https://en.wikipedia.org/wiki/Common_Development_and_Distribution_License
|
||||
[5]:https://en.wikipedia.org/wiki/GNU_General_Public_License
|
||||
[6]:https://en.wikipedia.org/wiki/Device_mapper
|
||||
[7]:https://en.wikipedia.org/wiki/XFS
|
@ -0,0 +1,120 @@
|
||||
Vim-plug:极简 Vim 插件管理器
|
||||
======
|
||||
|
||||

|
||||
|
||||
当没有插件管理器时,Vim 用户必须手动下载 tarball 包的插件并将它们解压到 **~/.vim** 目录中。在少量插件的时候可以。当他们安装更多的插件时,就会变得一团糟。所有插件文件分散在单个目录中,用户无法找到哪个文件属于哪个插件。此外,他们无法找到他们应该删除哪个文件来卸载插件。这时 Vim 插件管理器就可以派上用场。插件管理器将安装插件的文件保存在单独的目录中,因此管理所有插件变得非常容易。我们几个月前已经写了关于 [**Vundle**][1] 的文章。今天,我们将看到又一个名为 **“Vim-plug”** 的 Vim 插件管理器。
|
||||
|
||||
Vim-plug 是一个免费、开源、速度非常快的、极简的 vim 插件管理器。它可以并行安装或更新插件。你还可以回滚更新。它创建浅层克隆以最小化磁盘空间使用和下载时间。它支持按需加载插件以加快启动时间。其他值得注意的特性是分支/标签/提交支持、post-update hook、支持外部管理的插件等。
|
||||
|
||||
### Vim-plug:一个极简的 Vim 插件管理器
|
||||
|
||||
#### **安装**
|
||||
|
||||
安装和使用起来非常容易。你只需打开终端并运行以下命令:
|
||||
```
|
||||
$ curl -fLo ~/.vim/autoload/plug.vim --create-dirs https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
|
||||
|
||||
```
|
||||
|
||||
Neovim 用户可以使用以下命令安装 Vim-plug:
|
||||
```
|
||||
$ curl -fLo ~/.config/nvim/autoload/plug.vim --create-dirs https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
|
||||
|
||||
```
|
||||
|
||||
#### 用法
|
||||
|
||||
**安装插件**
|
||||
|
||||
要安装插件,你必须如下所示首先在 Vim 配置文件中声明它们。一般 Vim 的配置文件是 **~/.vimrc**,Neovim 的配置文件是 **~/.config/nvim/init.vim**。请记住,当你在配置文件中声明插件时,列表应该以 **call plug#begin(PLUGIN_DIRECTORY)** 开始,并以 **plug#end()** 结束。
|
||||
|
||||
例如,我们安装 “lightline.vim” 插件。为此,请在 **~/.vimrc** 的顶部添加以下行。
|
||||
```
|
||||
call plug#begin('~/.vim/plugged')
|
||||
Plug 'itchyny/lightline.vim'
|
||||
call plug#end()
|
||||
|
||||
```
|
||||
|
||||
在 vim 配置文件中添加上面的行后,通过输入以下命令重新加载:
|
||||
```
|
||||
:source ~/.vimrc
|
||||
|
||||
```
|
||||
|
||||
或者,只需重新加载 Vim 编辑器。
|
||||
|
||||
现在,打开 vim 编辑器:
|
||||
```
|
||||
$ vim
|
||||
|
||||
```
|
||||
|
||||
使用以下命令检查状态:
|
||||
```
|
||||
:PlugStatus
|
||||
|
||||
```
|
||||
|
||||
然后输入下面的命令,然后按 ENTER 键安装之前在配置文件中声明的插件。
|
||||
```
|
||||
:PlugInstall
|
||||
|
||||
```
|
||||
|
||||
**更新插件**
|
||||
|
||||
要更新插件,请运行:
|
||||
```
|
||||
:PlugUpdate
|
||||
|
||||
```
|
||||
|
||||
更新插件后,按下 **d** 查看更改。或者,你可以之后输入 **:PlugDiff**。
|
||||
|
||||
**审查插件**
|
||||
|
||||
有时,更新的插件可能有新的 bug 或无法正常工作。要解决这个问题,你可以简单地回滚有问题的插件。输入 **:PlugDiff** 命令,然后按 ENTER 键查看上次 **:PlugUpdate**的更改,并在每个段落上按 **X** 将每个插件回滚到更新前的前一个状态。
|
||||
|
||||
**删除插件**
|
||||
|
||||
删除一个插件删除或注释掉你以前在你的 vim 配置文件中添加的 **plug** 命令。然后,运行 **:source ~/.vimrc** 或重启 Vim 编辑器。最后,运行以下命令卸载插件:
|
||||
```
|
||||
:PlugClean
|
||||
|
||||
```
|
||||
|
||||
该命令将删除 vim 配置文件中所有未声明的插件。
|
||||
|
||||
**升级 Vim-plug**
|
||||
|
||||
要升级vim-plug本身,请输入:
|
||||
```
|
||||
:PlugUpgrade
|
||||
|
||||
```
|
||||
|
||||
如你所见,使用 Vim-plug 管理插件并不难。它简化了插件管理。现在去找出你最喜欢的插件并使用 Vim-plug 来安装它们。
|
||||
|
||||
**建议阅读:**
|
||||
|
||||
就是这些了。我将很快在这里发布另一个有趣的话题。在此之前,请继续关注 OSTechNix。
|
||||
|
||||
干杯!
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://www.ostechnix.com/vim-plug-a-minimalist-vim-plugin-manager/
|
||||
|
||||
作者:[SK][a]
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://www.ostechnix.com/author/sk/
|
||||
[1]:https://www.ostechnix.com/manage-vim-plugins-using-vundle-linux/
|
Loading…
Reference in New Issue
Block a user