vim教程

分類 devops, vim

Vim 编辑器使用笔记整理

1. 退出 Vim

命令行模式退出方式

命令 说明
:wq 保存并退出
:q! 强制退出,不保存
:q 退出(未修改时)
:wq! 强制保存并退出
:w <文件路径> 另存为指定文件
:saveas 文件路径 另存为指定文件
:x 保存并退出(类似:wq)

普通模式退出方式

  • 输入 Shift+zz 即可保存退出

2. 删除文本

普通模式删除命令

命令 说明
x 删除游标所在字符
X 删除游标前一个字符
Delete x
dd 删除整行
dw 删除一个单词(不适用中文)
d$D 删除至行尾
d^ 删除至行首
dG 删除到文档结尾
d1G 删除至文档开头
:%d 清空整个文档内容(删除所有行)
:1,$d :%d,删除从第一行到最后一行
ggdG 普通模式下清空文档的快捷方式(先跳转到首行,然后删除到末尾)

注意:这些命令会立即生效且不可撤销,使用前请确保已保存重要内容

数字前缀用法

  • 2dd 表示一次删除2行
  • 3dw 表示删除3个单词

3. 重复执行命令

  • 普通模式下 . (小数点)表示重复上一次命令
  • 数字前缀:10x 删除10个连续字符

4. 游标跳转

行间跳转

命令 说明
nG 跳转到第n行(需先:set nu显示行号)
gg 跳转到第一行
G 跳转到最后一行

行内跳转

命令 说明
w 到下一个单词开头
e 到当前单词结尾
b 到前一个单词开头
ge 到前一个单词结尾
0^ 到行头
$ 到行尾
f<字母> 向后搜索字母并跳转
F<字母> 向前搜索字母并跳转
t<字母> 向后搜索字母并跳转到匹配前
T<字母> 向前搜索字母并跳转到匹配后

5. 复制粘贴和剪切

复制命令(yank)

命令 说明
yy 复制整行(3yy复制3行)
y^ 复制至行首
y$ 复制至行尾
yw 复制一个单词
yG 复制至文本末
y1G 复制至文本开头

粘贴命令

命令 说明
p 粘贴至光标后
P 粘贴至光标前

6. 替换和撤销

命令 说明
r+<字母> 替换游标所在字母
R 连续替换(按Esc结束)
cc 替换整行
cw 替换一个单词
C 替换至行末
~ 反转字母大小写
u 撤销操作
U 撤销当前行所有修改
Ctrl+r 重做(redo)

7. 缩进调整

缩进命令

命令 说明
>> 整行向右缩进
<< 整行向左回退
:set shiftwidth=n 设置缩进字符数

文本位置调整

命令 说明
:ce 本行内容居中
:ri 本行文本靠右
:le 本行内容靠左

8. 查找功能

基本查找

命令 说明
/字符串 向下查找
?字符串 向上查找
n 继续查找
N 反向查找

高级查找

命令 说明
* 向后查找当前单词
# 向前查找当前单词
g* 向后查找部分匹配单词
g# 向前查找部分匹配单词

9. 多文件编辑

多文件操作

命令 说明
:n 编辑下一个文件
:N 编辑上一个文件
:e 文件名 打开新文件
:e# 回到前一个文件
:ls 列出编辑过的文档
:b 文件名/编号 切换到指定文件
:bd 文件名/编号 从列表删除文件
:f 显示当前文件名

文件恢复

1
2
vim -r 文件名
:ewcover 文件名

10. 可视模式

进入可视模式

命令 说明
v 字符选择模式
V 行选择模式
Ctrl+v 区域选择模式

可视模式操作

  • d 删除选中区域
  • y 复制选中区域

11. 视窗操作

窗口分割

命令 说明
:new 新建窗口
:sp 文件名 水平分割窗口
:vsp 文件名 垂直分割窗口
Ctrl+w s 水平分割当前窗口
Ctrl+w v 垂直分割当前窗口

窗口切换

命令 说明
Ctrl+w j/k/h/l 向下/上/左/右切换窗口
Ctrl+w q 关闭当前窗口
Ctrl+w o 只保留当前窗口

12. 其他功能

文档加密

1
vim -x 文件名

执行外部命令

1
2
:!命令
:w 文件名 # 另存为

帮助系统

1
2
3
:F1        # 打开帮助
:h 主题 # 查看特定帮助
:ver # 显示版本

功能设定

1
2
3
4
:set nu    # 显示行号
:set ai # 自动缩进
:set aw # 自动保存
:set cin # C语言风格缩进

提示:所有设置可通过修改 ~/.vimrc 文件永久保存

留言與分享

GitHub Actions 是 GitHub 的持续集成服务,于2018年10月推出

这些天,我一直在试用,觉得它非常强大,有创意,比 Travis CI 玩法更多。

本文是一个简单教程,演示如何使用 GitHub Actions 自动发布一个 React 应用到 GitHub Pages

一、GitHub Actions 是什么?

大家知道,持续集成由很多操作组成,比如抓取代码、运行测试、登录远程服务器,发布到第三方服务等等。GitHub 把这些操作就称为 actions。

很多操作在不同项目里面是类似的,完全可以共享。GitHub 注意到了这一点,想出了一个很妙的点子,允许开发者把每个操作写成独立的脚本文件,存放到代码仓库,使得其他开发者可以引用。

如果你需要某个 action,不必自己写复杂的脚本,直接引用他人写好的 action 即可,整个持续集成过程,就变成了一个 actions 的组合。这就是 GitHub Actions 最特别的地方。

GitHub 做了一个官方市场,可以搜索到他人提交的 actions。另外,还有一个 awesome actions 的仓库,也可以找到不少 action。

上面说了,每个 action 就是一个独立脚本,因此可以做成代码仓库,使用userName/repoName的语法引用 action。比如,actions/setup-node就表示github.com/actions/setup-node这个仓库,它代表一个 action,作用是安装 Node.js。事实上,GitHub 官方的 actions 都放在 github.com/actions 里面。

既然 actions 是代码仓库,当然就有版本的概念,用户可以引用某个具体版本的 action。下面都是合法的 action 引用,用的就是 Git 的指针概念,详见官方文档

1
2
3
4

actions/setup-node@74bc508 # 指向一个 commit
actions/[email protected] # 指向一个标签
actions/setup-node@master # 指向一个分支

二、基本概念

GitHub Actions 有一些自己的术语。

(1)workflow (工作流程):持续集成一次运行的过程,就是一个 workflow。

(2)job (任务):一个 workflow 由一个或多个 jobs 构成,含义是一次持续集成的运行,可以完成多个任务。

(3)step(步骤):每个 job 由多个 step 构成,一步步完成。

(4)action (动作):每个 step 可以依次执行一个或多个命令(action)。

三、workflow 文件

GitHub Actions 的配置文件叫做 workflow 文件,存放在代码仓库的.github/workflows目录。

workflow 文件采用 YAML 格式,文件名可以任意取,但是后缀名统一为.yml,比如foo.yml。一个库可以有多个 workflow 文件。GitHub 只要发现.github/workflows目录里面有.yml文件,就会自动运行该文件。

workflow 文件的配置字段非常多,详见官方文档。下面是一些基本字段。

(1)name

name字段是 workflow 的名称。如果省略该字段,默认为当前 workflow 的文件名。

1
2

name: GitHub Actions Demo

(2)on

on字段指定触发 workflow 的条件,通常是某些事件。

1
2

on: push

上面代码指定,push事件触发 workflow。

on字段也可以是事件的数组。

1
2

on: [push, pull_request]

上面代码指定,push事件或pull_request事件都可以触发 workflow。

完整的事件列表,请查看官方文档。除了代码库事件,GitHub Actions 也支持外部事件触发,或者定时运行。

(3)on.<push|pull_request>.<tags|branches>

指定触发事件时,可以限定分支或标签。

1
2
3
4
5

on:
push:
branches:
- master

上面代码指定,只有master分支发生push事件时,才会触发 workflow。

(4)jobs.<job_id>.name

workflow 文件的主体是jobs字段,表示要执行的一项或多项任务。

jobs字段里面,需要写出每一项任务的job_id,具体名称自定义。job_id里面的name字段是任务的说明。

1
2
3
4
5
6

jobs:
my_first_job:
name: My first job
my_second_job:
name: My second job

上面代码的jobs字段包含两项任务,job_id分别是my_first_jobmy_second_job

(5)jobs.<job_id>.needs

needs字段指定当前任务的依赖关系,即运行顺序。

1
2
3
4
5
6
7

jobs:
job1:
job2:
needs: job1
job3:
needs: [job1, job2]

上面代码中,job1必须先于job2完成,而job3等待job1job2的完成才能运行。因此,这个 workflow 的运行顺序依次为:job1job2job3

(6)jobs.<job_id>.runs-on

runs-on字段指定运行所需要的虚拟机环境。它是必填字段。目前可用的虚拟机如下。

  • ubuntu-latestubuntu-18.04ubuntu-16.04
  • windows-latestwindows-2019windows-2016
  • macOS-latestmacOS-10.14

下面代码指定虚拟机环境为ubuntu-18.04

1
2

runs-on: ubuntu-18.04

(7)jobs.<job_id>.steps

steps字段指定每个 Job 的运行步骤,可以包含一个或多个步骤。每个步骤都可以指定以下三个字段。

  • jobs.<job_id>.steps.name:步骤名称。
  • jobs.<job_id>.steps.run:该步骤运行的命令或者 action。
  • jobs.<job_id>.steps.env:该步骤所需的环境变量。

下面是一个完整的 workflow 文件的范例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

name: Greeting from Mona
on: push

jobs:
my-job:
name: My Job
runs-on: ubuntu-latest
steps:
- name: Print a greeting
env:
MY_VAR: Hi there! My name is
FIRST_NAME: Mona
MIDDLE_NAME: The
LAST_NAME: Octocat
run: |
echo $MY_VAR $FIRST_NAME $MIDDLE_NAME $LAST_NAME.

上面代码中,steps字段只包括一个步骤。该步骤先注入四个环境变量,然后执行一条 Bash 命令。

四、实例:React 项目发布到 GitHub Pages

下面是一个实例,通过 GitHub Actions 构建一个 React 项目,并发布到 GitHub Pages。最终代码都在这个仓库里面,发布后的参考网址为ruanyf.github.io/github-actions-demo

第一步,GitHub Actions 目前还处在测试阶段,需要到这个网址申请测试资格。申请以后,可能需要几天才能通过。据说,2019年11月就会放开。

获得资格后,仓库顶部的菜单会出现Actions一项。

第二步,这个示例需要将构建成果发到 GitHub 仓库,因此需要 GitHub 密钥。按照官方文档,生成一个密钥。然后,将这个密钥储存到当前仓库的Settings/Secrets里面。

上图是储存秘密的环境变量的地方。环境变量的名字可以随便起,这里用的是ACCESS_TOKEN。如果你不用这个名字,后面脚本里的变量名也要跟着改。

第三步,本地计算机使用create-react-app,生成一个标准的 React 应用。

1
2
3

$ npx create-react-app github-actions-demo
$ cd github-actions-demo

然后,打开package.json文件,加一个homepage字段,表示该应用发布后的根目录(参见官方文档)。

1
2

"homepage": "https://[username].github.io/github-actions-demo",

上面代码中,将[username]替换成你的 GitHub 用户名,参见范例

第四步,在这个仓库的.github/workflows目录,生成一个 workflow 文件,名字可以随便取,这个示例是ci.yml

我们选用一个别人已经写好的 action:JamesIves/github-pages-deploy-action,它提供了 workflow 的范例文件,直接拷贝过来就行了(查看源码)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

name: GitHub Actions Build and Deploy Demo
on:
push:
branches:
- master
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master

- name: Build and Deploy
uses: JamesIves/github-pages-deploy-action@master
env:
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
BRANCH: gh-pages
FOLDER: build
BUILD_SCRIPT: npm install && npm run build

上面这个 workflow 文件的要点如下。

  1. 整个流程在master分支发生push事件时触发。
  2. 只有一个job,运行在虚拟机环境ubuntu-latest
  3. 第一步是获取源码,使用的 action 是actions/checkout
  4. 第二步是构建和部署,使用的 action 是JamesIves/github-pages-deploy-action
  5. 第二步需要四个环境变量,分别为 GitHub 密钥、发布分支、构建成果所在目录、构建脚本。其中,只有 GitHub 密钥是秘密变量,需要写在双括号里面,其他三个都可以直接写在文件里。

第五步,保存上面的文件后,将整个仓库推送到 GitHub。

GitHub 发现了 workflow 文件以后,就会自动运行。你可以在网站上实时查看运行日志,日志默认保存30天。

等到 workflow 运行结束,访问 GitHub Page,会看到构建成果已经发上网了。

以后,每次修改后推送源码,GitHub Actions 都会自动运行,将构建产物发布到网页。

五、参考链接

(完)

留言與分享

pytest基础

分類 devops, pytest

1. 概述

pytest 是一个功能强大的 Python 测试框架,具有以下特点:

  • 简单灵活,文档丰富
  • 支持参数化测试
  • 可用于单元测试、功能测试、自动化测试(如 Selenium/Appium)和接口测试
  • 丰富的插件生态系统
  • 支持测试跳过(skip)和预期失败(xfail)
  • 良好的 CI 工具集成(如 Jenkins)

主要插件

