Docker入门

Soul Lv1

最近忙着写项目,好久没更新内容了。而且就在昨天,重装系统的时候我居然忘了把博客的本地文件保存一份!!!现在我已经失去了前面博客的所有内容,不得不花费大量时间重新把前面的内容整理一次,孩子心里苦啊。

闲话就聊到这里,今天我们来了解一下Docker技术.Docker可以帮助我们快速的实现项目依赖环境的完整打包.方便我们将项目快速的部署到不同的设备上去.

由于Windows上没有原生的Docker,想要运行需要套几层壳,所以这次我们使用Ubuntu.具体版本为Ubuntu24.04.(当然现在有了WSL2,其实在windows上也不是很麻烦,直接去官网https://www.docker.com/下载即可)

Docker的安装

我们首先来完场Docker的安装.这部分比较简单,先安装一些依赖

1
sudo apt-get install ca-certificates curl gnupg lsb-release

然后安装官方的GPG Key

1
2
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

然后将Docker的下载地址放到apt的搜索列表中

1
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

接着更新一下列表

1
sudo apt update

接着下载Docker的社区版本(Docker分为社区版和商业版)

1
sudo apt install docker-ce

可以通过

1
docker --version

来判断是否下载成功

image-20250316140606402

Docker的配置到此完成,我们接下来详细的了解一下Docker的结构

容器与镜像

容器与虚拟机的区别

在开始这一板块之前,我们先来思考一个问题:假设我现在使用的是Ubuntu22.04,然后下载一个Ubuntu24.04的Docker,那么我是否能使用Ubuntu24.04的全部功能呢?这个问题在虚拟机上是显然的,但是在Docker中却不那么肯定。

我们可以看一下这张图:

image-20220630181037698

这张图体现了虚拟机和Docker的重要区别:虚拟机的虚拟是内核级别的,所有的虚拟机相互独立,互不干扰。但是,所有的Docker共用设备的内核空间,只拥有独立的用户空间。也就是说,如果ubuntu22.04的内核支持相关功能,我们是可以顺利使用的,但如果内核不知相关功能,我们就无能为力了。

此外,还有一点值得注意:容器与镜像的区别。

直接构建

了解了这些,我们开始尝试自己构建一个镜像,每一个应用都运行在操作系统上,所以构建自己的镜像也必须基于最基本的系统镜像,我们一般称这些镜像为Base镜像(当然,在很多时候我们也不一定真的从最基本的镜像开始构建,而是使用一些已经安装了一些必要的环境的镜像开始构建)。我们这里尝试去构建一个包含了jdk的ubuntu镜像

首先我们拉取一个ubuntu的Base镜像(最近由于DockerHub的限制,很多镜像源都不能用了,如果下载不下来,请自行寻找合适的镜像源或者科学上网)

1
docker pull ubuntu:22.04

这里注意,一个镜像的标准名称通常为name:tag的格式,name用来说明名称,tag用来说明版本

下载完成后可以通过

1
docker images

来查看当前的所有镜像

image-20250402151040457

可以看到,刚刚下载的Ubuntu镜像小的惊人,只有77.9MB,这是因为作为Base镜像,只保留了最基本的用户空间运行能力,我们平时很多常用的命令都是无法在这个镜像中执行的。

接下来在容器中运行这个镜像

1
docker run -it ubuntu:22.04

其中i参数指创建一个标准的输入输出接口,t参数指创建一个虚拟的tty设备来作为终端供我们使用,这两个参数一般同时使用,效果如下:

image-20250402152336296我们已经进入了基于ubuntu镜像创建的容器的内部

我们的目的是构建一个包含jdk的ubuntu镜像,所以现在执行下列命令

1
apt update && apt install -y openjdk-21-jdk

下载完成后可以使用exit直接退出

现在查看以下我们的容器

1
docker ps -a

如果不使用a参数则只显示处于运行状态下的容器

image-20250402154045706

接下来执行

1
docker commit 容器ID/名称 新的镜像名

Docker中所有可以使用id的地方都可以使用ID的一部部分,只要能保证具有唯一性就好,例如我们可以在此处使用

1
docker commit fd jdk:21

看一看结果

image-20250402154822551

注意到经过我们的一番操作后镜像的体积发生了巨大的膨胀,这是因为我们在下载jdk时不但下载了jdk,还下载了jdk运行时必要的操作系统依赖。

对于已经存在的镜像,我们可以

1
2
docker start name/id #启动现有的镜像
docker stop name/id #停止正在运行的镜像

我们可以使用这种方法去构建任何镜像,但这种镜像的构建存在一个问题:不够透明。除你之外没有人知道这个镜像是如何被构建的,对个人使用来说,这种构建方式显然是无所谓的,但是如果这个镜像是面向其他人的,我们就不应该这么做,我们需要使用Dockerfile来完成构建

通过Dockerfile构建

在开始之前我们先删除之前创造的镜像与容器

1
2
docker rmi 镜像ID #删除镜像
docker rm 容器ID #删除容器

同样的,只要输入ID的开头,确保唯一性即可

接下来我们创建一个名为Dockerfile的文件,并写入如下的内容

1
2
FROM ubuntu:22.04
RUN apt update && apt install -y openjdk-21-jdk

在该文件目录下执行

1
docker build -t jdk:21 .

就能得到和我们之前所构建的jdk镜像完全相同的镜像了

下面我展示一些在Dockerfile中常使用的命令

1
2
3
4
5
6
7
8
9
10
11
12
FROM <基础镜像> #从对应的基础镜像开始构建
LABEL xxx=xx #给镜像添加元数据,一般用来标识作者,版本等信息
ARG xxx=xx #创建一个名为xxx的参数,其值为xx,为此后调用这个值提供方便
RUN xxx #在基础镜像内执行xxx
COPY /xx /xxx #将/xx目录下的内容复制到镜像的/xxx目录下
ADD https://example.com/file.tar.gz /app/ #与COPY类似,但支持远程下载
WORKDIR /xx #设置容器启动时将自动跳转到容器内的/xx目录下
ENV xxx=xx #为镜像添加一个名称为xxx的环境变量,其值为xx
EXPOSEE xxx #对外暴露容器的xxx端口
VOLUME /xx #在容器的/xx目录下创建一个挂载点用于数据持久化,这点我们后面再解释
ENTRYPOINT xxx #在容器启动时自动执行xxx,优先级高于CMD
CMD xxx #在容器启动时自动执行xxx

当然,这并不是全部的命令,但大多数时候对我们来说已经够用了

在这部分的最后,让我们了解一下如何将我们所创建的镜像推送到DockerHub,首先自行到Dockerhub创建一个仓库

image-20250402204650747

接下来我们将我们之前的镜像重新命名后推送

1
2
docker tag jdk:21 myjdk:21
docker push 你的用户名/myjdk:21

根据规范,我们必须在推送时添加自己的用户名来保证推送到属于我们自己的仓库,在拉取时同样采用

1
docker pull 你的用户名/myjdk:21

的格式来确保获得正确的镜像

这一步操作如果网络环境好可以试一试,毕竟一次性要上传800MB,在当前网络环境下有时候会比较困难

Docker网络管理

接下来我们来了解一下Docker的网络管理,我们执行如下的命令

1
docker network ls

image-20250402205731935

我们可以看到当前总共有三个网路,这三个网络是Docker在下载时就帮我们创建的,分别对应Docker支持的三种网络类型bridge null host

我们首先来看看null网络,这个网络中只有一个本地环路,与宿主机的网络环境相互隔离,无法通过网络访问,用于部分有特殊安全需求的环境。如果想使用null网络,可以使用

1
docker run --network=none jdk:21

然后是host网络,如果容器使用的是这种网络,相当于直接使用宿主机的网络环境,在这种网络下,我们,容器中的网络配置与宿主保持一致,不需要镜像额外的开放端口等操作。在这种模式下,网络通信的损失是最小的.如果想使用我们

最后是bridge网络,或者说是桥接网络,你可以简单的理解为在宿主机上创建了一个虚拟的局域网,宿主机与容器都是这个虚拟局域网的一台设备,不同设备之间通过这个局域网进行信息交互。这也是所有容器的默认网络模式。在这种模式下,我们前面的在Dockerfile中主动暴露端口的行为就有了意义,举个简单的例子

1
docker run -p 1234:80 -n test jdk:21

这里我使用了两个参数,p参数用来指定端口映射,将宿主机的1234端口映射到容器的80端口,此时对宿主机1234端口的访问就会通过桥接网络转发到容器的80端口。n参数是给容器命名的,我们之前所创建的容器起名称都是docker随即生产的,我们可以通过这个参数手动指定。

在这里你可能会问,那我们之前在Dockerfile中提到的EXPOSE指令又有什么作用呢?这个指令更多的起到提醒作用,用于提醒用户需要将这个端口映射到宿主机中

我们也可以创建自己的网络,例如这样

1
docker network creat --driver bridge test

这样我们就创建了一个名为test的桥接网络,我们在启动时可以手动指定使用的网络

1
docker run --network=test image

注意,虽然都是使用桥接网络,但是test网络与bridge网络两个网络是互相隔离的,无法互相进行通信,当然,我们也可以主动进行连接

1
docker network connect test containerID/name

此时容器就被主动的连接到了test网络

容器数据持久化

还记得我们上面提到的volume指令吗?对于一个一般的容器,在停止运行时所有运行时产生的数据都将被删除,这无疑是我们所不想看到的,所以我们可以使用VOLUME指令创建挂载点,此时docker会产生一个匿名卷来存储doker对应目录下的数据,这个匿名卷在宿主机上的路径一般为/var/lib/docker/volumes,不过我们可以选择进一步的定制化一些,我们可以将容器的数据挂载到宿主机的指定路径下,使用

1
docker run -v /host/path:/container/path image

此时会将容器的/container/path与宿主机的/host/path相互绑定,宿主机与容器任何一方对数据的修改都会在另一方体现

如果我们使用v参数但不指定宿主机路径,则会在宿主机上自动创建对应路径

如果我们想要在多个容器之间共享数据呢,聪明的你一定想到创建一个公共目录,经所有容器都挂载到对应的目录。但是我们还有别的办法:直接将一个容器的路径挂载到另一个容器

1
docker run -p 80:80 --volumes-from=data_test test

此时test继承了data_test的挂载信息,在总体容器数量比较多时我们通常会创建一个专门的容器用来管理数据(被称为数据卷容器),例如上面的data_test,所有的容器再继承其挂载方式

Docker容器管理

接下来我们来聊一聊对运行状态下的容器的管理,有以下常用的命令

1
docker log 容器名/ID

可以用来输出容器中的控制台信息(f参数可用来保持持续输出)

1
docker attach 容器ID/名称

直接进入某个运行的容器的内部,注意在完成操作后先按Ctrl+P再按Ctrl+Q退出,千万不要按Ctrl+C这会导致容器停止运行

1
docker exec -it 容器ID/名称

为容器创建一个新的bash终端,如果容器中运行的项目会不断的对控制台输出信息,那么可以通过这个指令开启一个新的终端来对容器进行操作

1
docker stats

用于查看所有容器的状态

1
docker top 容器ID/名称

查看某个容器的所有进程信息

接下来是几个比较不推荐的

1
2
3
docker kill 容器ID/名称 #强制终止容器
docker pause 容器ID/名称 #暂停容器
docker unpause 容器ID/名称 #与上一条相反

其实还有很多操作,但是这些操作都是偏运维向的,这里就不做介绍了

常用参数介绍

注意,docker非常重视灵活性,所有在dockefile中规定的东西都可以被在启动时覆盖,实际的覆盖操作使用以下这些参数

参数描述示例
-d后台运行容器docker run -d IMAGE
-it交互式终端,-i 保持标准输入打开,-t 分配一个伪终端docker run -it IMAGE
--name为容器指定一个名称docker run --name mycontainer -d IMAGE
-p端口映射,格式为 主机端口:容器端口docker run -p 8080:80 -d IMAGE
-v数据卷映射,格式为 主机路径:容器路径[:权限]docker run -v /host/path:/container/path -d IMAGE
--env-e设置环境变量docker run --env MY_VAR=value -d IMAGE
--network将容器连接到指定的 Docker 网络docker run --network mynetwork -d IMAGE
--restart设置容器的重启策略,可选值为 noon-failurealwaysunless-stoppeddocker run --restart always -d IMAGE
--link将容器连接到另一个容器docker run --link other-container:alias -d IMAGE
--cpus限制容器使用的 CPU 资源docker run --cpus="1.5" -d IMAGE
--memory限制容器使用的内存docker run --memory="256m" -d IMAGE
--gpus指定容器可以访问的 GPU 设备docker run --gpus all -d IMAGE
--log-driver指定日志驱动docker run --log-driver=syslog -d IMAGE
--health-cmd配置容器的健康检查命令`docker run –health-cmd=”curl -f http://localhost/
--user指定容器内进程的用户docker run --user username -d IMAGE
--security-opt设置 SELinux 或 AppArmor 配置docker run --security-opt seccomp=unconfined -d IMAGE
--cap-add添加容器的 Linux 能力docker run --cap-add=SYS_ADMIN -d IMAGE
--cap-drop删除容器的 Linux 能力docker run --cap-drop=SYS_ADMIN -d IMAGE
--privileged给容器赋予特权,可以访问主机的设备docker run --privileged -d IMAGE
--tmpfs在容器内创建临时文件系统docker run --tmpfs /tmp -d IMAGE
--ulimit设置容器的资源限制,如最大打开文件数、最大进程数等docker run --ulimit nofile=1024:1024 -d IMAGE
--hostname设置容器的主机名docker run --hostname my_container -d IMAGE
--dns指定容器使用的自定义 DNS 服务器docker run --dns 8.8.8.8 -d IMAGE
--dns-search指定容器的 DNS 域docker run --dns-search example.com -d IMAGE
--entrypoint覆盖镜像的默认入口点docker run --entrypoint /custom_entrypoint -d IMAGE
--rm容器退出时自动删除容器docker run --rm -d IMAGE
--mount更详细的挂载配置,支持类型 bindvolumetmpfsdocker run --mount type=bind,source=/data,target=/app/data -d IMAGE
--shm-size设置容器的共享内存大小docker run --shm-size 2g -d IMAGE
--volume-driver指定容器使用的卷驱动程序docker run --volume-driver my_driver -d IMAGE
--env-file从文件读取环境变量docker run --env-file=env.list -d IMAGE
--label为容器添加元数据标签docker run --label "env=prod" -d IMAGE

需要特别说明的是ENTRYPOINT可以通过--entrypoint 覆盖,而CMD指令可以通过类似

1
docker run my_image echo "Hello, World!"

这样原来的CMD命令将会被覆盖,如果需要的命令过于复杂,你还可以这样

1
docker run my_image < cmd.sh

直接编写一份完整的脚本然后导入

结语

如果你之前来过我的站,你可能会发现所有的之前的文章都已经消失了,由于某次出人意料的事故,所有之前写下的文章都美丽,悲~~~。所以只好从零开始了,有很多东西确实比较有用,有空的话我会尽力再尝试写一份的

img

  • 标题: Docker入门
  • 作者: Soul
  • 创建于 : 2025-04-01 21:39:37
  • 更新于 : 2025-04-04 11:30:04
  • 链接: https://soulmate.org.cn/2025/04/01/Docker入门/
  • 版权声明: 本文章采用 CC BY-NC 4.0 进行许可。