Dockerfile
Dockerfile 是一个文本文件,其内包含了一条条的 指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
Dockerfile 指令
From 指定基础镜像
所谓定制镜像,是以一个镜像为基础,在其上进行定制修改。就像我们之前运行了一个 nginx
镜像的容器,再进行修改一样,所以基础镜像是必须指定的。而 FROM
就是指定 基础镜像,因此一个 Dockerfile
中 FROM
是必备的指令,并且必须是第一条指令。
除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch
。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。
FROM scratch
...
如果你以 scratch
为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。不以任何系统为基础,直接将可执行文件复制进镜像的做法并不罕见,对于 Linux 下静态编译的程序来说,并不需要有操作系统提供运行时支持,所需的一切库都已经在可执行文件里了,因此直接 FROM scratch
会让镜像体积更加小巧。使用 Go 语言 开发的应用很多会使用这种方式来制作镜像,这也是为什么有人认为 Go 是特别适合容器微服务架构的语言的原因之一。
note: 镜像相关的内容可以查看这篇文章
https://www.ryanzoe.top/docker/docker-%e9%95%9c%e5%83%8f/
MAINTAINER 指定维护者信息
格式为 MAINTAINER <name>,指定维护者信息,注意:MAINTAINER 指令已经被弃用,建议使用 LABEL 指令
LABEL 为镜像添加标签
格式为 LABEL <key>=<value> <key>=<value> <key>=<value> …
可以给镜像添加多个 LABEL,需要注意的是,每条 LABEL 指令都会生成一个新的层。所以最好是把添加的多个 LABEL 合并为一条命令:
LABEL multi.label1="value1" \
multi.label2="value2" \
other="value3"
可以通过以下命令查看镜像信息,包括 LABEL
$ docker inspect image_name:tag
WORKDIR 指定工作目录
格式为 WORKDIR <工作目录路径>
如果 WORKDIR 使用的是相对路径,那么所切换的路径与之前的 WORKDIR 有关,例如
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
RUN pwd 的工作目录为 /a/b/c
RUN 执行命令
RUN
指令是用来执行命令行命令的,由于命令行的强大能力,RUN
指令在定制镜像时是最常用的指令之一
其格式有两种:
- shell 格式:
RUN <命令>
,就像直接在命令行中输入的命令一样 - exec 格式:
RUN ["可执行文件", "参数1", "参数2"]
,这更像是函数调用中的格式
COPY 复制文件
格式为 COPY [–chown=<user>:<group>] <源路径>… <目标路径>
COPY
指令将从构建上下文目录中 源路径
的文件/目录复制到新的一层的镜像内的 目标路径
位置,目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录,使用 COPY
指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等,如果源路径为文件夹,复制的时候不是直接复制该文件夹,而是将文件夹中的内容复制到目标路径
ENTRYPOINT 入口点
可以指定容器运行时执行的内容,例如执行一个 shell 脚本,如下所示
COPY Dockerstart /start
RUN chmod +x /start
ENTRYPOINT ["/start"]
VOLUME 定义匿名卷
格式为:VOLUME [“<路径1>”, “<路径2>”…]
为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile
中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据
VOLUME /data
这里的 /data
目录就会在容器运行时自动挂载为匿名卷,任何向 /data
中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行容器时可以覆盖这个挂载设置。比如:
$ docker run -d -v mydata:/data xxxx
在这行命令中,就使用了 mydata
这个命名卷挂载到了 /data
这个位置,替代了 Dockerfile
中定义的匿名卷的挂载配置
EXPOSE 暴露端口
格式为 EXPOSE <端口1> [<端口2>...]
EXPOSE
指令是声明容器运行时提供服务的端口,这只是一个声明,在容器运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P
时,会自动随机映射 EXPOSE
的端口。
构建镜像
创建名称为 Dockerfile 的文件,内容如下
FROM nginx
LABEL maintainer="ryan" email="zhengjianhong95@gmial.com"
WORKDIR /data/www
COPY nginx.conf /etc/nginx/nginx.conf
RUN echo "<?php phpinfo();?>" > index.php
EXPOSE 80 443
在 Dockerfile 的同级目录下放一个 nginx.conf,配置文件内容如下
server {
listen 80;
server_name 127.0.0.1 localhost;
access_log /home/log/access.log;
error_log /home/log/error.log;
root /home/www/;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass unix:/var/run/php-fpm/www.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_connect_timeout 3000;
fastcgi_send_timeout 3000;
fastcgi_read_timeout 3000;
}
if (-f $request_filename/index.html){
rewrite (.*) $1/index.html break;
}
if (-f $request_filename/index.php){
rewrite (.*) $1/index.php;
}
if (!-f $request_filename){
rewrite (.*) /index.php;
}
}
在 Dockerfile
文件所在目录执行:
$ docker build -t nginx:v3 .
可以使用以下命令查看 docker build 的详细介绍
$ docker build --help
Docker 还支持直接用 Git repo、tar 压缩包、标准输入种读取 Dockerfile 进行构建。
构建镜像上下文 Context
可以看到我们之前构建镜像 docker build 的最后都有一个 “.”, “.” 表示当前目录,指定上下文路径,当构建的时候,会讲上下文路径下的所有内容打包,然后上传到 Docker 引擎,Docker 引擎会根据这个上下文包,获取到镜像所需的文件,例如使用 COPY 指令复制文件到镜像里面的时候,就用到了上下文路径。
COPY ./package.json /app/
以上命令并不是要复制执行 docker build
命令所在的目录下的 package.json
,也不是复制 Dockerfile
所在目录下的 package.json
,而是复制 上下文(context) 目录下的 package.json
,因此 COPY ../package.json 是无法正常执行成功的,因为这些路径已经超出了上下文的范围,Docker 引擎无法获取到这些位置的文件。同时要注意上下文路径不要太大(按需即可),例如直接将整个 “/” 根目录直接作为上下文路径,会导致整个构建过程极其缓慢,因为在讲整个服务器的内容发送到 Docker 引擎。
Docker Compose
基本介绍
Compose
项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排,其代码目前在 https://github.com/docker/compose 上开源
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
文件中定义
使用实例
下面用 Python
建立一个能够记录页面访问次数的 web 网站
新建文件夹,在该目录中编写 app.py
文件
from flask import Flask
from redis import Redis
app = Flask(__name__)
redis = Redis(host='redis', port=6379)
@app.route('/')
def hello():
count = redis.incr('hits')
return 'Hello World! 该页面已被访问 {} 次。\n'.format(count)
if __name__ == "__main__":
app.run(host="0.0.0.0", debug=True)
编写 Dockerfile
文件,内容为
FROM python:3.6-alpine
ADD . /code
WORKDIR /code
RUN pip install redis flask
CMD ["python", "app.py"]
创建文件 docker-compose.yml,内容如下
version: '3'
services:
web:
build: .
ports:
- "5000:5000"
redis:
image: "redis:alpine"
运行 compose 项目
$ docker-compose up
此时访问本地 5000
端口,链接为 localhost:5000,每次刷新页面,计数就会加 1