docker笔记

记录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
2
3
4

FROM node:8.4 COPY . /app WORKDIR /app RUN npm install --registry=https://registry.npm.taobao.org EXPOSE 3000

CMD node demos/01.js

每一行的含义:

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
2
3
4
5
6
docker container run \  
--rm \ // 停止运行后,自动删除容器文件
--name wordpress \ // 容器的名字
--volume "$PWD/":/var/www/html \
// 将当前目录($PWD)映射到/var/www/html(即Apache对外访问的默认目录)
php:5.6-apache // image名,说明装的是PHP 5.6版本,并且自带apache服务器

②之后在当前目录下创建的文件,都会直接被Apache服务器提供给外界访问,所以直接把WordPress安装包拷贝到此处,就可以通过容器访问到WordPress的安装界面

所以这一步很简单,wget下载,然后tar -xvf解压即可。

(遇到的小问题,由于太多国人使用WordPress了,大概是导致别人服务器都炸了,于是WordPress已经默认屏蔽了大陆的IP,所以直接wget是不可行的。解决就是在windows自己找资源,再传)

③使用MySQL image来构建MySQL container,例子:

1
2
3
4
5
6
7
docker container run \  
-d \
--rm \
--name wordpressdb \
--env MYSQL_ROOT_PASSWORD=123456 \
--env MYSQL_DATABASE=wordpress \
mysql:5.7

每行参数的含义:

-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
2
3
4
5
6
docker container run \  
--rm \
--name wordpress \
--volume "$PWD/":/var/www/html \ // --volume == -v
--link wordpressdb:mysql \
phpwithmysql

新加的那行,表示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
2
3
4
5
6
7
docker container run \  
-d \
--rm \
--name wordpressdb \
--env MYSQL_ROOT_PASSWORD=123456 \
--env MYSQL_DATABASE=wordpress \
mysql:5.7

②然后基于官方的WordPress image,创建WordPress Container

1
2
3
4
5
6
docker container run \  
-d \ --rm \
--name wordpress \
--env WORDPRESS_DB_PASSWORD=123456 \
--link wordpressdb:mysql \
wordpress

(方法一,我们需要给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
2
3
4
5
6
7
8
9
docker container run \  
-d \
-p 127.0.0.2:8080:80 \
--rm \
--name wordpress \
--env WORDPRESS_DB_PASSWORD=123456 \
--link wordpressdb:mysql \
--volume "$PWD/wordpress":/var/www/html \
wordpress

只新加了两行参数:

  • -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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mysql:    
image: mysql:5.7
environment:
- MYSQL_ROOT_PASSWORD=123456
- MYSQL_DATABASE=wordpress
web:
image: wordpress
links:
- mysql
environment:
- WORDPRESS_DB_PASSWORD=123456
ports:
- "127.0.0.3:8080:80"
working_dir: /var/www/html
volumes:
- wordpress:/var/www/html

(显然,一个是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:

img

删除所有容器(包括已经结束了的):

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

-------------本文结束感谢您的阅读-------------