Docker
Docker 是基于 Google 公司推出的 GoLang 语言开发的开源项目,基于 Linux 内核的 Cgroups 和 Namespace 以及 UnionFS 等技术,对进程进行封装隔离,属于操作系统层面的虚拟化技术
利用Docker技术,可以保证开发、测试、生产环境的一致性,避免环境差异导致环境的问题
图例
Docker 引擎

Docker 组成

核心组件
- image 镜像 - 应用程序所需的环境打包为镜像
- container 容器 - 镜像运行后的实例
- 镜像仓库 - 存放镜像的仓库,作用好比Github
- Dockerfile - 将你部署项目的操作写成一个部署脚本,且该脚本还能构建出镜像文件
容器镜像
可以把 Docker 当成是 Maven 仓库,镜像可以理解成是 Maven 的依赖包,当需要使用某些镜像的时候如果本地没有,那么就会前往中央仓库下载,然后安装到本地,下次使用直接使用本地的镜像
拉取远程镜像命令,官方示例
# 拉取
docker pull hello-world
# 运行
docker run hello-world

一个镜像的名称是由两部分组成的,一个是repository,还有一个是tag,一般情况下约定repository就是镜像名称,tag作为版本,默认为latest,表示最新版本。所以指定版本运行的话使用下面命令,tag可以省略(默认最新版)
docker pull 名称:版本
把镜像拉取到本地运行之后会自动创建容器,可以使用 ps 命令查看所有的 Docker 容器
# 查看所有的容器
docker ps -a
# 查看运行中的容器
docker ps
如果是仅仅创建容器不进行启动的话,可以使用以下命令来进行创建容器
docker create hello-world
一个镜像可以创建多个容器,每个容器都有一个随机的容器ID,后面是容器的创建时间以及当前的运行状态,最后一列是容器的名称,在创建容器时,名称如果没有指定会自动生成,可以在使用 run 命令时使用 --name 参数进行手动指定名称启动
docker run --name=hello hello-world
开启/关闭容器
使用上面的 ps 命令可以查看到全部的容器,可以使用 docker start 和 docker stop 命令来开启和关闭容器
开启容器:
docker start [容器名称/容器ID]
关闭容器:
docker stop [容器名称/容器ID]
重启容器:
docker restart [容器名称/容器ID]
不可以直接使用镜像名称来进行开启或者关闭操作,需要使用容器名称或者容器ID
删除容器和镜像
删除容器:
docker rm [容器名称/容器ID]
不可使删除正在运行中的容器,需要先停止容器,然后再删除容器,如果希望在容器停止运行后自动删除可以使用以下命令
docker run --rm 镜像名称
所有的数据都是保存在容器中,所以删除容器的时候,容器中的数据也会被删除。
删除镜像:
docker rmi 镜像名称
可以使用以下命令来查看全部的镜像
docker images
容器的网络管理
Docker 在安装的时候会在主机创建三个网络,使用以下命令查看
docker network ls
默认情况下有bridge
、host
、none
这三种网络类型
- none:相当于内部容器进行断网,这让容器是无法连接到互联网的
- bridge(默认):容器默认使用类型,这是桥接网络
- host:当容器使用host网络时,会共享宿主主机的网络,并且跟宿主主机的网络配置一样(使用这种模式也就不需要做什么端口映射等操作)
创建容器的时候使用 network
进行指定使用的网络
docker run --network=[网络名] [镜像]
自定义网络
除了默认的三种网络之外,还可以自定义网络,让容器连接到这个网络
Docker默认提供三种网络驱动:bridge
、overlay
、macvlan
,不同的驱动对应着不同的网络设备驱动,实现的功能也不一样
使用 network 创建自定义网络(创建容器时添加 --network=[网络名]
使用新规则)
docker network create --driver bridge test
使用network inspect
命令来查看网络配置信息
docker network inspect test
不同的网络是相互隔离的,无法进行通信
可以在创建的时候就指定相同的自定义网络,如果容器已经创建完成可以使用
network connect
进行连接到指定的网络docker network connect [网络名] [容器名称/容器ID]
容器之间的通信
我们可以直接通过容器的IP地址在容器间进行通信,只要保证两个容器处于同一个网络下即可,非常方便
缺点:在大部分情况下,容器部署之后的IP地址是自动分配的(也可以使用--ip
来手动指定)
Docker DNS服务器
Docker 提供了类似于DNS服务器的套路,域名解析IP,这里的域名就是指容器的名称
docker run -it --name=netTest-3 ubuntu

