Docker

Docker安装

Docker之介绍与安装 - Qubernet - 博客园 (cnblogs.com)


镜像 image

如果我们要写一个比如论坛、电商这类的Java项目,那么数据库、消息队列、缓存这类中间件是必不可少的。因此我们如果想要将一个服务部署到服务器,要提前准备好各种各样的环境,先安装好MySQL、Redis、RabbitMQ等应用,配置好了环境,再将我们的Java应用程序启动。整个流程下来,光是配置环境就要浪费大量的时间,如果是大型的分布式项目,可能要部署很多台机器,项目上个线就要花几天时间,显然是很荒唐的。

而上述的MySQL、Redis、RabbitMQ等应用以及Java应用程序,可以一起被打包成一个镜像,整个镜像中环境都是已经配置好的状态,开箱即用。镜像就是一堆静态的模板,根据这个模板运行起来的就是容器。镜像一般需要拉取下来,是只读的。

我们在打包项目时,实际上往往需要一个基本的操作系统环境,这样我们才可以在这个操作系统上安装各种依赖软件,像这种基本的系统镜像,称为base镜像,我们的项目之后都会基于base镜像进行打包,如debian系统(docker pull debian)。

基于base镜像,就可以在此基础上安装各种软件。每安装一个软件,就在base镜像上一层层叠加上去,采用的是一种分层的结构,这样多个容器都可以将这些不同的层次自由拼装。

image-20220701143105247

可以看到,除了这些软件之外,最上层有一个可写容器层。由于所有的镜像会叠起来组成一个统一的文件系统,如果不同层中存在相同位置的文件,上层会覆盖掉下层文件。当需要修改容器中的文件时,实际上不会对镜像直接进行修改,而是在最上层的容器层进行修改只有这样,镜像才能保证完整性,多个镜像才能自由拼装。因此文件相关操作如下:

  • 文件读取:Docker会从上到下依次寻找
  • 文件创建和修改:创建文件直接添加到容器层中;修改文件会从上到下依次寻找,找到后复制到容器层,再进行修改
  • 删除文件:从上到下一次寻找,一旦找到,不会直接删除文件,而是在容器层标记删除操作

构建镜像

所有常用的应用程序都有对应的镜像,我们只需要下载这些镜像然后就可以使用了,而不需要自己去手动安装,顶多需要进行一些特别的配置。要是遇到某些冷门的应用,可能没有提供镜像,这时就要我们手动去安装

构建镜像有两种方式:

  1. 使用commit命令
  2. 使用Dockerfile

使用commit命令构建镜像

举例:在Debian的base镜像中安装Java环境,并将其打包为新的镜像

  1. 启动运行Debian容器,安装jdk

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # 1. 拉取debian镜像
    docker pull debian

    # 2. 运行debian容器
    docker run -it debian

    # 3. 使用apt命令安装java环境
    apt update
    apt install openjdk-17-jdk

    # 4. 确认安装完成
    java -version
    # 有如下输出
    # openjdk version "17.0.11" 2024-04-16
    # OpenJDK Runtime Environment (build 17.0.11+9-Debian-1deb12u1)
    # OpenJDK 64-Bit Server VM (build 17.0.11+9-Debian-1deb12u1, mixed mode, # sharing)
  2. 退出当前镜像,并将其构建为新的镜像

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

    image-20240717221433138

    可以看到,安装了软件之后的镜像大小比原来大得多。虽然这种构建镜像的方式自定义度很高,但是Docker官方并不推荐。使用者不知道镜像是如何构建的,是否有安全隐患,而且构建效率低。作为普通用户,采用Dockerfile的方式会更好一些


使用Dockerfile构建镜像

  1. 新建Dockerfile文件

    1
    touch Dockerfile
  2. 编辑Dockerfile文件,需要编写多种指令来告诉Docker我们的镜像信息。

    1
    2
    3
    4
    5
    # 必须以这个指令开始
    FROM <基础镜像>

    RUN apt update
    RUN apt install -y openjdk-17-jdk

    每条命令执行之后,都会生成一个新的镜像层

  3. 完成镜像构建。

    1
    2
    # docker会在构建目录中寻找Dockerfile文件,并依次执行其中的指令
    docker build -t <镜像名称> <构建目录>
    image-20240717223025762 image-20240717223303815
  4. 可以使用docker history命令查看构建历史。

    image-20240717223437516

    • 可以看到每条命令的执行都作为了一层镜像。
    • 镜像ID为missing的一般是从Docker Hub中下载的镜像会有这个问题,但是问题不大。

