
Dockerfile-详解
作者: weipeng.su 目前就职于携程, 从事大数据开发工作
前面我分享了关于容器、虚拟机、物理机的区别(docker分享与入门), 还介绍了容器里关键的镜像、容器、仓库等Docker的核心概念.容器的发展史(容器的发展历史).虚拟机的搭建(Vagrant搭建虚拟机).本次我们具体聊聊Dockerfile.
分享目的:
•为什么使用Dockerfile
•Dockerfile的常用语法
• 官网的MySQL的Dockerfile解析
一.为什么使用Dockerfile
在docker分享与入门 介绍过关于镜像构建的两种方式,一种是推荐的Dockerfile,一种是进入容器中一步步安装和配置我们想要的环境, 最后在通过docker 提供的docker commit命令,将该容器打包成一个镜像.很多人还是不明白为什么要使用Dockerfile.总结来说有以下几点优势.
•移植性
我们通过Dockerfile描述了, 我们的镜像是如何一步步构建的.可以清晰的看到安装了哪些软件,其基于哪个开源镜像构建.如果要将镜像分享给别人, 你可以直接分享Dockerfile就行.Dockerfile就几KB可远远比镜像来得小.更加具备方便的移植和分享.
•安全性
如果有人不想通过Dockerfile来构建,想直接使用你提供的镜像, 他可以查看你的Dockerfile来确认是否该镜像安全.
•维护性
如果公司中有人离职, 交接程序.一个Dockerfile就把所有的配置都说得清清楚楚.而不是只给你个镜像,出来问题,你也不知道是不是环境变量的原因,还是程序冲突,版本过久.
如果还认为Dockerfile不好使,可以自己阅读下官方和流行的开源项目构建的镜像, 都是提供了Dockerfile(比如上图就是MySQL的官方提供的Dockerfile).基于这点和Dockerfile的优势, 我们没有理由不适用Dockerfile.
二.Dockfile的语法
2.1 FROM
•FROM用于Dockerfile最开头,用来描述我们要构建的Image的BaseImage
如上图的MySQL官网构建的是
FROM debian:buster-slim
说明其基于debian这个BaseImage构建的MySQL
2.2 Label
定义了image的元数据信息.比如维护者、版本、这个image的作用.
LABEL maintainer='weipeng.su'LABEL version="1.0"LABEL description="This is ods image"
2.3 RUN(重点)
该命令用于执行,你要在容器里执行各种命令,都可以通过该关键字来描述.比如安装软件、依赖库.每RUN一次都会 产生一个新的层(Layer).所以RUN的最佳实践就是将 多条命令合并成一条,使用&&符号.使用 反斜线换行.
RUN yum update && yum install -y vim \python-devRUN /bin/bash -c 'source $HOME/.bashrc;echo $HOME'
•RUN背后的原理
RUN背后其实会自动创建一个临时容器,然后执行相应的命令,执行完后就会自动执行docker commit命令来生成新的镜像.所以才会导致m每RUN一次,都会产生新的一层(Layer).我们可以通过Docker构建镜像时候的log查看到这一点.
2.4 WORKDIR(重点)
设定当前的工作目录.如果该目录不存在会自动创建.
WORKDIR /testWORKDIR demoRUN pwd # 输出/test/demo
改变工作目录,不要使用RUN cd, 要知道每RUN一次就会生成新的一个层Layer,无形中镜像会膨胀
2.5 ADD和COPY
这两个关键字作用类似,都是用来 将本地的资源文件、代码添加到指定的容器目录里.唯一区别是, ADD有附加作用,对于压缩文件,其会自动解压.
•大部分情况下我们使用最多的是COPY.•如果是远程文件,可以使用RUN curl或者RUN wget获取.
2.6 Env(重点)
对于很多程序的部署,我们都需要设置环境变量,比如HADOOP的部署,我们需要设置HADOOP_HOME.Env关键字提供了该功能.
ENV MYSQL_VERSION 5.6RUN apt-get install -y mysql-server= "${MYSQL_VERSION}" \ && rm -rf /var/lib/apt/lists/*
2.7 VOLUME和EXPOSE(重点)
2.7.1 EXPOSE
expose主要 用来声明容器中要对宿主机开放的端口, 是 帮助镜像使用者理解这个镜像服务的守护端口, 以方便配置映射(使用
-p
这是小写的p)
•expose的是一个声明,表示容器将会监听这个端口.所以就算expose了, 端口还是没有启动, 如果要实现宿主机和容器的端口映射得用-p参数•Expose可以配合
-P
(这是大写的P)参数使用, 实现随机从宿主机挑选一个端口和Expose声明的端口进行映射.
1.编写如下Dockerfile
FROM busyboxEXPOSE 8080
2.编译镜像
docker build -t demo .
3.启动容器
docker run -it -d --rm --name demo1 demo /bin/sh -c "sleep 600;""
•这句话的意思是启动容器,并休眠10分钟,这样是为了让容器跑10分钟后会自动销毁,移除容器.•--name demo1 表示容器启动后就叫demo1. 后一个demo表示的是创建容器依据的demo这个镜像.
4.查看该容器的端口映射.
这时候这个容器启动了,并且监听着其自己的8080端口,但是这是无法被宿主机访问的.
5.查看该容器的IP
docker exec -it demo1 ifconfig
6.看下8080端口有没有映射成功
telnet不通说明8080端口实际上并没有和宿主机的8080映射上.
6.使用-p参数进行端口映射
docker run -d -p 8080:8080 --rm --name demo2 demo /bin/sh -c "sleep 800"
然后telnet 127.0.0.1 8080可以看到此时端口已经通了.
总结
•expose的作用仅仅是为了声明该容器提供服务的端口有哪些,方便使用者通过
docker run -p
进行端口映射.•如果想随机映射到宿主机的端口,可以使用
docker run -P
2.7.2 VOLUME
•docker 为我们提供了三种不同的方式将数据挂载到容器中:data volume、bind mount、
tmpfs
.这样容器销毁或者退出,数据都不会丢失.
•Data Volume是Docker中数据持久化的最佳方式.
先查看当前宿主机下有哪些VOLUME docker volume ls
1.编写如下Dockerfile
FROM busyboxVOLUME /home
2.构建镜像
docker build -t demo:vol .
3.启动容器
docker run -d --rm --name demo3 demo:vol /bin/sh -c "sleep 800"
4.进入容器的/home目录创建文件
创建完后,可以通过
docker volume ls
.看到宿主机已经挂载上了该数据.
5.销毁容器并再次启动一个新容器
docker rm -f demo3docker run -d --rm --name demo4 -v 6374ab509553b368be41cc5b5cb5534b8f64dae34e48fe969b386bb3c8969c37:/home demo:vol /bin/sh -c "sleep 800"
查看该容器的/home目录
docker exec -it demo4 ls /home
可以发现text.txt文件依然存在.
但是这样挂载存在问题.就是这个卷名特别长.我们可以在容器启动的时候就指定-v my_home:/home.这样就会在宿主机中创建一个叫my_home的卷, 以后就方便管理了.
如下图演示的过程.我们还可以通过
docker volume inspect my_home
.查看这个卷具所在的宿主机位置.
2.8 CMD 和ENTRYPOINT(重点)
这两个关键字主要用于在容器启动后要执行哪些命令,一般用于程序的启动.
2.8.1 Dockerfile里的shell和exec格式
Dockerfile里RUN、CMD、ENTRYPOINT都支持这两种语法格式.
shell格式
RUN apt-get install -y vimCMD echo "hello docker"ENTRYPOINT echo "hello docker"
exec格式
RUN ["apt-get", "install", "-y", "vim"]CMD ["/bin/echo", "hello docker"]ENTRYPOINT ["/bin/echo", "hello/docker"]
区别
编写如下两个Dockerfile,然后构建出这两个镜像.
Dockerfile1
FROM centosENV name DockerENTRYPOINT echo "hello $name"
Dockerfile2
FROM centosENV name DockerENTRYPOINT ["/bin/echo","hello $name"]
构建镜像
docker build -t entrypoint-shell .
docker build -t entrypoint-exec .
运行容器
第一个DK能打印出hello Docker.第二个DK打印出hello $name.
原因: 我们用shell格式,相当于是在shell中执行命令,所以其能识别出变量. 如果我们用exec格式, 我们只是执行echo这个程序,而不是在shell中执行echo,所以无法识别环境变量.虽然exec格式默认是无法识别出环境变量, 但是如果我们指定云运行时候是通过shell命令运行.也是可以绕过这个限制.
2.8.2 CMD
CMD所指定运行的命令会在容器启动后执行.但可能会被忽略, 如果在
docker run -it centos:7 /bin/bash
后这样指定了启动后运行的/bin/bash就会被覆盖.
2.8.3 ENTRYPOINT
ENTRYPOINT可以保证指定的命令一定会被执行,而不会被外部命令覆盖.所以
•一般用在指定容器启动后一定要运行的后台服务.
三.官网的MySQL Dockerfile解读
FROM debian:buster-slim# add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get addedRUN groupadd -r mysql && useradd -r -g mysql mysqlRUN apt-get update && apt-get install -y --no-install-recommends gnupg dirmngr && rm -rf /var/lib/apt/lists/*# add gosu for easy step-down from root# https://github.com/tianon/gosu/releasesENV GOSU_VERSION 1.12...RUN mkdir /docker-entrypoint-initdb.d...ENV MYSQL_MAJOR 8.0ENV MYSQL_VERSION 8.0.20-1debian10...RUN echo "deb http://repo.mysql.com/apt/debian/ buster mysql-${MYSQL_MAJOR}" > /etc/apt/sources.list.d/mysql.list...VOLUME /var/lib/mysql# Config filesCOPY config/ /etc/mysql/COPY docker-entrypoint.sh /usr/local/bin/RUN ln -s usr/local/bin/docker-entrypoint.sh /entrypoint.sh # backwards compatENTRYPOINT ["docker-entrypoint.sh"]EXPOSE 3306 33060CMD ["mysqld"]
如上是官网的MySQL Dockerfile.
•第一行就是描述基于哪个镜像.•
RUN groupadd -r mysql && useradd -r -g mysql mysql
是在创建mysql这个用户组,并且创建mysql用户,将其添加到这个用户组中.•
ENV MYSQL_VERSION 8.0.20-1debian10
. 设置了MYSQL的版本•
VOLUME /var/lib/mysql
该目录是mysql存储数据的目录, 表示将mysql的存储挂载到了宿主机•
COPY config/ /etc/mysql/
由于该Dockerfile是官网提供的,放在了github上, 在这个开源项目里官网已经创建了config这个文件夹用来描述mysql的一些配置.该句意思就是 将这些配置拷贝入宿主机器中的/etc/mysql/目录下•
EXPOSE 3306 33060
表示将容器将会通过3306和33060提供服务, 如果想进行端口映射应该选映射到容器的这两个端口•
CMD ["mysqld"]
表示容器启动后,就会执行这个mysqld,这是mysql的服务端后台进程.