使用这种方式只有在自定义网络的情况下才能使用
还有一种方式是让两个容器共享同一个网络,两个容器共享同一个IP地址,这样也可以达到通信的结果
docker run -it --name=netTest-2 --network=container:[共享的容器] ubuntu-net
容器持久化存储
在容器中所作的操作都是保存到最顶层的,这样不好处就是一旦删除容器在进行重装的时候会非常难受,在这种情况下可以使用数据卷(Data Volume)
在宿主机中创建一个文件夹,其中创建一个文件写入些许内容,然后保存,接着就可以将宿主主机上的目录或文件挂载到容器的某个目录上
docker run -it -v ~/docker/resource:/root/docker/resource ubuntu-vim
在使用
-v
参数时不指定宿主主机上的目录进行挂载的话,那么就由Docker来自动创建一个目录,并且会将容器中对应路径下的内容拷贝到这个自动创建的目录中,最后挂在到容器中,同时这个数据卷就被 Docker 进行管理了,即使是删除对应的容器数据卷也不会消失# 查看被Docker管理的数据卷 docker volume ls # 删除对应的数据卷 docker volume rm [VOLUME NAME] # 查看数据卷信息 docker volume inspect [VOLUME NAME]
容器数据之间的数据共享
通过上面持久化的形式进行数据共享,还有一种方式是通过创建一个容器,这个容器上挂载了主机上的文件,然后其他容器创建的时候通过 --volumes-from
指定即可
# 创建一个挂载容器
docker run -it -v ~/docker/resource:/root/docker/resource --name=test ubuntu-vim
# 创建一个容器指定 test 容器目录
docker run -it --volumes-from test ubuntu-vim
也可以通过 volume
创建一个数据卷然后再创建的时候指定一下挂载目录,两个容器的数据卷相同的情况下,也可以达到数据共享的实现
docker volume create [源名称]

