Welcome to pyfreyr’s wiki!¶
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 。
Pip 使用手册¶
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 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 镜像源:
- 清华 PyPI 源 :
https://pypi.tuna.tsinghua.edu.cn/simple
- 中科大 PyPI 源 :
https://mirrors.ustc.edu.cn/pypi/web/simple
如果使用豆瓣之类的 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 是针对于 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 包。
$ 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
环境管理命令¶
创建虚拟环境,使用 -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
。
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¶
路径信息 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_file()¶
如果路径是文件返回 True
,否则返回 False
,情况和 is_dir()
类似。
判断符号链接 is_symlink()¶
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)
- 如果设置
mode
,则根据进程的umask
值决定文件的权限。 - 如果创建的目录父目录不存在,则需要设置
parents=True
,效果类似mkdir -p
,否则抛FileNotFoundError
。 - 如果目录已存在,抛
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
创建符号链接 symlink_to()¶
symlink_to(target, target_is_directory=False)
如果要链接目标是目录,在 Windows 下必须设置 target_is_directory=True
;Unix 下忽略该选项。
p = Path('mylink')
p.symlink_to('setup.py')
p.resolve() # PosixPath('/home/antoine/pathlib/setup.py')
p.stat().st_size # 956
p.lstat().st_size # 8
创建文件 touch()¶
touch(mode=0o666, exist_ok=True)
创建文件,如果文件已存在且 exist_ok=False
,则抛 FileExistsError
,设置为 True
则忽略(文件修改时间更新到当前时间)。
删除文件 unlink()¶
只用于删除文件和符号链接,若路径指向目录,请改用调用 rmdir()
或 shutil.rmtree()
。
按字节写入 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 交互

Docker 架构¶


- 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。

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

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