插件名称 功能描述
pytest-selenium Selenium 集成
pytest-html 生成 HTML 测试报告
pytest-rerunfailures 失败用例重试
pytest-xdist 多 CPU 并行测试

2. 使用介绍

2.1 安装

1
pip install pytest

2.2 编写规则

  • 测试文件:test_*.py*_test.py
  • 测试类:以 Test 开头,不能有 __init__ 方法
  • 测试函数:以 test_ 开头
  • 断言:使用 Python 原生 assert

2.3 示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# pytest1.py
import pytest

@pytest.fixture(scope='function')
def setup_function(request):
def teardown_function():
print("teardown_function called.")
request.addfinalizer(teardown_function)
print('setup_function called.')

@pytest.fixture(scope='module')
def setup_module(request):
def teardown_module():
print("teardown_module called.")
request.addfinalizer(teardown_module)
print('setup_module called.')

@pytest.mark.website
def test_1(setup_function):
print('Test_1 called.')

def test_2(setup_module):
print('Test_2 called.')

def test_3(setup_module):
print('Test_3 called.')

2.4 fixture 的 scope 参数

参数值 作用范围
function 每个测试函数运行一次(默认)
class 每个测试类运行一次
module 每个模块运行一次
session 每次会话运行一次

2.5 setup 和 teardown

  • setup:测试前的准备工作
  • teardown:测试后的清理工作
  • 替代方案:使用 yield 实现 setup/teardown

3. 测试执行

3.1 基本执行命令

命令 功能
pytest 运行当前目录下所有测试
pytest test_mod.py 运行指定模块的测试
pytest somepath/ 运行指定路径下的测试
pytest -k "stringexpr" 运行名称匹配表达式的测试
pytest test_mod.py::test_func 运行指定模块的特定测试函数

3.2 标记执行

1
2
3
4
5
# 执行标记为 website 的测试
pytest -v -m "website" pytest1.py

# 执行未标记为 website 的测试
pytest -v -m "not website" pytest1.py

3.3 常用命令行参数

参数 功能
-v 显示详细测试结果
-q 只显示简要结果
-s 显示 print 输出
-x 遇到第一个失败立即停止
--exitfirst -x
-h 显示帮助信息

4. 扩展插件

4.1 测试覆盖率 (pytest-cov)

1
2
3
4
pip install pytest-cov

# 生成 HTML 覆盖率报告
pytest --cov-report=html --cov=./ test_code_target_dir

覆盖率参数

参数 说明
--cov=[path] 指定计算覆盖率的路径
--cov-report=type 报告类型:term, html, xml 等
--cov-fail-under=MIN 设置覆盖率最低阈值

4.2 其他实用插件

插件 功能 安装命令
pytest-randomly 随机执行测试 pip install pytest-randomly
pytest-xdist 分布式测试 pip install pytest-xdist
pytest-instafail 立即显示失败 pip install pytest-instafail

5. 示例输出

5.1 详细模式 (-v)

1
2
3
4
5
6
7
8
$ pytest -v pytest1.py
==============================================================================
platform linux2 -- Python 2.7.14, pytest-3.0.0
collected 3 items

pytest1.py::test_1 PASSED
pytest1.py::test_3 PASSED
pytest1.py::test_2 PASSED

5.2 显示输出 (-s)

1
2
3
4
5
6
7
8
9
$ pytest -s pytest1.py
==============================================================================
setup_function called.
Test_1 called.
.teardown_function called.
setup_module called.
Test_2 called.
.Test_3 called.
.teardown_module called.

5.3 覆盖率报告

1
2
3
4
---------------------------------------------------------------- coverage: platform linux2, python 2.7.14-final-0 ----------------------------------------------------------------
Name Stmts Miss Cover
--------------------------------
pytest1.py 18 0 100%

6. 参考资源

  1. Python 测试工具大全
  2. 主流测试工具比较
  3. pytest 官方播客
  4. pytest 简介

留言與分享

Docker Compose

分類 devops, Docker, Docker Compose

# Compose 简介

Compose 项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排。从功能上看,跟 OpenStack 中的 Heat 十分类似。

其代码目前在 https://github.com/docker/compose (opens new window) 上开源。

Compose 定位是 「定义和运行多个 Docker 容器的应用(Defining and running multi-container Docker applications)」,其前身是开源项目 Fig。

通过第一部分中的介绍,我们知道使用一个 Dockerfile 模板文件,可以让用户很方便的定义一个单独的应用容器。然而,在日常工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器,甚至还包括负载均衡容器等。

Compose 恰好满足了这样的需求。它允许用户通过一个单独的 docker-compose.yml 模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。