唯一不好的一点就是被挂载的目录是固定的,并不能自定义。然后进行挂载
docker run -it -v test:/root/test --name=t1 ubuntu-vim
docker run -it -v test:/root/test --name=t2 ubuntu-vim
以上的两个容器的 /root/test
中数据会被相互共享
常用命令
日志
打印容器中的日志信息(如果想要持续打印加入 -f
属性)
docker logs [容器名称/容器ID]
进入容器
进入正在运行中的容器内,如果使用 exit
或者 Ctrl + C
退出,容器会停止运行,如果想退出容器但不想容器停止,则按住 Ctrl + P + Q
退出
docker attach [容器名称/容器ID]
也可以使用 exec
命令在容器中启动一个新的终端或是在容器中执行命令
docker exec -it [容器名称/容器ID] bash
也可以仅仅在容器中执行一条命令
docker exec [容器名称/容器ID] [指令]
终止容器
之前使用 stop
进行终止容器,这种方式是等待善后工作完成在进行终止,下面是强制停止容器
docker kill [容器名称/容器ID]
暂停运行
容器暂时停止运行单不是真的停止了运行,在暂停运行后无法处理工作
# 暂停运行
docker pause [容器名称/容器ID]
# 继续运行
docker unpause [容器名称/容器ID]
监控
docker status
使用以上命令可以查看所有容器所占用的资源,可以使用以下命令查看某个容器中的进程
docker top [容器名称/容器ID]
资源管理
内存
对于容器所使用的硬件资源是可以手动指定的
docker run -m [内存限制] --memory-swap=[内存和交换分区总共的内存限制] [镜像名称]
其中-m
参数是对容器的物理内存的使用限制,而--memory-swap
是对内存和交换分区总和的限制,它们默认都是-1
,也就是说没有任何的限制(如果在一开始仅指定-m
参数,那么交换内存的限制与其保持一致,内存+交换等于-m
的两倍大小)默认情况下跟宿主主机一样
CPU
默认情况下所有容器使用的CPU资源数是一样的,可以通过调整不同的容器的CPU权重(默认为1024)来按需分配资源,这里需要使用到-c
选项,也可以输入全名--cpu-share
docker run -c [分配数] [镜像]
限制在指定的CPU上运行 --cpuset-cpus
,使用多个以逗号分开
docker run -it --cpuset-cpus=[CPU数] [镜像]
还有其余一些 --cpus
、--cpu-period
、 --cpu-quota
可以百度详细信息
系统镜像
所谓的镜像其实就是一个封装的操作系统,这个系统中有很多依赖如:JDK、MYSQL、Nginx等等内容,没有封装过的基本的系统镜像被称为 Base 镜像,项目就是基于 Base 镜像进行打包的(也可以不要,如之前的 hello-world 就是基于当前的系统执行)
相较于正常的系统,这些镜像去掉了系统内核,使用的内核是宿主机的内核,也就是说,你当前的系统内核版本是多少,这些系统镜像的内核版本就是多少(可以使用 uname -r
查看内核版本)
这些 Base 镜像基本都是由 Linux 的发行版本进行封装,如:Ubuntu、CentOS、Kali等
下载 Base 镜像:
docker pull ubuntu
启动镜像
Docker 会对没有操作的容器进行关闭,如果直接启动系统镜像会瞬间关闭(因为它没有事情做,开着也是浪费资源)对于这种情况可以加上 -it
属性
docker run -it ubuntu
-i
表示在容器上打开一个标准的输入接口,-t
表示分配一个伪tty设备,可以支持终端登录,一般这两个是一起使用,否则base容器启动后就自动停止了,退出时输入exit
当想重新启动的时候可以使用 start
进行重新启动,一定要加上 -i
否则无法进入镜像终端
docker start -i [容器名称/容器ID]
软件结构
在 Base 镜像中安装的软件是分层的,Base 镜像为底安装的软件会一层一层的叠加上去,封顶为容器层。如果不同层中存在相同位置的文件,那么上层的会覆盖掉下层的文件,最终看到的是一个叠加之后的文件系统。当需要修改容器中的文件时,不会直接对镜像中的文件镜像进行修改,修改的是容器层,不会影响到下面的镜像
结构示例 |
---|
容器层 |
...... |
软件二(MySql) |
软件一(Nginx) |
Base 地基 |
分层的好处:拿 Base 镜像举例,现在有两个镜像,底层的 Base 镜像是同一个,那么不采用分层结构的话这个 Base 镜像会被安装两遍造成无意义的资源浪费。但是采用分层的话 Base 镜像只需要安装一遍即可,这个 Base 镜像会被共享使用
既然是被共享使用,那这个镜像的文件就不能够被改变,否则就失去了通用性,想要进行改变而不失去通用性就需要使用封顶的容器层
可以使用 history
命令查看镜像层等
docker history 镜像名称
构建镜像
首先拉取一下 Base 镜像
docker pull ubuntu
commit(不推荐)
运行镜像
docker run -it ubuntu
更新一下 apt
apt update
安装一下JDK
apt install openjdk-17-jdk
先退出 Base 系统镜像,然后使用 commit 构建
docker commit [容器名称/容器ID] 新的镜像名称
按照上面的格式进行一个构建操作

优点:自定义程度很高
缺点:构建效率太低,作为使用者来说,构建情况不透明有安全风险
dockerfile
跟上面不同的是这种构建方式是采用命令的形式进行构建,类似于 win 的批处理
先创建一个名为 Dockerfile 文件(名称不可随意更改)
vim Dockerfile
输入格式:
FROM <基础镜像>
RUN <执行的命令>
# 案例
FROM ubuntu
RUN apt update
# 因为在构建过程中是无法干预的所以要加入-y 确认安装
RUN apt install -y openjdk-17-jdk
Dockerfile 的指令每执行一次都会在 docker 上新建一层。所以过多无意义的层,会造成镜像膨胀过大
输入完命令之后,保存退出,然后输入以下命令构建
docker build -t <镜像名称> <构建文件的目录>

