Welcome to pyfreyr’s wiki!

_images/favicon.ico

Python

Python 开发手册

Python 项目结构

“项目目录结构” 也属于 “可读性和可维护性” 的范畴,我们设计一个层次清晰的目录结构,就是为了达到以下两点:

  • 可读性高: 不熟悉这个项目的代码的人,一眼就能看懂目录结构,知道程序启动脚本是哪个,测试目录在哪儿,配置文件在哪儿等等。从而非常快速的了解这个项目。
  • 可维护性高: 定义好组织规则后,维护者就能很明确地知道,新增的哪个文件和代码应该放在什么目录之下。这个好处是,随着时间的推移,代码/配置的规模增加,项目结构不会混乱,仍然能够组织良好。
基础目录结构

比较方便快捷的目录结构如下:

myproject
├── myproject
│   └── __init__.py
├── data
│   └── data1.dat
├── scripts
│   └── rpm_install.sh
├── docs
│   └── abc.rst
├── examples
│   └── app.py
├── tests
│   └── test_myproject.py
├── LICENSE
├── MANIFEST.in
├── README.rst
├── requirements.txt
├── setup.cfg
└── setup.py
目录结构说明
data/

数据目录,Python 项目很少以 resources 命名数据目录。

scripts/

项目部署依赖的系统环境安装脚本。

docs/

项目文档目录,一般以 reStructureText 格式书写,最终使用 Sphinx 自动生成文档,并选择是否托管到 readthedocs

examples/

示例程序目录。

tests/

测试目录。

requirements.txt

软件外部依赖 Python 包列表。

LICENSE/MANIFEST.in/setup.py/setup.cfg

项目打包,参考 Python 打包分发工具 setuptools

README.rst

项目说明文件。

Tip

目录结构学习推荐项目:djangoscrapyflask

Pip 使用手册

pip-cheatsheet.pdf

Pip 是 Python 官方的包管理工具。

$ pip
Usage:
  pip <command> [options]

Commands:
  install Install packages.
  download Download packages.
  uninstall Uninstall packages.
  freeze Output installed packages in requirements format.
  list List installed packages.
  show Show information about installed packages.
  check Verify installed packages have compatible dependencies.
  search Search PyPI for packages.
  wheel Build wheels from your requirements.
  hash Compute hashes of package archives.
  completion A helper command used for command completion.
  help Show help for commands.

最常用的命令有:install, uninstall, search, show, freeze

列出已安装的包

pip list 用于列出已安装的包,一般使用 --format=(legacy|columns) 参数设置显示样式:

$ pip list --format=legacy
absl-py (0.2.1)
acora (2.0)
alabaster (0.7.9)
anaconda-clean (1.0)
...

$ pip list --format=columns
Package                            Version
---------------------------------- -----------
absl-py                            0.2.1
acora                              2.0
alabaster                          0.7.9
anaconda-clean                     1.0
...

Tip

如果不想每次输入 --format,参考 更换 PyPI 镜像

也可以简单使用 pip freeze 以 requirements 文件样式列出安装的包,使用重定向可方便导出 requirements:

$ pip freeze > requirements.txt

使用 list 的 -o 参数查看可升级的包:

$ pip list -o
搜索包

从 PyPI 镜像搜索包:

$ pip search tensorflow
tensorflow (1.8.0) - TensorFlow helps the tensors flow
tensorflow-qndex (0.0.22) - tensorflow-qnd x tensorflow-extenteten
tensorflow-lattice (0.9.6) - TensorFlow Lattice provides lattice models in TensorFlow
...
安装包

使用 pip install 在线安装或安装本地包,默认安装路径为 site-packages

从 PyPI 安装

默认从官方 PyPI 源安装包,如果设置了国内镜像源,则从指定源安装。

$ pip install tensorflow

安装时可以指定包的版本,如果不指定则安装最新版本:

$ pip install tensorflow==1.7.0  # 指定版本
$ pip install tensorflow>=1.7.0  # 最低版本
$ pip install tensorflow<=1.7.0  # 最高版本

可以简单的使用 -i 指定获取包的软件源,如果不想每次都手动指定,参考 更换 PyPI 镜像

$ pip install tensorflow==1.7.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
从 Github 安装

使用格式 git+URL 从 github 安装包,一般只用于未上传至 PyPI 或想要尝试最新开发版。

$ pip install git+https://github.com/fxsjy/jieba
从本地安装

pip 也可以安装本地下载或本地打包的 Python 包,通常用于自己写的包和下载编译好的 wheel 包。比如安装 windows 平台编译好的 twisted

$ pip install Twisted‑18.4.0‑cp35‑cp35m‑win_amd64.whl
从 requirements 安装

在配置生产环境时,需要安装的包较多且对版本有要求,如果一个个使用 pip install 去安装太费劲。pip 提供了从文本读取包名进行安装,只需要事先准备好 requirements.txt 文件。格式如:

tqdm==4.14.0
tornado==4.4.1
scikit-learn==0.18.1
tensorflow-gpu==1.0.1

安装时使用 -r 选项指定 requirements.txt:

$ pip install -r requirements.txt
不使用缓存

pip 默认使用缓存安装,如果包已经下载在本地,则直接使用本地下载过的包安装。如果不想使用缓存安装,使用选项 --no-cache-dir

$ pip install --no-cache-dir tensorflow
卸载包

卸载包使用 pip uninstall

$ pip uninstall jieba
升级包

使用 pip install -U 进行包升级:

$ pip install -U tensorflow
查看包的安装信息

使用 pip show 查看已安装的包的相关信息,如版本,目录等。

$ pip show jieba
Name: jieba
Version: 0.39
Summary: Chinese Words Segementation Utilities
Home-page: https://github.com/fxsjy/jieba
Author: Sun, Junyi
Author-email: ccnusjy@gmail.com
License: MIT
Location: /home/chenfei/.conda/envs/tf1.7/lib/python3.5/site-packages
Requires:
更换 PyPI 镜像

在 Linux 下新建/修改 ~/.config/pip/pip.conf

[global]
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
format = columns

Note

Mac 和 Windows 下配置文件地址参见:configuration

Tip

推荐 PyPI 镜像源:

如果使用豆瓣之类的 HTTP 镜像,需要使用 --trusted-host 添加信任:

$ pip install jieba -i http://pypi.douban.com/simple --trusted-host pypi.douban.com

不想每次指定 --trusted-host 就在 pip.conf 添加:

[global]
index-url = http://pypi.douban.com/simple
format = columns
[install]
trusted-host=pypi.douban.com

Conda 使用手册

conda-cheatsheet.pdf

Conda 是针对于 Python 的环境和包管理工具。可以安装 miniconda 或 anaconda 进行安装,前者是简化版本,只包含 conda 和其依赖。

Conda 有 Python3.x 和 Python2.x 系列两个版本,其实都没有关系,因为你在使用 conda 进行创建环境时,可以指定 Python 的版本。

安装 conda

以 miniconda 为例,进入 https://conda.io/miniconda.html 选择对应的版本下载并安装。

Tip

也可以使用清华 miniconda 源:https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda/

查看 conda 帮助

所有关于 conda 的使用都可以从帮助信息获取,在什么也不知道的情况下就可以使用 conda -h/--help 查看帮助信息:

$ conda -h
conda is a tool for managing and deploying applications, environments and packages.

Options:

positional arguments:
  command
    info Display information about current conda install.
    help Displays a list of available conda commands and their help
                 strings.
    list List linked packages in a conda environment.
    ......

可以看到,conda 有很多命令,要查看具体命令帮助,同样使用 -h 选项,如:

$ conda info -h
usage: conda info [-h] [--json] [--debug] [--verbose] [--offline] [-a] [-e]
                  [-l] [-s] [--root] [--unsafe-channels]
                  [packages [packages ...]]

Display information about current conda install.

Options:

positional arguments:
  packages Display information about packages.

optional arguments:
  -h, --help Show this help message and exit.
  --json Report all output as json. Suitable for using conda
                     programmatically.
  ......
conda 常用命令
conda info

显示当前 conda 安装的所有信息。通常使用 -e/--envs 选项查看创建的虚拟环境:

$ conda info -e
# conda environments:
#
root * /opt/anaconda3
包管理命令

一般来说 conda 仓库的软件没有 PyPI 更新快和全。所以推荐 conda 只用来创建虚拟环境(后面介绍),包的安装管理仍然使用 pip。但在 windows 下由于依赖不好处理,所以在使用 pip 失败时可以查询是否有编译好的 conda 包。

conda list

列出当前环境下所有安装的 conda 包。

$ conda list
# packages in environment at /opt/anaconda3:
#
_license 1.1 py35_1
_nb_ext_conf 0.3.0 py35_0
acora 2.0 <pip>
alabaster 0.7.9 py35_0
......
查询包

使用 conda search 查询 conda 包,只有经过 conda 重新编译入库的才能使用 conda search 查询得到,具体可以进 Anaconda package lists 查看。

$ conda search scrapy
Loading channels: done
Name Version Build Channel
......
scrapy 1.4.0 py27h060f748_1 defaults
scrapy 1.4.0 py35h03cf01c_1 defaults
scrapy 1.4.0 py36h5dd8a1d_1 defaults
scrapy 1.5.0 py27_0 defaults
scrapy 1.5.0 py35_0 defaults
scrapy 1.5.0 py36_0 defaults
安装包

使用 conda install 安装 conda 包,会自动处理包之间的依赖。

$ conda install scrapy

使用 conda 安装指定版本包,既可以使用类似 pip 的 ==,也可以直接使用 =

$ conda install scrapy=1.5.0
更新包

使用 conda update 更新 conda 包到最新版本,也可使用 conda upgrade

$ conda update scrapy
卸载包

使用 conda remove 卸载 conda 包,也可使用 conda uninstall

$ conda remove scrapy
环境管理命令
创建环境

创建虚拟环境,使用 -n/--name 指定环境名称。可以在创建环境的同时安装包。由于 conda 将 Python 也作为包,所以可以像其他包一样安装。

$ conda create --name tf python=3.5.2 tensorflow

现在使用 conda info -e 查看环境(也可使用命令 conda env list):

$ conda info -e
# conda environments:
#
base * /opt/anaconda3
tf /home/${user}/.conda/envs/tf

Note

用户创建的虚拟环境保存在 ~/.conda/envs 下。

激活环境

默认处于 base 环境,进入其他环境需要使用 source activate 手动切换:

$ source activate tf

Attention

Windows 下建议使用 git-bash 操作(安装 windows 版本 git 自动创建),或者在 cmd/powershell 下使用 activate ${env-name} 激活(没有 source)。

激活成功会在命令行提示符前面标识出当前环境:

(tf) ➜ ~

若要退出激活当前环境,使用 source deactivate,默认回到 base 环境:

$ source deactivate
删除环境

删除环境也使用 conda remove 命令,不过加上参数 --all 并使用 -n/--name 指定要删除的环境名。

$ conda remove -n tf --all

也可以使用命令 conda env remove -n tf

拷贝环境

在创建环境时可以使用 --clone 从已存在的环境进行拷贝。

$ conda create --clone tensorflow --name tf
在指定环境中管理包

Tip

如果和我一样使用 pip 进行包管理,可以忽略这部分。o(∩_∩)o

一种方法是激活该环境,然后按照上面包管理进行操作;另一种方法是使用 conda 进行包管理时指定环境:

$ conda install --name myenv redis
$ conda remove --name myenv redis
添加 conda 镜像源

同样,使用 pip 管理包的忽略。conda 会在每个用户家目录下创建 .conda 目录,用于管理创建的环境,而配置文件存放于 .condarc (没有可以新建)。

Tip

国内源比较好的有:清华 Conda 源中科大 Conda 源

使用方法:

$ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
$ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
$ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/
$ conda config --set show_channel_urls yes

若要使用官方仓库,删除 ~/.condarc

Tip

以上添加镜像源只针对默认 base 环境生效,因为 ~/.condarc 只服务于 base。

如果想在自己创建环境中生效,则需要在激活的环境内使用 conda config 选项 --env 重复上面添加操作。如:

$ conda config --env --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/

这样,会在 ~/.conda/ennvs/${env-name} 下创建 .condarc

Python 项目结构
好的项目目录结构是可读性和可维护性的基石。
Pip 使用手册
使用 pip 管理 Python 三方包。
Conda 使用手册
使用 conda 管理 Python 环境。

Python 库

路径处理 pathlib

pathlib 模块提供了文件路径对象抽象,不仅仅是替换 os.path 模块对文件的操作,可以说是路径处理的瑞士军刀。

This module offers classes representing filesystem paths with semantics appropriate for different operating systems. Path classes are divided between pure paths, which provide purely computational operations without I/O, and concrete paths, which inherit from pure paths but also provide I/O operations.

其中最常使用的就是 Path 类,因为它与具体系统无关。

pathlib.PurePath
分割路径 parts

类似 os.path.split(),但返回元组。

p = PurePath('/usr/bin/python3').parts
# ('/', 'usr', 'bin', 'python3')

PureWindowsPath('c:/Program Files/PSF').parts
# ('c:\\', 'Program Files', 'PSF')
驱动器 drive

返回驱动器名称,主要面向 Windows 系统

PureWindowsPath('c:/Program Files/').drive  # 'c:'

PureWindowsPath('/Program Files/').drive  # ''

PurePosixPath('/etc').drive  # ''
根目录 root

返回路径的根目录,面向 Unix/Linux 系统

PureWindowsPath('c:/Program Files/').root  # '\\'

PureWindowsPath('c:Program Files/').root  # ''

PureWindowsPath('//host/share').root  # '\\'

PurePosixPath('/etc').root  # '/'
自动判断驱动器或根目录 anchor
PureWindowsPath('c:/Program Files/').anchor  # 'c:\\'

PureWindowsPath('c:Program Files/').anchor  # 'c:'

PurePosixPath('/etc').anchor  # '/'

PureWindowsPath('//host/share').anchor  # '\\\\host\\share\\'
所有上级目录列表 parents

返回所有上级(祖先)目录列表,[上级目录, 上上级目录, …, 根目录]

PureWindowsPath('c:/foo/bar/setup.py').parents
# [PureWindowsPath('c:/foo/bar'), PureWindowsPath('c:/foo'), PureWindowsPath('c:/')]
父目录 parent

返回父目录

PurePosixPath('/a/b/c/d').parent  # PurePosixPath('/a/b/c')

Attention

注意相对路径问题:

PurePosixPath('/').parent  # PurePosixPath('/')

PurePosixPath('.').parent  # PurePosixPath('.')

PurePosixPath('foo/..').parent  # PurePosixPath('foo')

Tip

在遇到相对路径时,在获取父目录之前调用 Path.resolve() 消除符号链接和 .. 之类表示。

完整文件名 name

返回除驱动器或根目录外完整文件名(带文件名后缀)。

PurePosixPath('my/library/setup.py').name  # 'setup.py'

PureWindowsPath('//some/share/setup.py').name  # 'setup.py'

PureWindowsPath('//some/share').name  # ''
文件后缀 suffix

返回文件扩后缀,没有扩展名则返回空串。

PurePosixPath('my/library/setup.py').suffix  # '.py'

PurePosixPath('my/library.tar.gz').suffix  # '.gz'

PurePosixPath('my/library').suffix  # ''
文件后缀列表 suffixes

当文件有多个后缀,返回文件所有后缀列表;没有则返回空列表。

PurePosixPath('my/library.tar.gar').suffixes  # ['.tar', '.gar']

PurePosixPath('my/library.tar.gz').suffixes  # ['.tar', '.gz']

PurePosixPath('my/library').suffixes  # []

Attention

有些软件的版本号也会被当做后缀处理,如:redis-4.0.9.tar.gz。

文件名 stem

返回不带后缀的文件名,有多个后缀则去除最后一个后缀将文件名返回。

PurePosixPath('my/library.tar.gz').stem  # 'library.tar'

PurePosixPath('my/library.tar').stem  # 'library'

PurePosixPath('my/library').stem  # 'library'
Unix 路径分隔符表示 as_posix()

将 Windows 路径分隔符 \\ 改为 Unix 样式 /

PureWindowsPath('c:\\windows').as_posix()  # 'c:/windows'
文件 URI 表示 as_uri()

返回文件 URI 表示,如果不是绝对路径抛 ValueError 异常。

PurePosixPath('/etc/passwd').as_uri()  # 'file:///etc/passwd'

PureWindowsPath('c:/Windows').as_uri()  # 'file:///c:/Windows'
判断绝对路径 is_absolute()

判断是否为绝对路径。

PurePosixPath('/a/b').is_absolute()  # True

PurePosixPath('a/b').is_absolute()  # False



PureWindowsPath('c:/a/b').is_absolute()  # True

PureWindowsPath('/a/b').is_absolute()  # False

PureWindowsPath('c:').is_absolute()  # False

PureWindowsPath('//some/share').is_absolute()  # True
拼接路径 joinpath(*other)

类似 os.path.join(),拼接路径。

PurePosixPath('/etc').joinpath('passwd')  # PurePosixPath('/etc/passwd')

PurePosixPath('/etc').joinpath(PurePosixPath('passwd'))  # PurePosixPath('/etc/passwd')

PurePosixPath('/etc').joinpath('init.d', 'apache2')  # PurePosixPath('/etc/init.d/apache2')
测试路径符合模式 match(pattern)

测试路径是否符合 pattern。

PurePath('a/b.py').match('*.py')  # True

PurePath('/a/b/c.py').match('b/*.py')  # True

PurePath('/a/b/c.py').match('a/*.py')  # False

Attention

如果 pattern 是绝对路径,则要求路径也是绝对路径才能匹配。

PurePath('/a.py').match('/*.py')  # True

PurePath('a/b.py').match('/*.py')  # False

匹配是忽略大小写的。

PureWindowsPath('b.py').match('*.PY')  # True
计算相对路径 relative_to(*other)

计算两个路径的相对路径,如果不存在则抛 ValueError

p = PurePosixPath('/etc/passwd')

p.relative_to('/')  # PurePosixPath('etc/passwd')

p.relative_to('/etc')  # PurePosixPath('passwd')

p.relative_to('/usr')

# Traceback (most recent call last):

#   File "<stdin>", line 1, in <module>

#   File "pathlib.py", line 694, in relative_to

#     .format(str(self), str(formatted)))

# ValueError: '/etc/passwd' does not start with '/usr'
更改路径名 with_name(name)

返回新路径,更改最后一级路径名称,若原路径没有文件名,则抛 ValueError

PureWindowsPath('c:/Downloads/pathlib.tar.gz').with_name('setup.py')
# PureWindowsPath('c:/Downloads/setup.py')

PureWindowsPath('c:/').with_name('setup.py')

# Traceback (most recent call last):

#   File "<stdin>", line 1, in <module>

#   File "/home/antoine/cpython/default/Lib/pathlib.py", line 751, in with_name

#     raise ValueError("%r has an empty name" % (self,))

# ValueError: PureWindowsPath('c:/') has an empty name
更改路径后缀 with_suffix(suffix)

返回新路径,更改原路径的后缀。如果原路径没有后缀,则直接添加。

PureWindowsPath('c:/Downloads/pathlib.tar.gz').with_suffix('.bz2')
# PureWindowsPath('c:/Downloads/pathlib.tar.bz2')

PureWindowsPath('README').with_suffix('.txt')
# PureWindowsPath('README.txt')
pathlib.Path
工作目录 Path.cwd()

@classmethod,返回当前工作目录。

Path.cwd()  # PosixPath('/home/antoine/pathlib')
用户家目录 Path.home()

@classmethod,返回当前用户家目录。

Path.home()  # PosixPath('/home/antoine')
路径信息 stat()

返回路径信息,类似 os.stat

p = Path('setup.py')

p.stat().st_size  # 956

p.stat().st_mtime  # 1327883547.852554
更改路径权限 chmod(mode)

更改路径权限,类似 os.chmod

p = Path('setup.py')

p.stat().st_mode  # 33277

p.chmod(0o444)

p.stat().st_mode  # 33060
判断路径存在 exists()

判断路径是否真实存在。

Path('.').exists()  # True

Path('setup.py').exists()  # True

Path('/etc').exists()  # True

Path('nonexistentfile').exists()  # False

Note

如果路径指向符号链接,则判断所链接的文件或目录是否存在。

展开~完整路径 expanduser()

展开用户家目录 ~ 为绝对路径,类似 os.path.expanduser()

PosixPath('~/films/Monty Python').expanduser()
# PosixPath('/home/eric/films/Monty Python')
过滤目录 glob(pattern)

过滤目录,返回所有匹配文件的生成器。

sorted(Path('.').glob('*.py'))
# [PosixPath('pathlib.py'), PosixPath('setup.py'), PosixPath('test_pathlib.py')]

sorted(Path('.').glob('*/*.py'))
# [PosixPath('docs/conf.py')]

** 表示任意级子目录:

sorted(Path('.').glob('**/*.py'))

# [PosixPath('build/lib/pathlib.py'),

#  PosixPath('docs/conf.py'),

#  PosixPath('pathlib.py'),

#  PosixPath('setup.py'),

#  PosixPath('test_pathlib.py')]
用户组 group()

返回路径文件用户组,若 gid 不存在则抛 KeyError

判断目录 is_dir()

如果路径时目录,返回 True;否则返回 False,可能情况:路径为文件、路径不存在、损坏的符号链接、没有权限等。

所以返回 False 并不代表就一定是文件!

判断文件 is_file()

如果路径是文件返回 True,否则返回 False,情况和 is_dir() 类似。

判断套接字 is_socket()

False 情况类似 is_dir()

判断管道 is_fifo()

False 情况类似 is_dir()

判断块设备 is_block_device()

False 情况类似 is_dir()

判断字符设备 is_char_device()

False 情况类似 is_dir()

遍历目录 iterdir()

当路径为目录时,遍历目录。

p = Path('docs')

for child in p.iterdir():
    print(child)


# PosixPath('docs/conf.py')

# PosixPath('docs/_templates')

# PosixPath('docs/make.bat')

# PosixPath('docs/index.rst')

# PosixPath('docs/_build')

# PosixPath('docs/_static')

# PosixPath('docs/Makefile')
更改符号链接权限 lchmod(mode)

类似 chmod(),但当路径是符号链接时,更改的是链接的权限,而不是所指向文件的权限。

符号链接路径信息 lstat()

类似 stat(),但当路径是符号链接时,返回的是链接的信息,而不是所指向文件的信息。

创建目录 mkdir()
mkdir(mode=0o777, parents=False, exist_ok=False)
  1. 如果设置 mode,则根据进程的 umask 值决定文件的权限。
  2. 如果创建的目录父目录不存在,则需要设置 parents=True,效果类似 mkdir -p,否则抛 FileNotFoundError
  3. 如果目录已存在,抛 FileExistsError,可以设置 exist_ok=True 忽略异常。
打开文件 open()
open(mode='r', buffering=-1, encoding=None, errors=None, newline=None)

类似内置 open() 方法。

p = Path('setup.py')

with p.open() as f:
    f.readline()  # '#!/usr/bin/env python3\n'
文件拥有者 owner()

返回文件拥有者,如果 uid 未找到,则抛 KeyError

按字节读取 read_bytes()
p = Path('my_binary_file')

p.write_bytes(b'Binary file contents')  # 20

p.read_bytes()  # b'Binary file contents'
按字符读取 read_text()
read_text(encoding=None, errors=None)
p = Path('my_text_file')

p.write_text('Text file contents')  # 18

p.read_text()  # 'Text file contents'
重命名 rename(target)

Unix 下重命名文件或目录,target 可以是字符串或 path 对象。如果是文件,重命名后内容还是原文件内容。

p = Path('foo')

p.open('w').write('some text')  # 9

target = Path('bar')

p.rename(target)

target.open().read()  # 'some text'
跨平台重命名 replace(target)

跨平台重命名文件或目录。不同于 rename 只在 Unix 下可用。

解析绝对路径 resolve(strict=False)

返回绝对路径,符号链接和相对路径都会被解析。如果路径不存在且 strict=True,则抛 FileNotFoundError

p = Path()  # PosixPath('.')

p.resolve()  # PosixPath('/home/antoine/pathlib')

Path('docs/../setup.py').resolve()
# PosixPath('/home/antoine/pathlib/setup.py')
过滤相对路径 rglob(pattern)