Compose 中有两个重要的概念:

  • 服务 (service):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。

  • 项目 (project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。

Compose 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。

Compose 项目由 Python 编写,实现上调用了 Docker 服务提供的 API 来对容器进行管理。因此,只要所操作的平台支持 Docker API,就可以在其上利用 Compose 来进行编排管理。

# 安装与卸载

Compose 支持 Linux、macOS、Windows 三大平台。

Docker Desktop for Mac/Windows 自带 compose,安装 Docker 之后可以直接使用。

1
2
3
$ docker compose version

Docker Compose version v2.6.0

Linux 系统请使用以下介绍的方法安装。

# 二进制包

在 Linux 上的也安装十分简单,从 官方 GitHub Release (opens new window) 处直接下载编译好的二进制文件即可。

例如,在 Linux 64 位系统上直接下载对应的二进制包。

1
2
3
4
5
6
7
8
$ DOCKER_CONFIG=/usr/local/lib/docker/cli-plugins
$ sudo mkdir -p $DOCKER_CONFIG/cli-plugins
$ sudo curl -SL https://github.com/docker/compose/releases/download/v2.6.1/docker-compose-linux-x86_64 -o $DOCKER_CONFIG/cli-plugins/docker-compose
$ sudo chmod +x $DOCKER_CONFIG/cli-plugins
$ docker compose version

# 国内用户可以使用以下方式加快下载
$ sudo curl -SL https://download.fastgit.org/docker/compose/releases/download/v2.6.1/docker-compose-linux-x86_64 -o $DOCKER_CONFIG/cli-plugins/docker-compose

# 使用

# 术语

首先介绍几个术语。

  • 服务 (service):一个应用容器,实际上可以运行多个相同镜像的实例。

  • 项目 (project):由一组关联的应用容器组成的一个完整业务单元。

可见,一个项目可以由多个服务(容器)关联而成,Compose 面向项目进行管理。

# 场景

最常见的项目是 web 网站,该项目应该包含 web 应用和缓存。

下面我们用 Python 来建立一个能够记录页面访问次数的 web 网站。

# web 应用

新建文件夹,在该目录中编写 app.py 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
from flask import Flask
from redis import Redis

app = Flask(__name__)
redis = Redis(host='redis', port=6379)

@app.route('/')
def hello():
count = redis.incr('hits')
return 'Hello World! 该页面已被访问 {} 次。\n'.format(count)

if __name__ == "__main__":
app.run(host="0.0.0.0", debug=True)

# Dockerfile

编写 Dockerfile 文件,内容为

1
2
3
4
5
FROM python:3.6-alpine
ADD . /code
WORKDIR /code
RUN pip install redis flask
CMD ["python", "app.py"]

# docker-compose.yml

编写 docker-compose.yml 文件,这个是 Compose 使用的主模板文件。

1
2
3
4
5
6
7
8
9
10
version: '3'
services:

web:
build: .
ports:
- "5000:5000"

redis:
image: "redis:alpine"

# 运行 compose 项目

此时访问本地 5000 端口,每次刷新页面,计数就会加 1。

# Compose 命令说明

# 命令对象与格式

对于 Compose 来说,大部分命令的对象既可以是项目本身,也可以指定为项目中的服务或者容器。如果没有特别的说明,命令对象将是项目,这意味着项目中所有的服务都会受到命令影响。

执行 docker compose [COMMAND] --help 可以查看具体某个命令的使用格式。

docker compose 命令的基本的使用格式是

1
docker compose [-f=<arg>...] [options] [COMMAND] [ARGS...]

# 命令选项

  • -f, --file FILE 指定使用的 Compose 模板文件,默认为 docker-compose.yml,可以多次指定。

  • -p, --project-name NAME 指定项目名称,默认将使用所在目录名称作为项目名。

  • --verbose 输出更多调试信息。

  • -v, --version 打印版本并退出。

# 命令使用说明

# build

格式为 docker compose build [options] [SERVICE...]

构建(重新构建)项目中的服务容器。

服务容器一旦构建后,将会带上一个标记名,例如对于 web 项目中的一个 db 容器,可能是 web_db。

可以随时在项目目录下运行 docker compose build 来重新构建服务。

选项包括:

  • --force-rm 删除构建过程中的临时容器。

  • --no-cache 构建镜像过程中不使用 cache(这将加长构建过程)。

  • --pull 始终尝试通过 pull 来获取更新版本的镜像。

# config

验证 Compose 文件格式是否正确,若正确则显示配置,若格式错误显示错误原因。

# down

此命令将会停止 up 命令所启动的容器,并移除网络

# exec

进入指定的容器。

# help

获得一个命令的帮助。

# images

列出 Compose 文件中包含的镜像。

# kill

格式为 docker compose kill [options] [SERVICE...]

通过发送 SIGKILL 信号来强制停止服务容器。

支持通过 -s 参数来指定发送的信号,例如通过如下指令发送 SIGINT 信号。

1
$ docker compose kill -s SIGINT

# logs

格式为 docker compose logs [options] [SERVICE...]

查看服务容器的输出。默认情况下,docker compose 将对不同的服务输出使用不同的颜色来区分。可以通过 --no-color 来关闭颜色。

该命令在调试问题的时候十分有用。

# pause

格式为 docker compose pause [SERVICE...]

暂停一个服务容器。

# port

格式为 docker compose port [options] SERVICE PRIVATE_PORT

打印某个容器端口所映射的公共端口。

选项:

  • --protocol=proto 指定端口协议,tcp(默认值)或者 udp。

  • --index=index 如果同一服务存在多个容器,指定命令对象容器的序号(默认为 1)。

# ps

格式为 docker compose ps [options] [SERVICE...]

列出项目中目前的所有容器。

选项:

  • -q 只打印容器的 ID 信息。

# pull

格式为 docker compose pull [options] [SERVICE...]

拉取服务依赖的镜像。

选项:

  • --ignore-pull-failures 忽略拉取镜像过程中的错误。

# push

推送服务依赖的镜像到 Docker 镜像仓库。

# restart

格式为 docker compose restart [options] [SERVICE...]

重启项目中的服务。

选项:

  • -t, --timeout TIMEOUT 指定重启前停止容器的超时(默认为 10 秒)。

# rm

格式为 docker compose rm [options] [SERVICE...]

删除所有(停止状态的)服务容器。推荐先执行 docker compose stop 命令来停止容器。

选项:

  • -f, --force 强制直接删除,包括非停止状态的容器。一般尽量不要使用该选项。

  • -v 删除容器所挂载的数据卷。

# run

格式为 docker compose run [options] [-p PORT...] [-e KEY=VAL...] SERVICE [COMMAND] [ARGS...]

在指定服务上执行一个命令。

例如:

1
$ docker compose run ubuntu ping docker.com

将会启动一个 ubuntu 服务容器,并执行 ping docker.com 命令。

默认情况下,如果存在关联,则所有关联的服务将会自动被启动,除非这些服务已经在运行中。

该命令类似启动容器后运行指定的命令,相关卷、链接等等都将会按照配置自动创建。

两个不同点:

  • 给定命令将会覆盖原有的自动运行命令;

  • 不会自动创建端口,以避免冲突。

如果不希望自动启动关联的容器,可以使用 --no-deps 选项,例如

1
$ docker compose run --no-deps web python manage.py shell

将不会启动 web 容器所关联的其它容器。

选项:

  • -d 后台运行容器。

  • --name NAME 为容器指定一个名字。

  • --entrypoint CMD 覆盖默认的容器启动指令。

  • -e KEY=VAL 设置环境变量值,可多次使用选项来设置多个环境变量。

  • -u, --user="" 指定运行容器的用户名或者 uid。

  • --no-deps 不自动启动关联的服务容器。

  • --rm 运行命令后自动删除容器,d 模式下将忽略。

  • -p, --publish=[] 映射容器端口到本地主机。

  • --service-ports 配置服务端口并映射到本地主机。

  • -T 不分配伪 tty,意味着依赖 tty 的指令将无法运行。

# start

格式为 docker compose start [SERVICE...]

启动已经存在的服务容器。

# stop

格式为 docker compose stop [options] [SERVICE...]

停止已经处于运行状态的容器,但不删除它。通过 docker compose start 可以再次启动这些容器。

选项:

  • -t, --timeout TIMEOUT 停止容器时候的超时(默认为 10 秒)。

# top

查看各个服务容器内运行的进程。

# unpause

格式为 docker compose unpause [SERVICE...]

恢复处于暂停状态中的服务。

# up

格式为 docker compose up [options] [SERVICE...]

该命令十分强大,它将尝试自动完成包括构建镜像,(重新)创建服务,启动服务,并关联服务相关容器的一系列操作。

链接的服务都将会被自动启动,除非已经处于运行状态。

可以说,大部分时候都可以直接通过该命令来启动一个项目。

默认情况,docker compose up 启动的容器都在前台,控制台将会同时打印所有容器的输出信息,可以很方便进行调试。

当通过 Ctrl-C 停止命令时,所有容器将会停止。

如果使用 docker compose up -d,将会在后台启动并运行所有的容器。一般推荐生产环境下使用该选项。

默认情况,如果服务容器已经存在,docker compose up 将会尝试停止容器,然后重新创建(保持使用 volumes-from 挂载的卷),以保证新启动的服务匹配 docker-compose.yml 文件的最新内容。如果用户不希望容器被停止并重新创建,可以使用 docker compose up --no-recreate。这样将只会启动处于停止状态的容器,而忽略已经运行的服务。如果用户只想重新部署某个服务,可以使用 docker compose up --no-deps -d <SERVICE_NAME> 来重新创建服务并后台停止旧服务,启动新服务,并不会影响到其所依赖的服务。

选项:

  • -d 在后台运行服务容器。

  • --no-color 不使用颜色来区分不同的服务的控制台输出。

  • --no-deps 不启动服务所链接的容器。

  • --force-recreate 强制重新创建容器,不能与 --no-recreate 同时使用。

  • --no-recreate 如果容器已经存在了,则不重新创建,不能与 --force-recreate 同时使用。

  • --no-build 不自动构建缺失的服务镜像。

  • -t, --timeout TIMEOUT 停止容器时候的超时(默认为 10 秒)。

# version

格式为 docker compose version

打印版本信息。

# 参考资料

# Compose 模板文件

模板文件是使用 Compose 的核心,涉及到的指令关键字也比较多。但大家不用担心,这里面大部分指令跟 docker run 相关参数的含义都是类似的。

默认的模板文件名称为 docker-compose.yml,格式为 YAML 格式。

1
2
3
4
5
6
7
8
9
version: "3"

services:
webapp:
image: examples/web
ports:
- "80:80"
volumes:
- "/data"

注意每个服务都必须通过 image 指令指定镜像或 build 指令(需要 Dockerfile)等来自动构建生成镜像。

如果使用 build 指令,在 Dockerfile 中设置的选项(例如:CMD, EXPOSE, VOLUME, ENV 等) 将会自动被获取,无需在 docker-compose.yml 中重复设置。

下面分别介绍各个指令的用法。

# build

指定 Dockerfile 所在文件夹的路径(可以是绝对路径,或者相对 docker-compose.yml 文件的路径)。 Compose 将会利用它自动构建这个镜像,然后使用这个镜像。

1
2
3
4
5
version: '3'
services:

webapp:
build: ./dir

你也可以使用 context 指令指定 Dockerfile 所在文件夹的路径。

使用 dockerfile 指令指定 Dockerfile 文件名。

使用 arg 指令指定构建镜像时的变量。

1
2
3
4
5
6
7
8
9
version: '3'
services:

webapp:
build:
context: ./dir
dockerfile: Dockerfile-alternate
args:
buildno: 1

使用 cache_from 指定构建镜像的缓存

1
2
3
4
5
build:
context: .
cache_from:
- alpine:latest
- corp/web_app:3.14

# cap_add, cap_drop

指定容器的内核能力(capacity)分配。

例如,让容器拥有所有能力可以指定为:

去掉 NET_ADMIN 能力可以指定为:

# command

覆盖容器启动后默认执行的命令。

1
command: echo "hello world"

# configs

仅用于 Swarm mode,详细内容请查看 Swarm mode 一节。

# cgroup_parent

指定父 cgroup 组,意味着将继承该组的资源限制。

例如,创建了一个 cgroup 组名称为 cgroups_1

1
cgroup_parent: cgroups_1

# container_name

指定容器名称。默认将会使用 项目名称_服务名称_序号 这样的格式。

1
container_name: docker-web-container

注意: 指定容器名称后,该服务将无法进行扩展(scale),因为 Docker 不允许多个容器具有相同的名称。

# deploy

仅用于 Swarm mode,详细内容请查看 Swarm mode 一节

# devices

指定设备映射关系。

1
2
devices:
- "/dev/ttyUSB1:/dev/ttyUSB0"

# depends_on

解决容器的依赖、启动先后的问题。以下例子中会先启动 redis db 再启动 web

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: '3'

services:
web:
build: .
depends_on:
- db
- redis

redis:
image: redis

db:
image: postgres

注意:web 服务不会等待 redis db 「完全启动」之后才启动。

# dns

自定义 DNS 服务器。可以是一个值,也可以是一个列表。

1
2
3
4
5
dns: 8.8.8.8

dns:
- 8.8.8.8
- 114.114.114.114

配置 DNS 搜索域。可以是一个值,也可以是一个列表。

1
2
3
4
5
dns_search: example.com

dns_search:
- domain1.example.com
- domain2.example.com

# tmpfs

挂载一个 tmpfs 文件系统到容器。

1
2
3
4
tmpfs: /run
tmpfs:
- /run
- /tmp

# env_file

从文件中获取环境变量,可以为单独的文件路径或列表。

如果通过 docker compose -f FILE 方式来指定 Compose 模板文件,则 env_file 中变量的路径会基于模板文件路径。

如果有变量名称与 environment 指令冲突,则按照惯例,以后者为准。

1
2
3
4
5
6
env_file: .env

env_file:
- ./common.env
- ./apps/web.env
- /opt/secrets.env

环境变量文件中每一行必须符合格式,支持 # 开头的注释行。

1
2
# common.env: Set development environment
PROG_ENV=development

# environment

设置环境变量。你可以使用数组或字典两种格式。

只给定名称的变量会自动获取运行 Compose 主机上对应变量的值,可以用来防止泄露不必要的数据。

1
2
3
4
5
6
7
environment:
RACK_ENV: development
SESSION_SECRET:

environment:
- RACK_ENV=development
- SESSION_SECRET

如果变量名称或者值中用到 true|false,yes|no 等表达 布尔 (opens new window) 含义的词汇,最好放到引号里,避免 YAML 自动解析某些内容为对应的布尔语义。这些特定词汇,包括

1
y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF

# expose

暴露端口,但不映射到宿主机,只被连接的服务访问。

仅可以指定内部端口为参数

1
2
3
expose:
- "3000"
- "8000"

注意:不建议使用该指令。

链接到 docker-compose.yml 外部的容器,甚至并非 Compose 管理的外部容器。

1
2
3
4
external_links:
- redis_1
- project_db_1:mysql
- project_db_1:postgresql

# extra_hosts

类似 Docker 中的 --add-host 参数,指定额外的 host 名称映射信息。

1
2
3
extra_hosts:
- "googledns:8.8.8.8"
- "dockerhub:52.1.157.61"

会在启动后的服务容器中 /etc/hosts 文件中添加如下两条条目。

1
2
8.8.8.8 googledns
52.1.157.61 dockerhub

# healthcheck

通过命令检查容器是否健康运行。

1
2
3
4
5
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 1m30s
timeout: 10s
retries: 3

# image

指定为镜像名称或镜像 ID。如果镜像在本地不存在,Compose 将会尝试拉取这个镜像。

1
2
3
image: ubuntu
image: orchardup/postgresql
image: a4bc65fd

# labels

为容器添加 Docker 元数据(metadata)信息。例如可以为容器添加辅助说明信息。

1
2
3
4
labels:
com.startupteam.description: "webapp for a startup team"
com.startupteam.department: "devops department"
com.startupteam.release: "rc3 for v1.0"

注意:不推荐使用该指令。

# logging

配置日志选项。

1
2
3
4
logging:
driver: syslog
options:
syslog-address: "tcp://192.168.0.42:123"

目前支持三种日志驱动类型。

1
2
3
driver: "json-file"
driver: "syslog"
driver: "none"

options 配置日志驱动的相关参数。

1
2
3
options:
max-size: "200k"
max-file: "10"

# network_mode

设置网络模式。使用和 docker run--network 参数一样的值。

1
2
3
4
5
network_mode: "bridge"
network_mode: "host"
network_mode: "none"
network_mode: "service:[service name]"
network_mode: "container:[container name/id]"

# networks

配置容器连接的网络。

1
2
3
4
5
6
7
8
9
10
11
version: "3"
services:

some-service:
networks:
- some-network
- other-network

networks:
some-network:
other-network:

# pid

跟主机系统共享进程命名空间。打开该选项的容器之间,以及容器和宿主机系统之间可以通过进程 ID 来相互访问和操作。

# ports

暴露端口信息。

使用宿主端口:容器端口 (HOST:CONTAINER) 格式,或者仅仅指定容器的端口(宿主将会随机选择端口)都可以。

1
2
3
4
5
ports:
- "3000"
- "8000:8000"
- "49100:22"
- "127.0.0.1:8001:8001"

注意:当使用 HOST:CONTAINER 格式来映射端口时,如果你使用的容器端口小于 60 并且没放到引号里,可能会得到错误结果,因为 YAML 会自动解析 xx:yy 这种数字格式为 60 进制。为避免出现这种问题,建议数字串都采用引号包括起来的字符串格式。

# secrets

存储敏感数据,例如 mysql 服务密码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
version: "3.1"
services:

mysql:
image: mysql
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
secrets:
- db_root_password
- my_other_secret

secrets:
my_secret:
file: ./my_secret.txt
my_other_secret:
external: true

# security_opt

指定容器模板标签(label)机制的默认属性(用户、角色、类型、级别等)。例如配置标签的用户名和角色名。

1
2
3
security_opt:
- label:user:USER
- label:role:ROLE

# stop_signal

设置另一个信号来停止容器。在默认情况下使用的是 SIGTERM 停止容器。

# sysctls

配置容器内核参数。

1
2
3
4
5
6
7
sysctls:
net.core.somaxconn: 1024
net.ipv4.tcp_syncookies: 0

sysctls:
- net.core.somaxconn=1024
- net.ipv4.tcp_syncookies=0

# ulimits

指定容器的 ulimits 限制值。

例如,指定最大进程数为 65535,指定文件句柄数为 20000(软限制,应用可以随时修改,不能超过硬限制) 和 40000(系统硬限制,只能 root 用户提高)。

1
2
3
4
5
ulimits:
nproc: 65535
nofile:
soft: 20000
hard: 40000

# volumes

数据卷所挂载路径设置。可以设置为宿主机路径(HOST:CONTAINER)或者数据卷名称(VOLUME:CONTAINER),并且可以设置访问模式 (HOST:CONTAINER:ro)。

该指令中路径支持相对路径。

1
2
3
4
volumes:
- /var/lib/mysql
- cache/:/tmp/cache
- ~/configs:/etc/configs/:ro

如果路径为数据卷名称,必须在文件中配置数据卷。

1
2
3
4
5
6
7
8
9
10
version: "3"

services:
my_src:
image: mysql:8.0
volumes:
- mysql_data:/var/lib/mysql

volumes:
mysql_data:

# 其它指令

此外,还有包括 domainname, entrypoint, hostname, ipc, mac_address, privileged, read_only, shm_size, restart, stdin_open, tty, user, working_dir 等指令,基本跟 docker run 中对应参数的功能一致。

指定服务容器启动后执行的入口文件。

1
entrypoint: /code/entrypoint.sh

指定容器中运行应用的用户名。

指定容器中工作目录。

指定容器中搜索域名、主机名、mac 地址等。

1
2
3
domainname: your_website.com
hostname: test
mac_address: 08-00-27-00-0C-0A

允许容器中运行一些特权命令。

指定容器退出后的重启策略为始终重启。该命令对保持服务始终运行十分有效,在生产环境中推荐配置为 always 或者 unless-stopped

以只读模式挂载容器的 root 文件系统,意味着不能对容器内容进行修改。

打开标准输入,可以接受外部输入。

模拟一个伪终端。

# 读取变量

Compose 模板文件支持动态读取主机的系统环境变量和当前目录下的 .env 文件中的变量。

例如,下面的 Compose 文件将从运行它的环境中读取变量 ${MONGO_VERSION} 的值,并写入执行的指令中。

1
2
3
4
5
version: "3"
services:

db:
image: "mongo:${MONGO_VERSION}"

如果执行 MONGO_VERSION=3.2 docker compose up 则会启动一个 mongo:3.2 镜像的容器;如果执行 MONGO_VERSION=2.8 docker compose up 则会启动一个 mongo:2.8 镜像的容器。

若当前目录存在 .env 文件,执行 docker compose 命令时将从该文件中读取变量。

在当前目录新建 .env 文件并写入以下内容。

1
2
# 支持 # 号注释
MONGO_VERSION=3.6

执行 docker compose up 则会启动一个 mongo:3.6 镜像的容器。

# 参考资料

# 使用 Django

本小节内容适合 Python 开发人员阅读。

我们现在将使用 Docker Compose 配置并运行一个 Django/PostgreSQL 应用。

在一切工作开始前,需要先编辑好三个必要的文件。

第一步,因为应用将要运行在一个满足所有环境依赖的 Docker 容器里面,那么我们可以通过编辑 Dockerfile 文件来指定 Docker 容器要安装内容。内容如下:

1
2
3
4
5
6
7
FROM python:3
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
COPY requirements.txt /code/
RUN pip install -r requirements.txt
COPY . /code/

以上内容指定应用将使用安装了 Python 以及必要依赖包的镜像。更多关于如何编写 Dockerfile 文件的信息可以查看 Dockerfile 使用

第二步,在 requirements.txt 文件里面写明需要安装的具体依赖包名。

1
2
Django>=2.0,<3.0
psycopg2>=2.7,<3.0

第三步,docker-compose.yml 文件将把所有的东西关联起来。它描述了应用的构成(一个 web 服务和一个数据库)、使用的 Docker 镜像、镜像之间的连接、挂载到容器的卷,以及服务开放的端口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: "3"
services:

db:
image: postgres
environment:
POSTGRES_PASSWORD: 'postgres'

web:
build: .
command: python manage.py runserver 0.0.0.0:8000
volumes:
- .:/code
ports:
- "8000:8000"

查看 docker-compose.yml 章节 了解更多详细的工作机制。

现在我们就可以使用 docker compose run 命令启动一个 Django 应用了。

1
$ docker compose run web django-admin startproject django_example .

由于 web 服务所使用的镜像并不存在,所以 Compose 会首先使用 Dockerfile 为 web 服务构建一个镜像,接着使用这个镜像在容器里运行 django-admin startproject django_example 指令。

这将在当前目录生成一个 Django 应用。

1
2
$ ls
Dockerfile docker-compose.yml django_example manage.py requirements.txt

如果你的系统是 Linux,记得更改文件权限。

1
$ sudo chown -R $USER:$USER .

首先,我们要为应用设置好数据库的连接信息。用以下内容替换 django_example/settings.py 文件中 DATABASES = ... 定义的节点内容。

1
2
3
4
5
6
7
8
9
10
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'postgres',
'USER': 'postgres',
'HOST': 'db',
'PORT': 5432,
'PASSWORD': 'postgres',
}
}

