镜像生成途径
- dockerfile
- 基于容器制作
什么是dockerfile
dockerfile说白就是用来构建docker 镜像的源码,大家看到源码俩字不用惊慌,所为的dockerfile源码只是一些基础的指令并没有控制语句或条件判断等,仅仅是一些文本指令
dockerfile语法格式其实只有两类语法格式:
- 注释信息
- 指令和参数
注意:
- 默认dockerfile中的语法虽然不区分大小写,但是约定俗成一律用大写
- dockerfile在执行时,是自上而下去执行的
- dockerfile第一个非注释行必须是FROM指令,用来指定基础镜像
FROM指令
- FROM指令是最重的一个且必须为Dockerfile文件开篇的第一行非注释,用于为镜像文件构建过程指定基准镜像,后续的指令运行于此基准镜像所提供的运行环境
- 实践中,基准镜像可以使任何可用的镜像文件,默认情况下,docker build会在docker主机上查找指定的镜像文件,在其不存在时,则会从Docker Hub Registry上拉取所需的镜像文件
- 如果找不到指定的镜像文件,docker build会返回一个错误信息
MAINTAINER指令
- 用于让Dockerfile制作者提供本人的详细信息
- Dockerfile并不限制MAINTAINER指令出现在哪,但推荐将其放置于FROM之后
- 语法格式为
- MAINTAINER<authro's detail>
- <author's detail>可是任何文本信息,但约定俗成地使用作者名称和邮件地址
- MAINTAINER "liwang<liwang@126.com>"
COPY指令
如果仅仅是把本地的文件拷贝到容器镜像中,COPY 命令是最合适不过的。其命令的格式为:
语法规则:
- COPY <src> <dest>
- COPY ["<src>",....,"<dest>"]
- <src>:要复制的源文件或目录,支持使用通配符
- <dest>:目标路径,建议使用绝对路径
- note:在文件中如果有空格,通常使用第二种格式
复制法则:
- 复制目标目录如果不存在会自动创建
- 如果复制的是目录,那么dest必须要跟目录名称,而且必须以/结尾
ADD指令
ADD指令和COPY命令很类似,不同之处在于:
- ADD指令可以将本地tar包在注入镜像后进行解压缩,如果是URL路径那么只能是将tar包打包进镜像而不会展开
WORKDIR指令
用于为Dockerfile中所有的RUN,CMD,ENTRYPOINT,COPY,ADD指定设定工作目录
语法格式为:
WORKDIR <dirpath>
VOLUME指令
用于在image中创建一个挂载点目录,以挂载docker上的卷或者其他容器上的卷
语法格式为:
- VOLUME <mountpoint>
- VOLUME [<mountpoint>]
如果挂载点目录路径下此前存在,docker run命令会在卷挂载完成以后将此前的所有文件复制到新挂载的卷中
EXPOSE指令
- 用于为容器打开指定要监听的端口以实现与外部通信
- 语法格式:
- EXPOSE <port>/<protocol> <port>/<protocol> <port>/<protocol>
- 一次可以指定多个暴露的端口
- EXPOSE 80/tcp 11211/udp
ENV指令
用于为镜像定义所需的环境变量,并可被Dockerfile文件中位于其后的其它指令(如:ENV,ADD,COPY等)所调用
调用格式为$variable_name或${variable_name}
语法格式为:
- ENV <key>:<value>
- ENV <key>=<value>
- 第一种格式中,<key>之后的所有内容均被视作<value>的组成部分,因此,一次只能设置一个变量
- 第二种格式中,可以一次设置多个变量,每一个变量为一个“<key>=<value>”键值对,如果<value>包含空格,可以以反斜线\进行转义,也可以通过对<value>加引号进行标识,\进行续航
- 定义多个变量时,建议使用第二种方式
RUN指令和CMD指令
RUN和CMD可以执行指定的shell命令;
区别:
- 运行的时间段不同;
- CMD可以有多个,但是只有最后一个CMD有效;
- RUN可以有多个,并且都可以生效
RUN指令主要是dockerfile创建为镜像时要执行的任务,而CMD主要作用在于创建为容器时指定默认要运行的程序,不过,CMD可以被docker run的命令行选项所覆盖
如下图所示:
CMD 语法格式
- CMD <command> 或
- CMD ["<excutable>","<param1>","<param2>"]
- CMD["<param1>","<param2>"]
前两种语法格式与RUN意义相同
第三种语法格式主要用于为ENTRYPOINT指令提供默认参数
ENTRYPOINT指令
ENTRYPOINT指令和CMD指令很像,只不过ENTRYPOINT不能被覆盖(除非指定特殊选项),并且CMD命令会当做参数传递给ENTRYPOINT
使用ENTRYPOINT的意义在于我们可以给我们的应用配置文件制作一个配置文件模板,这样,我们就可以在容器外部注入变量生成新的配置文件
以nginx:1.14-alpine为例子,我们可以基于一个配置文件模板生成不同端口号的nginx,以下为例子:
Dockerfile文件内容
FROM nginx:1.14-alpine
LABEL maintainer="liwang<liwang@163.com>"
ENV HTML=/data/html/
ADD index.html ${HTML}
ADD entrypoint.sh /bin/
CMD ["/usr/sbin/nginx","-g","daemon off;"]
ENTRYPOINT ["/bin/entrypoint.sh"]
entrypoint.sh脚本文件内容如下:
cat > /etc/nginx/conf.d/www.conf <<EOF
server {
server_name $HOSTNAME;
listen ${PORT:-80};
root ${HTML:-/usr/share/nginx/html};
}
EOF
exec "$@"
Dockerfile目录的文件结构如下:
[root@bogon Dockerfile]# ls
Dockerfile entrypoint.sh index.html
生成镜像
[root@bogon Dockerfile]# docker build -t mynginx ./
这时候我们运行容器的时候就可以在容器外部给予变量生成不同的nginx配置文件,如下:
我们想生成1000端口的nginx那么可以如下:
[root@bogon Dockerfile]# docker run --name myweb1 -itd -e "PORT=1000" mynginx
539896dad037ea6f3e804a281b46cce5eec21c96bcd331d0dc6ed8da1653c540
[root@bogon Dockerfile]# docker exec -it myweb1 /bin/sh
/ # netstat -tnpl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1/nginx -g daemon o
tcp 0 0 0.0.0.0:1000 0.0.0.0:* LISTEN 1/nginx -g daemon o
/ # hostname
539896dad037
/ # wget -O - -q 539896dad037:1000
this is httpd box
我们想生成2000端口的nginx那么可以如下:
[root@bogon Dockerfile]# docker run --name myweb2 -itd -e "PORT=2000" mynginx
ce9b85e9a77b761bb832e2b29582093dd53bbcb9120bbf30fd4b69850874bc1a
[root@bogon Dockerfile]# docker exec -it myweb2 /bin/sh / # netstat -tnpl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:2000 0.0.0.0:* LISTEN 1/nginx -g daemon o
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1/nginx -g daemon o
USER指令
- 用于指定运行image时或Dockerfile中任何RUN,CMD,或ENTRYPOINT指令指定的程序时用户名或UID
- 默认情况下container运行的用户为root
- 语法格式
- USER <UID> |<USERNAME>
- 这里要注意的是UID可以是任意数字,但实践中必须为/etc/passwd中某用户的有效UID,否则,docker run命令将运行失败
HEALTHCHECK指令
有时候我们判断一个容器是否正常不能单一的只看容器当中的进程是否存在,比如,我们运行一个nginx,但是我们的document_root指错了,容器的进程一样会执行,但是这个服务已经不能够按照用户的需求正常访问了,因此我们用HEALTHCHECK去探测我们的服务是否正常
语法格式:
HEALTHCHECK [OPTIONS] CMD command
options选项有:
- --interval=#(默认30s)
- --timeout=#(默认30s)
- --start-period=#(默认0秒)
- --retries=#(默认为3次)
检测command指令发出后返回值有3种:
- 0:健康
- 1:不健康
- 2:预留的,用户可以根据2返回值来做操作
for example:
HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1
案例:
dockerfile如下:
[root@jiaqi211 dockerfile]# cat Dockerfile
FROM busybox:latest
MAINTAINER "liwang<liwang@126.com>"
HEALTHCHECK --start-period=3s CMD wget -O - -q http://0.0.0.0:80 || exit 1
RUN mkdir -p /data/web/html/; \
cd /data/web/html && echo "welcome to busybox" > index.html
CMD ["-f","-h","/data/web/html/"]
ENTRYPOINT ["/bin/httpd"]
EXPOSE 80
等待片刻如下:
[root@jiaqi211 dockerfile]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d4837b1dc5a9 busyweb "/bin/httpd -f -h /d…" 2 minutes ago Up 2 minutes (healthy) 0.0.0.0:80->80/tcp myweb1
如果我们把红色字体中80端口改成一个不通的端口比如10080,结果如下:
[root@jiaqi211 dockerfile]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bbaa030c1560 busyweb "/bin/httpd -f -h /d…" About a minute ago Up About a minute (unhealthy) 0.0.0.0:32775->80/tcp myweb1
SHELL指令
在shell中默认一般使用["/bin/sh","-c"],我们可以修改默认的shell
语法格式如下:
- SHELL ["excutable","parameters"]
不过改的情况不多见
ARG指令
ARG指令可以在docker build时传变量,而ENV是在docker run时给予变量
ARG用法如下:
[root@jiaqi211 dockerfile]# vim Dockerfile
FROM busybox:latest
ARG author="liwang<liwang@126.com>"
LABEL maintainer="${author}"
HEALTHCHECK --start-period=3s CMD wget -O - -q http://0.0.0.0:10080 || exit 1
RUN mkdir -p /data/web/html/; \
cd /data/web/html && echo "welcome to busybox" > index.html
CMD ["-f","-h","/data/web/html/"]
ENTRYPOINT ["/bin/httpd"]
EXPOSE 80
验证1:如果不传变量会用已经定义好的变量传入
[root@jiaqi211 dockerfile]# docker build -t myweb1 ./
[root@jiaqi211 dockerfile]# docker inspect myweb1
.....
"Labels": {
"maintainer": "liwang<liwang@126.com>"
}
.....
验证2:如果传入变量会按照用户传入的变量传入
[root@jiaqi211 dockerfile]# docker build -t myweb1 --build-arg author="fengzi<fengzi@126.com>" ./
[root@jiaqi211 dockerfile]# docker inspect myweb1
.....
"Labels": {
"maintainer": "fengzi<fengzi@126.com>"
}
.....
ONBUILD
- 用于在Dockerfile中定义一个触发器
- Dockerfile用于build映像文件,此映像文件可以被作为基础映像被另一个Dockerfile用做FROM指令参数,并构建新的映像文件,在后面docker build时会被触发
- ONBUILD不能触发FROM 和 MAINTAINER指令
语法格式:
- ONBUILD <INSTRUCTION>
示例
镜像目录的文件如下:
[root@localhost ~]# tree dockerfile/
dockerfile/
├── Dockerfile
├── index.html
└── nginx-1.16.1.tar.gz
Dockerfile文件内容如下:
[root@localhost dockerfile]# cat Dockerfile
FROM busybox:latest
MAINTAINER "liwang<liwang@126.com>"
WORKDIR /etc/
ENV DOCUMENT_ROOT="/data/web/html"
EXPOSE 80/tcp
ADD http://nginx.org/download/nginx-1.16.1.tar.gz /etc
RUN cd /etc/ && tar -zxvf nginx-1.16.1.tar.gz -C /tmp
RUN mkdir -p ${DOCUMENT_ROOT}
ADD index.html ${DOCUMENT_ROOT}
VOLUME ${DOCUMENT_ROOT}
CMD httpd -f -h ${DOCUMENT_ROOT}
运行Dockerfile命令如下:
[root@localhost dockerfile]# docker build -t myhttpd ./
Sending build context to Docker daemon 1.036MB
Step 1/11 : FROM busybox:latest
---> 020584afccce
Step 2/11 : MAINTAINER "liwang<liwang@126.com>"
---> Using cache
---> 3537a5a13f27
Step 3/11 : WORKDIR /etc/
---> Running in 6ffe9c85b719
Removing intermediate container 6ffe9c85b719
---> 434913bba9b0
Step 4/11 : ENV DOCUMENT_ROOT="/data/web/html"
---> Running in 04e7e3bae01a
Removing intermediate container 04e7e3bae01a
---> cc03fd190474
Step 5/11 : EXPOSE 80/tcp
---> Running in 44adb435af97
Removing intermediate container 44adb435af97
---> 54d69d81360d
Step 6/11 : ADD http://nginx.org/download/nginx-1.16.1.tar.gz /etc
Downloading [==================================================>] 1.033MB/1.033MB
---> e3bf4b5c993e
Step 7/11 : RUN cd /etc/ && tar -zxvf nginx-1.16.1.tar.gz -C /tmp
---> Running in a00836e16083
nginx-1.16.1/
nginx-1.16.1/auto/
nginx-1.16.1/conf/
nginx-1.16.1/contrib/
nginx-1.16.1/src/
nginx-1.16.1/configure
.......
Removing intermediate container a00836e16083
---> effc55a2bd3a
Step 8/11 : RUN mkdir -p ${DOCUMENT_ROOT}
---> Running in 911d5b78d827
Removing intermediate container 911d5b78d827
---> c78b410fc588
Step 9/11 : ADD index.html ${DOCUMENT_ROOT}
---> fb7e61c9e4fe
Step 10/11 : VOLUME ${DOCUMENT_ROOT}
---> Running in d38bccb71ddc
Removing intermediate container d38bccb71ddc
---> 2ace0d71de1f
Step 11/11 : CMD httpd -f -h ${DOCUMENT_ROOT}
---> Running in 07b43b1c3162
Removing intermediate container 07b43b1c3162
---> 42006762e446
Successfully built 42006762e446
Successfully tagged myhttpd:latest
资源限制
启动memory压力测试镜像,给与容器分配最多占用256m内存-m 256m,并且在容器内部启用2个子进程--vm 2,并且每个子进程所占用的内存资源时50m
[root@localhost yum.repos.d]# docker run --name stress -it -m 256m --rm lorel/docker-stress-ng:latest stress --vm 2 --vm-bytes 50m
[root@localhost yum.repos.d]# docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
712765609df2 stress 200.38% 103.1MiB / 256MiB 40.28% 656B / 0B 0B / 0B 5
启用cpu压力测试,给与容器分配最多占用4个cpu,并且在容器内部启动8个子进程来对cpu做压测
[root@localhost yum.repos.d]# docker run --name stress -it -m 256m --cpus 4 --rm lorel/docker-stress-ng:latest stress --vm 2 --vm-bytes 50m --cpu 8
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
2bf33001b07c myos 0.00% 56KiB / 1.777GiB 0.00% 656B / 0B 0B / 0B 1
8d0912451e7f stress 400.98% 125.3MiB / 256MiB 48.93% 516B / 0B 0B / 0B
启动cpu压力测试,并且限制只能在0和2号cpu上运行
[root@localhost yum.repos.d]# docker run --name stress -it --cpuset-cpus 0,2 --rm lorel/docker-stress-ng:latest stress --cpu 8
启动cpu共享压力测试,2个容器共享cpu资源
[root@localhost yum.repos.d]# docker run --name stress -it --cpu-shares 512 --rm lorel/docker-stress-ng:latest stress --cpu 8
[root@localhost yum.repos.d]# docker run --name stress -it --cpu-shares 256 --rm lorel/docker-stress-ng:latest stress --cpu 8
[root@localhost yum.repos.d]# docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
c5ae34837074 stress 253.22% 15.81MiB / 1.777GiB 0.87% 656B / 0B 0B / 0B 9
207bcffb19dc stress2 125.53% 15.81MiB / 1.777GiB 0.87% 656B / 0B 0B / 0B