Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从 Apache2.0 协议开源。
Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。
容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app),更重要的是容器性能开销极低。
Docker 允许开发人员使用您提供的应用程序或服务的本地容器在标准化环境中工作,从而简化了开发的生命周期。
容器非常适合持续集成和持续交付(CI / CD)工作流程,请考虑以下示例方案:
Docker 是基于容器的平台,允许高度可移植的工作负载。Docker 容器可以在开发人员的本机上,数据中心的物理或虚拟机上,云服务上或混合环境中运行。
Docker 的可移植性和轻量级的特性,还可以使您轻松地完成动态管理的工作负担,并根据业务需求指示,实时扩展或拆除应用程序和服务。
Docker 轻巧快速。它为基于虚拟机管理程序的虚拟机提供了可行、经济、高效的替代方案。Docker 非常适合于高密度环境以及中小型部署,你可以用更少的资源做更多的事情。
Docker 包括三个基本概念:
镜像(Image):Docker 镜像是用于创建 Docker 容器的模板。就相当于是一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。
容器(Container):容器是独立运行的一个或一组应用,是镜像运行时的实体。镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
仓库(Repository):仓库可看成一个代码控制中心,用来保存镜像。
Docker 使用客户端-服务器 (C/S) 架构模式,使用远程API来管理和创建Docker容器。
其中:
Docker 主机(Host): 一个物理或者虚拟的机器用于执行 Docker 守护进程和容器。
Docker Registry: Docker 仓库用来保存镜像,可以理解为代码控制中的代码仓库。官方Docker Hub(hub.docker.com) 提供了庞大的镜像集合供使用。
Docker Machine: Docker Machine是一个简化Docker安装的命令行工具,通过一个简单的命令行即可在相应的平台上安装Docker。
这里以在CentOS上安装docker为例做介绍。其他系统安装docker的方式也是类似的。
安装命令如下:
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
复制代码
sudo systemctl start docker
复制代码
删除安装包:
yum remove docker-ce
复制代码
删除镜像、容器、配置文件等内容:
rm -rf /var/lib/docker
复制代码
国内从 DockerHub 拉取镜像有时会遇到困难,此时可以配置镜像加速器。
Docker 官方和国内很多云服务商都提供了国内加速器服务,例如:
对于使用 systemd 的系统,请在 /etc/docker/daemon.json 中写入如下内容(如果文件不存在请新建该文件):
{"registry-mirrors":["https://reg-mirror.qiniu.com/"]}
复制代码
之后重新启动服务:
sudo systemctl daemon-reload
sudo systemctl restart docker
复制代码
在命令行执行 docker info,如果从结果中看到了如下内容,说明配置成功。
$ docker info
Registry Mirrors:
https://reg-mirror.qiniu.com
复制代码
如果我们本地没有镜像,比如ubuntu镜像,我们可以使用 docker pull 命令来载入 ubuntu 镜像:
docker pull ubuntu
复制代码
使用 ubuntu 镜像启动一个容器,并以命令行模式进入该容器:
docker run -it ubuntu /bin/bash
复制代码
参数说明:
此时我们已经进入ubuntu容器了,我们尝试在容器中运行命令ls查看当前目录下的文件列表
root@0123ce188bd8:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
复制代码
我们可以通过运行 exit 命令或者使用 CTRL+D 来退出容器。
在大部分的场景下,我们希望 docker 的服务是在后台运行的,我们可以过 -d 指定容器的运行模式。
runoob@runoob:~$ docker run -d ubuntu:15.10 /bin/sh -c "while true; do echo hello world; sleep 1; done"
2b1b7a428627c51ab8810d541d759f072b4fc75487eed05812646b8534a2fe63
复制代码
2b1b7a428627...这个长字符串叫做容器 ID,我们可以通过容器 ID 来查看对应的容器发生了什么。
首先,我们需要确认容器有在运行,可以通过 docker ps 来查看:
runoob@runoob:~$ docker ps
CONTAINER ID IMAGE COMMAND ...
5917eac21c36 ubuntu:15.10 "/bin/sh -c 'while t…" ...
复制代码
输出详情介绍:
CONTAINER ID: 容器 ID。
IMAGE: 使用的镜像。
COMMAND: 启动容器时运行的命令。
CREATED: 容器的创建时间。
STATUS: 容器状态。
状态有7种:
created(已创建)
restarting(重启中)
running 或 Up(运行中)
removing(迁移中)
paused(暂停)
exited(停止)
dead(死亡)
PORTS: 容器的端口信息和使用的连接类型(tcp\udp)。
NAMES: 容器名称。
在宿主主机内使用 docker logs 命令,查看容器内的标准输出:
runoob@runoob:~$ docker logs 2b1b7a428627
hello world
hello world
hello world
复制代码
我们使用 docker stop 命令来停止容器:
docker stop 2b1b7a428627
复制代码
docker restart 2b1b7a428627
复制代码
在使用 -d 参数时,容器启动后会进入后台。此时想要进入容器,可以通过以下指令进入:
docker attach
docker exec:推荐大家使用 docker exec 命令,因为此退出容器终端,不会导致容器的停止。
docker attach 1e560fca3906
复制代码
docker exec -it 243c32535da7 /bin/bash
复制代码
docker export 1e560fca3906 > ubuntu.tar
复制代码
可以使用 docker import 从容器快照文件中再导入为镜像,以下实例将快照文件 ubuntu.tar 导入到镜像 test/ubuntu:v1:
cat docker/ubuntu.tar | docker import - test/ubuntu:v1
复制代码
使用docker images查看镜像列表
docker images
复制代码
此外,也可以通过指定 URL 或者某个目录来导入,例如:
docker import http://example.com/exampleimage.tgz example/imagerepo
复制代码
docker rm 1e560fca3906
复制代码
删除容器时,容器必须是停止状态,否则会报错。
下面的命令可以清理掉所有处于终止状态的容器。
docker container prune
复制代码
接下来让我们尝试使用 docker 构建一个 web 应用程序。
我们使用现成的镜像
runoob@runoob:~# docker pull training/webapp # 载入镜像
runoob@runoob:~# docker run -d -P training/webapp python app.py
复制代码
参数说明:
-d:让容器在后台运行。
-P:将容器内部使用的网络端口随机映射到我们使用的主机上。
使用 docker ps 来查看我们正在运行的容器:
runoob@runoob:~# docker ps
CONTAINER ID IMAGE COMMAND ... PORTS
d3d5e39ed9d3 training/webapp "python app.py" ... 0.0.0.0:32769->5000/tcp
复制代码
这里多了端口信息。
Docker 开放了 5000 端口(默认 Python Flask 端口)映射到主机端口 32769 上。
这时我们就可以通过浏览器访问WEB应用了!
我们也可以通过 -p 参数来设置不一样的端口:
runoob@runoob:~$ docker run -d -p 5000:5000 training/webapp python app.py
复制代码
通过 docker ps 命令可以查看到容器的端口映射,docker 还提供了另一个快捷方式 docker port,使用 docker port 可以查看指定 (ID 或者名字)容器的某个确定端口映射到宿主机的端口号。
runoob@runoob:~$ docker port bf08b7f2cd89
5000/tcp -> 0.0.0.0:5000
复制代码
docker logs [ID或者名字] 可以查看容器内部的标准输出。
unoob@runoob:~$ docker logs -f bf08b7f2cd89
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
192.168.239.1 - - [09/May/2016 16:30:37] "GET / HTTP/1.1" 200 -
192.168.239.1 - - [09/May/2016 16:30:37] "GET /favicon.ico HTTP/1.1" 404 -
复制代码
-f: 让 docker logs 像使用 tail -f 一样来输出容器内部的标准输出。
我们还可以使用 docker top 来查看容器内部运行的进程
runoob@runoob:~$ docker top wizardly_chandrasekhar UID PID PPID ... TIME CMD root 23245 23228 ... 00:00:00 python app.py
使用 docker inspect 来查看 Docker 的底层信息。它会返回一个 JSON 文件记录着 Docker 容器的配置和状态信息。
runoob@runoob:~$ docker inspect wizardly_chandrasekhar
[
{
"Id": "bf08b7f2cd897b5964943134aa6d373e355c286db9b9885b1f60b6e8f82b2b85",
"Created": "2018-09-17T01:41:26.174228707Z",
"Path": "python",
"Args": [
"app.py"
],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 23245,
"ExitCode": 0,
"Error": "",
"StartedAt": "2018-09-17T01:41:26.494185806Z",
"FinishedAt": "0001-01-01T00:00:00Z"
},
......
复制代码
当运行容器时,使用的镜像如果在本地中不存在,docker 就会自动从 docker 镜像仓库中下载,默认是从 Docker Hub 公共镜像源下载。
我们可以使用 docker images 来列出本地主机上的镜像。
runoob@runoob:~$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu 14.04 90d5884b1ee0 5 days ago 188 MB
php 5.6 f40e9e0f10c8 9 days ago 444.8 MB
复制代码
各个选项说明:
REPOSITORY:表示镜像的仓库源
TAG:镜像的标签
IMAGE ID:镜像ID
CREATED:镜像创建时间
SIZE:镜像大小
同一仓库源可以有多个 TAG,代表这个仓库源的不同个版本,如 ubuntu 仓库源里,有 15.10、14.04 等多个不同的版本,我们使用 REPOSITORY:TAG 来定义不同的镜像。如果你不指定一个镜像的版本标签,例如你只使用 ubuntu,docker 将默认使用 ubuntu:latest 镜像。
当我们在本地主机上使用一个不存在的镜像时 Docker 就会自动下载这个镜像。如果我们想预先下载这个镜像,我们可以使用 docker pull 命令来下载它。
Crunoob@runoob:~$ docker pull ubuntu:13.10
复制代码
我们可以从 Docker Hub 网站来搜索镜像,Docker Hub 网址为: hub.docker.com/
我们也可以使用 docker search 命令来搜索镜像。
镜像删除使用 docker rmi 命令,比如我们删除 hello-world 镜像:
$ docker rmi hello-world
复制代码
当我们从 docker 镜像仓库中下载的镜像不能满足我们的需求时,我们可以通过以下两种方式对镜像进行更改。
1、从已经创建的容器中更新镜像,并且提交这个镜像 2、使用 Dockerfile 指令来创建一个新的镜像
更新镜像之前,我们需要使用镜像来创建一个容器。
runoob@runoob:~$ docker run -t -i ubuntu:15.10 /bin/bash
root@e218edb10161:/#
复制代码
在运行的容器内使用 apt-get update 命令进行更新。
在完成操作之后,输入 exit 命令来退出这个容器。
此时 ID 为 e218edb10161 的容器,是按我们的需求更改的容器。我们可以通过命令 docker commit 来提交容器副本。
runoob@runoob:~$ docker commit -m="has update" -a="runoob" e218edb10161 runoob/ubuntu:v2
sha256:70bf1840fd7c0d2d8ef0a42a817eb29f854c1af8f7c59fc03ac7bdee9545aff8
复制代码
各个参数说明:
-m: 提交的描述信息
-a: 指定镜像作者
e218edb10161:容器 ID
runoob/ubuntu:v2: 指定要创建的目标镜像名
我们使用命令 docker build , 从零开始来创建一个新的镜像。为此,我们需要创建一个 Dockerfile 文件,其中包含一组指令来告诉 Docker 如何构建我们的镜像。这个我们后面会介绍。
我们可以使用 docker tag 命令,为镜像添加一个新的标签。
runoob@runoob:~$ docker tag 860c279d2fec runoob/centos:dev
复制代码
docker tag 镜像ID,这里是 860c279d2fec,用户名称、镜像源名(repository name)和新的标签名(tag)。
容器中可以运行一些网络应用,要让外部也可以访问这些应用,可以通过 -P 或 -p 参数来指定端口映射。
两种方式的区别是:
docker run -d -p 5000:5000 training/webapp python app.py
复制代码
另外,我们可以指定容器绑定的网络地址,比如绑定 127.0.0.1。
runoob@runoob:~$ docker run -d -p 127.0.0.1:5001:5000 training/webapp python app.py
95c6ceef88ca3e71eaf303c2833fd6701d8d1b2572b5613b5a932dfdfe8a857c
runoob@runoob:~$ docker ps
CONTAINER ID IMAGE COMMAND ... PORTS NAMES
95c6ceef88ca training/webapp "python app.py" ... 5000/tcp, 127.0.0.1:5001->5000/tcp adoring_stonebraker
33e4523d30aa training/webapp "python app.py" ... 0.0.0.0:5000->5000/tcp berserk_bartik
fce072cc88ce training/webapp "python app.py" ... 0.0.0.0:32768->5000/tcp grave_hopper
复制代码
如果要绑定 UDP 端口,可以在端口后面加上 /udp。
docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py
复制代码
端口映射并不是唯一把 docker 连接到另一个容器的方法。
docker 有一个连接系统允许将多个容器连接在一起,共享连接信息。
docker 连接会创建一个父子关系,其中父容器可以看到子容器的信息。
先创建一个新的 Docker 网络。
docker network create -d bridge test-net
复制代码
参数说明:
-d:参数指定 Docker 网络类型,有 bridge、overlay。
其中 overlay 网络类型用于 Swarm mode,在本小节中你可以忽略它。
运行一个容器并连接到新建的 test-net 网络:
$ docker run -itd --name test1 --network test-net ubuntu /bin/bash
复制代码
打开新的终端,再运行一个容器并加入到 test-net 网络:
$ docker run -itd --name test2 --network test-net ubuntu /bin/bash
复制代码
下面通过 ping 来证明 test1 容器和 test2 容器建立了互联关系。
如果 test1、test2 容器内中无 ping 命令,则在容器内执行以下命令安装 ping(即学即用:可以在一个容器里安装好,提交容器到镜像,在以新的镜像重新运行以上俩个容器)。
apt-get update
apt install iputils-ping
复制代码
然后在 test1 容器输入以下命令:
docker exec -it test1 /bin/bash
ping test2
复制代码
可以测试到test1 容器和 test2 容器建立了互联关系。
如果你有多个容器之间需要互相连接,推荐使用 Docker Compose,后面会介绍。
我们可以在宿主机的 /etc/docker/daemon.json 文件中增加以下内容来设置全部容器的 DNS:
{
"dns" : [
"114.114.114.114",
"8.8.8.8"
]
}
复制代码
设置后,启动容器的 DNS 会自动配置为 114.114.114.114 和 8.8.8.8。
配置完,需要重启 docker 才能生效。
查看容器的 DNS 是否生效可以使用以下命令,它会输出容器的 DNS 信息:
$ docker run -it --rm ubuntu cat etc/resolv.conf
复制代码
如果只想在指定的容器设置 DNS,则可以使用以下命令:
$ docker run -it --rm -h host_ubuntu --dns=114.114.114.114 --dns-search=test.com ubuntu
复制代码
仓库(Repository)是集中存放镜像的地方。以下介绍一下 Docker Hub。当然不止 docker hub,只是远程的服务商不一样,操作都是一样的。
目前 Docker 官方维护了一个公共仓库 Docker Hub。
大部分需求都可以通过在 Docker Hub 中直接下载镜像来实现。
在 hub.docker.com 免费注册一个 Docker 账号。
登录需要输入用户名和密码,登录成功后,我们就可以从 docker hub 上拉取自己账号下的全部镜像。
退出 docker hub 可以使用以下命令:
docker logout
复制代码
你可以通过 docker search 命令来查找官方仓库中的镜像,并利用 docker pull 命令来将它下载到本地。
用户登录后,可以通过 docker push 命令将自己的镜像推送到 Docker Hub。
以下命令中的 username 请替换为你的 Docker 账号用户名。
$ docker tag ubuntu:18.04 username/ubuntu:18.04
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED ...
ubuntu 18.04 275d79972a86 6 days ago ...
username/ubuntu 18.04 275d79972a86 6 days ago ...
$ docker push username/ubuntu:18.04
$ docker search username/ubuntu
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
username/ubuntu
复制代码
Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。
下面以定制一个 nginx 镜像(构建好的镜像内会有一个 /usr/share/nginx/html/index.html 文件)为例讲解使用 Dockerfile 定制镜像。
在一个空目录下,新建一个名为 Dockerfile 文件,并在文件内添加以下内容:
FROM nginx
RUN echo '这是一个本地构建的nginx镜像' > /usr/share/nginx/html/index.html
复制代码
FROM:定制的镜像都是基于 FROM 的镜像,这里的 nginx 就是定制需要的基础镜像。后续的操作都是基于 nginx。
RUN:用于执行后面跟着的命令行命令。有以下俩种格式:
shell 格式:
RUN <命令行命令>
# <命令行命令> 等同于,在终端操作的 shell 命令。
复制代码
exec 格式:
RUN ["可执行文件", "参数1", "参数2"]
# 例如:
# RUN ["./test.php", "dev", "offline"] 等价于 RUN ./test.php dev offline
复制代码
注意:Dockerfile 的指令每执行一次都会在 docker 上新建一层。所以过多无意义的层,会造成镜像膨胀过大。例如:
FROM centos
RUN yum install wget
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"
RUN tar -xvf redis.tar.gz
以上执行会创建 3 层镜像。可简化为以下格式:
FROM centos
RUN yum install wget \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
&& tar -xvf redis.tar.gz
复制代码
通过目录下的 Dockerfile 构建一个 nginx:v3(镜像名称:镜像标签)。
$ docker build -t nginx:v3 .
复制代码
指令最后一个 . 是上下文路径,那么什么是上下文路径呢?
上下文路径,是指 docker 在构建镜像,有时候想要使用到本机的文件(比如复制),docker build 命令得知这个路径后,会将路径下的所有内容打包。
解析:由于 docker 的运行模式是 C/S。我们本机是 C,docker 引擎是 S。实际的构建过程是在 docker 引擎下完成的,所以这个时候无法用到我们本机的文件。这就需要把我们本机的指定目录下的文件一起打包提供给 docker 引擎使用。
注意:上下文路径下不要放无用的文件,因为会一起打包发送给 docker 引擎。
复制指令,从上下文目录中复制文件或者目录到容器里指定路径。
格式:
COPY [--chown=:] <源路径1>... <目标路径>
COPY [--chown=:] ["<源路径1>",... "<目标路径>"]
复制代码
例如:
COPY hom* /mydir/
COPY hom?.txt /mydir/
复制代码
ADD 指令和 COPY 的使用格式一致(同样需求下,官方推荐使用 COPY)。功能也类似,不同之处如下:
ADD 在执行 <源文件> 为 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,会自动复制并解压到 <目标路径>。
类似于 RUN 指令,用于运行程序,但二者运行的时间点不同:
CMD 在docker run 时运行。 RUN 是在 docker build。
作用:为启动的容器指定默认要运行的程序,程序运行结束,容器也就结束。CMD 指令指定的程序可被 docker run 命令行参数中指定要运行的程序所覆盖。
注意:如果 Dockerfile 中如果存在多个 CMD 指令,仅最后一个生效。
格式:
CMD
CMD ["<可执行文件或命令>","","",...]
CMD ["","",...] # 该写法是为 ENTRYPOINT 指令指定的程序提供默认参数
复制代码
推荐使用第二种格式,执行过程比较明确。第一种格式实际上在运行的过程中也会自动转换成第二种格式运行,并且默认可执行文件是 sh。
类似于 CMD 指令,但其不会被 docker run 的命令行参数指定的指令所覆盖,而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序。
但是, 如果运行 docker run 时使用了 --entrypoint 选项,将覆盖 CMD 指令指定的程序。
优点:在执行 docker run 的时候可以指定 ENTRYPOINT 运行所需的参数。
注意:如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效。
格式:
ENTRYPOINT ["","","",...]
复制代码
可以搭配 CMD 命令使用:一般是变参才会使用 CMD ,这里的 CMD 等于是在给 ENTRYPOINT 传参,以下示例会提到。
示例:
假设已通过 Dockerfile 构建了 nginx:test 镜像:
FROM nginx
ENTRYPOINT ["nginx", "-c"] # 定参
CMD ["/etc/nginx/nginx.conf"] # 变参
复制代码
1、不传参运行
$ docker run nginx:test
复制代码
容器内会默认运行以下命令,启动主进程。
nginx -c /etc/nginx/nginx.conf
复制代码
2、传参运行
$ docker run nginx:test -c /etc/nginx/new.conf
复制代码
容器内会默认运行以下命令,启动主进程(/etc/nginx/new.conf:假设容器内已有此文件)
nginx -c /etc/nginx/new.conf
复制代码
设置环境变量,定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。
格式:
ENV
ENV = =...
复制代码
以下示例设置 NODE_VERSION = 7.2.0 , 在后续的指令中可以通过 $NODE_VERSION 引用:
ENV NODE_VERSION 7.2.0
复制代码
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
&& curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc"
复制代码
构建参数,与 ENV 作用一至。不过作用域不一样。ARG 设置的环境变量仅对 Dockerfile 内有效,也就是说只有 docker build 的过程中有效,构建好的镜像内不存在此环境变量。
构建命令 docker build 中可以用 --build-arg <参数名>=<值> 来覆盖。
格式:
ARG <参数名>[=<默认值>]
复制代码
定义匿名数据卷。在启动容器时忘记挂载数据卷,会自动挂载到匿名卷。
作用:
避免重要的数据,因容器重启而丢失,这是非常致命的。 避免容器不断变大。
格式:
VOLUME ["<路径1>", "<路径2>"...]
VOLUME <路径>
复制代码
在启动容器 docker run 的时候,我们可以通过 -v 参数修改挂载点。
仅仅只是声明端口。
作用:
帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射。 在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。
格式:
EXPOSE <端口1> [<端口2>...]
复制代码
指定工作目录。用 WORKDIR 指定的工作目录,会在构建镜像的每一层中都存在。(WORKDIR 指定的工作目录,必须是提前创建好的)。
docker build 构建镜像过程中的,每一个 RUN 命令都是新建的一层。只有通过 WORKDIR 创建的目录才会一直存在。
格式:
WORKDIR <工作目录路径>
复制代码
用于指定执行后续命令的用户和用户组,这边只是切换后续命令执行的用户(用户和用户组必须提前已经存在)。
格式:
USER <用户名>[:<用户组>]
复制代码
用于指定某个程序或者指令来监控 docker 容器服务的运行状态。
格式:
HEALTHCHECK [选项] CMD <命令>:设置检查容器健康状况的命令
HEALTHCHECK NONE:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
HEALTHCHECK [选项] CMD <命令> : 这边 CMD 后面跟随的命令使用,可以参考 CMD 的用法。
复制代码
用于延迟构建命令的执行。简单的说,就是 Dockerfile 里用 ONBUILD 指定的命令,在本次构建镜像的过程中不会执行(假设镜像为 test-build)。当有新的 Dockerfile 使用了之前构建的镜像 FROM test-build ,这是执行新镜像的 Dockerfile 构建时候,会执行 test-build 的 Dockerfile 里的 ONBUILD 指定的命令。
格式:
ONBUILD <其它指令>
复制代码
Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose,您可以使用 YML 文件来配置应用程序需要的所有服务。然后,使用一个命令,就可以从 YML 文件配置中创建并启动所有服务。
Compose 使用的三个步骤:
使用 Dockerfile 定义应用程序的环境。
使用 docker-compose.yml 定义构成应用程序的服务,这样它们可以在隔离环境中一起运行。
最后,执行 docker-compose up 命令来启动并运行整个应用程序。
Linux 上运行以下命令以下载 Docker Compose 的某个稳定版本:
$ sudo curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
复制代码
将可执行权限应用于二进制文件:
$ sudo chmod +x /usr/local/bin/docker-compose
复制代码
创建软链:
$ sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
复制代码
测试是否安装成功:
$ docker-compose --version
cker-compose version 1.24.1, build 4667896b
复制代码
创建一个测试目录composetest,在测试目录中创建一个名为 app.py 的文件,并复制粘贴以下内容:
import time
import redis
from flask import Flask
app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)
def get_hit_count():
retries = 5
while True:
try:
return cache.incr('hits')
except redis.exceptions.ConnectionError as exc:
if retries == 0:
raise exc
retries -= 1
time.sleep(0.5)
@app.route('/')
def hello():
count = get_hit_count()
return 'Hello World! I have been seen {} times.\n'.format(count)
复制代码
在此示例中,redis 是应用程序网络上的 redis 容器的主机名,该主机使用的端口为 6379。
在 composetest 目录中创建另一个名为 requirements.txt 的文件,内容如下:
flask
redis
复制代码
在 composetest 目录中,创建一个名为的文件 Dockerfile,内容如下:
FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP app.py
ENV FLASK_RUN_HOST 0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY . .
CMD ["flask", "run"]
复制代码
Dockerfile 内容解释:
在测试目录中创建一个名为 docker-compose.yml 的文件,然后粘贴以下内容:
docker-compose.yml 配置文件
# yaml 配置
version: '3'
services:
web:
build: .
ports:
- "5000:5000"
redis:
image: "redis:alpine"
复制代码
该 Compose 文件定义了两个服务:web 和 redis。
web:该 web 服务使用从 Dockerfile 当前目录中构建的镜像。然后,它将容器和主机绑定到暴露的端口 5000。此示例服务使用 Flask Web 服务器的默认端口 5000 。
redis:该 redis 服务使用 Docker Hub 的公共 Redis 映像。
在测试目录中,执行以下命令来启动应用程序:
docker-compose up
复制代码
如果你想在后台执行该服务可以加上 -d 参数:
docker-compose up -d
复制代码
之前整理过一篇虚拟机的建站教程:阿里云服务器建站指南,包含了node.js、mysql、redis、nginx的部署,搭建起了一个基础的后端应用。
但是这种部署方式迁移机器每次都需要执行很多操作,费时费力,所以决定现学现用,用docker来替代它。
我的是阿里云机器,系统是CentOS。
所以执行以下命令安装docker
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
复制代码
安装后验证一下
[root@iZ8vb55rs42xic2s53uc3yZ nodejs]# docker -v
Docker version 20.10.5, build 55c4c88
复制代码
然后启动docker
systemctl start docker
复制代码
部署一个node.js应用需要node.js环境、安装依赖包、执行启动命令等等。
这些docker都支持,我们直接来看最终编写完成的Dockerfile。
Dockerfile是用来构建镜像的文本文件,包含了构建镜像所需的指令。
# 它是基于node:12.22.1-alpine3.10基础镜像的,这些镜像可以在[官网](https://hub.docker.com/_/node) 找,你可以选择你想要的node.js版本。
FROM node:12.22.1-alpine3.10
# ADD这行命令是将工程下所有文件都加到镜像中,因为镜像的构建是B/S架构,它无法直接获取工程里的文件
ADD . /nodejs
# WORKDIR这行命令是设置工作目录,这里类似于cd到工程的根目录
WORKDIR /nodejs
# RUN可以执行一些命令,这里安装了工程的依赖包。
RUN npm --registry=https://registry.npm.taobao.org \
--cache=$HOME/.npm/.cache/cnpm \
--disturl=https://npm.taobao.org/dist \
--userconfig=$HOME/.cnpmrc install
# EXPOSE定义了应用的端口
EXPOSE 3000
# CMD为程序启动命令,这里使用了pm2为应用提供进程守护
CMD ./node_modules/.bin/pm2 start pm2.json --no-daemon --env production
复制代码
有了Dockerfile,我们直接执行以下命令就可以构建镜像了
docker build -t nodejs .
复制代码
nodejs是镜像名,你也可以修改成其它名字。
运行后的结果为
...
Successfully built 621c07eeba87
Successfully tagged nodejs:latest
复制代码
说明镜像已经构建成功了。
执行以下命令启动容器
docker run --name nodejs -it -p 3000:3000 nodejs
复制代码
第1个nodejs是容器名,第2是镜像名,这行命令的含义是使用nodejs镜像(就是前面构建的镜像)运行一个容器,端口为3000。
这样node.js服务就启动了,你通过ip+端口号(3000)就可以访问应用了。
部署mysql也是类似的。
mysql有现成的镜像,所以不需要通过Dockerfile构建
执行命令拉取镜像
docker pull mysql/mysql-server:5.7
复制代码
执行命令
docker run --name mysql -d -e MYSQL_ROOT_PASSWORD=password -p 3306:3306 mysql/mysql-server:5.7
复制代码
这样mysql就运行起来了。
MySQL默认只能使用本地IP(127.0.0.1)访问,不能从外部网络访问。所以需要设置一下,运行node.js的容器才能访问运行MySQL服务的容器。
首先进入MySQL容器
docker exec -it mysql bash
复制代码
进入Mysql服务
mysql -uroot -ppassword
复制代码
password就是之前运行容器时设置的密码
最后添加外部访问权限
-- 切换数据库
USE mysql;
-- 给root账户开放所有ip访问权限
GRANT ALL PRIVILEGES ON *.* TO "root"@"%" IDENTIFIED BY "password";
-- 更新权限设置
FLUSH PRIVILEGES;
复制代码
这样,你的node.js服务就可以通过用户名、密码连接访问mysql数据库了。
前面我们其实运行了两个服务(node.js和mysql),那么有没有什么方式可以一键启动所有服务呢?答案是Docker Compose,它使用 YML 文件来配置应用程序需要的所有服务,并且可以一键启动它们。
直接来看最终的docker-compose.yml
# Docker Compose版本
version: "3"
services:
nodejs:
# 使用前面介绍的Dockerfile来构建镜像
build:
context: .
dockerfile: Dockerfile
# 镜像名
image: nodejs
# 容器名
container_name: nodejs
restart: unless-stopped
ports:
- "3000:3000"
# 依赖mysql,也就是说需要先启动mysql服务
depends_on:
- "mysql"
networks:
- app-network
mysql:
network_mode: "host"
environment:
MYSQL_ROOT_PASSWORD: "password"
image: "docker.io/mysql:5.7"
container_name: mysql
restart: always
volumes:
- "./mysql/conf/my.cnf:/etc/my.cnf"
- "./mysql/init:/docker-entrypoint-initdb.d/"
# 解决了/var/log/mysql没有权限的问题
entrypoint: bash -c "chown -R mysql:mysql /var/log/mysql && exec /entrypoint.sh mysqld"
ports:
- "3306:3306"
networks:
app-network:
driver: bridge
复制代码
可以看到它有两个services,分别是nodejs和mysql。
其中有两行还需要解释一下
- "./mysql/conf/my.cnf:/etc/my.cnf"
- "./mysql/init:/docker-entrypoint-initdb.d/"
复制代码
它可以让你自定义mysql的my.cnf以及执行一些初始化的sql(或脚本)
在工程中的目录结构如下,与命令中目录结构是关联的。
mysql
- conf
- my.cnf
- init
- init.sql
复制代码
我们看一下init.sql的内容
USE mysql;
GRANT ALL PRIVILEGES ON *.* TO "root"@"%" IDENTIFIED BY "password";
FLUSH PRIVILEGES;
复制代码
这样,之前添加从外部访问数据库权限的命令就可以自动执行了。
介绍到现在,我们此时的目录结构已经是这样的了,文件里的内容前面都介绍了
your-nodejs-project
- mysql
- conf
- my.cnf
- init
- init.sql
- docker-compose.yml
- Dockerfile
复制代码
最后我们只要在工程的根目录执行以下命令,就可以一键部署node.js+mysql服务了。
docker-compose up -d
复制代码
如果没有镜像(执行docker-compose命令前我把之前构建好的node.js镜像删除了),它会构建(node.js)或拉取(mysql)镜像
Building nodejs
Step 1/6 : FROM node:12.22.1-alpine3.10
...
Successfully built 90227eea977f
Successfully tagged nodejs:latest
Creating mysql ... done
Creating nodejs ... done
复制代码
可以看到两个服务都创建完成了。
并且都成功运行了。
[root@iZ8vb55rs42xic2s53uc3yZ blog-server]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f9dbd102678f nodejs "docker-entrypoint.s…" About an hour ago Up About an hour 0.0.0.0:3000->3000/tcp nodejs
f91b47be9d02 mysql:5.7 "bash -c 'chown -R m…" About an hour ago Up About an hour mysql
复制代码
如果再执行一遍同样的docker-compose up -d命令,因为是完全相同的,且已经有镜像了,所以会很快地返回结果:
[root@iZ8vb55rs42xic2s53uc3yZ blog-server]# docker-compose up -d
mysql is up-to-date
nodejs is up-to-date
复制代码
至此,我们成功地使用docker部署了node.js+mysql应用,以后在其它虚拟机上迁移我们的服务就方便多了,因为它可以实现一键部署!
同样的nginx、redis这些都有相应的成熟的镜像,部署的操作大同小异,这里就不再做介绍了。
作者:番茄子
链接:https://juejin.cn/post/6954604160327090212
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。