Docker的常见用法


一、Docker是什么?

Docker是一个开源的应用容器引擎,它基于Go语言并遵从Apache2.0协议开源。Docker允许开发者将应用程序及其依赖项打包到一个独立的容器中,然后在不同的环境中运行。这种容器技术使得应用程序能够在不同的操作系统和硬件平台上一致地运行,并且具有轻量级、可移植的特点。

二、Docker的使用

1.安装Docker

以Centos7-X86_64这个操作系统为例,如果你是其他Linux发行版,如Ubuntu、Debian等,请查看Docker官方文档进行相应的安装:https://docs.docker.com/engine/install/.
1)卸载旧的docker版本。旧版本的Docker的名字是docker、docker-engine等。新版本的Docker的安装名字叫做docker-ce(社区版,免费)或docker-ee (商业版,收费)。在安装新版本Docker以及相关的依赖项之前,必须先卸载任相关的旧版本Docker。使用如下命令进行卸载(命令行中的\表示换行,使得命令看上去更加清晰):

sudo yum remove docker \
                docker-client \
                docker-client-latest \
                docker-common \
                docker-latest \
                docker-latest-logrotate \
                docker-logrotate \
                docker-engine

运行上述命令之后,如果你之前没安装过旧版本Dcoker,就不会删除任何软件,如下所示:
在这里插入图片描述
2) 安装yum-utils包。该包提供了yum-config-manager这个实用程序,而yum-config-manager用于设置Docker仓库。我们通过设置Docker的存储库并从中进行Docker安装,这是Docker官网所推荐的方法。命令如下:

sudo yum install -y yum-utils

运行结果如下,看到”完毕!“二字,就代表yum-utils安装成功。
在这里插入图片描述
3) 设置Docker仓库。如果在国外,或服务器在国外,这里可以设置成官方Docker仓库,命令行如下:

sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

运行结果如下:
在这里插入图片描述
如果服务器在国内,访问Docker官方仓库的速度可能会很慢,因此可以使用国内的Docker仓库源,例如阿里云Docker源,命令行如下:

sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

运行结果如下:
在这里插入图片描述
4) 从Docker仓库中安装Docker Engine、containerd和Docker Compose,命令如下:

sudo yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

注意,上面这个指令默认下载的是最新的版本的docker-ce,如果你想要下载指定版本的,需要使用以下格式的命令:

 sudo yum install docker-ce-VERSION_STRING docker-ce-cli-VERSION_STRING containerd.io docker-buildx-plugin docker-compose-plugin

其中VERSION_STRING代表你要安装的版本,可以使用如下命令来获取所有能适配本机的docker-ce软件的版本列表:

yum list docker-ce --showduplicates | sort -r

运行结果如下,第二列代表这不同的docker-ce版本的VERSION_STRING,直接替换掉前面安装命令中的<VERSION_STRING>即可。这里解释一下第二列的字符串,拿3:25.0.3-1.el7举例。 ① 第一个数字3代表着第三轮更新,3代表着epoch的值。在RPM包管理系统中,epoch用于区分同名但不同版本的软件包,这有助于解决软件包版本冲突的问题。② 25.0.3: 这是软件的版本号。它通常由三个数字组成,分别表示主版本号、次版本号和修订号。在这个例子中,主版本号是25,次版本号是0,修订号是3。③ -1: 这通常是发布或构建号,它表示这是该版本的第1次发布或构建。④ el7: 这表示该软件包是为“Enterprise Linux 7”构建的,也就是为Red Hat Enterprise Linux 7(RHEL 7)或类似的发行版(如CentOS 7)构建的。
在这里插入图片描述
假如你要安装docker软件版本号为25.0.1的docker-ce,那么你输入的指令就为:

sudo yum install docker-ce-3:25.0.0-1.el7 docker-ce-cli-3:25.0.0-1.el7 containerd.io docker-buildx-plugin docker-compose-plugin

安装命令执行过程中,有两次需要你输入y,如下所示:
第一次:
在这里插入图片描述
第二次:
在这里插入图片描述
如果最后出现“完毕”二字,即代表安装完毕,如下所示:
在这里插入图片描述

5)启动Docker。直接输入以下命令即可:

sudo systemctl start docker

如果你遇到下错误:
在这里插入图片描述
出现Failed to start docker.service: Unit not found.这个错误的原因是缺少 rhel-push-plugin.socket 单元,该单元是rhel-push-plugin软件包的一部分,输入以下命令安装该单元后再次启动Docker即可:

curl -sSL https://get.docker.com/ | sh

Docker成功启动后不会有任何提示,如下所示:
在这里插入图片描述
可以查看一下docker的版本号,使用

docker --version

在这里插入图片描述
6)设置Docker为开机启动。
运行命令为:

sudo systemctl enable docker

运行结果如下:
在这里插入图片描述