类似 glob(),但在 pattern 前添加 **

sorted(Path().rglob("*.py"))

# [PosixPath('build/lib/pathlib.py'),

#  PosixPath('docs/conf.py'),

#  PosixPath('pathlib.py'),

#  PosixPath('setup.py'),

#  PosixPath('test_pathlib.py')]
删除空目录 rmdir()

删除目录,要求目录为空。若非空可使用 shutil.rmtree()

判断同一文件 samefile(other_pattern)

判断两路径是否执行同一个文件,可以传入字符串或者 path 对象。类似 os.path.samefile()os.path.samestat()

p = Path('spam')

q = Path('eggs')

p.samefile(q)  # False

p.samefile('spam')  # True
创建文件 touch()
touch(mode=0o666, exist_ok=True)

创建文件,如果文件已存在且 exist_ok=False,则抛 FileExistsError,设置为 True 则忽略(文件修改时间更新到当前时间)。

按字节写入 write_bytes(data)
p = Path('my_binary_file')

p.write_bytes(b'Binary file contents')  # 20

p.read_bytes()  # b'Binary file contents'
按字符写入 write_text()
write_text(data, encoding=None, errors=None)
p = Path('my_text_file')

p.write_text('Text file contents')  # 18

p.read_text()  # 'Text file contents'
路径处理 pathlib
Python 路径处理标准库。
Python 开发手册
Python 开发手册,专注提升 Python 开发能力。
Python 库
站在巨人的肩膀上,多快好省。

Docker

Docker 基础

Docker 简介

Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从 Apache2.0 协议开源。

Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。

容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app),更重要的是容器性能开销极低。

Docker Engine

Docker 引擎(Engine)基于 client-server 架构,主要包括以下组件:

  • Server: 一个持续运行的守护进程(dockerd 命令)
  • REST API:用于与守护进程交互
  • Client:命令行客户端(docker 命令), 也是使用 REST API 与 Server 交互
_images/engine-components-flow.png
Docker 架构
_images/docker-architecture1.png _images/docker-architecture2.png
Docker 镜像(Images)
Docker 镜像是用于创建 Docker 容器的模板。
Docker 容器(Container)
容器是独立运行的一个或一组应用。
Docker 客户端(Client)
Docker 客户端通过命令行或者其他工具使用 Docker API 与 Docker 的守护进程通信。
Docker 主机(Host)
一个物理或者虚拟的机器用于执行 Docker 守护进程和容器。
Docker 仓库(Registry)
Docker 仓库用来保存镜像,可以理解为代码控制中的代码仓库。Docker Hub 提供了庞大的镜像集合供使用。
Docker Machine
Docker Machine是一个简化 Docker 安装的命令行工具,通过一个简单的命令行即可在相应的平台上安装 Docker,比如VirtualBox、 Digital Ocean、Microsoft Azure。
_images/docker-machine.png

容器(container)与镜像(image)的关系类似于面向对象编程中的对象与类。关于 Container 和 Image 的具体概念,参考 Dockerfile 入门指南

Docker 容器与虚拟机差别
_images/vm-vs-docker1.jpg

使用虚拟机运行多个相互隔离的应用时,如下图:

_images/vm-structure.jpg

从下到上理解上图:

  • 基础设施 (Infrastructure)

    可以是你的个人电脑、数据中心的服务器,或者是云主机。

  • 主操作系统 (Host Operating System)

    你的个人电脑之上,运行的可能是 MacOS,Windows 或者某个 Linux 发行版。

  • 虚拟机管理系统 (Hypervisor)

    利用 Hypervisor, 可以在主操作系统之上运行多个不同的从操作系统。Hypervisor 有支持 MacOS 的 HyperKit,支持 Windows 的 Hyper-V 以及支持 Linux 的 KVM;还有 VirtualBox 和 VMWare。

  • 从操作系统 (Guest Operating System)

    假设你需要运行 3 个相互隔离的应用,则需要使用 Hypervisor 启动 3 个从操作系统,也就是 3 个虚拟机。这些虚拟机都非常大,也许有 700MB,这就意味着它们将占用 2.1GB 的磁盘空间。更糟糕的是, 它们还会消耗很多 CPU 和内存。

  • 各种依赖

    每一个从操作系统都需要安装许多依赖。如果你的的应用需要连接 PostgreSQL 的话,则需要安装 libpq-dev;如果你使用 Ruby 的话,应该需要安装 gems;如果使用其他编程语言,比如 Python 或者 Node.js,都会需要安装对应的依赖库。

  • 应用

    安装依赖之后,就可以在各个从操作系统分别运行应用了,这样各个应用就是相互隔离的。

使用 Docker 容器运行多个相互隔离的应用时, 如下图:

_images/docker-structure.jpg

相比于虚拟机,Docker 要简洁很多,因为我们不需要运行一个臃肿的从操作系统了。从下到上理解上图:

  • 基础设施

  • 主操作系统

    所有主流的操作系统都可以运行 Docker。

  • Docker 守护进程 (Docker Daemon)

    Docker 守护进程取代了 Hypervisor,它是运行在操作系统之上的后台进程,负责管理 Docker 容器。

  • 各种依赖

    对于 Docker,应用的所有依赖都打包在 Docker 镜像中,Docker 容器是基于 Docker 镜像创建的。

  • 应用

    应用的源代码与它的依赖都打包在 Docker 镜像中,不同的应用需要不同的Docker镜像。不同的应用运行在不同的 Docker 容器中,它们是相互隔离的。

Docker 守护进程可以直接与主操作系统进行通信,为各个 Docker 容器分配资源。它还可以将容器与主操作系统隔离,并将各个容器互相隔离。虚拟机启动需要数分钟,而 Docker 容器可以在数毫秒内启动。由于没有臃肿的从操作系统,Docker 可以节省大量的磁盘空间以及其他系统资源。

_images/vm-vs-docker2.jpg
Docker 优点
  1. 简化程序:

    Docker 让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,便可以实现虚拟化。Docker改变了虚拟化的方式,使开发者可以直接将自己的成果放入Docker中进行管理。方便快捷已经是 Docker的最大优势,过去需要用数天乃至数周的 任务,在Docker容器的处理下,只需要数秒就能完成。

  2. 避免选择恐惧症:

    如果你有选择恐惧症,还是资深患者。Docker 帮你 打包你的纠结!比如 Docker 镜像;Docker 镜像中包含了运行环境和配置,所以 Docker 可以简化部署多种应用实例工作。比如 Web 应用、后台应用、数据库应用、大数据应用比如 Hadoop 集群、消息队列等等都可以打包成一个镜像部署。

  3. 节省开支:

    一方面,云计算时代到来,使开发者不必为了追求效果而配置高额的硬件,Docker 改变了高性能必然高价格的思维定势。Docker 与云的结合,让云空间得到更充分的利用。不仅解决了硬件管理的问题,也改变了虚拟化的方式。

Docker 应用场景

虚拟机更擅长于彻底隔离整个运行环境,例如云服务提供商通常采用虚拟机技术隔离不同的用户;而 Docker 通常用于隔离不同的应用,如:

  • Web 应用的自动化打包和发布。
  • 自动化测试和持续集成、发布。
  • 在服务型环境中部署和调整数据库或其他的后台应用。

Docker 安装教程

CentOS 7

本文参考 https://store.docker.com/search?type=edition&offering=community

系统依赖

centos-extras 仓库必须开启(/etc/yum.repos.d/Centos-Base.repo 内)。

卸载旧版(可选)

如果安装老版本(以前叫做 docker 或 docker-engine,现在拆分为 docker-ee 和 docker-ce)则需要先卸载:

$ yum remove docker \
      docker-client \
      docker-client-latest \
      docker-common \
      docker-latest \
      docker-latest-logrotate \
      docker-logrotate \
      docker-selinux \
      docker-engine-selinux \
      docker-engine
$ rm -rf /var/lib/docker
安装 docker-ce
使用 yum 安装
  1. 添加仓库

    $ yum install -y yum-utils \
          device-mapper-persistent-data \
          lvm2
    $ yum-config-manager \
        --add-repo \
        https://download.docker.com/linux/centos/docker-ce.repo
    
  2. 安装

    使用以下命令直接安装最新版:

    $ yum install docker-ce
    

    如果要安装指定版本,先查看可用版本:

    $ yum list docker-ce --showduplicates | sort -r
    docker-ce.x86_64 18.06.1.ce-3.el7 docker-ce-stable
    docker-ce.x86_64 18.06.0.ce-3.el7 docker-ce-stable
    docker-ce.x86_64 18.03.1.ce-1.el7.centos docker-ce-stable
    ......
    

    然后指定版本安装:

    $ yum install docker-ce-18.03.1.ce
    

更新使用 yum install 指定版本即可。

使用 rpm 安装

进入 https://download.docker.com/linux/centos/7/x86_64/stable/Packages/ 下载安装包,执行以下命令安装:

$ yum install docker-ce-18.06.0.ce-3.el7.x86_64.rpm

更新时使用 yum upgrade 指定新 rpm 包。

查看版本
$ docker version
Client:
 Version: 17.09.0-ce
 API version: 1.32
 Go version: go1.8.3
 Git commit: afdb6d4
 Built: Tue Sep 26 22:41:23 2017
 OS/Arch: linux/amd64

此时还没启动 docker daemon,只能看到客户端信息。

启动 docker

启动 docker daemon:

$ systemctl start docker

现在可以查看服务端信息:

$ docker version
Client:
 Version: 17.09.0-ce
 API version: 1.32
 Go version: go1.8.3
 Git commit: afdb6d4
 Built: Tue Sep 26 22:41:23 2017
 OS/Arch: linux/amd64

Server:
 Version: 17.09.0-ce
 API version: 1.32 (minimum version 1.12)
 Go version: go1.8.3
 Git commit: afdb6d4
 Built: Tue Sep 26 22:42:49 2017
 OS/Arch: linux/amd64
 Experimental: false
开机自启
$ systemctl enable docker
非 root 用户使用 docker
$ usermod -aG docker ${username}

$ systemctl daemon-reload
$ systemctl restart docker
更换 docker 镜像仓库

中科大源教程:http://mirrors.ustc.edu.cn/help/dockerhub.html

更新 /etc/docker/daemon.json 为:

{
    "registry-mirrors": ["https://docker.mirrors.ustc.edu.cn/"]
}
卸载 docker
$ yum remove docker-ce
$ rm -rf /var/lib/docker
Ubuntu 16.04

本节参考 https://docs.docker.com/install/linux/docker-ce/ubuntu/

卸载旧版(可选)

如果安装过较老版本的 docker 或 docker-engine,执行删除:

$ apt-get remove docker docker-engine docker.io
$ rm -rf /var/lib/docker
安装 docker-ce
使用 apt 安装
  1. 添加仓库

    $ apt-get update
    
    $ apt-get install \
          apt-transport-https \
          ca-certificates \
          curl \
          software-properties-common
    
    $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
    
    $ apt-key fingerprint 0EBFCD88
    
    $ add-apt-repository \
          "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
          $(lsb_release -cs) \
          stable"
    
  2. 安装

    安装最新版本:

    $ apt-get update
    $ apt-get install docker-ce
    

    安装特定版本:

    $ apt-cache madison docker-ce
    docker-ce | 18.06.1~ce~3-0~ubuntu | https://download.docker.com/linux/ubuntu xenial/stable amd64 Packages
    docker-ce | 18.06.0~ce~3-0~ubuntu | https://download.docker.com/linux/ubuntu xenial/stable amd64 Packages
    docker-ce | 18.03.1~ce-0~ubuntu | https://download.docker.com/linux/ubuntu xenial/stable amd64 Packages
    ......
    
    $ sudo apt-get install docker-ce=18.06.1~ce~3-0~ubuntu
    
  3. 升级

    先 update 系统,然后指定版本 install。参考上面安装。

使用 deb 安装

前往 https://download.docker.com/linux/ubuntu/dists/xenial/pool/stable/amd64/ 下载安装包,执行:

$ dpkg -i docker-ce_17.09.1~ce-0~ubuntu_amd64.deb

升级下载新 deb 包安装。

启动 docker
$ service docker start
开机自启

Warning

Unknown, to be continued…

非 root 用户使用 docker
$ usermod -aG docker ${username}

$ service docker restart
更换 docker 镜像仓库

参考 更换 docker 镜像仓库

卸载 docker
$ apt-get purge docker-ce
$ rm -rf /var/lib/docker

开启 Docker 远程访问

CentOS 7

Note

在 CentOS 中没有 /etc/default/docker,另外也没有找到 /etc/sysconfig/docker 这个配置文件。

/usr/lib/systemd/system/docker.service 配置远程访问,主要是在 [Service] 部分,加上下面两个参数:

[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix://var/run/docker.sock

重启 docker 服务

$ systemctl daemon-reload
$ systemctl restart docker

在另一台机器上使用命令检查是否成功:

$ docker -H ${your-IP} version

Docker 入门指南

本文介绍 Docker 的基本使用方法和常用命令,包括容器的创建、查看、停止和删除。通过这些操作可以实现单个容器的使用和应用部署。

至于 Docker 镜像构建、Dockerfile 书写和编配工具 Compose 的使用等进阶话题,移步 Docker 基础

安装和启动 Docker

安装和启动参考 Docker 安装教程

如果没有启动 docker,则需要手动启动 Docker 守护进程:

$ systemctl start docker
镜像和容器
_images/image-and-container.jpg

镜像 (Image)就是一堆只读层(read-only layer)的统一视角。

_images/docker-image.jpg

容器 (container)的定义和镜像(image)几乎一模一样,也是一堆层的统一视角,唯一区别在于容器的最上面那一层是可读可写的

_images/docker-container.jpg

详细的说明以及 Docker 命令的理解参考:Docker objectsDocker 容器和镜像的区别

运行第一个容器

Docker 操作的对象就是容器,具体操作包括创建、启动、查看、关闭、删除等。

启动一个容器:

$ docker run -it --name myubuntu ubuntu /bin/bash

其中:

  • docker run 为 Docker 命令;
  • -it--name 为命令参数,-i 表示开启容器 stdin-t 表示创建伪 tty 终端,只有指定这两个参数才能提供交互式 shell;--name 用于指定要创建容器的名字,这里取为 myubuntu,若不指定容器名称则自动生成随机名称。容器名称必须唯一!
  • ubuntu 指定创建容器所需要的镜像名,每个容器都依赖于镜像创建,镜像概念后续更新。
  • /bin/bash 指定了容器启动后执行的命令,这里表示启动 ubuntu 系统内的 bash。

不出意外,成功进入 ubuntu:

root@22c17371eeae:/#

Note

Docker 命令的使用帮助:

  1. 查看 Docker 支持的命令 docker --help
  2. 查看具体命令的使用 docker run --help
容器的使用

上面已经成功以 root 用户启动容器 myubuntu 并进入 bash 终端,这是一个完整的 ubuntu 系统,可以用来做任何事情。其中容器的 ID 为 22c17371eeae 同时也是容器的主机名。

root@22c17371eeae:/# hostname
22c17371eeae

我们可以尝试在系统内安装 vim:

root@22c17371eeae:/# apt update -y && apt install -y vim
Get:1 http://archive.ubuntu.com/ubuntu xenial InRelease [247 kB]
Get:2 http://security.ubuntu.com/ubuntu xenial-security InRelease [107 kB]
Get:3 http://security.ubuntu.com/ubuntu xenial-security/universe Sources [87.2 kB]
...

再看看看容器的进程:

root@22c17371eeae:/# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 12:04 pts/0    00:00:00 /bin/bash
root       423     1  0 12:12 pts/0    00:00:00 ps -ef

你可以继续做任何想做的,如安装 python3, 部署 flask …,甚至 rm -rf / o(∩_∩)o 。不用担心,创建容器是 docker run 那么容易。

现在是时候退出了,输入 exit 并回车(或者 Ctrl-D):

root@22c17371eeae:/# exit
exit
$

一旦退出容器,容器也随之停止(如果不想容器退出就停止,参考下面 创建后台容器)。我们又回到了主机,初次的体验完毕。

查看容器

退出容器回到宿主机,容器虽然停止,但是仍然存在,可以再次使用。

使用 docker ps 查看当前系统的容器列表(-a 显示所有容器,包括运行和停止的,不加则只显示运行的)

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
22c17371eeae        ubuntu              "/bin/bash"         12 minutes ago      Exited (0) 3 minutes ago                       myubuntu

其中:

  • CONTAINER ID,容器 id
  • IMAGE,创建容器使用的镜像
  • COMMAND,容器最后执行的命令
  • CREATED,容器的创建时间
  • STATUS,容器的退出状态
  • PORTS,容器和主机的端口映射
  • NAMES,容器名称

Tip

docker inspect 可以查看容器的配置信息,如名称、网络等。

重新启动已停止的容器

上面使用 exit 退出了容器,容器也随之停止。也许以后某个时候,我们还需要再次使用(当然可以再创建一个新的,这样的话,不用的容器记得删除,删除方法见本文最后),则可以使用 docker start 重新启动停止的容器:

$ docker start myubuntu

一般的,容器名和容器 ID 可以替换使用,如上面操作也可以用:

$ docker start 22c17371eeae

Tip

不需要使用完整的 ID,开始的 3 - 4 位足矣!

类似相关命令有:docker restart, docker create,如何学习?docker --help

有没有发现执行了上述命令,好像没有看到什么变化? docker ps 一下:

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
22c17371eeae        ubuntu              "/bin/bash"         16 minutes ago      Up 3 seconds                            myubuntu

注意 STATUS,已经显示为 UP

那么问题来了,如何再次进入终端呢?有两种方法(科普一下茴香豆的茴有4种写法:茴 ,回,囘,囬):

  1. 附着到容器(docker attach 执行后需要 Enter 直到进入)
$ docker attach myubuntu
  1. 在容器内重新运行新进程(docker exec 登场)
$ docker exec -it myubuntu /bin/bash

在第二种方法中,我们新启动了 bash 进程:

root@22c17371eeae:/# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 12:20 pts/0    00:00:00 /bin/bash
root        11     0  0 12:23 pts/1    00:00:00 /bin/bash
root        21    11  0 12:23 pts/1    00:00:00 ps -ef

Attention

注意上面操作都要求容器先启动!

同样,exit 退出。

创建后台容器

前面创建的容器都是交互式运行的,更多时候我们需要创建长期运行的容器来运行服务,而且不需要交互式会话(比如数据库)。

容器创建仍旧使用 docker run 命令,但这次使用 -d 参数:

$ docker run --name myredis -d -p 9527:6379 redis

这里我使用 redis 镜像 并添加 -d 将任务放到了后台,所以执行命令后仍然回到主机而没有进入容器。至于 -p 参数,用于容器与主机的端口映射(上面例子将容器内 redis 端口映射到主机的 9527),这次 docker ps 可以看到 PORTS 有了内容。

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
e88992f6a3e0        redis               "docker-entrypoint.s…"   5 seconds ago       Up 4 seconds        0.0.0.0:9527->6379/tcp   myredis

Tip

查看某容器的端口可直接使用命令 docker port

$ docker port myredis
6379/tcp -> 0.0.0.0:9527

通过 docker ps 可以看到容器已经成功在后台运行,如何与该容器交互使用呢?比如这里如何连接上 redis?其实很简单,只要使用主机的 ip 和 容器映射的主机端口就可以正常使用。

$ redis-cli -h ${your-IP} -p 9527

如果想研究容器内部都做了些什么,可以使用 docker logs 来读取容器日志。命令可以添加参数 -f 进行滚动查看(效果类似 tail -f),参数 -t 为 每条日志添加时间戳。退出日志跟踪使用 Ctrl-C

$ docker logs -ft myredis
2018-08-13T12:25:28.424333397Z 1:C 13 Aug 12:25:28.423 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
2018-08-13T12:25:28.424390765Z 1:C 13 Aug 12:25:28.424 # Redis version=4.0.9, bits=64, commit=00000000, modified=0, pid=1, just started
...

如果想进入容器,使用上面的 docker exec 命令。

更多容器状态检测命令,查阅:docker top , docker stats

停止后台容器

只需要执行 docker stop 命令:

$ docker stop myredis

还有个命令 docker kill 直接向容器发送 SIGKILL 信号停止容器。

删除容器

如果容器不再使用,可以使用 docker rm 删除。

$ docker rm myredis

Tip

若要删除运行中的容器,使用 -f 参数。但最好先 stop 然后 rm。

若要删除所有容器:

$ docker rm ``docker ps -aq``
e88992f6a3e0
22c17371eeae
总结

Docker 最基本常用的命令就这些:

  • docker run 启动容器;
  • docker ps 查看容器状态;
  • docker stop 停止容器;
  • docker rm 删除容器。

有了 Docker,你可以通过 docker search 查询镜像仓库是否已经有人做好相关镜像,如果有则直接 docker run 创建容器使用,否则需要自己构建镜像。作为入门篇,这里就不多说了。

Docker 最直观的好处,就是可以无痛快速尝试新的技术,这些往往别人已经做好镜像配置好环境(对于学习这些环境足够了),初学者不必自己去处理繁琐的运维配置。而且启动容器的代价非常小,资源隔离保证主机安全下满足好奇心,折腾坏了直接删除新建一个就可以。

Dockerfile 参考手册

第一个 Dockerfile

创建一个名为 web 的目录并在里面创建初始的 Dockerfile,这个目录就是我们的构建环境(build environment),Docker 称此环境为**上下文**(context)。Docker 会在构建镜像时将上下文和其中的文件及目录上传到 Docker 守护进程,这样守护进程就能直接访问用户想在镜像中存储的任何代码、文件或数据。

Tip

如果在构建镜像时不想把某些目录或文件(如数据目录)上传到 docker daemon,可以添加进 .dockerignore 进行忽略。具体语法参见 .dockerignore file

FROM ubuntu:16.04
RUN apt-get update && apt-get install -y nginx
RUN echo 'Hi, I am in your container' > /usr/share/nginx/html/index.html
EXPOSE 80

Dockerfile 由一系列指令和参数组成,指令会按顺序从上到下执行(所以需要合理安排指令的顺序)。

每条指令都会创建一个新的镜像层并提交,大致执行流程:

  1. 从基础镜像运行一个容器
  2. 执行一条指令,并对容器做出修改
  3. 提交新的镜像层
  4. 再基于刚提交的镜像运行一个新的容器
  5. 执行下一条指令,直到所有指令执行完毕

可以看出,如果 Dockerfile 由于某些原因(如某条指令失败)没有正常结束,也能得到一个可以使用的镜像,这对调试非常有帮助。

使用 Dockerfile 构建新镜像

在上下文环境中(这里即 web 目录内)执行 docker build

$ docker build -t "datascience/web" .

其中,-t 选项指定了新镜像的仓库和镜像名,方便追踪和管理。此外,可以同时为镜像设置标签(不指定默认为 latest ),如:

$ docker build -t "datascience/web:v1" .

Attention

上面构建命令的最后有个 .,用于告诉 Docker 到本地目录中去找 Dockerfile。也可以通过 -f 选项指定 Dockerfile 位置(文件名可以不为 Dockerfile),如:

$ docker build -t "datascience/web:v1"  -f path/to/dockerfile

Tip

由于每一步构建过程都将结果提交为镜像,所以可将之前的镜像层看做缓存。如本例如果新的应用只修改了 Dockerfile 的第 4 步,则构建时直接从第 4 步开始。需要忽略缓存构建可使用选项 --no-cache

$ docker build --no-cache -t "datascience/web" .

以上的镜像名称的引号可以省略。

查看新镜像
$ docker images

如果想了解镜像构建的流程,可使用 docker history,可以看到镜像的每一层和创建该层的指令。

从新镜像启动容器
$ docker run -d -p 80:80 --name web datascience/web nginx -g "daemon off;"

docker run 的使用方法参见:Docker 入门指南

下面介绍 Dockerfile 指令:

FROM
syntax:

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

每个 Dockerfile 的第一条指令必须是 FROM ,用于为后续指令指定基础镜像(base image)。Dockerfile 中可以包含多条 FROM (多层级构建),这样会在构建时产出多个镜像,或者使其中一些作为其他构建的依赖。

参数 tagname 是可选的,不指定 tag 默认使用 latestname 在多层级构建时会有用。

RUN
syntax:

    RUN <command>                             # shell form, the command is run in a shell,
                                              # default is `/bin/sh -c`
    RUN ["executable", "param1", "param2"]    # exec form, using `"`

RUN 执行命令并创建新的镜像层,经常用于安装软件包。每条 RUN 指令都会创建一个新的镜像层。

在 shell 模式下,默认使用 sh,也可以自己指定:

RUN /bin/bash -c 'echo hello'

同样的操作,在 exec 模式下为:

RUN ["/bin/bash", "-c", "echo hello"]
EXPOSE
syntax:

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

EXPOSE 通知 Docker 该容器运行时监听的网络端口,可以设置 TCP 或 UDP 协议,默认使用 TCP。

EXPOSE 80/tcp
EXPOSE 80/udp

EXPOSE 是镜像构建者和使用者之间关于端口映射的桥梁,实际使用时需要使用 -p 选项映射宿主机与容器对应端口:

docker run -p 80:80/tcp -p 80:80/udp ...
CMD
syntax:

    CMD command param1 param2               # shell form
    CMD ["executable","param1","param2"]    # exec form
    CMD ["param1","param2"]                 # as default params to ENTRYPOINT

CMD 用于指定一个容器启动时要运行的命令或参数,,类似于 RUN,区别在于:

  • RUN 是指定镜像构建时要运行的命令;
  • CMD 指定容器启动时要运行的命令或参数。

Attention

  1. 使用 docker run 命令可以覆盖 CMD 指令。
  2. 在 Dockerfile 中只能指定一条 CMD 指令,多条只有最后一条生效。
LABEL
syntax:

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

LABEL 用于为镜像添加元数据,元数据以键值对展现。

LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."

Note

  1. 标签会从基础镜像中继承,如果更新了同名变量值,则覆盖父镜像。

  2. docker inspect 可以查看镜像中的标签信息。

  3. MAINTAINER 指令已经废弃,改为使用标签:

    LABEL maintainer="SvenDowideit"
    
ENV
syntax:

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

ENV 用于在镜像构建过程中设置环境变量,环境变量被持久保存在镜像创建的任何容器中,可以在后续指令中使用。

ENV TARGET_DIR /opt/app
ENV RVM_PATH=/home/rvm RVM_ARCHFLAGS="-arch_i486"
WORKDIR $TARGET_DIR

Note

docker run 选项 --env/-e 可以运行时传递环境变量,但只会在运行时有效。

ADD
syntax:

    ADD [--chown=<user>:<group>] <src>... <dest>
    ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]    # if path contains whitespace

