项目需要使用Docker,所以自学了几天,仅提供给新手…写该博客一是希望能够帮助像我这样刚学习Docker的新人,二是加深自己的印象,如果忘了也可以再看看,有些片段是从其他博客、文档和书籍学习来的,但都是自己理解的,一些没有理解的就没有写出来,如有问题希望大家能够批评指正,由衷的表示感谢。
主要运行环境:centos7
Docker的官网是:http://www.docker.com/
简单的说Docker是一个能够把开发的应用程序自动部署到容器的开源引擎。使用Docker需要先了解容器、镜像和仓库的概念(当然还有其他更底层些的)。
Docker的容器就是“软件界的集装箱”,它可以安装任意的软件和库文件,做任何的运行环境部署。白痴点说,我们可以在Docker的容器中配置centos环境、安装配置jdk、tomcat等。这功能类似虚拟机,不过Docker容器与主机共享操作系统内核,不同的容器之间可以共享部分系统资源,因此容器更加轻量级,消耗的资源也更少。而虚拟机会独占分配给自己的资源,几乎不存在资源共享,各个虚拟机实例之间近乎完全隔离,因此虚拟机更加重量级,消耗更多的资源。
Docker的镜像
Docker的镜像类似虚拟机的镜像,但是可以从镜像仓库下载,Docker公司提供了很多镜像下载,我们也可以自己创建镜像(很简单)并提供给别人下载使用。Docker的镜像是分层的,最底层的叫做基础镜像,而一个镜像可以放到另一个镜像的顶部(类似继承的概念),比如我们在内核、引导文件系统的基础上创建一个centos系统的镜像,又在这个centos上面创建了一个配置jdk的镜像,又在这个jdk的镜像上面创建了一个配置tomcat的镜像。这些镜像是只读的,当我们从一个镜像启动容器时,Docker会在该镜像的最顶层加载一个读写文件系统,我们操作Docker的就是在这读写层中执行。
Docker容器和镜像的关系
Docker镜像和容器的关系类似:我先创建一个“人”类(镜像),然后我们来实例化这个“人”类叫做“程序员”(容器),我们在“人”类(镜像)的基础上,教这个“程序员”(容器)java,并让他输出“hello world”。
Docker的仓库
Docker的仓库,如果大家用过git或者maven等就会很好理解,一般我们写完代码都会push到git的仓库中,提供给其他开发人员使用,或者直接从git上pull下来其他人写好的代码,而Docker的仓库管理的不仅仅是代码,而是整个开发的环境,包括我们的系统、环境变量、软件包和运行时执行的命令等。
我使用的是centos7,其他的系统请参考自己系统命令。
yum -y install docker
启动服务(停止、查看状态类似)
systemctl start docker.service
docker --help
包括:容器、镜像、配置的详细信息
docker info
虽然现在我们还没有镜像,但是我们可以使用Docker提供给我们的镜像来运行容器。
docker run -it centos /bin/bash
Docker的命令都是docker开头,docker run就是运行容器的命令,-i参数标志保证容器中的STDIN是开启的,就是持久交互(…)的标准输入;-t参数告诉Docker为要创建的容器分配一个伪tty终端,这样新创建的容器就能提供一个交互式的shell,而不是一个运行在后台服务的容器;centos是我们要使用的镜像名称,它由Docker公司提供,运行该命令的时候会首先在我们的宿主机上找centos的镜像,如果有就直接使用,如果没有就从Docker Hub Registry上下载该镜像,类似的还有ubuntu、fedora甚至类似redis(配置好redis)和java(配置好java)的镜像;/bin/bash,是我们告诉Docker在容器中要运行/bin/bash命令启动一个Bash shell。运行这个docker run命令后Docker就会在文件系统内部用这个镜像创建一个新容器,该容器拥有自己的网络、IP地址、以及一个用来和宿主机进行通信的桥接网络接口。
运行完命令后,我们会进入以下界面:
@后面的“e3d0723b511b”是这个容器的id,现在我们相当于运行了一个新的虚拟机,我们可以在这里面安装软件或者查看文件,当然这是一个全新的“系统”,所以只有一些基础的命令和配置。
exit
我们可以退出该容器回到宿主机。
docker ps
此时该容器的状态是停止的,我们现在可以查看正在运行的容器(我们回到宿主机上了),添加-a参数可以查看所有的正在运行和停止的容器,添加-l参数查看最后一次运行的容器。
docker start e3d0723b511b
我们可以重新启动已经停止的容器“e3d0723b511b”是你容器的id,或者使用docker restart e3d0723b511b,此时我们可以使用docker ps -l命令查看“e3d0723b511b”已经起来了。
docker attach e3d0723b511b
我们可以重新进入容器里面继续未完成的工作。
docker run --name hello_world -d centos /bin/sh -c "while true; do echo hello world; sleep 1; done"
我们也可以创建守护式容器,也就是在后台长期运行的容器,比较适用于运行应用程序和服务。我们在docker run命令中的-d参数可以使容器在后台运行,–name参数给该容器指定了一个名字,这样我们可以根据容器的ID或者容器的名字来获取容器,并且我们可以在创建容器的时候执行运行时命令,该命令会一直循环打印hello world,执行该命令后会返回一个容器的长UUID(每次创建一个容器相当于new一个新对象,得到不同的实例)。
docker logs hello_world
我们可以使用docker ps查看容器的状态,也可以使用docker logs命令查看容器的日志,hello_world就是创建容器时指定的名字,我们可以加-f参数来监控Docker日志,加-t参数为每一条日志加上时间戳。
docker exec -d hello_world touch /etc/new_file
我们可以对正在后台运行的容器执行命令,docker exec命令可以在容器内部额外启动新进程,-d参数是后台运行,也可以指定-it参数和/bin/bash命令开启交互式的进程,执行该命令我们会在容器内的/etc下创建new_file文件。
docker stop hello_world
或者使用docker kill杀死容器,docker rm删除容器,目前没有命令支持杀死、删除所有命令,我们可以执行。
docker kill $(docker ps -a -q)
docker rm $(docker ps -a -q)
docker images
我们也可以在images后面指定查看某个镜像的内容,比如docker images centos。
docker pull ubuntu:14.04
docker pull就是拉取镜像了,ubuntu指定拉取哪个镜像,后面的“:14.04”是这个镜像的标签(这里可以理解成ubuntu的版本,当然你可以随便给自己创建的镜像起名字和标签),如果我们没有指定标签,Docker会默认给我们的镜像一个“latest”标签,执行该命令就是我们从Docker仓库拉取了一个具有14.04版本的ubuntu系统到本地。我们也可以直接使用docker run命令运行某个镜像容器,如果该镜像本地不存在,也会先从Docker的仓库中拉取下来然后运行该镜像的容器。
docker search java
docker search命令就是查看Docker仓库中的镜像,执行该命令我们查看所有Docker仓库中的关于java镜像。
我们可以使用docker commit命令来创建镜像,但是一般推荐使用更灵活的docker build命令和Dockerfile文件来创建镜像。
touch Dockerfile
现在我们来编辑这个文件:
# Version: 0.0.1
FROM centos
MAINTAINER my name @126.com>
ENV UPDATE_TINE 2016-06-29
RUN yum -y install java-1.8.0-openjdk.x86_64
ENV JAVA_HOME /usr/lib/jvm/jre-1.8.0-openjdk-1.8.0.91-0.b14.el7_2.x86_64/
ENV PATH $JAVA_HOME/bin:$PATH
ENV CLASSPATH .:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
RUN mkdir /home/webapp
RUN yum -y install wget
RUN wget -P /home/webapp/ https://raw.githubusercontent.com/zjm9109/docker/master/1/beyond-center-webapp-0.0.1-SNAPSHOT.jar
ENTRYPOINT [ "java", "-jar", "/home/webapp/beyond-center-webapp-0.0.1-SNAPSHOT.jar" ]
EXPOSE 8080
Dockerfile文件的所有指令都是在开头,并且必须为大写字母,Dockerfile的指令会按从上到下的顺序执行,并且每条指令都会创建一个新的镜像层(前文说过的镜像继承关系),对该镜像进行提交并缓存起来,这样我们可以在后期继续使用某一个镜像,比如我们在Dockerfile中执行了一个下载java的指令,我们再想下载java的时候,如果指令没有变就不需要重新下载,而直接使用该镜像(类似java中重用对象),也节省了内存不需要创建很多一样的镜像保存,也就是我们构建同一个镜像,如果没有指令被修改,就会很快的构建完成,不过如果某一条指令修改了,那么从该指令之后的所有指令都会重新执行。
Dockerfile指令
“#”用来注释,在这里我使用“#”在开头标注了版本;
“FROM”指令指定一个镜像的名字,如果本地存在则可以直接使用该镜像,如果本地不存在会从Docker仓库自动下载,后续的所有指令都是基于该镜像进行,我使用的是centos镜像;
“MAINTAINER ”指令告诉Docker创建该镜像人的名字、邮箱或者电话;
“ENV”指令会在该层镜像中设置环境变量,比如我在第三个指令中设置了“UPDATE_TINE”的环境变量,标注该镜像的修改时间,在第五个指令中设置了 “JAVA_HOME”的环境变量;
“RUN”指令会在该镜像运行指定的命令,比如第四个指令“RUN yum -y install java-1.8.0-openjdk.x86_64”会在该层镜像中下载一个1.8的jdk,在第八个指令中在该层镜像的“home”目录下创建了一个“webapp”目录;
“ENTRYPOINT”指令是在启动的容器的时候执行的命令,我在我的Dockerfile中使用该指令运行了一个我下载的jar;
“EXPOSE ”指令告诉Docker容器内的应用程序将会使用容器指定的端口,但是我们不能任意访问容器运行中的服务端口,出于安全考虑Docker并不会自动打开该端口,我们需要在docker run命令中另外加参数来指定需要打开的端口,我在我的Dockerfile中打开的是8080,因为我的jar默认运行的就是8080,后面会继续说明容器的端口问题。
其他常用指令
“CMD”指令类似“ENTRYPOINT”,不过“CMD”指令可以被docker run中的容器启动命令覆盖,而“ENTRYPOINT”只会把docker run中的容器启动命令作为参数不会被覆盖;
“WORKDIR”指令用于在容器内部设置一个工作目录,“ENTRYPOINT”和“CMD”指定的程序会在这个目录下执行;
“USER”指令用于指定镜像会以什么样的用户运行;
“COPY”指令用来将构建环境下的文件和目录复制到镜像中,比如:“COPY index.html /opt/webapp/index.html”指令用来把宿主机的index.html文件复制到镜像中的/opt/webapp/文件夹中,并且也叫index.html,也可以直接复制整个目录,所以我们可以在宿主机上下载好我的jar,使用“COPY”复制到镜像中使用;
“ADD”指令和“COPY”指令类似,用来将构建环境下的文件和目录复制到镜像中,不过“ADD”指令可以复制URL,并且复制归档文件时自动解压;
“VOLUME”指令用来向基于镜像创建的容器添加卷,一个卷可以存在于一个或多个容器内的特定的目录。这个目录可以绕过联合文件系统,可以提供数据共享或者对数据进行持久化的功能,下文会继续介绍卷。
构建镜像
docker build -t webapp:v1 .
docker build命令就是构建镜像,-t参数可以设置镜像的名字,我将镜像的名字设置成了“webapp”,并且给了一个“v1”标签,最后的“.”不能少,表示在该目录下找我们创建的Dockerfile文件,也可以指定一个git仓库的地址。
执行命令后我们会看到如下信息:
我们看到我们的Dockerfile中的每一个指令都按照顺序执行,并且从基础镜像开始运行一个容器来执行指令,然后提交到新的镜像层,然后再基于该镜像运行一个新的容器来执行指令,并返回该镜像的id,如果显示如下则表示我们的镜像构建成功:
我们可以使用docker images命令找到我们创建的镜像。
如果在执行指令的过程中遇到了错误,我们也可以得到之前执行完成的镜像,比如我重新build镜像,并把第三个安装jdk的指令的yum命令改为um显示如下:
Docker提示我们“/bin/sh: um: command not found”,我们可以使用docker images查看第二个指令执行成的容器id,进入该容器执行错误的命令来调试:
docker run -it 9dbc43c63936 /bin/bash
如果构建成功后我们使用自己的镜像来启动一个容器:
docker run -dp 80:8080 webapp:v1
-d参数是后台运行,-p参数是指定宿主机的端口映射容器的端口,也就是宿主机的80端口映射容器用于启动服务的8080端口(目前我对容器的ip这段还不是很理解,只知道默认容器的ip地址和端口不可直接访问,需要通过宿主机来进行端口映射,然后可以通过访问宿主机的ip+端口来访问容器的服务),运行后返回一个ID,现在该服务就启动成功了。该服务是我之前用spring boot写的一个微服务例子,用maven打包后上传到我的git,现在可以访问下我的用户列表:
我们可以使用上文的“VOLUME”指令在容器中添加卷也可以运行容器的时候指定宿主机的本地目录作为容器的卷:
docker run -dp 80:8080 webapp:v1 -v $PWD/webapp:/var/www/html/webapp
-v参数就是将我们的宿主机当前目录下的webapp作为容器的卷挂载到容器里,比如我在webapp下有一个index.html的首页,使用该命令运行后就可以在容器中/var/www/html/webapp目录下找到index.html文件,如果我们在宿主机上修改了index.html,所有指定该目录作为卷的容器下的index.html都会修改,而且对卷的修改不会对更新镜像产生影响,这样我们如果使用该index.html同时作为开发环境的镜像和测试环境的卷的时候,可以频繁的修改并且不需要更新镜像,而且修改后其他所有引用该卷的镜像也会被修改,但是如果这个卷没有被任何容器使用的时候会被删掉,所以有时候我们需要做好备份。
现在我们创建好了自己的镜像,我们想让别人也可以使用,我们就可以将我们的镜像推送到Docker Hub上供被人下载,我们也可以在https://hub.docker.com/上面注册一个账号来保存我们的镜像。
docker push webapp:v1
我们使用docker push命令推送我们的镜像,提示错误:
显示我们未经授权,因为默认这样会将我们的镜像推送到Docker的root仓库,当然无权访问,我们可以使用“docker login”命令登录我们自己注册账号,然后修改我们的镜像名称,给镜像重新打标记:
docker tag webapp:v1 zjm9109/webapp:v1
docker tag可以修改镜像名,我在“webapp:v1”镜像前面加了“zjm9109/”这个是我个人Docker仓库的用户名,这个时候我们就可以将自己的镜像推送到自己的仓库中了:
docker push zjm9109/webapp:v1
docker pull registry:2.4.1
显示如下则成功:
此时我们启动我们的私有仓库,“–restart=always ”参数是设置开机启动:
docker run -dp 5000:5000 --restart=always -v /opt/docker/registry/data:/var/lib/registry --name docker-registry docker.io/registry:2.4.1
显示如下启动成功:
因为我们使用的v2版本的私有仓库,所以现在我们可以使用你的“你的ip:5000/v2/_catalog”来访问你的私有仓库,如果是v1版本的话就是“你的ip:5000/v1/search“来访问。
当然大家可以搭建复杂些的仓库,这里推荐大家看下这个博客:http://blog.csdn.net/gqtcgq/article/details/51163558,这里面详细的介绍了怎么搭建私有仓库。