构建镜像docker bulid
简介
镜像构建的完整命令为docker build [OPTIONS] PATH | URL | -。在这条语句中,并没有指定Dockerfile路径,这是因为大家都习惯使用默认的文件名,实际上 Dockerfile 的文件名并不要求必须为 Dockerfile,而且并不要求必须位于上下文目录中,比如可以用 -f ../Dockerfile.php 参数指定某个文件作为 Dockerfile。
PATH指的是上下文目录,那么什么是上下文呢?首先我们要理解 docker build 的工作原理。Docker 在运行时分为 Docker daemon(也就是服务端守护进程)和docker client客户端工具。Docker 的引擎提供了一组 REST API,被称为 Docker Remote API,而如 docker 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 docker 功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。
docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。所以才引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径,docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。
所以在一般情况下,我们会在构建镜像的时候,会先创建一个空目录,然后把所需要的文件都放置在这个目录下,包括Dockerfile文件,需要构建时,使用docker bulid -t name:verison .来运行。而 . 表示PATH,即上下文,docker client会把这个目录里面的文件都打包,传给docker daemon。正是由于这个特性,所有Dockerfile的指令中,COPY、ADD这两个都只能使用相对绝对,即COPY ./a.txt /test/
实例
使用Dockerfile来构建一个tank大战的image。先下载源码:
1 | [root@master ~]# git clone https://codehub.devcloud.huaweicloud.com/Demo04260/tank.git |
开始创建,运行命令之后,从第一个输出内容可以看到,docker有包内容全部打包给了Docker daemon。
1 | [root@master tank]# docker build -t tank . |
这样就创建成功了。运行:
1 | [root@master tank]# docker run --name tank -d -P tank |
访问docker服务器的32769端口即可开始坦克大战了。
由于在构建的过程中docker会采用缓存的机制,如果需要重新构建,不想使用cache需要添加—no-cache。
Dockerfile指令
Dockerfile是一个包含用户能够构建镜像的所有命令的文本文档,它有自己的语法以及命令,docker能够从dockerfile中读取指令自动的构建镜像。
FROM
FROM 就是指定 基础镜像,因此一个 Dockerfile 中 FROM 是必备的指令,并且必须是第一条指令。有以下格式:
1 | FROM scratch |
scratch 这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。以 scratch 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。需要自己来一层一层地创建。直接 FROM scratch 会让镜像体积更加小巧。使用 Go 语言 开发的应用很多会使用这种方式来制作镜像,这也是为什么有人认为 Go 是特别适合容器微服务架构的语言的原因之一。
如果没有指定 tag ,latest 将会被指定为要使用的基础镜像版本。
允许多个from,即就是多阶段构建。后续介绍
MAINTAINER
格式为MAINTAINER user_name user_email,指定维护者信息。可以使用LABEL代替。
LABEL
给镜像添加元数据,可以用LABEL命令替换MAINTAINER命令。指定一些作者、邮箱等信息。设置之后,使用docker inspect来查看。
具体格式:LABEL <key>=<value> <key>=<value> <key>=<value> ...
1 | [root@master tank]# docker inspect -f '{{.Config.Labels}}' tank |
RUN
格式为:RUN ["executable", "param1", "param2"] 或者 RUN <command>
RUN会在当前镜像的最上面创建一个新层,并且能执行任何的命令,然后对执行的结果进行提交。提交后的结果镜像在dockerfile的后续步骤中可以使用。
如果有多个 RUN 指令,最佳建议是使用 && 合并为一个,并且格式要统一。
1 | FROM php:7.1-fpm |
WORKDIR
格式为: WORKDIR /path/to/workdir
WORKDIR 用来指定工作目录的。Docker 默认的工作目录是/,只有 RUN 能执行 cd 命令切换目录,而且还只作用在当下下的 RUN,也就是说每一个 RUN 都是独立进行的。如果想让其他指令在指定的目录下执行,就得靠 WORKDIR。WORKDIR 动作的目录改变是持久的,不用每个指令前都使用一次 WORKDIR。如果该目录不存在,则会创建。
ENV
设置环境变量,设置的变量可供后面指令使用。可以使用docker inspect查的。使用docker exec进入容器之后,使用env命令也是可以输出的。
设置的方法是: ENV <key1>=<value1> <key2>=<value2>... 或者 ENV <key> <value>,建议还加上等号,比较直观。
如果在容器运行时想修改环境变量的值 ,可以使用docker run --env <key>=<value>来修改环境变量。
1 | [root@master tank]# docker exec tank env |
ARG
格式:ARG <参数名>[=<默认值>]
构建参数和 ENV 的效果一样,都是设置环境变量。所不同的是,ARG 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。即ARG 定义的变量只在建立 image 时有效,建立完成后变量就失效消失。
Dockerfile 中的 ARG 指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令 docker build 中用 --build-arg <参数名>=<值> 来覆盖。
使用时,类似shell的变量一样,前面加$即可。
COPY
格式:COPY [--chown=<user>:<group>] <源路径>... <目标路径> 或者 COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]
COPY 将文件从路径 COPY [--chown=<user>:<group>] <源路径>... <目标路径>
标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。在使用该指令的时候还可以加上 --chown=<user>:<group> 选项来改变文件的所属用户及所属组。
ADD
跟COPY的作用一样,都是复制文件到容器。但比COPY更高级,会自动下载或者自动解压,如果 <源路径> 为一个 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,ADD 指令将会自动解压缩这个压缩文件到 <目标路径> 去。
VOLUME
定义数据卷,格式为:VOLUME ["<路径1>", "<路径2>"...] 或者 VOLUME <路径>
定义完成之后,docker在创建容器时,会自动创建volume数据卷,这时即使容器删除了,其数据也不是丢失。
EXPOSE
指定端口。格式为 EXPOSE <端口1> [<端口2>...]。
指定容器运行时对外暴露的端口,但是该指定实际上不会发布该端口,它的功能是镜像构建者和容器运行者之间的记录文件。run命令有-p和-P两个参数,如果是-P(大写)就是会随机端口映射,容器内会随机映射到EXPOSE指定的端口。
CMD
格式:
- shell 格式:CMD <命令>
- exec 格式:CMD [“可执行文件”, “参数1”, “参数2”…]
在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 ",而不要使用单引号。注意,如果使用 shell 格式的话,实际的命令会被包装为 sh -c 的参数的形式进行执行。即如果CMD echo $HOME,在实际执行中,会将其变更为:CMD [ "sh", "-c", "echo $HOME" ]
指定容器启动时默认运行的命令,在一个Dockerfile文件中,如果有多个CMD命令,只有一个最后一个会生效。RUN指令是在构建镜像时候执行的,而CMD指令是在每次容器运行的时候执行的。而如果docker run命令接了command,会覆盖CMD的命令。
对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。如果写为 CMD service nginx start,会解析为 CMD [ "sh", "-c", "service nginx start"],这样主进程是sh了。那么当 service nginx start 命令结束后,sh 也就结束了,sh 作为主进程退出了,自然就会令容器退出。正确的做法是直接执行 nginx 可执行文件,并且要求以前台形式运行。比如:CMD ["nginx", "-g", "daemon off;"]
ENTRYPOINT
ENTRYPOINT 的格式和 RUN 指令格式一样,分为 exec 格式和 shell 格式。
ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数。ENTRYPOINT 在运行时也可以替代,不过比 CMD 要略显繁琐,需要通过 docker run 的参数 --entrypoint 来指定。
ENTRYPOINT和CMD同时存在时, docker把CMD的命令拼接到ENTRYPOINT命令之后,即<ENTRYPOINT> "<CMD>", 拼接后的命令才是最终执行的命令。
在一个Dockerfile文件中,如果有多个ENTRYPOINT命令,也只有一个最后一个会生效。而不同点是通过docker run command命令会覆盖CMD的命令,但如果有ENTRYPOINT,则执行的命令不会覆盖ENTRYPOINT,docker run命令中指定的任何参数都会被当做参数传递给ENTRYPOINT。
例如,如果Dockerfile只有 CMD [ "curl", "-s", "https://ip.cn" ],那么运行 docker run myip -i 会出错,如果是 ENTRYPOINT [ "curl", "-s", "https://ip.cn" ],运行 docker run myip -i 就会输出Http头部信息,这是因为当存在 ENTRYPOINT 后,CMD 的内容将会作为参数传给 ENTRYPOINT,而这里 -i 就是新的 CMD,因此会作为参数传给 curl,从而达到了我们预期的效果。
有关这两者的区别,可以参考:https://yeasy.gitbooks.io/docker_practice/content/image/dockerfile/entrypoint.html 以及 Dockerfile: ENTRYPOINT和CMD的区别
HEALTHCHECK
格式:
HEALTHCHECK [选项] CMD <命令>:设置检查容器健康状况的命令HEALTHCHECK NONE:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
HEALTHCHECK 指令是告诉 Docker 应该如何进行判断容器的状态是否正常,这是 Docker 1.12 引入的新指令。支持下列选项:
--interval=<间隔>:两次健康检查的间隔,默认为 30 秒;--timeout=<时长>:健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;--retries=<次数>:当连续失败指定次数后,则将容器状态视为unhealthy,默认 3 次。
如下的Dockerfile
1 | FROM nginx |
运行之后就会检测其健康,如下是正常的,healthy:
1 | root@fdm:~/test# docker ps |
.dockerignore
这个不是指令,只是一个文件,用来忽略上下文目录中包含的一些image用不到的文件,它们不会传送到docker daemon。
其他的一些指令,可以参考:
实例
ubuntu ssh
官方镜像都没有ssh的功能,给他加上吧。先创建一个空的目录,然后创建以下文件:
1 | root@fdm:~/sshd_ubuntu# cat Dockerfile |
然后直接docker build -t ubuntu-sshd:v1 .即可。完成之后,可以看到,其大小比原来的ubuntu大了三分之二。仅仅只是安装了openssh-server。
1 | [root@master ubuntu_ssh]# docker images |grep ubuntu |
在此基本上,可以再次制作nginx,不过是简单版:
1 | root@fdm:~# cat nginx/Dockerfile |
可以使用supervisord来控制服务,https://my.oschina.net/renguijiayi/blog/365087
nginx-alpine版
alpine介绍
首先先介绍下busybox以及alpine。
- busyBox是一个集成了一百多个最常用Linux命令和工具(如cat、echo、grep、mount、telnet等)的精简工具箱,它只有2MB的左右的大小,很方便进行各种快速验证,被誉为“Linux系统的瑞士军刀”。BusyBox可运行于多款POSIX环境的操作系统中,如Linux(包括Android)、Hurd、FreeBSD等。官网是:https://busybox.net/
- Alpine Linux 是一个社区开发的面向安全应用的轻量级Linux发行版。 Alpine 的意思是“高山的”,它采用了musl libc和busybox以减小系统的体积和运行时资源消耗,但功能上比BusyBox又完善得多。在保持瘦身的同时,Alpine还提供了自己的包管理工具apk,可以通过https://pkgs.alpinelinux.org/packages查询包信息,也可以通过apk命令直接查询和安装各种软件。官网是:https://www.alpinelinux.org/
两者的大小为:
1 | [root@master ~]# docker images |egrep 'busybox|alpine' |
由于alpine小巧、安全、简单以及功能完备的特点,被广泛应用于众多Docker容器中。不同于centos使用yum、ubuntu使用apt-get去安装软件,alpine使用了apk这个指令去安装相应的指令。以下是修改apk使用阿里的源。
1 | alpine 只有/bin/sh,没有/bin/bash |
使用apk --update add --no-cache <package>来安装命令。安装完成vim,就增加了40MB了。
根据官方的alpine Dockerfile来看,其实非常简单:
1 | FROM scratch |
构建nginx
首先先看一下官方的写法 docker-nginx Dockerfile ,可以看出,其思路是直接nginx提供的源,然后直接使用apk去安装。这是比较方便,但是无法自定义参数,所以还是需要使用编译大法。
先下载必要的软件包:
1 | git clone https://github.com/alibaba/nginx-http-concat.git |
编写Dockerfile,nginx默认安装在/usr/local/nginx目录下,如下:
1 | [root@master nginx]# cat Dockerfile |
为了减少镜像的体积,使用了多阶段构建的方法。同时多阶段构建时,只能复制文件,不能复制目录,所以需要对/usr/local/nginx进行打包,使用``docker build -t nginx_alpine .构建完成之后,可以看到nginx_alpine大小为31.1M,而编译过程中,产生了284M。
1 | [root@master nginx]# docker images |
另外,alpine是不支持中文,要集成中文环境,可以参考:https://www.clxz.top/2019/05/09/160241/
创建centos镜像
tar打包构建
第一种方法,通过tar打包构建基础镜像:
1 | yum clean all |
安装docker:
1 | 安装EPEL源和REMI源 |
将打包好的文件导入到docker中,然后验证:1
2
3
4
5
6
7
8
9[root@localhost ~]# cat /mnt/CentOS-7.-BaseImage.tar.gz |docker import - centos-tar:7.6
sha256:b629e1dd6b5365d96cc8aa0d4918215a1609fd2d8d41688a3507c78f428451d9
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centos-tar 7.6 b629e1dd6b53 2 minutes ago 751 MB
[root@localhost ~]# docker run --rm -it centos-tar:7.6 bash
[root@949f13b77435 /]# w
09:11:52 up 1:12, 2 users, load average: 0.12, 0.62, 0.42
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
mkimage-yum.sh脚本构建
第二种方法,运行mkimage-yum.sh脚本,创建CentOS的基础镜像,首先下载wget https://raw.githubusercontent.com/moby/moby/master/contrib/mkimage-yum.sh,然后运行脚本:
1 | [root@localhost ~]# sh mkimage-yum.sh centos-mkimage |
由上述两图可知,通过tar打包创建的基础镜像的体积大约是通过mkimage-yum.sh脚本创建的基础镜像的3倍左右。这是由于前者是基于最小化安装的CentOS系统而创建的,而后者是从CentOS官方源安装必要的软件包而创建的,前者比后者多安装了很多软件包,包括邮件工具、设备驱动程序,等等
http://ghoulich.xninja.org/2017/10/13/how-to-create-docker-base-image-of-centos/
mysql 5.7
基础知识
想对mysql进行镜像化,必须掌握mysql的一些操作命令。
my_print_defaults为查询my.cnf配置文件的命令,第一个参数为section
1 | [root@ee1995b6bc44 /]# my_print_defaults mysqld |
另外,也可以使用mysqld —verbose —help方法来获取1
2
3
4[root@xmxyk bin]#./mysqld --verbose --help 2>/dev/null | awk -v key="datadir" '$1 == key { print $2; exit }'
/usr/local/mysql/var/
[root@xmxyk bin]#./mysqld --verbose --help 2>/dev/null | awk -v key="socket" '$1 == key { print $2; exit }'
/tmp/mysql.sock
以空密码的方式初始化数据库:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18[root@ee1995b6bc44 /]# mysqld --initialize-insecure --datadir="/var/lib/mysql/" --user=${MYSQL_USER}
[root@ee1995b6bc44 /]# ll /var/lib/mysql
total 110620
-rw-r----- 1 mysql mysql 56 Mar 16 12:35 auto.cnf
-rw-r----- 1 mysql mysql 419 Mar 16 12:35 ib_buffer_pool
-rw-r----- 1 mysql mysql 50331648 Mar 16 12:35 ib_logfile0
-rw-r----- 1 mysql mysql 50331648 Mar 16 12:35 ib_logfile1
-rw-r----- 1 mysql mysql 12582912 Mar 16 12:35 ibdata1
drwxr-x--- 2 mysql mysql 4096 Mar 16 12:35 mysql
drwxr-x--- 2 mysql mysql 4096 Mar 16 12:35 performance_schema
drwxr-x--- 2 mysql mysql 12288 Mar 16 12:35 sys
[root@ee1995b6bc44 /]# cat /var/log/mysql/error.log
2019-03-16T12:35:48.881747Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
2019-03-16T12:35:49.345885Z 0 [Warning] InnoDB: New log files created, LSN=45790
2019-03-16T12:35:49.439030Z 0 [Warning] InnoDB: Creating foreign key constraint system tables.
2019-03-16T12:35:49.521977Z 0 [Warning] No existing UUID has been found, so we assume that this is the first time that this server has been started. Generating a new UUID: 077e6497-47e8-11e9-9508-0242ac110002.
2019-03-16T12:35:49.524840Z 0 [Warning] Gtid table is not ready to be used. Table 'mysql.gtid_executed' cannot be opened.
2019-03-16T12:35:49.526731Z 1 [Warning] root@localhost is created with an empty password ! Please consider switching off the --initialize-insecure option.
Dockerfile
从Dockerfile文件可以看到是使用了yum进行安装的mysql 5.7,安装好了之后,生成了/etc/my.cnf文件,而启动mysql则是交给了docker-entrypoint.sh
1 | [root@host mysql]# cat Dockerfile |
docker-entrypoint.sh
docker-entrypoint.sh内容如下,主要作用是启动Mysql,且进行初始化。此脚本由国外大神cytopia制作,实现了shell的debug模式。值得认真学习。
1 | [root@host mysql]# cat docker-entrypoint.sh |
运行之后,使用docker logs mysql就可以看出mysql输出的密码。如果想自定义密码,可以使用-e MYSQL_ROOT_PASSWORD=password来实现。
1 | [root@host mysql]# docker logs mysql |