ADD 用来将构建环境下的文件和目录添加到镜像中。

<src> 支持通配符筛选文件:

ADD hom* /mydir/
ADD hom?.txt /mydir/

新添加的文件或目录默认使用 UID=0, GID=0(即 root),可以使用 --chown 指定用户和用户组(username/groupname 或 UID/GID,使用名称时必须确保已存在于宿主机的 /etc/passwd/etc/group,UID/GID 则无此要求):

ADD --chown=55:mygroup files* /somedir/
ADD --chown=bin files* /somedir/
ADD --chown=1 files* /somedir/
ADD --chown=10:11 files* /somedir/

Note

  1. <src> 必须位于上下文(指构建目录本身,但内部目录可以)之中;
  2. <src> 为目录时,目录下所有内容将被复制,而不包含目录本身;
  3. <src> 为本地压缩文件时,ADD 会自动解压 gzip、bzip2、xz;
  4. <src> 也可以为网络资源,若为压缩文件不会被解压;
  5. <dest> 可以是相对路径(相对 WORKDIR )或绝对路径;
  6. 如果 <src> 为多个文件,要求 <dest> 为目录(以 / 结尾);
  7. 如果 <dest> 不存在,会自动创建缺失的目录。
COPY
syntax:

    COPY [--chown=<user>:<group>] <src>... <dest>
    COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]    # if path contains whitespace

COPY 类似 ADD (相应规则参考 ADD ),但不会对文件解压。同样如果目的位置不存在会自动创建。

COPY conf.d/ /etc/apache2/
ENTRYPOINT
syntax:

    ENTRYPOINT ["executable", "param1", "param2"]    # exec form, preferred
    ENTRYPOINT command param1 param2                 # shell form

ENTRYPOINT 用于执行指令,和 CMD 非常类似,但 ENTRYPOINT 提供的命令不会在容器启动时被覆盖!实际上,docker run 指定的任何参数都会被当做参数传递给 ENTRYPOINT 指令中指定的命令。

ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]

可以组合使用 ENTRYPOINTCMD 来完成一些巧妙的工作(如果启动容器时没有指定参数,则在 CMD 中指定的 -h 会被传递给 Nginx):

ENTRYPOINT ["/usr/sbin/nginx"]
CMD ["-h"]

Note

docker run 选项 --entrypoint 可以覆盖 ENTRYPOINT 指令。

VOLUME
syntax:

    VOLUME ["dir1", "dir2", ...]

VOLUME 用来向基于镜像创建的容器添加卷(挂载点)。一个卷可以存在于一个或多个容器内特定的目录,这个目录可以绕过联合文件系统,用于数据共享或数据持久化。

卷功能让我们可以将数据(如源码)、数据库或者其他内容挂载到镜像中,而不是提交到镜像中,并且运行在多个容器间共享这些内容。

Note

docker run 选项 -v 用于在运行时挂载本地目录到容器内挂载点。

docker cp 允许从容器复制文件和复制文件到容器上。

USER
syntax:

    USER <user>[:<group>]
    USER <UID>[:<GID>]

USER 用于指定镜像以什么用户去运行,如果不指定默认为 root:root。

USER user
USER user:group
USER uid
USER uid:gid
USER user:gid
USER uid:group

Note

docker run 选项 -u 可在运行时覆盖 USER

WORKDIR

WORKDIR 创建新容器时,在容器内部设置一个工作目录,RUN, CMD, ENTRYPOINT, COPYADD 指令会在这个目录下执行。

WORKDIR /opt/webapp/db
RUN bundle install
WORKDIR /opt/webapp
ENTRYPOINT ["rackuo"]

Note

  1. WORKDIR 可以使用相对路径或绝对路径;
  2. 路径不存在时自动创建;
  3. WORKDIR 可以使用 ENV 指定的环境变量;
  4. docker run 选项 -w 可在运行时覆盖工作目录。
ARG
syntax:

    ARG <name>[=<default value>]

ARG 用来定义可以在 docker build 运行时传递的变量。只需要在构建时使用 --build-arg。当指定 Dockerfile 中未定义过的参数时,会输出警告(未定义说明在 Dockerfile 中不会使用)。

ARG user
ARG buildno=1

$ docker build --build-arg user=someuser -t datascience/web .

ARG 变量的作用域从声明开始:

  • 示例一

    FROM busybox
    USER ${user:-some_user}
    ARG user
    USER $user
    
    $ docker build --build-arg user=aaa .
    

    user 的值分别为:some_user, aaa, aaa

  • 示例二

    FROM ubuntu
    ARG CONT_IMG_VER
    ENV CONT_IMG_VER v1.0.0
    RUN echo $CONT_IMG_VER
    
    $ docker build --build-arg CONT_IMG_VER=v2.0.1 .
    

    CONT_IMG_VER 值分别为 v2.0.1, v1.0.0, v1.0.0

ARG 只在当前构建层级(前一个 FROM 和后一个 FROM 之间,服务于前一个 FROM)生效,如果多级构建都需要变量则需要分别设置:

FROM busybox
ARG SETTINGS
RUN ./run/setup $SETTINGS

FROM busybox
ARG SETTINGS
RUN ./run/other $SETTINGS

Note

Docker 预定义的 ARG 变量:

  • HTTP_PROXY
  • http_proxy
  • HTTPS_PROXY
  • https_proxy
  • FTP_PROXY
  • ftp_proxy
  • NO_PROXY
  • no_proxy
STOPSIGNAL
syntax:

    STOPSIGNAL signal

STOPSIGNAL 用来设置停止容器时发送什么系统调用信号给容器,如 SIGKILL

ONBUILD
syntax:

    ONBUILD [INSTRUCTION]

ONBUILD 为镜像添加触发器,广泛用在制作基础镜像:用构建的镜像创建其他镜像时,才会触发 ONBUILD 的命令。

当我们在一个 Dockerfile 文件中加上 ONBUILD 指令,该指令对利用该 Dockerfile 构建镜像(比如 image A)不会产生实质性影响。

但是当我们编写一个新的 Dockerfile 文件来基于A镜像构建一个镜像(比如 image B)时,这时构造 A 镜像的 Dockerfile 文件中的 ONBUILD 指令就生效了,在构建 B 镜像的过程中,首先会执行 ONBUILD 指定的指令,然后才会执行其它指令(如 FROM)。

...
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
...

Warning

  1. ONBUILD 指定的指令不能是 ONBUILDFROM
  2. 触发器无法被继承,imageA -> imageB -> imageC 时,C 不会执行 A 中的触发器。
HEALTHCHECK
syntax:

    HEALTHCHECK [OPTIONS] CMD command    # check health by running a command inside the container
    HEALTHCHECK NONE                     # disable any healthcheck inherited from the base image

HEALTHCHECK 指令声明了健康检测命令,用这个命令来判断容器主进程的服务状态是否正常,从而比较真实的反应容器实际状态。

CMD 之前可选项有:

  • --interval=DURATION (default: 30s),两次健康检查的间隔;
  • --timeout=DURATION (default: 30s),健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败;
  • --start-period=DURATION (default: 0s),应用启动的初始化时间,在启动过程中的健康检查失效不会计入;
  • --retries=N (default: 3),当连续失败指定次数后,则将容器状态视为 unhealthy。

在 HEALTHCHECK [选项] CMD 后面命令的返回值决定了该次健康检查的成功与否:

HEALTHCHECK --interval=5m --timeout=3s \
  CMD curl -f http://localhost/ || exit 1

容器启动之后,初始状态会为 starting (启动中)。Docker Engine会等待 interval 时间,开始执行健康检查命令,并周期性执行。如果单次检查返回值非0或者运行需要比指定 timeout 时间还长,则本次检查被认为失败。如果健康检查连续失败超过了 retries 重试次数,状态就会变为 unhealthy (不健康)。

Note

  1. 在 Dockerfile 中 HEALTHCHECK 只可以出现一次,如果写了多个,只有最后一个生效。
  2. 一旦有一次健康检查成功,Docker会将容器置回 healthy (健康)状态
  3. 当容器的健康状态发生变化时,Docker Engine会发出一个 health_status 事件。
SHELL
syntax:

    SHELL ["executable", "parameters"]

SHELL 用于设置默认 shell。

Compose 入门指南

Compose is a tool for defining and running multi-container Docker applications.

Docker Compose 是 Docker 的编配工具,用于完成自动配置、协作和管理服务等过程。

Compose 安装

因为 docker-compose 使用 Python 编写,所以可以使用 pip 直接安装:

$ pip install -U docker-compose

Tip

docker-compose 命令较长,可以使用 alias 指定别名:

# .bashrc
alias dc="docker-compose"

使用 docker-compose -h 可以查看命令帮助,常用命令:

  • build
  • logs
  • ps
  • rm
  • stop
  • up
运行 Compose

这里同时启动 8 个 spider 服务:

$ docker-compose up -d --scale spider=8
Compose 命令

使用命令 docker-compose --help 可查看命令工具支持的命令和参数,docker-compose help [COMMAND] 查看命令详情。

Define and run multi-container applications with Docker.

Usage:
  docker-compose [-f <arg>...] [options] [COMMAND] [ARGS...]
  docker-compose -h|--help

Options:
  -f, --file FILE            Specify an alternate Compose file (default: docker-compose.yml)
  -p, --project-name NAME    Specify an alternate project name (default: directory name)
  --verbose                  Show more output
  --no-ansi                  Do not print ANSI control characters
  -v, --version              Print version and exit
  -H, --host HOST            Daemon socket to connect to

  --tls                      Use TLS; implied by --tlsverify
  --tlscacert CA_PATH        Trust certs signed only by this CA
  --tlscert CLIENT_CERT_PATH  Path to TLS certificate file
  --tlskey TLS_KEY_PATH      Path to TLS key file
  --tlsverify                Use TLS and verify the remote
  --skip-hostname-check      Don't check the daemon's hostname against the name specified
                              in the client certificate (for example if your docker host
                              is an IP address)
  --project-directory PATH    Specify an alternate working directory
                              (default: the path of the Compose file)

Commands:
  build              Build or rebuild services
  bundle            Generate a Docker bundle from the Compose file
  config            Validate and view the Compose file
  create            Create services
  down              Stop and remove containers, networks, images, and volumes
  events            Receive real time events from containers
  exec              Execute a command in a running container
  help              Get help on a command
  images            List images
  kill              Kill containers
  logs              View output from containers
  pause              Pause services
  port              Print the public port for a port binding
  ps                List containers
  pull              Pull service images
  push              Push service images
  restart            Restart services
  rm                Remove stopped containers
  run                Run a one-off command
  scale              Set number of containers for a service
  start              Start services
  stop              Stop services
  top                Display the running processes
  unpause            Unpause services
  up                Create and start containers
  version            Show the Docker-Compose version information

其中,最常用的命令为:

build

build 根据 docker-compose 文件指定的镜像和选项构建镜像,如果事先使用 Dockerfile 编译好镜像则无需 build,直接使用 up 启动。

$ docker-compose build
up

类似 Docker,Compose 使用 up 启动容器,使用制定的参数来执行,并将所有的日志输出合并到一起。

$ docker-compose up

如果启动时指定 -d 标志,则以守护进程模式运行服务。

$ docker-compose up -d

如果要批量启动服务(如启动 8 个 Scrapy),则在 --scale 选项指定服务的个数:

$ docker-compose up -d --scale spider=8
ps

ps 列出本地 docker-compose.yml 文件里定义的正在运行的所有服务。

$ docker-compose ps
logs

logs 查看服务的日志,这个命令会追踪服务的日志文件,类似 tail -f 命令,使用 Ctrl+C 退出。

$ docker-compose logs
stop

stop 停止所有服务,如果服务没有停止,可以使用 docker-compose kill 强制杀死服务。

$ docker-compose stop
rm

rm 删除所有服务。

$ docker-compose rm
一个例子

最后,以一个官方 docker-compose.yml 结束:

version: "3"
services:

  redis:
    image: redis:alpine
    ports:
      - "6379"
    networks:
      - frontend
    deploy:
      replicas: 2
      update_config:
        parallelism: 2
        delay: 10s
      restart_policy:
        condition: on-failure
  db:
    image: postgres:9.4
    volumes:
      - db-data:/var/lib/postgresql/data
    networks:
      - backend
    deploy:
      placement:
        constraints: [node.role == manager]
  vote:
    image: dockersamples/examplevotingapp_vote:before
    ports:
      - 5000:80
    networks:
      - frontend
    depends_on:
      - redis
    deploy:
      replicas: 2
      update_config:
        parallelism: 2
      restart_policy:
        condition: on-failure
  result:
    image: dockersamples/examplevotingapp_result:before
    ports:
      - 5001:80
    networks:
      - backend
    depends_on:
      - db
    deploy:
      replicas: 1
      update_config:
        parallelism: 2
        delay: 10s
      restart_policy:
        condition: on-failure

  worker:
    image: dockersamples/examplevotingapp_worker
    networks:
      - frontend
      - backend
    deploy:
      mode: replicated
      replicas: 1
      labels: [APP=VOTING]
      restart_policy:
        condition: on-failure
        delay: 10s
        max_attempts: 3
        window: 120s
      placement:
        constraints: [node.role == manager]

  visualizer:
    image: dockersamples/visualizer:stable
    ports:
      - "8080:8080"
    stop_grace_period: 1m30s
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
    deploy:
      placement:
        constraints: [node.role == manager]

networks:
  frontend:
  backend:

volumes:
  db-data:

Compose file 参考手册

Note

详细 Compose file 选项参见:Compose file version 3 reference

第一个 Compose file
version: '3'
services:
  spider:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - $PWD:/code
      - /data1/datascience/scrapy-data:/data
    command: scrapy crawl comment

说明:

  • version Compose file 的版本,最新的版本为 3.x;
  • services 定义服务,这里定义了一个爬虫服务 spider;
  • build 构建镜像上下文、Dockerfile 文件和 ARG 等;
  • volumes 用于创建卷并挂载,这里挂载了源码目录和数据存储目录;
  • command 服务启动时执行的命令。

Compose file 通过 YAML 文件定义服务(services),网络(networks)和卷(volumes):

version: "3"
services:
  web:
    # replace username/repo:tag with your name and image details
    image: username/repo:tag
    deploy:
      replicas: 5
      restart_policy:
        condition: on-failure
      resources:
        limits:
          cpus: "0.1"
          memory: 50M
    ports:
      - "80:80"
    networks:
      - webnet
  visualizer:
    image: dockersamples/visualizer:stable
    ports:
      - "8080:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
    deploy:
      placement:
        constraints: [node.role == manager]
    networks:
      - webnet
  redis:
    image: redis
    ports:
      - "6379:6379"
    volumes:
      - "/home/docker/data:/data"
    deploy:
      placement:
        constraints: [node.role == manager]
    command: redis-server --appendonly yes
    networks:
      - webnet
networks:
  webnet:
services

每个 service 定义了本服务启动时传递给容器的配置信息,类似于命令行操作 docker container create

docker container create 一样,已经在 Dockerfile 中指定的选项(如 CMD, EXPOST, VOLUME, ENV)不需要重复设置,会作为默认值使用。

build

构建镜像时配置选项。

服务除了可以基于指定的镜像,还可以基于一份 Dockerfile,在使用 up 启动之时执行构建任务,这个构建标签就是 build,它可以指定 Dockerfile 所在文件夹的路径。Compose 将会利用它自动构建这个镜像,然后使用这个镜像启动服务容器。

version: '3'
services:
  webapp:
    build: ./dir

如果需要更细粒度的配置,需要使用 context, dockerfile, args, labels 等选项。

version: '3'
services:
  webapp:
    build:
      context: ./dir
      dockerfile: Dockerfile-alternate
      args:
        buildno: 1

在执行镜像构建时,Compose 通过 image 指定构建出的镜像名称:

build: ./dir
image: webapp:tag

Note

使用 Swarm 部署服务时会忽略 build 选项,docker stack 只接受预先构建好的镜像。

context

构建上下文,可以是本地目录或者 git 仓库 URL。

build:
  context: ./dir
dockerfile

使用此 Dockerfile 文件来构建,必须指定构建路径。

build:
  context: .
  dockerfile: Dockerfile-alternate
args

添加构建参数,这些参数是仅在构建过程中可访问的环境变量。

首先, 在 Dockerfile 中指定参数:

ARG buildno
ARG gitcommithash

RUN echo "Build number: $buildno"
RUN echo "Based on commit: $gitcommithash"

然后指定 build 下的参数,可以传递映射或列表:

build:
  context: .
  args:
    buildno: 1
    gitcommithash: cdc3b19

也可以使用 Compose 构建时手动传入,此时不用设置默认值:

args:
  - buildno
  - gitcommithash

Note

YAML 的布尔值(true/false, yes/no, on/off)必须使用引号括起来才能准确解析为字符串。

cache_from

用于指定缓存解析镜像列表。

build:
  context: .
  cache_from:
    - alpine:latest
    - corp/web_app:3.14
labels

使用 Docker 标签将元数据添加到生成的镜像中,可以使用数组或字典。建议使用反向 DNS 标记来防止签名与其他软件所使用的签名冲突。

build:
  context: .
  labels:
    com.example.description: "Accounting webapp"
    com.example.department: "Finance"
    com.example.label-with-empty-value: ""

build:
  context: .
  labels:
    - "com.example.description=Accounting webapp"
    - "com.example.department=Finance"
    - "com.example.label-with-empty-value"
shm_size

用于设置 /dev/shm 分区大小,值为表示字节的整数值或表示字符的字符串,使用数值时单位 byte。

build:
  context: .
  shm_size: '2gb'


build:
  context: .
  shm_size: 10000000

进一步了解 /dev/shm

target

在多层级镜像构建时,用于构建指定镜像:

# Dockerfile
FROM golang:1.7.3 as builder
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go    .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]

# docker-compose.yml
build:
  context: .
  target: builder
cap_add, cap_drop

增加或删除容器系统功能。默认情况下,docker 的容器中的 root 的权限是有严格限制的,比如,网络管理(NET_ADMIN)等很多权限都是没有的。

cap_add:
  - ALL

cap_drop:
  - NET_ADMIN
  - SYS_ADMIN

进一步阅读 capabilities

Note

cap_addcap_drop 在 Swarm 部署时被忽略。

command

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

command: bundle exec thin -p 3000

command: ["bundle", "exec", "thin", "-p", "3000"]
config

使用服务 configs 配置为每个服务赋予相应的访问权限,支持两种不同的语法。

Note

配置必须存在或在 configs 此堆栈文件的顶层中定义,否则堆栈部署失效。

SHORT syntax

SHORT 语法只能指定配置名称,这允许容器访问配置并将其安装在 /<config_name> 容器内,源名称和目标装入点都设为配置名称。

version: "3.3"
services:
  redis:
    image: redis:latest
    deploy:
      replicas: 1
    configs:
      - my_config
      - my_other_config
configs:
  my_config:
    file: ./my_config.txt
  my_other_config:
    external: true

以上实例使用 SHORT 语法将 redis 服务访问授予 my_configmy_other_config ,并被 my_other_config 定义为外部资源,这意味着它已经在 Docker 中定义。可以通过 docker config create 命令或通过另一个堆栈部署。如果外部部署配置都不存在,则堆栈部署会失败并出现 config not found 错误。

LONG syntax

LONG 语法提供了创建服务配置的更加详细的信息

  • source:Docker 中存在的配置的名称
  • target:要在服务的任务中装载的文件的路径或名称。如果未指定则默认为 /<source>
  • uidgid:在服务的任务容器中拥有安装的配置文件的数字 UID 或 GID。如果未指定,则默认为在 Linux 上,Windows 不支持。
  • mode:在服务的任务容器中安装的文件的权限,以八进制表示法。例如,0444 代表文件可读的。默认是 0444。如果配置文件无法写入,是因为它们安装在临时文件系统中,所以如果设置了可写位,它将被忽略。可执行位可以设置。

下面示例在容器中将 my_config 名称设置为 redis_config,将模式设置为 0440``(group-readable)并将用户和组设置为 ``103。该 redis 服务无法访问 my_other_config 配置。

version: "3.3"
services:
  redis:
    image: redis:latest
    deploy:
      replicas: 1
    configs:
      - source: my_config
        target: /redis_config
        uid: '103'
        gid: '103'
        mode: 0440
configs:
  my_config:
    file: ./my_config.txt
  my_other_config:
    external: true

可以同时授予多个配置的服务相应的访问权限,也可以混合使用 LONG 和 SHORT 语法。定义配置并不意味着授予服务访问权限。

cgroup_parent

可以为容器选择一个可选的父 cgroup。

cgroup_parent: m-executor-abcd

Note

cgroup_parent 在 Swarm 模式下无效。

container_name

为自定义的容器指定一个名称,而不是使用默认的名称。因为 docker 容器名称必须是唯一的,所以如果指定了一个自定义的名称,不能扩展一个服务超过 1 个容器。

container_name: my-web-container

Note

container_name 在 Swarm 模式中无效。

deploy

指定与部署和运行服务相关的配置,只在 Swarm 模式下生效,在 docker-compose up/run 时无效。

version: '3'
services:
  redis:
    image: redis:alpine
    deploy:
      replicas: 6
      update_config:
        parallelism: 2
        delay: 10s
      restart_policy:
        condition: on-failure
endpoint_mode

指定连接到群组外部客户端服务发现方法,支持两种模式:

  • endpoint_mode:vip (virtual IP),默认模式,Docker 为该服务分配了一个虚拟 IP,作为客户端的 “前端” 部位用于访问网络上的服务;
  • dnsrr (DNS round-robin),DNS 轮询服务发现不使用单个虚拟 IP。Docker 为服务设置 DNS 条目,使得服务名称的 DNS 查询返回一个 IP 地址列表,并且客户端直接连接到其中的一个。如果想使用自己的负载平衡器,或者混合 Windows 和 Linux 应用程序,则 DNS 轮询调度(round-robin)功能就非常实用。