发布镜像到远程仓库

可以将自己的镜像发布到Repositories | Docker Hub中,就像Git远程仓库一样。

  1. Create repository

    image-20240717224042038

  2. 填写信息并创建。

    image-20240717224208972
  3. 上传本地镜像。

    1
    2
    # 使用tag命令重新打标签,创建一个新的本地镜像。
    docker tag debian-java-file:latest 用户名/仓库名称:版本

    image-20240717224608304

  4. 登录docker,上传镜像。

    1
    2
    3
    docker login -u 用户名

    docker push 用户名/仓库名称:版本

    image-20240717232742331

    image-20240717232807126

    image-20240717232820913

    镜像比较大,上传了好几遍才成功。

  5. 打开仓库,可以看到已经有上传的镜像版本了。

    image-20240717233048031

公共仓库可以被搜索和下载

1
2
3
docker search hunter1023/debian-java

docker pull hunter1023/debian-java

image-20240717233508875


容器 container

运行拉取的镜像,就会根据这个镜像生成一个容器。运行的容器就像应用程序,可以访问、可以停止。运用多次Run命令,就运行了很多容器,也可以说是镜像的实例。


容器网络管理

Docker在安装后,会在主机上创建三个网络,使用docker network ls命令查看:

image-20240718142356018

进入debian容器,安装网络相关软件,并构建为新镜像:

1
2
3
4
5
6
7
docker pull debian
docker run -it debian

apt update
apt install net-tools inputils-ping curl

docker commit 2e3d9cbce7a7 debian-net

none 网络

这个网络除了有一个本地环回网络之外,就你没有其他网络了。可以在创建容器时指定网络。

1
2
3
4
docker run -it --network=none debian-net

# 查看当前网络
ifconfig
image-20240718151625115

只有1个本地环回lo网络设备,因此该容器无法连接到互联网


bridge 网络

桥接网络,这是容器默认使用的网络类型。可以看到容器的网络接口地址为172.17.0.3,实际这是Docker创建的虚拟网络

image-20240718153853722

可以使用docker network inspect bridge命令查看docker网桥的配置信息。

image-20240718154407299

这里配置的子网是172.17.0.0,子网掩码16位,对应掩码255.255.0.0,网关是172.17.0.1。


host 网络

当容器连接到此网络后,会共享宿主主机的网络,可以在容器中直接开放端口,不需要进行任何桥接。

1
docker run -it --network=host debian-net

用户自定义网络

用户可以通过docker network create创建新的网络。

1
2
3
4
5
6
7
8
9
docker network create --driver bridge test

# 查看test网络的配置信息
docker network inspect test

# 使用test网络创建新容器
docker run -it --network=test debian-net

# 使用 Ctrl + P & Ctrl + Q 退出容器但不关闭容器
image-20240718170829797

不同的网络是相互隔离的,例如bridge和test。但是可以将容器连接到另一个容器所属的网络下,实现相互通信

1
2
3
4
5
6
7
8
9
10
11
# 默认以bridge网络创建的容器
docker run -it debian-net

# 使用 Ctrl + P & Ctrl + Q 退出容器但不关闭容器

# 将容器连接到 test网络
docker network connect [net name] [容器id/容器name]

docker attach [容器id/容器name]

# ifconfig查看,连接了一个新的网络
image-20240718172134364

容器间网络

当两个容器都是使用bridge网络时,在同一个子网中,所以可以互相访问。但是大部分情况下,容器部署之后的IP地址是自动分配的,有没有方法可以灵活地让容器互相访问呢?

可以借助Docker提供的DNS服务器,能够将容器名解析成对应的容器IP地址但是这个操作只能在用户自定义的网络下生效,默认的网络则做不到

