Dockerfile

一、结构:

  1. 基础镜像信息
  2. 维护者信息
  3. 镜像操作指令
  4. 容器启动时执行指令

FROM

指明构建的新镜像是来自于哪个基础镜像,例如:

FROM centos:6

MAINTAINER

指明镜像维护着及其联系方式(一般是邮箱地址),例如:

MAINTAINER Edison Zhou <xxx@xxx.com>

不过,MAINTAINER并不推荐使用,更推荐使用LABEL来指定镜像作者,例如:

LABEL maintainer="xxxxx.cn"

RUN

构建镜像时运行的Shell命令,例如:

RUN ["yum", "install", "httpd"]
RUN yum install httpd

CMD

启动容器时执行的Shell命令,例如:

CMD ["-C", "/start.sh"] 
CMD ["/usr/sbin/sshd", "-D"] 
CMD /usr/sbin/sshd -D

EXPOSE

声明容器运行的服务端口,例如:

EXPOSE 80 443

ENV

设置环境内环境变量,例如:

ENV MYSQL_ROOT_PASSWORD 123456
ENV JAVA_HOME /usr/local/jdk1.8.0_45

ADD

拷贝文件或目录到镜像中,例如:

ADD <src>...<dest>
ADD html.tar.gz /var/www/html
ADD https://xxx.com/html.tar.gz /var/www/html

***PS:***如果是URL或压缩包,会自动下载或自动解压


COPY

拷贝文件或目录到镜像中,用法同ADD,只是不支持自动下载和解压,例如:

COPY ./start.sh /start.sh

ENTRYPOINT

启动容器时执行的Shell命令,同CMD类似,只是由ENTRYPOINT启动的程序不会被docker run命令行指定的参数所覆盖,而且,这些命令行参数会被当作参数传递给ENTRYPOINT指定指定的程序,例如:

ENTRYPOINT ["/bin/bash", "-C", "/start.sh"]
ENTRYPOINT /bin/bash -C '/start.sh'

***PS:***Dockerfile文件中也可以存在多个ENTRYPOINT指令,但仅有最后一个会生效。


VOLUME

指定容器挂载点到宿主机自动生成的目录或其他容器,例如:

VOLUME ["/var/lib/mysql"]

***PS:***一般不会在Dockerfile中用到,更常见的还是在docker run的时候指定-v数据卷。


USER

为RUN、CMD和ENTRYPOINT执行Shell命令指定运行用户,例如:

USER <user>[:<usergroup>]
USER <UID>[:<UID>]
USER edisonzhou

WORKDIR

为RUN、CMD、ENTRYPOINT以及COPY和AND设置工作目录,例如:

WORKDIR /data

HEALTHCHECK

告诉Docker如何测试容器以检查它是否仍在工作,即健康检查,例如:

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

其中,一些选项的说明:

  • –interval=DURATION (default: 30s):每隔多长时间探测一次,默认30秒
  • – timeout= DURATION (default: 30s):服务响应超时时长,默认30秒
  • –start-period= DURATION (default: 0s):服务启动多久后开始探测,默认0秒
  • –retries=N (default: 3):认为检测失败几次为宕机,默认3次

一些返回值的说明:

  • 0:容器成功是健康的,随时可以使用
  • 1:不健康的容器无法正常工作
  • 2:保留不使用此退出代码

ARG

在构建镜像时,指定一些参数,例如:

FROM centos:6
ARG user # ARG user=root
USER $user

这时,在docker build时可以带上自定义参数user了,如下所示:

docker build --build-arg user=xxxxx Dockerfile .

二、注意事项:

构建镜像最具挑战性的一点是使镜像体积尽可能的小


使用 .dockerignore 排除构建无关文件

.dockerignore` 语法与 `.gitignore` 语法一致。使用它排除构建无关的文件及目录,如 `node_modules

使用多阶段构建

对于多阶段构建,可以在 Dockerfile 中使用多个 FROM 语句。每个 FROM 指令都可以使用不同的基镜像,并且它们都开始了构建的新阶段,示例如下:

FROM golang:1.7.3
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=0 /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]

在这种模式下,第二个FROM会以alpine:latest镜像为基础,开始一个新的构建,其中**–from=0**意为将前一阶段的构建工件复制到此新阶段。Go SDK 和任何中间工件都会被留下,不会保存在最终的镜像中


为构建阶段命名

