【Docker新手篇】Docker入门
- Docker
- Docker 镜像
- 获取镜像
- 运行
- 列出镜像
- 删除本地镜像
- 使用 Dockerfile 定制镜像
- 构建镜像
- 保存镜像
- 镜像构建上下文(Context)
- Docker 容器
- 启动容器
- 新建并启动
- 守护态运行
- 终止容器
- 进入容器
- exec 命令
- 数据卷(容器数据管理)
- 什么是数据卷
- 数据集操作命令
- 创建和查看数据卷
- 挂载数据卷
- 案例-给MySQL挂载本地目录
- 小结
- Docker 构建 Tomcat
- 查找 Docker Hub 上的 Tom
- 运行容器
- Docker 构建 MySQL
- 查找 Docker Hub 上的 MySQL 镜像
- 复制容器文件到宿主机实现数据卷共用配置文件
- 导入文件过大的错误
Docker
Docker 最初是 dotCloud 公司创始人 Solomon Hykes 在法国期间发起的一个公司内部项目,它是基于 dotCloud 公司多年云服务技术的一次革新,并于 2013 年 3 月以 Apache 2.0 授权协议开源,主要项目代码在 GitHub 上进行维护。Docker 项目后来还加入了 Linux 基金会,并成立推动 开放容器联盟(OCI)。
Docker 使用 Google 公司推出的 Go 语言 进行开发实现,基于 Linux 内核的 cgroup,namespace,以及 AUFS 类的 Union FS 等技术,对进程进行封装隔离,属于 操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。最初实现是基于 LXC,从 0.7 版本以后开始去除 LXC,转而使用自行开发的 libcontainer,从 1.11 开始,则进一步演进为使用 runC 和 containerd。
Docker 在容器的基础上,进行了进一步的封装,从文件系统、网络互联到进程隔离等等,极大的简化了容器的创建和维护。使得 Docker 技术比虚拟机技术更为轻便、快捷。
下面的图片比较了 Docker 和传统虚拟化方式的不同之处。传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。
Docker 镜像
获取镜像
从 Docker
镜像仓库获取镜像的命令是 docker pull
docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]
docker pull tomcat
- Docker 镜像仓库地址:地址的格式一般是 <域名/IP>[:端口号]。默认地址是 Docker Hub。
- 仓库名:如之前所说,这里的仓库名是两段式名称,即 <用户名>/<软件名>。对于 Docker Hub,如果不给出用户名,则默认为 library,也就是官方镜像。
运行
$ docker run -it --rm tomcat bash
docker run
就是运行容器的命令,我们这里简要的说明一下上面用到的参数。
-
-it
:这是两个参数,一个是 -i:交互式操作,一个是 -t 终端。我们这里打算进入 bash 执行一些命令并查看返回结果,因此我们需要交互式终端。 -
--rm
:这个参数是说容器退出后随之将其删除。默认情况下,为了排障需求,退出的容器并不会立即删除,除非手动 docker rm。我们这里只是随便执行个命令,看看结果,不需要排障和保留结果,因此使用 --rm 可以避免浪费空间。 -
tomcat
: 这是指用 tomcat 镜像为基础来启动容器。 -
bash
:放在镜像名后的是命令,这里我们希望有个交互式Shell
,因此用的是 bash。
最后我们通过
exit
(Ctrl+D) 退出了这个容器。
列出镜像
要想列出已经下载下来的镜像,可以使用 docker image ls
命令。
虚悬镜像:上面的镜像列表中,还可以看到一个特殊的镜像,这个镜像既没有仓库名,也没有标签,均为 。
<none> <none> 00285df0df87 5 days ago 342 MB
这个镜像原本是有镜像名和标签的,原来为 mongo:3.2
,随着官方镜像维护,发布了新版本后,重新 docker pull mongo:3.2
时,mongo:3.2
这个镜像名被转移到了新下载的镜像身上,而旧的镜像上的这个名称则被取消,从而成为了 <none>
。除了 docker pull
可能导致这种情况,docker build
也同样可以导致这种现象。由于新旧镜像同名,旧镜像名称被取消,从而出现仓库名、标签均为 <none>
的镜像。这类无标签镜像也被称为 虚悬镜像(dangling image)
一般来说,虚悬镜像已经失去了存在的价值,是可以随意删除的,可以用下面的命令删除。
docker image prune
删除本地镜像
如果要删除本地的镜像,可以使用 docker image rm
命令,其格式为:
docker image rm [选项] <镜像1> [<镜像2> ...]
其中,<镜像> 可以是镜像短 ID、镜像长 ID、镜像名 或者 镜像摘要。
使用 Dockerfile 定制镜像
镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。
Dockerfile
是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
定制 tomcat 镜像为例,这次我们使用 Dockerfile 来定制。
mkdir mytomcat
cd mytomcat
touch Dockerfile
vim Dockerfile
Dockerfile文件内容
FROM tomcat
RUN echo "hello docker" > index.html
FROM指定基础镜像:
所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。就像我们之前运行了一个 nginx 镜像的容器,再进行修改一样,基础镜像是必须指定的。而 FROM 就是指定基础镜像,因此一个 Dockerfile 中 FROM 是必备的指令,并且必须是第一条指令。
RUN执行命令:
RUN 指令是用来执行命令行命令的。由于命令行的强大能力,RUN 指令在定制镜像时是最常用的指令之一。
构建镜像
在 Dockerfile
文件所在目录执行:
docker build -t tomcat_zyshep .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM tomcat
---> e43d811ce2f4
Step 2 : RUN echo '<h1>Hello, Docker!</h1>' > index.html
---> Running in 9cdc27646c7b
---> 44aa4490ce2c
Removing intermediate container 9cdc27646c7b
Successfully built 44aa4490ce2c
从命令的输出结果中,我们可以清晰的看到镜像的构建过程。在 Step 2
中,如同我们之前所说的那样,RUN
指令启动了一个容器 9cdc27646c7b
,执行了所要求的命令,并最后提交了这一层 44aa4490ce2c
,随后删除了所用到的这个容器 9cdc27646c7b
。
这里我们使用了 docker build
命令进行镜像构建。其格式为:
docker build [选项] <上下文路径/URL/->
在这里我们指定了最终镜像的名称 -t tomcat_zyshep,构建成功后,我们可以像之前运行 tomcat 那样来运行这个镜像,其结果会和 tomcat 一样。
保存镜像
备份本地仓库的镜像
1、⽤ save ⼦命令将本地仓库的镜像保存当前⽬录下
docker save -o tomcat.zysheep.tar 镜像名称
2、将本地目录下的镜像备份文件导⼊到本地 Docker 仓库
# ⽅式⼀(不输出详细信息):
[root@localhost ~]# docker load -i tomcat.zysheep.tar
# ⽅式⼆(输出详细信息):
[root@localhost ~]# docker load < tomcat.zysheep.tar
镜像构建上下文(Context)
如果注意,会看到 docker build
命令最后有一个 .
。.
表示当前目录,而 Dockerfile
就在当前目录,因此不少初学者以为这个路径是在指定 Dockerfile
所在路径,这么理解其实是不准确的。如果对应上面的命令格式,你可能会发现,这是在指定上下文路径。那么什么是上下文呢?
首先我们要理解 docker build
的工作原理。Docker
在运行时分为 Docker
引擎(也就是服务端守护进程)和客户端工具。Docker
的引擎提供了一组 REST API
,被称为 ocker Remote API
,而如 docker
命令这样的客户端工具,则是通过这组 API
与 Docker
引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 docker
功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎
)完成。也因为这种 C/S
设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。
当我们进行镜像构建的时候,并非所有定制都会通过 RUN
指令完成,经常会需要将一些本地文件复制进镜像,比如通过 COPY
指令、ADD
指令等。而 docker build
命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker
引擎中构建的。那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?
这就引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径,docker build
命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker
引擎。这样 Docker
引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。
如果在 Dockerfile
中这么写:
COPY ./package.json /app/
这并不是要复制执行 docker build
命令所在的目录下的 package.json
,也不是复制 Dockerfile
所在目录下的 package.json
,而是复制 上下文(context) 目录下的 package.json
。
因此,COPY
这类指令中的源文件的路径都是相对路径。这也是初学者经常会问的为什么 COPY ../package.json /app
或者 COPY /opt/xxxx /app
无法工作的原因,因为这些路径已经超出了上下文的范围,Docker 引擎
无法获得这些位置的文件。如果真的需要那些文件,应该将它们复制到上下文目录中去。
现在就可以理解刚才的命令 docker build -t tomcat_zysheep .
中的这个.
,实际上是在指定上下文的目录,docker build
命令会将该目录下的内容打包交给 Docker 引擎
以帮助构建镜像。
如果观察 docker build
输出,我们其实已经看到了这个发送上下文的过程:
$ docker build -t tomcat .
Sending build context to Docker daemon 2.048 kB
理解构建上下文对于镜像构建是很重要的,避免犯一些不应该的错误。比如有些初学者在发现 COPY /opt/xxxx /app
不工作后,于是干脆将 Dockerfile
放到了硬盘根目录去构建,结果发现 docker build
执行后,在发送一个几十 GB 的东西,极为缓慢而且很容易构建失败。那是因为这种做法是在让 docker build
打包整个硬盘,这显然是使用错误。
一般来说,应该会将 Dockerfile
置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎
,那么可以用 .gitignore 一样的语法写一个 .dockerignore,该文件是用于剔除不需要作为上下文传递给 Docker 引擎的。
那么为什么会有人误以为 . 是指定 Dockerfile
所在目录呢?这是因为在默认情况下,如果不额外指定 Dockerfile
的话,会将上下文目录下的名为 Dockerfile
的文件作为 Dockerfile
。
这只是默认行为,实际上 Dockerfile
的文件名并不要求必须为 Dockerfile
,而且并不要求必须位于上下文目录中,比如可以用 -f …/Dockerfile.php 参数指定某个文件作为 Dockerfile。
当然,一般大家习惯性的会使用默认的文件名 Dockerfile
,以及会将其置于镜像构建上下文目录中。
Docker 容器
容器是独立运行的一个或一组应用,以及它们的运行态环境。对应的,虚拟机可以理解为模拟运行的一整套操作系统(提供了运行态环境和其他系统环境)和跑在上面的应用。
启动容器
启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态(stopped)的容器重新启动。
新建并启动
所需要的命令主要为 docker run
。
启动一个 bash 终端,允许用户进行交互。
docker run -t -i tomcat bash
其中,-t 选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上, -i 则让容器的标准输入保持打开。
守护态运行
更多的时候,需要让 Docker
在后台运行而不是直接把执行命令的结果输出在当前宿主机下。此时,可以通过添加 -d
参数来实现。
docker run -d ubuntu:17.10 /bin/sh -c "while true; do echo hello world; sleep 1; done"
77b2dc01fe0f3f1265df143181e7b9af5e05279a884f4776ee75350ea9d8017a
此时容器会在后台运行并不会把输出的结果 (STDOUT) 打印到宿主机上面(输出结果可以用 docker logs 查看)。
注: 容器是否会长久运行,是和 docker run 指定的命令有关,和 -d 参数无关。
终止容器
可以使用 docker container stop
来终止一个运行中的容器。
此外,当 Docker 容器中指定的应用终结时,容器也自动终止
终止状态的容器可以用 docker container ls -a
或者 docker ps -a
命令看到
root@zysheep:~# docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5a68ca3aa075 mysql:5.7.22 "docker-entrypoint.s…" 2 hours ago Up 8 minutes 0.0.0.0:3306->3306/tcp mysql
9f676a420eee zysheep "catalina.sh run" 2 hours ago Up 8 minutes 0.0.0.0:8080->8080/tcp tomcat_zysheep
处于终止状态的容器,可以通过 docker container start
命令来重新启动。
此外,docker container restart
命令会将一个运行态的容器终止,然后再重新启动它。
进入容器
在使用 -d
参数时,容器启动后会进入后台。使用 docker attach
命令(如果从这个 stdin 中 exit,会导致容器的停止)。或 docker exec
命令,推荐大家使用 docker exec
命令
exec 命令
docker exec
后边可以跟多个参数,这里主要说明 -i -t
参数。
只用 -i
参数时,由于没有分配伪终端,界面没有我们熟悉的 Linux
命令提示符,但命令执行结果仍然可以返回。
当 -i -t
参数一起使用时,则可以看到我们熟悉的 Linux
命令提示符。
数据卷(容器数据管理)
在之前的nginx案例中,修改nginx的html页面时,需要进入nginx内部。并且因为没有编辑器,修改文件也很麻烦。
这就是因为容器与数据(容器内文件)耦合带来的后果。
要解决这个问题,必须将数据与容器解耦,这就要用到数据卷了。
什么是数据卷
数据卷(volume) 是一个虚拟目录,指向宿主机文件系统中的某个目录。
一旦完成数据卷挂载,对容器的一切操作都会作用在数据卷对应的宿主机目录了。
这样,我们操作宿主机的/var/lib/docker/volumes/html目录,就等于操作容器内的/usr/share/nginx/html目录了
数据集操作命令
数据卷操作的基本语法如下:
docker volume [COMMAND]
docker volume命令是数据卷操作,根据命令后跟随的command来确定下一步的操作:
- create 创建一个volume
- inspect 显示一个或多个volume的信息
- ls 列出所有的volume
- prune 删除未使用的volume
- rm 删除一个或多个指定的volume
创建和查看数据卷
需求:创建一个数据卷,并查看数据卷在宿主机的目录位置
① 创建数据卷
docker volume create html
② 查看所有数据
docker volume ls
结果:
③ 查看数据卷详细信息卷
docker volume inspect html
结果:
可以看到,我们创建的html这个数据卷关联的宿主机目录为/var/lib/docker/volumes/html/_data
目录。
小结:
数据卷的作用:
- 将容器与数据分离,解耦合,方便操作容器内数据,保证数据安全
数据卷操作:
- docker volume create:创建数据卷
- docker volume ls:查看所有数据卷
- docker volume inspect:查看数据卷详细信息,包括关联的宿主机目录位置
- docker volume rm:删除指定数据卷
- docker volume prune:删除所有未使用的数据卷
挂载数据卷
我们在创建容器时,可以通过 -v
参数来挂载一个数据卷或者宿主机目录到某个容器内目录,命令格式如下:
docker run \
--name myshop -d \
-v /usr/local/docker/tomcat/ROOT:/usr/local/tomcat/webapps/ROOT \
-p 8081:8080 \
myshop \
docker run 启动一个容器
-p(端口映射) 宿主机端口:容器默认端口
--name 容器名字
-d 以守护状态运行在后台运行容器
-v 数据卷路径:挂载的路径
这里的-v就是挂载数据卷的命令:
-
-v html:/root/htm
:把html数据卷挂载到容器内的/root/html这个目录中
案例-给MySQL挂载本地目录
容器不仅仅可以挂载数据卷,也可以直接挂载到宿主机目录上。 关联关系如下:
- 带数据卷模式:宿主机目录 --> 数据卷 —> 容器内目录
- 直接挂载模式:宿主机目录 —> 容器内目录
如图:
语法:
目录挂载与数据卷挂载的语法是类似的:
- -v [宿主机目录]:[容器内目录]
- -v [宿主机文件]:[容器内文件]
需求:创建并运行一个MySQL容器,将宿主机目录直接挂载到容器
实现思路如下:
1)在将课前资料中的mysql.tar文件上传到虚拟机,通过load命令加载为镜像
2)创建目录/tmp/mysql/data
3)创建目录/tmp/mysql/conf,将课前资料提供的hmy.cnf文件上传到/tmp/mysql/conf
4)去DockerHub查阅资料,创建并运行MySQL容器,要求:
① 挂载/tmp/mysql/data到mysql容器内数据存储目录
② 挂载/tmp/mysql/conf/hmy.cnf到mysql容器的配置文件
③ 设置MySQL密码
小结
docker run的命令中通过 -v 参数挂载文件或目录到容器中:
- -v volume名称:容器内目录
- -v 宿主机文件:容器内文
- -v 宿主机目录:容器内目录
数据卷挂载与目录直接挂载的
- 数据卷挂载耦合度低,由docker来管理目录,但是目录较深,不好找
- 目录挂载耦合度高,需要我们自己管理目录,不过目录容易寻找查看
Docker 构建 Tomcat
查找 Docker Hub 上的 Tom
docker search tomcat
这里我们拉取官方的镜像
docker pull tomcat
运行容器
docker run -p 8081:8080 --name tomcat2 -v /usr/local/docker/tomcat/ROOT/:/usr/local/tomcat/webapps/ROOT/ tomcat
命令说明:
-p 8080:8080:将容器的8080端口映射到主机的8080端口
-v /usr/local/docker/tomcat/ROOT/:/usr/local/tomcat/webapps/ROOT/:将主机中当前目录下的ROOT挂载到容器的/ROOT
容器没有则自动创建
查看容器启动情况
docker ps
通过浏览器访问
主机地址:端口号
docker logs 容器id 查看日志
docker logs -f 容器id 监听日志
Docker 构建 MySQL
查找 Docker Hub 上的 MySQL 镜像
docker search mysql
这里我们拉取官方的镜像
docker pull mysql:5.7.22
运行容器:
docker run -p 3306:3306 --name mysql \
-v /usr/local/docker/mysql/conf:/etc/mysql \
-v /usr/local/docker/mysql/logs:/var/log/mysql \
-v /usr/local/docker/mysql/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
-d mysql:5.7.22
命令参数:
-p 3306:3306:将容器的3306端口映射到主机的3306端口
-v /usr/local/docker/mysql/conf:/etc/mysql:将主机当前目录下的 conf 挂载到容器的 /etc/mysql(直接使用这个命令因为数据卷里面没有东西,所以挂载mysql容器conf目录下也为空)
-v /usr/local/docker/mysql/logs:/var/log/mysql:将主机当前目录下的 logs 目录挂载到容器的 /var/log/mysql
-v /usr/local/docker/mysql/data:/var/lib/mysql:将主机当前目录下的 data 目录挂载到容器的 /var/lib/mysql
-e MYSQL\_ROOT\_PASSWORD=123456:初始化root用户的密码
查看容器启动情况
docker ps
使用客户端工具连接 MySQL
注意!!!:实现配置挂载时,本地配置目录如果是空目录,那么挂载的时候,mysql容器内的配置目录也会为空(因为挂载了,会同步,同步会将容器内的配置目录也为空),这个时候,容器就无法成功运行。能启动成功,也会出现一系列问题,如连接数问题,无法区分表大小写问题,等
解决这个问题,我们只要保证本地配置目录不为空就行了。解决思路有2个 (我用的是第一个)
1、启动一个容器,将容器内的配置文件 /etc/mysql/my.cnf,copy到我们本地。利用 docker cp命令完成;
2、手动,自己本地新建一个my.cnf配置文件,手动将容器内配置文件 /etc/mysql/my.cnf内容 复制到本地的配置文件中;
复制容器文件到宿主机实现数据卷共用配置文件
不带数据卷的方式启动,查看容器内
/etc/mysql
的配置,复制到宿主机,删除服务,重新以数据卷的方式启动
docker run -p 3307:3306 --name mysql5.7.22 \
-e MYSQL_ROOT_PASSWORD=123456 \
-d mysql:5.7.22 \
mysql容器内的/etc/mysql
是存在文件的
exit
退出容器,复制容器/etc/mysql
目录下的文件到宿主机数据卷目录下
1、cd到宿主机数据卷目录/usr/local/docker/mysql/conf/
cd /usr/local/docker/mysql/conf/
2、复制容器配置到宿主机目录
docker cp 73df52bd37b1:/etc/mysql .
3、查看目录文件,cd到mysql目录
4、移动mysql目录下所有文件到上一级目录(配置文件数据卷挂载容器的目录)
mv *.* ..
5、删除mysql目录
rm -rf mysql
6、删除容器,换带数据卷的方式启动
7、重新启动容器,带数据卷的方式
docker run -p 3306:3306 --name mysql5.7 \
-v /usr/local/docker/mysql/conf:/etc/mysql \
-v /usr/local/docker/mysql/logs:/var/log/mysql \
-v /usr/local/docker/mysql/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
-d mysql:5.7.22;
导入文件过大的错误
原因:mysql
容器中 /etc/mysql/conf.d
路径下的文件mysqldump.cnf限制了初始的大小
[mysqldump]
quick
quote-names
max_allowed_packet = 16M
解决:在/etc/mysql/mysql.conf.d
路径文件mysqld.cnf
中添加内容max_allowed_packet = 128M
(这里我该大一点)
each max_allowed_packet= 128M>> mysqld.cnf
就可以导入成功了,重启mysql容器
docker restart mysql