version: "3.3"

services:
  wordpress:
    image: wordpress
    ports:
      - "8080:80"
    networks:
      - overlay
    deploy:
      mode: replicated
      replicas: 2
      endpoint_mode: vip

  mysql:
    image: mysql
    volumes:
       - db-data:/var/lib/mysql/data
    networks:
       - overlay
    deploy:
      mode: replicated
      replicas: 2
      endpoint_mode: dnsrr

volumes:
  db-data:

networks:
  overlay:
labels

指定服务的标签,这些标签仅在服务上设置(而非服务的容器上)。

version: "3"
services:
  web:
    image: web
    deploy:
      labels:
        com.example.description: "This label will appear on the web service"

通过将 deploy 外面的 labels 标签来设置容器上的 labels

version: "3"
services:
  web:
    image: web
    labels:
      com.example.description: "This label will appear on all containers for the web service"
mode
  • global:每个集节点只有一个容器
  • replicated:指定容器数量(默认)
version: '3'
services:
  worker:
    image: dockersamples/examplevotingapp_worker
    deploy:
      mode: global
placement

指定 constraints 和 preferences,参考 docker service create :

version: '3.3'
services:
  db:
    image: postgres
    deploy:
      placement:
        constraints:
          - node.role == manager
          - engine.labels.operatingsystem == ubuntu 14.04
        preferences:
          - spread: node.labels.zone
replicas

如果服务是 replicated (默认),需要指定运行的容器数量:

version: '3'
services:
  worker:
    image: dockersamples/examplevotingapp_worker
    networks:
      - frontend
      - backend
    deploy:
      mode: replicated
      replicas: 6
resources

配置资源限制,类似 docker service create

version: '3'
services:
  redis:
    image: redis:alpine
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 50M
        reservations:
          cpus: '0.25'
          memory: 20M

此例子中,redis 服务限制使用不超过 50M 的内存和 0.50(50%)可用处理时间(CPU),并且 保留 20M 了内存和 0.25 CPU 时间

restart_policy

配置容器的重新启动,代替 restart

  • condition:值可以为 noneon-failure 以及 any (默认)
  • delay:尝试重启的等待时间,默认为 0
  • max_attempts:在放弃之前尝试重新启动容器次数(默认:从不放弃)。如果 max_attempts 值为 2,并且第一次尝试重新启动失败,则可能会尝试重新启动两次以上。
  • windows:在决定重新启动是否成功之前的等时间,指定为持续时间(默认值:立即决定)。
version: "3"
services:
  redis:
    image: redis:alpine
    deploy:
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
update_config