FROM 指令从 0 开始,也可以通过添加一个 AS <NAME>FROM 指令来命名阶段,示例如下:

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"]

在特定的构建阶段停止

在构建映像时,不必构建包括每个阶段的整个 Dockerfile。你可以指定目标构建阶段。以下命令假设你正在使用之前的 Dockerfile,但是在名为 builder 的阶段停止:

$ docker build --target builder -t alexellis2/href-counter:latest .

这可能非常强有力的几个场景是:

  • 调试一个特定的构建阶段
  • 使用一个启用了所有调试符号或工具的 调试(debug) 阶段和一个精益的 生产(production) 阶段
  • 使用一个测试(testing)阶段,在这个阶段你的应用会被测试数据填充,但是在构建产品时,使用一个使用真实数据的不同阶段。

使用外部镜像作为“阶段”

当使用多阶段构建时,您不受限于从 Dockerfile 中先前创建的阶段进行复制。您可以使用 COPY --from 指令从单独的镜像中进行复制,可以使用本地镜像名称、本地或 Docker 注册表上可用的标签或标签 ID。Docker 客户端会在必要时拉取镜像并从中复制工件。语法是:

COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf

把以前的阶段作为新的阶段

在使用 FROM 指令时,您可以引用前一阶段的内容。例如:

FROM alpine:latest as builder
RUN apk --no-cache add build-base

FROM builder as build1
COPY source1.cpp source.cpp
RUN g++ -o /binary source.cpp

FROM builder as build2
COPY source2.cpp source.cpp
RUN g++ -o /binary source.cpp

避免安装不必要的包

减小体积,减少构建时间。如前端应用使用 npm install --production 只装生产环境所依赖的包。


一个容器只做一件事

如一个web应用将会包含三个部分,web 服务,数据库与缓存。把他们解耦到多个容器中,方便横向扩展。如果你需要网络通信,则可以将他们至于一个网络下,示例如下:

version: '3'

services:
  # 该镜像会暴露出自身的 `header` 信息
  whoami:
    image: containous/whoami
    restart: always
    labels:
      # 设置Host 为 whoami.docker.localhost 进行域名访问
      - "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)"

# 使用已存在的 traefik 的 network
networks:
  default:
    external:
      name: traefik_default

减少镜像层数

  • 只有 RUN, COPY, ADD 会创建层数, 其它指令不会增加镜像的体积
  • 尽可能使用多阶段构建

使用以下方法安装依赖

RUN yum install -y node python go

错误的方法安装依赖,这将增加镜像层数

RUN yum install -y node
RUN yum install -y python
RUN yum install -y go

将多行参数排序

便于可读性以及不小心地重复装包

RUN apt-get update && apt-get install -y \
  bzr \
  cvs \
  git \
  mercurial \
  subversion

充分利用构建缓存

在镜像的构建过程中 docker 会遍历 Dockerfile 文件中的所有指令,顺序执行。对于每一条指令,docker 都会在缓存中查找是否已存在可重用的镜像,否则会创建一个新的镜像

我们可以使用 docker build --no-cache 跳过缓存

  • ADDCOPY 将会计算文件的 checksum 是否改变来决定是否利用缓存
  • RUN 仅仅查看命令字符串是否命中缓存,如 RUN apt-get -y update 可能会有问题

如一个 node 应用,可以先拷贝 package.json 进行依赖安装,然后再添加整个目录,可以做到充分利用缓存的目的。

FROM node:10-alpine as builder

WORKDIR /code

ADD package.json /code
# 此步将可以充分利用 node_modules 的缓存
RUN npm install --production

ADD . /code

RUN npm run build

三、常用命令:

docker build

命令用于使用 Dockerfile 创建镜像

docker build [OPTIONS] PATH | URL | -

--build-arg=[] :设置镜像创建时的变量;

--cpu-shares :设置 cpu 使用权重;

--cpu-period :限制 CPU CFS周期;

--cpu-quota :限制 CPU CFS配额;

--cpuset-cpus :指定使用的CPU id;

--cpuset-mems :指定使用的内存 id;

--disable-content-trust :忽略校验,默认开启;

-f :指定要使用的Dockerfile路径;

--force-rm :设置镜像过程中删除中间容器;

--isolation :使用容器隔离技术;

--label=[] :设置镜像使用的元数据;

-m :设置内存最大值;

--memory-swap :设置Swap的最大值为内存+swap,"-1"表示不限swap;

--no-cache :创建镜像的过程不使用缓存;