这些信息是在 postgres (opens new window) 镜像固定设置好的。然后,运行 docker compose up

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ docker compose up

django_db_1 is up-to-date
Creating django_web_1 ...
Creating django_web_1 ... done
Attaching to django_db_1, django_web_1
db_1 | The files belonging to this database system will be owned by user "postgres".
db_1 | This user must also own the server process.
db_1 |
db_1 | The database cluster will be initialized with locale "en_US.utf8".
db_1 | The default database encoding has accordingly been set to "UTF8".
db_1 | The default text search configuration will be set to "english".

web_1 | Performing system checks...
web_1 |
web_1 | System check identified no issues (0 silenced).
web_1 |
web_1 | November 23, 2017 - 06:21:19
web_1 | Django version 1.11.7, using settings 'django_example.settings'
web_1 | Starting development server at http://0.0.0.0:8000/
web_1 | Quit the server with CONTROL-C.

这个 Django 应用已经开始在你的 Docker 守护进程里监听着 8000 端口了。打开 127.0.0.1:8000 即可看到 Django 欢迎页面。

你还可以在 Docker 上运行其它的管理命令,例如对于同步数据库结构这种事,在运行完 docker compose up 后,在另外一个终端进入文件夹运行以下命令即可:

1
$ docker compose run web python manage.py syncdb

# 使用 Rails

本小节内容适合 Ruby 开发人员阅读。

我们现在将使用 Compose 配置并运行一个 Rails/PostgreSQL 应用。

在一切工作开始前,需要先设置好三个必要的文件。

首先,因为应用将要运行在一个满足所有环境依赖的 Docker 容器里面,那么我们可以通过编辑 Dockerfile 文件来指定 Docker 容器要安装内容。内容如下:

1
2
3
4
5
6
7
FROM ruby
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev
RUN mkdir /myapp
WORKDIR /myapp
ADD Gemfile /myapp/Gemfile
RUN bundle install
ADD . /myapp

以上内容指定应用将使用安装了 Ruby、Bundler 以及其依赖件的镜像。更多关于如何编写 Dockerfile 文件的信息可以查看 Dockerfile 使用

下一步,我们需要一个引导加载 Rails 的文件 Gemfile 。 等一会儿它还会被 rails new 命令覆盖重写。

1
2
source 'https://rubygems.org'
gem 'rails', '4.0.2'

最后,docker-compose.yml 文件才是最神奇的地方。 docker-compose.yml 文件将把所有的东西关联起来。它描述了应用的构成(一个 web 服务和一个数据库)、每个镜像的来源(数据库运行在使用预定义的 PostgreSQL 镜像,web 应用侧将从本地目录创建)、镜像之间的连接,以及服务开放的端口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: "3"
services:

db:
image: postgres
ports:
- "5432"

web:
build: .
command: bundle exec rackup -p 3000
volumes:
- .:/myapp
ports:
- "3000:3000"

所有文件就绪后,我们就可以通过使用 docker compose run 命令生成应用的骨架了。

1
$ docker compose run web rails new . --force --database=postgresql --skip-bundle

Compose 会先使用 Dockerfile 为 web 服务创建一个镜像,接着使用这个镜像在容器里运行 rails new 和它之后的命令。一旦这个命令运行完后,应该就可以看一个崭新的应用已经生成了。

1
2
3
4
5
6
$ ls
Dockerfile app docker-compose.yml tmp
Gemfile bin lib vendor
Gemfile.lock condocker-compose log
README.rdoc condocker-compose.ru public
Rakefile db test

在新的 Gemfile 文件去掉加载 therubyracer 的行的注释,这样我们便可以使用 Javascript 运行环境:

1
gem 'therubyracer', platforms: :ruby

现在我们已经有一个新的 Gemfile 文件,需要再重新创建镜像。(这个会步骤会改变 Dockerfile 文件本身,所以需要重建一次)。

应用现在就可以启动了,但配置还未完成。Rails 默认读取的数据库目标是 localhost ,我们需要手动指定容器的 db 。同样的,还需要把用户名修改成和 postgres 镜像预定的一致。 打开最新生成的 database.yml 文件。用以下内容替换:

1
2
3
4
5
6
7
8
9
10
11
12
development: &default
adapter: postgresql
encoding: unicode
database: postgres
pool: 5
username: postgres
password:
host: db

test:
<<: *default
database: myapp_test

现在就可以启动应用了。

如果一切正常,你应该可以看到 PostgreSQL 的输出,几秒后可以看到这样的重复信息:

1
2
3
myapp_web_1 | [2014-01-17 17:16:29] INFO  WEBrick 1.3.1
myapp_web_1 | [2014-01-17 17:16:29] INFO ruby 2.0.0 (2013-11-22) [x86_64-linux-gnu]
myapp_web_1 | [2014-01-17 17:16:29] INFO WEBrick::HTTPServer#start: pid=1 port=3000

最后, 我们需要做的是创建数据库,打开另一个终端,运行:

1
$ docker compose run web rake db:create

这个 web 应用已经开始在你的 docker 守护进程里面监听着 3000 端口了。

留言與分享

Kubernetes

分類 devops, Docker, Kubernetes

# 项目简介

Kubernetes 是 Google 团队发起的开源项目,它的目标是管理跨多个主机的容器,提供基本的部署,维护以及应用伸缩,主要实现语言为 Go 语言。Kubernetes 是:

  • 易学:轻量级,简单,容易理解
  • 便携:支持公有云,私有云,混合云,以及多种云平台
  • 可拓展:模块化,可插拔,支持钩子,可任意组合
  • 自修复:自动重调度,自动重启,自动复制

Kubernetes 构建于 Google 数十年经验,一大半来源于 Google 生产环境规模的经验。结合了社区最佳的想法和实践。

在分布式系统中,部署,调度,伸缩一直是最为重要的也最为基础的功能。Kubernetes 就是希望解决这一序列问题的。

Kubernetes 目前在GitHub (opens new window)进行维护。

# Kubernetes 能够运行在任何地方!

虽然 Kubernetes 最初是为 GCE 定制的,但是在后续版本中陆续增加了其他云平台的支持,以及本地数据中心的支持。

# 基本概念

  • 节点(Node):一个节点是一个运行 Kubernetes 中的主机。
  • 容器组(Pod):一个 Pod 对应于由若干容器组成的一个容器组,同个组内的容器共享一个存储卷(volume)。
  • 容器组生命周期(pos-states):包含所有容器状态集合,包括容器组状态类型,容器组生命周期,事件,重启策略,以及 replication controllers。
  • Replication Controllers:主要负责指定数量的 pod 在同一时间一起运行。
  • 服务(services):一个 Kubernetes 服务是容器组逻辑的高级抽象,同时也对外提供访问容器组的策略。
  • 卷(volumes):一个卷就是一个目录,容器对其有访问权限。
  • 标签(labels):标签是用来连接一组对象的,比如容器组。标签可以被用来组织和选择子对象。
  • 接口权限(accessing_the_api):端口,IP 地址和代理的防火墙规则。
  • web 界面(ux):用户可以通过 web 界面操作 Kubernetes。
  • 命令行操作(cli):kubectl命令。

# 节点

Kubernetes 中,节点是实际工作的点,节点可以是虚拟机或者物理机器,依赖于一个集群环境。每个节点都有一些必要的服务以运行容器组,并且它们都可以通过主节点来管理。必要服务包括 Docker,kubelet 和代理服务。

# 容器状态

容器状态用来描述节点的当前状态。现在,其中包含三个信息:

# 主机IP

主机 IP 需要云平台来查询,Kubernetes 把它作为状态的一部分来保存。如果 Kubernetes 没有运行在云平台上,节点 ID 就是必需的。IP 地址可以变化,并且可以包含多种类型的 IP 地址,如公共 IP,私有 IP,动态 IP,ipv6 等等。

# 节点周期

通常来说节点有 PendingRunningTerminated 三个周期,如果 Kubernetes 发现了一个节点并且其可用,那么 Kubernetes 就把它标记为 Pending。然后在某个时刻,Kubernetes 将会标记其为 Running。节点的结束周期称为 Terminated。一个已经 Terminated 的节点不会接受和调度任何请求,并且已经在其上运行的容器组也会删除。

# 节点状态

节点的状态主要是用来描述处于 Running 的节点。当前可用的有 NodeReachableNodeReady。以后可能会增加其他状态。NodeReachable 表示集群可达。NodeReady 表示 kubelet 返回 Status Ok 并且 HTTP 状态检查健康。

# 节点管理

节点并非 Kubernetes 创建,而是由云平台创建,或者就是物理机器、虚拟机。在 Kubernetes 中,节点仅仅是一条记录,节点创建之后,Kubernetes 会检查其是否可用。在 Kubernetes 中,节点用如下结构保存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"id": "10.1.2.3",
"kind": "Minion",
"apiVersion": "v1beta1",
"resources": {
"capacity": {
"cpu": 1000,
"memory": 1073741824
},
},
"labels": {
"name": "my-first-k8s-node",
},
}

Kubernetes 校验节点可用依赖于 ID。在当前的版本中,有两个接口可以用来管理节点:节点控制和 Kube 管理。

# 节点控制

在 Kubernetes 主节点中,节点控制器是用来管理节点的组件。主要包含:

  • 集群范围内节点同步
  • 单节点生命周期管理

节点控制有一个同步轮寻,主要监听所有云平台的虚拟实例,会根据节点状态创建和删除。可以通过 --node_sync_period标志来控制该轮寻。如果一个实例已经创建,节点控制将会为其创建一个结构。同样的,如果一个节点被删除,节点控制也会删除该结构。在 Kubernetes 启动时可用通过 --machines标记来显示指定节点。同样可以使用 kubectl 来一条一条的添加节点,两者是相同的。通过设置 --sync_nodes=false标记来禁止集群之间的节点同步,你也可以使用 api/kubectl 命令行来增删节点。

# 容器组

在 Kubernetes 中,使用的最小单位是容器组,容器组是创建,调度,管理的最小单位。 一个容器组使用相同的 Docker 容器并共享卷(挂载点)。一个容器组是一个特定应用的打包集合,包含一个或多个容器。

和运行的容器类似,一个容器组被认为只有很短的运行周期。容器组被调度到一组节点运行,直到容器的生命周期结束或者其被删除。如果节点死掉,运行在其上的容器组将会被删除而不是重新调度。(也许在将来的版本中会添加容器组的移动)。

# 容器组设计的初衷

# 资源共享和通信

容器组主要是为了数据共享和它们之间的通信。

在一个容器组中,容器都使用相同的网络地址和端口,可以通过本地网络来相互通信。每个容器组都有独立的 IP,可用通过网络来和其他物理主机或者容器通信。

容器组有一组存储卷(挂载点),主要是为了让容器在重启之后可以不丢失数据。

# 容器组管理

容器组是一个应用管理和部署的高层次抽象,同时也是一组容器的接口。容器组是部署、水平放缩的最小单位。

# 容器组的使用

容器组可以通过组合来构建复杂的应用,其本来的意义包含:

  • 内容管理,文件和数据加载以及本地缓存管理等。
  • 日志和检查点备份,压缩,快照等。
  • 监听数据变化,跟踪日志,日志和监控代理,消息发布等。
  • 代理,网桥
  • 控制器,管理,配置以及更新

# 替代方案

为什么不在一个单一的容器里运行多个程序?

  • 1.透明化。为了使容器组中的容器保持一致的基础设施和服务,比如进程管理和资源监控。这样设计是为了用户的便利性。
  • 2.解偶软件之间的依赖。每个容器都可能重新构建和发布,Kubernetes 必须支持热发布和热更新(将来)。
  • 3.方便使用。用户不必运行独立的程序管理,也不用担心每个应用程序的退出状态。
  • 4.高效。考虑到基础设施有更多的职责,容器必须要轻量化。

# 容器组的生命状态