7)配置阿里云镜像加速。
以后我们需要使用docker软件来安装很多其他的软件,例如mysql,那么我们默认会从Docker官方的Docker Hub中下载mysql的镜像,可能会存在下载速度慢的问题,因此如果服务器在国内的话,可以配置国内的docker镜像网站,这里以阿里云提供的docker镜像网站为例。进入阿里云官网(https://www.aliyun.com/),登录自己的账号信息,依次点击左上角的产品、容器和容器镜像服务 ACR,如下所示:
在这里插入图片描述
点击之后页面会跳转,继续点击“管理控制台”这个按钮,如下所示:
在这里插入图片描述
点击之后页面会跳转,依次点击镜像工具、镜像加速器和Centos,即可得到阿里云为我们生成的镜像加速地址,如下所示
在这里插入图片描述
只需要把阿里云给我们提供的命令都执行了即可完成阿里云docker镜像的配置,命令运行结果如下所示:
在这里插入图片描述
使用以下命令可以查看是否成功讲阿里云镜像地址配置到docker中去:

docker info

运行成功之后,在倒数第二行中可以看到阿里云镜像地址已经在docker配置中了。
在这里插入图片描述

2.Docker的常用方法

(1) Docker容器、镜像和引擎

在介绍docker的常用命令之前,先介绍一下docker镜像,docker容器,docker引擎以及他们之间的关系。
1)docker镜像
docker镜像是一个轻量级、可执行的软件包,它包含了运行应用所需的所有内容,包括代码、库、环境变量、配置文件等。镜像是只读的,你可以把它看作是构建容器的蓝图或模板。通常,镜像会基于一些基础镜像层,上面再叠加一些定制化的软件层。它们可以被存储在仓库中(比如Docker Hub),供用户下载和使用。
2)docker容器
docker容器是从docker镜像创建的运行实例。简单来说,当你启动一个或多个镜像时,docker引擎根据镜像创建容器。容器可以被理解为镜像的一个可执行实例,它在操作系统的用户空间中运行。容器是隔离的,它拥有自己的文件系统、网络和进程空间,但与宿主机操作系统共享内核。但是容器内部不能看到宿主机上的进程、环境变量等,容器也不能看到其他容器中的进程、环境变量等。容器可以被启动、停止、移动和删除。
3) docker引擎
docker引擎是一个客户端-服务器应用程序,它基于docker镜像文件构建对应的docker容器。docker引擎的服务器部分是一个长期运行的守护进程),负责处理Docker API请求、管理docker镜像、容器、网络和存储卷等。客户端部分(docker命令行)则是用户与docker引擎进行交互的接口。
4) docker镜像、容器、引擎之间的关系
镜像与容器:你可以把docker镜像理解为一本菜谱书,而容器则是根据菜谱所制作的菜肴。容器的每次运行都源于某个镜像,但它在运行时是可以被修改的,即容器的状态可以改变,而镜像是只读的。网上也有人说可以把docker镜像理解成java面向对象中的类,而docker容器可以看作是docker镜像的实例。
docker引擎与docker镜像和容器。docker引擎是负责管理docker镜像和docker容器的系统。它管理本地环境中的镜像,提供命令创建和管理容器,确保应用的顺利运行。
5)docker 容器与虚拟机
docker容器运行于宿主机上,且容器之间相互独立,这两天和虚拟机一模一样。docker容器和虚拟机确实都是虚拟化技术下的不同应用产品,docker容器达到了线程级别的隔离,而虚拟机到了操作系统级别的隔离。docker容器比虚拟机更加轻量,因为docker镜像中只包含了和本应用相关的库,而虚拟机却是一整套操作系统都有(会有冗余软件)。例如,mysql容器中不会有vi这个软件,如下所示,而虚拟机中装好linux操作系统后都会有vi这个编辑软件。
在这里插入图片描述
6)docker使用的一般流程
①创建Docker镜像:使用Dockerfile定义镜像内容,然后使用docker build命令来创建自己的镜像。当然,如果Docker Hub中开源的镜像就能满足我们的使用需求了,就可以直接拉取别人提供的镜像文件到本地即可。
②运行容器:使用docker run命令根据docker镜像启动docker容器。
③管理容器:通过docker引擎可以启动、停止、删除和检查容器。同时,它也能够管理网络设置和存储卷等。
④分享镜像:创建的镜像可以推送到Docker Hub,供其他人使用。
6)docker常用命令

(2)入门案例-mysql容器

接下来通过运行一个mysql容器来讲述docker命令的常用参数,

docker run -d \
	--name mymysql2 \
	-p 3308:3306 \
	-e MYSQL_ROOT_PASSWORD=123 \
	mysql:5.7

运行结果如下:
在这里插入图片描述
① docker run表示创建并运行一个容器,如果你只想创建一个容器,暂时先不运行它,可以使用docker create命令,如下所示:

docker create \
	--name mymysql2 \
	-p 3307:3306 \
	-e MYSQL_ROOT_PASSWORD=123 \
	mysql:5.7

运行结果如下:
在这里插入图片描述
此时创建的容器不会立即启动。
--name mymysql2这部分表示容器的名字,注意,这个名字必须唯一,例如,我再创建一个名字叫做mymysql2的容器会报错,如下所示:
在这里插入图片描述
我们怎么查看创建的容器的信息呢?使用docker ps -a即可,如下所示:
在这里插入图片描述
观察上述图片可以发现,在STATUS这一列中,第一个容器的状态是Created,这表面该容器只是被创建,还没有启动,而第二个容器已经运行了21分钟了。如果使用docker ps命令,只能看到运行的容器,看不到创建的和停止的容器,如下所示:
在这里插入图片描述
docker run -d中的-d指的是docker run命令创建的容器在后台运行。容器在后台运行的意思是指当前的命令窗口不会运行该容器,可以继续输入后续命令,如果不加这个-d,当前的命令窗口就会立即运行该容器,如下所示。这时候你需要重新建立一个和服务器的连接窗口,才能继续输入linux命令,因此建议把-d加上,更加方便。
在这里插入图片描述
-p 3307:3306参数表示宿主机与容器的端口映射,这个参数的意思访问centos7服务器的3307端口就等于是访问容器的3306端口。mysql容器会把自身的3306端口暴露出来,mysql容器自身也有自己的私有ip地址,可以通过docker inspect mymysql3 命令进行获取,mymysql3是你对docker容器起的别名,运行结果中可以看到该容器的docker地址:
在这里插入图片描述
centos7服务器能够访问mymysql3容器的3306端口,但是在外面的电脑根本不能访问到mymysql3容器,突变为该容器的IP地址是一个私有IP地址。由于外部电脑可以访问centos7服务器,因此我们通过让访问centos7的3307端口,再让centos转发这个请求到mymysql3容器的3306端口,就能成功访问mymysql3容器了,整个流程如下所示:
在这里插入图片描述