1
2
3
4
5
docker run -itd --name=debian01 --network=test debian-net

docker run -it --name=debian02 --network=test debian-net
# 进入容器后 ping 容器名
ping debian01

还可以让两个容器共用同一个IP地址:

1
2
# 创建容器时,通过 --network=container:[容器名] 即可让容器debian02共用debian01的网络。
docker run -it --name=debian02 --network=container:[debian01] debian-net

容器外部网络

在默认的三种网络下,只有host和bridge可以连接到外网。bridge模式实际上就是创建一个单独的虚拟网络,容器在这个虚拟网络中,然后通过桥接器与外界相连。数据包通过虚拟NAT(Network Address Translation)将地址进行转换,再利用宿主主机的IP地址将数据包发送出去。

image-20220702232449520

单纯依靠NAT的话,只有主动与外界联系时,才能被感知。但是我们的容器中可能会部署一些服务,需要外界主动来连接我们。可以通过在启动容器时,通过-p [宿主机端口:容器端口]配置端口映射。这样,当外部访问到宿主机的对应端口时,就会直接转发给容器内映射的端口。


容器存储管理

容器持久化存储

容器在创建后,实际在容器中创建和修改的文件,实际时被容器的分层机制保存在最顶层的容器层进行操作,以保护下面每一层的镜像不被修改。但是这样也会导致容器在销毁时数据丢失

在某些情况下,我们可能希望对容器内的某些文件进行持久化存储,这就要用到数据卷(Data Volume)。数据卷用来做数据持久化到我们的宿主机上容器间的数据共享,简单说就是将宿主机的目录映射到容器中的目录,应用程序在容器中的目录读写数据会同步到宿主机上,这样容器产生的数据就可以持久化。

首先在宿主机上创建一个文件,随便写点内容。例如:

image-20240718200132695

接着就能将宿主机上的目录或文件挂载到容器的某个目录上

1
2
3
4
5
6
7
# -v: 上的~/docker_practice/volume_test目录
# 挂载到容器的/root/volume_test路径
docker run -it -v ~/docker_practice/volume_test:/root/volume_test debian-vim

# -v 如果不指定宿主机上的目录进行挂载,Docker就会自动创建一个目录,并将容器中对应路径下的内容拷贝到自动创建的目录中,最后再挂载到容器中
# 即命令中 -v之后只跟了一个目录,它是容器中的目录
docker run -it -v /root/volume_test debian-vim

这样就能在容器中访问宿主机上的文件。这样,即使将来销毁容器,挂载的数据还能保留。

有时候为了方便,并不需要直接挂载一个目录到容器中,仅仅是从宿主主机传递一些文件到容器中,可以通过docker cp命令来完成

1
2
3
docker cp ~/hello.txt [容器名/容器ID]:[容器中的目录]

docker cp [容器名/容器ID]:[容器中的目录或文件] [宿主机中的目录]

容器数据共享

可以通过构建一个专门存放数据的容器,直接将容器中打包好的数据分享给其他容器。本质上依然是一个Docker管理的数据卷,但可移植性很好。

如下一个Dockerfile

1
2
3
4
5
FROM debian
# ADD命令能自动解压缩,以及从远程URL下载文件;COPY产生的镜像大小要比ADD小,且性能更好
ADD xxx.tar.gz /etc
# 挂载容器中的目录
VOLUME /etc
1
2
3
4
# docker会在构建目录中寻找Dockerfile文件,并依次执行其中的指令
docker build -t [镜像名称] <构建目录>

docker run -it --name=[数据容器名] [镜像名称]

创建一个容器直接继承该数据容器:

1
docker run --volume-from=[数据容器名] [容器名/容器ID]

这样就能实现将数据放在容器中共享,而不需要刻意指定宿主主机的挂载点。就算迁移主机,依然可以快速部署。

Docker自行管理的挂载目录,可以通过docker inspect [容器name/容器id]命令,再查找关键字Mount查看,就能找到宿主机上的保存路径

image-20240718210459910


