初等函数的导数推导
1. 幂函数求导
求 $$\textcolor{red}{f(x)=x^n}$$ 在 处的导数:
\begin{align*} f'(x) &= \lim_{x\to a} \frac{f(x)-f(a)}{x-a} \\ &= \lim_{x\to a} \frac{x^n-a^n}{x-a} \\ &= \lim_{x\to a} \frac{(x^n-ax^{n-1}) + (ax^{n-1}-a^2x^{n-2}) + \cdots + a^{n-1}x-a^n}{x-a} \\ &= \lim_{x\to a} \left[(x^{n-1}) + (ax^{n-2}) + \cdots + a^{n-1}\right] \\ &= \lim_{x\to a} (x^{n-1}) + \lim_{x \to a} (ax^{n-2})...+\lim_{x\to a} a^{n-1} \\ &= na^{n-1} \end{align*} \begin{align*} \therefore f(x)=x^n \\ f'(x) = n x^{n-1} \end{align*}2. 正弦函数求导
求 $$\textcolor{red}{f(x)= \sin x}$$ 的导数:
\begin{align*} f'(x) &= \lim_{h\to 0} \frac{f(x+h)-f(x)}{h} \\ &= \lim_{h\to 0} \frac{\sin(x+h)-\sin x}{h} \\ &= \lim_{h\to 0} \frac{2\sin\left(\frac{h}{2}\right)\cos\left(x+\frac{h}{2}\right)}{h} \\ &= \lim_{h\to 0} \left(\frac{\sin\left(\frac{h}{2}\right)}{\frac{h}{2}} \cdot \cos\left(x+\frac{h}{2}\right)\right) \\ &= \cos x \end{align*}3. 余弦函数求导
求 $$\textcolor{red}{f(x)= \cos x}$$ 的导数:
\begin{align*} f'(x) &= \lim_{h\to 0} {f(x+h)-f(x) \over h} \\ &= \lim_{h\to 0} \frac{\cos(x+h)-\cos x}{h} \\ &= \lim_{h\to 0} \frac{-2\sin\left(\frac{h}{2}\right)\sin\left(x+\frac{h}{2}\right)}{h} \\ &= -\lim_{h\to 0} \left(\frac{\sin\left(\frac{h}{2}\right)}{\frac{h}{2}} \cdot \sin\left(x+\frac{h}{2}\right)\right) \\ &= -\sin x \end{align*}4. 指数函数求导
求 $$\textcolor{red}{f(x)= a^x}$$ 的导数:
\begin{align*} f'(x) &= \lim_{h\to 0} {f(x+h)-f(x) \over h} \\ &= \lim_{h\to 0} \frac{a^{x+h}-a^x}{h} \\ &= \textcolor{red}{a^x \lim_{h\to 0} \frac{a^h-1}{h}} \\ &= \textcolor{red}{a^x \ln a} \end{align*}推导关键步骤:
令 $$t = a^h - 1$$,则:
5. 对数函数求导
求 $$\textcolor{red}{f(x)= \log_a x}$$ 的导数:
\begin{align*} f'(x) &= \lim_{h\to 0} {f(x+h)-f(x) \over h} \\ &= \lim_{h\to 0} \frac{\log_a(x+h) - \log_a x}{h} \\ &= \lim_{h\to 0} {log_a \ {(x+h) \over x} \over h} \\ &= \lim_{h\to 0} \frac{1}{x} \cdot \textcolor{red}{\frac{\log_a\left(1+\frac{h}{x}\right)}{\frac{h}{x}}} \\ &\textcolor{red}{= \frac{1}{x \ln a}} \end{align*}特例:当 时,
\begin{align*} & f'(x) =(\ln x)' = \frac{1}{x} \end{align*}
等比公式:
\begin{align*} S_n=a_1(1-q^n)/(1-q) \end{align*}等差公式:
\begin{align*} S_n=(a_1+a_n)n/2=na_1+n(n-1)d/2 \end{align*}排列组合:
排列
\begin{align*} A^m_n=n(n-1)(n-2)...(n-m+1)=\frac{n!}{(n-m)!} \end{align*}组合
\begin{align*} C^m_n={A^m_n \over m!}={n(n-1)(n-2)...(n-m+1)\over m!}={n! \over m!(n-m)!} \end{align*}牛顿二项式
\begin{align*} (a+b)^n=\sum^n_{k=0}C^k_nx^k \end{align*}三角函数
\begin{align*} \sin(\alpha+\beta)=\sin(\alpha)\cos(\beta)+\cos(\alpha)\sin(\beta) \therefore \sin(2\alpha)=2\sin(\alpha)\cos(\alpha) \end{align*} \begin{align*} \cos(\alpha+\beta)=\cos(\alpha)\cos(\beta)-\sin(\alpha)s\in(\beta) \therefore \cos(2\alpha)=\cos^2(\alpha)-\sin^2(\alpha) \end{align*}对数函数
e的定义
\begin{align*} \lim_{x\to \infty} (1+{1 \over {n}})^n=e \end{align*}数学符号和对应的LaTeX代码:
符号显示 | LaTeX代码 | 符号显示 | LaTeX代码 |
---|---|---|---|
∑ | $\sum$ |
∑_{i=0}^n | $\sum_{i=0}^n$ |
∏ | $\prod$ |
∏_{i=0}^n | $\prod_{i=0}^n$ |
× | $\times$ |
∗ | $\ast$ |
± | $\pm$ |
÷ | $\div$ |
∣ | $\mid$ |
⋅ | $\cdot$ |
⨀ | $\bigodot$ |
≈ | $\approx$ |
≤ | $\leq$ |
≥ | $\geq$ |
≠ | $\neq$ |
≡ | $\equiv$ |
x̄ | $\overline{x}$ |
x̲ | $\underline{x}$ |
x̂ | $\hat{x}$ |
x̌ | $\check{x}$ |
x̆ | $\breve{x}$ |
↑ | $\uparrow$ |
↓ | $\downarrow$ |
← | $\leftarrow$ |
→ | $\rightarrow$ |
⇑ | $\Uparrow$ |
⇓ | $\Downarrow$ |
⇐ | $\Leftarrow$ |
⇒ | $\Rightarrow$ |
⟵ | $\longleftarrow$ |
⟶ | $\longrightarrow$ |
⟸ | $\Longleftarrow$ |
⟹ | $\Longrightarrow$ |
∵ | $\because$ |
∴ | $\therefore$ |
∀ | $\forall$ |
∃ | $\exists$ |
y′ | $y'$ |
∫ | $\int$ |
∬ | $\iint$ |
∭ | $\iiint$ |
∮ | $\oint$ |
lim | $\lim$ |
∞ | $\infty$ |
∇ | $\nabla$ |
⊥ | $\bot$ |
∠30° | $\angle 30^\circ$ |
sin | $\sin$ |
cos | $\cos$ |
tan | $\tan$ |
cot | $\cot$ |
sec | $\sec$ |
csc | $\csc$ |
log | $\log$ |
lg | $\lg$ |
ln | $\ln$ |
∅ | $\emptyset$ |
∈ | $\in$ |
∉ | $\notin$ |
⊂ | $\subset$ |
⊃ | $\supset$ |
⊆ | $\subseteq$ |
⊇ | $\supseteq$ |
⋂ | $\bigcap$ |
⋃ | $\bigcup$ |
⋁ | $\bigvee$ |
⋀ | $\bigwedge$ |
⨄ | $\biguplus$ |
⨆ | $\bigsqcup$ |
分式与特殊符号
显示 | LaTeX代码 |
---|---|
1/(2x+1) | $\frac{1}{2x+1}$ |
1/(2x+1) | ${{1} \over {2x+1}}$ |
du/dx|_{x=0} | `$\left.\frac{du}{dx}\right |
∛9 | $\sqrt[3]{9}$ |
√16 | $\sqrt{16}$ |
… | $\ldots$ |
f(x₁,x₂,…,xₙ)=x₁²+⋯+xₙ² | $f(x_1,x_2,\ldots,x_n)=x_1^2+\cdots+x_n^2$ |
a⃗ | $\vec{a}$ |
∫₀¹ x²dx | $\int_0^1 x^2 dx$ |
limₙ→∞ 1/[n(n+1)] | $\lim_{n\to\infty}\frac{1}{n(n+1)}$ |
∑₁ⁿ 1/x² | $\sum_1^n \frac{1}{x^2}$ |
x∈[0,100] | $x \in [0,100]$ |
xʸᶻ=(1+eˣ)⁻²ˣʸʷ | $x^{y^z}=(1+e^x)^{-2xy^w}$ |
分段函数
\begin{align*} y = \begin{cases} x \\ \alpha \end{cases} \end{align*}LaTeX代码:
1 | $$y =\begin{cases} x\\ \alpha \end{cases}$$ |
在大型应用程序中,配置主从数据库并使用读写分离是常见的设计模式。在Spring应用程序中,要实现读写分离,最好不要对现有代码进行改动,而是在底层透明地支持。
Spring内置了一个AbstractRoutingDataSource
,它可以把多个数据源配置成一个Map,然后,根据不同的key返回不同的数据源。因为AbstractRoutingDataSource
也是一个DataSource接口,因此,应用程序可以先设置好key, 访问数据库的代码就可以从AbstractRoutingDataSource
拿到对应的一个真实的数据源,从而访问指定的数据库。它的结构看起来像这样:
1 | ┌───────────────────────────┐ |
第一步:配置多数据源
首先,我们在SpringBoot中配置两个数据源,其中第二个数据源是ro-datasource
:
1 | spring: |
在开发环境下,没有必要配置主从数据库。只需要给数据库设置两个用户,一个rw
具有读写权限,一个ro
只有SELECT权限,这样就模拟了生产环境下对主从数据库的读写分离。
在SpringBoot的配置代码中,我们初始化两个数据源:
1 |
|
第二步:编写RoutingDataSource
然后,我们用Spring内置的RoutingDataSource,把两个真实的数据源代理为一个动态数据源:
1 | public class RoutingDataSource extends AbstractRoutingDataSource { |
对这个RoutingDataSource
,需要在SpringBoot中配置好并设置为主数据源:
1 |
|
现在,RoutingDataSource配置好了,但是,路由的选择是写死的,即永远返回"masterDataSource"
,
现在问题来了:如何存储动态选择的key以及在哪设置key?
在Servlet的线程模型中,使用ThreadLocal存储key最合适,因此,我们编写一个RoutingDataSourceContext,来设置并动态存储key:
1 | public class RoutingDataSourceContext implements AutoCloseable { |
然后,修改RoutingDataSource,获取key的代码如下:
1 | public class RoutingDataSource extends AbstractRoutingDataSource { |
这样,在某个地方,例如一个Controller的方法内部,就可以动态设置DataSource的Key:
1 |
|
到此为止,我们已经成功实现了数据库的动态路由访问。
这个方法是可行的,但是,需要读从数据库的地方,就需要加上一大段try (RoutingDataSourceContext ctx = ...) {}
代码,使用起来十分不便。有没有方法可以简化呢?
有!
我们仔细想想,Spring提供的声明式事务管理,就只需要一个@Transactional()
注解,放在某个Java方法上,这个方法就自动具有了事务。
我们也可以编写一个类似的@RoutingWith("slaveDataSource")
注解,放到某个Controller的方法上,这个方法内部就自动选择了对应的数据源。代码看起来应该像这样:
1 |
|
这样,完全不修改应用程序的逻辑,只在必要的地方加上注解,自动实现动态数据源切换,这个方法是最简单的。
想要在应用程序中少写代码,我们就得多做一点底层工作:必须使用类似Spring实现声明式事务的机制,即用AOP实现动态数据源切换。
实现这个功能也非常简单,编写一个RoutingAspect
,利用AspectJ实现一个Around
拦截:
1 |
|
注意方法的第二个参数RoutingWith
是Spring传入的注解实例,我们根据注解的value()
获取配置的key。编译前需要添加一个Maven依赖:
1 | <dependency> |
到此为止,我们就实现了用注解动态选择数据源的功能。最后一步重构是用字符串常量替换散落在各处的"masterDataSource"
和"slaveDataSource"
。
使用限制
受Servlet线程模型的局限,动态数据源不能在一个请求内设定后再修改,也就是@RoutingWith
不能嵌套。此外,@RoutingWith
和@Transactional
混用时,要设定AOP的优先级。
本文代码需要SpringBoot支持,JDK 1.8编译并打开-parameters
编译参数。
# Compose 简介
Compose
项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排。从功能上看,跟 OpenStack
中的 Heat
十分类似。
其代码目前在 https://github.com/docker/compose (opens new window) 上开源。
Compose
定位是 「定义和运行多个 Docker 容器的应用(Defining and running multi-container Docker applications)」,其前身是开源项目 Fig。
通过第一部分中的介绍,我们知道使用一个 Dockerfile
模板文件,可以让用户很方便的定义一个单独的应用容器。然而,在日常工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器,甚至还包括负载均衡容器等。
Compose
恰好满足了这样的需求。它允许用户通过一个单独的 docker-compose.yml
模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。
Compose
中有两个重要的概念:
-
服务 (
service
):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。 -
项目 (
project
):由一组关联的应用容器组成的一个完整业务单元,在docker-compose.yml
文件中定义。
Compose
的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。
Compose
项目由 Python 编写,实现上调用了 Docker 服务提供的 API 来对容器进行管理。因此,只要所操作的平台支持 Docker API,就可以在其上利用 Compose
来进行编排管理。
# 安装与卸载
Compose
支持 Linux、macOS、Windows 三大平台。
Docker Desktop for Mac/Windows
自带 compose
,安装 Docker 之后可以直接使用。
1 | $ docker compose version |
Linux 系统请使用以下介绍的方法安装。
# 二进制包
在 Linux 上的也安装十分简单,从 官方 GitHub Release (opens new window) 处直接下载编译好的二进制文件即可。
例如,在 Linux 64 位系统上直接下载对应的二进制包。
1 | $ DOCKER_CONFIG=/usr/local/lib/docker/cli-plugins |
# 使用
# 术语
首先介绍几个术语。
-
服务 (
service
):一个应用容器,实际上可以运行多个相同镜像的实例。 -
项目 (
project
):由一组关联的应用容器组成的一个完整业务单元。
可见,一个项目可以由多个服务(容器)关联而成,Compose
面向项目进行管理。
# 场景
最常见的项目是 web 网站,该项目应该包含 web 应用和缓存。
下面我们用 Python
来建立一个能够记录页面访问次数的 web 网站。
# web 应用
新建文件夹,在该目录中编写 app.py
文件
1 | from flask import Flask |
# Dockerfile
编写 Dockerfile
文件,内容为
1 | FROM python:3.6-alpine |
# docker-compose.yml
编写 docker-compose.yml
文件,这个是 Compose 使用的主模板文件。
1 | version: '3' |
# 运行 compose 项目
此时访问本地 5000
端口,每次刷新页面,计数就会加 1。
# Compose 命令说明
# 命令对象与格式
对于 Compose 来说,大部分命令的对象既可以是项目本身,也可以指定为项目中的服务或者容器。如果没有特别的说明,命令对象将是项目,这意味着项目中所有的服务都会受到命令影响。
执行 docker compose [COMMAND] --help
可以查看具体某个命令的使用格式。
docker compose
命令的基本的使用格式是
1 | docker compose [-f=<arg>...] [options] [COMMAND] [ARGS...] |
# 命令选项
-
-f, --file FILE
指定使用的 Compose 模板文件,默认为docker-compose.yml
,可以多次指定。 -
-p, --project-name NAME
指定项目名称,默认将使用所在目录名称作为项目名。 -
--verbose
输出更多调试信息。 -
-v, --version
打印版本并退出。
# 命令使用说明
# build
格式为 docker compose build [options] [SERVICE...]
。
构建(重新构建)项目中的服务容器。
服务容器一旦构建后,将会带上一个标记名,例如对于 web 项目中的一个 db 容器,可能是 web_db。
可以随时在项目目录下运行 docker compose build
来重新构建服务。
选项包括:
-
--force-rm
删除构建过程中的临时容器。 -
--no-cache
构建镜像过程中不使用 cache(这将加长构建过程)。 -
--pull
始终尝试通过 pull 来获取更新版本的镜像。
# config
验证 Compose 文件格式是否正确,若正确则显示配置,若格式错误显示错误原因。
# down
此命令将会停止 up
命令所启动的容器,并移除网络
# exec
进入指定的容器。
# help
获得一个命令的帮助。
# images
列出 Compose 文件中包含的镜像。
# kill
格式为 docker compose kill [options] [SERVICE...]
。
通过发送 SIGKILL
信号来强制停止服务容器。
支持通过 -s
参数来指定发送的信号,例如通过如下指令发送 SIGINT
信号。
1 | $ docker compose kill -s SIGINT |
# logs
格式为 docker compose logs [options] [SERVICE...]
。
查看服务容器的输出。默认情况下,docker compose 将对不同的服务输出使用不同的颜色来区分。可以通过 --no-color
来关闭颜色。
该命令在调试问题的时候十分有用。
# pause
格式为 docker compose pause [SERVICE...]
。
暂停一个服务容器。
# port
格式为 docker compose port [options] SERVICE PRIVATE_PORT
。
打印某个容器端口所映射的公共端口。
选项:
-
--protocol=proto
指定端口协议,tcp(默认值)或者 udp。 -
--index=index
如果同一服务存在多个容器,指定命令对象容器的序号(默认为 1)。
# ps
格式为 docker compose ps [options] [SERVICE...]
。
列出项目中目前的所有容器。
选项:
-q
只打印容器的 ID 信息。
# pull
格式为 docker compose pull [options] [SERVICE...]
。
拉取服务依赖的镜像。
选项:
--ignore-pull-failures
忽略拉取镜像过程中的错误。
# push
推送服务依赖的镜像到 Docker 镜像仓库。
# restart
格式为 docker compose restart [options] [SERVICE...]
。
重启项目中的服务。
选项:
-t, --timeout TIMEOUT
指定重启前停止容器的超时(默认为 10 秒)。
# rm
格式为 docker compose rm [options] [SERVICE...]
。
删除所有(停止状态的)服务容器。推荐先执行 docker compose stop
命令来停止容器。
选项:
-
-f, --force
强制直接删除,包括非停止状态的容器。一般尽量不要使用该选项。 -
-v
删除容器所挂载的数据卷。
# run
格式为 docker compose run [options] [-p PORT...] [-e KEY=VAL...] SERVICE [COMMAND] [ARGS...]
。
在指定服务上执行一个命令。
例如:
1 | $ docker compose run ubuntu ping docker.com |
将会启动一个 ubuntu 服务容器,并执行 ping docker.com
命令。
默认情况下,如果存在关联,则所有关联的服务将会自动被启动,除非这些服务已经在运行中。
该命令类似启动容器后运行指定的命令,相关卷、链接等等都将会按照配置自动创建。
两个不同点:
-
给定命令将会覆盖原有的自动运行命令;
-
不会自动创建端口,以避免冲突。
如果不希望自动启动关联的容器,可以使用 --no-deps
选项,例如
1 | $ docker compose run --no-deps web python manage.py shell |
将不会启动 web 容器所关联的其它容器。
选项:
-
-d
后台运行容器。 -
--name NAME
为容器指定一个名字。 -
--entrypoint CMD
覆盖默认的容器启动指令。 -
-e KEY=VAL
设置环境变量值,可多次使用选项来设置多个环境变量。 -
-u, --user=""
指定运行容器的用户名或者 uid。 -
--no-deps
不自动启动关联的服务容器。 -
--rm
运行命令后自动删除容器,d
模式下将忽略。 -
-p, --publish=[]
映射容器端口到本地主机。 -
--service-ports
配置服务端口并映射到本地主机。 -
-T
不分配伪 tty,意味着依赖 tty 的指令将无法运行。
# start
格式为 docker compose start [SERVICE...]
。
启动已经存在的服务容器。
# stop
格式为 docker compose stop [options] [SERVICE...]
。
停止已经处于运行状态的容器,但不删除它。通过 docker compose start
可以再次启动这些容器。
选项:
-t, --timeout TIMEOUT
停止容器时候的超时(默认为 10 秒)。
# top
查看各个服务容器内运行的进程。
# unpause
格式为 docker compose unpause [SERVICE...]
。
恢复处于暂停状态中的服务。
# up
格式为 docker compose up [options] [SERVICE...]
。
该命令十分强大,它将尝试自动完成包括构建镜像,(重新)创建服务,启动服务,并关联服务相关容器的一系列操作。
链接的服务都将会被自动启动,除非已经处于运行状态。
可以说,大部分时候都可以直接通过该命令来启动一个项目。
默认情况,docker compose up
启动的容器都在前台,控制台将会同时打印所有容器的输出信息,可以很方便进行调试。
当通过 Ctrl-C
停止命令时,所有容器将会停止。
如果使用 docker compose up -d
,将会在后台启动并运行所有的容器。一般推荐生产环境下使用该选项。
默认情况,如果服务容器已经存在,docker compose up
将会尝试停止容器,然后重新创建(保持使用 volumes-from
挂载的卷),以保证新启动的服务匹配 docker-compose.yml
文件的最新内容。如果用户不希望容器被停止并重新创建,可以使用 docker compose up --no-recreate
。这样将只会启动处于停止状态的容器,而忽略已经运行的服务。如果用户只想重新部署某个服务,可以使用 docker compose up --no-deps -d <SERVICE_NAME>
来重新创建服务并后台停止旧服务,启动新服务,并不会影响到其所依赖的服务。
选项:
-
-d
在后台运行服务容器。 -
--no-color
不使用颜色来区分不同的服务的控制台输出。 -
--no-deps
不启动服务所链接的容器。 -
--force-recreate
强制重新创建容器,不能与--no-recreate
同时使用。 -
--no-recreate
如果容器已经存在了,则不重新创建,不能与--force-recreate
同时使用。 -
--no-build
不自动构建缺失的服务镜像。 -
-t, --timeout TIMEOUT
停止容器时候的超时(默认为 10 秒)。
# version
格式为 docker compose version
。
打印版本信息。
# 参考资料
# Compose 模板文件
模板文件是使用 Compose
的核心,涉及到的指令关键字也比较多。但大家不用担心,这里面大部分指令跟 docker run
相关参数的含义都是类似的。
默认的模板文件名称为 docker-compose.yml
,格式为 YAML 格式。
1 | version: "3" |
注意每个服务都必须通过 image
指令指定镜像或 build
指令(需要 Dockerfile)等来自动构建生成镜像。
如果使用 build
指令,在 Dockerfile
中设置的选项(例如:CMD
, EXPOSE
, VOLUME
, ENV
等) 将会自动被获取,无需在 docker-compose.yml
中重复设置。
下面分别介绍各个指令的用法。
# build
指定 Dockerfile
所在文件夹的路径(可以是绝对路径,或者相对 docker-compose.yml 文件的路径)。 Compose
将会利用它自动构建这个镜像,然后使用这个镜像。
1 | version: '3' |
你也可以使用 context
指令指定 Dockerfile
所在文件夹的路径。
使用 dockerfile
指令指定 Dockerfile
文件名。
使用 arg
指令指定构建镜像时的变量。
1 | version: '3' |
使用 cache_from
指定构建镜像的缓存
1 | build: |
# cap_add, cap_drop
指定容器的内核能力(capacity)分配。
例如,让容器拥有所有能力可以指定为:
去掉 NET_ADMIN 能力可以指定为:
# command
覆盖容器启动后默认执行的命令。
1 | command: echo "hello world" |
# configs
仅用于 Swarm mode
,详细内容请查看 Swarm mode
一节。
# cgroup_parent
指定父 cgroup
组,意味着将继承该组的资源限制。
例如,创建了一个 cgroup 组名称为 cgroups_1
。
1 | cgroup_parent: cgroups_1 |
# container_name
指定容器名称。默认将会使用 项目名称_服务名称_序号
这样的格式。
1 | container_name: docker-web-container |
注意: 指定容器名称后,该服务将无法进行扩展(scale),因为 Docker 不允许多个容器具有相同的名称。
# deploy
仅用于 Swarm mode
,详细内容请查看 Swarm mode
一节
# devices
指定设备映射关系。
1 | devices: |
# depends_on
解决容器的依赖、启动先后的问题。以下例子中会先启动 redis
db
再启动 web
1 | version: '3' |
注意:
web
服务不会等待redis
db
「完全启动」之后才启动。
# dns
自定义 DNS
服务器。可以是一个值,也可以是一个列表。
1 | dns: 8.8.8.8 |
# dns_search
配置 DNS
搜索域。可以是一个值,也可以是一个列表。
1 | dns_search: example.com |
# tmpfs
挂载一个 tmpfs 文件系统到容器。
1 | tmpfs: /run |
# env_file
从文件中获取环境变量,可以为单独的文件路径或列表。
如果通过 docker compose -f FILE
方式来指定 Compose 模板文件,则 env_file
中变量的路径会基于模板文件路径。
如果有变量名称与 environment
指令冲突,则按照惯例,以后者为准。
1 | env_file: .env |
环境变量文件中每一行必须符合格式,支持 #
开头的注释行。
1 | # common.env: Set development environment |
# environment
设置环境变量。你可以使用数组或字典两种格式。
只给定名称的变量会自动获取运行 Compose 主机上对应变量的值,可以用来防止泄露不必要的数据。
1 | environment: |
如果变量名称或者值中用到 true|false,yes|no
等表达 布尔 (opens new window) 含义的词汇,最好放到引号里,避免 YAML 自动解析某些内容为对应的布尔语义。这些特定词汇,包括
1 | y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF |
# expose
暴露端口,但不映射到宿主机,只被连接的服务访问。
仅可以指定内部端口为参数
1 | expose: |
# external_links
注意:不建议使用该指令。
链接到 docker-compose.yml
外部的容器,甚至并非 Compose
管理的外部容器。
1 | external_links: |
# extra_hosts
类似 Docker 中的 --add-host
参数,指定额外的 host 名称映射信息。
1 | extra_hosts: |
会在启动后的服务容器中 /etc/hosts
文件中添加如下两条条目。
1 | 8.8.8.8 googledns |
# healthcheck
通过命令检查容器是否健康运行。
1 | healthcheck: |
# image
指定为镜像名称或镜像 ID。如果镜像在本地不存在,Compose
将会尝试拉取这个镜像。
1 | image: ubuntu |
# labels
为容器添加 Docker 元数据(metadata)信息。例如可以为容器添加辅助说明信息。
1 | labels: |
# links
注意:不推荐使用该指令。
# logging
配置日志选项。
1 | logging: |
目前支持三种日志驱动类型。
1 | driver: "json-file" |
options
配置日志驱动的相关参数。
1 | options: |
# network_mode
设置网络模式。使用和 docker run
的 --network
参数一样的值。
1 | network_mode: "bridge" |
# networks
配置容器连接的网络。
1 | version: "3" |
# pid
跟主机系统共享进程命名空间。打开该选项的容器之间,以及容器和宿主机系统之间可以通过进程 ID 来相互访问和操作。
# ports
暴露端口信息。
使用宿主端口:容器端口 (HOST:CONTAINER)
格式,或者仅仅指定容器的端口(宿主将会随机选择端口)都可以。
1 | ports: |
注意:当使用 HOST:CONTAINER
格式来映射端口时,如果你使用的容器端口小于 60 并且没放到引号里,可能会得到错误结果,因为 YAML
会自动解析 xx:yy
这种数字格式为 60 进制。为避免出现这种问题,建议数字串都采用引号包括起来的字符串格式。
# secrets
存储敏感数据,例如 mysql
服务密码。
1 | version: "3.1" |
# security_opt
指定容器模板标签(label)机制的默认属性(用户、角色、类型、级别等)。例如配置标签的用户名和角色名。
1 | security_opt: |
# stop_signal
设置另一个信号来停止容器。在默认情况下使用的是 SIGTERM 停止容器。
# sysctls
配置容器内核参数。
1 | sysctls: |
# ulimits
指定容器的 ulimits 限制值。
例如,指定最大进程数为 65535,指定文件句柄数为 20000(软限制,应用可以随时修改,不能超过硬限制) 和 40000(系统硬限制,只能 root 用户提高)。
1 | ulimits: |
# volumes
数据卷所挂载路径设置。可以设置为宿主机路径(HOST:CONTAINER
)或者数据卷名称(VOLUME:CONTAINER
),并且可以设置访问模式 (HOST:CONTAINER:ro
)。
该指令中路径支持相对路径。
1 | volumes: |
如果路径为数据卷名称,必须在文件中配置数据卷。
1 | version: "3" |
# 其它指令
此外,还有包括 domainname, entrypoint, hostname, ipc, mac_address, privileged, read_only, shm_size, restart, stdin_open, tty, user, working_dir
等指令,基本跟 docker run
中对应参数的功能一致。
指定服务容器启动后执行的入口文件。
1 | entrypoint: /code/entrypoint.sh |
指定容器中运行应用的用户名。
指定容器中工作目录。
指定容器中搜索域名、主机名、mac 地址等。
1 | domainname: your_website.com |
允许容器中运行一些特权命令。
指定容器退出后的重启策略为始终重启。该命令对保持服务始终运行十分有效,在生产环境中推荐配置为 always
或者 unless-stopped
。
以只读模式挂载容器的 root 文件系统,意味着不能对容器内容进行修改。
打开标准输入,可以接受外部输入。
模拟一个伪终端。
# 读取变量
Compose 模板文件支持动态读取主机的系统环境变量和当前目录下的 .env
文件中的变量。
例如,下面的 Compose 文件将从运行它的环境中读取变量 ${MONGO_VERSION}
的值,并写入执行的指令中。
1 | version: "3" |
如果执行 MONGO_VERSION=3.2 docker compose up
则会启动一个 mongo:3.2
镜像的容器;如果执行 MONGO_VERSION=2.8 docker compose up
则会启动一个 mongo:2.8
镜像的容器。
若当前目录存在 .env
文件,执行 docker compose
命令时将从该文件中读取变量。
在当前目录新建 .env
文件并写入以下内容。
1 | # 支持 # 号注释 |
执行 docker compose up
则会启动一个 mongo:3.6
镜像的容器。
# 参考资料
# 使用 Django
本小节内容适合
Python
开发人员阅读。
我们现在将使用 Docker Compose
配置并运行一个 Django/PostgreSQL
应用。
在一切工作开始前,需要先编辑好三个必要的文件。
第一步,因为应用将要运行在一个满足所有环境依赖的 Docker 容器里面,那么我们可以通过编辑 Dockerfile
文件来指定 Docker 容器要安装内容。内容如下:
1 | FROM python:3 |
以上内容指定应用将使用安装了 Python 以及必要依赖包的镜像。更多关于如何编写 Dockerfile
文件的信息可以查看 Dockerfile 使用。
第二步,在 requirements.txt
文件里面写明需要安装的具体依赖包名。
1 | Django>=2.0,<3.0 |
第三步,docker-compose.yml
文件将把所有的东西关联起来。它描述了应用的构成(一个 web 服务和一个数据库)、使用的 Docker 镜像、镜像之间的连接、挂载到容器的卷,以及服务开放的端口。
1 | version: "3" |
查看 docker-compose.yml
章节 了解更多详细的工作机制。
现在我们就可以使用 docker compose run
命令启动一个 Django
应用了。
1 | $ docker compose run web django-admin startproject django_example . |
由于 web 服务所使用的镜像并不存在,所以 Compose 会首先使用 Dockerfile
为 web 服务构建一个镜像,接着使用这个镜像在容器里运行 django-admin startproject django_example
指令。
这将在当前目录生成一个 Django
应用。
1 | $ ls |
如果你的系统是 Linux,记得更改文件权限。
1 | $ sudo chown -R $USER:$USER . |
首先,我们要为应用设置好数据库的连接信息。用以下内容替换 django_example/settings.py
文件中 DATABASES = ...
定义的节点内容。
1 | DATABASES = { |
这些信息是在 postgres (opens new window) 镜像固定设置好的。然后,运行 docker compose up
:
1 | $ docker compose up |
这个 Django
应用已经开始在你的 Docker 守护进程里监听着 8000
端口了。打开 127.0.0.1:8000
即可看到 Django
欢迎页面。
你还可以在 Docker 上运行其它的管理命令,例如对于同步数据库结构这种事,在运行完 docker compose up
后,在另外一个终端进入文件夹运行以下命令即可:
1 | $ docker compose run web python manage.py syncdb |
# 使用 Rails
本小节内容适合
Ruby
开发人员阅读。
我们现在将使用 Compose
配置并运行一个 Rails/PostgreSQL
应用。
在一切工作开始前,需要先设置好三个必要的文件。
首先,因为应用将要运行在一个满足所有环境依赖的 Docker 容器里面,那么我们可以通过编辑 Dockerfile
文件来指定 Docker 容器要安装内容。内容如下:
1 | FROM ruby |
以上内容指定应用将使用安装了 Ruby、Bundler 以及其依赖件的镜像。更多关于如何编写 Dockerfile 文件的信息可以查看 Dockerfile 使用。
下一步,我们需要一个引导加载 Rails 的文件 Gemfile
。 等一会儿它还会被 rails new
命令覆盖重写。
1 | source 'https://rubygems.org' |
最后,docker-compose.yml
文件才是最神奇的地方。 docker-compose.yml
文件将把所有的东西关联起来。它描述了应用的构成(一个 web 服务和一个数据库)、每个镜像的来源(数据库运行在使用预定义的 PostgreSQL 镜像,web 应用侧将从本地目录创建)、镜像之间的连接,以及服务开放的端口。
1 | version: "3" |
所有文件就绪后,我们就可以通过使用 docker compose run
命令生成应用的骨架了。
1 | $ docker compose run web rails new . --force --database=postgresql --skip-bundle |
Compose
会先使用 Dockerfile
为 web 服务创建一个镜像,接着使用这个镜像在容器里运行 rails new
和它之后的命令。一旦这个命令运行完后,应该就可以看一个崭新的应用已经生成了。
1 | $ ls |
在新的 Gemfile
文件去掉加载 therubyracer
的行的注释,这样我们便可以使用 Javascript 运行环境:
1 | gem 'therubyracer', platforms: :ruby |
现在我们已经有一个新的 Gemfile
文件,需要再重新创建镜像。(这个会步骤会改变 Dockerfile 文件本身,所以需要重建一次)。
应用现在就可以启动了,但配置还未完成。Rails 默认读取的数据库目标是 localhost
,我们需要手动指定容器的 db
。同样的,还需要把用户名修改成和 postgres 镜像预定的一致。 打开最新生成的 database.yml
文件。用以下内容替换:
1 | development: &default |
现在就可以启动应用了。
如果一切正常,你应该可以看到 PostgreSQL 的输出,几秒后可以看到这样的重复信息:
1 | myapp_web_1 | [2014-01-17 17:16:29] INFO WEBrick 1.3.1 |
最后, 我们需要做的是创建数据库,打开另一个终端,运行:
1 | $ docker compose run web rake db:create |
这个 web 应用已经开始在你的 docker 守护进程里面监听着 3000 端口了。
# 项目简介
Kubernetes 是 Google 团队发起的开源项目,它的目标是管理跨多个主机的容器,提供基本的部署,维护以及应用伸缩,主要实现语言为 Go 语言。Kubernetes 是:
- 易学:轻量级,简单,容易理解
- 便携:支持公有云,私有云,混合云,以及多种云平台
- 可拓展:模块化,可插拔,支持钩子,可任意组合
- 自修复:自动重调度,自动重启,自动复制
Kubernetes 构建于 Google 数十年经验,一大半来源于 Google 生产环境规模的经验。结合了社区最佳的想法和实践。
在分布式系统中,部署,调度,伸缩一直是最为重要的也最为基础的功能。Kubernetes 就是希望解决这一序列问题的。
Kubernetes 目前在GitHub (opens new window)进行维护。
# Kubernetes 能够运行在任何地方!
虽然 Kubernetes 最初是为 GCE 定制的,但是在后续版本中陆续增加了其他云平台的支持,以及本地数据中心的支持。
# 基本概念
- 节点(
Node
):一个节点是一个运行 Kubernetes 中的主机。 - 容器组(
Pod
):一个 Pod 对应于由若干容器组成的一个容器组,同个组内的容器共享一个存储卷(volume)。 - 容器组生命周期(
pos-states
):包含所有容器状态集合,包括容器组状态类型,容器组生命周期,事件,重启策略,以及 replication controllers。 - Replication Controllers:主要负责指定数量的 pod 在同一时间一起运行。
- 服务(
services
):一个 Kubernetes 服务是容器组逻辑的高级抽象,同时也对外提供访问容器组的策略。 - 卷(
volumes
):一个卷就是一个目录,容器对其有访问权限。 - 标签(
labels
):标签是用来连接一组对象的,比如容器组。标签可以被用来组织和选择子对象。 - 接口权限(
accessing_the_api
):端口,IP 地址和代理的防火墙规则。 - web 界面(
ux
):用户可以通过 web 界面操作 Kubernetes。 - 命令行操作(
cli
):kubectl
命令。
# 节点
在 Kubernetes
中,节点是实际工作的点,节点可以是虚拟机或者物理机器,依赖于一个集群环境。每个节点都有一些必要的服务以运行容器组,并且它们都可以通过主节点来管理。必要服务包括 Docker,kubelet 和代理服务。
# 容器状态
容器状态用来描述节点的当前状态。现在,其中包含三个信息:
# 主机IP
主机 IP 需要云平台来查询,Kubernetes
把它作为状态的一部分来保存。如果 Kubernetes
没有运行在云平台上,节点 ID 就是必需的。IP 地址可以变化,并且可以包含多种类型的 IP 地址,如公共 IP,私有 IP,动态 IP,ipv6 等等。
# 节点周期
通常来说节点有 Pending
,Running
,Terminated
三个周期,如果 Kubernetes 发现了一个节点并且其可用,那么 Kubernetes 就把它标记为 Pending
。然后在某个时刻,Kubernetes 将会标记其为 Running
。节点的结束周期称为 Terminated
。一个已经 Terminated
的节点不会接受和调度任何请求,并且已经在其上运行的容器组也会删除。
# 节点状态
节点的状态主要是用来描述处于 Running
的节点。当前可用的有 NodeReachable
和 NodeReady
。以后可能会增加其他状态。NodeReachable
表示集群可达。NodeReady
表示 kubelet 返回 Status Ok 并且 HTTP 状态检查健康。
# 节点管理
节点并非 Kubernetes 创建,而是由云平台创建,或者就是物理机器、虚拟机。在 Kubernetes 中,节点仅仅是一条记录,节点创建之后,Kubernetes 会检查其是否可用。在 Kubernetes 中,节点用如下结构保存:
1 | { |
Kubernetes 校验节点可用依赖于 ID。在当前的版本中,有两个接口可以用来管理节点:节点控制和 Kube 管理。
# 节点控制
在 Kubernetes 主节点中,节点控制器是用来管理节点的组件。主要包含:
- 集群范围内节点同步
- 单节点生命周期管理
节点控制有一个同步轮寻,主要监听所有云平台的虚拟实例,会根据节点状态创建和删除。可以通过 --node_sync_period
标志来控制该轮寻。如果一个实例已经创建,节点控制将会为其创建一个结构。同样的,如果一个节点被删除,节点控制也会删除该结构。在 Kubernetes 启动时可用通过 --machines
标记来显示指定节点。同样可以使用 kubectl
来一条一条的添加节点,两者是相同的。通过设置 --sync_nodes=false
标记来禁止集群之间的节点同步,你也可以使用 api/kubectl 命令行来增删节点。
# 容器组
在 Kubernetes 中,使用的最小单位是容器组,容器组是创建,调度,管理的最小单位。 一个容器组使用相同的 Docker 容器并共享卷(挂载点)。一个容器组是一个特定应用的打包集合,包含一个或多个容器。
和运行的容器类似,一个容器组被认为只有很短的运行周期。容器组被调度到一组节点运行,直到容器的生命周期结束或者其被删除。如果节点死掉,运行在其上的容器组将会被删除而不是重新调度。(也许在将来的版本中会添加容器组的移动)。
# 容器组设计的初衷
# 资源共享和通信
容器组主要是为了数据共享和它们之间的通信。
在一个容器组中,容器都使用相同的网络地址和端口,可以通过本地网络来相互通信。每个容器组都有独立的 IP,可用通过网络来和其他物理主机或者容器通信。
容器组有一组存储卷(挂载点),主要是为了让容器在重启之后可以不丢失数据。
# 容器组管理
容器组是一个应用管理和部署的高层次抽象,同时也是一组容器的接口。容器组是部署、水平放缩的最小单位。
# 容器组的使用
容器组可以通过组合来构建复杂的应用,其本来的意义包含:
- 内容管理,文件和数据加载以及本地缓存管理等。
- 日志和检查点备份,压缩,快照等。
- 监听数据变化,跟踪日志,日志和监控代理,消息发布等。
- 代理,网桥
- 控制器,管理,配置以及更新
# 替代方案
为什么不在一个单一的容器里运行多个程序?
- 1.透明化。为了使容器组中的容器保持一致的基础设施和服务,比如进程管理和资源监控。这样设计是为了用户的便利性。
- 2.解偶软件之间的依赖。每个容器都可能重新构建和发布,Kubernetes 必须支持热发布和热更新(将来)。
- 3.方便使用。用户不必运行独立的程序管理,也不用担心每个应用程序的退出状态。
- 4.高效。考虑到基础设施有更多的职责,容器必须要轻量化。
# 容器组的生命状态
包括若干状态值:pending
、running
、succeeded
、failed
。
# pending
容器组已经被节点接受,但有一个或多个容器还没有运行起来。这将包含某些节点正在下载镜像的时间,这种情形会依赖于网络情况。
# running
容器组已经被调度到节点,并且所有的容器都已经启动。至少有一个容器处于运行状态(或者处于重启状态)。
# succeeded
所有的容器都正常退出。
# failed
容器组中所有容器都意外中断了。
# 容器组生命周期
通常来说,如果容器组被创建了就不会自动销毁,除非被某种行为触发,而触发此种情况可能是人为,或者复制控制器所为。唯一例外的是容器组由 succeeded 状态成功退出,或者在一定时间内重试多次依然失败。
如果某个节点死掉或者不能连接,那么节点控制器将会标记其上的容器组的状态为 failed
。
举例如下。
- 容器组状态
running
,有 1 容器,容器正常退出- 记录完成事件
- 如果重启策略为:
- 始终:重启容器,容器组保持
running
- 失败时:容器组变为
succeeded
- 从不:容器组变为
succeeded
- 始终:重启容器,容器组保持
- 容器组状态
running
,有1容器,容器异常退出- 记录失败事件
- 如果重启策略为:
- 始终:重启容器,容器组保持
running
- 失败时:重启容器,容器组保持
running
- 从不:容器组变为
failed
- 始终:重启容器,容器组保持
- 容器组状态
running
,有2容器,有1容器异常退出- 记录失败事件
- 如果重启策略为:
- 始终:重启容器,容器组保持
running
- 失败时:重启容器,容器组保持
running
- 从不:容器组保持
running
- 始终:重启容器,容器组保持
- 当有2容器退出
- 记录失败事件
- 如果重启策略为:
- 始终:重启容器,容器组保持
running
- 失败时:重启容器,容器组保持
running
- 从不:容器组变为
failed
- 始终:重启容器,容器组保持
- 容器组状态
running
,容器内存不足- 标记容器错误中断
- 记录内存不足事件
- 如果重启策略为:
- 始终:重启容器,容器组保持
running
- 失败时:重启容器,容器组保持
running
- 从不:记录错误事件,容器组变为
failed
- 始终:重启容器,容器组保持
- 容器组状态
running
,一块磁盘死掉- 杀死所有容器
- 记录事件
- 容器组变为
failed
- 如果容器组运行在一个控制器下,容器组将会在其他地方重新创建
- 容器组状态
running
,对应的节点段溢出- 节点控制器等到超时
- 节点控制器标记容器组
failed
- 如果容器组运行在一个控制器下,容器组将会在其他地方重新创建
# Replication Controllers
# 服务
# 卷
# 标签
# 接口权限
# web界面
# 命令行操作
# 基本架构
任何优秀的项目都离不开优秀的架构设计。本小节将介绍 Kubernetes 在架构方面的设计考虑。
# 基本考虑
如果让我们自己从头设计一套容器管理平台,有如下几个方面是很容易想到的:
- 分布式架构,保证扩展性;
- 逻辑集中式的控制平面 + 物理分布式的运行平面;
- 一套资源调度系统,管理哪个容器该分配到哪个节点上;
- 一套对容器内服务进行抽象和 HA 的系统。
# 运行原理
下面这张图完整展示了 Kubernetes 的运行原理。
可见,Kubernetes 首先是一套分布式系统,由多个节点组成,节点分为两类:一类是属于管理平面的主节点/控制节点(Master Node);一类是属于运行平面的工作节点(Worker Node)。
显然,复杂的工作肯定都交给控制节点去做了,工作节点负责提供稳定的操作接口和能力抽象即可。
从这张图上,我们没有能发现 Kubernetes 中对于控制平面的分布式实现,但是由于数据后端自身就是一套分布式的数据库 Etcd,因此可以很容易扩展到分布式实现。
# 控制平面
# 主节点服务
主节点上需要提供如下的管理服务:
apiserver
是整个系统的对外接口,提供一套 RESTful 的 Kubernetes API (opens new window),供客户端和其它组件调用;scheduler
负责对资源进行调度,分配某个 pod 到某个节点上。是 pluggable 的,意味着很容易选择其它实现方式;controller-manager
负责管理控制器,包括 endpoint-controller(刷新服务和 pod 的关联信息)和 replication-controller(维护某个 pod 的复制为配置的数值)。
# Etcd
这里 Etcd 即作为数据后端,又作为消息中间件。
通过 Etcd 来存储所有的主节点上的状态信息,很容易实现主节点的分布式扩展。
组件可以自动的去侦测 Etcd 中的数值变化来获得通知,并且获得更新后的数据来执行相应的操作。
# 工作节点
- kubelet 是工作节点执行操作的 agent,负责具体的容器生命周期管理,根据从数据库中获取的信息来管理容器,并上报 pod 运行状态等;
- kube-proxy 是一个简单的网络访问代理,同时也是一个 Load Balancer。它负责将访问到某个服务的请求具体分配给工作节点上的 Pod(同一类标签)。
云服务器99/元首年特惠 (opens new window)
部署 Kubernetes
小于 1 分钟 约 102 字
# 部署 Kubernetes
目前,Kubernetes 支持在多种环境下使用,包括本地主机(Ubuntu、Debian、CentOS、Fedora 等)、云服务(腾讯云 (opens new window)、阿里云 (opens new window)、百度云 (opens new window) 等)。
你可以使用以下几种方式部署 Kubernetes:
- kubeadm
- docker-desktop
- k3s
接下来的小节会对以上几种方式进行详细介绍。
基本架构 使用 kubeadm 部署 kubernetes(CRI 使用 containerd)
# 使用 kubeadm 部署 kubernetes(CRI 使用 containerd)
kubeadm
提供了 kubeadm init
以及 kubeadm join
这两个命令作为快速创建 kubernetes
集群的最佳实践。
# 安装 containerd
参考 安装 Docker 一节添加 apt/yum 源,之后执行如下命令。
1 | # debian 系 |
# 配置 containerd
新建 /etc/systemd/system/cri-containerd.service
文件
1 | [Unit] |
新建 /etc/cri-containerd/config.toml
containerd 配置文件
1 | version = 2 |
# 安装 kubelet kubeadm kubectl cri-tools kubernetes-cni
# Ubuntu/Debian
1 | $ apt-get update && apt-get install -y apt-transport-https |
# CentOS/Fedora
1 | $ cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo |
# 修改内核的运行参数
1 | $ cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf |
# 配置 kubelet
# 修改 kubelet.service
/etc/systemd/system/kubelet.service.d/10-proxy-ipvs.conf
写入以下内容
1 | # 启用 ipvs 相关内核模块 |
执行以下命令应用配置。
1 | $ sudo systemctl daemon-reload |
# 部署
# master
1 | $ systemctl enable cri-containerd |
--pod-network-cidr 10.244.0.0/16
参数与后续 CNI 插件有关,这里以flannel
为例,若后续部署其他类型的网络插件请更改此参数。
执行可能出现错误,例如缺少依赖包,根据提示安装即可。
执行成功会输出
1 | ... |
# node 工作节点
在 另一主机 重复 部署 小节以前的步骤,安装配置好 kubelet。根据提示,加入到集群。
1 | $ systemctl enable cri-containerd |
# 查看服务
所有服务启动后,通过 crictl
查看本地实际运行的容器。这些服务大概分为三类:主节点服务、工作节点服务和其它服务。
1 | CONTAINER_RUNTIME_ENDPOINT=/run/cri-containerd/cri-containerd.sock crictl ps -a |
# 主节点服务
-
apiserver
是整个系统的对外接口,提供 RESTful 方式供客户端和其它组件调用; -
scheduler
负责对资源进行调度,分配某个 pod 到某个节点上; -
controller-manager
负责管理控制器,包括 endpoint-controller(刷新服务和 pod 的关联信息)和 replication-controller(维护某个 pod 的复制为配置的数值)。
# 工作节点服务
proxy
为 pod 上的服务提供访问的代理。
# 其它服务
- Etcd 是所有状态的存储数据库;
# 使用
将 /etc/kubernetes/admin.conf
复制到 ~/.kube/config
执行 $ kubectl get all -A
查看启动的服务。
由于未部署 CNI 插件,CoreDNS 未正常启动。如何使用 Kubernetes,请参考后续章节。
# 部署 CNI
这里以 flannel
为例进行介绍。
# flannel
检查 podCIDR 设置
1 | $ kubectl get node -o yaml | grep CIDR |
1 | $ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/v0.11.0/Documentation/kube-flannel.yml |
# master 节点默认不能运行 pod
如果用 kubeadm
部署一个单节点集群,默认情况下无法使用,请执行以下命令解除限制
1 | $ kubectl taint nodes --all node-role.kubernetes.io/master- |
# 参考文档
# Docker Desktop 启用 Kubernetes
使用 Docker Desktop 可以很方便的启用 Kubernetes,由于国内获取不到 k8s.gcr.io
镜像,我们必须首先解决这一问题。
# 获取 k8s.gcr.io
镜像
由于国内拉取不到 k8s.gcr.io
镜像,我们可以使用开源项目 AliyunContainerService/k8s-for-docker-desktop (opens new window) 来获取所需的镜像。
# 启用 Kubernetes
在 Docker Desktop 设置页面,点击 Kubernetes
,选择 Enable Kubernetes
,稍等片刻,看到左下方 Kubernetes
变为 running
,Kubernetes 启动成功。
# 测试
1 | $ kubectl version |
如果正常输出信息,则证明 Kubernetes 成功启动。
# Kubernetes Dashboard
Kubernetes Dashboard (opens new window) 是基于网页的 Kubernetes 用户界面。
# 部署
执行以下命令即可部署 Dashboard:
1 | kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0/aio/deploy/recommended.yaml |
# 访问
通过命令行代理访问,执行以下命令:
# 登录
目前,Dashboard 仅支持使用 Bearer 令牌登录。下面教大家如何创建该令牌:
1 | $ kubectl create sa dashboard-admin -n kube-system |
将结果粘贴到登录页面,即可登录。
# 参考文档
# 如何调试 Docker
# 开启 Debug 模式
在 dockerd 配置文件 daemon.json(默认位于 /etc/docker/)中添加
1 | { |
重启守护进程。
1 | $ sudo kill -SIGHUP $(pidof dockerd) |
此时 dockerd 会在日志中输入更多信息供分析。
# 检查内核日志
1 | $ sudo dmesg |grep dockerd |
# Docker 不响应时处理
可以杀死 dockerd 进程查看其堆栈调用情况。
1 | $ sudo kill -SIGUSR1 $(pidof dockerd) |
# 重置 Docker 本地数据
注意,本操作会移除所有的 Docker 本地数据,包括镜像和容器等。
1 | $ sudo rm -rf /var/lib/docker |
# Dockerfile 最佳实践
本附录是笔者对 Docker 官方文档中 Best practices for writing Dockerfiles (opens new window) 的理解与翻译。
# 一般性的指南和建议
# 容器应该是短暂的
通过 Dockerfile
构建的镜像所启动的容器应该尽可能短暂(生命周期短)。「短暂」意味着可以停止和销毁容器,并且创建一个新容器并部署好所需的设置和配置工作量应该是极小的。
# 使用 .dockerignore
文件
使用 Dockerfile
构建镜像时最好是将 Dockerfile
放置在一个新建的空目录下。然后将构建镜像所需要的文件添加到该目录中。为了提高构建镜像的效率,你可以在目录下新建一个 .dockerignore
文件来指定要忽略的文件和目录。.dockerignore
文件的排除模式语法和 Git 的 .gitignore
文件相似。
# 使用多阶段构建
在 Docker 17.05
以上版本中,你可以使用 多阶段构建 来减少所构建镜像的大小。
# 避免安装不必要的包
为了降低复杂性、减少依赖、减小文件大小、节约构建时间,你应该避免安装任何不必要的包。例如,不要在数据库镜像中包含一个文本编辑器。
# 一个容器只运行一个进程
应该保证在一个容器中只运行一个进程。将多个应用解耦到不同容器中,保证了容器的横向扩展和复用。例如 web 应用应该包含三个容器:web应用、数据库、缓存。
如果容器互相依赖,你可以使用 Docker 自定义网络 来把这些容器连接起来。
# 镜像层数尽可能少
你需要在 Dockerfile
可读性(也包括长期的可维护性)和减少层数之间做一个平衡。
# 将多行参数排序
将多行参数按字母顺序排序(比如要安装多个包时)。这可以帮助你避免重复包含同一个包,更新包列表时也更容易。也便于 PRs
阅读和审查。建议在反斜杠符号 \
之前添加一个空格,以增加可读性。
下面是来自 buildpack-deps
镜像的例子:
1 | RUN apt-get update && apt-get install -y \ |
# 构建缓存
在镜像的构建过程中,Docker 会遍历 Dockerfile
文件中的指令,然后按顺序执行。在执行每条指令之前,Docker 都会在缓存中查找是否已经存在可重用的镜像,如果有就使用现存的镜像,不再重复创建。如果你不想在构建过程中使用缓存,你可以在 docker build
命令中使用 --no-cache=true
选项。
但是,如果你想在构建的过程中使用缓存,你得明白什么时候会,什么时候不会找到匹配的镜像,遵循的基本规则如下:
- 从一个基础镜像开始(
FROM
指令指定),下一条指令将和该基础镜像的所有子镜像进行匹配,检查这些子镜像被创建时使用的指令是否和被检查的指令完全一样。如果不是,则缓存失效。 - 在大多数情况下,只需要简单地对比
Dockerfile
中的指令和子镜像。然而,有些指令需要更多的检查和解释。 - 对于
ADD
和COPY
指令,镜像中对应文件的内容也会被检查,每个文件都会计算出一个校验和。文件的最后修改时间和最后访问时间不会纳入校验。在缓存的查找过程中,会将这些校验和和已存在镜像中的文件校验和进行对比。如果文件有任何改变,比如内容和元数据,则缓存失效。 - 除了
ADD
和COPY
指令,缓存匹配过程不会查看临时容器中的文件来决定缓存是否匹配。例如,当执行完RUN apt-get -y update
指令后,容器中一些文件被更新,但 Docker 不会检查这些文件。这种情况下,只有指令字符串本身被用来匹配缓存。
一旦缓存失效,所有后续的 Dockerfile
指令都将产生新的镜像,缓存不会被使用。
# Dockerfile 指令
下面针对 Dockerfile
中各种指令的最佳编写方式给出建议。
# FROM
尽可能使用当前官方仓库作为你构建镜像的基础。推荐使用 Alpine (opens new window) 镜像,因为它被严格控制并保持最小尺寸(目前小于 5 MB),但它仍然是一个完整的发行版。
# LABEL
你可以给镜像添加标签来帮助组织镜像、记录许可信息、辅助自动化构建等。每个标签一行,由 LABEL
开头加上一个或多个标签对。下面的示例展示了各种不同的可能格式。#
开头的行是注释内容。
注意:如果你的字符串中包含空格,必须将字符串放入引号中或者对空格使用转义。如果字符串内容本身就包含引号,必须对引号使用转义。
1 | # Set one or more individual labels |
一个镜像可以包含多个标签,但建议将多个标签放入到一个 LABEL
指令中。
1 | # Set multiple labels at once, using line-continuation characters to break long lines |
关于标签可以接受的键值对,参考 Understanding object labels (opens new window)。关于查询标签信息,参考 Managing labels on objects (opens new window)。
# RUN
为了保持 Dockerfile
文件的可读性,可理解性,以及可维护性,建议将长的或复杂的 RUN
指令用反斜杠 \
分割成多行。
# apt-get
RUN
指令最常见的用法是安装包用的 apt-get
。因为 RUN apt-get
指令会安装包,所以有几个问题需要注意。
不要使用 RUN apt-get upgrade
或 dist-upgrade
,因为许多基础镜像中的「必须」包不会在一个非特权容器中升级。如果基础镜像中的某个包过时了,你应该联系它的维护者。如果你确定某个特定的包,比如 foo
,需要升级,使用 apt-get install -y foo
就行,该指令会自动升级 foo
包。
永远将 RUN apt-get update
和 apt-get install
组合成一条 RUN
声明,例如:
1 | RUN apt-get update && apt-get install -y \ |
将 apt-get update
放在一条单独的 RUN
声明中会导致缓存问题以及后续的 apt-get install
失败。比如,假设你有一个 Dockerfile
文件:
1 | FROM ubuntu:18.04 |
构建镜像后,所有的层都在 Docker 的缓存中。假设你后来又修改了其中的 apt-get install
添加了一个包:
1 | FROM ubuntu:18.04 |
Docker 发现修改后的 RUN apt-get update
指令和之前的完全一样。所以,apt-get update
不会执行,而是使用之前的缓存镜像。因为 apt-get update
没有运行,后面的 apt-get install
可能安装的是过时的 curl
和 nginx
版本。
使用 RUN apt-get update && apt-get install -y
可以确保你的 Dockerfiles 每次安装的都是包的最新的版本,而且这个过程不需要进一步的编码或额外干预。这项技术叫作 cache busting
。你也可以显示指定一个包的版本号来达到 cache-busting
,这就是所谓的固定版本,例如:
1 | RUN apt-get update && apt-get install -y \ |
固定版本会迫使构建过程检索特定的版本,而不管缓存中有什么。这项技术也可以减少因所需包中未预料到的变化而导致的失败。
下面是一个 RUN
指令的示例模板,展示了所有关于 apt-get
的建议。
1 | RUN apt-get update && apt-get install -y \ |
其中 s3cmd
指令指定了一个版本号 1.1.*
。如果之前的镜像使用的是更旧的版本,指定新的版本会导致 apt-get udpate
缓存失效并确保安装的是新版本。
另外,清理掉 apt 缓存 var/lib/apt/lists
可以减小镜像大小。因为 RUN
指令的开头为 apt-get udpate
,包缓存总是会在 apt-get install
之前刷新。
注意:官方的 Debian 和 Ubuntu 镜像会自动运行 apt-get clean,所以不需要显式的调用 apt-get clean。
# CMD
CMD
指令用于执行目标镜像中包含的软件,可以包含参数。CMD
大多数情况下都应该以 CMD ["executable", "param1", "param2"...]
的形式使用。因此,如果创建镜像的目的是为了部署某个服务(比如 Apache
),你可能会执行类似于 CMD ["apache2", "-DFOREGROUND"]
形式的命令。我们建议任何服务镜像都使用这种形式的命令。
多数情况下,CMD
都需要一个交互式的 shell
(bash, Python, perl 等),例如 CMD ["perl", "-de0"]
,或者 CMD ["PHP", "-a"]
。使用这种形式意味着,当你执行类似 docker run -it python
时,你会进入一个准备好的 shell
中。CMD
应该在极少的情况下才能以 CMD ["param", "param"]
的形式与 ENTRYPOINT
协同使用,除非你和你的镜像使用者都对 ENTRYPOINT
的工作方式十分熟悉。
# EXPOSE
EXPOSE
指令用于指定容器将要监听的端口。因此,你应该为你的应用程序使用常见的端口。例如,提供 Apache
web 服务的镜像应该使用 EXPOSE 80
,而提供 MongoDB
服务的镜像使用 EXPOSE 27017
。
对于外部访问,用户可以在执行 docker run
时使用一个标志来指示如何将指定的端口映射到所选择的端口。
# ENV
为了方便新程序运行,你可以使用 ENV
来为容器中安装的程序更新 PATH
环境变量。例如使用 ENV PATH /usr/local/nginx/bin:$PATH
来确保 CMD ["nginx"]
能正确运行。
ENV
指令也可用于为你想要容器化的服务提供必要的环境变量,比如 Postgres 需要的 PGDATA
。
最后,ENV
也能用于设置常见的版本号,比如下面的示例:
1 | ENV PG_MAJOR 9.3 |
类似于程序中的常量,这种方法可以让你只需改变 ENV
指令来自动的改变容器中的软件版本。
# ADD 和 COPY
虽然 ADD
和 COPY
功能类似,但一般优先使用 COPY
。因为它比 ADD
更透明。COPY
只支持简单将本地文件拷贝到容器中,而 ADD
有一些并不明显的功能(比如本地 tar 提取和远程 URL 支持)。因此,ADD
的最佳用例是将本地 tar 文件自动提取到镜像中,例如 ADD rootfs.tar.xz
。
如果你的 Dockerfile
有多个步骤需要使用上下文中不同的文件。单独 COPY
每个文件,而不是一次性的 COPY
所有文件,这将保证每个步骤的构建缓存只在特定的文件变化时失效。例如:
1 | COPY requirements.txt /tmp/ |
如果将 COPY . /tmp/
放置在 RUN
指令之前,只要 .
目录中任何一个文件变化,都会导致后续指令的缓存失效。
为了让镜像尽量小,最好不要使用 ADD
指令从远程 URL 获取包,而是使用 curl
和 wget
。这样你可以在文件提取完之后删掉不再需要的文件来避免在镜像中额外添加一层。比如尽量避免下面的用法:
1 | ADD http://example.com/big.tar.xz /usr/src/things/ |
而是应该使用下面这种方法:
1 | RUN mkdir -p /usr/src/things \ |
上面使用的管道操作,所以没有中间文件需要删除。
对于其他不需要 ADD
的自动提取功能的文件或目录,你应该使用 COPY
。
# ENTRYPOINT
ENTRYPOINT
的最佳用处是设置镜像的主命令,允许将镜像当成命令本身来运行(用 CMD
提供默认选项)。
例如,下面的示例镜像提供了命令行工具 s3cmd
:
1 | ENTRYPOINT ["s3cmd"] |
现在直接运行该镜像创建的容器会显示命令帮助:
或者提供正确的参数来执行某个命令:
1 | $ docker run s3cmd ls s3://mybucket |
这样镜像名可以当成命令行的参考。
ENTRYPOINT
指令也可以结合一个辅助脚本使用,和前面命令行风格类似,即使启动工具需要不止一个步骤。
例如,Postgres
官方镜像使用下面的脚本作为 ENTRYPOINT
:
1 |
|
注意:该脚本使用了 Bash 的内置命令 exec,所以最后运行的进程就是容器的 PID 为 1 的进程。这样,进程就可以接收到任何发送给容器的 Unix 信号了。
该辅助脚本被拷贝到容器,并在容器启动时通过 ENTRYPOINT
执行:
1 | COPY ./docker-entrypoint.sh / |
该脚本可以让用户用几种不同的方式和 Postgres
交互。
你可以很简单地启动 Postgres
:
也可以执行 Postgres
并传递参数:
1 | $ docker run postgres postgres --help |
最后,你还可以启动另外一个完全不同的工具,比如 Bash
:
1 | $ docker run --rm -it postgres bash |
# VOLUME
VOLUME
指令用于暴露任何数据库存储文件,配置文件,或容器创建的文件和目录。强烈建议使用 VOLUME
来管理镜像中的可变部分和用户可以改变的部分。
# USER
如果某个服务不需要特权执行,建议使用 USER
指令切换到非 root 用户。先在 Dockerfile
中使用类似 RUN groupadd -r postgres && useradd -r -g postgres postgres
的指令创建用户和用户组。
注意:在镜像中,用户和用户组每次被分配的 UID/GID 都是不确定的,下次重新构建镜像时被分配到的 UID/GID 可能会不一样。如果要依赖确定的 UID/GID,你应该显式的指定一个 UID/GID。
你应该避免使用 sudo
,因为它不可预期的 TTY 和信号转发行为可能造成的问题比它能解决的问题还多。如果你真的需要和 sudo
类似的功能(例如,以 root 权限初始化某个守护进程,以非 root 权限执行它),你可以使用 gosu (opens new window)。
最后,为了减少层数和复杂度,避免频繁地使用 USER
来回切换用户。
# WORKDIR
为了清晰性和可靠性,你应该总是在 WORKDIR
中使用绝对路径。另外,你应该使用 WORKDIR
来替代类似于 RUN cd ... && do-something
的指令,后者难以阅读、排错和维护。
# 官方镜像示例
这些官方镜像的 Dockerfile 都是参考典范:https://github.com/docker-library/docs
# Docker 命令查询
# 基本语法
Docker 命令有两大类,客户端命令和服务端命令。前者是主要的操作接口,后者用来启动 Docker Daemon。
-
客户端命令:基本命令格式为
docker [OPTIONS] COMMAND [arg...]
; -
服务端命令:基本命令格式为
dockerd [OPTIONS]
。
可以通过 man docker
或 docker help
来查看这些命令。
接下来的小节对这两个命令进行介绍。
# 客户端命令(docker)
# 客户端命令选项
--config=""
:指定客户端配置文件,默认为~/.docker
;-D=true|false
:是否使用 debug 模式。默认不开启;-H, --host=[]
:指定命令对应 Docker 守护进程的监听接口,可以为 unix 套接字unix:///path/to/socket
,文件句柄fd://socketfd
或 tcp 套接字tcp://[host[:port]]
,默认为unix:///var/run/docker.sock
;-l, --log-level="debug|info|warn|error|fatal"
:指定日志输出级别;--tls=true|false
:是否对 Docker 守护进程启用 TLS 安全机制,默认为否;--tlscacert=/.docker/ca.pem
:TLS CA 签名的可信证书文件路径;--tlscert=/.docker/cert.pem
:TLS 可信证书文件路径;--tlscert=/.docker/key.pem
:TLS 密钥文件路径;--tlsverify=true|false
:启用 TLS 校验,默认为否。
# 客户端命令
可以通过 docker COMMAND --help
来查看这些命令的具体用法。
attach
:依附到一个正在运行的容器中;build
:从一个 Dockerfile 创建一个镜像;commit
:从一个容器的修改中创建一个新的镜像;cp
:在容器和本地宿主系统之间复制文件中;create
:创建一个新容器,但并不运行它;diff
:检查一个容器内文件系统的修改,包括修改和增加;events
:从服务端获取实时的事件;exec
:在运行的容器内执行命令;export
:导出容器内容为一个tar
包;history
:显示一个镜像的历史信息;images
:列出存在的镜像;import
:导入一个文件(典型为tar
包)路径或目录来创建一个本地镜像;info
:显示一些相关的系统信息;inspect
:显示一个容器的具体配置信息;kill
:关闭一个运行中的容器 (包括进程和所有相关资源);load
:从一个 tar 包中加载一个镜像;login
:注册或登录到一个 Docker 的仓库服务器;logout
:从 Docker 的仓库服务器登出;logs
:获取容器的 log 信息;network
:管理 Docker 的网络,包括查看、创建、删除、挂载、卸载等;node
:管理 swarm 集群中的节点,包括查看、更新、删除、提升/取消管理节点等;pause
:暂停一个容器中的所有进程;port
:查找一个 nat 到一个私有网口的公共口;ps
:列出主机上的容器;pull
:从一个Docker的仓库服务器下拉一个镜像或仓库;push
:将一个镜像或者仓库推送到一个 Docker 的注册服务器;rename
:重命名一个容器;restart
:重启一个运行中的容器;rm
:删除给定的若干个容器;rmi
:删除给定的若干个镜像;run
:创建一个新容器,并在其中运行给定命令;save
:保存一个镜像为 tar 包文件;search
:在 Docker index 中搜索一个镜像;service
:管理 Docker 所启动的应用服务,包括创建、更新、删除等;start
:启动一个容器;stats
:输出(一个或多个)容器的资源使用统计信息;stop
:终止一个运行中的容器;swarm
:管理 Docker swarm 集群,包括创建、加入、退出、更新等;tag
:为一个镜像打标签;top
:查看一个容器中的正在运行的进程信息;unpause
:将一个容器内所有的进程从暂停状态中恢复;update
:更新指定的若干容器的配置信息;version
:输出 Docker 的版本信息;volume
:管理 Docker volume,包括查看、创建、删除等;wait
:阻塞直到一个容器终止,然后输出它的退出符。
# 一张图总结 Docker 的命令
# 参考
# 服务端命令(dockerd)
# dockerd 命令选项
--api-cors-header=""
:CORS 头部域,默认不允许 CORS,要允许任意的跨域访问,可以指定为 “*”;--authorization-plugin=""
:载入认证的插件;-b=""
:将容器挂载到一个已存在的网桥上。指定为none
时则禁用容器的网络,与--bip
选项互斥;--bip=""
:让动态创建的docker0
网桥采用给定的 CIDR 地址; 与-b
选项互斥;--cgroup-parent=""
:指定 cgroup 的父组,默认 fs cgroup 驱动为/docker
,systemd cgroup 驱动为system.slice
;--cluster-store=""
:构成集群(如Swarm
)时,集群键值数据库服务地址;--cluster-advertise=""
:构成集群时,自身的被访问地址,可以为host:port
或interface:port
;--cluster-store-opt=""
:构成集群时,键值数据库的配置选项;--config-file="/etc/docker/daemon.json"
:daemon 配置文件路径;--containerd=""
:containerd 文件的路径;-D, --debug=true|false
:是否使用 Debug 模式。缺省为 false;--default-gateway=""
:容器的 IPv4 网关地址,必须在网桥的子网段内;--default-gateway-v6=""
:容器的 IPv6 网关地址;--default-ulimit=[]
:默认的 ulimit 值;--disable-legacy-registry=true|false
:是否允许访问旧版本的镜像仓库服务器;--dns=""
:指定容器使用的 DNS 服务器地址;--dns-opt=""
:DNS 选项;--dns-search=[]
:DNS 搜索域;--exec-opt=[]
:运行时的执行选项;--exec-root=""
:容器执行状态文件的根路径,默认为/var/run/docker
;--fixed-cidr=""
:限定分配 IPv4 地址范围;--fixed-cidr-v6=""
:限定分配 IPv6 地址范围;-G, --group=""
:分配给 unix 套接字的组,默认为docker
;-g, --graph=""
:Docker 运行时的根路径,默认为/var/lib/docker
;-H, --host=[]
:指定命令对应 Docker daemon 的监听接口,可以为 unix 套接字unix:///path/to/socket
,文件句柄fd://socketfd
或 tcp 套接字tcp://[host[:port]]
,默认为unix:///var/run/docker.sock
;--icc=true|false
:是否启用容器间以及跟 daemon 所在主机的通信。默认为 true。--insecure-registry=[]
:允许访问给定的非安全仓库服务;--ip=""
:绑定容器端口时候的默认 IP 地址。缺省为0.0.0.0
;--ip-forward=true|false
:是否检查启动在 Docker 主机上的启用 IP 转发服务,默认开启。注意关闭该选项将不对系统转发能力进行任何检查修改;--ip-masq=true|false
:是否进行地址伪装,用于容器访问外部网络,默认开启;--iptables=true|false
:是否允许 Docker 添加 iptables 规则。缺省为 true;--ipv6=true|false
:是否启用 IPv6 支持,默认关闭;-l, --log-level="debug|info|warn|error|fatal"
:指定日志输出级别;--label="[]"
:添加指定的键值对标注;--log-driver="json-file|syslog|journald|gelf|fluentd|awslogs|splunk|etwlogs|gcplogs|none"
:指定日志后端驱动,默认为json-file
;--log-opt=[]
:日志后端的选项;--mtu=VALUE
:指定容器网络的mtu
;-p=""
:指定 daemon 的 PID 文件路径。缺省为/var/run/docker.pid
;--raw-logs
:输出原始,未加色彩的日志信息;--registry-mirror=<scheme>://<host>
:指定docker pull
时使用的注册服务器镜像地址;-s, --storage-driver=""
:指定使用给定的存储后端;--selinux-enabled=true|false
:是否启用 SELinux 支持。缺省值为 false。SELinux 目前尚不支持 overlay 存储驱动;--storage-opt=[]
:驱动后端选项;--tls=true|false
:是否对 Docker daemon 启用 TLS 安全机制,默认为否;--tlscacert=/.docker/ca.pem
:TLS CA 签名的可信证书文件路径;--tlscert=/.docker/cert.pem
:TLS 可信证书文件路径;--tlscert=/.docker/key.pem
:TLS 密钥文件路径;--tlsverify=true|false
:启用 TLS 校验,默认为否;--userland-proxy=true|false
:是否使用用户态代理来实现容器间和出容器的回环通信,默认为 true;--userns-remap=default|uid:gid|user:group|user|uid
:指定容器的用户命名空间,默认是创建新的 UID 和 GID 映射到容器内进程。