--pull :尝试去更新镜像的新版本;

--quiet, -q :安静模式,成功后只输出镜像 ID;

--rm :设置镜像成功后删除中间容器;

--shm-size :设置/dev/shm的大小,默认值是64M;

--ulimit :Ulimit配置。

--tag, -t: 镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签。

--network: 默认 default。在构建期间设置RUN指令的网络模式

示例如下,使用当前目录的 Dockerfile 创建镜像,标签为 runoob/ubuntu:v1:

docker build -t runoob/ubuntu:v1 .

docker commit :

从容器创建一个新的镜像

docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]

-a :提交的镜像作者;
-c :使用Dockerfile指令来创建镜像;
-m :提交时的说明文字;
-p :在commit时,将容器暂停。

示例如下:

docker commit -a "runoob.com" -m "my apache" a404c6c174a2  mymysql:v1

**docker tag **

标记本地镜像,将其归入某一仓库

docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]

示例如下,将镜像ubuntu:15.10标记为 runoob/ubuntu:v3 镜像:

docker tag ubuntu:15.10 runoob/ubuntu:v3

Docker-compose

结构

docker-compose.yml组成一个project,project包括多个service,每个service定义了容器运行的镜像(或构建镜像),网络端口,文件挂载,参数,依赖等,每个service包括同一个镜像多个容器实例,即 project 包含 service ,service 包含 container ,示例如下:

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

version

指定 docker-compose.yml 文件的写法格式


services

多个容器集合environment:环境变量配置,可以用数组或字典两种方式


image

指定服务所使用的镜像

image: java

expose

暴露端口,但不映射到宿主机,只被连接的服务访问。
仅可以指定内部端口为参数(一般用来标识镜像使用的端口,方便用ports映射

expose:
    - "3000"
    - "8000"

ports

定义宿主机端口和容器端口的映射,可使用宿主机IP+宿主机端口进行访问 宿主机端口:容器端口

ports:   # 暴露端口信息  - "宿主机端口:容器暴露端口"
- "8763:8763"
- "8763:8763"

volumes

动态挂载,卷挂载路径,定义宿主机的目录/文件和容器的目录/文件的映射 宿主机路径:容器路径

volumes:
  # 只需指定一个路径,让引擎创建一个卷
  - /var/lib/mysql
  # 指定绝对路径映射
  - /opt/data:/var/lib/mysql
 
  # 相对于当前compose文件的相对路径
  - ./cache:/tmp/cache
 
  # 用户家目录相对路径
  - ~/configs:/etc/configs/:ro
 
  # 命名卷
  - datavolume:/var/lib/mysql

environment

添加环境变量,可以使用数组或字典两种形式, 任何布尔值; true,false,yes,no需要用引号括起来,以确保它们不被YML解析器转换为True或False

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

environment:

  - RACK_ENV=development
  - SHOW=true
  - SESSION_SECRET

depend_on

规定service加载顺序,例如数据库服务需要在后台服务前运行

version: '2' 
services:  
	db:    
		image: postgres  
	web:    
		build: .    
		command: python manage.py runserver 0.0.0.0:8000    
		volumes:      
			- .:/code    
		ports:      
			- "8000:8000"    
		depends_on:      
			- db

extra_hosts

类似于docker里的–add-host参数 配置DNS域名解析(域名和IP的映射)

extra_hosts:

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

restart

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

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

privileged: true

开启特权模式


user: root

指定容器运行的用户名


networks/links

指定容器名称进行不同容器间的通信

web:
  links:
   - db
   - db:database
   - redis

logging

日志服务

logging:
      driver: "gelf"
      options:
        gelf-address: "udp://graylogserver:12201"
        tag: front-tomcat

network_mode

设置网络模式

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

build

置构建时,Compose 会利用它自动构建镜像,该值可以是一个路径,也可以是一个对象,用于指定 Dockerfile 路径

注:如果指定build同时也指定image,那么会从build里构建,镜像的名字和tag将取image指定的


logging

日志服务

logging:
      driver: "gelf"
      options:
        gelf-address: "udp://graylogserver:12201"
        tag: front-tomcat

network_mode

设置网络模式

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

build

置构建时,Compose 会利用它自动构建镜像,该值可以是一个路径,也可以是一个对象,用于指定 Dockerfile 路径

注:如果指定build同时也指定image,那么会从build里构建,镜像的名字和tag将取image指定的


command

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

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