包括若干状态值:pendingrunningsucceededfailed

# pending

容器组已经被节点接受,但有一个或多个容器还没有运行起来。这将包含某些节点正在下载镜像的时间,这种情形会依赖于网络情况。

# running

容器组已经被调度到节点,并且所有的容器都已经启动。至少有一个容器处于运行状态(或者处于重启状态)。

# succeeded

所有的容器都正常退出。

# failed

容器组中所有容器都意外中断了。

# 容器组生命周期

通常来说,如果容器组被创建了就不会自动销毁,除非被某种行为触发,而触发此种情况可能是人为,或者复制控制器所为。唯一例外的是容器组由 succeeded 状态成功退出,或者在一定时间内重试多次依然失败。

如果某个节点死掉或者不能连接,那么节点控制器将会标记其上的容器组的状态为 failed

举例如下。

  • 容器组状态 running,有 1 容器,容器正常退出
    • 记录完成事件
    • 如果重启策略为:
      • 始终:重启容器,容器组保持 running
      • 失败时:容器组变为 succeeded
      • 从不:容器组变为 succeeded
  • 容器组状态 running,有1容器,容器异常退出
    • 记录失败事件
    • 如果重启策略为:
      • 始终:重启容器,容器组保持 running
      • 失败时:重启容器,容器组保持 running
      • 从不:容器组变为 failed
  • 容器组状态 running,有2容器,有1容器异常退出
    • 记录失败事件
    • 如果重启策略为:
      • 始终:重启容器,容器组保持 running
      • 失败时:重启容器,容器组保持 running
      • 从不:容器组保持 running
    • 当有2容器退出
      • 记录失败事件
      • 如果重启策略为:
        • 始终:重启容器,容器组保持 running
        • 失败时:重启容器,容器组保持 running
        • 从不:容器组变为 failed
  • 容器组状态 running,容器内存不足
    • 标记容器错误中断
    • 记录内存不足事件
    • 如果重启策略为:
      • 始终:重启容器,容器组保持 running
      • 失败时:重启容器,容器组保持 running
      • 从不:记录错误事件,容器组变为 failed
  • 容器组状态 running,一块磁盘死掉
    • 杀死所有容器
    • 记录事件
    • 容器组变为 failed
    • 如果容器组运行在一个控制器下,容器组将会在其他地方重新创建
  • 容器组状态 running,对应的节点段溢出
    • 节点控制器等到超时
    • 节点控制器标记容器组 failed
    • 如果容器组运行在一个控制器下,容器组将会在其他地方重新创建

# Replication Controllers

# 服务

#

# 标签

# 接口权限

# web界面

# 命令行操作

# 基本架构

任何优秀的项目都离不开优秀的架构设计。本小节将介绍 Kubernetes 在架构方面的设计考虑。

# 基本考虑

如果让我们自己从头设计一套容器管理平台,有如下几个方面是很容易想到的:

  • 分布式架构,保证扩展性;
  • 逻辑集中式的控制平面 + 物理分布式的运行平面;
  • 一套资源调度系统,管理哪个容器该分配到哪个节点上;
  • 一套对容器内服务进行抽象和 HA 的系统。

# 运行原理

下面这张图完整展示了 Kubernetes 的运行原理。

Kubernetes 架构

可见,Kubernetes 首先是一套分布式系统,由多个节点组成,节点分为两类:一类是属于管理平面的主节点/控制节点(Master Node);一类是属于运行平面的工作节点(Worker Node)。

显然,复杂的工作肯定都交给控制节点去做了,工作节点负责提供稳定的操作接口和能力抽象即可。

从这张图上,我们没有能发现 Kubernetes 中对于控制平面的分布式实现,但是由于数据后端自身就是一套分布式的数据库 Etcd,因此可以很容易扩展到分布式实现。

# 控制平面

# 主节点服务

主节点上需要提供如下的管理服务:

  • apiserver 是整个系统的对外接口,提供一套 RESTful 的 Kubernetes API (opens new window),供客户端和其它组件调用;
  • scheduler 负责对资源进行调度,分配某个 pod 到某个节点上。是 pluggable 的,意味着很容易选择其它实现方式;
  • controller-manager 负责管理控制器,包括 endpoint-controller(刷新服务和 pod 的关联信息)和 replication-controller(维护某个 pod 的复制为配置的数值)。

# Etcd

这里 Etcd 即作为数据后端,又作为消息中间件。

通过 Etcd 来存储所有的主节点上的状态信息,很容易实现主节点的分布式扩展。

组件可以自动的去侦测 Etcd 中的数值变化来获得通知,并且获得更新后的数据来执行相应的操作。

# 工作节点

  • kubelet 是工作节点执行操作的 agent,负责具体的容器生命周期管理,根据从数据库中获取的信息来管理容器,并上报 pod 运行状态等;
  • kube-proxy 是一个简单的网络访问代理,同时也是一个 Load Balancer。它负责将访问到某个服务的请求具体分配给工作节点上的 Pod(同一类标签)。

Proxy 代理对服务的请求

Docker 从入门到实践

微信交流群 (opens new window)

小程序 (opens new window)

安装 Docker

Docker 入门

Docker 实战

CI/CD

Compose

Kubernetes

云计算

云服务器99/元首年特惠 (opens new window)

GitHub (opens new window)

  1. Kubernetes
  2. 部署 Kubernetes

部署 Kubernetes

小于 1 分钟 约 102 字


# 部署 Kubernetes

目前,Kubernetes 支持在多种环境下使用,包括本地主机(Ubuntu、Debian、CentOS、Fedora 等)、云服务(腾讯云 (opens new window)阿里云 (opens new window)百度云 (opens new window) 等)。

你可以使用以下几种方式部署 Kubernetes:

  • kubeadm
  • docker-desktop
  • k3s

接下来的小节会对以上几种方式进行详细介绍。

基本架构 使用 kubeadm 部署 kubernetes(CRI 使用 containerd)

# 使用 kubeadm 部署 kubernetes(CRI 使用 containerd)

kubeadm 提供了 kubeadm init 以及 kubeadm join 这两个命令作为快速创建 kubernetes 集群的最佳实践。

# 安装 containerd

参考 安装 Docker 一节添加 apt/yum 源,之后执行如下命令。

1
2
3
4
5
# debian 系
$ sudo apt install containerd.io

# rhel 系
$ sudo yum install containerd.io

# 配置 containerd

新建 /etc/systemd/system/cri-containerd.service 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[Unit]
Description=containerd container runtime for kubernetes
Documentation=https://containerd.io
After=network.target local-fs.target

[Service]
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/bin/containerd --config //etc/cri-containerd/config.toml

Type=notify
Delegate=yes
KillMode=process
Restart=always
RestartSec=5
# Having non-zero Limit*s causes performance problems due to accounting overhead
# in the kernel. We recommend using cgroups to do container-local accounting.
LimitNPROC=infinity
LimitCORE=infinity
LimitNOFILE=infinity
# Comment TasksMax if your systemd version does not supports it.
# Only systemd 226 and above support this version.
TasksMax=infinity
OOMScoreAdjust=-999

[Install]
WantedBy=multi-user.target

新建 /etc/cri-containerd/config.toml containerd 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
version = 2
# persistent data location
root = "/var/lib/cri-containerd"
# runtime state information
state = "/run/cri-containerd"
plugin_dir = ""
disabled_plugins = []
required_plugins = []
# set containerd's OOM score
oom_score = 0

[grpc]
address = "/run/cri-containerd/cri-containerd.sock"
tcp_address = ""
tcp_tls_cert = ""
tcp_tls_key = ""
# socket uid
uid = 0
# socket gid
gid = 0
max_recv_message_size = 16777216
max_send_message_size = 16777216

[debug]
address = ""
format = "json"
uid = 0
gid = 0
level = ""

[metrics]
address = "127.0.0.1:1338"
grpc_histogram = false

[cgroup]
path = ""

[timeouts]
"io.containerd.timeout.shim.cleanup" = "5s"
"io.containerd.timeout.shim.load" = "5s"
"io.containerd.timeout.shim.shutdown" = "3s"
"io.containerd.timeout.task.state" = "2s"

[plugins]
[plugins."io.containerd.gc.v1.scheduler"]
pause_threshold = 0.02
deletion_threshold = 0
mutation_threshold = 100
schedule_delay = "0s"
startup_delay = "100ms"
[plugins."io.containerd.grpc.v1.cri"]
disable_tcp_service = true
stream_server_address = "127.0.0.1"
stream_server_port = "0"
stream_idle_timeout = "4h0m0s"
enable_selinux = false
selinux_category_range = 1024
sandbox_image = "registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.5"
stats_collect_period = 10
# systemd_cgroup = false
enable_tls_streaming = false
max_container_log_line_size = 16384
disable_cgroup = false
disable_apparmor = false
restrict_oom_score_adj = false
max_concurrent_downloads = 3
disable_proc_mount = false
unset_seccomp_profile = ""
tolerate_missing_hugetlb_controller = true
disable_hugetlb_controller = true
ignore_image_defined_volumes = false
[plugins."io.containerd.grpc.v1.cri".containerd]
snapshotter = "overlayfs"
default_runtime_name = "runc"
no_pivot = false
disable_snapshot_annotations = false
discard_unpacked_layers = false
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v2"
pod_annotations = []
container_annotations = []
privileged_without_host_devices = false
base_runtime_spec = ""
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
# SystemdCgroup enables systemd cgroups.
SystemdCgroup = true
# BinaryName is the binary name of the runc binary.
# BinaryName = "runc"
# BinaryName = "crun"
# NoPivotRoot disables pivot root when creating a container.
# NoPivotRoot = false

# NoNewKeyring disables new keyring for the container.
# NoNewKeyring = false

# ShimCgroup places the shim in a cgroup.
# ShimCgroup = ""

# IoUid sets the I/O's pipes uid.
# IoUid = 0

# IoGid sets the I/O's pipes gid.
# IoGid = 0

# Root is the runc root directory.
Root = ""

# CriuPath is the criu binary path.
# CriuPath = ""

# CriuImagePath is the criu image path
# CriuImagePath = ""

# CriuWorkPath is the criu work path.
# CriuWorkPath = ""
[plugins."io.containerd.grpc.v1.cri".cni]
bin_dir = "/opt/cni/bin"
conf_dir = "/etc/cni/net.d"
max_conf_num = 1
conf_template = ""
[plugins."io.containerd.grpc.v1.cri".registry]
config_path = "/etc/cri-containerd/certs.d"
[plugins."io.containerd.grpc.v1.cri".registry.headers]
# Foo = ["bar"]
[plugins."io.containerd.grpc.v1.cri".image_decryption]
key_model = ""
[plugins."io.containerd.grpc.v1.cri".x509_key_pair_streaming]
tls_cert_file = ""
tls_key_file = ""
[plugins."io.containerd.internal.v1.opt"]
path = "/opt/cri-containerd"
[plugins."io.containerd.internal.v1.restart"]
interval = "10s"
[plugins."io.containerd.metadata.v1.bolt"]
content_sharing_policy = "shared"
[plugins."io.containerd.monitor.v1.cgroups"]
no_prometheus = false
[plugins."io.containerd.runtime.v2.task"]
platforms = ["linux/amd64"]
[plugins."io.containerd.service.v1.diff-service"]
default = ["walking"]
[plugins."io.containerd.snapshotter.v1.devmapper"]
root_path = ""
pool_name = ""
base_image_size = ""
async_remove = false

# 安装 kubelet kubeadm kubectl cri-tools kubernetes-cni

# Ubuntu/Debian

1
2
3
4
5
6
7
8
9
$ apt-get update && apt-get install -y apt-transport-https
$ curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add -

$ cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOF

$ apt-get update
$ apt-get install -y kubelet kubeadm kubectl

# CentOS/Fedora

1
2
3
4
5
6
7
8
9
10
11
$ cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF

$ sudo yum install -y kubelet kubeadm kubectl

# 修改内核的运行参数

1
2
3
4
5
6
7
8
$ cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF

# 应用配置
$ sysctl --system

# 配置 kubelet

# 修改 kubelet.service

/etc/systemd/system/kubelet.service.d/10-proxy-ipvs.conf 写入以下内容

1
2
3
4
5
6
# 启用 ipvs 相关内核模块
[Service]
ExecStartPre=-/sbin/modprobe ip_vs
ExecStartPre=-/sbin/modprobe ip_vs_rr
ExecStartPre=-/sbin/modprobe ip_vs_wrr
ExecStartPre=-/sbin/modprobe ip_vs_sh

执行以下命令应用配置。

1
$ sudo systemctl daemon-reload

# 部署

# master

1
2
3
4
5
6
7
8
9
10
$ systemctl enable cri-containerd

$ systemctl start cri-containerd

$ sudo kubeadm init \
--image-repository registry.cn-hangzhou.aliyuncs.com/google_containers \
--pod-network-cidr 10.244.0.0/16 \
--cri-socket /run/cri-containerd/cri-containerd.sock \
--v 5 \
--ignore-preflight-errors=all
  • --pod-network-cidr 10.244.0.0/16 参数与后续 CNI 插件有关,这里以 flannel 为例,若后续部署其他类型的网络插件请更改此参数。

执行可能出现错误,例如缺少依赖包,根据提示安装即可。