从下到上理解上图:
基础设施 (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 容器运行多个相互隔离的应用时, 如下图:

相比于虚拟机,Docker 要简洁很多,因为我们不需要运行一个臃肿的从操作系统了。从下到上理解上图:
基础设施
主操作系统
所有主流的操作系统都可以运行 Docker。
Docker 守护进程 (Docker Daemon)
Docker 守护进程取代了 Hypervisor,它是运行在操作系统之上的后台进程,负责管理 Docker 容器。
各种依赖
对于 Docker,应用的所有依赖都打包在 Docker 镜像中,Docker 容器是基于 Docker 镜像创建的。
应用
应用的源代码与它的依赖都打包在 Docker 镜像中,不同的应用需要不同的Docker镜像。不同的应用运行在不同的 Docker 容器中,它们是相互隔离的。
Docker 守护进程可以直接与主操作系统进行通信,为各个 Docker 容器分配资源。它还可以将容器与主操作系统隔离,并将各个容器互相隔离。虚拟机启动需要数分钟,而 Docker 容器可以在数毫秒内启动。由于没有臃肿的从操作系统,Docker 可以节省大量的磁盘空间以及其他系统资源。

Docker 优点¶
简化程序:
Docker 让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,便可以实现虚拟化。Docker改变了虚拟化的方式,使开发者可以直接将自己的成果放入Docker中进行管理。方便快捷已经是 Docker的最大优势,过去需要用数天乃至数周的 任务,在Docker容器的处理下,只需要数秒就能完成。
避免选择恐惧症:
如果你有选择恐惧症,还是资深患者。Docker 帮你 打包你的纠结!比如 Docker 镜像;Docker 镜像中包含了运行环境和配置,所以 Docker 可以简化部署多种应用实例工作。比如 Web 应用、后台应用、数据库应用、大数据应用比如 Hadoop 集群、消息队列等等都可以打包成一个镜像部署。
节省开支:
一方面,云计算时代到来,使开发者不必为了追求效果而配置高额的硬件,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 install -y yum-utils \ device-mapper-persistent-data \ lvm2 $ yum-config-manager \ --add-repo \ https://download.docker.com/linux/centos/docker-ce.repo
安装
使用以下命令直接安装最新版:
$ 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
指定版本即可。
进入 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-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"
安装
安装最新版本:
$ 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
升级
先 update 系统,然后指定版本 install。参考上面安装。
前往 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 基础 。
镜像和容器¶

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

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

详细的说明以及 Docker 命令的理解参考:Docker objects 和 Docker 容器和镜像的区别 。
运行第一个容器¶
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 命令的使用帮助:
- 查看 Docker 支持的命令
docker --help
- 查看具体命令的使用
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
,容器 idIMAGE
,创建容器使用的镜像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种写法:茴 ,回,囘,囬):
- 附着到容器(
docker attach
执行后需要 Enter 直到进入)
$ docker attach myubuntu
- 在容器内重新运行新进程(
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 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 由一系列指令和参数组成,指令会按顺序从上到下执行(所以需要合理安排指令的顺序)。
每条指令都会创建一个新的镜像层并提交,大致执行流程:
- 从基础镜像运行一个容器
- 执行一条指令,并对容器做出修改
- 提交新的镜像层
- 再基于刚提交的镜像运行一个新的容器
- 执行下一条指令,直到所有指令执行完毕
可以看出,如果 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 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
(多层级构建),这样会在构建时产出多个镜像,或者使其中一些作为其他构建的依赖。
参数 tag
和 name
是可选的,不指定 tag
默认使用 latest
;name
在多层级构建时会有用。
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
- 使用
docker run
命令可以覆盖CMD
指令。 - 在 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
标签会从基础镜像中继承,如果更新了同名变量值,则覆盖父镜像。
docker inspect
可以查看镜像中的标签信息。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
<src>
必须位于上下文(指构建目录本身,但内部目录可以)之中;<src>
为目录时,目录下所有内容将被复制,而不包含目录本身;<src>
为本地压缩文件时,ADD
会自动解压 gzip、bzip2、xz;<src>
也可以为网络资源,若为压缩文件不会被解压;<dest>
可以是相对路径(相对WORKDIR
)或绝对路径;- 如果
<src>
为多个文件,要求<dest>
为目录(以/
结尾); - 如果
<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;"]
可以组合使用 ENTRYPOINT
和 CMD
来完成一些巧妙的工作(如果启动容器时没有指定参数,则在 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
, COPY
和 ADD
指令会在这个目录下执行。
WORKDIR /opt/webapp/db
RUN bundle install
WORKDIR /opt/webapp
ENTRYPOINT ["rackuo"]
Note
WORKDIR
可以使用相对路径或绝对路径;- 路径不存在时自动创建;
WORKDIR
可以使用ENV
指定的环境变量;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
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
ONBUILD
指定的指令不能是ONBUILD
和FROM
;- 触发器无法被继承,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
- 在 Dockerfile 中
HEALTHCHECK
只可以出现一次,如果写了多个,只有最后一个生效。 - 一旦有一次健康检查成功,Docker会将容器置回 healthy (健康)状态
- 当容器的健康状态发生变化时,Docker Engine会发出一个 health_status 事件。
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 命令¶
使用命令 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
一个例子¶
最后,以一个官方 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
只接受预先构建好的镜像。
添加构建参数,这些参数是仅在构建过程中可访问的环境变量。
首先, 在 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
)必须使用引号括起来才能准确解析为字符串。
使用 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"
用于设置 /dev/shm
分区大小,值为表示字节的整数值或表示字符的字符串,使用数值时单位 byte。
build:
context: .
shm_size: '2gb'
build:
context: .
shm_size: 10000000
进一步了解 /dev/shm 。
在多层级镜像构建时,用于构建指定镜像:
# 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_add
和 cap_drop
在 Swarm 部署时被忽略。
command¶
覆盖容器启动后默认执行的命令:
command: bundle exec thin -p 3000
command: ["bundle", "exec", "thin", "-p", "3000"]
config¶
使用服务 configs 配置为每个服务赋予相应的访问权限,支持两种不同的语法。
Note
配置必须存在或在 configs 此堆栈文件的顶层中定义,否则堆栈部署失效。
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_config
和 my_other_config
,并被 my_other_config
定义为外部资源,这意味着它已经在 Docker 中定义。可以通过 docker config create
命令或通过另一个堆栈部署。如果外部部署配置都不存在,则堆栈部署会失败并出现 config not found
错误。
LONG 语法提供了创建服务配置的更加详细的信息
source
:Docker 中存在的配置的名称target
:要在服务的任务中装载的文件的路径或名称。如果未指定则默认为/<source>
uid
和gid
:在服务的任务容器中拥有安装的配置文件的数字 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: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:
指定服务的标签,这些标签仅在服务上设置(而非服务的容器上)。
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"
global
:每个集节点只有一个容器replicated
:指定容器数量(默认)
version: '3'
services:
worker:
image: dockersamples/examplevotingapp_worker
deploy:
mode: global
指定 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
如果服务是 replicated
(默认),需要指定运行的容器数量:
version: '3'
services:
worker:
image: dockersamples/examplevotingapp_worker
networks:
- frontend
- backend
deploy:
mode: replicated
replicas: 6
配置资源限制,类似 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
。
condition
:值可以为none
、on-failure
以及any
(默认)delay
:尝试重启的等待时间,默认为 0max_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
配置更新服务,用于无缝更新应用(rolling update)。
parallelism
:一次性更新的容器数量,如果设置为 0,所有容器同时操作。delay
:更新一组容器之间的等待时间,默认 0s。failure_action
:如果更新失败,可以执行的的是continue
、rollback
或pause
(默认)monitor
:每次任务更新后监视失败的时间(ns|us|ms|s|m|h
)(默认为0)max_failure_ratio
:在更新期间能接受的失败率order
:更新次序设置,top-first
(旧的任务在开始新任务之前停止)、start-first
(新的任务首先启动,并且正在运行的任务短暂重叠)(默认stop-first
)
服务升级失败时回滚配置。选项参考 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
以依赖顺序启动服务,下面例子中redis
和db
服务在web
启动前启动docker-compose up SERVICE
自动包含SERVICE
的依赖性,下面例子中会先启动redis
和db
两个服务,最后才启动web
服务:
version: '3'
services:
web:
build: .
depends_on:
- db
- redis
redis:
image: redis
db:
image: postgres
注意的是,默认情况下使用 docker-compose up web
这样的方式启动 web
服务时,也会启动 redis
和 db
两个服务,因为在配置文件中定义了依赖关系。
Note
depends_on
在 Swarm 模式无效。
dns_search¶
自定义 DNS 搜索域,可以是单个值或列表。
dns_search: example.com
dns_search:
- dc1.example.com
- dc2.example.com
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"
external_links¶
链接到 docker-compose.yml 外部的容器,甚至并非 Compose 项目文件管理的容器。参数格式跟 links
类似。
在使用 Docker 过程中,会有许多单独使用 docker run
启动的容器的情况,为了使 Compose 能够连接这些不在docker-compose.yml 配置文件中定义的容器,那么就需要一个特殊的标签,就是 external_links
,它可以让 Compose 项目里面的容器连接到那些项目配置外部的容器(前提是外部容器中必须至少有一个容器是连接到与项目内的服务的同一个网络里面)。
external_links:
- redis_1
- project_db_1:mysql
- project_db_1:postgresql
Note
link
已经弃用,优先使用networks
;external_links
在 Swarm 模式无效。
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
interval
,timeout
以及 start_period
都定为持续时间。
test
必须是字符串或列表,如果它是一个列表,第一项必须是 NONE
,CMD
或 CMD-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"
links¶
Note
未来会弃用,建议使用 networks 。
链接到其它服务的中的容器,可以指定服务名称也可以指定链接别名( SERVICE:ALIAS
),与 Docker 客户端的 --link
有一样效果,会连接到其它服务中的容器。
web:
links:
- db
- db:database
- redis
使用的别名将会自动在服务容器中的 /etc/hosts
里创建,相应的环境变量也将被创建。例如:
172.12.2.186 db
172.12.2.186 database
172.12.2.187 redis
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-file
和 journald
驱动程序可以直接从 docker-compose up
和 docker-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
network_mode
在 Swarm 模式无效。network_mode: "host"
不能与links
混合使用。
networks¶
加入指定网络,可选网络来自顶层 networks 。
services:
some-service:
networks:
- some-network
- other-network
Aliases(alternative hostnames),同一网络上的其他容器可以使用服务器名称或别名(也是 hostname)来连接到其他服务的容器。
services:
some-service:
networks:
some-network:
aliases:
- alias1
- alias3
other-network:
aliases:
- alias2
下面实例中,提供 web
、worker
以及 db
三个服务和两个网络 ( new
和 legacy
),相同的服务可以在不同的网络有不同的别名。
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
为服务的容器指定一个静态 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 模式无效。
ports¶
映射端口。
Note
端口映射和 network_mode: host
不兼容。
可以使用 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 语法支持 SHORT 语法不支持的附加字段:
target
:容器内的端口published
:公开的端口protocol
: 端口协议(tcp
或udp
)mode
:通过host
用在每个节点还是哪个发布的主机端口或使用 ingress 用于集群模式端口进行平衡负载
ports:
- target: 80
published: 8080
protocol: tcp
mode: host
secrets¶
通过 secrets
为每个服务授予相应的访问权限,选项必须在顶层 secrets 定义。
仅用于指定 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 语法可以添加其他选项:
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:
挂载主机目录可以直接使用 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 语法有些附加字段:
type
:安装类型,可以为volume
、bind
或tmpfs
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 。
durations¶
某些配置选项如 check
的子选项 interval
以及 timeout
的设置格式,支持的单位有 us
、ms
、s
、m
以及 h
。
2.5s
10s
1m30s
2h32m
5h34m56s
byte values¶
某些选项如 bulid
的子选项 shm_size
。
2b
1024kb
2048k
300m
1gb
支持的单位是 b
,k
,m
,g
,或 kb
,mb
和 gb
。目前不支持十进制值。
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
external¶
如果指定为 true
,说明该卷在 Compose 之外创建,docker-compose up
时不会创建,但如果不存在则报错。
external
不能喝其他卷配置一起使用( driver
,driver_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 模式下,外部卷不存在则创建。
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
网络。
在 Swarm 多节点间使用 overlay
驱动创建网络。
使用主机网络,或不使用网络。只在 docker stack
下有效,等价于:
docker run --net=host
docker run --net=none
如果使用 docker-compose
,请使用 network_mode 。
内置网络(Docker 自动创建) host
和 none
的使用需要创建别名(如 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 。
internal¶
默认情况下,Docker 使用 bridge 网络提供外界连接,如果要创建与外界隔离的 overlay 网络,将 internal 设置为 true
。
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 内的服务共享,来源只能为 file
和 external
。
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¶
选项 file
,external
,name
类似 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 运行 Redis¶
本镜像用于启动 Redis 服务,并解决通常 redis-server 启动报警。
启动 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 服务器。
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 压缩
解包/解压¶
解压缩使用参数 -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
...
配置环境变量¶
新建文件 /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 后配置¶
配置静态 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
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
,指定使用的 shellHOME
,指定 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 教程
- 负载测试和基准测试工具。
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 时文件的生命周期如图:

基础操作¶
创建版本库¶
在需要版本控制的任何目录内使用命令 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
:

$ 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
:

$ 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
- 可以多次 add 不同的文件到暂存区,然后一次 commit 提交。
- 使用
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
要撤销已提交到暂存区和工作区的修改,需要分两步:
git reset HEAD
撤销暂存区修改;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
一个
^
表示一个版本,可以多个(如HEAD^^
);另外也可以使用HEAD~n
形式;或者直接指定 commit id;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 进入代码仓库创建页面:

依次填写、勾选、确认,完成创建。

注意上面的 URL 就是远程仓库的地址。
添加 SSH Key¶
在家目录下创建 SSH Key:
$ ssh-keygen -t rsa -C "you_email@example.com"
一路回车使用默认值,在家目录的 .ssh
子目录里新增 id_rsa
和 id_rsa.pub
两个文件(SSH Key 密钥对),id_rsa
是私钥,不能泄露出去;id_rsa.pub
是公钥,可以放心告诉别人。
登录 Github,进入 settings —— SSH and GPG keys —— New SSH key,将 id_rsa.pub
的内容粘贴到 Key 内完成添加 SSH key。

使用如下目录验证是够成功:
$ 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 项目主页就和本地仓库一样了:

拉取远程分支到本地¶
在多人协作下,远程分支更新通常都超前于本地分支,所以需要使用 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 区别:
git fetch
从远程获取最新版本到本地,不会自动合并。$ git fetch origin master # 下载 origin 的 master 主分支到本地 ``origin/master`` 分支上
git pull
从远程获取最新版本并 merge 到本地$ git pull origin master # 下载远程 master 分支到本地 ``origin/master`` 并合并到本地 master
从远程库克隆¶
前面介绍了先有本地库,后有远程库,如何关联本地到远程。
如果项目从零开始,最好的方式是先创建远程库,然后从远程库克隆。使用命令 git clone
:
$ git clone https://github.com/ifreyr/learning-git.git
远程操作示意图¶

多人协作:分支¶
分支就是科幻电影里面的平行宇宙,当你正在电脑前努力学习 git 的时候,另一个你正在另一个平行宇宙里努力学习 SVN。
如果两个平行宇宙互不干扰,那对现在的你也没啥影响。不过,在某个时间点,两个平行宇宙合并了,结果,你既学会了 git 又学会了 SVN!

几乎每一种版本控制系统都以某种形式支持分支。使用分支意味着你可以从开发主线上分离开来,然后在不影响主线的同时继续工作。例如,我们发布了 1.0 版本的产品,可能需要创建一个分支,以便将 2.0 功能的开发与 1.0 版本中错误修复分开。
每次提交,git 都把它们串成一条时间线。这条时间线就是一个分支。当创建版本库的时候,默认就创建了主分支 master
。Git 使用 master
指向最新的提交,再用 HEAD
指向 master
,就能确定当前的分支,以及当前分支的提交点:

每次提交,master
分支都会向前移动一步,随着不断的提交,master
分支的时间线也越来越长。
列出分支¶
使用 git branch
列出本地分支:
$ git branch
* master
显示目前只有一个分支 master
,且 *
表示当前处于 master
分支。
Tip
使用 -a
选项能同时显示本地和远程分支。
创建分支¶
使用命令 git branch
创建分支:
$ git branch dev
列出分支:
$ git branch
dev
* master
当创建新分支 dev
时,git 新建了一个指针 dev
,指向 master
相同的提交:

切换分支¶
使用命令 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
上:

Git 创建和切换分支很快,因为除了增加一个 dev
指针,修改下 HEAD
的指向,工作区的文件没有任何变化。
从现在开始,对工作区的修改和提交都是针对 dev
分支,比如新提交一次后,dev
指针往前移动一步,而 master
指针不变:

合并分支¶
如果在 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
的当前提交:

所以,git 合并分支也很快,就改改指针,工作区内容也不变。
合并完成后,就可以使用 git branch -d
删除 dev 分支(还是 git branch
命令,加了 -d
选项):
$ git branch -d dev
Deleted branch dev (was 78c9a3c).
删除分支就是把 dev
指针给删掉,删掉后就只剩下一条 master
分支:

解决冲突¶
合并分支往往也不是一帆风顺,当多分支同时修改统一文件,合并时就会出现冲突。
创建分支 feature1
并修改 README 内容为:
甜豆脑好吃
而 master 分支修改 README 内容为:
咸豆脑好吃
现在,master
和 feature1
分支各自都有新的提交:

这种情况下,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
现在,master
和 feature1
分支变成:

Tip
合并分支时,加上 --no-ff
参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而默认使用的 fast forward
合并就看不出来曾经做过合并。
分支管理策略¶
在实际开发中,我们应该按照几个基本原则进行分支管理:
master
分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;develop
分支是不稳定的,到某个时候,比如 1.0 版本发布时,再把develop
分支合并到master
上,在master
分支发布 1.0 版本;- 团队成员在开发时只与
develop
分支打交道,在develop
上创建各种分支(feature
,bugfix
,hotfix
)。在开发完成后合并到develop
分支并删除功能分支。
所以,团队合作的分支看起来就像这样:

版本库快照:标签¶
发布一个版本时,我们通常先在版本库中打一个标签(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 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 的分支:

现在在 origin
和 mywork
两个分支上分别开发:

通常使用 git merge
合并,结果如图:

但是,如果你想让 mywork
分支历史看起来像没有经过任何合并一样,也可以用 git rebase
,当 mywork
分支更新之后,它会指向这些新创建的提交,而那些老的提交会被丢弃。:
$ git checkout mywork # 在 mywork 上执行 rebase
$ git rebase origin




在 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 。

使用 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
.gitlab-ci.yml
要放在 git 仓库的根目录,具体语法参考:.gitlab-ci.yml 语法 。- 集成任务的实际运行依赖于
gitlab-runner
,否则任务一直处于 stuck (挂起)状态。具体参考:gitlab-runner 安装 和 gitlab-runner 配置 。

一个例子¶
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 下查看任务执行。

点击 status
图标可以查看任务细节:


构建成功则 status 图标为绿色 passed
,否则为红色 failed
,同时发送错误邮件。

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:

注册 Runner¶
项目只有注册了 Runner 才能完成实际执行,使用命令 gitlab-runner register
进行注册,在我们的 docker 环境下使用:
$ docker exec -it gitlab-runner gitlab-runner register
完成几个问题交互,即完成注册:

其中,URL
和 token
就是上一步在项目内得到的,description
和 tag
可以随意填写,executor
根据实际需要选择,这里选择使用 docker,所以下一步的默认镜像使用 docker:stable
。
Note
也可以在 register 的时候使用参数指定所有设置,gitlab-runner register -h
查看可用选项。
如果设置了 tag,以后的 .gitlab-ci.yml
使用中都必须通过显式指定 tags 来选择执行 runner。
正常的注册到此就结束,但 docker 还需要进一步的修改配置。
使用 docker 的额外配置¶
gitlab-runner 的配置文件为 /config/config.toml
,需要修改 privileged
和 volumes
:
修改后的配置如下:
[[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 了:

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
only & except¶
only
和 except
用于控制代码分支和 tag 的选择:
only
圈定的分支和 tags 会执行该任务except
圈定的分支和 tags 不会执行该任务
二者使用的一些规则:
only
和except
可以同时使用,最终取条件的交集only
和except
都支持正则表达式only
和except
可以指定代码仓库地址
示例:
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
提供其他镜像(通常为数据库),使得运行任务时主镜像能连接到这些镜像。image
和 services
支持全局和 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 下载安装。
Firefox 配置¶
Tip
推荐在 foxyproxy 设置!这样所有代理都统一管理,只需要按需求切换。
Chrome 使用 SwitchyOmega 。
添加证书¶
如果出现如下“不安全的连接”,需要给浏览器添加 fiddler 证书。

从 Fiddler 导出证书
TOOLS - Options - HTTPS - Actions - Export Root Certificate to Desktop,此时桌面会出现文件 “FiddlerRoot.cer”
导入证书到浏览器
选项 - 高级 - 证书 - 查看证书 - 证书机构 - 导入 - 选择所有信任选项
此时访问网站应该能看到各种连接刷屏

- 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 架构:
- 每一个 URI 代表一种资源;
- 客户端和服务器之间,传递这种资源的某种表现层;
- 客户端通过四个 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.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/animals
与 GET /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"
}
上面代码表示,服务器给出了提示信息,以及文档的网址。
RESTful API 最佳实践¶
基本要求¶
- 当标准合理的时候遵守标准。
- API 应该对程序员友好,并且在浏览器地址栏容易输入。
- API 应该简单,直观,容易使用的同时优雅。
- API 应该具有足够的灵活性来支持上层ui。
- 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://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.
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:
- numbers
- numbers again
- 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:
upper-case letters and it gores over many lines
with two paragraphs and all!
- lower-case letters
- with a sub-list
- make sure
- upper-case roman numerals
- 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:

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
===== ===== =======
Hyperlink¶
如果链接文本是网址,则不需要特别标记,分析器会自动发现文本里的链接或邮件地址。
最简单的外链在 <>
内放入链接:
External hyperlinks, like `Python <http://www.python.org/>`_.
可以将链接和标签分开:
External hyperlinks, like Python_.
.. _Python: http://www.python.org/
如果标记文字包含空格或符号,需要使用 `
标记,如:
See the `Python home page`_ for info.
.. _Python home page: http://www.python.org
内链通过在文内设置锚点以支持跳转,如:
Internal crossreferences, like example_.
.. _example:
This is an example crossreference target.
匿名超链接不通过标记引用,而按顺序引用,使用 __
标记,如:
See `the web site of my favorite programming language`__.
.. __: http://www.python.org
也可以简化为:
See `the web site of my favorite programming language`__.
__ http://www.python.org
小节标题、脚注和引用参考会自动生成超链接地址, 使用小节标题、脚注或引用参考名称作为超链接名称就可以生成隐式链接。
Hyperlink
=========
Implict references, like Hyperlink_.
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.pdf
和 gnu.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_prolog 或 rst_epilog 或其他单独文件,并通过 include 指令在使用它们的文档文件里包含这个文件。
Sphinx 默认预定义了以下替换引用,并放置在 conf.py
:
|release|
|version|
|today|
Comment¶
注释以 ..
开头,后面接注释内容即可,可以是多行内容。
.. This is a comment.
可以通过缩进产生多行评论。
..
This whole indented block
is a comment.
Still in the comment.
- Quickstart
- 快速入门。
- reStructuredText Primer
- 参考手册。
Sphinx¶
- reStructedText
- reStructedText 参考手册