记录docker学习的笔记。本文主要是参考了阮一峰的Docker教程,:http://www.ruanyifeng.com/blog/2018/02/docker-tutorial.html。在阅读完这篇Docker教程的过程中所做的笔记,用我自己的语言整理了一遍。
Docker出现的原因,解决了哪些问题,如何解决的,有哪些应用?
软件开发的一个大难题,就是环境配置,经常出现各种因为环境配置而出现的错误。(我相信你在编写maven项目的时候就已经被各种依赖包的版本给困扰到了,环境变量同样如此)比如安装一个python应用,计算机必须要有python引擎,还要有各种依赖,可能还要配置环境变量。除此之外,随着软件的迭代还有运行环境的升级,一些老旧的模块可能与当前环境不兼容。开发者常常会说:“它在我的电脑上是没有问题的”(It works on my machine),换言之,在其他电脑能不能运行,我不确定,我也不想管。环境变量如此麻烦,换一台机器就要重来一次,十分浪费时间。于是有人在想,能不能从根本上解决问题,比如软件可是带着环境变量一起安装?也就是说,在安装的时候,把原始环境一模一样地复制过来。
一种可行的解决方法,虚拟机。直接去现场对机器进行配置,或者去客户家配置电脑环境,并不现实,但使用虚拟机就可以达到模拟运行系统的效果,直接把开发时的最终环境一起打包,就可以彻底还原软件的原始环境。
但这个方案存在一些缺点:
①资源占用多。一个虚拟机是需要一定的内存和磁盘空间的,所以即使你的项目只有1MB,续集你依然要几百MB才能运行。
②冗余步骤多。一个虚拟机是一个完整的操作系统,所以一些系统级别的操作步骤,往往都不能跳过,比如用户登录。
③启动慢。启动的时候就跟启动一台电脑的时间一样。
显然,为了保存运行环境,直接copy一个虚拟机,并不可取,因为我们需要的只是一些必要的环境变量,一些组件,而非一个彻底完整的操作系统。所以,我们需要的是一个虚拟的“小型虚拟机”,里面只包含我们的程序所需要的组件即可。这种东西就叫做:Linux容器(Linux Containers,LXC)
Linux容器不是模拟一个完整的操作系统,而是对进程进行隔离。或者说,在正常进程的外面套了一个保护层。对于容器里面的进程来说,它接触到的各种资源都是虚拟的,从而实现与底层系统的隔离。因为的进程级别的,相比虚拟机就有很多的优势,比如:启动快,资源占用少,体积小。因为它只需要占用需要的资源,包含要用到的组件。总而言之,有点像轻量级的虚拟机。
Docker,就是一种LXC的封装,提供了简单易用的容器使用接口。Dockers将应用程序与该程序的依赖,一起打包在一个文件里面(image)。运行这个文件,就会生成一个虚拟容器(container)。程序在这个虚拟容器里运行,就好像在真实的物理机器上运行。有了Docker就无须担心环境问题。用户可以方便地创建和使用容器,把自己的应用放入容器。容器还可以进行版本管理,复制,分享,修改,就像管理普通的代码一样。
Docker的主要用途:
①提供一次性的环境。无论是开发环境,单元测试环境,还是其他,都可以轻松提供。
②提供弹性的云服务。因为Docker容器可以随开随关,很适合动态扩容和缩容。
③组建微服务架构。容器可以存放多个服务,还有它们的各种依赖。所以一台机器就可以跑多个服务,在本机就可以模拟出微服务架构。
Docker重要概念:
image文件:Docker把应用程序及其依赖,都打包在image文件里面。一个image文件可以通过继承另一个image文件,进行拓展。这是一个二进制文件
container:容器文件,通过image文件生成的实例,本身也是一个文件,称为容器文件。
(二者的关系,image就像class,container就像是具体的对象)
Dockerfile:它是一个文本文件,用于配置image。Docker根据该文件生成二进制的image文件(也就是说,我们要生成image文件,实际上是通过生成Dockerfile文件来指定依赖)
(而且,Dockerfile的文件名必须就叫Dockerfile,这样也使得后续用Dockerfile构建image的时候,直接指定Dockerfile的路径即可,那个参数是【path】,所以无法指定具体的文件名)
编写Dockerfile文件的过程:
1.在项目的根目录,新建一个文件 .dockerignore (就是忽略的路径),(可选)
.git node_modules npm-debug.log
2.创建一个文本文件Dockerfile,写入: (举例)
1 |
|
每一行的含义:
FROM:表明要继承哪个image,这里继承的官方的node image,版本号是8.4(默认library/…)
COPY:将当前目录下的所有文件,(除了.dockerignore里提到的),都copy到image文件里的/app目录
WORKDIR:指定工作路径
RUN……:启动成功之后会运行的命令,这里是运行npm install安装依赖
EXPOSE:将容器暴露特定的端口号,允许外部连接这个端口
CMD:在容器成功启动后,会自动执行该命令
(ps:RUN和CMD有什么区别?RUN命令是在image文件的构建阶段执行,执行的结果都会打包进入image文件。而CMD命令则是在容器启动成功后执行。另外,一个dockerfile可以包含多个RUN命令,但只能有一个CMD命令。同时,指定了CMD命令之后,docker container run命令就不能附加命令,否则它会覆盖掉CMD命令,也就是docker run的CMD命令把dockerfile里的覆盖了。)
Docker常用命令:
①列出本机的所有image
docker image ls / docker images
删除image文件
docker image rm 【imageName】
从Docker仓库抓取具体image到本地(默认是官方仓库,可配置国内的镜像文件)
docker image pull 【path/.../imageName】
(前面默认的path是 library/,是官方仓库的默认组)
②通过image生成container实例,并运行该实例
docker container run 【imageName】
(如果本地无法找到,会自动去仓库抓取,即pull)
列出本地的container:
docker container ls [ -all ] / docker ps
删除container:
docker container rm 【containerID】
docker container run的一些参数:
例子:docker container run -p 8000:3000 -it koa-demo /bin/bash
-p:容器的3000端口,映射到本机的8000端口
-it:容器的Shell映射到当前的Shell,然后你在本机窗口输入的命令,就会传入容器中
/xxx:容器启动之后,内部第一个执行的命令。这里是启动Bash,保证用户可以使用Shell
退出容器:先ctrl+c停止进程,在ctrl+d退出容器。
docker contain kill
:终止容器运行
退出之后,容器文件并不会删除,所以需要手动rm,但应该删除,下次再由image创建。可以在运行时增加一个参数–rm,表明容器终止运行后自动删除容器文件。
③使用Dockerfile文件,创建image文件:
docker image build -t xxx .
(docker image build,-t表明image文件的名字, “.”表示当前路径)
(xxx可以指定版本号,格式为 xxx:yyy,放在冒号之后。如果不指定版本号,默认为latest)
(ps: docker image build == docker build)
④一些有用的命令
docker container start
(docker container run命令是新建容器,每运行一次,就会新建一个容器,如果同样的命令运行两次,就会生成两个一模一样的容器文件。如果希望重复使用容器(当然,没有–rm参数了),那么就要使用docker container start命令,用于启动已经生成,已经停止运行的容器文件。
docker container stop
(docker contain kill是向容器里面的主进程发出SIGKILL信号,而docker container stop是发出SIGTERM信号,然后过一段时间再发出SIGKILL信号。这两个信号的的差别是,如果进程收到SIGTERM信号,可以自行进行收尾清理工作,但也可以不理会这个信号。而如果收到SIGKILL信号,就会强行立即终止,那么正在进行中的操作会全部丢失。也就是说,kill是强制性立即关闭,而stop会给一定的时间让container把收尾工作完成,如果超出一定时间后还没完成收尾,那么才强制结束)
docker container logs
用于查看容器的输出,即容器里Shell的标准输出。如果容器docker run的时候没有使用-it参数,就要用这个命令查看输出。(或者是显式指定要运行在后端的时候)
docker container exec
docker container exec命令用于进入一个正在运行的 docker 容器。如果docker run命令运行容器的时候,没有使用-it参数,就要用这个命令进入容器。一旦进入了容器,就可以在容器的 Shell 执行命令.
docker container cp
用于从正在运行的Docker容器里面,将文件拷贝到本机。
写法:docker container cp 【containerID】:【/path/to/file】
-—-
实例:使用Docker架设WordPress网站(需要WordPress容器和MySQL容器)
方法一:自己自建WordPress容器
步骤:
①通过官方的PHP image,构建一个PHP container 例子:
1 | docker container run \ |
②之后在当前目录下创建的文件,都会直接被Apache服务器提供给外界访问,所以直接把WordPress安装包拷贝到此处,就可以通过容器访问到WordPress的安装界面
所以这一步很简单,wget下载,然后tar -xvf解压即可。
(遇到的小问题,由于太多国人使用WordPress了,大概是导致别人服务器都炸了,于是WordPress已经默认屏蔽了大陆的IP,所以直接wget是不可行的。解决就是在windows自己找资源,再传)
③使用MySQL image来构建MySQL container,例子:
1 | docker container run \ |
每行参数的含义:
-d:容器启动后,在后台运行(这时候要查看输出就需要用logs
–rm, –name:自动删除,容器名
–env:向容器进程传入一个环境变量MYSQL_ROOT_PASSWORD,该变量会作为MySQL的根密码,同时还传入一个MYSQL_DATABASE,容器里面的MySQL会根据该变量创建一个同名的数据库
④定制Container,也就是Dockerfile
此时WordPress容器和MySQL容器都已经有了,但二者并没有链接到PHP Container中。我们需要先在PHP image的基础上,安装mysqli的拓展,然后启动Apache。Dockerfile文件如下:
FROM php:5.6-apache RUN docker-php-ext-install mysqli CMD apache2-foreground
然后再基于Dockerfile创建一个image文件(包含了mysqli拓展的PHP image)
docker build -t phpwithmysql .
(该image文件名为phpwithmysql)
⑤WordPress容器链接mysql
PHP container已经增加了mysqli的拓展,而对于WordPress,在启动的时候连接即可。启动命令如下
1 | docker container run \ |
新加的那行,表示WordPress容器要连接到wordpressdb容器,冒号表示该容器的别名为mysql。最后还要修改一下wordpress目录的权限,让容器可以将配置信息写入这个目录(/var/www/html)
chmod -R 777 wordpress
接着,在启动PHP container的时候,命令行会返回一个对外开放的IP地址,比如172.17.0.2,直接访问即可出现WordPress的安装界面。
总结:PHP image(web server) + wordpress.tar.gz(到/var/www/html) + mysql链接
方法二:
方法一需要自己创建WordPress容器,还是有点麻烦,实际上Docker已经提供了官方的WordPress image,直接用那个即可。
步骤:
①新建并启动MySQL Container(跟方法1一样)
1 | docker container run \ |
②然后基于官方的WordPress image,创建WordPress Container
1 | docker container run \ |
(方法一,我们需要给php image增加mysqli拓展,然后启动php container,作为我们自己创建的WordPress image,然后再link数据库,实际上,直接一个wordpress即可(调用官方Docker)
但是,上面指定了-d,也就是在后台运行,所以前台看不到输出,此时需要inspect命令查看输出。
docker container inspect wordpress
找到IPAddress字段,那就是对外开放的IP地址,直接访问即可。
此时虽然用了官方的WordPress image,简化了很多不必要的步骤,但还是存在一些问题。比如,每次新建容器,返回的IP地址不能保证相同,导致需要更换IP地址来访问WordPress。WordPress安装在容器里面,本地无法修改文件。解决方法如下:
创建WordPress Container的命令如下:
1 | docker container run \ |
只新加了两行参数:
- -p 127.0.0.2:8080:80:将容器的 80 端口映射到127.0.0.2的8080端口。
- –volume “$PWD/wordpress”:/var/www/html:将容器的/var/www/html目录映射到当前目录的wordpress子目录。
这样我们就固定了IP地址,同时还指定了一下端口的映射,还有容器的文件目录映射。(容器会把文件默认放到Apache的默认路径,然后再映射到wordpress子目录,使得本地也可以修改文件)
方法三:
方法二主要是,分别启动两个容器,然后在启动时,在命令行里提供容器之间的连接信息。还有一种更简单的方法,就是使用Docker Compose。关键点:定义一个YAML格式的配置文件,在里面写好多个容器之间的调用关系,只需要一个命令就可以同时启动/关闭这些容器。
步骤:
①安装,pass
②创建yml文件
1 | mysql: |
(显然,一个是mysql容器,一个是web容器)
启动:docker-compose up
(同时启动两个容器,此时就可以访问127.0.0.3到安装界面)
关闭:docker-compose stop
关闭之后,容器文件还是存在的,写在里面的数据也不会丢失。下次再启动的时候,还可以复用。如果要删除,需要先暂停两个容器,然后调用删除命令:
docker-compose rm
首先是自定义image,然后启动,连接。后续的改进是,可以直接用官方或者别人写好的image来进行启动,连接。最后的方法是,直接使用docker-compose,不需要在启动命令的时候写一堆参数,直接写到yml文件里即可,同时还能保存容器信息,进行复用。自此,Docker的基本示例到此结束。
参考:http://www.ruanyifeng.com/blog/2018/02/docker-tutorial.html
PS: (很多情况下可以省略image , container等关键字)
docker image build == docker build
docker container run == docker run(即使是run image,也是构建出container)
docker container rm = docker rm
docker container stop = docker stop
进入容器,除了exec,还可以用attach:
删除所有容器(包括已经结束了的):
docker rm -f $(docker ps -a -q)
删除所有image:
docker rmi -f $(docker images)
导出容器为压缩包:
docker export 【options】 container_id > xxx.tar
(> xxx其实不是必须的)
导入容器:
docker import xxx.tar container_name
Dockerfile一些常用指令:
FROM:表示基本的image(一般是Docker Hub或者其他仓库别人制作好的image),在此基础上进行修改调整。FROM必须在其他指令之前。
RUN:启动成功之后会执行的命令
CMD:容器启动成功之后执行的(区别上面看RUN与CMD的区别,记住多条CMD只会执行一条,所以如果启动时docker run存在CMD命令,那么会直接把Dockerfile的CMD命令全部忽略掉)
ADD:添加源文件到目的路径。如果路径不以斜杠结尾,被视为文件。如果源文件是可识别的压缩包格式,docker还会自动解压
ARG:设置构造参数 ENV:设置环境变量
(ARG与ENV的区别:ARG设置的是构建时的环境变量,在容器运行时是不会存在这些变量的。ENV主要就是运行时的环境变量)
COPY:与ADD类似,但不支持URL和压缩包(ADD除了文件,还可以是URL或压缩包)
EXPOSE:声明暴露的端口
LABEL:为镜像添加元数据metadata