执行成功会输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
...
[addons] Applied essential addon: CoreDNS
I1116 12:35:13.270407 86677 request.go:538] Throttling request took 181.409184ms, request: POST:https://192.168.199.100:6443/api/v1/namespaces/kube-system/serviceaccounts
I1116 12:35:13.470292 86677 request.go:538] Throttling request took 186.088112ms, request: POST:https://192.168.199.100:6443/api/v1/namespaces/kube-system/configmaps
[addons] Applied essential addon: kube-proxy

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.199.100:6443 --token cz81zt.orsy9gm9v649e5lf \
--discovery-token-ca-cert-hash sha256:5edb316fd0d8ea2792cba15cdf1c899a366f147aa03cba52d4e5c5884ad836fe

# node 工作节点

另一主机 重复 部署 小节以前的步骤,安装配置好 kubelet。根据提示,加入到集群。

1
2
3
4
5
6
7
8
$ systemctl enable cri-containerd

$ systemctl start cri-containerd

$ kubeadm join 192.168.199.100:6443 \
--token cz81zt.orsy9gm9v649e5lf \
--discovery-token-ca-cert-hash sha256:5edb316fd0d8ea2792cba15cdf1c899a366f147aa03cba52d4e5c5884ad836fe \
--cri-socket /run/cri-containerd/cri-containerd.sock

# 查看服务

所有服务启动后,通过 crictl 查看本地实际运行的容器。这些服务大概分为三类:主节点服务、工作节点服务和其它服务。

1
CONTAINER_RUNTIME_ENDPOINT=/run/cri-containerd/cri-containerd.sock crictl ps -a

# 主节点服务

  • apiserver 是整个系统的对外接口,提供 RESTful 方式供客户端和其它组件调用;

  • scheduler 负责对资源进行调度,分配某个 pod 到某个节点上;

  • controller-manager 负责管理控制器,包括 endpoint-controller(刷新服务和 pod 的关联信息)和 replication-controller(维护某个 pod 的复制为配置的数值)。

# 工作节点服务

  • proxy 为 pod 上的服务提供访问的代理。

# 其它服务

  • Etcd 是所有状态的存储数据库;

# 使用

/etc/kubernetes/admin.conf 复制到 ~/.kube/config

执行 $ kubectl get all -A 查看启动的服务。

由于未部署 CNI 插件,CoreDNS 未正常启动。如何使用 Kubernetes,请参考后续章节。

# 部署 CNI

这里以 flannel 为例进行介绍。

# flannel

检查 podCIDR 设置

1
2
3
4
5
$ kubectl get node -o yaml | grep CIDR

# 输出
podCIDR: 10.244.0.0/16
podCIDRs:
1
$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/v0.11.0/Documentation/kube-flannel.yml

# master 节点默认不能运行 pod

如果用 kubeadm 部署一个单节点集群,默认情况下无法使用,请执行以下命令解除限制

1
2
3
4
$ kubectl taint nodes --all node-role.kubernetes.io/master-

# 恢复默认值
# $ kubectl taint nodes NODE_NAME node-role.kubernetes.io/master=true:NoSchedule

# 参考文档

# Docker Desktop 启用 Kubernetes

使用 Docker Desktop 可以很方便的启用 Kubernetes,由于国内获取不到 k8s.gcr.io 镜像,我们必须首先解决这一问题。

# 获取 k8s.gcr.io 镜像

由于国内拉取不到 k8s.gcr.io 镜像,我们可以使用开源项目 AliyunContainerService/k8s-for-docker-desktop (opens new window) 来获取所需的镜像。

# 启用 Kubernetes

在 Docker Desktop 设置页面,点击 Kubernetes,选择 Enable Kubernetes,稍等片刻,看到左下方 Kubernetes 变为 running,Kubernetes 启动成功。

# 测试

1
$ kubectl version

如果正常输出信息,则证明 Kubernetes 成功启动。

# Kubernetes Dashboard

Kubernetes Dashboard (opens new window) 是基于网页的 Kubernetes 用户界面。

# 部署

执行以下命令即可部署 Dashboard:

1
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0/aio/deploy/recommended.yaml

# 访问

通过命令行代理访问,执行以下命令:

http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/ 即可访问。

# 登录

目前,Dashboard 仅支持使用 Bearer 令牌登录。下面教大家如何创建该令牌:

1
2
3
4
5
6
7
8
9
$ kubectl create sa dashboard-admin -n kube-system

$ kubectl create clusterrolebinding dashboard-admin --clusterrole=cluster-admin --serviceaccount=kube-system:dashboard-admin

$ ADMIN_SECRET=$(kubectl get secrets -n kube-system | grep dashboard-admin | awk '{print $1}')

$ DASHBOARD_LOGIN_TOKEN=$(kubectl describe secret -n kube-system ${ADMIN_SECRET} | grep -E '^token' | awk '{print $2}')

echo ${DASHBOARD_LOGIN_TOKEN}

将结果粘贴到登录页面,即可登录。

# 参考文档

留言與分享

# 如何调试 Docker

# 开启 Debug 模式

在 dockerd 配置文件 daemon.json(默认位于 /etc/docker/)中添加

1
2
3
{
"debug": true
}

重启守护进程。

1
$ sudo kill -SIGHUP $(pidof dockerd)

此时 dockerd 会在日志中输入更多信息供分析。

# 检查内核日志

1
2
$ sudo dmesg |grep dockerd
$ sudo dmesg |grep runc

# Docker 不响应时处理

可以杀死 dockerd 进程查看其堆栈调用情况。

1
$ sudo kill -SIGUSR1 $(pidof dockerd)

# 重置 Docker 本地数据

注意,本操作会移除所有的 Docker 本地数据,包括镜像和容器等。

1
$ sudo rm -rf /var/lib/docker

留言與分享

# Dockerfile 最佳实践

本附录是笔者对 Docker 官方文档中 Best practices for writing Dockerfiles (opens new window) 的理解与翻译。

# 一般性的指南和建议

# 容器应该是短暂的

通过 Dockerfile 构建的镜像所启动的容器应该尽可能短暂(生命周期短)。「短暂」意味着可以停止和销毁容器,并且创建一个新容器并部署好所需的设置和配置工作量应该是极小的。

# 使用 .dockerignore 文件

使用 Dockerfile 构建镜像时最好是将 Dockerfile 放置在一个新建的空目录下。然后将构建镜像所需要的文件添加到该目录中。为了提高构建镜像的效率,你可以在目录下新建一个 .dockerignore 文件来指定要忽略的文件和目录。.dockerignore 文件的排除模式语法和 Git 的 .gitignore 文件相似。

# 使用多阶段构建

Docker 17.05 以上版本中,你可以使用 多阶段构建 来减少所构建镜像的大小。

# 避免安装不必要的包

为了降低复杂性、减少依赖、减小文件大小、节约构建时间,你应该避免安装任何不必要的包。例如,不要在数据库镜像中包含一个文本编辑器。

# 一个容器只运行一个进程

应该保证在一个容器中只运行一个进程。将多个应用解耦到不同容器中,保证了容器的横向扩展和复用。例如 web 应用应该包含三个容器:web应用、数据库、缓存。

如果容器互相依赖,你可以使用 Docker 自定义网络 来把这些容器连接起来。

# 镜像层数尽可能少

你需要在 Dockerfile 可读性(也包括长期的可维护性)和减少层数之间做一个平衡。

# 将多行参数排序

将多行参数按字母顺序排序(比如要安装多个包时)。这可以帮助你避免重复包含同一个包,更新包列表时也更容易。也便于 PRs 阅读和审查。建议在反斜杠符号 \ 之前添加一个空格,以增加可读性。

下面是来自 buildpack-deps 镜像的例子:

1
2
3
4
5
6
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion

# 构建缓存

在镜像的构建过程中,Docker 会遍历 Dockerfile 文件中的指令,然后按顺序执行。在执行每条指令之前,Docker 都会在缓存中查找是否已经存在可重用的镜像,如果有就使用现存的镜像,不再重复创建。如果你不想在构建过程中使用缓存,你可以在 docker build 命令中使用 --no-cache=true 选项。

但是,如果你想在构建的过程中使用缓存,你得明白什么时候会,什么时候不会找到匹配的镜像,遵循的基本规则如下:

  • 从一个基础镜像开始(FROM 指令指定),下一条指令将和该基础镜像的所有子镜像进行匹配,检查这些子镜像被创建时使用的指令是否和被检查的指令完全一样。如果不是,则缓存失效。
  • 在大多数情况下,只需要简单地对比 Dockerfile 中的指令和子镜像。然而,有些指令需要更多的检查和解释。
  • 对于 ADDCOPY 指令,镜像中对应文件的内容也会被检查,每个文件都会计算出一个校验和。文件的最后修改时间和最后访问时间不会纳入校验。在缓存的查找过程中,会将这些校验和和已存在镜像中的文件校验和进行对比。如果文件有任何改变,比如内容和元数据,则缓存失效。
  • 除了 ADDCOPY 指令,缓存匹配过程不会查看临时容器中的文件来决定缓存是否匹配。例如,当执行完 RUN apt-get -y update 指令后,容器中一些文件被更新,但 Docker 不会检查这些文件。这种情况下,只有指令字符串本身被用来匹配缓存。

一旦缓存失效,所有后续的 Dockerfile 指令都将产生新的镜像,缓存不会被使用。

# Dockerfile 指令

下面针对 Dockerfile 中各种指令的最佳编写方式给出建议。

# FROM

尽可能使用当前官方仓库作为你构建镜像的基础。推荐使用 Alpine (opens new window) 镜像,因为它被严格控制并保持最小尺寸(目前小于 5 MB),但它仍然是一个完整的发行版。

# LABEL

你可以给镜像添加标签来帮助组织镜像、记录许可信息、辅助自动化构建等。每个标签一行,由 LABEL 开头加上一个或多个标签对。下面的示例展示了各种不同的可能格式。# 开头的行是注释内容。

注意:如果你的字符串中包含空格,必须将字符串放入引号中或者对空格使用转义。如果字符串内容本身就包含引号,必须对引号使用转义。

1
2
3
4
5
6
7
8
# Set one or more individual labels
LABEL com.example.version="0.0.1-beta"

LABEL vendor="ACME Incorporated"

LABEL com.example.release-date="2015-02-12"

LABEL com.example.version.is-production=""

一个镜像可以包含多个标签,但建议将多个标签放入到一个 LABEL 指令中。

1
2
3
4
5
6
# Set multiple labels at once, using line-continuation characters to break long lines
LABEL vendor=ACME\ Incorporated \
com.example.is-beta= \
com.example.is-production="" \
com.example.version="0.0.1-beta" \
com.example.release-date="2015-02-12"

关于标签可以接受的键值对,参考 Understanding object labels (opens new window)。关于查询标签信息,参考 Managing labels on objects (opens new window)

# RUN

为了保持 Dockerfile 文件的可读性,可理解性,以及可维护性,建议将长的或复杂的 RUN 指令用反斜杠 \ 分割成多行。

# apt-get

RUN 指令最常见的用法是安装包用的 apt-get。因为 RUN apt-get 指令会安装包,所以有几个问题需要注意。

不要使用 RUN apt-get upgradedist-upgrade,因为许多基础镜像中的「必须」包不会在一个非特权容器中升级。如果基础镜像中的某个包过时了,你应该联系它的维护者。如果你确定某个特定的包,比如 foo,需要升级,使用 apt-get install -y foo 就行,该指令会自动升级 foo 包。

永远将 RUN apt-get updateapt-get install 组合成一条 RUN 声明,例如:

1
2
3
4
RUN apt-get update && apt-get install -y \
package-bar \
package-baz \
package-foo

apt-get update 放在一条单独的 RUN 声明中会导致缓存问题以及后续的 apt-get install 失败。比如,假设你有一个 Dockerfile 文件:

1
2
3
4
5
FROM ubuntu:18.04

RUN apt-get update

RUN apt-get install -y curl

构建镜像后,所有的层都在 Docker 的缓存中。假设你后来又修改了其中的 apt-get install 添加了一个包:

1
2
3
4
5
FROM ubuntu:18.04

RUN apt-get update

RUN apt-get install -y curl nginx

Docker 发现修改后的 RUN apt-get update 指令和之前的完全一样。所以,apt-get update 不会执行,而是使用之前的缓存镜像。因为 apt-get update 没有运行,后面的 apt-get install 可能安装的是过时的 curlnginx 版本。

使用 RUN apt-get update && apt-get install -y 可以确保你的 Dockerfiles 每次安装的都是包的最新的版本,而且这个过程不需要进一步的编码或额外干预。这项技术叫作 cache busting。你也可以显示指定一个包的版本号来达到 cache-busting,这就是所谓的固定版本,例如:

1
2
3
4
RUN apt-get update && apt-get install -y \
package-bar \
package-baz \
package-foo=1.3.*

固定版本会迫使构建过程检索特定的版本,而不管缓存中有什么。这项技术也可以减少因所需包中未预料到的变化而导致的失败。