注意,宿主机(centos7服务器)的一个端口只能对应一个docker容器的端口,因此,如果你还要运行一个mysql容器,宿主机的端口不能再设置成3307了,不然宿主机就区分不了这此访问应该转发到哪个docker容器的3306端口,报错信息如下:
在这里插入图片描述
-e MYSQL_ROOT_PASSWORD=123,这部分用以设置docker容器的环境变量,其表明该mysql容器的root账户的密码是123。每个镜像可以设置的环境变量通常是不一样的。在docker hub官网(https://hub.docker.com/)中,输入镜像名字,可以查看该镜像有哪些环境变量可以设置,下图为mysql镜像的一些环境变量:
在这里插入图片描述
mysql:5.7这部分表明docker容器所依赖的镜像。前面已经说了,docker镜像与docker容器的关系就像类和对象的关系,这里的mysql:5.7表明你的这个容器依赖于mysql镜像的5.7版本。如果你本地没有mysql:5.7的这个镜像,docker引擎会自动帮我们从Docker镜像网站上下载到本地。如果你省略了版本号,那么默认会从最新的版本中下载,如下所示:
在这里插入图片描述
停止一个docker容器,docker stop 容器名即可停止容器,如下所示:
在这里插入图片描述
注意,这个容器名也可以换成容器的ID,它俩都是全局唯一的。docker容器启动、重启和删除也是类似,使用方法如下:
启动一个docker容器,docker start 容器名
重启一个docker容器,docker restart 容器名
删除一个docker容器,docker rm 容器名
这里说一下docker rundocker start的区别,docker run每次都会创建一个新的容器,并将新的容器启动,而docker start的作用是启动一个创建好的或已停止的容器,并不会创建新的容器。
docker images可以查看本地上下载了的所有镜像,如下所示:
在这里插入图片描述
使用docker pull 镜像名:版本,将Docker hub上的镜像下载到本地,如下所示,不输入版本号默认就是最新的版本:
在这里插入图片描述
docker pull [Registry]/[Repository]/[Image]:[Tag]为拉取镜像的完整命令,其中Registry默认是docker.io,即从docker hub服务器上拉取镜像文件;Repository是镜像文件所属的仓库名字,和github上的仓库类似;Image则是镜像名,Tag为该镜像的版本,默认是lastest。在执行这条命令的时候,都会先从本地搜索,如果搜索不到,才从docker hub服务器上搜索。
docker rmi 镜像名:版本号用于删除某个镜像,注意在删除镜像之前,必须要保证先把镜像生成的容器给删除掉,不然无法删除该镜像,如下所示:
在这里插入图片描述
docker tag [SOURCE_IMAGE][:TAG] [TARGET_IMAGE][:TAG]是镜像重名名的指令,可以把[SOURCE_IMAGE][:TAG镜像重命名成[TARGET_IMAGE][:TAG],如下所示:
在这里插入图片描述

对于一些不能上公网的场景,可以先使用

docker save -o 文件名.tar 镜像名:版本号

将镜像保存成一个.tar的文件,然后通过

docker load -i 文件名.tar 

来进行离线镜像安装。
如果你想在启动docker容器后想在容器中执行命令,该怎么办呢?假设你现在有一个mymysql5的容器,通过以下命令即可进入容器内部的控制终端中:

docker exec -it mymysql5 bash

执行结果如下所示:
在这里插入图片描述
docker exec指令允许我们往运行中的容器中输入指令,我们输入的是bash指令,这个指令可以展示容器的终端窗口。-it表示我们以交互式和伪终端的方式执行bash指令。

(3)数据卷

为什么需要数据卷呢?原因是docker容器的本质是一个轻量虚拟机,如果你想将docker容器中的一些运行结果(例如错误日志)保存到宿主机中,就必须要使用数据卷。数据卷很像虚拟机中的共享文件夹,通过将docker目录的某个文件夹挂载在这个数据卷上,docker容器中的那个目录将会和宿主机上的某个特定目录形成双向绑定。双向绑定的结果是宿主机上的这个目录有啥文件变动,docker容器中的对应目录也会跟着变动,反之亦然。docker会将数据卷存储在主机的/var/lib/docker/volumes目录下,每个数据卷对应该目录下的一个目录。
docker volume create 数据卷名 用于创建一个数据卷
docker volume inspect 数据卷名 用于删除一个数据卷
docker volume ls 用于显示所有的数据卷
我们举一个例子,我们想把nginx容器中的/usr/share/mginx/html目录挂载到html数据卷中,可以执行以下命令:

docker run -d --name mynginx -p 80:80 -v html:/usr/share/nginx/html nginx

其中,html:/usr/share...中的html为数据卷的名字。在这个命令中,如果数据卷没有被创建,会自动被创建。执行完后,使用docker volume ls执行可以看到创建的html数据卷,
在这里插入图片描述
并且我们可以发现,在/var/lib/docker/volumes多了一个html文件夹,它里面有一个_data的文件夹,里面有两个文件,如下所示,这俩文件正是nginx容器下的/usr/share/nginx/html目录下的文件
在这里插入图片描述
此时,我们在宿主机中添加一个test.txt的文件,就会发现mynginx容器中的/usr/share/nginx/html中也会新增一个test.txt文件,如下所示,反之亦然,
在这里插入图片描述
使用docker inspect命令可以查看容器的挂载信息,如下所示,Source为宿主机上的目录,Destination为容器中的目录。
在这里插入图片描述
我们发现我们启动mysql容器的时候,并没有手动对其进行挂载,但是却发现它居然也挂载到了一个数据卷上,如下所示:
在这里插入图片描述
这个名字很长的数据卷被称为匿名数据卷,它是docker引擎为mysql容器自动创建和挂载的。

如果我们不想把宿主机的挂载点设置到/var/lib/docker/volumes目录下,而是自己服务器上任意目录,例如/root/test,应该怎么做呢?首先,现在/root目录下创建一个test文件夹,然后在启动容器时,-v 数据卷名:/usr/share/nginx/html需要变成-v 路径名:/usr/share/nginx/html即可,命令如下,

docker run -d --name mynginx -p 80:80 -v /root/test:/usr/share/nginx/html nginx

运行结果如下:
在这里插入图片描述,发现居然test文件中没有内容,这种方式和第一种方式不一样,由于宿主机的目录会覆盖掉docker容器的目录,我们进入docker容器种,发现其/usr/share/nginx/html文件也是空的,如下所示:
在这里插入图片描述
注意,如果容器已经创建成功了,就没有办法再进行挂载了。

(4)自定义镜像

有时候公开的镜像已经不能满足我们的个性化需求了,我们就必须自己构建一个自定义的docker镜像。例如我们想让docker运行我们自己编写的java程序,就需要自定义docker镜像。docker的镜像是分层的,大致可以分为基础层,中间层和入口层。
① 基础层是Docker镜像的最底层,通常基于一个Linux发行版的Docker镜像,如Ubuntu、Debian或CentOS等。这些基础镜像提供了最小安装的Linux发行版环境,为构建应用程序的容器提供了基础操作系统。基础镜像作为整个镜像的起点,包含了必要的系统文件和库,以确保容器能够正常运行。
②中间层是在基础层之上添加的层,用于包含应用程序的依赖项、配置文件、源代码等。这些层是在构建Docker镜像时通过Dockerfile中的指令逐步添加的。每当Dockerfile中的指令执行时(如RUN、COPY、ADD等),都会创建一个新的层。这些层是增量式的,意味着每个新层只包含与上一层相比的更改部分。中间层的设计使得Docker镜像的构建过程非常高效,因为只有发生变化的部分才会被添加到新的层中。
③入口层通常包含用于启动容器内应用程序的命令或脚本。在Dockerfile中,可以使用ENTRYPOINT指令来定义入口点。当容器启动时,Docker会执行入口层中定义的命令来启动应用程序。这使得容器能够以一致的方式启动,并确保应用程序在容器内的正确执行。

这种层次结构的设计的好处是使得Docker镜像具有轻量级、可重用和可组合的特点。每个层都是只读的,并且多个镜像可以共享相同的层,从而减少了存储空间的占用。同时,由于每个层都是增量式的,因此可以很容易地对镜像进行更新和修改。
①通过容器来构建镜像,我们运行完一个容器后,对容器进行了一些修改,例如添加了一些文件,我们可以反过来把容器打包成镜像。首先,创建一个nginx1的容器,并在根目录下增加一个hello.txt的文件,如下所示:
在这里插入图片描述
使用docker commit 容器名 镜像名:版本号讲docker容器打包成指定名字和版本号的镜像,如下所示:
在这里插入图片描述
当你基于这个镜像运行容器时,你会发现容器中根目录下有一个hello.txt,这是符合预期的:
在这里插入图片描述

②使用Dockerfile构建镜像
Dockerfile是一个包含了用户构建镜像的所有命令的文本文件。Dockerfile的每一行命令都会生成一个独立的镜像层,并且拥有唯一的ID。在了解Dockerfile之前,先了解Dockerfile常见命令:
FROM:Dockerfile除了注释第一行必须是FROM ,FROM后面跟镜像名称,代表我们要基于哪个基础镜像构建我们的容器。
RUN:RUN后面跟一个具体的命令,类似于Linux命令行执行命令。
COPY:拷贝本机文件到镜像内。
ENTRYPOINT:容器的启动命令。
CMD:CMD为ENTRYPOINT指令提供默认参数,也可以单独使用CMD指定容器启动参数。
ENV:指定容器运行时的环境变量,格式为key=value。
EXPOSE:指定容器监听的端口,格式为[port]/tcp 或者[port]/udp。
WORKDIR:为Dockerfile 中跟在其后的所有RUN、CMD、ENTRYPOINT、COPY和ADD命令设置工作目录。

以下为一个springboot项目的docker部署案例,首先通过maven将springboot打成jar包,这里注意要把pom文件中的skip标签设置成false,如下所示:
在这里插入图片描述
在本机测试jar包程序是否能运行成功,如下所示,springboot成功启动。
在这里插入图片描述
在浏览器输入http://127.1.1.0:8080/helloworld,注意,这个网址和你自己写的Controller有关,不一定和我一样,如下所示:
http://127.1.1.0:8080/helloworld

编写Dockerfile文件如下:

# 基础镜像jre 8
FROM openjdk:8-jre-alpine
# 将自己编写的java程序(jar包)复制到容器的跟目录下,并且名字变成app.jar
COPY helloword-0.0.1-SNAPSHOT.jar /app.jar
# 容器启动后自动运行 java -jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]

将Dockerfile和jar包都上传到服务器上,通过docker build -t myapp:1.0 .构建自己的镜像,如下所示,其中myapp:1.0表示镜像名和版本号,.表示当前目录下的Dockerfile文件。
在这里插入图片描述

基于自己构建的镜像运行容器,发现springboot项目启动成功:
在这里插入图片描述

浏览器输入docker容器宿主机的ip+8080端口+路由,发现成功获得返回结果,如下所示:
在这里插入图片描述
RUN、CMD和ENTRYPOINT的区别与联系
这三条命令的共同点是后续都可以接一些linux命令,但是他们的作用却又区别。
RUN指令用于在构建Docker镜像时执行命令。每个RUN指令都会在当前镜像的顶层创建一个新的镜像层,并提交结果。这通常用于安装软件包、设置配置文件和执行构建过程中的其他任务。RUN指令其他两个指令的使用时机不同,RUN指令是在构建镜像时运行,而CMD和ENTRYPOINT是在镜像构造完后,容器启动时自动运行。
CMD指令用于为容器指定默认执行的命令。当容器启动时,如果没有指定其他命令,就会执行CMD指令中定义的命令。CMD指令可以被docker run命令后面的参数所覆盖,这意味着你可以在启动容器时指定要运行的命令来替代CMD指令中的默认命令。在一个Dockerfile中,如果存在多个CMD指令,只有最后一个会生效。ENTRYPOINT指令也用于指定容器启动时运行的命令,但与CMD指令不同的是,ENTRYPOINT指令指定的命令不会被docker run命令后面的参数所覆盖。相反,这些参数会被作为参数传递给ENTRYPOINT指令指定的程序。例如以下这个Dockerfile例子:

.....省略之前的镜像构建步骤,以下步骤都是容器运行时自动启动的
ENTRYPOINT ["/usr/sbin/nginx"]
CMD ["-g", "daemon off;"]

如果直接运行容器docker run -d nginx_image,那么容器将会执行/usr/sbin/nginx -g daemon off。如果提供了额外的参数,比如docker run -d nginx_image -h,那么-h参数会被传递给ENTRYPOINT,所以实际执行的命令会变成/usr/sbin/nginx -h,而CMD中的参数会被覆盖掉,CMD中的值相当于函数传参时的默认值,当容器运行时没有传参的时候才使用。

(5)网络

docker网络是docker容器之间以及docker与外部进行通信的基础。
Docker网络驱动程序主要包括以下几种:

  1. Bridge(桥接模式):这是docker默认的网络驱动程序。它利用NAT(网络地址转换)技术,将docker容器的IP地址映射到宿主机的IP地址上,从而实现容器与宿主机以及其他容器之间的通信。在bridge模式下,容器没有一个公有IP,只有宿主机可以直接访问,外部主机是不可见的,但容器通过宿主机的NAT规则后可以访问外网。在这种模式下,所有的虚拟机都连上一个docker0的网卡,如下图所示,因为它们都连上同一个网卡,因此容器与容器之间以及容器和宿主机之间都能互联通信。
    在这里插入图片描述

  2. Host(主机模式):在这种模式下,docker容器将加入到宿主机的网络命名空间中,使得容器可以直接使用宿主机的网络接口。这种模式的优点是容器无需做网络策略配置,只要能访问到宿主机,就能访问到容器。但缺点是隔离性差,会占用宿主机的端口,无法实现自定义任意端口。

  3. None(无网络模式):在这种模式下,Docker容器不加入任何网络,仅作为孤立的容器存在,没有网卡、IP、路由等信息。
    先使用如下命令运行两个包含centos的docker镜像,

docker run -d --name centos1 -it centos /bin/bash
docker run -d --name centos2 -it centos /bin/bash

接着使用docker inspect命令查看这两个容器的ip地址,一个为172.17.0.2,一个为172.17.0.3,如下所示。
在这里插入图片描述
在这里插入图片描述

进入一个docker容器中,使用ping命令看看能不能ping通另一个容器,发现是能ping通的,如下所示:
在这里插入图片描述

在容器中ping 百度网址,也是能ping通的,如下所示:
在这里插入图片描述

在宿主机中ping 容器ip,也是能ping通的,如下所示:
在这里插入图片描述

但是外部机是没法ping通docker容器,例如我使用我的windows电脑ping 172.17.0.3会显示请求超时,如下所示:
在这里插入图片描述
这一切的原因都是因为默认的docker网络模式是桥接模式。在桥接模式下,守护进程会创建一对对等虚拟设备接口 veth pair,将其中一个接口设置为容器的 eth0 接口(容器的网卡),另一个接口放置在宿主机的命名空间中,以类似 vethxxx 这样的名字命名,从而将宿主机上的所有容器都连接到这个内部网络上。
在宿主机上输入ip addr即可看到docker0网卡和虚拟设备接口 veth**,如下所示:
在这里插入图片描述
在docker容器中输入ip addr,可以看到其自身的eth0**网卡,如下所示:
在这里插入图片描述
在这样的桥接模式,每个docker容器的ip地址都是被自动分配的,也就是说当你删除这个容器后,即使基于相同的镜像启动容器,也可能会导致新的容器和删除的容器的ip地址不一致。我们已知容器的名字是唯一的,能不能通过容器的名字来访问它呢?这样无论容器的具体ip怎么变,反正我们写代码的时候连接该容器使用的是容器名,而非ip地址。答案是可以的,步骤如下:
①创建一个docker网络。
使用以下docker network create 网络名来创建一个网络,并且可以使用dockers network ls来查看所有的docker网络,如下所示:

docker network create centosnet

在这里插入图片描述
当我们把docker网络创建成功后,宿主机会多一个以“br-”开头的网卡,如下所示。凡是与该网卡相连的容器,都能互相以容器名代替ip地址进行通信。
在这里插入图片描述

②将现有docker容器加入到自定义的docker网络中。使用docker network connect 网络名 容器名来将容器加入指定网络,如下所示:

docker network connect centosnet centos1
docker network connect centosnet centos2

这样,在centos1中,可以直接通过centos2容器的容器名来通信,如下所示:
在这里插入图片描述
③创建docker容器时就让他加入网络,使用–network 网络名,如下所示:

docker run -d --name centos3 --network centosnet -it centos /bin/bash

检查是否能通过容器名代替ip,发现可以,结果如下:
在这里插入图片描述

(6)docker compose

假设我们的项目中,既要使用mysql,又要使用redis、nginx等多个组件,像之前一样一个一个的容器去创建显得效率不高,而且也不好进行统一管理。由此,简化多容器应用程序的部署、管理和维护过程,我们需要使用docker compose。Docker Compose通过一个单独的docker-compose.yml模板文件(YAML格式)来定义一组相关联的应用容器,帮助我们实现多个相互关联的Docker容器的快速部署。
使用如下命令下载docker compose:

curl -SL https://github.com/docker/compose/releases/download/v2.17.3/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose

使用如下命令赋予docker compose可执行权限,

chmod +x /usr/local/bin/docker-compose

输入docker-compose --version可以查看docker compose的版本号,如下所示:
在这里插入图片描述

以下为一个yml文件实例,用于实现部署mysql、nginx和自己写的java程序,如下所示:

version: '3.8'
networks:
  mynet:
    driver: bridge
services:
  mysql_service:
    image: "mysql:5.7"
    container_name: mymysql
    ports:
      - "3307:3306"
    environment:
      MYSQL_ROOT_PASSWORD: 123
    networks:
      - mynet
  nginx_service:
    image: "nginx"
    container_name: mynginx
    ports:
      - "80:80"
    volumes:
      - "/root/test:/usr/share/nginx/html"
    networks:
      - mynet
    depends_on:
      - mysql_service
  app_service:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    networks:
      - mynet
    container_name: myapp
    depends_on:
      - nginx_service

其中,version指定了docker compose文件的版本,这这个version与我们下载的docker compose的版本无关,而是与你安装的docker的版本有关,以下是一篇博客中讲解的docker compose文件版本对dokcer版本的兼容关系,我的docker版本是 25.0.3,因此这里填写3.8。
在这里插入图片描述
services定义了服务列表。在上面的例子中,有三个服务,他们的名字分别为mysql_service、nginx_service和app1。
image指定要使用的Docker镜像。
ports:端口映射从宿主机到容器,格式是 “宿主机端口:容器端口”。
depends_on定义服务之间的依赖关系,依赖的服务如果失败,该服务也不会启动。
volumes 把容器中的目录挂载到宿主机中,格式和数据卷章节类似。
container_name定义了容器启动后的名字,不然就是系统会自动取名字。
networks 用于定义网络,上诉代码配置了一个名叫mynet的网络,且使用桥接模型进行容器之间的通信。
使用``直接启动所有的docker容器,如下所示

使用docker compose -f docker-compose.yml up直接关闭所有的docker容器,如下所示,-f 后面接docker compose文件的路径,如果想后台运行,可以加-d参数。
在这里插入图片描述
使用docker ps -a查看容器,发现运行着创建的三个docker容器,如下所示:
在这里插入图片描述

本文只展示了docker的一些常用命令,想要查阅docke的具体某条命令,可以在docker官网(https://docs.docker.com/reference/cli/docker/)上查看。


三、docker的原理

docker软件的架构

在这里插入图片描述
这是Docker官方网站上的架构图,docker软件是一个经典的cs架构。docker客户端主要作用是发送docker操作指令(docker run等),服务端负责接收和处理指令。docker客户端和服务端可以在同一台电脑上,也可以不在同一台电脑中。除了使用docker命令的方式与服务端进行交互,也可以使用RESTful风格的API或各语言对应的SDK的方式与docker服务端进行交互。dockerd负责响应和处理来自docker客户端的请求然后将客户端的请求转化为docker的具体操作(比如镜像、容器、网络和挂载卷等)。除了dockerd组件之外,docker服务端还有containerd、runC等其他组件。runC组件是用来运行容器的轻量级工具,是真正用来运行容器的。containerd组件通过containerd-shim启动并管理runC,可以说containerd组件是真正管理docker容器的生命周期的组件。dockerd通过gRPC与containerd组件通信。docker的架构之所以要这么设计是为了满足OCI标准。OCI全称是开放容器标准(Open Container Initiative),是一个轻量级,开放的治理结构,旨在围绕工业化容器的格式和镜像运行时制定一个开放的容器标准。

docker容器

docker是怎样把镜像变成docker容器的呢?使用镜像创建容器时,会将镜像文件的每一层都复制一个副本,这将会添加一个容器层,这个层可以修改所有镜像文件文件的副本,如下图所示:
在这里插入图片描述
镜像包含了容器运行所需要的文件系统结构和内容,是静态的只读文件。容器是在镜像的只读层上创建了可写层,并且容器中的进程属于运行状态,容器是真正的应用载体。同时,docker容器的文件系统设计成写时复制(Copy-On-Write, CoW),即只有在容器试图写入文件时,才会真正地从镜像文件中创建一个副本。这样的好处有以下几点:

快速启动:如果每次启动容器都必须复制整个镜像,将会消耗大量的时间和资源,启动将会变得缓慢。使用CoW,容器几乎可以立即启动,因为它们共享大部分未修改的底层文件系统。

内存效率:写时复制还利于内存使用。操作系统级别的CoW技术,例如Linux中的OverlayFS,不仅在文件系统层面共享了未修改的文件,还可以使得处于同一节点的不同容器可以共享内存中的相同数据。

节省存储空间:CoW策略确保只有在容器试图写入文件时,才会真正地创建一个副本,这样可以大大减少存储空间的使用,因为未被修改的文件都不会被复制。

docker的资源隔离与限制

Docker容器是基于Linux内核的Namespace技术实现文件系统、进程、设备以及网络资源等的隔离,然后再基于cgroups对CPU、内存等资源进行限制,最终实现了容器之间的隔离与资源限制。docker容器之间的隔离是仅仅由linux内核提供,其隔离性也远弱于虚拟机。但是由于docker容器的本质是进程而不是一个完整操作系统,因此其创建和运行所需要的资源也远远小于虚拟机。
什么是Namespace呢?Namespace是Linux内核的一项功能,该功能对内核资源进行分区,以使一组进程看到一组资源,而另一组进程看到另一组资源。Namespace的工作方式通过为一组资源和进程设置相同的Namespace而起作用。这些资源可以是进程ID、主机名、用户ID、文件名、与网络访问相关的名称和进程间通信。接下来介绍Docker中使用的六种Namespace
①Mount Namespace。隔离不同的进程或进程组看到的挂载点,实现容器内只能看到自己的挂载信息,在容器内的挂载操作不会影响主机的挂载目录。接下来演示Mount Namespace。首先介绍以下unshare,这是util-linux_工具包中的一个工具,可以实现创建并访问不同类型的Namespace,是centos7系统自带的软件。使用如下命令创建一个mount namespace,

sudo unshare --mount --fork /bin/bash

这个命令运行之后,我们就已经创建了一个mount namespace,并且当前命令行窗口就已经加入了该mount namespace。接下来在新的mount namespace中新建一个/tmp/tmpfs文件夹,如下所示:

mkdir /tmp/tmpfs

接着使用以下命令将新建的/tmp/tmpfs挂载到

mount -t tmpfs -o size=20m tmpfs /tmp/tmpfs

解释以下上述命令:mount是Linux中用于挂载文件系统的命令。-t tmpfs: 这指定了要挂载的文件系统类型为tmpfs。tmpfs是一个基于内存的文件系统,它允许你使用RAM来存储文件,因此访问速度非常快。但是,当系统重启或tmpfs被卸载时,上面的数据会丢失。-o size=20m: 这是挂载选项,其中size=20m指定了tmpfs文件系统的大小为20MB。这意味着tmpfs只会使用最多20MB的RAM。tmpfs:这是挂载命令中指定的文件系统的标识,复用了文件系统的类型名称。/tmp/tmpfs:这是要挂载 tmpfs 的目标目录。这意味着所有对 /tmp/tmpfs 目录的读写访问都将在内存中进行,不会有硬盘 I/O 操作。综上所述,这条命令创建了一个大小为 20MB 的 tmpfs 文件系统,并将它挂载到 /tmp/tmpfs 目录。你可以在这个目录下创建文件、目录等,但所有的数据都会在卸载或重启后丢失。
使用df -h 查看已经挂载的目录信息,如下所示:
在这里插入图片描述
此时,使用一个新的窗口连接服务器,并再次输入df -h命令,并没有发现上诉挂载目录,如下所示。由此可见,在独立的mount namespace中的挂载操作并不会影响到主机。
在这里插入图片描述
返回到创建mount namespace的命令行窗口,输入ls -l /proc/self/ns/指令可以看到当前进程的所有Namespace信息,这里记住你的mnt namespace的id,如下所示:
在这里插入图片描述
然后在新的连接服务器窗口输入ls -l /proc/self/ns/,可以发现,除了mount namespace的id不一样,其他都一样,这可证明unshare工具确实创建了一个新的mount namespace,而且新的mount namespace内部的挂载和外部是完全隔离的。
在这里插入图片描述
②PID namespace。该namespace 的作用是用来隔离进程,在不同的PID namespace中,可以有相同的PID号。也许一个进程在宿主机上的PID是666,但是它在容器中的进程为1。使用以下命令可以创建一个PID namespace,并且当前的命令行窗口加入了新的PID namespace中

sudo unshare --pid --fork --mount-proc /bin/bash

在当前的命令行窗口使用ps aux命令查看所有进程信息,可以发现只有两个,一个是bash,一个是ps aux,看不到任何宿主机相关的线程信息。
在这里插入图片描述
③UTS Namespace。这主要是用来隔离主机名的它允许每个UTS Namespace拥有一个独立的主机名。使用以下命令可以创建一个新的UTS namespace,并且将当前窗口加入到新的UTS namespace中,

sudo unshare --uts --fork /bin/bash

接着使用hostname -b mydocker设置主机名为mydocker,并查看,如下所示:
在这里插入图片描述
新建一个窗口链接服务器,输入hostname命令,发现其主机名还是localhost,可以发现新的UTS namespace与外部是完全独立的。
在这里插入图片描述
④IPC Namespace。这主要是用来隔离进程间通信的,PID Namespace和IPC Namespace一起使用可以实现同一IPC Namespace内的进程彼此可以通信,不同IPC Namespace的进程却不能通信。使用如下命令创建一个新的IPC namespace,

sudo unshare --ipc --fork /bin/bash

ipcs -q命令:查看系统间通信队列列表;ipcmk -Q命令:创建系统间通信队列。在新的IPC namespace中使用ipcmk -Q创建一个通信队列,如下所示:
在这里插入图片描述
新建一个命令行窗口来连接服务器,并使用ipcs -q来查看所有通信队列,发现为空,也就是上述创建的IPC namespace与宿主机完全隔离。
⑤User Namespace主要是用来隔离用户和用户组的。使用User Namespace可以实现进程在容器内拥有root权限,而在主机上却只是普通用户。echo 10 > /proc/sys/user/max_user_namespaces将系统的最大user_namespaces数量改成10,并切换到一个普通用户,随后使用如下命令创建一个User namespace:

unshare --user -r /bin/bash

此时输入id命令,可以发现当前用户已经变成了root用户,但是输入reboot这个需要root权限才能执行的命令时,却又会报权限不足,说明新建的User Namespace中的root和外部的root是完全独立的。
在这里插入图片描述
⑥ Net Namespace。用来隔离网络设备、IP地址和端口等信息Net Namespace可以让每个进程拥有自己独立的IP地址,端口和网卡信息例如主机IP地址为172.16.4.1,容器内可以设置独立的IP地址为192.168.1.1。
先使用ip a来查看主机上的网卡,如下所示,又l0、ens33,docker0等
在这里插入图片描述
使用以下命令创建一个Net namespace,

sudo unshare --net --fork /bin/bash

再次使用ip a,发现就只有一个网卡l0了,这就说明新建的net namespace和宿主机是隔离的。
在这里插入图片描述
当Docker新建一个容器时,会创建这六种Namespace,然后将容器中的进程加入这些Namespace之中。

虽然此时我们已经能对各种资源进行隔离,但是各种隔离的环境理论上可以无限制的使用宿主机的CPU和内存,因此,我们需要对docker容器使用的CPU和内存等资源进行限制。这时就需要使用linux内核的另一个技术,cgroups了。
cgroups(全称: control groups)是Linux内核的一个功能可以实现限制进程或者进程组的资源(如CPU、内存、磁盘IO等),优先级控制,资源使用情况统计和控制进程的挂起与恢复。cgroups的核心组件为子系统、控制组和层级树。子系统是一个内核的组件,一个子系统代表一类资源调度控制器。例如,内存子系统可以限制内存的使用量,cpu子系统可以限制cpu的执行时间。控制组表示一组进程和一组带有参数的子系统的关联关系。每一组进程就是一个控制组,也就是一个cgroup。例如,一组进程使用cpu子系统来限制cpu的使用时间,则这组线程与cpu子系统的关联关系被叫做控制组。层级树是由一系列的控制组按照树状结构排列组成的,子控制组默认拥有父控制组的属性。例如,控制组c1限制只能使用1个cpu,另一个控制组c2想实现控制线程只能使用1个cpu外加2G内存,因此c2可以继承c1,并额外增加一个2G内存的限制。
使用sudo mount -t cgroup可以查看所有的子系统,我们重点关心cpu和内存这两个子系统。
在这里插入图片描述
接下来演示如何使用cgroups限制进程使用cpu的时间。使用mkdir /sys/fs/cgroup/cpu/mydocker命令创建一个测试文件夹,创建好之后,文件夹中会被自动创建很多文件,如下所示:
在这里插入图片描述
其中,cpu.cfs_period_us文件代表限制使用CPU的时间总量,单位为微秒,注意,默认为100000,则代表所有的CPU将会被该进程使用。tasks文件中存放着需要进行限制的进程id。首先,使用cd /sys/fs/cgroup/cpu/mydocker进入cpu子系统文件,接着使用echo $$ > tasks把shell进程加入tasks文件中进行控制,使用 cat tasks命令查看shell进程的pid,为56245,如下所示:
在这里插入图片描述
接着在命令行中输入使用while true;do echo;done;来制造一个死循环,把该控制组的cpu资源全部使用,接着打开另一个服务器连接窗口,使用``查看该56245进程的cpu使用率,如下所示,大约为15%:
在这里插入图片描述
我们使用echo 200000 > cpu.cfs_quota_us命令把cpu的占用时间增加1倍,再次在另一个窗口观察该线程的cpu使用率,发现为cpu占用率增加了大约两倍,如下所示:
在这里插入图片描述


四、为什么我要使用Docker

  1. 使用docker,可以很方便的使用一些开发组件,例如mysql,redis等,往往一行命令即可安装好一个开发组件,非常方便。
  2. 移植性高,docker可以看作是一个轻量化的虚拟机,它屏蔽了不同硬件平台和不同操作系统之间的差距。docker镜像封装了应用程序及其依赖项,使得应用程序可以在几乎任何docker环境中以相同的方式运行。例如,你写的程序在windows平台下的docker中环境能运行,那么大部分情况下在linux操作系统的docker环境中也能运行。这种一致性确保了开发、测试和生产环境之间的无缝迁移。

总结

docker是一个开源的容器化平台,它允许开发者将应用程序及其依赖项打包到一个可移植的容器中,从而在不同环境下实现一致的运行方式。docker通过利用宿主机的操作系统内核来运行应用程序,实现了轻量级和高效的资源利用。
Docker的主要特点有:

  1. 一致性:Docker容器封装了应用程序及其所有依赖项,确保在不同环境中运行的应用程序具有相同的行为和性能。
  2. 轻量级与快速:相比传统的虚拟机,Docker容器不需要启动完整的操作系统,因此启动速度更快,资源占用更低。
  3. 隔离性:Docker通过Linux内核的命名空间和cgroup技术实现资源隔离,确保每个容器都在其自己的隔离环境中运行,提高了系统的安全性和稳定性。
  4. 可移植性:Docker镜像可以在不同的操作系统和平台上运行,使得应用程序的部署和迁移变得更加简单。
  • 31
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值