这部分介绍比较实用,在开中会常常用到,由于咱们会根据本身的项目来构建本身的镜像,而后发布。html
首先跑起来一个ubuntu,在ubuntu上安装相应的软件。java
docker run -it --name temp ubuntu /bin/bash
进来后,安装一个apache2吧mysql
apt-get update && apt-get install apache2
使用dpkg -L来查看下apache2安装的位置。在/usr/local/apache2就是咱们安装的。nginx
开始以这个容器为基础来制做镜像吧!git
docker commit -a "robinyang" temp robinyang0909/apache2:latest
好了,很快就会出现长uuid说明生成完成了。执行docker ps看看github
你确定有疑惑,开发要是这样来为应用制做镜像的话,岂不是要疯了,确实,我也这么以为,死难用!别慌,下面来看看第二种制做镜像的方法。web
首先,在学习目录下新建一个buildImage下新建一个Dockerfile文件(buildImage是一个docker上下文)。并在新建文件里加入下面指令:sql
FROM ubuntu:14.04 MAINTAINER robinyang RUN apt-get update && apt-get install nginx RUN echo "HelloWorld">/usr/share/nginx/html/index.html EXPOSR 80
保存退出,先来执行下:docker
docker build -t robinyang0909/nginx:latest .
上面指定不知道什么意思吗?第一节有操做过,固然,记不住啊!确实有时候忘记了,docker build --help 查看。shell
这里说下原理,每执行一条指令都穿建立一个镜像层,而且会commit提交,执行吓一跳指定,就是基于刚才的镜像层来建立。
你会发如今你的镜像列表里多出一个你刚才构建的镜像。这样的好,咱们能够在咱们的项目的顶级目录下新建一个Dockerfile来给你的应用定制镜像。既然用Dockerfile来构建镜像的话,咱们先来系统了解一下Dockerfile所须要的基本指令。
Dockerfile的第一条指令,后面指定一个基础镜像。你新建的镜像是想基于那个基础镜像来构建的。必定要放在第一句。
FROM ubuntu:14.04
你能够 docker search 去搜索你须要的镜像。
指定这个镜像的做者信息。
MAINTAINER robinyang robinyang_beijing@163.com
这个用的不少,就详细说说。在构建镜像的时候,在当前镜像层里执行这些shell脚本,而且会commit,构建成新一层镜像。
RUN <command> //默认是使用/bin/sh 来执行 RUN ["", "", "", ...] //默认调用docker exec来执行,有的镜像没有sh
来看几个例子
RUN touch /tmp/hello.html && echo hello>hello.html RUN /bin/bash -c "touch /tmp/hello.html && echo hello>hello.html" RUN ["/bin/bash", "-c", "touch /tmp/hello.html && echo hello>hello.html"] RUN ["touch", "/tmp/hello.html && echo hello>hello.html"]
这个很简单,让你的容器在运行的时候监听哪一个端口。可是有一点要注意,他监听的是容器的端口,而不是主机的端口,因此咱们建立容器的时候,要用 -p 来指定端口。
EXPOSE 8080
学了以上的几个命令的话,如今能够来个小练习。以tomcat为基础,加入一个hello.html页面,并在页面中加入一个helloWorld内容。
FROM tomcat MAINTAINER robinyang robinyang_beijing@163.com RUN touch /usr/local/tomcat/webapps/ROOT/hello.html RUN ["/bin/bash", "-c", "echo helloWorld>/usr/local/tomcat/webapps/ROOT/hello.html"] EXPOSE 8080
构建下
docker build -t robinyang09090/tomcat:latest .
镜像制做完成了,如今能够建立容器了
docker run -d -p 8080:8080 --name testTomcat robinyang09090/tomcat 【-p 宿主机:容器】
能够用你的浏览器访问下 host:8080/hello.html试试。
在上面的Dockerfile 构建文件中,咱们会发现不少有重复的,能够用一个变量来带代替重复的地方。改写下上面Dockerfile
FROM tomcat MAINTAINER robinyang robinyang_beijing@163.com ENV rootPath=/usr/local/tomcat/webapps/ROOT/hello.html RUN touch $rootPath RUN ["/bin/bash", "-c", "echo helloWorld>$rootPath"] EXPOSE 8080
是否是简洁不少。这里ENV的书写有两种方式:
ENV key1=val1 key2=val2 //推荐 或者 ENV key1 val1 ENV key2 val2
这些变量会被持久保存到咱们建立的任何容器里,因此咱们取的变量名要注意,别和系统自带变量名冲突就行。
在容器启动的时候执行的命令,RUN则是在构建镜像的时候执行的命令,这里要区分开。还记得咱们在以前容器里执行循环,docker run
后面 /bin/sh -c "while [ true ]; do echo helloWorld; sleep 1; done"
至关于CMD的做用。
FROM ubuntu CMD ["/bin/sh", "-c", "while [ true ];do echo helloWorld; sleep 1;"]
//指定了本身的执行器, CMD ["executable","param1","param2"] //还有其余的写法 CMD ["param1","param2"] (as default parameters to ENTRYPOINT) CMD command param1 param2 (shell form)
注意:咱们在docker build -t 取镜像名的时候,这里要注意,字母只能所有小写。给容器取名的能够大写。
能够构建咱们的镜像了
docker build -t robinyang0909/loop .
建立并运行容器
docker run -d --name testLoop robinyang0909/loop //接着打印日志 docker logs testLoop
这个指令跟CMD有点相似,都是容器启动成功开始运行执行脚本;但又不一样的地方,CMD会被覆盖,而ENTRYPOINT不会被覆盖;ENTRYPOINT是一个基础前缀指令。
ENTRYPOINT ["/bin/sh", "-c"] CMD ["java Hello"]
上面的会构成一个完整的指令/bin/sh -c java Hello
,CMD或者docker run后面的参数,都会传入ENTRYPOINT,他来指定执行器,咱们这里的执行器是/bin/bash -c
,通常咱们会ENTRYPOINT和CMD搭配起来一块儿用。
上面ENTRYPOINT里咱们指定了一个java Hello
运行咱们本身的jar包,可是有一个问题,你怎么知道你的jar在哪里。就算你知道目录了,你难道要在Hello前拼接路径吗?这样确实能够,可是不优雅,这里有更好的方法,就是WORKDIR。
WORKDIR指定一个工做目录,这样CMD和ENTRYPOINT就会在该目录下执行咱们的程序。
FROM java ENV jarPath=/var/class/ ENTRYPOINT ["/bin/sh", "-c"] WORKDIR $jarPath CMD ["java Hello"]
构建镜像后,run一个容器,启动会失败!别慌,还有一步没有完成,等下一步完成后,就能够了。
WORKDIR能够屡次出现,意思就是切换目录。
上面的例子跑不起来,还缺乏COPY一个Hello文件,接下来,将会拷贝一个文件到镜像里去。在Dockerfile同级目录下新建一个tmp文件,生成一个Hello.class文件。
COPY tmp/Hello.class /var/class/
补充上面的,完整的是
FROM java ENV jarPath=/var/class/ ENTRYPOINT ["/bin/sh", "-c"] WORKDIR $jarPath COPY tmp/Hello.class $jarPath CMD ["java Hello"]
生成镜像docker build -t roinyang0909/hello .
,建立启动容器docker run -d --name hello robinyang0909/hello
,看下容器的日志docker logs hello
发现咱们的java程序跑起来了。
上面的
CMD ["java Hello"]
千万别写成了CMD ["java", "Hello"]
系统会先执行java再执行Hello的,是分步执行的,很显然只运行java指令就出错。建议 一个""
就引发一个完整的指令。
上面COPY文件的时候,咱们只能拷贝上下文里的文件,上下文以外的文件是无法拷贝成功的。什么是上下文了,就是Dockerfile文件所在目录及其子目录,都是上下文。
这个指令,估计有点陌生,确实!首先咱们来谈谈容器的存储机制,咱们知道,容器只能在最上面的读写层进行文件的读写,这样作理论上没有问题,可是你有没有想过一个很严重的问题,我在这个容器里写了一些东西,下次升级,旧容器里的文件是否是要拷贝出来,而后放入到新容器里去,最典型的应用就是数据库容器。如今咱们能够将宿主机的一个目录挂在到容器上,这样能够将数据放到宿主机上。
来吧!咱们用容器跑一个mysql
docker pull mysql //拉去mysql镜像 docker run -d -p 3307:3306 --name mysql1 -e MYSQL_ROOT_PASSWORD=root mysql //建立启动mysql容器
用SQLyog工具链接3307,密码为root ,没有问题。咱们建立一个demo库。docker stop mysql1
关闭容器,从新建立启动一个mysql2的容器docker run -d -p 3307:3306 --name mysql1 -e MYSQL_ROOT_PASSWORD=root mysql
,再次链接,发现咱们刚才建立的demo库没有了。而后你可能想到的解释就是,mysql容器将数据放在了容器里面,不一样的容器数据固然不同。这样理解,也不能算错。其实数据是写在宿主机上的,由于mysql这个镜像指定了VOLUMN,https://github.com/docker-lib... 这是docker镜像的制做github仓库,在Dockerfile 里能够看到VOLUME /var/lib/mysql
已经挂在了/var/lib/mysql。
其实Dockerfile的VOLUMN挂在,只是挂在了/var/lib/docker/volumes/w75wew.../_date/这个下面的一个零食目录里的,能够docker inspect mysql1
看到Mounts信息,挂在在Source指定的宿主机目录下
name的一长串数字字符组成的id是惟一的,每次启动一个Dockerfile指定了VOLUMN的容器,都会建立一个像这样的目录,因此新建容器就是一个新的,是没有数据的。
容器删除的时候,这些零时文件不会被删除的。假设咱们没有将这些目录挂在到宿主机上,容器删除了,数据还在。只不过寻找过程麻烦点,可是这也会是一个补救的机会。
小结: 在Dockerfile里指定VOLUMN会映射到宿主机上,可是他会在/var/lib/docker/volumns/下生成一个惟一的目录来作挂载。
能不能让容器的挂在目录映射到我指定的目录下?固然能
docker run -d -p 3307:3306 --name mysql2 -e MYSQL_ROOT_PASSWORD=root -v /home/mysql/data/:/var/lib/mysql mysql //将容器的/var/lib/mysql映射到宿主机的/home/mysql/data/