容器资源管理

  1. 限制内存

    1
    2
    3
    # docker run -m [内存使用限制] --memory-swap=[内存和交换分区总共的内存限制] [image name]

    docker run -it -m 50M --memory-swap=100M [image name]

    -m--memory-swap默认值都是-1,即没有限制。如果只指定-m参数,交换分区的内存限制与其保持一致,即--memory-swap-m的两倍大小

  2. 限制CPU权重

    CPU权重默认为1024。

    1
    2
    3
    4
    5
    6
    7
    8
    docker run -c 1024 debian
    docker run -c 512 debian

    #也可以直接指定容器使用的CPU
    docker run -it --cpuset-cpus=0,1 debian

    # 指定使用的CPU数量
    docker run -it --cpus=1 debian
  3. 查看容器资源占用情况

    docker stats

  4. 查看容器中进程

    docker top [容器ID/容器名称]


Docker 命令

  1. 查看已有镜像

docker iamges

  1. 镜像下载

镜像的官方资源仓库:Docker Hub,需要的镜像及对应版本都有陈列,且官网有直接的下载命令可以复制。

docker pull zookeeper:latest

  1. 删除镜像

docker rmi [image Name]

  1. 查看所有容器

docker ps [OPTIONS]

OPTION:

  • -a: 显示所有容器,包括未运行的

  • --filter: 根据条件过滤

    1
    2
    3
    4
    docker ps --filter "name=lucid_beaver"

    # 展示指定的列
    docker ps -a --format "table {{.ID}}\t{{.Names}}\t{{.Ports}}\t{{.Image}}"
  • -l: 显示最近创建的容器

  • -n: 列出最近创建的n个容器

    docker ps -n 2 列出最近创建的2个容器

  • -s: 显示总的文件大小

  • -q: 静默模式,只显示容器编号

  1. 运行容器

    docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

    OPTIONS:

    • -d: 后台运行容器,并返回容器ID 守护进程,当这个进程意外死亡后,会自动拉起来

    • -p: 指定端口映射,宿主机端口:容器端口

    • -i: 以交互模式运行容器,通常与-t同时使用

    • -t: 为容器重新分配一个伪输入终端,通常与-i同时使用

    • --name=xxx: 为容器指定一个名称,xxx为字符串但不能加引号

    • -v:指定文件或目录挂载到容器,[宿主机文件或目录]:[容器中文件或目录]

      -v如果不指定宿主机上的目录进行挂载,Docker就会自动创建一个目录,并将容器中对应路径下的内容拷贝到自动创建的目录中,最后再挂载到容器中。即命令中如果-v之后只跟了一个目录,它就是容器中的目录

    • -e: 设置环境变量

    docker run -itd --name=mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql

  2. 启动处于停止状态的容器

    docker start [CONTAINER NAME/CONTAINER ID]

    • -i:进入容器进行交互
  3. 进入容器内部

    docker exec -it [CONTAINER ID] bash

    docker attach [CONTAINER ID]

    暂时不清楚这两个命令的具体区别。

  4. 不进入容器,执行一条命令

    docker exec [CONTAINER ID] [容器中的操作命令]

  5. 仅退出容器,不关闭容器

    Ctrl + P,再Ctrl + Q

  6. 关闭容器

    docker stop [CONTAINER ID]

    会等待容器处理善后,再关闭容器

  7. 强制终止容器

    docker kill [CONTAINER ID]

  8. 删除容器

docker rm [CONTAINER ID]

  • -f:强制删除,即使容器正在运行
  1. 暂停容器

docker pause [CONTAINER ID]

  1. 恢复运行容器

docker unpause [CONTAINER ID]

  1. 打印容器中的日志信息

docker logs [CONTAINER ID]

  • -f:持续打印

运行各容器

Debian

1
2
3
4
5
6
# 首次运行
docker run -it debian