下面是一个 RUN 指令的示例模板,展示了所有关于 apt-get 的建议。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
RUN apt-get update && apt-get install -y \
aufs-tools \
automake \
build-essential \
curl \
dpkg-sig \
libcap-dev \
libsqlite3-dev \
mercurial \
reprepro \
ruby1.9.1 \
ruby1.9.1-dev \
s3cmd=1.1.* \
&& rm -rf /var/lib/apt/lists/*

其中 s3cmd 指令指定了一个版本号 1.1.*。如果之前的镜像使用的是更旧的版本,指定新的版本会导致 apt-get udpate 缓存失效并确保安装的是新版本。

另外,清理掉 apt 缓存 var/lib/apt/lists 可以减小镜像大小。因为 RUN 指令的开头为 apt-get udpate,包缓存总是会在 apt-get install 之前刷新。

注意:官方的 Debian 和 Ubuntu 镜像会自动运行 apt-get clean,所以不需要显式的调用 apt-get clean。

# CMD

CMD 指令用于执行目标镜像中包含的软件,可以包含参数。CMD 大多数情况下都应该以 CMD ["executable", "param1", "param2"...] 的形式使用。因此,如果创建镜像的目的是为了部署某个服务(比如 Apache),你可能会执行类似于 CMD ["apache2", "-DFOREGROUND"] 形式的命令。我们建议任何服务镜像都使用这种形式的命令。

多数情况下,CMD 都需要一个交互式的 shell (bash, Python, perl 等),例如 CMD ["perl", "-de0"],或者 CMD ["PHP", "-a"]。使用这种形式意味着,当你执行类似 docker run -it python 时,你会进入一个准备好的 shell 中。CMD 应该在极少的情况下才能以 CMD ["param", "param"] 的形式与 ENTRYPOINT 协同使用,除非你和你的镜像使用者都对 ENTRYPOINT 的工作方式十分熟悉。

# EXPOSE

EXPOSE 指令用于指定容器将要监听的端口。因此,你应该为你的应用程序使用常见的端口。例如,提供 Apache web 服务的镜像应该使用 EXPOSE 80,而提供 MongoDB 服务的镜像使用 EXPOSE 27017

对于外部访问,用户可以在执行 docker run 时使用一个标志来指示如何将指定的端口映射到所选择的端口。

# ENV

为了方便新程序运行,你可以使用 ENV 来为容器中安装的程序更新 PATH 环境变量。例如使用 ENV PATH /usr/local/nginx/bin:$PATH 来确保 CMD ["nginx"] 能正确运行。

ENV 指令也可用于为你想要容器化的服务提供必要的环境变量,比如 Postgres 需要的 PGDATA

最后,ENV 也能用于设置常见的版本号,比如下面的示例:

1
2
3
4
5
6
7
ENV PG_MAJOR 9.3

ENV PG_VERSION 9.3.4

RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …

ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH

类似于程序中的常量,这种方法可以让你只需改变 ENV 指令来自动的改变容器中的软件版本。

# ADD 和 COPY

虽然 ADDCOPY 功能类似,但一般优先使用 COPY。因为它比 ADD 更透明。COPY 只支持简单将本地文件拷贝到容器中,而 ADD 有一些并不明显的功能(比如本地 tar 提取和远程 URL 支持)。因此,ADD 的最佳用例是将本地 tar 文件自动提取到镜像中,例如 ADD rootfs.tar.xz

如果你的 Dockerfile 有多个步骤需要使用上下文中不同的文件。单独 COPY 每个文件,而不是一次性的 COPY 所有文件,这将保证每个步骤的构建缓存只在特定的文件变化时失效。例如:

1
2
3
4
5
COPY requirements.txt /tmp/

RUN pip install --requirement /tmp/requirements.txt

COPY . /tmp/

如果将 COPY . /tmp/ 放置在 RUN 指令之前,只要 . 目录中任何一个文件变化,都会导致后续指令的缓存失效。

为了让镜像尽量小,最好不要使用 ADD 指令从远程 URL 获取包,而是使用 curlwget。这样你可以在文件提取完之后删掉不再需要的文件来避免在镜像中额外添加一层。比如尽量避免下面的用法:

1
2
3
4
5
ADD http://example.com/big.tar.xz /usr/src/things/

RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things

RUN make -C /usr/src/things all

而是应该使用下面这种方法:

1
2
3
4
RUN mkdir -p /usr/src/things \
&& curl -SL http://example.com/big.tar.xz \
| tar -xJC /usr/src/things \
&& make -C /usr/src/things all

上面使用的管道操作,所以没有中间文件需要删除。

对于其他不需要 ADD 的自动提取功能的文件或目录,你应该使用 COPY

# ENTRYPOINT

ENTRYPOINT 的最佳用处是设置镜像的主命令,允许将镜像当成命令本身来运行(用 CMD 提供默认选项)。

例如,下面的示例镜像提供了命令行工具 s3cmd:

1
2
3
ENTRYPOINT ["s3cmd"]

CMD ["--help"]

现在直接运行该镜像创建的容器会显示命令帮助:

或者提供正确的参数来执行某个命令:

1
$ docker run s3cmd ls s3://mybucket

这样镜像名可以当成命令行的参考。

ENTRYPOINT 指令也可以结合一个辅助脚本使用,和前面命令行风格类似,即使启动工具需要不止一个步骤。

例如,Postgres 官方镜像使用下面的脚本作为 ENTRYPOINT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
set -e

if [ "$1" = 'postgres' ]; then
chown -R postgres "$PGDATA"

if [ -z "$(ls -A "$PGDATA")" ]; then
gosu postgres initdb
fi

exec gosu postgres "$@"
fi

exec "$@"

注意:该脚本使用了 Bash 的内置命令 exec,所以最后运行的进程就是容器的 PID 为 1 的进程。这样,进程就可以接收到任何发送给容器的 Unix 信号了。

该辅助脚本被拷贝到容器,并在容器启动时通过 ENTRYPOINT 执行:

1
2
3
COPY ./docker-entrypoint.sh /

ENTRYPOINT ["/docker-entrypoint.sh"]

该脚本可以让用户用几种不同的方式和 Postgres 交互。

你可以很简单地启动 Postgres

也可以执行 Postgres 并传递参数:

1
$ docker run postgres postgres --help

最后,你还可以启动另外一个完全不同的工具,比如 Bash

1
$ docker run --rm -it postgres bash

# VOLUME

VOLUME 指令用于暴露任何数据库存储文件,配置文件,或容器创建的文件和目录。强烈建议使用 VOLUME 来管理镜像中的可变部分和用户可以改变的部分。

# USER

如果某个服务不需要特权执行,建议使用 USER 指令切换到非 root 用户。先在 Dockerfile 中使用类似 RUN groupadd -r postgres && useradd -r -g postgres postgres 的指令创建用户和用户组。

注意:在镜像中,用户和用户组每次被分配的 UID/GID 都是不确定的,下次重新构建镜像时被分配到的 UID/GID 可能会不一样。如果要依赖确定的 UID/GID,你应该显式的指定一个 UID/GID。

你应该避免使用 sudo,因为它不可预期的 TTY 和信号转发行为可能造成的问题比它能解决的问题还多。如果你真的需要和 sudo 类似的功能(例如,以 root 权限初始化某个守护进程,以非 root 权限执行它),你可以使用 gosu (opens new window)

最后,为了减少层数和复杂度,避免频繁地使用 USER 来回切换用户。

# WORKDIR

为了清晰性和可靠性,你应该总是在 WORKDIR 中使用绝对路径。另外,你应该使用 WORKDIR 来替代类似于 RUN cd ... && do-something 的指令,后者难以阅读、排错和维护。

# 官方镜像示例

这些官方镜像的 Dockerfile 都是参考典范:https://github.com/docker-library/docs

留言與分享

# Docker 命令查询

# 基本语法

Docker 命令有两大类,客户端命令和服务端命令。前者是主要的操作接口,后者用来启动 Docker Daemon。

  • 客户端命令:基本命令格式为 docker [OPTIONS] COMMAND [arg...]

  • 服务端命令:基本命令格式为 dockerd [OPTIONS]

可以通过 man dockerdocker help 来查看这些命令。

接下来的小节对这两个命令进行介绍。

# 客户端命令(docker)

# 客户端命令选项

  • --config="":指定客户端配置文件,默认为 ~/.docker
  • -D=true|false:是否使用 debug 模式。默认不开启;
  • -H, --host=[]:指定命令对应 Docker 守护进程的监听接口,可以为 unix 套接字 unix:///path/to/socket,文件句柄 fd://socketfd 或 tcp 套接字 tcp://[host[:port]],默认为 unix:///var/run/docker.sock
  • -l, --log-level="debug|info|warn|error|fatal":指定日志输出级别;
  • --tls=true|false:是否对 Docker 守护进程启用 TLS 安全机制,默认为否;
  • --tlscacert=/.docker/ca.pem:TLS CA 签名的可信证书文件路径;
  • --tlscert=/.docker/cert.pem:TLS 可信证书文件路径;
  • --tlscert=/.docker/key.pem:TLS 密钥文件路径;
  • --tlsverify=true|false:启用 TLS 校验,默认为否。

# 客户端命令

可以通过 docker COMMAND --help 来查看这些命令的具体用法。

  • attach:依附到一个正在运行的容器中;
  • build:从一个 Dockerfile 创建一个镜像;
  • commit:从一个容器的修改中创建一个新的镜像;
  • cp:在容器和本地宿主系统之间复制文件中;
  • create:创建一个新容器,但并不运行它;
  • diff:检查一个容器内文件系统的修改,包括修改和增加;
  • events:从服务端获取实时的事件;
  • exec:在运行的容器内执行命令;
  • export:导出容器内容为一个 tar 包;
  • history:显示一个镜像的历史信息;
  • images:列出存在的镜像;
  • import:导入一个文件(典型为 tar 包)路径或目录来创建一个本地镜像;
  • info:显示一些相关的系统信息;
  • inspect:显示一个容器的具体配置信息;
  • kill:关闭一个运行中的容器 (包括进程和所有相关资源);
  • load:从一个 tar 包中加载一个镜像;
  • login:注册或登录到一个 Docker 的仓库服务器;
  • logout:从 Docker 的仓库服务器登出;
  • logs:获取容器的 log 信息;
  • network:管理 Docker 的网络,包括查看、创建、删除、挂载、卸载等;
  • node:管理 swarm 集群中的节点,包括查看、更新、删除、提升/取消管理节点等;
  • pause:暂停一个容器中的所有进程;
  • port:查找一个 nat 到一个私有网口的公共口;
  • ps:列出主机上的容器;
  • pull:从一个Docker的仓库服务器下拉一个镜像或仓库;
  • push:将一个镜像或者仓库推送到一个 Docker 的注册服务器;
  • rename:重命名一个容器;
  • restart:重启一个运行中的容器;
  • rm:删除给定的若干个容器;
  • rmi:删除给定的若干个镜像;
  • run:创建一个新容器,并在其中运行给定命令;
  • save:保存一个镜像为 tar 包文件;
  • search:在 Docker index 中搜索一个镜像;
  • service:管理 Docker 所启动的应用服务,包括创建、更新、删除等;
  • start:启动一个容器;
  • stats:输出(一个或多个)容器的资源使用统计信息;
  • stop:终止一个运行中的容器;
  • swarm:管理 Docker swarm 集群,包括创建、加入、退出、更新等;
  • tag:为一个镜像打标签;
  • top:查看一个容器中的正在运行的进程信息;
  • unpause:将一个容器内所有的进程从暂停状态中恢复;
  • update:更新指定的若干容器的配置信息;
  • version:输出 Docker 的版本信息;
  • volume:管理 Docker volume,包括查看、创建、删除等;
  • wait:阻塞直到一个容器终止,然后输出它的退出符。

# 一张图总结 Docker 的命令

Docker 命令总结

# 参考

# 服务端命令(dockerd)

# dockerd 命令选项

  • --api-cors-header="":CORS 头部域,默认不允许 CORS,要允许任意的跨域访问,可以指定为 “*”;
  • --authorization-plugin="":载入认证的插件;
  • -b="":将容器挂载到一个已存在的网桥上。指定为 none 时则禁用容器的网络,与 --bip 选项互斥;
  • --bip="":让动态创建的 docker0 网桥采用给定的 CIDR 地址; 与 -b 选项互斥;
  • --cgroup-parent="":指定 cgroup 的父组,默认 fs cgroup 驱动为 /docker,systemd cgroup 驱动为 system.slice
  • --cluster-store="":构成集群(如 Swarm)时,集群键值数据库服务地址;
  • --cluster-advertise="":构成集群时,自身的被访问地址,可以为 host:portinterface:port
  • --cluster-store-opt="":构成集群时,键值数据库的配置选项;
  • --config-file="/etc/docker/daemon.json":daemon 配置文件路径;
  • --containerd="":containerd 文件的路径;
  • -D, --debug=true|false:是否使用 Debug 模式。缺省为 false;
  • --default-gateway="":容器的 IPv4 网关地址,必须在网桥的子网段内;
  • --default-gateway-v6="":容器的 IPv6 网关地址;
  • --default-ulimit=[]:默认的 ulimit 值;
  • --disable-legacy-registry=true|false:是否允许访问旧版本的镜像仓库服务器;
  • --dns="":指定容器使用的 DNS 服务器地址;
  • --dns-opt="":DNS 选项;
  • --dns-search=[]:DNS 搜索域;
  • --exec-opt=[]:运行时的执行选项;
  • --exec-root="":容器执行状态文件的根路径,默认为 /var/run/docker
  • --fixed-cidr="":限定分配 IPv4 地址范围;
  • --fixed-cidr-v6="":限定分配 IPv6 地址范围;
  • -G, --group="":分配给 unix 套接字的组,默认为 docker
  • -g, --graph="":Docker 运行时的根路径,默认为 /var/lib/docker
  • -H, --host=[]:指定命令对应 Docker daemon 的监听接口,可以为 unix 套接字 unix:///path/to/socket,文件句柄 fd://socketfd 或 tcp 套接字 tcp://[host[:port]],默认为 unix:///var/run/docker.sock
  • --icc=true|false:是否启用容器间以及跟 daemon 所在主机的通信。默认为 true。
  • --insecure-registry=[]:允许访问给定的非安全仓库服务;
  • --ip="":绑定容器端口时候的默认 IP 地址。缺省为 0.0.0.0
  • --ip-forward=true|false:是否检查启动在 Docker 主机上的启用 IP 转发服务,默认开启。注意关闭该选项将不对系统转发能力进行任何检查修改;
  • --ip-masq=true|false:是否进行地址伪装,用于容器访问外部网络,默认开启;
  • --iptables=true|false:是否允许 Docker 添加 iptables 规则。缺省为 true;
  • --ipv6=true|false:是否启用 IPv6 支持,默认关闭;
  • -l, --log-level="debug|info|warn|error|fatal":指定日志输出级别;
  • --label="[]":添加指定的键值对标注;
  • --log-driver="json-file|syslog|journald|gelf|fluentd|awslogs|splunk|etwlogs|gcplogs|none":指定日志后端驱动,默认为 json-file
  • --log-opt=[]:日志后端的选项;
  • --mtu=VALUE:指定容器网络的 mtu
  • -p="":指定 daemon 的 PID 文件路径。缺省为 /var/run/docker.pid
  • --raw-logs:输出原始,未加色彩的日志信息;
  • --registry-mirror=<scheme>://<host>:指定 docker pull 时使用的注册服务器镜像地址;
  • -s, --storage-driver="":指定使用给定的存储后端;
  • --selinux-enabled=true|false:是否启用 SELinux 支持。缺省值为 false。SELinux 目前尚不支持 overlay 存储驱动;
  • --storage-opt=[]:驱动后端选项;
  • --tls=true|false:是否对 Docker daemon 启用 TLS 安全机制,默认为否;
  • --tlscacert=/.docker/ca.pem:TLS CA 签名的可信证书文件路径;
  • --tlscert=/.docker/cert.pem:TLS 可信证书文件路径;
  • --tlscert=/.docker/key.pem:TLS 密钥文件路径;
  • --tlsverify=true|false:启用 TLS 校验,默认为否;
  • --userland-proxy=true|false:是否使用用户态代理来实现容器间和出容器的回环通信,默认为 true;
  • --userns-remap=default|uid:gid|user:group|user|uid:指定容器的用户命名空间,默认是创建新的 UID 和 GID 映射到容器内进程。

# 参考

留言與分享

# 常见问题总结

# 镜像相关

# 如何批量清理临时镜像文件?

答:可以使用 docker image prune 命令。

# 如何查看镜像支持的环境变量?

答:可以使用 docker run IMAGE env 命令。

# 本地的镜像文件都存放在哪里?

答:与 Docker 相关的本地资源默认存放在 /var/lib/docker/ 目录下,以 overlay2 文件系统为例,其中 containers 目录存放容器信息,image 目录存放镜像信息,overlay2 目录下存放具体的镜像层文件。

# 构建 Docker 镜像应该遵循哪些原则?

答:整体原则上,尽量保持镜像功能的明确和内容的精简,要点包括

  • 尽量选取满足需求但较小的基础系统镜像,例如大部分时候可以选择 alpine 镜像,仅有不足六兆大小;

  • 清理编译生成文件、安装包的缓存等临时文件;

  • 安装各个软件时候要指定准确的版本号,并避免引入不需要的依赖;

  • 从安全角度考虑,应用要尽量使用系统的库和依赖;

  • 如果安装应用时候需要配置一些特殊的环境变量,在安装后要还原不需要保持的变量值;

  • 使用 Dockerfile 创建镜像时候要添加 .dockerignore 文件或使用干净的工作目录。

更多内容请查看 Dockerfile 最佳实践

# 碰到网络问题,无法 pull 镜像,命令行指定 http_proxy 无效?

答:在 Docker 配置文件中添加 export http_proxy="http://<PROXY_HOST>:<PROXY_PORT>",之后重启 Docker 服务即可。

# 容器相关

# 容器退出后,通过 docker container ls 命令查看不到,数据会丢失么?

答:容器退出后会处于终止(exited)状态,此时可以通过 docker container ls -a 查看。其中的数据也不会丢失,还可以通过 docker start 命令来启动它。只有删除掉容器才会清除所有数据。

# 如何停止所有正在运行的容器?

答:可以使用 docker stop $(docker container ls -q) 命令。

# 如何批量清理已经停止的容器?

答:可以使用 docker container prune 命令。

# 如何获取某个容器的 PID 信息?

答:可以使用

1
docker inspect --format '{{ .State.Pid }}' <CONTAINER ID or NAME>

# 如何获取某个容器的 IP 地址?

答:可以使用

1
docker inspect --format '{{ .NetworkSettings.IPAddress }}' <CONTAINER ID or NAME>

# 如何给容器指定一个固定 IP 地址,而不是每次重启容器 IP 地址都会变?

答:使用以下命令启动容器可以使容器 IP 固定不变

1
2
3
$ docker network create -d bridge --subnet 172.25.0.0/16 my-net

$ docker run --network=my-net --ip=172.25.3.3 -itd --name=my-container busybox

# 如何临时退出一个正在交互的容器的终端,而不终止它?

答:按 Ctrl-p Ctrl-q。如果按 Ctrl-c 往往会让容器内应用进程终止,进而会终止容器。

# 使用 docker port 命令映射容器的端口时,系统报错“Error: No public port ‘80’ published for xxx”?

答:

  • 创建镜像时 Dockerfile 要通过 EXPOSE 指定正确的开放端口;

  • 容器启动时指定 PublishAllPort = true

# 可以在一个容器中同时运行多个应用进程么?

答:一般并不推荐在同一个容器内运行多个应用进程。如果有类似需求,可以通过一些额外的进程管理机制,比如 supervisord 来管理所运行的进程。可以参考 https://docs.docker.com/config/containers/multi-service\_container/

# 如何控制容器占用系统资源(CPU、内存)的份额?

答:在使用 docker create 命令创建容器或使用 docker run 创建并启动容器的时候,可以使用 -c|–cpu-shares[=0] 参数来调整容器使用 CPU 的权重;使用 -m|–memory[=MEMORY] 参数来调整容器使用内存的大小。

# 仓库相关

# 仓库(Repository)、注册服务器(Registry)、注册索引(Index) 有何关系?

首先,仓库是存放一组关联镜像的集合,比如同一个应用的不同版本的镜像。

注册服务器是存放实际的镜像文件的地方。注册索引则负责维护用户的账号、权限、搜索、标签等的管理。因此,注册服务器利用注册索引来实现认证等管理。

# 配置相关

# Docker 的配置文件放在哪里,如何修改配置?

答:使用 systemd 的系统(如 Ubuntu 16.04、Centos 等)的配置文件在 /etc/docker/daemon.json

# 如何更改 Docker 的默认存储位置?

答:Docker 的默认存储位置是 /var/lib/docker,如果希望将 Docker 的本地文件存储到其他分区,可以使用 Linux 软连接的方式来完成,或者在启动 daemon 时通过 -g 参数指定,或者修改配置文件 /etc/docker/daemon.json 的 “data-root” 项 。可以使用 docker system info | grep "Root Dir" 查看当前使用的存储位置。

例如,如下操作将默认存储位置迁移到 /storage/docker。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@s26 ~]# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/VolGroup-lv_root 50G 5.3G 42G 12% /
tmpfs 48G 228K 48G 1% /dev/shm
/dev/sda1 485M 40M 420M 9% /boot
/dev/mapper/VolGroup-lv_home 222G 188M 210G 1% /home
/dev/sdb2 2.7T 323G 2.3T 13% /storage
[root@s26 ~]# service docker stop
[root@s26 ~]# cd /var/lib/
[root@s26 lib]# mv docker /storage/
[root@s26 lib]# ln -s /storage/docker/ docker
[root@s26 lib]# ls -la docker
lrwxrwxrwx. 1 root root 15 11月 17 13:43 docker -> /storage/docker
[root@s26 lib]# service docker start

# 使用内存和 swap 限制启动容器时候报警告:“WARNING: Your kernel does not support cgroup swap limit. WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.”?

答:这是因为系统默认没有开启对内存和 swap 使用的统计功能,引入该功能会带来性能的下降。要开启该功能,可以采取如下操作:

  • 编辑 /etc/default/grub 文件(Ubuntu 系统为例),配置 GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1"

  • 更新 grub:$ sudo update-grub

  • 重启系统,即可。

# Docker 与虚拟化

# Docker 与 LXC(Linux Container)有何不同?

答:LXC 利用 Linux 上相关技术实现了容器。Docker 则在如下的几个方面进行了改进:

  • 移植性:通过抽象容器配置,容器可以实现从一个平台移植到另一个平台;
  • 镜像系统:基于 OverlayFS 的镜像系统为容器的分发带来了很多的便利,同时共同的镜像层只需要存储一份,实现高效率的存储;
  • 版本管理:类似于Git的版本管理理念,用户可以更方便的创建、管理镜像文件;
  • 仓库系统:仓库系统大大降低了镜像的分发和管理的成本;
  • 周边工具:各种现有工具(配置管理、云平台)对 Docker 的支持,以及基于 Docker的 PaaS、CI 等系统,让 Docker 的应用更加方便和多样化。

# Docker 与 Vagrant 有何不同?

答:两者的定位完全不同。

  • Vagrant 类似 Boot2Docker(一款运行 Docker 的最小内核),是一套虚拟机的管理环境。Vagrant 可以在多种系统上和虚拟机软件中运行,可以在 Windows,Mac 等非 Linux 平台上为 Docker 提供支持,自身具有较好的包装性和移植性。

  • 原生的 Docker 自身只能运行在 Linux 平台上,但启动和运行的性能都比虚拟机要快,往往更适合快速开发和部署应用的场景。

简单说:Vagrant 适合用来管理虚拟机,而 Docker 适合用来管理应用环境。

# 开发环境中 Docker 和 Vagrant 该如何选择?

答:Docker 不是虚拟机,而是进程隔离,对于资源的消耗很少,但是目前需要 Linux 环境支持。Vagrant 是虚拟机上做的封装,虚拟机本身会消耗资源。

如果本地使用的 Linux 环境,推荐都使用 Docker。

如果本地使用的是 macOS 或者 Windows 环境,那就需要开虚拟机,单一开发环境下 Vagrant 更简单;多环境开发下推荐在 Vagrant 里面再使用 Docker 进行环境隔离。

# 其它

# Docker 能在非 Linux 平台(比如 Windows 或 macOS )上运行么?

答:完全可以。安装方法请查看 安装 Docker 一节

# 如何将一台宿主主机的 Docker 环境迁移到另外一台宿主主机?

答:停止 Docker 服务。将整个 Docker 存储文件夹复制到另外一台宿主主机,然后调整另外一台宿主主机的配置即可。

# 如何进入 Docker 容器的网络命名空间?

答:Docker 在创建容器后,删除了宿主主机上 /var/run/netns 目录中的相关的网络命名空间文件。因此,在宿主主机上是无法看到或访问容器的网络命名空间的。

用户可以通过如下方法来手动恢复它。

首先,使用下面的命令查看容器进程信息,比如这里的 1234。

1
2
$ docker inspect --format='{{. State.Pid}} ' $container_id
1234

接下来,在 /proc 目录下,把对应的网络命名空间文件链接到 /var/run/netns 目录。

1
$ sudo ln -s /proc/1234/ns/net /var/run/netns/

然后,在宿主主机上就可以看到容器的网络命名空间信息。例如

1
2
$ sudo ip netns show
1234

此时,用户可以通过正常的系统命令来查看或操作容器的命名空间了。例如修改容器的 IP 地址信息为 172.17.0.100/16

1
$ sudo ip netns exec 1234 ifconfig eth0 172.17.0.100/16

# 如何获取容器绑定到本地那个 veth 接口上?

答:Docker 容器启动后,会通过 veth 接口对连接到本地网桥,veth 接口命名跟容器命名毫无关系,十分难以找到对应关系。

最简单的一种方式是通过查看接口的索引号,在容器中执行 ip a 命令,查看到本地接口最前面的接口索引号,如 205,将此值加上 1,即 206,然后在本地主机执行 ip a 命令,查找接口索引号为 206 的接口,两者即为连接的 veth 接口对。

留言與分享

# podman

podman (opens new window) 是一个无守护程序与 docker 命令兼容的下一代 Linux 容器工具。

# 安装

1
$ sudo yum -y install podman

# 使用

podman 与 docker 命令完全兼容,只需将 docker 替换为 podman 即可,例如运行一个容器:

1
2
3
# $ docker run -d -p 80:80 nginx:alpine

$ podman run -d -p 80:80 nginx:alpine

# 参考

留言與分享

作者的圖片

Kein Chan

這是獨立全棧工程師Kein Chan的技術博客
分享一些技術教程,命令備忘(cheat-sheet)等


全棧工程師
資深技術顧問
數據科學家
Hit廣島觀光大使


Tokyo/Macau