导学:学习Docker使用的一些基础知识,为将来部署项目打下基础,具体用法可以参考Docker的官方文档:
Docker DocsHome page for Docker's documentationhttps://docs.docker.com/
1. 常见命令
Docker最常见的命令就是操作镜像、容器的命令,可以参考官方文档:
Use the Docker command line | Docker DocsDocker's CLI command description and usagehttps://docs.docker.com/engine/reference/commandline/cli/其中,比较常见的命令有:


- docker stop:停止的是容器内部的进程,但是容器还是在的!
- docker start:是启动已经停掉的容器进程!
用⼀副图来表示这些命令的关系:
补充:
默认情况下,每次重启虚拟机我们都需要手动启动Docker和Docker中的容器,通过命令可以实现开启自启:
- Nginx是一个Web服务器!
案例:查看DockerHub,拉取Nginx镜像,创建并运行Nginx容器
需求:
- 在DockerHub中搜索Nginx镜像,查看镜像的名称
- 拉取Nginx镜像
- 查看本地镜像列表
- 创建并运行Nginx容器
- 查看容器
- 停止容器
- 再次启动容器
- 进入Nginx容器
- 删除容器
1. 第一步,去DockerHub查看nginx镜像仓库及其相关信息
2. 第二步,拉取Nginx镜像:docker pull nginx
3. 查看本地镜像列表验证是否拉取成功:docker images
4. 保存Nginx镜像到本地压缩文件:docker save -o nginx.tar.gz nginx:latest
5. 删除Nginx本地镜像:docker rmi nginx:latest
6. 重新加载刚才保存到本地的Nginx镜像的压缩文件:docker load -i nginx.tar.gz
7. 第四步,创建并运行Nginx容器:docker run -d --name nginx -p 80:80 nginx
8. 第五步:docker ps查看正在运行的Docker容器的信息
- 容器ID是当前容器的唯一标识!
也可以加格式化访问,格式会更加清爽:
- docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"
9. 停止容器:docker stop nginx
10. 查看所有容器:docker ps -a
11. 再次启动Nginx容器:docker start nginx
12. 查看对应容器的日志:docker logs [ -f ] 容器名
- -f 选项表示持续跟踪/输出日志!
13. 查看容器的详细信息:docker inspect nginx,返回格式为JSON
14. 进入容器的内部,查看容器内目录,-it指添加一个可输入的终端,bash命令行,注意,想要进入容器内部首先要启动容器!
- docker exec -it nginx bash
- 或者可以进入MySQL:docker exec -it mysql mysql -uroot -p => MySQL客户端命令 或 docker exec -it mysql bash
- 输入exit退出
容器内部有自己独立的文件系统等:
15. 删除容器:docker rm 容器名,如果容器在运行中,那么会发现无法删除,可以强制删除容器:docker rm -f 容器名
- ~指的就是root目录!
2. 数据卷
容器是隔离环境,容器内程序的文件、配置、运行时产生的容器都在容器内部,我们要读写容器内的文件非常不方便,大家思考几个问题:
- 如果要升级MySQL版本,需要销毁旧容器,那么数据岂不是跟着被销毁了?
- MySQL、Nginx容器运行后,如果我要修改其中的某些配置该怎么办?
- 我想要让Nginx容器 部署/代理 我的静态资源怎么办?
因此,容器提供程序的运行环境,但是程序运行产生的数据、程序运行依赖的配置都应该与容器解耦!
什么是数据卷?
- 数据卷(volume)是一个虚拟目录(是一个逻辑的概念),是容器内目录与宿主机目录之间映射的桥梁,它将宿主机目录映射到容器内目录,方便我们操作容器内文件,或者方便迁移容器产生的数据。
以Nginx为例,我们知道Nginx中有两个关键的目录:
- html:放置一些静态资源
- conf:放置配置文件
如果我们要让Nginx代理我们的静态资源,最好是放到html目录,如果我们要修改Nginx的配置,最好是找到conf下的nginx.conf文件,但遗憾的是,容器运行的Nginx所有的文件都在容器内部,所以我们必须利用数据卷将两个目录与宿主机目录关联,方便我们操作,如图:
在上图中:
- 我们创建了两个数据卷:conf、html,此时Docker就会去自动创建数据卷对应的宿主机文件系统的真实的目录,每一个数据卷都跟宿主机上的一个真实的文件目录进行了一一对应!
- 需要让容器目录跟数据卷做挂载,让两者之间产生关联,Nginx容器内部的conf目录和html目录分别与两个数据卷关联。
- 而数据卷conf和html分别指向了宿主机的 /var/lib/docker/volumes/conf/_data目录和/var/lib/docker/volumes/html/_data目录
这样一来,容器内的conf和html目录就间接的与宿主机的conf和html目录关联起来(双向映射),我们称为挂载。
此时,我们操作宿主机的/var/lib/docker/volumes/html/_data就是在操作容器内的/usr/share/nginx/html/_data目录,只要我们将静态资源放入宿主机对应目录,就可以被Nginx代理了。
- Nginx容器html静态资源的目录:/usr/share/nginx/html
- 容器内部的文件没有vi / vim等命令!
数据库命令
数据卷的相关命令有:
- docker volume prune:删除未使用的数据卷
数据卷挂载 - 如何挂载数据库?
- 在执行docker run命令时,使用 -v 数据卷名称:容器内的挂载目录 可以完成数据卷挂载!
- 当创建容器时,如果挂载了数据卷且发现数据卷不存在,会自动创建数据卷。
注意:
- 容器与数据卷的挂载要在创建容器时配置,对于创建好的容器,是不能设置数据卷挂载的,而且创建容器的过程中,数据卷会自动创建。
- 数据卷一创建,宿主机目录就会跟着自动创建!
案例一:利用Nginx容器部署静态资源(Nginx的html目录挂载 - 实现容器目录与数据卷的挂载)
需求:
- 创建Nginx容器,修改nginx容器内的html目录下的index.html文件内容
- 将静态资源部署到nginx的html目录
首先,删除原先下载好的nginx镜像:
1. 首先创建容器并指定数据卷,注意通过 -v 参数来指定数据卷(做数据卷和容器目录的挂载)
- docker run -d --name nginx -p 80:80 -v html:/usr/share/nginx/html nginx
docker ps验证nginx是否安装成功:
2. 查看所有数据卷:docker volume ls
3. 查看html数据卷的详情
4. cd查看宿主机目录:/var/lib/docker/volumes/html/_data
- total - 总容量
index.html就是Nginx的欢迎界面!
5. 进入容器内部,查看/usr/share/nginx/html目录内的文件是否变化
- docker exec -it nginx bash
利用数据卷我们就实现了宿主机目录与容器内目录之间自动的双向映射!
案例2 - mysql容器的数据挂载
需求:
- 查看mysql容器,判断是否有数据卷挂载
- 基于宿主机目录实现MySQL数据目录、配置文件、初始化脚本的挂载(查阅官方镜像文档)
1. 查看MySQL容器的详细信息:docker inspect mysql
- Mounts:挂载
可以发现,其中有几个关键属性:
- Name:数据卷名称。这种由容器运行时自动创建的数据卷叫做匿名卷,这里就是匿名卷自动生成的名字,一串hash值。
- Source:宿主机目录
- Destination:容器内的目录 {/var/lib/mysql:MySQL的数据存储目录}
为什么MySQL要给它的数据存储目录挂载到宿主机呢?
-
出于数据解耦的考虑。
删除容器后它的数据卷依然还存在!
本地目录挂载:挂载本地目录或文件
- 可以发现,数据卷的目录结构较深,如果我们去操作数据卷目录会不太方便,在很多情况下,我们会直接将容器目录与宿主机目录挂载,挂载语法与数据卷类型:
在执行docker run命令时,使用 -v 本地目录:容器内目录 可以完成本地目录挂载!
注意:
- 本地目录或文件必须以 / 或 ./ 开头,如果直接以名字开头,会被识别为数据卷名而非本地目录名。
-v mysql:/var/lib/mysql 会被识别为一个数据卷叫mysql,运行时会自动创建这个数据卷
-v ./mysql:/var/lib/mysql 会被识别为当前目录下的mysql目录,运行时如果不存在会创建目录
删除并重新创建mysql容器,并完成本地目录挂载:
• 挂载 /root/mysql/data 到容器内的 /var/lib/mysql ⽬录
• 挂载 /root/mysql/init 到容器内的 /docker-entrypoint-initdb.d ⽬录(初始化的 SQL脚本⽬录)
• 挂载 /root/mysql/conf 到容器内的 /etc/mysql/conf.d ⽬录(这个是MySQL配置⽂件目录)
创建并运行新MySQL容器,挂载本地目录:
3. 自定义镜像
前面我们一直在使用别人准备好的镜像,那如果我要部署一个Java项目,把它打包为一个镜像该则怎么做呢?
镜像结构
要想自己构建镜像,必须先了解镜像的结构。
回顾:镜像就是包含了应用程序、程序运行的系统函数库、运行配置等文件的文件包!
镜像之所以能让我们快速跨操作系统部署应用而忽略其运行环境、配置,就是因为镜像中包含了程序运行需要的系统函数库、环境、配置、依赖等各种文件,这些文件分层打包而成。
因此,自定义镜像 / 构建镜像的本质就是依次准备好程序运行的基础环境、依赖、应用本身、运行配置等文件,并且打包而成。
举个例子,我们要从0部署一个Java应用,大概流程是这样:
- 准备一个Linux环境(CentOS或Ubuntu均可)
- 安装JDK并配置环境变量
- 上传Jar包
- 运行Jar包
因此,我们打包镜像也是分成这么几步,即构建一个Java镜像的步骤:
- 直接准备Linux的全部运行环境(Java项目并不需要完整的操作系统,仅仅是基础运行环境即可)
- 安装并配置JDK
- 拷贝Jar包
- 配置启动脚本:java -jar xx.jar,启动脚本我们也叫做入口(Entrypoint)
上述步骤中的每一次操作其实都是在生产一些文件(系统运行环境、函数库、配置最终都是磁盘文件),所以镜像就是一堆文件的集合!我们的Docker镜像是有很多的压缩包合并而成的!
但需要注意的是,镜像文件不是随意堆放的,而是按照操作的步骤分层叠加而成,每一层形成的文件都会单独打包并标记一个唯一ID,称为Layer - 层。
Docker为什么要把这些文件分层去打包呢?
- 这样,如果我们构建时用到的某些层其他人已经制作过,就可以直接拷贝使用这些层,而不用重复制作 => 可以共享某些基础镜像(BaseImage)。
- 例如,第一步中需要的Linux运行环境,通用性就很强,所以Docker官方就制作了这样的只包含Linux运行环境的镜像,因此,我们在制作Java镜像时,就无需重复制作,直接使用Docker官方提供的CentOS或Ubuntu镜像作为基础镜像,然后再搭建其它层即可,这样逐层搭建,最终整个Java项目的镜像结构如图所示:
4. Dockerfile
- 由于制作镜像的过程中,需要逐层处理和打包,比较复杂,所以Docker就提供了自动打包镜像的功能,我们只需要将打包的过程,每一层要做的事情用固定的语法写下来,交给Docker去执行即可,而这种记录镜像结构的文件就成为Dockerfile,其对应的语法可以参考官方文档:https://docs.docker.com/engine/reference/builder/
Dockerfile就是利用固定的指令来描述镜像的结构和构建过程,这样Docker才可以依次来构建镜像!
Dockerfile就是一个文本文件,其中包含一个个的指令(Instruction),用指令来说明要执行什么操作来构建镜像,将来Docker可以根据Dockerfile帮我们构建镜像,常见指令如下:
思考:
- 以后我们会有很多的Java项目需要打包为镜像,他们都需要Linux系统环境、JDK环境这两层,如果每层制作Java镜像都重复制作前两层镜像,岂不是很麻烦,所以,就有人提供了基础的系统加JDK环境,我们在此基础上制作Java镜像,就可以省去JDK的配置了:
我们可以基于Ubuntu基础镜像,利用Dockerfile描述镜像结构,也可以直接基于JDK为基础镜像,省略前面的步骤:
构建镜像
当Dockerfile文件写好以后,就可以利用下面命令来构建镜像了:
- docker build -t repository(镜像名):tag(Dockerfile目录) .
docker build:就是构建一个Docker镜像
-t:-t参数是指定镜像的名称,是给镜像起名,格式依然是repository:tag的格式,不知道tag时,默认为latest,代表是最新的版本
.:最后的点是指构建时Dockfile所在路径,是指定Dockfile所在目录,如果就在当前目录,则指定为 ".",也可以直接指定Dockerfile目录
查看镜像列表:docker images
尝试运行该镜像:
- docker run -d --name docker-demo -p 8080:8080 docker-demo
查看容器:docker ps -a
查看日志:docker logs -f docker-demo
reset:重置
5. 网络 - 容器网络互联
刚才我们创建了一个Java项目的容器,而Java项目往往需要访问其它各种中间件,例如MySQL、Redis等,而容器是有各自独立的隔离空间,现在,我们的容器之间能否互相访问呢?
我们来测试一下,首先,我们查看下MySQL容器的详细信息,重点关注其中的网络IP地址:
1. 用基本命令docker inspect mysql,寻找Networks.bridge.IPAddress属性
2. 通过命令docker exec -it docker-demo bash进入docker-demo容器
在容器内,通过ping命令测试网络
发现容器间可以进行网络互联,没有问题! (IP地址如果在一个网段当中,则说明是可以ping通的!)
不是说容器之间的环境是相互隔离的,怎么还又在一个网段当中呢?
- 默认情况下,所有容器都是以bridage方式连接到Docker的一个虚拟网桥上!
但是,容器的网络IP其实是一个虚拟的IP,其值并不固定与某一个容器绑定,如果我们在开发时写死某个IP,而在部署时很有可能MySQL容器的IP会发生变化,连接会失败。
所以,我们必须借助于docker的网络功能来解决这个问题,官方文档:
docker network | Docker Docshttps://docs.docker.com/engine/reference/commandline/network/加入自定义网络的容器才可以通过容器名互相访问(IP地址可以变,容器名总归是不会变的)!
Docker的网络操作的常见命令有:
查看所有网络:docker network ls
通过命令创建一个自己的网络:docker network create Melo
让docker-demo和mysql都加入该网络,这样该网络内的其它容器都可以用别名互相访问,注意,在加入网络时可以通过--alias给容器起别名:docker network connect 网络 要加入的容器
进入Java项目docker-demo容器,然后ping mysql:
OK,现在就无需记住IP地址也可以实现容器互联了!
总结:
- 在自定义网络中,可以给容器起多个别名,默认的别名是容器名本身
- 在同一个自定义网络中的容器,可以通过容器名互相访问