最实用的 Docker 知识(一)

1. Docker 是什么

问题:

  • 分布式系统中,依赖的组件非常多,不同组件之间部署时往往会产生一些冲突
  • 在数百上千台服务中重复部署,环境不一定一致,会遇到各种问题

Docker 解决:

  • 将应用的Libs(函数库)、Deps(依赖)、配置与应用一起打包
  • 将每个应用放到一个隔离容器去运行,避免互相干扰

![image.png](https://img-blog.csdnimg.cn/img_convert/990ef0d09a015b769428ba8b44f8d5c5.png#averageHue=#f5e1dd&clientId=ubcad8047-46b2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=723&id=ufebe826f&margin=[object Object]&name=image.png&originHeight=976&originWidth=1415&originalType=binary&ratio=1&rotation=0&showTitle=false&size=216325&status=done&style=none&taskId=uaa3034f1-0fc1-4eb6-9b97-ee32ab4c274&title=&width=1048.148222192003)
这样打包好的应用包中,既包含应用本身,也保护应用所需要的Libs、Deps,无需再操作系统上安装这些,自然就不存在不同应用之间的兼容问题了
Docker如何解决大型项目依赖关系复杂,不同组件依赖的兼容性问题?

  • Docker允许开发中将应用、依赖、函数库、配置一起打包,形成可移植镜像
  • Docker应用运行在容器中,使用沙箱机制,相互隔离

Docker如何解决开发、测试、生产环境有差异的问题?

  • Docker镜像中包含完整运行环境,包括系统函数库,仅依赖系统的Linux内核,因此可以在任意Linux操作系统上运行

Docker是一个快速交付应用、运行应用的技术,具备下列优势:

  • 可以将程序及其依赖、运行环境一起打包为一个镜像,可以迁移到任意Linux操作系统
  • 运行时利用沙箱机制形成隔离容器,各个应用互不干扰
  • 启动、移除都可以通过一行命令完成,方便快捷

Docker 中的一些概念
镜像(Image):Docker将应用程序及其所需的依赖、函数库、环境、配置等文件打包在一起,称为镜像
容器(Container):镜像中的应用程序运行后形成的进程就是容器,只是Docker会给容器进程做隔离,对外不可见
![image.png](https://img-blog.csdnimg.cn/img_convert/a716b645180224a5074eddc1f9f79471.png#averageHue=#dce1d0&clientId=ubcad8047-46b2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=555&id=u534a8222&margin=[object Object]&name=image.png&originHeight=749&originWidth=1432&originalType=binary&ratio=1&rotation=0&showTitle=false&size=240925&status=done&style=none&taskId=u1e855c4b-cabf-4553-8ad6-526e0fe1eed&title=&width=1060.7408156741683)
DockerHub:DockerHub 是一个官方的 Docker 镜像的托管平台。这样的平台称为 Docker Registry
![image.png](https://img-blog.csdnimg.cn/img_convert/7de709821585b88b40aec2bc3b5c338c.png#averageHue=#fcf9f8&clientId=ubcad8047-46b2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=452&id=u30251f4b&margin=[object Object]&name=image.png&originHeight=610&originWidth=1459&originalType=binary&ratio=1&rotation=0&showTitle=false&size=117463&status=done&style=none&taskId=u9842b0af-aca3-48b8-869e-cce2b93986c&title=&width=1080.7408170870192)
Docker 是一个 CS 架构的程序,由两部分组成:

  • 服务端(server):Docker 守护进程,负责处理 Docker 指令,管理镜像、容器等
  • 客户端(client):通过命令或RestAPI向Docker服务端发送指令。可以在本地或远程向服务端发送指令

![image.png](https://img-blog.csdnimg.cn/img_convert/c6e8cfb8d0004ab071930f45affb287a.png#averageHue=#dbab52&clientId=ubcad8047-46b2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=428&id=uc46874c2&margin=[object Object]&name=image.png&originHeight=578&originWidth=1443&originalType=binary&ratio=1&rotation=0&showTitle=false&size=215211&status=done&style=none&taskId=uf9196356-0bda-4363-bac8-66187e97e89&title=&width=1068.8889643979223)

2. Docker 的基本操作

2.1 镜像操作

首先来看下镜像的名称组成

  • 镜名称一般分两部分组成:[repository]:[tag]
  • 在没有指定 tag 时,默认是 latest,代表最新版本的镜像

常见的镜像命令如下图所示:
![image.png](https://img-blog.csdnimg.cn/img_convert/e635208b2e26112ceb468a15ad24ab86.png#averageHue=#faf8f6&clientId=ubcad8047-46b2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=544&id=uc2aaf58d&margin=[object Object]&name=image.png&originHeight=735&originWidth=1475&originalType=binary&ratio=1&rotation=0&showTitle=false&size=183382&status=done&style=none&taskId=u6e53ce3b-1554-402a-8625-8ea749b1758&title=&width=1092.592669776116)

Tip:利用 docker xx --help 命令查看相关命令语法

2.2 容器操作

常见容器命令如下图所示:
![image.png](https://img-blog.csdnimg.cn/img_convert/4b5ba9e123ec5668bfe9209b0f5900f2.png#averageHue=#ac752a&clientId=ubcad8047-46b2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=519&id=u20a27398&margin=[object Object]&name=image.png&originHeight=700&originWidth=1455&originalType=binary&ratio=1&rotation=0&showTitle=false&size=150394&status=done&style=none&taskId=ue780ae7a-8323-48f0-9cea-ea6cc4aa9a1&title=&width=1077.777853914745)
容器保护三个状态:

  • 运行:进程正常运行
  • 暂停:进程暂停,CPU 不再运行,并不释放内存
  • 停止:进程终止,回收进程占用的内存、CPU 等资源

查看容器日志的命令:

  • docker logs
  • 添加 -f 参数可以持续查看日志

查看容器状态:

  • docker ps
  • docker ps -a 查看所有容器,包括已经停止的

案例:创建并运行一个容器
创建并运行nginx容器的命令:

docker run --name mn1 -p 80:80 -d nginx

命令解读:

  • docker run :创建并运行一个容器
  • –name : 给容器起一个名字,比如叫做mn1
  • -p :将宿主机端口与容器端口映射,冒号左侧是宿主机端口,右侧是容器端口
  • -d:后台运行容器
  • nginx:镜像名称,例如nginx

我们还可以进入容器进入修改相关文件(了解)

docker exec -it mn1 bash

命令解读:

  • docker exec :进入容器内部,执行一个命令
  • -it : 给当前进入的容器创建一个标准输入、输出终端,允许我们与容器交互
  • mn1 :要进入的容器的名称
  • bash:进入容器后执行的命令,bash是一个linux终端交互命令

2.3 数据卷(容器数据管理)

在之前的 nginx 案例中,修改 nginx 的 html 页面时,需要进入 nginx 内部。并且因为没有编辑器,修改文件也很麻烦。这就是因为容器与数据(容器内文件)耦合带来的后果
![image.png](https://img-blog.csdnimg.cn/img_convert/d227f2c1b547a70ab3fc97784aec2818.png#averageHue=#f6f4f4&clientId=ubcad8047-46b2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=379&id=ud1b8c7da&margin=[object Object]&name=image.png&originHeight=512&originWidth=1464&originalType=binary&ratio=1&rotation=0&showTitle=false&size=142135&status=done&style=none&taskId=ud327c33a-3903-4879-9bc6-bb1c4e19659&title=&width=1084.444521052362)
要解决这个问题,必须将数据与容器解耦,这就要用到数据卷了
实现数据与容器分离的方式:

  • 绑定宿主机上的文件夹: 此文件夹自定义
  • 绑定数据卷: 此文件夹由 docker 指定
  • 绑定宿主机上的文件: 自定义文件的位置

![image.png](https://img-blog.csdnimg.cn/img_convert/ad0eee7cc8e779be4da2a2765b3e76fb.png#averageHue=#fefcfb&clientId=ubcad8047-46b2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=583&id=u115b2b4f&margin=[object Object]&name=image.png&originHeight=787&originWidth=1464&originalType=binary&ratio=1&rotation=0&showTitle=false&size=294880&status=done&style=none&taskId=uf9411b94-54c6-47d2-9e8d-8201abd20e7&title=&width=1084.444521052362)
接下来我们使用三种方式进行使用:
绑定文件夹:
我们首先在我们的宿主机下创建一个文件,此时该文件下为空目录,如下所示:
![image.png](https://img-blog.csdnimg.cn/img_convert/e2a6c15e95ac24d31dae3036c84b7197.png#averageHue=#1b1a19&clientId=ubcad8047-46b2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=85&id=u39397ba8&margin=[object Object]&name=image.png&originHeight=115&originWidth=914&originalType=binary&ratio=1&rotation=0&showTitle=false&size=8249&status=done&style=none&taskId=u3a339474-d435-45c5-9544-1dfbe4180d8&title=&width=677.0370848646577)
然后我们使用绑定文件夹的方式运行容器,如下所示:

docker run \
--name mn1 \
-d \
-p 82:80 \
-v /tmp/nginx/html:/usr/share/nginx/html \
nginx

然后我们访问,如下所示:
![image.png](https://img-blog.csdnimg.cn/img_convert/153decda8eecdfb6a20596d050bcad31.png#averageHue=#fbfaf9&clientId=ubcad8047-46b2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=231&id=ucc0b7902&margin=[object Object]&name=image.png&originHeight=312&originWidth=1830&originalType=binary&ratio=1&rotation=0&showTitle=false&size=37360&status=done&style=none&taskId=u41514df9-2855-450e-a7c4-79f08fa4b7d&title=&width=1355.5556513154525)
此时我们发现 403 Forbidden ,WHY???
这是因为我们使用绑定文件夹方式的话,数据是以宿主机的文件夹内容为主
当我们在宿主机下添加一个 index.html 后,我们再次进行访问,如下所示
![image.png](https://img-blog.csdnimg.cn/img_convert/a33cd2b17a1afbcbadfa3a99731f9a42.png#averageHue=#060504&clientId=ubcad8047-46b2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=56&id=ubf20e586&margin=[object Object]&name=image.png&originHeight=76&originWidth=884&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4995&status=done&style=none&taskId=u2f667552-05eb-49e6-89a8-c1a2270a75d&title=&width=654.8148610726012)
![image.png](https://img-blog.csdnimg.cn/img_convert/17cbd82c136aade7d276bdc4054d2e7e.png#averageHue=#f6f6f5&clientId=ubcad8047-46b2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=188&id=u397a7f5a&margin=[object Object]&name=image.png&originHeight=254&originWidth=1519&originalType=binary&ratio=1&rotation=0&showTitle=false&size=24718&status=done&style=none&taskId=u662cb60d-8c7c-4784-a209-2b0799debdd&title=&width=1125.1852646711325)
绑定文件:
绑定文件的方式与绑定文件夹的方式类似,不过我们在编写命令时将对应的文件夹改为文件,如下所示:

docker run \
--name mn1 \
-d \
-p 83:80 \
-v /tmp/nginx/html/index.html:/usr/share/nginx/html/index.html \
nginx

这里依然是以宿主机的文件内容为主
绑定数据卷:
**数据卷(volume)**是一个虚拟目录,指向宿主机文件系统中的某个目录
![image.png](https://img-blog.csdnimg.cn/img_convert/fc77ecb745ae23dbb909170c69a72ece.png#averageHue=#f8ceaa&clientId=ubcad8047-46b2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=502&id=u81026b9e&margin=[object Object]&name=image.png&originHeight=678&originWidth=1430&originalType=binary&ratio=1&rotation=0&showTitle=false&size=179819&status=done&style=none&taskId=u272b4ada-4b7f-4512-9f93-e80fae81c3a&title=&width=1059.2593340880312)
一旦完成数据卷挂载,对容器的一切操作都会作用在数据卷对应的宿主机目录了
这样,我们操作宿主机的 /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

**案例:**给 nginx 挂载数据卷

  1. 创建数据卷
docker volume create html
  1. 查看所有数据卷
docker volume ls

![image.png](https://img-blog.csdnimg.cn/img_convert/33e1ad914da631ffd67f0301d37bf2fa.png#averageHue=#090806&clientId=ubcad8047-46b2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=88&id=udaea2eed&margin=[object Object]&name=image.png&originHeight=119&originWidth=1073&originalType=binary&ratio=1&rotation=0&showTitle=false&size=10333&status=done&style=none&taskId=u9f1e158f-b628-4644-95c6-3ffd49693e3&title=&width=794.8148709625577)

  1. 查看数据卷详细信息
docker volume inspect html

![image.png](https://img-blog.csdnimg.cn/img_convert/5b55494324b4dca7df8894cb08c09052.png#averageHue=#040303&clientId=ubcad8047-46b2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=216&id=u01c371d6&margin=[object Object]&name=image.png&originHeight=292&originWidth=1072&originalType=binary&ratio=1&rotation=0&showTitle=false&size=25555&status=done&style=none&taskId=ubc101a6a-2786-427c-beff-55e84eb2bf7&title=&width=794.0741301694891)
我们创建的 html 这个数据卷关联的宿主机目录为/var/lib/docker/volumes/html/_data目录
并且此时该文件下的内容为空
![image.png](https://img-blog.csdnimg.cn/img_convert/646341fafa08a9acc043c18aba2c4732.png#averageHue=#090706&clientId=ubcad8047-46b2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=134&id=u6d5ce115&margin=[object Object]&name=image.png&originHeight=181&originWidth=1002&originalType=binary&ratio=1&rotation=0&showTitle=false&size=16656&status=done&style=none&taskId=u1882eab5-a5ef-4ec2-b421-22022ce7315&title=&width=742.2222746546904)

  1. 挂载数据卷
docker run \
--name mn2 \
-d \
-p 83:80 \
-v html:/usr/share/nginx/html
nginx

然后我们访问即可,此时数据是以容器内的为主,如下所示:
![image.png](https://img-blog.csdnimg.cn/img_convert/c35ef7f8dad82f0bd33936dd0fa23cff.png#averageHue=#f8f8f7&clientId=ubcad8047-46b2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=352&id=ub6541601&margin=[object Object]&name=image.png&originHeight=475&originWidth=1696&originalType=binary&ratio=1&rotation=0&showTitle=false&size=73914&status=done&style=none&taskId=udab31fa8-c381-46e8-9dfd-039348e8d58&title=&width=1256.2963850442663)
然后我们再查看数据卷目录,如下所示,会发现多了 index.html 和 50x.html 两个文件
![image.png](https://img-blog.csdnimg.cn/img_convert/e97004e39633754f177b5c289fdfa347.png#averageHue=#050403&clientId=ubcad8047-46b2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=73&id=u08c43abb&margin=[object Object]&name=image.png&originHeight=98&originWidth=1021&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5769&status=done&style=none&taskId=u4f2fc6c7-8dbf-4bf8-93b8-0763621e1b0&title=&width=756.2963497229929)

  1. 修改宿主机下的 index.html 文件内容,然后再次访问

![image.png](https://img-blog.csdnimg.cn/img_convert/e7078c8d4abe6d4caa97e0f5936471b9.png#averageHue=#f7f7f6&clientId=ubcad8047-46b2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=363&id=u1a83c4ab&margin=[object Object]&name=image.png&originHeight=490&originWidth=1992&originalType=binary&ratio=1&rotation=0&showTitle=false&size=80097&status=done&style=none&taskId=u13ea87ee-3752-4f1a-8643-2721f28c230&title=&width=1475.5556597925581)
通过上述案例可以实现了容器与数据分离
总结:
docker run 的命令中通过 -v 参数挂载文件或目录到容器中:

  • -v volume名称:容器内目录
  • -v 宿主机文件:容器内文
  • -v 宿主机目录:容器内目录

数据卷挂载与目录直接挂载的

  • 数据卷挂载耦合度低,由 docker 来管理目录,但是目录较深,不好找
  • 目录挂载耦合度高,需要我们自己管理目录,不过目录容易寻找查看

3. Docker 自定义镜像

3.1 镜像结构

镜像是将应用程序及其需要的系统函数库、环境、配置、依赖打包而成。
我们以 MySQL 为例,来看看镜像的组成结构:
![image.png](https://img-blog.csdnimg.cn/img_convert/fae371eb3e874da12e8cff91b11b600e.png#averageHue=#c7ae8d&clientId=u8c786143-1287-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=616&id=u8290ca61&margin=[object Object]&name=image.png&originHeight=832&originWidth=1456&originalType=binary&ratio=1&rotation=0&showTitle=false&size=321532&status=done&style=none&taskId=u42c69d0e-5ce5-43e0-be2a-758a41613bb&title=&width=1078.5185947078137)
简单来说,镜像就是在系统函数库、运行环境基础上,添加应用程序文件、配置文件、依赖文件等组合,然后编写好启动脚本打包在一起形成的文件
我们要构建镜像,其实就是实现上述打包的过程

3.2 Dockerfile 语法

构建自定义的镜像时,并不需要一个个文件去拷贝,打包。
我们只需要告诉 Docker,我们的镜像的组成,需要哪些 BaseImage、需要拷贝什么文件、需要安装什么依赖、启动脚本是什么,将来 Docker 会帮助我们构建镜像。
而描述上述信息的文件就是Dockerfile文件。
Dockerfile 就是一个文本文件,其中包含一个个的指令(Instruction),用指令来说明要执行什么操作来构建镜像。每一个指令都会形成一层Layer。
这里给出一些常见的的指令,更多指令可去官网查看
![image.png](https://img-blog.csdnimg.cn/img_convert/ca7496c991260928da17d8c134590fc1.png#averageHue=#d0bcb9&clientId=u8c786143-1287-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=414&id=u6f933ddd&margin=[object Object]&name=image.png&originHeight=559&originWidth=1428&originalType=binary&ratio=1&rotation=0&showTitle=false&size=170614&status=done&style=none&taskId=u87396d41-bf2d-45a8-8f64-63691ab2d74&title=&width=1057.7778525018941)
官网地址:https://docs.docker.com/engine/reference/builder

3.3 实战

3.3.1 基于 Ubuntu 构建 Java 项目

这里先给出我们的项目文件,如下所示:
![image.png](https://img-blog.csdnimg.cn/img_convert/57091b2b07df363873eb056eea39f968.png#averageHue=#fbfbfb&clientId=u8c786143-1287-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=204&id=uca05111a&margin=[object Object]&name=image.png&originHeight=275&originWidth=1454&originalType=binary&ratio=1&rotation=0&showTitle=false&size=24656&status=done&style=none&taskId=u09d28aed-0416-4eec-b01b-975385e5af5&title=&width=1077.0371131216764)
Dockerfile 文件如下所示:

# 指定基础镜像
FROM ubuntu:16.04
# 配置环境变量,JDK的安装目录
ENV JAVA_DIR=/usr/local

# 拷贝jdk和java项目的包
COPY ./jdk8.tar.gz $JAVA_DIR/
COPY ./docker-demo.jar /tmp/app.jar

# 安装JDK
RUN cd $JAVA_DIR \
 && tar -xf ./jdk8.tar.gz \
 && mv ./jdk1.8.0_144 ./java8

# 配置环境变量
ENV JAVA_HOME=$JAVA_DIR/java8
ENV PATH=$PATH:$JAVA_HOME/bin

# 暴露端口
EXPOSE 8090
# 入口,java项目的启动命令
ENTRYPOINT java -jar /tmp/app.jar

将上述文件上传到虚拟机目录下,然后进入 docker-demo 目录下,运行命令:

docker build -t javaweb:1.0 .

![image.png](https://img-blog.csdnimg.cn/img_convert/d412368d5ce67b559e65d99324782c30.png#averageHue=#0e0c0a&clientId=u8c786143-1287-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=124&id=u617f1fc7&margin=[object Object]&name=image.png&originHeight=168&originWidth=996&originalType=binary&ratio=1&rotation=0&showTitle=false&size=17830&status=done&style=none&taskId=u15a4fdec-7538-4ad8-9298-e16dcd0d1d8&title=&width=737.7778298962791)
可以发现我们的镜像多了两个,然后我们将该镜像运行即可,输入以下命令:

docker run \
--name javaweb1.0 \
-d \
-p 8090:8090 \
javaweb:1.0

![image.png](https://img-blog.csdnimg.cn/img_convert/adedd289f20fde476712450e580ef2a0.png#averageHue=#080605&clientId=u8c786143-1287-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=161&id=ua3509a28&margin=[object Object]&name=image.png&originHeight=218&originWidth=1541&originalType=binary&ratio=1&rotation=0&showTitle=false&size=22692&status=done&style=none&taskId=ue8dd9686-8d1a-4237-9bd3-48801b5392c&title=&width=1141.4815621186406)
然后访问 http://192.168.80.128:8090/hello/count 即可,访问成功

3.3.2 基于 java8 构建 Java 项目

此时我们的项目文件如下所示:
![image.png](https://img-blog.csdnimg.cn/img_convert/44a9370c1a74b3ce285ec8e9aea302c8.png#averageHue=#fbfafa&clientId=u8c786143-1287-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=170&id=u08c49df8&margin=[object Object]&name=image.png&originHeight=230&originWidth=1354&originalType=binary&ratio=1&rotation=0&showTitle=false&size=20000&status=done&style=none&taskId=u074b99b4-130a-42f1-846c-361fcbb588f&title=&width=1002.9630338148212)
我们的 Dockerfile 如下所示:

# 指定基础镜像
FROM java:8-alpine
# 拷贝 java 项目的包
COPY ./docker-demo.jar /tmp/app.jar
# 暴露端口
EXPOSE 8090
# 入口,java 项目的启动命令
ENTRYPOINT java -jar /tmp/app.jar

然后把文件拷贝到 虚拟机目录下,然后构建镜像
![image.png](https://img-blog.csdnimg.cn/img_convert/7a2fbe23006d1e00b3711945a7cc0e05.png#averageHue=#090706&clientId=u8c786143-1287-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=564&id=u9080f215&margin=[object Object]&name=image.png&originHeight=762&originWidth=1222&originalType=binary&ratio=1&rotation=0&showTitle=false&size=82534&status=done&style=none&taskId=u64680c41-bd0a-445c-83a5-776517e7243&title=&width=905.1852491297722)
然后我们将镜像运行即可,这种方式是我们常见的一种部署方式

更多知识在我的语雀知识库:https://www.yuque.com/ambition-bcpii/muziteng

你可能感兴趣的:(Java,docker,运维,容器)