配置更新服务,用于无缝更新应用(rolling update)。

  • parallelism:一次性更新的容器数量,如果设置为 0,所有容器同时操作。
  • delay:更新一组容器之间的等待时间,默认 0s。
  • failure_action:如果更新失败,可以执行的的是 continuerollbackpause (默认)
  • monitor:每次任务更新后监视失败的时间( ns|us|ms|s|m|h )(默认为0)
  • max_failure_ratio:在更新期间能接受的失败率
  • order:更新次序设置,top-first (旧的任务在开始新任务之前停止)、start-first (新的任务首先启动,并且正在运行的任务短暂重叠)(默认 stop-first
rollback_config

服务升级失败时回滚配置。选项参考 update_config

Note

不支持 docker stack deploy 的子选项(支持 docker-compose up/run ):

  • build
  • cgroup_parent
  • container_name
  • devices
  • tmpfs
  • external_links
  • links
  • network_mode
  • restart
  • security_opt
  • stop_signal
  • sysctls
  • userns_mode
device

设置映射列表,与 Docker 客户端的 --device 参数类似 :

devices:
  - "/dev/ttyUSB0:/dev/ttyUSB0"

Note

device 在 Swarm 模式无效。

depends_on

此选项解决了启动顺序的问题。

在使用 Compose 时,最大的好处就是少打启动命令,但是一般项目容器启动的顺序是有要求的,如果直接从上到下启动容器,必然会因为容器依赖问题而启动失败。例如在没启动数据库容器的时候启动了应用容器,这时候应用容器会因为找不到数据库而退出,为了避免这种情况我们需要加入一个标签,就是 depends_on,这个标签解决了容器的依赖、启动先后的问题。

指定服务之间的依赖关系,有两种效果:

  • docker-compose up 以依赖顺序启动服务,下面例子中 redisdb 服务在 web 启动前启动
  • docker-compose up SERVICE 自动包含 SERVICE 的依赖性,下面例子中会先启动 redisdb 两个服务,最后才启动 web 服务:
version: '3'
services:
  web:
    build: .
    depends_on:
      - db
      - redis
  redis:
    image: redis
  db:
    image: postgres

注意的是,默认情况下使用 docker-compose up web 这样的方式启动 web 服务时,也会启动 redisdb 两个服务,因为在配置文件中定义了依赖关系。

Note

depends_on 在 Swarm 模式无效。

dns

自定义 DNS 服务器,与 --dns 具有一样的用途,可以是单个值或列表。

dns: 8.8.8.8
dns:
  - 8.8.8.8
  - 9.9.9.9
tmpfs

挂载临时文件目录到容器内部,size 以 bytes 指定大小,默认无限制。

- type: tmpfs
     target: /app
     tmpfs:
       size: 1000
entrypoint

在 Dockerfile 中有一个指令叫做 ENTRYPOINT 指令,用于指定接入点。在 docker-compose.yml 中可以定义接入点,覆盖 Dockerfile 中的定义:

entrypoint: /code/entrypoint.sh

entrypoint 也可以是一个列表:

entrypoint:
    - php
    - -d
    - zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20100525/xdebug.so
    - -d
    - memory_limit=-1
    - vendor/bin/phpunit

Note

entrypoint 会覆盖 ENTRYPOINT 并忽略 CMD

env_file

从文件中添加环境变量,可以是单个值或是列表。

如果已经用 docker-compose -f FILE 指定了 Compose 文件,那么 env_file 路径值为相对于该文件所在的目录。

environment 环境中的设置的变量会会覆盖这些值,无论这些值为空还是未定义。

env_file: .env

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

环境配置文件 env_file 中的声明每行都是以 VAR=VAL 格式,其中以 # 开头的被解析为注释而被忽略:

# Set Rails/Rack environment
RACK_ENV=development

Note

如果在配置文件中有 build 操作,环境配置文件中的变量并不会进入构建过程中,如果要在构建中使用变量还是首选 arg 标签。

以下例子中,$VAR 的值为 hello

services:
  some-service:
    env_file:
      - a.env
      - b.env

# a.env
VAR=1

# b.env
VAR=hello
environment

添加环境变量,可以使用数组或字典。与上面的 env_file 选项完全不同,反而和 arg 有几分类似,这个标签的作用是设置镜像变量,它可以保存变量到镜像里面,也就是说启动的容器也会包含这些变量设置,这是与 arg 最大的不同。

一般 arg 标签的变量仅用在构建过程中。而 environment 和 Dockerfile 中的 ENV 指令一样会把变量一直保存在镜像、容器中,类似 docker run -e 的效果

environment:
  RACK_ENV: development
  SHOW: 'true'
  SESSION_SECRET:

environment:
  - RACK_ENV=development
  - SHOW=true
  - SESSION_SECRET

Note

如果在配置文件中有 build 操作,environment 定义的变量并不会进入构建过程中,如果要在构建中使用变量请使用 arg 标签。

expose

暴露端口,但不映射到宿主机,只被连接的服务访问。这个标签与 Dockerfile 中的 EXPOSE 指令一样,用于指定暴露的端口,但是只是作为一种参考,实际上 docker-compose.yml 的端口映射还得 ports 这样的标签。

expose:
 - "3000"
 - "8000"
extra_hosts

添加主机名的标签,就是往 /etc/hosts 文件中添加一些记录,与 Docker 客户端 中的 --add-host 类似:

extra_hosts:
 - "somehost:162.242.195.82"
 - "otherhost:50.31.209.229"

具有 IP 地址和主机名的条目在 /etc/hosts 内部容器中创建。启动之后查看容器内部 hosts,例如:

162.242.195.82  somehost
50.31.209.229   otherhost
healthcheck

用于检查测试服务使用的容器是否正常。

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

intervaltimeout 以及 start_period 都定为持续时间。

test 必须是字符串或列表,如果它是一个列表,第一项必须是 NONECMDCMD-SHELL ;如果它是一个字符串,则相当于指定 CMD-SHELL 后跟该字符串。

# Hit the local web app
test: ["CMD", "curl", "-f", "http://localhost"]

# As above, but wrapped in /bin/sh. Both forms below are equivalent.
test: ["CMD-SHELL", "curl -f http://localhost || exit 1"]
test: curl -f https://localhost || exit 1

如果需要禁用镜像的所有检查项目,可以使用 disable:true ,相当于 test:["NONE"]

healthcheck:
  disable: true
image

从指定的镜像中启动容器,可以是存储仓库、标签以及镜像 ID。如果镜像在本地不存在,Compose 将会尝试从官方仓库拉取这个镜像。

image: redis
image: ubuntu:14.04
image: tutum/influxdb
image: example-registry.com:4000/postgresql
image: a4bc65fd

如果使用 build 构建镜像,则 image 为构建的镜像名。

init

Run an init inside the container that forwards signals and reaps processes. Either set a boolean value to use the default init, or specify a path to a custom one.

version: '3.7'
services:
  web:
    image: alpine:latest
    init: true


version: '2.2'
services:
  web:
    image: alpine:latest
    init: /usr/libexec/docker-init
isolation

Specify a container’s isolation technology. On Linux, the only supported value is default.

labels

使用 Docker 标签将元数据添加到容器,可以使用数组或字典。与 Dockerfile 中的 LABELS 类似:

labels:
  com.example.description: "Accounting webapp"
  com.example.department: "Finance"
  com.example.label-with-empty-value: ""

labels:
  - "com.example.description=Accounting webapp"
  - "com.example.department=Finance"
  - "com.example.label-with-empty-value"
logging

配置日志服务。

logging:
  driver: syslog
  options:
    syslog-address: "tcp://192.168.0.42:123"

driver 值是指定服务器的日志记录驱动程序,默认值为 json-file,与 --log-diver 选项一样:

driver: "json-file"
driver: "syslog"
driver: "none"

Note

只有驱动程序 json-filejournald 驱动程序可以直接从 docker-compose updocker-compose logs 获取日志。使用任何其他方式不会显示任何日志。

对于可选值,可以使用 options 指定日志记录中的日志记录选项:

driver: "syslog"
options:
  syslog-address: "tcp://192.168.0.42:123"

默认驱动程序 json-file 具有限制存储日志量的选项,所以,使用键值对来获得最大存储大小以及最小存储数量:

options:
  max-size: "200k"
  max-file: "10"

上面实例将存储日志文件,直到它们达到 max-size (200kB),存储的单个日志文件的数量由该 max-file 值指定。随着日志增长超出最大限制,旧日志文件将被删除以存储新日志。

docker-compose.yml 限制日志存储的示例:

services:
  some-service:
    image: some-service
    logging:
      driver: "json-file"
      options:
        max-size: "200k"
        max-file: "10"
network_mode

网络模式,用法类似于 Docker 客户端的 --network 选项,格式为:service:[service name]

network_mode: "bridge"
network_mode: "host"
network_mode: "none"
network_mode: "service:[service name]"
network_mode: "container:[container name/id]"

Note

  1. network_mode 在 Swarm 模式无效。
  2. network_mode: "host" 不能与 links 混合使用。
networks

加入指定网络,可选网络来自顶层 networks

services:
  some-service:
    networks:
     - some-network
     - other-network
aliases

Aliases(alternative hostnames),同一网络上的其他容器可以使用服务器名称或别名(也是 hostname)来连接到其他服务的容器。

services:
  some-service:
    networks:
      some-network:
        aliases:
         - alias1
         - alias3
      other-network:
        aliases:
         - alias2

下面实例中,提供 webworker 以及 db 三个服务和两个网络 ( newlegacy ),相同的服务可以在不同的网络有不同的别名。

version: '2'

services:
  web:
    build: ./web
    networks:
      - new

  worker:
    build: ./worker
    networks:
      - legacy

  db:
    image: mysql
    networks:
      new:
        aliases:
          - database
      legacy:
        aliases:
          - mysql

networks:
  new:
  legacy:

其他服务可以使用以下方式连接到服务 db

  • 直接使用 db
  • 同在 new 网络上的服务可以使用 database
  • 同在 legacy 网络上的服务可以使用 mysql
ipv4_address、ipv6_address

为服务的容器指定一个静态 IP 地址。

version: '2.1'

services:
  app:
    image: busybox
    command: ifconfig
    networks:
      app_net:
        ipv4_address: 172.16.238.10
        ipv6_address: 2001:3984:3989::10

networks:
  app_net:
    driver: bridge
    enable_ipv6: true
    ipam:
      driver: default
      config:
      -
        subnet: 172.16.238.0/24
      -
        subnet: 2001:3984:3989::/64

Note

启用 IPV6 需要设置 enable_ipv6,且只在 2.x 版本 Compose file 下适用,本选项在 Swarm 模式无效。

pid

将 PID 模式设置为主机 PID 模式,可以打开容器与主机操作系统之间的共享 PID 地址空间。使用此标志启动的容器可以访问和操作宿主机的其他容器,反之亦然。

pid: "host"
ports

映射端口。

Note

端口映射和 network_mode: host 不兼容。

SHORT syntax

可以使用 HOST:CONTAINER 的方式指定端口,也可以指定容器端口(选择临时主机端口),宿主机会随机映射端口。

ports:
 - "3000"
 - "3000-3005"
 - "8000:8000"
 - "9090-9091:8080-8081"
 - "49100:22"
 - "127.0.0.1:8001:8001"
 - "127.0.0.1:5000-5010:5000-5010"
 - "6060:6060/udp"

Note

当使用 HOST:CONTAINER 格式来映射端口时,如果使用的容器端口小于 60 可能会得到错误得结果,因为 YAML 将会解析 xx:yy 这种数字格式为 60 进制,所以建议采用字符串格式。

LONG syntax

LONG 语法支持 SHORT 语法不支持的附加字段:

  • target :容器内的端口
  • published :公开的端口
  • protocol : 端口协议( tcpudp
  • mode :通过 host 用在每个节点还是哪个发布的主机端口或使用 ingress 用于集群模式端口进行平衡负载
ports:
  - target: 80
    published: 8080
    protocol: tcp
    mode: host
secrets

通过 secrets 为每个服务授予相应的访问权限,选项必须在顶层 secrets 定义。

SHORT syntax

仅用于指定 secret 名称,允许容器进入 secret 并挂载到 /run/secrets/>secret_name>

version: "3.1"
services:
  redis:
    image: redis:latest
    deploy:
      replicas: 1
    secrets:
      - my_secret
      - my_other_secret
secrets:
  my_secret:
    file: ./my_secret.txt
  my_other_secret:
    external: true
LONG syntax

LONG 语法可以添加其他选项:

  • source :secret 名称
  • target :在服务任务容器中需要装载在 /run/secrets/ 中的文件名称,如果 source 未定义,那么默认为此值
  • uid/gid :在服务的任务容器中拥有该文件的 UID 或 GID 。如果未指定,两者都默认为 0。
  • mode :以八进制表示法将文件装载到服务的任务容器中 /run/secrets/ 的权限。例如,0444 代表可读。
version: "3.1"
services:
  redis:
    image: redis:latest
    deploy:
      replicas: 1
    secrets:
      - source: my_secret
        target: redis_secret
        uid: '103'
        gid: '103'
        mode: 0440
secrets:
  my_secret:
    file: ./my_secret.txt
  my_other_secret:
    external: true
security_opt

为每个容器覆盖默认的标签。简单说来就是管理全部服务的标签。

security_opt:
  - label:user:USER
  - label:role:ROLE

Note

security_opt 在 Swarm 模式无效。

stop_grace_period

在发送 SIGKILL 之前 ,如果试图停止容器(如果它没有处理 SIGTERM 或使用 stop_signal 指定的任何停止信号),则需要等待的时间。

stop_grace_period: 1s
stop_grace_period: 1m30s

默认情况下,stop 在发送 SIGKILL 之前等待 10 秒钟容器退出。

stop_signal

设置另一个信号来停止容器。在默认情况下使用的 SIGTERM 来停止容器。设置另一个信号可以使用 stop_signal 标签:

stop_signal: SIGUSR1

Note

stop_signal 在 Swarm 模式无效。

sysctls

在容器中设置的内核参数,可以为数组或字典。

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

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

Note

sysctls 在 Swarm 模式无效。

ulimits

覆盖容器的默认 ulimits,可以单一地将限制值设为一个整数,也可以将 soft/hard 限制指定为映射

ulimits:
  nproc: 65535
  nofile:
    soft: 20000
    hard: 40000
userns_mode

如果 docker deamon 设置了用户命名空间,可以使用 userns_mode 禁用。

userns_mode: "host"

Note

userns_mode 在 Swarm 模式无效。

volumes

挂载主机目录或数据卷。

如果直接挂载主机目录并作为单一服务使用,无需在顶层 volumes 预定义后引用;如果要做多服务间复用,则需要先定义。

下面例子 web 服务使用 named volume ( mydata ),db 服务使用了两个挂载:bind mount (postgres.sock )和 named volume( dbdata )。

version: "3.2"
services:
  web:
    image: nginx:alpine
    volumes:
      - type: volume
        source: mydata
        target: /data
        volume:
          nocopy: true
      - type: bind
        source: ./static
        target: /opt/app/static

  db:
    image: postgres:latest
    volumes:
      - "/var/run/postgres/postgres.sock:/var/run/postgres/postgres.sock"
      - "dbdata:/var/lib/postgresql/data"

volumes:
  mydata:
  dbdata:
SHORT syntax

挂载主机目录可以直接使用 HOST:CONTAINER 这样的格式,或者使用 HOST:CONTAINER:ro 这样的格式,后者对于容器来说,数据卷是只读的,这样可以有效保护宿主机的文件系统。

可以在主机上挂载相对路径,该路径相对于正在使用的 Compose 配置文件的目录进行扩展。相对路径应始终以 ... 开头

volumes:
  # Just specify a path and let the Engine create a volume
  # 只是指定一个路径,Docker 会自动在创建一个数据卷(这个路径是容器内部的)。
  - /var/lib/mysql

  # Specify an absolute path mapping
  - /opt/data:/var/lib/mysql

  # Path on the host, relative to the Compose file
  - ./cache:/tmp/cache

  # User-relative path
  - ~/configs:/etc/configs/:ro

  # Named volume
  - datavolume:/var/lib/mysql
LONG syntax

LONG 语法有些附加字段:

  • type :安装类型,可以为 volumebindtmpfs
  • source :挂载源路径,主机路径或定义在顶级 volumes 中卷的名称,不适用于 tmpfs 类型安装。
  • target :挂载在容器中的路径
  • read_only :将卷设置为只读
  • bind :配置额外的绑定选项
    • propagation :用于绑定的传播模式
  • volume :配置额外的卷选项
    • nocopy :创建卷时禁止从容器复制数据的标志
  • tmpfs :配置额外的 tmpfs 选项
    • size :tmpfs 的大小,以 byte 为单位
version: "3.2"
services:
  web:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - type: volume
        source: mydata
        target: /data
        volume:
          nocopy: true
      - type: bind
        source: ./static
        target: /opt/app/static

networks:
  webnet:

volumes:
  mydata:
用于服务、群集以及堆栈文件的卷

在使用服务,群集和 docker-stack.yml 文件时,请记住支持服务的任务(容器)可以部署在群集中的任何节点上,并且每次更新服务时都可能是不同的节点。

在缺少指定源的命名卷的情况下,Docker 为支持服务的每个任务创建一个匿名卷。关联的容器被移除后,匿名卷不会保留。

如果希望数据持久存在,请使用可识别多主机的命名卷和卷驱动程序,以便可以从任何节点访问数据。或者,对该服务设置约束,以便将其任务部署在具有该卷的节点上。

下面一个例子中定义了一个 db 的服务。它被配置为一个命名卷来保存群体上的数据,并且仅限于在节点上运行。下面是来自该文件的部分内容:

version: "3"
services:
  db:
    image: postgres:9.4
    volumes:
      - db-data:/var/lib/postgresql/data
    networks:
      - backend
    deploy:
      placement:
        constraints: [node.role == manager]
restart

默认值为 no ,即在任何情况下都不会重新启动容器;当值为 always 时,容器总是重新启动;当值为 on-failure 时,当出现 on-failure 报错容器退出时,容器重新启动。

restart: "no"
restart: always
restart: on-failure
restart: unless-stopped

Note

restart 在 Swarm 模式无效,请使用 restart_policy

domainname
domainname: foo.com

类似于使用 docker run

hostname
hostname: foo

类似于使用 docker run

ipc
ipc: host

类似于使用 docker run

mac_address
mac_address: 02:42:ac:11:65:43

类似于使用 docker run

privileged
privileged: true

类似于使用 docker run

read_only
read_only: true

类似于使用 docker run

shm_size
shm_size: 64M

类似于使用 docker run

stdin_open
stdin_open: true

类似于使用 docker run

tty
tty: true

类似于使用 docker run

user
user: postgresql

类似于使用 docker run

working_dir
working_dir: /code

类似于使用 docker run

durations

某些配置选项如 check 的子选项 interval 以及 timeout 的设置格式,支持的单位有 usmssm 以及 h

2.5s
10s
1m30s
2h32m
5h34m56s
byte values

某些选项如 bulid 的子选项 shm_size

2b
1024kb
2048k
300m
1gb

支持的单位是 bkmg,或 kbmbgb 。目前不支持十进制值。

volumes

命名卷可以跨服务复用,下面两服务共享同一个目录用作数据备份。

version: "3"

services:
  db:
    image: db
    volumes:
      - data-volume:/var/lib/db
  backup:
    image: backup-service
    volumes:
      - data-volume:/var/lib/backup/data

volumes:
  data-volume:
driver

volumes 定义的卷可以为空,此时使用默认驱动(通常是 local ),也可以手动指定驱动。但如果驱动不可以,docker-compose up 启动时创建卷失败,如:

driver: foobar
driver_opts

指定驱动选项。

driver_opts:
  foo: "bar"
  baz: 1
external

如果指定为 true,说明该卷在 Compose 之外创建,docker-compose up 时不会创建,但如果不存在则报错。

external 不能喝其他卷配置一起使用( driverdriver_opts )。

version: '2'

services:
  db:
    image: postgres
    volumes:
      - data:/var/lib/postgresql/data

volumes:
  data:
    external: true

可使用 name 指定外部卷实际名称:

volumes:
  data:
    external:
      name: actual-name-of-volume

Note

在 Swarm 模式下,外部卷不存在则创建。

labels

参考 labels

name

设置挂载卷名称,可用于引用包含特殊字符的卷。

version: '3.4'
volumes:
  data:
    name: my-app-data

name 可以和 external 结合使用:

version: '3.4'
volumes:
  data:
    external: true
    name: my-app-data
networks
driver

用于指定网络驱动。默认驱动通常为 bridge (single host)或 overlay (swarm)。

如果驱动不可用则报错。

driver: overlay
bridge

单节点上默认使用 bridge 网络。

overlay

在 Swarm 多节点间使用 overlay 驱动创建网络。

host or none

使用主机网络,或不使用网络。只在 docker stack 下有效,等价于:

docker run --net=host

docker run --net=none

如果使用 docker-compose,请使用 network_mode

内置网络(Docker 自动创建) hostnone 的使用需要创建别名(如 hostnet/nonet),然后才能在 Compose 这使用内置网络:

services:
  web:
    ...
    networks:
      hostnet: {}

networks:
  hostnet:
    external: true
    name: host
services:
  web:
    ...
    networks:
      nonet: {}

networks:
  nonet:
    external: true
    name: none
driver_opts

参考 driver_opts

attachable

参考 attachable

ipam

参考 ipam

internal

默认情况下,Docker 使用 bridge 网络提供外界连接,如果要创建与外界隔离的 overlay 网络,将 internal 设置为 true

labels

参考 labels

external

如果为 true,指明本网络为 Compose 外部创建,docker-compose up 时不会创建,但如果不存在则报错。

external 不能与其他网络配置混合使用( driver, driver_opts, ipam, internal )。

version: '2'

services:
  proxy:
    build: ./proxy
    networks:
      - outside
      - default
  app:
    build: ./app
    networks:
      - default

networks:
  outside:
    external: true

使用 name 设置实际网络名称:

networks:
  outside:
    external:
      name: actual-name-of-network
name

设置网络实际名称,可包含特殊字符。

version: '3.5'
networks:
  network1:
    name: my-app-net

name 可与 external 一起使用:

version: '3.5'
networks:
  network1:
    external: true
    name: my-app-net
configs

configs 可被同一 stack 内的服务共享,来源只能为 fileexternal

  • file :使用指定路径文件创建配置
  • external :如果值为 true,指明配置已经创建,如果不存在报错。
  • name :配置对象名称,可以包含特殊字符。
configs:
  my_first_config:
    file: ./config_data
  my_second_config:
    external: true
configs:
  my_first_config:
    file: ./config_data
  my_second_config:
    external:
      name: redis_config
secrets

选项 fileexternalname 类似 configs

secrets:
  my_first_secret:
    file: ./secret_data
  my_second_secret:
    external: true
secrets:
  my_first_secret:
    file: ./secret_data
  my_second_secret:
    external:
      name: redis_secret
variable substitution

Compose 变量能使用环境变量,如定义环境变量 POSTGRES_VERSION=9.3,在 Compose 中使用:

db:
  image: "postgres:${POSTGRES_VERSION}"

当使用 docker-compose up 时,Compose 将 image 解析为 postgres:9.3,然后运行。

如果环境变量没有设置,Compose 替换为空字符串,如上面 image 解析为 postgres:

.env 中设置的环境变量会被 shell 环境变量覆盖,且只在 docker-compose up 中有效,在 docker stack deploy 中无效。

变量取值支持 $VARIABLE${VARIABLE} 两种语法,后一种支持使用默认值:

  • ${VARIABLE:-default} :如果 VARIABLE 不存在或为空时,使用默认值 default
  • ${VARIABLE-default} :如果 VARIABLE 不存在时,使用默认值 default
  • ${VARIABLE:?err} :如果 VARIABLE 不存在或为空时,以包含 err 错误消息退出
  • ${VARIABLE?err} :如果 VARIABLE 不存在时,以包含 err 错误消息退出

如果配置需要使用美元符号 $,需要使用 $$ 引用:

web:
  build: .
  command: "$$VAR_NOT_INTERPOLATED_BY_COMPOSE"
Docker 简介
Docker 架构、优点、应用场景以及与虚拟机的区别。
Docker 安装教程
常用操作系统下 docker 安装教程。
开启 Docker 远程访问
开启 Docker TCP 远程访问。
Docker 入门指南
Docker 基本使用,了解容器如何启动、查看、停止和删除。
Dockerfile 参考手册
Dockerfile 指令和书写,及使用 Dockerfile 构建镜像。
Compose 入门指南
Docker 编排工具 Compose 基本使用。
Compose file 参考手册
Docker Compose file 指令和书写。

Docker 实战

搭建 Docker 私有仓库

搭建私有仓库

使用 registry 镜像搭建本地私有仓库,compose 文件如下:

version: '3'
services:
  registry:
    image: registry
    restart: always
    container_name: registry
    volumes:
      - $PWD/docker-registry/registry:/var/lib/registry
    ports:
      - 5000:5000

使用如下命令启动仓库镜像:

$ docker-compose up -d
开启 HTTP 访问

Note

由于 Registry 为了安全性考虑,默认是需要 HTTPS 证书支持的。而启动的 docker registry 未采用 HTTPS 服务,所以需要把客户端的请求也改为 HTTP。

/etc/docker/daemon.json 加入如下内容(没有就新建文件,注意 IP 换成自己私有仓库的):

{
    "insecure-registries" : ["1.1.1.1:5000"]
}

重启 docker:

$ sudo systemctl restart docker
推送镜像

首先将本地镜像以仓库 URL 为前缀重命名:

$ docker tag datascience/tensorflow-gpu:1.7.0 1.1.1.1:5000/datascience/tensorflow-gpu:1.7.0

推送到私有仓库:

$ docker push 1.1.1.1:5000/datascience/tensorflow-gpu:1.7.0

如果没有按照上面配置 HTTP 访问,则推送时报错:

The push refers to a repository [1.1.1.1:5000/datascience/tensorflow-gpu]
Get https://1.1.1.1:5000/v2/: http: server gave HTTP response to HTTPS client
查看私有仓库

查看所有镜像,使用 GET /v2/_catalog

$ curl http://1.1.1.1:5000/v2/_catalog
{"repositories":["datascience/base","datascience/cuda","datascience/tensorflow-gpu"]}

查看具体镜像,使用 GET /v2/<name>/tags/list

$ curl http://1.1.1.1:5000/v2/datascience/tensorflow-gpu/tags/list
{"name":"datascience/tensorflow-gpu","tags":["1.7.0","1.0.1"]}
拉取镜像
$ docker pull 1.1.1.1:5000/datascience/tensorflow-gpu:1.7.0

Tip

初始镜像名较长,推荐重命名(docker tag)后使用。

Docker 运行 Redis

本镜像用于启动 Redis 服务,并解决通常 redis-server 启动报警。

redis.conf

示例配置:redis.conf

Attention

注意不要使用后台启动,否则容器启动后马上关闭!

启动 Redis

使用 docker-compose 启动 Redis,对应的 Compose 文件如下:

version: '3'
services:
  redis:
    image: redis
    sysctls:
      net.core.somaxconn: 511
    ports:
      - 6379:6379
    volumes:
      - $PWD/redis.conf:/etc/redis/redis.conf
      - $PWD/data:/data
    command: redis-server /etc/redis/redis.conf

启动命令如下:

$ docker-compose up -d
WARNING 处理

默认启动 redis-server 会出现几个警告,可按照以下操作配置宿主机解决。

maximum number of open files

Warning

Increased maximum number of open files to 10032 (it was originally set to 1024).

解决方法:

$ echo '* soft nofile 65535' >> /etc/security/limits.conf
$ echo '* hard nofile 65535' >> /etc/security/limits.conf
overcommit_memory

Warning

WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add ‘vm.overcommit_memory = 1’ to /etc/sysctl.conf and then reboot or run the command ‘sysctl vm.overcommit_memory=1’ for this to take effect.

解决方法:

$ echo 1 > /proc/sys/vm/overcommit_memory
$ echo "vm.overcommit_memory=1" >> /etc/sysctl.conf
backlog

Note

如果使用 docker 启动可忽略,已经在 docker-compose.yml 中处理。

Warning

WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.

解决方法:

$ echo 511 >/proc/sys/net/core/somaxconn
$ echo "net.core.somaxconn = 551" >> /etc/sysctl.conf
THP

Warning

WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command ‘echo never > /sys/kernel/mm/transparent_hugepage/enabled’ as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.

解决方法:

$ echo never > /sys/kernel/mm/transparent_hugepage/enabled
$ echo 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' >> /etc/rc.d/rc/local

Docker 运行 IPython Notebook

使用 Docker 构建 notebook 镜像,Dockerfile 如下:

FROM registry.cn-beijing.aliyuncs.com/freyr/python:ubuntu16.04

RUN pip install jupyter

ADD requirements.txt .

RUN pip install -r requirements.txt && rm -rf requirements.txt

ADD jupyter_notebook_config.py .

RUN mkdir ~/.jupyter && mv jupyter_notebook_config.py ~/.jupyter

WORKDIR /notebook

CMD ["/bin/bash"]

其中,jupyter_notebook_config.py 为 jupyter 配置文件,可通过以下命令在本地生成(路径 ~/.jupyter/

$ jupyter notebook --generate-config

主要修改参数:

c.NotebookApp.allow_root = True
c.NotebookApp.ip = '*'
c.NotebookApp.notebook_dir = '/notebook'
c.NotebookApp.token = ''

Docker-compose 文件如下:

version: '3'
services:
  notebook:
    build: .
    image: notebook
    container_name: notebook
    ports:
      - 8888:8888
    volumes:
      - $PWD/notebooks:/notebook
    command: jupyter notebook

使用如下命令启动容器:

$ docker-compose up -d

Docker 搭建 PyPI 服务器

运行 pypi-server 容器

使用镜像 codekoala/pypi 启动 pypi-server 服务器,docker-compose 如下:

version: '3'
services:
  pypi:
    image: codekoala/pypi
    volumes:
      - $PWD:/srv/pypi
    container_name: pypi
    ports:
      - 9527:80

使用如下命令启动:

$ docker-compose up -d
添加用户

使用 htpasswd 添加用户:

$ htpasswd -c -s /htpasswd ${username}

两次输入密码后创建用户成功。

此时已经可以访问服务器:http://${your-IP}:9527。注意要把 IP 换成自己的服务器 IP。

Tip

如果在 CentOS7 下没有 htpasswd,则需要使用 sudo yum install httpd-tools 进行安装。

使用方法
上传 package

配置 ~/.pypirc,为了不用每次上传输入账号密码和仓库 URL。格式如下:

[distutils]
index-servers =
    pypi
    pypitest
    internal

[pypi]
username: xxxxx
password: xxxxx

[pypitest]
repository: https://test.pypi.org/legacy/
username: xxxxx
password: xxxxx

[internal]
repository: http://${your-IP}:9527
username: xxxxx
password: xxxxx

使用 twine 上传

$ twine upload dist/* -r internal
使用仓库安装 package
$ pip install ${package-name} -i http://${your-IP}:9527 --trusted-host ${your-IP}

因为是 HTTP 连接,所以需要选项 --trusted-host

搭建 Docker 私有仓库
使用 registry 镜像搭建本地 Docker 私有仓库。
Docker 运行 Redis
启动 Redis 服务,并解决启动 Warning。
Docker 运行 IPython Notebook
构建 IPython Notebook 镜像,修改配置免 token 登录。
Docker 搭建 PyPI 服务器
使用 pypi-server 搭建本地 PyPI 服务器。
Docker 基础
Docker 基础知识及相关工具使用。
Docker 实战
使用 Docker 实现服务容器化。

Linux

Linux 命令

tar

tar 命令可以为 linux 的文件和目录创建档案。利用 tar,可以为某一特定文件创建档案(备份文件),也可以在档案中改变文件,或者向档案中加入新的文件。

首先要弄清两个概念——打包和压缩:

  • 打包是指将一大堆文件或目录变成一个总的文件;
  • 压缩则是将一个大的文件通过一些压缩算法变成一个小文件。

为什么要区分这两个概念呢?这源于 Linux 中很多压缩程序只能针对一个文件进行压缩,这样当你想要压缩一大堆文件时,你得先将这一大堆文件先打成一个包(tar 命令),然后再用压缩程序进行压缩(gzip/bzip2 命令)。

语法
$ tar [OPTION...] [FILE]...
选项
-A, -catenate 新增文件到以存在的备份文件
-B 设置区块大小
-c, --create 建立新的备份文件
-C <目录> 这个选项用在解压缩,若要在特定目录解压缩,可以使用这个选项
-d 记录文件的差别
-x, --extract, --get
 从备份文件中还原文件
-t, --list 列出备份文件的内容
-z, --gzip, --ungzip
 通过gzip指令处理备份文件
-Z, --compress, --uncompress
 通过compress指令处理备份文件
-f <备份文件>, --file=<备份文件>
 指定备份文件
-v, --verbose 显示指令执行过程
-r 添加文件到已经压缩的文件
-u 添加改变了和现有的文件到已经存在的压缩文件
-j 支持bzip2解压文件
-v 显示操作过程
-l 文件系统边界设置
-k 保留原有文件不覆盖
-m 保留文件不被覆盖
-w 确认压缩文件的正确性
-p, --same-permissions
 用原来的文件权限还原文件
-P, --absolute-names
 文件名使用绝对名称,不移除文件名称前的“/”号
-N <日期格式>, --newer=<日期时间>
 只将较指定日期更新的文件保存到备份文件里
--exclude=<范本样式>
 排除符合范本样式的文件
打包/压缩

打包使用参数 -c,压缩使用参数 -z/-j

$ tar -cvf log.tar *.log  # 仅打包,不压缩!
$ tar -zcvf log.tar.gz *.log  # 打包后,以 gzip 压缩
$ tar -jcvf log.tar.bz2 *.log  # 打包后,以 bzip2 压缩
查看包内容
$ tar -ztvf log.tar.gz

使用 -t 参数查看包内容,如果压缩则需要带上相应压缩参数 -z/-j

解包/解压

解压缩使用参数 -x:

$ tar -zxvf log.tar.gz

如果要指定解压的目录(目录要求存在),使用 -C 参数:

$ tar -zxvf log.tar.gz -C ~/logs

以上是解压所有文件,如果只想解压部分文件在最后指定文件列表(可以先使用 -t 查看包内容):

$ tar -zxvf log.tar.gz log2012.log
tar
tar 命令可以为 linux 的文件和目录创建档案,注意区分打包和压缩。

Linux 运维

Linux 安装 JDK

下载 JDK

前往 Java SE Downloads 下载 JDK(如 jdk-8u181-linux-x64.tar.gz )。

卸载 OpenJDK

使用 java -version 查看是否已经安装 OpenJDK,如果已经安装先卸载。

查看安装位置:

$ rpm -aq | grep java

卸载:

$ rpm -e --nodeps java-1.8.0-openjdk-1.8.0.171-8.b10.el7_5.x86_64
...
安装 JDK

解压 JDK 安装包:

$ tar -zxvf jdk-8u181-linux-x64.tar.gz -C /opt/jdk-8u181
配置环境变量

新建文件 /etc/profile.d/java.sh,添加如下内容:

export JAVA_HOME=/opt/jdk-8u181
export JRE_HOME=$JAVA_HOME/jre
export CLASSPATH=.:$JAVA_HOME/lib:$JRE_HOME/lib:$CLASSPATH
export PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH

修改文件权限:

$ sudo chmod 755 /etc/profile.d/java.sh

Attention

重启电脑生效,使用 java -version 查看是否成功。

Vmware 安装 CentOS7 后配置

配置虚拟机网络

首先设置虚拟机网络连接为 NAT 模式

_images/vmware-nat.png

然后进入 “编辑——虚拟机网络编辑器”,配置 VMnet8 网络:

_images/vmware-nat1.png _images/vmware-nat2.png
配置静态 IP

编辑文件 /etc/sysconfig/network-scripts/ifcfg-ens33 (文件名可能因系统不同而不同,如可能为 ifcfg-eth0 ):

TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=static
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=ens33
UUID=4f321d80-7fb7-4e16-879e-9897971b5ae5
DEVICE=ens33
ONBOOT=yes
NETMASK=255.255.255.0
GATEWAY=192.168.70.2
IPADDR=192.168.70.33
DNS1=114.114.114.114
DNS2=202.106.0.20

其中,GATEWAY 配置为上一步操作中的 网关 IP;IPADDR 为静态 IP,可依据网关自由修改。

Tip

使用 ifconfig 查看自己 IP 地址。

重启网络服务:

$ service network restart

Tip

可以使用 ping www.baidu.com 查看网络是否已通。

宿主机如果为 Windows,可以在 Xshell 中配置使用上面设置的静态 IP 进行登录。

更换 yum 源

可以使用国内镜像源进行系统更新和软件下载的加速,以 aliyun 为例:

进入 yum 配置文件目录,备份默认源配置:

$ cd /etc/yum.repos.d
$ mv CentOS-Base.repo CentOS-Base.repo.bak

下载 aliyun 配置:

$ wget -O CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo

下载 epel 源(可选):

$ wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo

更新缓存:

$ yum clearn all
$ yum makecache

更新系统:

$ yum update
解决 SMBus 错误

/etc/modprobe.d/blacklist.conf 中添加黑名单:

blacklist i2c_piix4

oh-my-zsh 安装配置

安装 zsh
$ sudo yum install zsh
安装 oh-my-zsh
$ sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
命令自动提示
$ cd ~/.oh-my-zsh/custom/plugins
$ git clone git://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions

更改显示颜色:

$ vim zsh-autosuggestions/zsh-autosuggestions.zsh

ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=10'

更新 .zshrc

plugins=(zsh-autosuggestions git)
语法高亮
$  git clone https://github.com/zsh-users/zsh-syntax-highlighting.git

更新 .zshrc

$ vim ~/.zshrc

source $ZSH_CUSTOM/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh

Attention

使用 source ~/.zshrc 使配置即刻生效。

crontab 配置环境变量

Note

当使用 crontab 部署的任务没执行时怎么办?

首先收集 crontab 日志,在任务最后加上 2> log.txt,然后更改下执行时间下一分钟生效,在日志中找到出错原因。(也可直接查看 cron 的运行日志 /var/log/cron

如果是因为环境变量缺失,那就是本文要解决的问题。

系统 cron 任务

系统 cron 任务存在于 /etc/crontab 文件,这个文件是系统安装时设置好的自动安排的进程任务的 crontab 文件。这为系统管理员安排 cron 任务提供了方便。

CentOS 默认的 /etc/crontab 文件的内容为:

SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
HOME=/
# run-parts
01 * * * * root run-parts /etc/cron.hourly
02 4 * * * root run-parts /etc/cron.daily
22 4 * * 0 root run-parts /etc/cron.weekly
42 4 1 * * root run-parts /etc/cron.monthly

/etc/cron.daily/etc/cron.monthly/etc/cron.weekly/etc/cron.hourly 是四个目录,分别放置系统每天、每个月、每周和每个小时要执行的任务的脚本文件。

Note

可以看到 crontab 文件最前面是可以设置环境变量的!

用户 cron 任务添加环境变量

用户 cron 任务存在于 /var/spool/cron 目录下用户的 crontab 文件。当任务依赖于环境变量时可以仿照系统 crontab 文件在用于 crontab 文件最开始添加环境变量。

比如:

SHELL=/bin/zsh
HOME=/data1/datascience/scrapy-docker
PYTHONPATH=/data1/datascience/scrapy-docker
PATH=/opt/anaconda3/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin

# ftp data to cluster
*/5 * * * * cd crawler_tools && python sftp.py

其中,

  • SHELL,指定使用的 shell
  • HOME,指定 cron 工作目录,所有任务默认在该目录下执行。非默认目录执行需要手动使用 cd 切换(支持相对路径)。
  • PATH,默认只搜索系统 crontab 的四个目录,上面添加了 anaconda3
  • 其他环境变量,如 PYTHONPATH。
在 shell 脚本添加环境变量

如果任务是 shell 脚本,也可以在脚本开头加上:

source /etc/profile

来添加环境变量。/etc/profile 可换成 ~/.bashrc, ~/.bash_profile 等,根据环境变量定义的位置进行替换。

Siege 教程

Siege 是一个 http 负载测试和基准测试工具。可以根据配置对一个WEB站点进行多用户的并发访问,记录每个用户所有请求过程的相应时间,并在一定数量的并发访问下重复进行。可以根据配置对一个WEB站点进行多用户的并发访问,记录每个用户所有请求过程的相应时间,并在一定数量的并发访问下重复进行。Siege 可以从您选择的预置列表中请求随机的 URL,然后进行压力测试。

安装

进入 http://download.joedog.org/siege/ 选择最新版本:

$ curl http://download.joedog.org/siege/siege-latest.tar.gz -O

解压:

$ tar -zxvf siege-latest.tar.gz

编译,安装:

$ cd siege-4.0.4
$ ./configure
$ make && make install

安装路径为 /usr/local/bin,查看版本:

$ siege --version
SIEGE 4.0.4
参数说明

使用 siege -h 查看帮助信息:

$ siege -h
SIEGE 4.0.2
Usage: siege [options]
       siege [options] URL
       siege -g URL
Options:
  -V, --version             VERSION, prints the version number.
  -h, --help                HELP, prints this section.
  -C, --config              CONFIGURATION, show the current config.
  -v, --verbose             VERBOSE, prints notification to screen.
  -q, --quiet               QUIET turns verbose off and suppresses output.
  -g, --get                 GET, pull down HTTP headers and display the
                            transaction. Great for application debugging.
  -c, --concurrent=NUM      CONCURRENT users, default is 10
  -r, --reps=NUM            REPS, number of times to run the test.
  -t, --time=NUMm           TIMED testing where "m" is modifier S, M, or H
                            ex: --time=1H, one hour test.
  -d, --delay=NUM           Time DELAY, random delay before each requst
  -b, --benchmark           BENCHMARK: no delays between requests.
  -i, --internet            INTERNET user simulation, hits URLs randomly.
  -f, --file=FILE           FILE, select a specific URLS FILE.
  -R, --rc=FILE             RC, specify an siegerc file
  -l, --log[=FILE]          LOG to FILE. If FILE is not specified, the
                            default is used: PREFIX/var/siege.log
  -m, --mark="text"         MARK, mark the log file with a string.
                            between .001 and NUM. (NOT COUNTED IN STATS)
  -H, --header="text"       Add a header to request (can be many)
  -A, --user-agent="text"   Sets User-Agent in request
  -T, --content-type="text" Sets Content-Type in request
使用示例

请求 url:

$ siege -c 20 -r 10 http://www.baidu.com

从 urls.txt 中选择 url 请求:

$ siege -c 5 -r 10 -f urls.txt

POST 请求:

$ siege -c 10 -r 5 http://www.xxxx.com/index.php POST UserId=XXX&Sign=cff6wyt505wyt4c

设置请求间隔:

$ siege -c 10 -r 5 -d 3 http://www.baidu.com
结果说明
Transactions:                  50 hits  (处理次数,本次处理 50 条请求)
Availability:              100.00 %  (成功次数百分比)
Elapsed time:                0.28 secs  (运行总时间)
Data transferred:            0.16 MB  (数据传输量)
Response time:               0.03 secs  (平均响应时间)
Transaction rate:          178.57 trans/sec  (请求频率,每秒处理 178.57 次请求)
Throughput:                  0.56 MB/sec  (吞吐量,传输速度)
Concurrency:                 4.86  (实际最高并发连接数)
Successful transactions:       50  (成功传输次数)
Failed transactions:            0  (失败传输次数)
Longest transaction:         0.04  (最长传输时间)
Shortest transaction:        0.01  (最短传输时间)
Linux 安装 JDK
Linux 下安装 JDK 并配置环境变量。
Vmware 安装 CentOS7 后配置
VMware 虚拟机安装 CentOS 7 后网络连接和 yum 源配置。
oh-my-zsh 安装配置
Zsh 安装并配置自动提示与语法高亮。
crontab 配置环境变量
用户 cron 任务添加环境变量
Siege 教程
负载测试和基准测试工具。
Linux 命令
学习 Linux 常用命令和 shell 脚本。
Linux 运维
掌握基础的 Linux 运维能力。

Git

Git 基础

Git 基础教程

Git 安装配置

进入 Git 官网 https://git-scm.com/ 下载对应平台版本进行安装。

配置用户信息:

$ git config --global user.name "you name"
$ git config --global user.email "you_email@example.com"

使用了 --global 参数,表示本机所有的 git 仓库都会使用这个配置,当然也可以对各个仓库指定不同的用户名和邮件地址(不使用 --global)。

Git 默认使用操作系统默认编辑器,通常是 Vim,如果想使用不同的文本编辑器(如 emacs):

$ git config --global core.editor emacs

使用 git config --list 列出所有 git 能找到的配置:

$ git config --list

core.symlinks=false

core.autocrlf=true

core.fscache=true

color.diff=auto

color.status=auto

color.branch=auto

Git 配置变量存储在三个不同位置,分别有不同作用域(更小作用域有更高优先级):

  • /etc/gitconfig:系统作用域,包含系统上每一个用户及他们仓库的通用配置。 如果使用 --system 选项,它会从此文件读写配置变量。
  • ~/.gitconfig~/.config/git/config:用户作用域,只针对当前用户。 可以传递 --global 选项让 git 读写此文件。
  • .git/config:项目作用域,只针对该仓库有效。
基本概念
工作区
工作区就是要进行版本管理的目录,也就是普通的文件夹。
版本库
当使用 git init 创建一个关于工作区的版本库时,工作区内会新建一个隐藏目录 .git,这个不算工作区,而是 git 的版本库。
暂存区
Git 的版本库里存了很多东西,其中 index 文件被称为暂存区(stage),有时也叫做索引(index)。

Attention

首次 git add 之后才会出现 index 文件。

工作区、暂存区和版本库的关系

暂存区是工作区和版本库的桥梁,是一个临时存储的地方,所有的修改经由暂存区,再提交到版本库。好处是可以在真正提交到版本库之前做任意的操作,在需要真正提交的时候提交到版本库。

Git 的三种状态

Git 有三种状态,你的文件可能处于其中之一:

  • 已修改(modified)
  • 已暂存(staged)
  • 已提交(committed)

通过上面工作区、暂存区和版本库的概念,可以很容易理解:

  • 已修改 表示修改了工作区的文件,但还没保存到版本库中;
  • 已暂存 表示对一个已修改文件的当前版本做了标记,将文件的快照放入暂存区;
  • 已提交 表示暂存区的文件已经永久的保存在本地版本库中。

使用 git 时文件的生命周期如图:

_images/git-lifetime.png
基础操作
创建版本库

在需要版本控制的任何目录内使用命令 git init,以 learning-git 目录为例:

$ git init

Initialized empty Git repository in /Users/freyr/selfRepo/learning-git/.git/

在空目录内创建 git 仓库,结果显示如上: initialized empty Git repository。

查看工作区和暂存区状态

git status 命令用于显示工作区和暂存区的状态,能看到哪些修改被暂存,哪些文件没有被追踪(tracked,如新建的文件):

$ git status

On branch master



No commits yet



nothing to commit (create/copy files and use "git add" to track)

因为版本库新创建,工作区和暂存区都为空,所以没有修改需要提交。

Tip

使用 git status-s 参数简洁输出状态。

$ git status -s

 M README.md

MM Rakefile

A lib/git.rb

M lib/simplegit.rb

?? LICENSE.txt
把文件修改添加到版本库

在工作区进行一些操作,比如创建文件 README.md:

$ touch README.md

查看状态,提示有未追踪的文件(Untracked files):

$ git status

On branch master



No commits yet



Untracked files:

  (use "git add <file>..." to include in what will be committed)



 README.md



nothing added to commit but untracked files present (use "git add" to track)

Git 进行版本控制的操作分两步:

第一步,把 文件修改 添加进暂存区,使用命令 git add:

_images/git-add.png
$ git add README.md  # 如果有多个文件,可以使用 git add . 添加整个目录内所有文件

查看状态未出现 Untracked,说明文件已经被追踪:

$ git status

On branch master



No commits yet



Changes to be committed:

  (use "git rm --cached <file>..." to unstage)



 new file: README.md

第二步,把暂存区的内容提交到当前分支(Git 默认自动创建 master 分支,分支概念后面介绍),使用命令 git commit

_images/git-commit.png
$ git commit -m 'add README.md'  # -m 参数添加本次提交的说明,方便以后查找到本次改动

[master (root-commit) 3b83f4f] add README.md

 1 file changed, 0 insertions(+), 0 deletions(-)

 create mode 100644 README.md

显示一个文件被改动(1 changed,因为未有内容增删,所以 insertions/deletions 都为 0),查看状态显示工作区已干净。

$ git status

On branch master

nothing to commit, working tree clean

Note

最终提交的是文件修改,因为 git 跟踪管理的是修改,而非文件。比如新增一行、删除一行、更改某些字符、新增文件、删除文件等这些都是修改。

Tip

  1. 可以多次 add 不同的文件到暂存区,然后一次 commit 提交。
  2. 使用 git commit -am 同时添加暂存并提交(相当于 git add + git commit

到此,文件已经被成功提交到版本库,可以使用命令 git log 查看提交记录:

$ git log

commit 3b83f4fc79f99d2749c058a713233b6928400435 (HEAD -> master)

Author: freyr <mrchan3030@foxmail.com>

Date: Sun May 27 11:35:55 2018 +0800



    add README.md

可以看到提交的 ID、分支名、作者、时间和提交说明。

撤销修改

使用版本库的第一个好处是:可以随时撤销修改,恢复到上一次提交版本库的文件状态。

由于工作区、暂存区、版本库的存在,对应的修改也会发生在这三个地方。

撤销工作区修改

Note

场景:工作区修改没有 add,没有 commit

在 README 中增加一行内容:

咸豆脑好吃

再查看下版本库:

$ git status

On branch master

Changes not staged for commit:

  (use "git add <file>..." to update what will be committed)

  (use "git checkout -- <file>..." to discard changes in working directory)



    modified: README.md



no changes added to commit (use "git add" and/or "git commit -a")

显示文件已经发生修改,可以使用 git checkout -- 撤销修改:

$ git checkout -- README.md

再查看状态可以看到干净的工作区。

Attention

对于工作区单个文件,checkout 后的 -- 不能少!(不然就变成切换分支)

Tip

对于工作区的修改:

  • 撤销 tracked 的单个文件使用 git checkout -- <file>
  • 撤销 tracked 的所有文件使用 git checkout .
  • 撤销 untracked 文件使用 git clean -df
撤销暂存区修改

Note

场景:工作区修改已经 add,但还没有 commit

在 README 中新增如下一行,并 add 到暂存区:

坚决拥护甜的!!表示无法想象咸豆脑是神马样的!

查看状态:

$ git status

On branch master

Changes to be committed:

  (use "git reset HEAD <file>..." to unstage)



    modified: README.md

提示显示可以使用 git reset HEAD 把暂存区的修改退回到工作区:

$ git reset HEAD README.md

Unstaged changes after reset:

M README.md

此时暂存区的修改已经撤销,修改只存在于工作区(回到了上一个场景),如果需要继续撤销工作区修改,则参考上一小节。

Tip

要撤销已提交到暂存区和工作区的修改,需要分两步:

  1. git reset HEAD 撤销暂存区修改;
  2. git checkout -- 撤销工作区修改。
撤销版本库修改: 版本回退

Note

场景:修改已经 add,且 commit 到版本库

在 README 中添加如下内容,并提交到 git 仓库。

为了构建社会主义和谐社会,建议豆腐脑加打卤加白糖姜汁,各自让一步。

查看提交日志:

$ git log

commit 466938fc4266e0ed6061ae7c831d61c0936ebd4e (HEAD -> master)

Author: freyr <mrchan3030@foxmail.com>

Date: Sun May 27 12:25:44 2018 +0800



    update README.md



commit 3b83f4fc79f99d2749c058a713233b6928400435

Author: freyr <mrchan3030@foxmail.com>

Date: Sun May 27 11:35:55 2018 +0800



    add README.md

对于这种看似最棘手的状况,其实处理起来反而很简单。只需要使用 git reset 进行版本回退!

$ git reset --hard HEAD^

HEAD is now at 3b83f4f add README.md

Tip

  1. 一个 ^ 表示一个版本,可以多个(如 HEAD^^);另外也可以使用 HEAD~n 形式;或者直接指定 commit id;

  2. reset 选项:

    • --mixed: reset HEAD and index(default)
    • --soft: reset only HEAD
    • --hard: reset HEAD, index and working tree
远程仓库:GitHub

目前我们使用到的 git 命令都是在本地执行,如果你想通过 Git 分享你的代码或者与其他开发人员合作,就需要将数据放到一台其他开发人员能够连接的服务器上。

Github 是一个基于 git 的代码托管平台,付费用户可以建私人仓库,一般的免费用户只能使用公共仓库,也就是代码要公开。

创建远程库

进入 https://github.com/ 注册账号,回到主页点击 Start a project 进入代码仓库创建页面:

_images/github-create.png

依次填写、勾选、确认,完成创建。

_images/github-done.png

注意上面的 URL 就是远程仓库的地址。

添加 SSH Key

在家目录下创建 SSH Key:

$ ssh-keygen -t rsa -C "you_email@example.com"

一路回车使用默认值,在家目录的 .ssh 子目录里新增 id_rsaid_rsa.pub 两个文件(SSH Key 密钥对),id_rsa 是私钥,不能泄露出去;id_rsa.pub 是公钥,可以放心告诉别人。

登录 Github,进入 settings —— SSH and GPG keys —— New SSH key,将 id_rsa.pub 的内容粘贴到 Key 内完成添加 SSH key。

_images/github-ssh.png

使用如下目录验证是够成功:

$ ssh -T git@github.com

提示 “You’ve successfully authenticated, but GitHub does not provide shell access.” 表示成功连上 github。

与远程库关联

在要上传的仓库内,使用如下命令添加远程地址(替换为自己的仓库地址):

$ git remote add origin https://github.com/ifreyr/learning-git.git
查看远程分支

使用 git remote 查看远程库信息:

$ git remote

origin

可以使用 -v 显示详细信息:

$ git remote -v

origin      https://github.com/ifreyr/learning-git.git (fetch)

origin      https://github.com/ifreyr/learning-git.git (push)
推送本地分支到远程

然后使用 git push 将本地分支的更新推送到远程:

$ git push -u origin master

Counting objects: 3, done.

Writing objects: 100% (3/3), 217 bytes | 217.00 KiB/s, done.

Total 3 (delta 0), reused 0 (delta 0)

To https://github.com/ifreyr/learning-git.git

 * [new branch] master -> master

Branch master set up to track remote branch master from origin.

Tip

使用 -u 选项指定一个默认主机(因为本地分支可能与多个主机存在关系),这样以后就可以不加任何参数使用 git push 了:

$ git push origin master

如果当前分支只有一个追踪分支,主机名和分支名可以省略:

$ git push

现在,Github 项目主页就和本地仓库一样了:

_images/github-master.png
拉取远程分支到本地

在多人协作下,远程分支更新通常都超前于本地分支,所以需要使用 git pull 从远程拉取最新更新到本地:

$ git pull origin master

同样在只有一个追踪分支时可以省略主机和分支名:

$ git pull

Note

git pull 命令的作用是:取回远程主机某个分支的更新,再与本地的指定分支合并。

在默认模式下,git pull 使用给定的参数运行 git fetch,并调用 git merge 将检索到的分支头合并到当前分支中。 使用 --rebase,它运行 git rebase 而不是 git merge

在实际使用中,git fetch 更安全一些,因为在 merge 前,我们可以查看更新情况,然后再决定是否合并。

Note

git fetch 和 git pull 区别:

  1. git fetch 从远程获取最新版本到本地,不会自动合并。

    $ git fetch origin master
    
    # 下载 origin 的 master 主分支到本地 ``origin/master`` 分支上
    
  2. git pull 从远程获取最新版本并 merge 到本地

    $ git pull origin master
    
    # 下载远程 master 分支到本地 ``origin/master`` 并合并到本地 master
    
从远程库克隆

前面介绍了先有本地库,后有远程库,如何关联本地到远程。

如果项目从零开始,最好的方式是先创建远程库,然后从远程库克隆。使用命令 git clone

$ git clone https://github.com/ifreyr/learning-git.git
远程操作示意图
_images/git-remote.jpg
多人协作:分支

分支就是科幻电影里面的平行宇宙,当你正在电脑前努力学习 git 的时候,另一个你正在另一个平行宇宙里努力学习 SVN。

如果两个平行宇宙互不干扰,那对现在的你也没啥影响。不过,在某个时间点,两个平行宇宙合并了,结果,你既学会了 git 又学会了 SVN!

_images/git-svn.png

几乎每一种版本控制系统都以某种形式支持分支。使用分支意味着你可以从开发主线上分离开来,然后在不影响主线的同时继续工作。例如,我们发布了 1.0 版本的产品,可能需要创建一个分支,以便将 2.0 功能的开发与 1.0 版本中错误修复分开。

每次提交,git 都把它们串成一条时间线。这条时间线就是一个分支。当创建版本库的时候,默认就创建了主分支 master。Git 使用 master 指向最新的提交,再用 HEAD 指向 master,就能确定当前的分支,以及当前分支的提交点:

_images/git-branch01.png

每次提交,master 分支都会向前移动一步,随着不断的提交,master 分支的时间线也越来越长。

列出分支

使用 git branch 列出本地分支:

$ git branch
* master

显示目前只有一个分支 master,且 * 表示当前处于 master 分支。

Tip

使用 -a 选项能同时显示本地和远程分支。

创建分支

使用命令 git branch 创建分支:

$ git branch dev

列出分支:

$ git branch

  dev

* master

当创建新分支 dev 时,git 新建了一个指针 dev,指向 master 相同的提交:

_images/git-branch02.png
切换分支

使用命令 git checkout 切换分支:

$ git checkout dev

Switched to branch 'dev'

Tip

可以使用 git checkout -b 创建并切换到该分支(相当于 git branch + git checkout)。

查看当前分支:

$ git branch

* dev

  master

当从 master 分支切换到 dev 分支时,git 把 HEAD 指向 dev,就表示当前分支在 dev 上:

_images/git-branch03.png

Git 创建和切换分支很快,因为除了增加一个 dev 指针,修改下 HEAD 的指向,工作区的文件没有任何变化。

从现在开始,对工作区的修改和提交都是针对 dev 分支,比如新提交一次后,dev 指针往前移动一步,而 master 指针不变:

_images/git-branch04.png
合并分支

如果在 dev 上工作完成,就可以把 dev 合并到 master 上。具体操作分两步:

第一步,切换回 master 分支:

$ git checkout master

Switched to branch 'master'

Your branch is up-to-date with 'origin/master'.

第二步,合并 dev 分支到 master

$ git merge dev

Updating 3b83f4f..78c9a3c

Fast-forward

 README.md | 1 +

 1 file changed, 1 insertion(+)

以上合并操作直接把 master 指向 dev 的当前提交:

_images/git-branch05.png

所以,git 合并分支也很快,就改改指针,工作区内容也不变。

合并完成后,就可以使用 git branch -d 删除 dev 分支(还是 git branch 命令,加了 -d 选项):

$ git branch -d dev
Deleted branch dev (was 78c9a3c).

删除分支就是把 dev 指针给删掉,删掉后就只剩下一条 master 分支:

_images/git-branch06.png
解决冲突

合并分支往往也不是一帆风顺,当多分支同时修改统一文件,合并时就会出现冲突。

创建分支 feature1 并修改 README 内容为:

甜豆脑好吃

而 master 分支修改 README 内容为:

咸豆脑好吃

现在,masterfeature1 分支各自都有新的提交:

_images/git-branch07.png

这种情况下,git 只能试图把各自的修改合并起来,但这种合并就可能会有冲突:

$ git merge feature1

Auto-merging README.md

CONFLICT (content): Merge conflict in README.md

Automatic merge failed; fix conflicts and then commit the result.

提示有冲突,查看 README:

$ cat README.md

<<<<<<< HEAD

咸豆脑好吃

=======

甜豆脑好吃

>>>>>>> feature1

Git用 <<<<<<<=======>>>>>>> 标记出不同分支的内容。修改 README 如下:

咸豆脑好吃

甜豆脑也好吃

解决好冲突再提交:

$ git commit -am 'conflict fixed'

[master d88e6b6] conflict fixed

现在,masterfeature1 分支变成:

_images/git-branch08.png

Tip

合并分支时,加上 --no-ff 参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而默认使用的 fast forward 合并就看不出来曾经做过合并。

删除分支

删除本地分支:

$ git branch -d ${branch-name}

删除远程分支:

$ git  push origin -d ${branch-name}
分支管理策略

在实际开发中,我们应该按照几个基本原则进行分支管理:

  1. master 分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
  2. develop 分支是不稳定的,到某个时候,比如 1.0 版本发布时,再把 develop 分支合并到 master 上,在 master 分支发布 1.0 版本;
  3. 团队成员在开发时只与 develop 分支打交道,在 develop 上创建各种分支(feature, bugfix, hotfix)。在开发完成后合并到 develop 分支并删除功能分支。

所以,团队合作的分支看起来就像这样:

_images/git-flow.png
多人协作

多人协作的工作模式通常是这样:

  1. 试图用 git push 推送自己的修改;
  2. 如果推送失败,则因为远程分支比你的本地更新,需要先用 git pull 试图合并;
  3. 如果合并有冲突,则解决冲突,并在本地提交;
  4. 没有冲突或者解决掉冲突后,再用 git push 推送就能成功。
在本地创建和远程分支对应分支

本地和远程分支名称最好一样。

$ git checkout -b branch-name origin/branch-name
关联本地分支和远程分支

如果 git pull 提示 no tracking information,则说明本地分支和远程分支的链接关系没有创建:

$ git branch --set-upstream branch-name origin/branch-name
版本库快照:标签

发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。

Git 的标签虽然是版本库的快照,但其实它就是指向某个 commit 的指针(跟分支很像,但是分支可以移动,标签不能移动),所以,创建和删除标签都是瞬间完成的。

标签就是一个让人容易记住的有意义的名字,它跟某个 commit 绑在一起。因为标签总是和某个 commit 挂钩。如果这个 commit 既出现在两个分支上,那么在这两个分支上都可以看到这个标签。

创建标签

在需要打标签的分支上使用 git tag 创建标签:

$ git tag v1.0

以上默认在最新提交上打标签,可以使用 commit id 将标签打在历史提交上:

$ git tag v0.1 78c9

可以创建带有说明的标签,用 -a 指定标签名,-m 指定说明文字:

$ git tag -a v0.2 -m 'version 0.2 released' d88e
查看标签

使用命令 git tag 查看所有标签:

$ git tag

v0.1

v0.2

v1.0

Attention

标签不是按时间顺序列出,而是按字母排序的。

可以用 git show 查看标签详细信息:

$ git show v0.1

commit 78c9a3c63ed6c62cd4175cbf1ed7c29cd64b300a (tag: v0.1)

Author: freyr <mrchan3030@foxmail.com>

Date: Sun May 27 21:54:18 2018 +0800



    add title to README.md



diff --git a/README.md b/README.md

index e69de29..a125ae6 100644

--- a/README.md

+++ b/README.md

@@ -0,0 +1 @@
推送标签到远程

使用命令 git push 推送标签到远程:

$ git push origin v1.0

Counting objects: 13, done.

Delta compression using up to 8 threads.

Compressing objects: 100% (5/5), done.

Writing objects: 100% (13/13), 1.01 KiB | 1.01 MiB/s, done.

Total 13 (delta 2), reused 0 (delta 0)

remote: Resolving deltas: 100% (2/2), done.

To https://github.com/ifreyr/learning-git.git

 * [new tag] v1.0 -> v1.0

Tip

多个未推送的本地标签,可以一次性推送到远程:

$ git push origin --tags

Counting objects: 1, done.

Writing objects: 100% (1/1), 166 bytes | 166.00 KiB/s, done.

Total 1 (delta 0), reused 0 (delta 0)

To https://github.com/ifreyr/learning-git.git

 * [new tag] v0.1 -> v0.1

 * [new tag] v0.2 -> v0.2
删除标签
删除本地标签

如果标签打错了,可以使用 git tag -d 删除:

$ git tag -d v0.1

Deleted tag 'v0.1' (was 78c9a3c)
删除远程标签

删除远程标签需要两步操作:

第一步,删除本地标签:

$ git tag -d v0.1

第二步,使用 git push 删除远程标签:

$ git push origin :refs/tags/v0.1

To https://github.com/ifreyr/learning-git.git

 - [deleted] v0.1
其他主题
储藏

经常有这样的事情发生,当你正在进行项目中某一部分的工作,里面的东西处于一个比较杂乱的状态,而你想转到其他分支上进行一些工作。问题是,你不想提交进行了一半的工作,否则以后你无法回到这个工作点。解决这个问题的办法就是 git stash 命令。

“‘储藏”可以获取你工作目录的中间状态——也就是你修改过的被追踪的文件和暂存的变更——并将它保存到一个未完结变更的堆栈中,随时可以重新应用。

$ git stash

Saved working directory and index state \

  "WIP on master: 049d078 added the index file"

HEAD is now at 049d078 added the index file

(To restore them type "git stash apply")

这时,工作区就干净了,你可以方便地切换到其他分支工作;你的变更都保存在栈上。要查看现有的储藏,你可以使用 git stash list

$ git stash list

stash@{0}: WIP on master: 049d078 added the index file

stash@{1}: WIP on master: c264051 Revert "added file_size"

stash@{2}: WIP on master: 21d80a5 added number to log

使用命令 git stash apply 就可以重新应用你刚刚实施的储藏:

$ git stash apply

# On branch master

# Changes not staged for commit:

# (use "git add <file>..." to update what will be committed)

#

# modified: index.html

# modified: lib/simplegit.rb

#

如果你想应用更早的储藏,你可以通过名字指定它,如果你不指明,git 默认使用最近一次的储藏:

$ git stash apply stash@{2}

apply 选项只尝试应用储藏的工作,储藏的内容仍然在栈上。可以使用 git stash drop 从栈上移除:

$ git stash drop stash@{0}

Dropped stash@{0} (364e91f3f268f0900bc3ee613f9f733e82aaed43)

Tip

可以使用 git stash pop 来重新应用储藏,同时立刻将其从堆栈中移走。

忽略特殊文件

有些时候,你必须把某些文件放到Git工作目录中,但又不能提交它们,比如保存了数据库密码的配置文件。在 Git 工作区的根目录下创建一个特殊的 .gitignore 文件,然后把要忽略的文件名填进去,Git 就会自动忽略这些文件。

不需要从头写 .gitignore,Github 已经准备了各种配置文件:

有些时候,你想添加一个文件到 git,但发现添加不了,原因可能是这个文件被 .gitignore 忽略了,可以使用 git check-ignore 检查看看:

$ git check-ignore -v conf.json

.gitignore:3:*.json conf.json

可以修改 .gitignore 文件或者使用 -f 强制添加:

$ git add -f conf.json
git rebase

Tip

本小节图片箭头和通常时间线方向相反,如果不适可以找找其他教程。

假设你现在基于远程分支 origin ,创建一个叫 mywork 的分支:

_images/git-rebase01.png

现在在 originmywork 两个分支上分别开发:

_images/git-rebase02.png

通常使用 git merge 合并,结果如图:

_images/git-rebase03.png

但是,如果你想让 mywork 分支历史看起来像没有经过任何合并一样,也可以用 git rebase,当 mywork 分支更新之后,它会指向这些新创建的提交,而那些老的提交会被丢弃。:

$ git checkout mywork  # 在 mywork 上执行 rebase

$ git rebase origin
_images/git-rebase04.png _images/git-rebase05.png _images/git-rebase06.png _images/git-rebase07.png

在 rebase 的过程中,也许会出现冲突。在这种情况,git 会停止 rebase 并会让你去解决冲突;在解决完冲突后,用 git add 命令去更新这些内容的索引(index), 然后,你无需执行 git commit,只要执行:

$ git rebase --continue
Git 基础教程
Git 最基本的配置和使用。

Gitlab CI/CD

Gitlab CI 简介

Note

本文只对 gitlab-ci 进行概览,不涉及集成运行环境 gitlab-runner 的搭建使用以及配置文件 .gitlab-ci.yml 的语法说明,后续会有相关专题介绍。

什么是持续集成

持续集成是一种软件开发实践,即团队开发成员经常集成他们的工作,通过每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。

软件开发一般流程:编码 —— 测试 —— 封装/部署,除了代码自己实现,测试,封装和部署都可以自动化。

如何集成

有各种集成工具可以使用,主流的 travis(github 默认集成工具)和 jekins,而 gitlab 自带 Gitlab CI/CD

_images/gitlab-ci-pipeline.png

使用 gitlab-ci 只要自己编写 .gitlab-ci.yml 就可以,例如简单的 Python:Miniconda 环境构建:

before_script:
  - docker -v

python-3.5-centos7:
  script:
    - cd python/3.5/centos7
    - docker build -t python:3.5-centos7 .

Attention

  1. .gitlab-ci.yml 要放在 git 仓库的根目录,具体语法参考:.gitlab-ci.yml 语法
  2. 集成任务的实际运行依赖于 gitlab-runner,否则任务一直处于 stuck (挂起)状态。具体参考:gitlab-runner 安装gitlab-runner 配置
_images/gitlab-job.png
一个例子

gitlab-ci 集成 docker 镜像构建

仓库目录结构如下:

.
├── .gitignore
├── .gitlab-ci.yml
├── python
│   └── 3.5
│       ├── centos7
│       │   ├── docker-compose.yml
│       │   ├── Dockerfile
│       │   └── pip.conf
│       ├── debian9
│       │   ├── docker-compose.yml
│       │   ├── Dockerfile
│       │   └── pip.conf
│       └── ubuntu16.04
│           ├── docker-compose.yml
│           ├── Dockerfile
│           └── pip.conf
└── README.md

只要在根目录添加 .gitlab-ci.yml 文件,分支的任何修改(提交或合并),就会触发 gitlab-ci 集成任务。可以在 CI/CD —— jobs 下查看任务执行。

_images/gitlab-job1.png

点击 status 图标可以查看任务细节:

_images/gitlab-job2.png _images/gitlab-job3.png

构建成功则 status 图标为绿色 passed,否则为红色 failed,同时发送错误邮件。

_images/gitlab-job4.png

Gitlab Runner 安装使用

安装 Runner

本文使用 docker 运行 Runner,所以只需要启动容器即可工作,其他安装方式参考:Install GitLab Runner

Tip

Docker 的基础知识和使用参考:Docker 基础

为方便容器管理,使用 docker-compose 管理容器,compose 文件如下:

version: '3'
services:
  gitlab-runner:
    image: gitlab/gitlab-runner
    container_name: gitlab-runner
    volumes:
      - $PWD/gitlab-runner/config:/etc/gitlab-runner
      - /var/run/docker.sock:/var/run/docker.sock
    privileged: true
    restart: always

使用如下命令启动 gitlab-runner:

$ docker-compose up -d
获取 Gitlab token

根据 token 的来源权限不同,gitlab-runner 对应的有三种类型(Configuring GitLab Runners ):

  • Specific Runner: 一个 Runner 对应一个 Project,只能在本项目内使用
  • Group Runner: 一个 Runner 对应一个 Group,群组内的项目都可以使用
  • Shared Runner: 可应用于所有项目

以 Specific Runner 为例,进入项目内 Settings —— CI/CD —— Runner settings 即可看到 URL 和 token:

_images/gitlab-runner-token.png
注册 Runner

项目只有注册了 Runner 才能完成实际执行,使用命令 gitlab-runner register 进行注册,在我们的 docker 环境下使用:

$ docker exec -it gitlab-runner gitlab-runner register

完成几个问题交互,即完成注册:

_images/gitlab-runner-register.png

其中,URLtoken 就是上一步在项目内得到的,descriptiontag 可以随意填写,executor 根据实际需要选择,这里选择使用 docker,所以下一步的默认镜像使用 docker:stable

Note

也可以在 register 的时候使用参数指定所有设置,gitlab-runner register -h 查看可用选项。

如果设置了 tag,以后的 .gitlab-ci.yml 使用中都必须通过显式指定 tags 来选择执行 runner。

正常的注册到此就结束,但 docker 还需要进一步的修改配置。

使用 docker 的额外配置

gitlab-runner 的配置文件为 /config/config.toml,需要修改 privilegedvolumes

修改后的配置如下:

[[runners]]
  name = "docker runner"
  url = "https://gitlab.*****.com/"
  token = "34ac*******************"
  executor = "docker"
  [runners.docker]
    tls_verify = false
    image = "docker:stable"
    privileged = true
    disable_cache = false
    volumes = ["/cache", "/var/run/docker.sock:/var/run/docker.sock"]
    shm_size = 0
  [runners.cache]

最后需要重启 runner 容器生效:

$ docker-compose up -d

现在,在 Runner settings 里面就能看到注册好的 Runner 了:

_images/gitlab-runner-activated.png

Gitlab CI 配置文件使用指南

Gitlab CI 使用 .gitlab-ci.yml 文件进行持续集成管理,通过使用一组预定义的指令完成所有控制。

执行单元 Job

Job 是 gitlab-ci 的最小执行单元,所有的操作都被合理划分在各个 job 内,通过 script 指令控制执行。

Attention

Job 必须包含 script 指令。

下面定义了两个任务:

job1:
  script: "execute-script-for-job1"

job2:
  script: "execute-script-for-job2"

Note

Job 名称可以随意取,除了 gitlab 保留字:image, services, stages, types, before_script, after_script, variables, cache。

script

script 指令中,可以使用纯 shell 命令或执行 shell 脚本。这也是集成工作 最核心 的部分(考察的是 shell 脚本能力)。

job1:
  script: "bundle exec rspec"

job2:
  script:
    - uname -a
    - bundle exec rspec

通过定义 job 并写好 script,我们已经能完成了最基本的集成工作。下面的指令都是为了增加扩展和功能。

before_script & after_script

如果多个 jobs 有一些共同的操作,除了在各自 script 里都添加之外,我们有更好的方法:

  • 使用 before_script 添加预处理,如环境的配置
  • 使用 after_script 完成后处理,如资源的清理

Note

before_script/after_script 可同时用于全局或 Job 之内,Job 内会覆盖全局的设置。

before_script:
  - global before script

job:
  before_script:
    - execute this instead of global before script
  script:
    - my command
  after_script:
    - execute this after my script
stages & stage

为了使多个 Jobs 好管理,一种好的实践是使用 stages 将任务按照操作流程进行划分。Gitlab 默认提供了 build, test, deploy 三种 stage,我们自己可以根据需要进行扩充。

在全局定义 stages

stages:
  - build
  - test
  - deploy

Attention

stages 是有顺序的:

  • 靠前 stage 的 jobs 会优先执行,同一级的 jobs 会并行执行
  • 默认情形下只有前面的 jobs 都执行成功才执行下一级(如 build 的所有 jobs 都成功才执行 test 的 jobs,只要有一个失败集成任务就失败了)。

定义好的 stages 可以在各个 jobs 内通过 stage 指令进行使用:

stages:
  - build
  - test
  - deploy

job 1:
  stage: build
  script: make build dependencies

job 2:
  stage: build
  script: make build artifacts

job 3:
  stage: test
  script: make test

job 4:
  stage: deploy
  script: make deploy
tags

Tags 用于指定本项目的执行 Runner,对 gitlab-runner 的使用可参考 Gitlab Runner 安装使用

下面任务指定了同时标记有 ruby 和 postgres 的 Runner 执行集成:

job:
  tags:
    - ruby
    - postgres
variables

可以在 .gitlab-ci.yml 中定义变量进行重复使用,这就需要 variables 指令。同样的,可以在全局和 Job 内使用,且 Job 会覆盖全局相同的变量。

variables:
  VERSION_SUFFIX: v1.0.0

job:
  script:
    - VERSION="${VERSION_SUFFIX}.${CI_JOB_ID}"

Note

gitlab-ci 也预定义了一些 环境变量 ,上面的 CI_JOB_ID 就是其一。

allow_failure

stages 部分提到只有前一个 stage 的所有任务都成功,才会执行下一 stage 的任务。这在绝大部分时候是合适的,但有些时候我们需要弱化一下规则,忽略一些不太重要的任务(如某个测试)。

allow_failure 允许整个 pipeline 出现一些小的失败,下面例子中,job1 和 job2 同时执行,但如果 job1 失败,job3 仍然能够继续执行:

job1:
  stage: test
  script:
    - execute_script_that_will_fail
  allow_failure: true

job2:
  stage: test
  script:
    - execute_script_that_will_succeed

job3:
  stage: deploy
  script:
    - deploy_to_staging
when

allow_failure 在自己 job 内控制对外的影响(自己出错了要不要影响他人),类似的可以使用 when 控制自己对上层 jobs 的响应(他人出错了自己如何面对)。

when 指令可选值包括:

  • on_success,只有上层 jobs 全成功才执行
  • on_failure,只要上层有一个失败就执行
  • always,忽略上层执行结果,总是执行
  • manual,手动控制任务的执行(一般用在项目部署)

下面例子中,cleanup_build_job 只有在 build_job 失败的时候才会执行,cleanup_job 总是会执行,而 deploy_job 手动执行:

stages:
  - build
  - cleanup_build
  - test
  - deploy
  - cleanup

build_job:
  stage: build
  script:
    - make build

cleanup_build_job:
  stage: cleanup_build
  script:
    - cleanup build when failed
  when: on_failure

test_job:
  stage: test
  script:
    - make test

deploy_job:
  stage: deploy
  script:
    - make deploy
  when: manual

cleanup_job:
  stage: cleanup
  script:
    - cleanup after jobs
  when: always
retry

当任务执行失败,可以使用 retry 进行重试:

test:
  script: rspec
  retry: 2
only & except

onlyexcept 用于控制代码分支和 tag 的选择:

  • only 圈定的分支和 tags 会执行该任务
  • except 圈定的分支和 tags 不会执行该任务

二者使用的一些规则:

  • onlyexcept 可以同时使用,最终取条件的交集
  • onlyexcept 都支持正则表达式
  • onlyexcept 可以指定代码仓库地址

示例:

job1:
  # 只允许以 issue 开始的 refs,忽略所有分支
  only:
    - /^issue-.*$/
  # use special keyword
  except:
    - branches

job2:
  # 运行 gitlab-org/gitlab-ce 上所有分支,排除 master 分支
  only:
    - branches@gitlab-org/gitlab-ce
  except:
    - master@gitlab-org/gitlab-ce
image & services

image 指定镜像名称,services 提供其他镜像(通常为数据库),使得运行任务时主镜像能连接到这些镜像。imageservices 支持全局和 Job 级定义。

before_script:
  - bundle install

test:2.1:
  image: ruby:2.1
  services:
  - postgres:9.3
  script:
  - bundle exec rake spec

test:2.2:
  image: ruby:2.2
  services:
  - postgres:9.4
  script:
  - bundle exec rake spec
其他指令

Gitlab 提供的其他指令,并不太常用,有需要的可以自行查看文档 Configuration

  • environment
  • cache
  • artifacts
  • dependencies
  • coverage
  • include
一个较完整的 .gitlab-ci.yml
image: docker:stable

variables:
  PROJECT: kerberos
  REPOSITORY: username/${PROJECT}
  REGISTRY_USER: username
  REGISTRY_TOKEN: password
  VERSION_SUFFIX: py35-centos7-v1.0.0

before_script:
  - docker login -u ${REGISTRY_USER} -p ${REGISTRY_TOKEN}

stages:
  - build

kerberos:
  stage: build
  tags:
    - docker
  script:
    - VERSION="${VERSION_SUFFIX}.${CI_JOB_ID}"
    - IMAGE_NAME="${REPOSITORY}:${VERSION}"
    - cd kerberos/py35/centos7
    - docker build --network=host -t ${IMAGE_NAME} .
    - docker push ${IMAGE_NAME}
    - docker rmi ${IMAGE_NAME}

after_script:
  - docker logout
Gitlab CI 简介
Gitlab CI 流程速览。
Gitlab Runner 安装使用
Gitlab CI 运行环境 Runner 安装与使用。
Gitlab CI 配置文件使用指南
Gitlab CI 配置文件语法和使用。
Git 基础
Git 使用指南。
Gitlab CI/CD
Gitlab 持续集成。

Web Crawler

爬虫工具

Fiddler 安装配置

Fiddler 安装

官网 http://www.telerik.com/fiddler 下载安装。

Fiddler 客户端配置
连接设置

TOOLS - Options - Connections,如图:

_images/fiddler1.png
HTTPS 支持

TOOLS - Options - HTTPS,如图:

_images/fiddler2.png
Firefox 配置

Tip

推荐在 foxyproxy 设置!这样所有代理都统一管理,只需要按需求切换。

Chrome 使用 SwitchyOmega 。

方法一:使用浏览器设置

网上教程都是在浏览器 选项 - 高级 - 网络 - 设置 如下设置:

_images/fiddler3.png
方法二:使用 foxyproxy 管理

在 foxyproxy 中新建代理服务器,参数如下:

_images/fiddler4.png

需要抓包时候选择这个代理就可以。

添加证书

如果出现如下“不安全的连接”,需要给浏览器添加 fiddler 证书。

_images/fiddler5.png
  1. 从 Fiddler 导出证书

    TOOLS - Options - HTTPS - Actions - Export Root Certificate to Desktop,此时桌面会出现文件 “FiddlerRoot.cer”

  2. 导入证书到浏览器

    选项 - 高级 - 证书 - 查看证书 - 证书机构 - 导入 - 选择所有信任选项

    _images/fiddler6.png

此时访问网站应该能看到各种连接刷屏

_images/fiddler7.png
Fiddler 安装配置
Windows 下强大的 HTTP 抓包工具 Fiddler。
爬虫工具
爬虫工具汇总。

RESTful

RESTful 基础

理解 RESTful 架构

Note

本文转载自:理解 RESTful 架构

越来越多的人开始意识到,网站即软件,而且是一种新型的软件。

这种”互联网软件”采用客户端/服务器模式,建立在分布式体系上,通过互联网通信,具有高延时(high latency)、高并发等特点。

网站开发,完全可以采用软件开发的模式。但是传统上,软件和网络是两个不同的领域,很少有交集;软件开发主要针对单机环境,网络则主要研究系统之间的通信。互联网的兴起,使得这两个领域开始融合,现在我们必须考虑,如何开发在互联网环境中使用的软件。

RESTful 架构,就是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。

但是,到底什么是 RESTful 架构,并不是一个容易说清楚的问题。下面,我就谈谈我理解的 RESTful 架构。

起源

REST 这个词,是 Roy Thomas Fielding 在他 2000 年的博士论文中提出的。

Fielding 是一个非常重要的人,他是 HTTP 协议(1.0 版和 1.1 版)的主要设计者、Apache 服务器软件的作者之一、Apache 基金会的第一任主席。所以,他的这篇论文一经发表,就引起了关注,并且立即对互联网开发产生了深远的影响。

他这样介绍论文的写作目的:

“本文研究计算机科学两大前沿—-软件和网络—-的交叉点。长期以来,软件研究主要关注软件设计的分类、设计方法的演化,很少客观地评估不同的设计选择对系统行为的影响。而相反地,网络研究主要关注系统之间通信行为的细节、如何改进特定通信机制的表现,常常忽视了一个事实,那就是改变应用程序的互动风格比改变互动协议,对整体表现有更大的影响。我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。”
名称

Fielding 将他对互联网软件的架构原则,定名为 REST,即 Representational State Transfer 的缩写。我对这个词组的翻译是”表现层状态转化”。

如果一个架构符合 REST 原则,就称它为 RESTful 架构。

要理解 RESTful 架构,最好的方法就是去理解 Representational State Transfer 这个词组到底是什么意思,它的每一个词代表了什么涵义。如果你把这个名称搞懂了,也就不难体会 REST 是一种什么样的设计。

资源(Resources)

REST 的名称”表现层状态转化”中,省略了主语。”表现层”其实指的是”资源”(Resources)的”表现层”。

所谓”资源”,就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个 URI(统一资源定位符)指向它,每种资源对应一个特定的 URI。要获取这个资源,访问它的 URI 就可以,因此URI就成了每一个资源的地址或独一无二的识别符。

所谓”上网”,就是与互联网上一系列的”资源”互动,调用它的 URI。

表现层(Representation)

“资源”是一种信息实体,它可以有多种外在表现形式。我们把”资源”具体呈现出来的形式,叫做它的”表现层”(Representation)。

比如,文本可以用txt格式表现,也可以用 HTML 格式、XML 格式、JSON 格式表现,甚至可以采用二进制格式;图片可以用 JPG 格式表现,也可以用 PNG 格式表现。

URI 只代表资源的实体,不代表它的形式。严格地说,有些网址最后的 “.html” 后缀名是不必要的,因为这个后缀名表示格式,属于”表现层”范畴,而 URI 应该只代表”资源”的位置。它的具体表现形式,应该在 HTTP 请求的头信息中用 Accept 和 Content-Type 字段指定,这两个字段才是对”表现层”的描述。

状态转化(State Transfer)

访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。

互联网通信协议 HTTP 协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生”状态转化”(State Transfer)。而这种转化是建立在表现层之上的,所以就是”表现层状态转化”。

客户端用到的手段,只能是 HTTP 协议。具体来说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源(也可以用于更新资源),PUT 用来更新资源,DELETE 用来删除资源。

综述

综合上面的解释,我们总结一下什么是 RESTful 架构:

  1. 每一个 URI 代表一种资源;
  2. 客户端和服务器之间,传递这种资源的某种表现层;
  3. 客户端通过四个 HTTP 动词,对服务器端资源进行操作,实现”表现层状态转化”。
误区

RESTful 架构有一些典型的设计误区。

最常见的一种设计错误,就是 URI 包含动词。因为”资源”表示一种实体,所以应该是名词,URI 不应该有动词,动词应该放在 HTTP 协议中。

举例来说,某个 URI 是 /posts/show/1,其中 show 是动词,这个 URI 就设计错了,正确的写法应该是 /posts/1,然后用 GET 方法表示 show。

如果某些动作是 HTTP 动词表示不了的,你就应该把动作做成一种资源。比如网上汇款,从账户 1 向账户 2 汇款 500 元,错误的 URI 是:

POST /accounts/1/transfer/500/to/2

正确的写法是把动词 transfer 改成名词 transaction,资源不能是动词,但是可以是一种服务:

POST /transaction HTTP/1.1
Host: 127.0.0.1

from=1&to=2&amount=500.00

RESTful API 设计指南

Note

本文转载自:RESTful API 设计指南

网络应用程序,分为前端和后端两个部分。当前的发展趋势,就是前端设备层出不穷(手机、平板、桌面电脑、其他专用设备……)。

因此,必须有一种统一的机制,方便不同的前端设备与后端进行通信。这导致 API 构架的流行,甚至出现 “API First” 的设计思想。RESTful API 是目前比较成熟的一套互联网应用程序的 API 设计理论。我以前写过一篇《理解 RESTful 架构》,探讨如何理解这个概念。

今天,我将介绍 RESTful API 的设计细节,探讨如何设计一套合理、好用的 API。我的主要参考了两篇文章 https://codeplanet.io/principles-good-restful-api-design/https://bourgeois.me/rest/

协议

API 与用户的通信协议,总是使用 HTTPs 协议。

域名

应该尽量将 API 部署在专用域名之下。

https://api.example.com

如果确定 API 很简单,不会有进一步扩展,可以考虑放在主域名下。

https://example.org/api/
版本(Versioning)

应该将 API 的版本号放入 URL。

https://api.example.com/v1/

另一种做法是,将版本号放在 HTTP 头信息中,但不如放入 URL 方便和直观。Github 采用这种做法。

路径(Endpoint)

路径又称”终点”(endpoint),表示 API 的具体网址。

在 RESTful 架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的”集合”(collection),所以 API 中的名词也应该使用复数。

举例来说,有一个 API 提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。

https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/employees
HTTP 动词

对于资源的具体操作类型,由 HTTP 动词表示。

常用的 HTTP 动词有下面五个(括号里是对应的 SQL 命令)。

  • GET (SELECT):从服务器取出资源(一项或多项)。
  • POST (CREATE):在服务器新建一个资源。
  • PUT (UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
  • PATCH (UPDATE):在服务器更新资源(客户端提供改变的属性)。
  • DELETE (DELETE):从服务器删除资源。

还有两个不常用的 HTTP 动词。

  • HEAD:获取资源的元数据。
  • OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。

下面是一些例子。

GET /zoos:列出所有动物园
POST /zoos:新建一个动物园
GET /zoos/ID:获取某个指定动物园的信息
PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
DELETE /zoos/ID:删除某个动物园
GET /zoos/ID/animals:列出某个指定动物园的所有动物
DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物
过滤信息(Filtering)

如果记录数量很多,服务器不可能都将它们返回给用户。API 应该提供参数,过滤返回结果。

下面是一些常见的参数。

?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1:指定筛选条件

参数的设计允许存在冗余,即允许 API 路径和 URL 参数偶尔有重复。比如,GET /zoo/ID/animalsGET /animals?zoo_id=ID 的含义是相同的。

状态码(Status Codes)

服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的 HTTP 动词)。

  • 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
  • 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
  • 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
  • 204 NO CONTENT - [DELETE]:用户删除数据成功。
  • 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
  • 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
  • 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
  • 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
  • 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
  • 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
  • 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
  • 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

状态码的完全列表参见 https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html

错误处理(Error handling)

如果状态码是 4xx,就应该向用户返回出错信息。一般来说,返回的信息中将 error 作为键名,出错信息作为键值即可。

{
    error: "Invalid API key"
}
返回结果

针对不同操作,服务器向用户返回的结果应该符合以下规范。

  • GET /collection:返回资源对象的列表(数组)
  • GET /collection/resource:返回单个资源对象
  • POST /collection:返回新生成的资源对象
  • PUT /collection/resource:返回完整的资源对象
  • PATCH /collection/resource:返回完整的资源对象
  • DELETE /collection/resource:返回一个空文档
Hypermedia API

RESTful API 最好做到 Hypermedia,即返回结果中提供链接,连向其他 API 方法,使得用户不查文档,也知道下一步应该做什么。

比如,当用户向 api.example.com 的根目录发出请求,会得到这样一个文档。

{"link": {
    "rel": "collection https://www.example.com/zoos",
    "href": "https://api.example.com/zoos",
    "title": "List of zoos",
    "type": "application/vnd.yourformat+json"
}}

上面代码表示,文档中有一个 link 属性,用户读取这个属性就知道下一步该调用什么 API 了。rel 表示这个 API 与当前网址的关系(collection 关系,并给出该 collection 的网址),href 表示 API 的路径,title 表示 API 的标题,type 表示返回类型。

Hypermedia API 的设计被称为 HATEOAS。Github 的 API 就是这种设计,访问 api.github.com 会得到一个所有可用API的网址列表。

{
    "current_user_url": "https://api.github.com/user",
    "authorizations_url": "https://api.github.com/authorizations",
    ...
}

从上面可以看到,如果想获取当前用户的信息,应该去访问 api.github.com/user,然后就得到了下面结果。

{
    "message": "Requires authentication",
    "documentation_url": "https://developer.github.com/v3"
}

上面代码表示,服务器给出了提示信息,以及文档的网址。

其他
  1. API 的身份认证应该使用 OAuth 2.0 框架。
  2. 服务器返回的数据格式,应该尽量使用 JSON,避免使用 XML。

RESTful API 最佳实践

基本要求
  1. 当标准合理的时候遵守标准。
  2. API 应该对程序员友好,并且在浏览器地址栏容易输入。
  3. API 应该简单,直观,容易使用的同时优雅。
  4. API 应该具有足够的灵活性来支持上层ui。
  5. API 设计权衡上述几个原则。

你的 API 越容易使用,那么就会有越多的人去用它。

术语
  • 资源:一个对象的单独实例,如一只动物
  • 集合:一群同种对象,如动物
  • 端点:endpoint,也称为“路径”,表示 API 的具体网址
  • 幂等:无边际效应,多次操作得到相同的结果
  • URL 段:在 URL 里面以斜杠分隔的内容
协议

API 与用户的通信协议,总是使用 HTTPS 协议,确保交互数据的传输安全。

域名

应该尽量将 API 部署在专用域名之下,如果你的应用很庞大或者你预期它将会变的很庞大,那么将 API 放到子域下通常是一个好选择。这种做法可以保持某些规模化上的灵活性。。

https://api.example.com/

如果确定 API 很简单,不会有进一步扩展,可以考虑放在主域名下。

https://example.org/api/
版本

常见的三种方式:

  • 在 URI 中放版本信息:GET /v1/users/1
  • Accept Header:Accept: application/json+v1
  • 自定义 Header:X-Api-Version: 1

用第一种,虽然没有那么优雅,但最明显最方便:

https://api.example.com/v{n}/

版本号分为整形和浮点型:

  • 整形的版本号:大功能版本发布形式;具有当前版本状态下的所有 API 接口 ,例如:v1,v2
  • 浮点型:小版本号,只具备补充 API 的功能,其他api都默认调用对应大版本号的 API,例如:v1.1,v2.2

随着系统发展,总有一些 API 失效或者迁移,对失效的 API,返回 404 Not Found 或 410 Gone;对迁移的 API,返回 301 重定向。

URI

URI 表示资源,资源一般对应服务器端领域模型中的实体类。

URI 命名规范
  • 不用大写;
  • 用中杠 - 不用下划线 _,因为浏览器中超链接的默认效果是带下划线;
  • 参数列表要 encode;
  • 使用名词,URI 是指向资源的,而不是描述行为的,应使用名词而非动词来描述语义。
  • URI 中的名词表示资源集合,使用复数形式。

不推荐的 API:

/api/getArticle/1/
/api/updateArticle/1/
/api/deleteArticle/1/

推荐的 API:

/api/articles/

资源的获取、更新和删除分别通过 GET, PUT 和 DELETE 方法请求 API 即可:

GET /api/articles/    //文章列表
POST /api/articles/    // 文章添加
PUT /api/articles/    // 文章修改
DELETE /api/articles/    // 文章删除
资源表示

URI 表示资源的两种方式:资源集合、单个资源。

资源集合:

/zoos    //所有动物园
/zoos/1/animals    //id为1的动物园中的所有动物

单个资源:

/zoos/1    //id为1的动物园
资源子集

如果要获取一个资源子集,采用 nested routing 是一个优雅的方式:

/api/authors/gevin/articles/

另一种方式是基于过滤,这两种方式都符合规范,但语义不同:如果语义上将资源子集看作一个独立的资源集合,则使用 nested routing 感觉更恰当,如果资源子集的获取是出于过滤的目的,则使用 filter 更恰当。

可以使用过滤处理那些获取资源集合的请求。所以只要出现 GET 的请求,就应该通过 URL 来过滤信息:

?type=1&age=16    // 允许一定的冗余,如 /zoos/1 与 /zoos?id=1
?sortby=age&order=asc
?page=2&per_page=100
?limit=10&offset=3
减少路径嵌套

在一些有父路径/子路径嵌套关系的资源数据模块中,路径可能有非常深的嵌套关系,例如:

/orgs/{org_id}/apps/{app_id}/dynos/{dyno_id}

推荐在根路径下指定资源来限制路径的嵌套深度。使用嵌套指定范围的资源,例如在下面的情况下:

/orgs/{org_id}
/orgs/{org_id}/apps

/apps/{app_id}
/apps/{app_id}/dynos

/dynos/{dyno_id}
Request
HTTP 方法

GET,从服务器查询资源(一个具体的资源或者一个资源列表。):

GET /zoos
GET /zoos/1
GET /zoos/1/employees

POST,在服务器新建单个资源(一般向“资源集合”型 URI 发起):

POST /animals    //新增动物
POST /zoos/1/employees    //为id为1的动物园雇佣员工

PUT,以整体的方式更新服务器上的一个资源( 一般向“单个资源”型 URI 发起):

PUT /animals/1
PUT /zoos/1

PATCH,只更新服务器上一个资源的一个属性( 一般向“单个资源”型 URI 发起)。

DELETE,从服务器删除资源:

DELETE /zoos/1/employees/2
DELETE /zoos/1/animals    //删除id为1的动物园内的所有动物

HEAD,获取一个资源的元数据,如数据的哈希值或最后的更新时间。

OPTION,获取客户端能对资源做什么操作的信息。

安全性和幂等性
  • 安全性:不会改变资源状态,可以理解为只读的;
  • 幂等性:执行 1 次和执行 N 次,对资源状态改变的效果是等价的。

GET 和 HEAD 方法必须始终是安全的。

HTTP 方法 安全性 幂等性
GET
POST × ×
PUT ×
DELETE ×

安全性和幂等性均不保证反复请求能拿到相同的 Response。以 DELETE 为例,第一次 DELETE 返回 200 表示删除成功,第二次返回 404 提示资源不存在,这是允许的。

消息体

只用以下常见的 3 种 body format:

Content-Type: application/json

POST /v1/animal HTTP/1.1
Host: api.example.org
Accept: application/json
Content-Type: application/json
Content-Length: 24

{
  "name": "Gir",
  "animalType": "12"
}

Content-Type: application/x-www-form-urlencoded

POST /login HTTP/1.1
Host: example.com
Content-Length: 31
Accept: text/html
Content-Type: application/x-www-form-urlencoded

username=root&password=Zion0101

Content-Type: multipart/form-data

Response
不要包装

Response 的 body 直接就是数据,不要做多余的无意义包装。

{
    "login": "octocat",
    "id": 1,
    "node_id": "MDQ6VXNlcjE=",
    "avatar_url": "https://github.com/images/error/octocat_happy.gif",
    ...
}

错误示例:

{
    "success":true,
    "data":{"id":1,"name":"xiaotuan"},
}
各 HTTP 方法处理后数据格式
HTTP 方法 Response 格式
GET 单个对象、集合
POST 新增成功的对象
PUT/PATCH 更新成功的对象
DELETE
HTTP 状态码
HTTP 状态码分类
分类 分类描述
1xx 信息,服务器收到请求,需要请求者继续执行操作
2xx 成功,操作被成功接收并处理
3xx 重定向,需要进一步的操作以完成请求
4xx 客户端错误,请求包含语法错误或无法完成请求
5xx 服务器错误,服务器在处理请求的过程中发生了错误

一些说明:

  • 1xx 范围的状态码是保留给底层 HTTP 功能使用的,并且估计在你的职业生涯里面也用不着手动发送这样一个状态码出来。
  • 2xx 范围的状态码是保留给成功消息使用的,你尽可能的确保服务器总发送这些状态码给用户。
  • 3xx 范围的状态码是保留给重定向用的。大多数的API不会太常使用这类状态码,但是在新的超媒体样式的 API 中会使用更多一些。
  • 4xx 范围的状态码是保留给客户端错误用的。例如,客户端提供了一些错误的数据或请求了不存在的内容。这些请求应该是幂等的,不会改变任何服务器的状态。
  • 5xx 范围的状态码是保留给服务器端错误用的。这些错误常常是从底层的函数抛出来的,并且开发人员也通常没法处理。发送这类状态码的目的是确保客户端能得到一些响应。收到 5xx 响应后,客户端没办法知道服务器端的状态,所以这类状态码是要尽可能的避免。
HTTP 状态码列表
状态码 英文名称 中文描述
100 Continue 继续。客户端应继续其请求
101 Switching Protocols 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议
200 OK 请求成功。一般用于GET与POST请求
201 Created 已创建。成功请求并创建了新的资源
202 Accepted 已接受。已经接受请求,但未处理完成
203 Non-Authoritative Information 非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本
204 No Content 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档
205 Reset Content 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域
206 Partial Content 部分内容。服务器成功处理了部分GET请求
300 Multiple Choices 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择
301 Moved Permanently 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
302 Found 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
303 See Other 查看其它地址。与301类似。使用GET和POST请求查看
304 Not Modified 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源
305 Use Proxy 使用代理。所请求的资源必须通过代理访问
306 Unused 已经被废弃的HTTP状态码
307 Temporary Redirect 临时重定向。与302类似。使用GET请求重定向
400 Bad Request 客户端请求的语法错误,服务器无法理解
401 Unauthorized 请求要求用户的身份认证
402 Payment Required 保留,将来使用
403 Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求
404 Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置”您所请求的资源无法找到”的个性页面
405 Method Not Allowed 客户端请求中的方法被禁止
406 Not Acceptable 服务器无法根据客户端请求的内容特性完成请求
407 Proxy Authentication Required 请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权
408 Request Time-out 服务器等待客户端发送的请求时间过长,超时
409 Conflict 服务器完成客户端的PUT请求是可能返回此代码,服务器处理请求时发生了冲突
410 Gone 客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置
411 Length Required 服务器无法处理客户端发送的不带Content-Length的请求信息
412 Precondition Failed 客户端请求信息的先决条件错误
413 Request Entity Too Large 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息
414 Request-URI Too Large 请求的URI过长(URI通常为网址),服务器无法处理
415 Unsupported Media Type 服务器无法处理请求附带的媒体格式
416 Requested range not satisfiable 客户端请求的范围无效
417 Expectation Failed 服务器无法满足Expect的请求头信息
500 Internal Server Error 服务器内部错误,无法完成请求
501 Not Implemented 服务器不支持请求的功能,无法完成请求
502 Bad Gateway 充当网关或代理的服务器,从远端服务器接收到了一个无效的请求
503 Service Unavailable 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中
504 Gateway Time-out 充当网关或代理的服务器,未及时从远端服务器获取请求
505 HTTP Version not supported 服务器不支持请求的HTTP协议的版本,无法完成处理
错误处理
  • 不要发生了错误但给 2xx 响应,客户端可能会缓存成功的 HTTP 请求;

  • 正确设置 HTTTP 状态码,不要自定义;

  • Response body 提供:

    • 错误的代码(日志/问题追查)
    • 错误的描述文本(展示给用户)。

示例:

{
   "error": {
      "message": "An active access token must be used to query information about the current user.",
      "type": "OAuthException",
      "code": 2500,
      "fbtrace_id": "DzkTMkgIA7V"
   }
}
序列化和反序列化

RESTful API 以规范统一的格式作为数据的载体,常用的格式为 JSON 或 XML。确保序列化和反序列化方法的实现,是开发 RESTful API 最重要的一步准备工作。

服务器返回的数据格式,应该尽量使用 JSON,避免使用 XML。

JSON 格式约定
  • 时间用长整形(毫秒数),客户端自己按需解析
  • 不传 null 字段
使用友好的 JSON 输出

用户在第一次看到你的 API 使用情况,很可能是在命令行下使用 curl 进行的。友好的输出会让他们非常容易的理解你的 API:

{
  "beta": false,
  "email": "alice@heroku.com",
  "id": "01234567-89ab-cdef-0123-456789abcdef",
  "last_login": "2012-01-01T12:00:00Z",
  "created_at": "2012-01-01T12:00:00Z",
  "updated_at": "2012-01-01T12:00:00Z"
}

而不是这样:

{"beta":false,"email":"alice@heroku.com","id":"01234567-89ab-cdef-0123-456789abcdef","last_login":"2012-01-01T12:00:00Z", "created_at":"2012-01-01T12:00:00Z","updated_at":"2012-01-01T12:00:00Z"}
数据校验

数据校验,是开发健壮 RESTful API 中另一个重要的一环。服务器在做数据处理之前,先做数据校验。如果客户端发送的数据不正确或不合理,服务器端直接向客户端返回 400 错误及相应的数据错误信息即可。常见的数据校验包括:

  • 数据类型校验,如字段类型如果是 int,那么给字段赋字符串的值则报错;
  • 数据格式校验,如邮箱或密码,其赋值必须满足相应的正则表达式,才是正确的输入数据;
  • 数据逻辑校验,如数据包含出生日期和年龄两个字段,如果这两个字段的数据不一致,则数据校验失败。

数据校验虽然是 RESTful API 编写中的一个可选项,但它对 API 的安全、服务器的开销和交互的友好性而言,都具有重要意义。

认证和权限

常用的认证机制是 Basic Auth 和 OAuth,RESTful API 开发中,除非 API 非常简单,且没有潜在的安全性问题,否则,认证机制是必须实现的,并应用到 API 中去。

权限机制是对 API 请求更近一步的限制,只有通过认证的用户符合权限要求,才能访问API。常用的权限机制主要包含全局型的和对象型的:

  • 全局型的权限机制,主要指通过为用户赋予权限,或者为用户赋予角色或划分到用户组,然后为角色或用户组赋予权限的方式来实现权限控制;
  • 对象型的权限机制,主要指权限控制的颗粒度在 object 上,用户对某个具体对象的访问、修改、删除或其行为,要单独在该对象上为用户赋予相关权限来实现权限控制。
提供人类可读的文档

提供人类可读的文档让客户端开发人员可以理解你的 API。

除此之在详细信息的结尾,提供一个关于如下信息的API摘要:

  • 验证授权,包含获取及使用验证 tokens;
  • API 稳定性及版本控制,包含如何选择所需要的版本;
  • 一般的请求和响应头信息;
  • 错误信息序列格式;
  • 不同语言客户端使用 API 的例子,以减少用户尝试使用 API 的工作量。。
API 范例
Github

https://api.github.com/

https://developer.github.com/v3/

GET https://api.github.com/users/{user}
GET https://api.github.com/user/orgs
GET https://api.github.com/users/{user}/repos{?type,page,per_page,sort}
GET https://api.github.com/search/users?q={query}{&page,per_page,sort,order}
GET https://api.github.com/rate_limit
Twitter

https://developer.twitter.com/en/docs/api-reference-index

GET account/settings
GET favorites/list
GET followers/ids
GET followers/list
GET friends/ids
GET friends/list
GET geo/search
GET users/search
GET users/show
Facebook

https://developers.facebook.com/docs/apis-and-sdks

GET https://graph.facebook.com/820882001277849
GET https://graph.facebook.com/820882001277849/feed
GET https://graph.facebook.com/820882001277849?fields=about,fan_count,website
GET https://graph.facebook.com/820882001277849/photos
理解 RESTful 架构
RESTful 架构,就是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。
RESTful API 设计指南
如何设计一套合理、好用的 API。
RESTful API 最佳实践
RESTful API 设计规范和范例。
RESTful 基础
理解 RESTful 架构和设计。

Sphinx

reStructedText

Quickstart

Inline markup
Italic
This is a *paragraph*. It's quite short.

As shown below:

This is a paragraph. It’s quite short.

Bold
This is **another** one.

As shown below:

This is another one.

Incline code
This is ``hello world``.

As shown below:

This is hello world.

Block
An example::

    This paragraph will result in an indented block of
    text, typically used for quoting other text.

As shown below:

An example:

This paragraph will result in an indented block of
text, typically used for quoting other text.
::

    This is preformatted text, and the
    last "::" paragraph is removed

As shown below:

This is preformatted text, and the
last "::" paragraph is removed
List
Number list
1. numbers

(2) numbers again

3) and again

As shown below:

  1. numbers
  1. numbers again
  1. and again
Letter list
A. upper-case letters
and it gores over many lines

with two paragraphs and all!

a. lower-case letters

    3. with a sub-list
    4. make sure

I. upper-case roman numerals

i. lower-case roman numerals

As shown below:

  1. upper-case letters and it gores over many lines

    with two paragraphs and all!

  1. lower-case letters
    1. with a sub-list
    2. make sure
  1. upper-case roman numerals
  1. lower-case roman numerals
Definition list
what
    Definition lists associate
    a term with a definition

As shown below:

what
Definition lists associate a term with a definition
Image
.. image:: _images/sphinx.jpg
   :height: 106
   :width: 160
   :scale: 70
   :alt: sphinx profile
   :align: center

As shown below:

sphinx profile

reStructuredText Primer

Sphinx 默认使用 reStructuredText 标记语言,但在原 reST 基础上进行了语法扩展。

Paragraph

段落(Paragraph)是由空行分隔的一段文本。 和 Python 一样,对齐也是 reST 的操作符,因此同一段落的行都是左对齐的。

This is a paragraph.

Paragraphs line up at their left
edges, and are normally separated
by blank lines.
Inline markup

标准的 reST 行内标记(Inline Markup)相当简单:

  • * for emphasis(斜体)
  • ** for strong emphasis(粗体)
  • ` for interpreted text(用于程序标识符)
  • `` for code samples
This is *emphasized text*.

This is **strong text**.

This is `interpreted text`.

The regular expression ``[+-]?(\d+(\.\d*)?|\.\d+)`` matches
floating-point numbers (without exponents).

Note

  • 行内标记不能嵌套
  • 标记内容首尾不能包含空白字符,如 * text* 是错误的
  • 使用反斜杠可以对标记符号进行转义

reST 也允许自定义 “文本解释角色”(Role),这意味着可以以特定的方式解释文本。 Sphinx 以此方式提供语义标记及参考索引,操作符为 :rolename:`content`

标准 reST 提供以下规则:

  • emphasis,如 :emphasis:`text` 等价于 *emphasis*
  • strong,如 :strong:`text` 等价于 **text**
  • literal,如 :literal:`text` 等价于 ``text``
  • subscript,如 H\ :sub:`2`\ O
  • superscript,如 E = mc\ :sup:`2`

Sphinx 扩展的角色参见 Roles

List

无序列表(Bullet List)可以使用 - * + 来表示, 有序列表(Enumerated List)支持数字、字母和罗马字符,还支持 # 自动编号。

* This is a bulleted list.
* It has two items, the second
item uses two lines.

1. This is a numbered list.
2. It has two items too.

#. This is a numbered list.
#. It has two items too.

列表开始和结尾需要空行,同级列表项之间空行可选,但嵌套时上下级列表前后需要空行。

* this is
* a list

  * with a nested list
  * and some subitems

* and here the parent list continues

列表标记也使用 \ 进行转义,如:

\A. Einstein was a really smart dude.

定义列表(Definition List)用于名词解释。 条目占一行,解释文本要有缩进;多层可根据缩进实现。

term (up to a line of text)
   Definition of the term, which must be indented

   and can even consist of multiple paragraphs

next term
   Description.

字段列表(Field List)用于字段显示,如 docstring 参数说明。

:Authors:
    Tony J. (Tibs) Ibbs,
    David Goodger

    (and sundry other good-natured folks)

:Version: 1.0 of 2001/08/08
:Dedication: To my father.
def my_function(my_arg, my_other_arg):
    """A function just for me.

    :param my_arg: The first of my arguments.
    :param my_other_arg: The second of my arguments.

    :returns: A message (just for me, of course).
    """

选项列表(Option List)是一个类似两列的表格,左边是参数,右边是描述信息。 当参数选项过长时,参数选项和描述信息各占一行。

选项与参数之间有一个空格,参数选项与描述信息之间至少有两个空格。

-a            command-line option "a"
-b file       options can have arguments
              and long descriptions
--long        options can be long also
--input=file  long options can also have
              arguments
/V            DOS/VMS-style options too
Block

字面代码块(Literal Block)在段落的后面使用标记 :: 引出。 代码块必须缩进(同段落需要与周围文本以空行分隔):

This is a normal text paragraph. The next paragraph is a code sample::

   It is not processed in any way, except
   that the indentation is removed.

   It can span multiple lines.

This is a normal text paragraph again.

这个 :: 标记很优雅:

  • 如果作为独立段落存在,则整段都不会出现在文档里;
  • 如果前面有空白,则标记被移除;
  • 如果前面是非空白,则标记被一个冒号取代。

块引用(Block Quote)就是缩进的段落, 可以使用空的注释 .. 分隔上下的块引用(注释前后都要有空行)。

This is an ordinary paragraph, introducing a block quote.

    "It is my business to know things.  That is my trade."

    -- Sherlock Holmes

..

    Block quote 2.

行块(Line Block)对于地址、诗句以及无装饰列表是非常有用的。 行块是以 | 开头,每一个行块可以是多段文本。

Take it away, Eric the Orchestra Leader!

    | A one, two, a one two three four
    |
    | Half a bee, philosophically,
    |     must, *ipso facto*, half not be.
    | But half the bee has got to be,
    |     *vis a vis* its entity.  D'you see?
    |
    | But can a bee be said to be
    |     or not to be an entire bee,
    |         when half the bee is not a bee,
    |             due to some ancient injury?
    |
    | Singing...

文档测试块(Doctest Block)是交互式的 Python 会话,以 >>> 开始,一个空行结束。

This is an ordinary paragraph.

>>> print('this is a Doctest block')
this is a Doctest block

The following is a literal block::

    >>> This is not recognized as a doctest block by
    reStructuredText.  It *will* be recognized by the doctest
    module, though!
Table

网格表(Grid Table)使用 - 用来分隔行, = 用来分隔表头和表体行, | 用来分隔列,+ 用来表示行和列相交的节点。

+------------------------+------------+----------+----------+
| Header row, column 1   | Header 2   | Header 3 | Header 4 |
| (header rows optional) |            |          |          |
+========================+============+==========+==========+
| body row 1, column 1   | column 2   | column 3 | column 4 |
+------------------------+------------+----------+----------+
| body row 2             | Cells may span columns.          |
+------------------------+------------+---------------------+
| body row 3             | Cells may  | - Table cells       |
+------------------------+ span rows. | - contain           |
| body row 4             |            | - body elements.    |
+------------------------+------------+---------------------+

简单表(Simple Table)书写简单,只用 -= 表示表格。 但有一些限制:需要有多行,且第一列元素不能分行显示。

=====  =====  =======
A      B    A and B
=====  =====  =======
False  False  False
True   False  False
False  True   False
True   True   True
=====  =====  =======
Section

章节(Section)的标题在双上划线符号之间(或为下划线), 并且符号的长度不能小于文本的长度。

下面符号在标题中都可用:

! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~

通常没有专门的符号表示标题的等级,但 Python’s Style Guide for documenting 使用风格:

  • # with overline, for parts
  • * with overline, for chapters
  • = for sections
  • - for subsections
  • ^ for subsubsections
  • " for paragraphs
=================
This is a heading
=================

This is a section
=================

This is a subsection
--------------------

Note

注意输出格式(HTML,LaTeX)所支持的层次深度。

Explicit Markup

显式标记(Explicit markup)用在那些需做特殊处理的 reST 结构中, 如尾注、突出段落、评论、通用指令。

显式标记以 .. 开始,后跟空白符,结束于下一个同级缩进的段落.

Directive

指令(Directive)是显式标记最常用的模块。也是 reST 的扩展规则。在 Sphinx 经常被用到。

Sphinx 扩展的指令参见 Directives

指令通常由名字、参数、选项及内容组成:

.. function:: foo(x)
              foo(y, z)
   :module: some.module.name

   Return a line of text input from the user.

上面例子中,function 是指令名称,包含两个参数 foo 和一个选项 module (选项在参数后给出,由冒号引出)。指令的内容在隔开一个空行后,与指令有一样缩进。

Image

图片(Image)支持相对路径和绝对路径(conf.py 所在目录)。

.. image:: _images/sphinx.jpg
   :height: 106
   :width: 160
   :scale: 70
   :alt: sphinx profile
   :align: center

Sphinx 扩展 doctuils 允许对后缀名使用通配符。每个生成器则会选择最合适的图像。

.. image:: gnu.*

例如在源文件目录里文件名 gnu.* 会含有两个文件 gnu.pdfgnu.png, LaTeX 生成器会选择前者,而HTML 生成器则匹配后者。

Note

图片文件名不能包含空格。

Footnote

脚注(Footnote)使用 [#name]_ 标记位置,内容则在文档底部。

Lorem ipsum [#f1]_ dolor sit amet ... [#f2]_

.. rubric:: Footnotes

.. [#f1] Text of the first footnote.
.. [#f2] Text of the second footnote.

脚注可以使用手工序号([1]_)或不带名称的自动序号([#]_)。

Citation

引用(Citation)类似于脚注,不过没有数字标签或以 # 开始。 Sphinx 将 reST 引用扩展为全局(所有参考引用不受所在文件的限制)。

Lorem ipsum [Ref]_ dolor sit amet.

.. [Ref] Book or article reference, URL or whatever.
Substitution

替换引用(Substitution)将 |name| 内文本或标记替换为文字或图片。

The |biohazard| symbol must be used on containers
used to dispose of |name|.

.. |biohazard| image:: biohazard.png
.. |name| replace:: medical waste

如果需要对所有文档进行替换,可将替换内容写入 rst_prologrst_epilog 或其他单独文件,并通过 include 指令在使用它们的文档文件里包含这个文件。

Sphinx 默认预定义了以下替换引用,并放置在 conf.py

  • |release|
  • |version|
  • |today|
Comment

注释以 .. 开头,后面接注释内容即可,可以是多行内容。

.. This is a comment.

可以通过缩进产生多行评论。

..
   This whole indented block
   is a comment.

   Still in the comment.
HTML Metadata

meta 指令运行指定 Sphinx HTML 文档元数据。

.. meta::
   :description: The Sphinx documentation builder
   :keywords: Sphinx, documentation, builder

将生成以下 HTML:

<meta name="description" content="The Sphinx documentation builder">
<meta name="keywords" content="Sphinx, documentation, builder">
Quickstart
快速入门。
reStructuredText Primer
参考手册。

Sphinx

reStructedText
reStructedText 参考手册