# 退出后就会停止运行
# 再次启动是用start,加上-i进入容器进行交互,否则会后台运行
docker start -i [debian's container id]

ZooKeeper

1
docker run -d --name=zookeeper -p 2181:2181 zookeeper

MySQL

1
docker run -itd --name=mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql

进入mysql容器:

1
docker exec -it mysql bash

本机可以通过 root 和密码 123456 访问 MySQL 服务:

1
mysql -h localhost -u root -p

Redis

1
2
3
4
5
# 不带密码运行容器
docker run -itd --name myRedis -p 6379:6379 redis

# 带密码运行容器
docker run -itd --name myRedis -p 6379:6379 redis --requirepass "123456"

RabbitMQ

1
docker run -itd --rm --name rabbitmq -p 5672:5672 -p 15672:15672 -e RABBITMQ_DEFAULT_USER=root -e RABBITMQ_DEFAULT_PASS=123456 rabbitmq:3.12-management
  • 15672端口:RabbitMQ的管理页面端口
  • 5672端口:RabbitMQ的消息接收端口

使用IDEA构建Spring Boot程序镜像

  1. 在项目的根目录,创建一个Dockerfile。修改内容如下:
1
2
FROM debian
RUN apt update && apt install -y openjdk-17-jdk
  1. 由于IDEA自带了Docker插件,直接点击运行按钮即可。
image-20240717234431852
  1. 首次执行,会弹出的Edit Run Configuration框,由于当前是windows环境安装的docker,无需额外的远程连接,直接填写Image tag即可。Apply之后,点击Close
image-20240718002605541
  1. 再点击运行按钮,选择',构建镜像。
image-20240718002714925
  1. 执行完成后,可以看到刚构建好的镜像。
image-20240717235440787
  1. 基本环境搭建好了,接着将Spring Boot项目打包。

image-20240717235816150

  1. 编辑Dockerfile,将构建好的jar包放入,指定在启动时运行Java程序。
1
2
3
4
5
FROM debian
RUN apt update && apt install -y openjdk-17-jdk

COPY target/seckill-0.0.1-SNAPSHOT.jar app.jar
CMD java -jar app.jar

image-20240718105631400

  1. 在IDEA中,右键镜像,创建对应的容器。
image-20240718105826181 image-20240718110045438

Docker-Compose 进行单机容器编排

比如现在我们要在一台主机上部署很多种类型的服务,包括数据库、消息队列、SpringBoot应用程序若干,或是想要搭建一个MySQL集群,这时我们就需要创建多个容器来完成来。但是我们希望能够实现一键部署,就可以使用Docker-Compose对容器进行编排


编排 SpringMVC 项目

以使用Tomcat部署SpringMVC项目为例,SpringMVC项目默认打成War包,希望部署SpringMVC项目的同时,部署一个MySQL服务器,并且配置远程Debug。

  1. 查看是否已安装Docker-Compose
1
docker compose version
image-20240718220544555
  1. 在IDEA中配置Docker-Compose:

image-20240718221047907

  1. 在IDEA中编写compose.yaml文件

war包的包名会被Tomcat默认作为所部署应用的基础URL,形如http://localhost:8080/springmvc_demo-1.0-SNAPSHOT。如果想变更包名,可以在pom.xml文件中更改:

1
2
3
4
5
6
7
8
9
<build>
<!--自定义打包文件名-->
<finalName>springmvc_demo</finalName>
<plugins>
<plugin>
...
</plugin>
</plugins>
</build>

compose.yaml文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
services:
springmvc: # 自定义的服务名称
container_name: app_springmvc_demo
image: tomcat:10.1.26-jdk17 # 已经打包好JDK环境的tomcat镜像
ports:
- "8080:8080" # 提供正常访问的接口
- "5005:5005" # 提供调试的接口
volumes:
- "./target:/usr/local/tomcat/webapps" # 挂载需要部署的war包所在目录
environment:
# tomcat开启远程调试,不同的Jdk版本对应的参数格式不同(如下是JDK9及以上的格式)
JAVA_OPTS: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"

depends_on:
mysql:
condition: service_healthy # mysql服务稳定后再创建项目容器
mysql:
container_name: app_mysql
image: mysql/mysql-server:8.0.32 # 表示使用对应的镜像,自动从仓库下载,然后启动容器
environment:
MYSQL_ROOT_HOST: '%' # 登录的主机
MYSQL_ROOT_PASSWORD: '123456.root' # root账号的密码
MYSQL_DATABASE: 'demo' # 启动时创建的数据库
TZ: 'Asia/Shanghai' # 时区
volumes:
- ./src/main/resources/sql:/docker-entrypoint-initdb.d # 挂载需要执行的sql脚本所在目录
ports:
- "3306:3306" # 对外访问的端口
  1. 点击compose.yaml文件右上角的Synchronize with the Services tool window按钮
image-20240719155132066

就可以在Services标签页查看。

  1. 执行docker-compose up或在Service标签页中,选择Compose进行deploy。

image-20240719155518391

image-20240719160238173
  1. 查看部署情况,容器全部正在运行,并且会自动为这些容器创建一个bridge网络用于互通。
image-20240719160819653
  1. 远程调试项目

创建一个Remote JVM Debug配置。

image-20240801165511142

一般来说,Java项目的调试端口都选择5005。确定好调试的Hostportcompose.yaml中的配置保持一致。其实可以看到,填写HostPort,下面的Command line arguments for remote JVM中会生成相应的命令行参数,其实是和compose.yaml中的配置一致的。

image-20240801165703160

配置无误的话,开启Debug时,Debug标签页会显示如下内容:

image-20240801170315686


编排 Spring Boot 项目

以部署SpringBoot项目为例,希望部署SpringBoot项目的同时,部署一个MySQL服务器,一个Redis服务器。

  1. 查看是否已安装Docker-Compose
1
docker compose version
image-20240718220544555
  1. 在IDEA中配置Docker-Compose:

image-20240718221047907

  1. 在IDEA中编写Dockerfilecompose.yaml文件

    Dockerfile文件如下:

    1
    2
    3
    4
    # 基础镜像为 已经打包好JDK环境的镜像
    FROM eclipse-temurin:17-jdk
    COPY target/seckill-0.0.1-SNAPSHOT.jar app.jar
    CMD java -jar app.jar

    compose.yaml文件如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    services: # services下填写需要编排的服务
    spring: # 服务名称,自定义
    container_name: app_springboot_seckill
    build: . # 表示使用构建的镜像,. 表示使用当前目录下的Dockerfile进行构建
    ports:
    - "8080:8080"
    depends_on:
    mysql:
    condition: service_healthy # mysql服务稳定后再创建项目容器
    mysql:
    container_name: app_mysql
    image: mysql/mysql-server:8.0.32 # 表示使用对应的镜像,自动从仓库下载,然后启动容器
    environment:
    MYSQL_ROOT_HOST: '%' # 登录的主机
    MYSQL_ROOT_PASSWORD: '123456.root' # root账号的密码
    MYSQL_DATABASE: 'seckill' # 启动时创建的数据库
    TZ: 'Asia/Shanghai' # 时区
    volumes:
    - ./src/main/resources/sql:/docker-entrypoint-initdb.d # 挂载需要执行的sql脚本所在目录
    ports:
    - "3306:3306" # 对外访问的端口
    redis:
    container_name: app_redis
    image: redis:latest
    ports:
    - "6379:6379"
    zookeeper:
    container_name: app_zookeeper
    image: zookeeper:latest
    rabbitmq:
    container_name: app_rabbitmq
    image: rabbitmq:3.13.4-management
    environment:
    RABBITMQ_DEFAULT_USER: root
    RABBITMQ_DEFAULT_PASS: 123456
  2. 修改项目的application.yaml配置文件,与compose.yaml文件相对应。

    image-20240719161154551 image-20240719161321761 image-20240719161333488
  3. 点击compose.yaml文件右上角的Synchronize with the Services tool window按钮

    image-20240719155132066

    就可以在Services标签页查看。

  4. 执行docker-compose up或在Service标签页中,选择Compose进行deploy。

    image-20240719155518391

    image-20240719160238173
  5. 查看部署情况,容器全部正在运行,并且会自动为这些容器创建一个bridge网络用于互通。

    image-20240719160819653

问题

docker run hello-world失败

参考如下,docker的镜像源一定要添加能ping通的。

docker下载镜像超时问题:Get https://registry-1.docker.io/v2/_myydan的博客-CSDN博客