-t
:设置镜像名称不对其进行指定的话默认查找的是
Dockerfile
这个文件,可以使用-f
或者--file
参数来指定 Dockerfile 的路径和文件名,如:docker build -f mycustom-Dockerfile .
在这个命令中,
mycustom-Dockerfile
是自定义的 Dockerfile 文件名,.
指示 Docker 在当前目录下寻找该文件
常用关键字
FROM mysql:5.6 #FROM指令定义基础镜像,FROM指令必须是第一个指令
MAINTAINER Benjamin <qq.com> #MAINTAINER指令指定维护者信息,此指令为可选
ENV JAVA_HOMR="/usr/local/java" #ENV定义环境变量,后续指令中可以通过$JAVA_HOME来引用环境变量,ENV定义的变量可以在容器中使用
ARG name="xiaolming" #ARG也是定义环境变量,与ENV不同的是,ARG定义的环境变量只能在Dockerfile中使用,在容器中不存在
RUN yum install -y httpd #RUN指令用于运行一个命令,如使用yum安装httpd服务
COPY /file*.txt /file/ #COPY用于复制宿主机文件到容器指定目录,COPY支持通配符
ADD hello-world /app/ #ADD也是用于复制宿主机文件到容器指定目录,ADD也支持通配符,与COPY不同的是,ADD会自动解压文件
WORKDIR /app/ #WORKDIR指令用于用于设置当前工作目录
USER root #USER指令用于切换用户
VOLUME /data #VOLUME指令用于定义匿名卷,表示将容器内的/data目录挂载为匿名卷到宿主机上
EXPOSE 80 8089/udp #EXPOSE用于声明容器端口,如果未指定协议,则默认为TCP,仅仅是声明,并没有绑定宿主机端口映射
CMD ["nginx", "-g", "daemon off;"] #CMD指令的作用是指定容器主进程的默认启动指令
ENTRYPOINT ["/usr/sbin/nginx","-g","daemon off;"] #ENTRYPOINT指令也是指定容器主进程的默认启动指令,但与CMD有区别。
Docker Hub
Docker Hub 是云端存储镜像可以把创建的镜像推送到远程仓库
上传
上传之前在 Hub 进行创建仓库(不做赘述),在系统中使用 docker login
进行登录
进行镜像重命名(这回复制一个新的镜像但是名称和版本不同),如果命名不一致的话它会新建一个新的仓库
docker tag test_java:latest ximuliunian/test_java:1.0

上传到远程仓库(名称要和远程仓库名称一样)
docker push [名称]:[版本]

完事之后可以使用 search 命令进行搜搜镜像
docker search ximuliunian
查看 ximuliunian 账户里面的所有公共仓库
SpringBoot应用程序镜像
首先得有一个可以运行的 SpringBoot 项目,在项目根目录创建 Dockerfile 文件,并配置(IDEA)一下Docke,采用TCP套接字,然后在Docke服务器更改一下 service 文件
vim /etc/systemd/system/multi-user.target.wants/docker.service
添加标红内容 -H tcp://0.0.0.0

重启Docker服务,如果是云服务器,记得开启2375 TCP连接端口
systemctl daemon-reload && systemctl restart docker.service

对项目进行打包,然后编写 Dockerfile 文件
# Base 镜像
FROM ubuntu
# 作者
LABEL authors="曦暮流年"
# 安装JDK环境
RUN apt update && apt install -y openjdk-17-jdk
# 复制jar包
COPY target/weiliao-0.0.1.jar app.jar
# 启动命令,在容器启动的时候执行的命令
CMD java -jar app.jar
打包好的镜像会存放在服务器中,可以使用以下命令运行
docker run -d -p 8080:8080 [镜像]
-d 代表在后台运行
-p 表示绑定端口,前面为 宿主机端口:容器端口