Docker
Docker安装
Docker之介绍与安装 - Qubernet - 博客园 (cnblogs.com)
镜像 image
如果我们要写一个比如论坛、电商这类的Java项目,那么数据库、消息队列、缓存这类中间件是必不可少的。因此我们如果想要将一个服务部署到服务器,要提前准备好各种各样的环境,先安装好MySQL、Redis、RabbitMQ等应用,配置好了环境,再将我们的Java应用程序启动。整个流程下来,光是配置环境就要浪费大量的时间,如果是大型的分布式项目,可能要部署很多台机器,项目上个线就要花几天时间,显然是很荒唐的。
而上述的MySQL、Redis、RabbitMQ等应用以及Java应用程序,可以一起被打包成一个镜像,整个镜像中环境都是已经配置好的状态,开箱即用。镜像就是一堆静态的模板,根据这个模板运行起来的就是容器。镜像一般需要拉取下来,是只读的。
我们在打包项目时,实际上往往需要一个基本的操作系统环境,这样我们才可以在这个操作系统上安装各种依赖软件,像这种基本的系统镜像,称为base镜像,我们的项目之后都会基于base镜像进行打包,如debian系统(docker pull debian
)。
基于base镜像,就可以在此基础上安装各种软件。每安装一个软件,就在base镜像上一层层叠加上去,采用的是一种分层的结构,这样多个容器都可以将这些不同的层次自由拼装。
可以看到,除了这些软件之外,最上层有一个可写容器层。由于所有的镜像会叠起来组成一个统一的文件系统,如果不同层中存在相同位置的文件,上层会覆盖掉下层文件。当需要修改容器中的文件时,实际上不会对镜像直接进行修改,而是在最上层的容器层进行修改,只有这样,镜像才能保证完整性,多个镜像才能自由拼装。因此文件相关操作如下:
- 文件读取:Docker会从上到下依次寻找。
- 文件创建和修改:创建文件直接添加到容器层中;修改文件会从上到下依次寻找,找到后复制到容器层,再进行修改。
- 删除文件:从上到下一次寻找,一旦找到,不会直接删除文件,而是在容器层标记删除操作。
构建镜像
所有常用的应用程序都有对应的镜像,我们只需要下载这些镜像然后就可以使用了,而不需要自己去手动安装,顶多需要进行一些特别的配置。要是遇到某些冷门的应用,可能没有提供镜像,这时就要我们手动去安装。
构建镜像有两种方式:
- 使用
commit
命令 - 使用
Dockerfile
使用commit
命令构建镜像
举例:在Debian的base镜像中安装Java环境,并将其打包为新的镜像。
启动运行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)退出当前镜像,并将其构建为新的镜像。
docker commit 容器名称/容器ID 新的镜像名称
可以看到,安装了软件之后的镜像大小比原来大得多。虽然这种构建镜像的方式自定义度很高,但是Docker官方并不推荐。使用者不知道镜像是如何构建的,是否有安全隐患,而且构建效率低。作为普通用户,采用Dockerfile的方式会更好一些。
使用Dockerfile
构建镜像
新建Dockerfile文件
1
touch Dockerfile
编辑Dockerfile文件,需要编写多种指令来告诉Docker我们的镜像信息。
1
2
3
4
5# 必须以这个指令开始
FROM <基础镜像>
RUN apt update
RUN apt install -y openjdk-17-jdk每条命令执行之后,都会生成一个新的镜像层。
完成镜像构建。
1
2# docker会在构建目录中寻找Dockerfile文件,并依次执行其中的指令
docker build -t <镜像名称> <构建目录>可以使用
docker history
命令查看构建历史。- 可以看到每条命令的执行都作为了一层镜像。
- 镜像ID为missing的一般是从Docker Hub中下载的镜像会有这个问题,但是问题不大。
发布镜像到远程仓库
可以将自己的镜像发布到Repositories | Docker Hub中,就像Git远程仓库一样。
Create repository
填写信息并创建。
上传本地镜像。
1
2# 使用tag命令重新打标签,创建一个新的本地镜像。
docker tag debian-java-file:latest 用户名/仓库名称:版本登录docker,上传镜像。
1
2
3docker login -u 用户名
docker push 用户名/仓库名称:版本镜像比较大,上传了好几遍才成功。
打开仓库,可以看到已经有上传的镜像版本了。
公共仓库可以被搜索和下载:
1 | docker search hunter1023/debian-java |
容器 container
运行拉取的镜像,就会根据这个镜像生成一个容器。运行的容器就像应用程序,可以访问、可以停止。运用多次Run命令,就运行了很多容器,也可以说是镜像的实例。
容器网络管理
Docker在安装后,会在主机上创建三个网络,使用docker network ls
命令查看:
进入debian容器,安装网络相关软件,并构建为新镜像:
1 | docker pull debian |
none 网络
这个网络除了有一个本地环回网络之外,就你没有其他网络了。可以在创建容器时指定网络。
1 | docker run -it --network=none debian-net |
只有1个本地环回lo
网络设备,因此该容器无法连接到互联网。
bridge 网络
桥接网络,这是容器默认使用的网络类型。可以看到容器的网络接口地址为172.17.0.3
,实际这是Docker创建的虚拟网络。
可以使用docker network inspect bridge
命令查看docker网桥的配置信息。
这里配置的子网是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 | docker network create --driver bridge test |
不同的网络是相互隔离的,例如bridge和test。但是可以将容器连接到另一个容器所属的网络下,实现相互通信。
1 | # 默认以bridge网络创建的容器 |
容器间网络
当两个容器都是使用bridge网络时,在同一个子网中,所以可以互相访问。但是大部分情况下,容器部署之后的IP地址是自动分配的,有没有方法可以灵活地让容器互相访问呢?
可以借助Docker提供的DNS服务器,能够将容器名解析成对应的容器IP地址。但是这个操作只能在用户自定义的网络下生效,默认的网络则做不到。
1 | docker run -itd --name=debian01 --network=test debian-net |
还可以让两个容器共用同一个IP地址:
1 | # 创建容器时,通过 --network=container:[容器名] 即可让容器debian02共用debian01的网络。 |
容器外部网络
在默认的三种网络下,只有host和bridge可以连接到外网。bridge模式实际上就是创建一个单独的虚拟网络,容器在这个虚拟网络中,然后通过桥接器与外界相连。数据包通过虚拟NAT(Network Address Translation)将地址进行转换,再利用宿主主机的IP地址将数据包发送出去。
单纯依靠NAT的话,只有主动与外界联系时,才能被感知。但是我们的容器中可能会部署一些服务,需要外界主动来连接我们。可以通过在启动容器时,通过-p [宿主机端口:容器端口]
配置端口映射。这样,当外部访问到宿主机的对应端口时,就会直接转发给容器内映射的端口。
容器存储管理
容器持久化存储
容器在创建后,实际在容器中创建和修改的文件,实际时被容器的分层机制保存在最顶层的容器层进行操作,以保护下面每一层的镜像不被修改。但是这样也会导致容器在销毁时数据丢失。
在某些情况下,我们可能希望对容器内的某些文件进行持久化存储,这就要用到数据卷(Data Volume)。数据卷用来做数据持久化到我们的宿主机上容器间的数据共享,简单说就是将宿主机的目录映射到容器中的目录,应用程序在容器中的目录读写数据会同步到宿主机上,这样容器产生的数据就可以持久化。
首先在宿主机上创建一个文件,随便写点内容。例如:
接着就能将宿主机上的目录或文件挂载到容器的某个目录上:
1 | # -v: 上的~/docker_practice/volume_test目录 |
这样就能在容器中访问宿主机上的文件。这样,即使将来销毁容器,挂载的数据还能保留。
有时候为了方便,并不需要直接挂载一个目录到容器中,仅仅是从宿主主机传递一些文件到容器中,可以通过docker cp
命令来完成:
1 | docker cp ~/hello.txt [容器名/容器ID]:[容器中的目录] |
容器数据共享
可以通过构建一个专门存放数据的容器,直接将容器中打包好的数据分享给其他容器。本质上依然是一个Docker管理的数据卷,但可移植性很好。
如下一个Dockerfile
:
1 | FROM debian |
1 | # docker会在构建目录中寻找Dockerfile文件,并依次执行其中的指令 |
创建一个容器直接继承该数据容器:
1 | docker run --volume-from=[数据容器名] [容器名/容器ID] |
这样就能实现将数据放在容器中共享,而不需要刻意指定宿主主机的挂载点。就算迁移主机,依然可以快速部署。
Docker自行管理的挂载目录,可以通过docker inspect [容器name/容器id]
命令,再查找关键字Mount
查看,就能找到宿主机上的保存路径:
容器资源管理
限制内存
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
的两倍大小。限制CPU权重
CPU权重默认为1024。
1
2
3
4
5
6
7
8docker 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查看容器资源占用情况
docker stats
查看容器中进程
docker top [容器ID/容器名称]
Docker 命令
- 查看已有镜像
docker iamges
- 镜像下载
镜像的官方资源仓库:Docker Hub,需要的镜像及对应版本都有陈列,且官网有直接的下载命令可以复制。
如docker pull zookeeper:latest
- 删除镜像
docker rmi [image Name]
- 查看所有容器
docker ps [OPTIONS]
OPTION:
-a
: 显示所有容器,包括未运行的--filter
: 根据条件过滤1
2
3
4docker 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
: 静默模式,只显示容器编号
运行容器
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
启动处于停止状态的容器
docker start [CONTAINER NAME/CONTAINER ID]
-i
:进入容器进行交互
进入容器内部
docker exec -it [CONTAINER ID] bash
docker attach [CONTAINER ID]
暂时不清楚这两个命令的具体区别。
不进入容器,执行一条命令
docker exec [CONTAINER ID] [容器中的操作命令]
仅退出容器,不关闭容器
先
Ctrl + P
,再Ctrl + Q
关闭容器
docker stop [CONTAINER ID]
会等待容器处理善后,再关闭容器
强制终止容器
docker kill [CONTAINER ID]
删除容器
docker rm [CONTAINER ID]
-f
:强制删除,即使容器正在运行
- 暂停容器
docker pause [CONTAINER ID]
- 恢复运行容器
docker unpause [CONTAINER ID]
- 打印容器中的日志信息
docker logs [CONTAINER ID]
-f
:持续打印
运行各容器
Debian
1 | # 首次运行 |
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 | # 不带密码运行容器 |
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程序镜像
- 在项目的根目录,创建一个
Dockerfile
。修改内容如下:
1 | FROM debian |
- 由于IDEA自带了Docker插件,直接点击运行按钮即可。
- 首次执行,会弹出的
Edit Run Configuration
框,由于当前是windows环境安装的docker,无需额外的远程连接,直接填写Image tag
即可。Apply之后,点击Close。
- 再点击运行按钮,选择
'
,构建镜像。
- 执行完成后,可以看到刚构建好的镜像。
- 基本环境搭建好了,接着将Spring Boot项目打包。
- 编辑Dockerfile,将构建好的jar包放入,指定在启动时运行Java程序。
1 | FROM debian |
- 在IDEA中,右键镜像,创建对应的容器。
Docker-Compose 进行单机容器编排
比如现在我们要在一台主机上部署很多种类型的服务,包括数据库、消息队列、SpringBoot应用程序若干,或是想要搭建一个MySQL集群,这时我们就需要创建多个容器来完成来。但是我们希望能够实现一键部署,就可以使用Docker-Compose对容器进行编排。
编排 SpringMVC 项目
以使用Tomcat部署SpringMVC项目为例,SpringMVC项目默认打成War包,希望部署SpringMVC项目的同时,部署一个MySQL服务器,并且配置远程Debug。
- 查看是否已安装Docker-Compose
1 | docker compose version |
- 在IDEA中配置Docker-Compose:
- 在IDEA中编写
compose.yaml
文件
war包的包名会被Tomcat默认作为所部署应用的基础URL,形如http://localhost:8080/springmvc_demo-1.0-SNAPSHOT
。如果想变更包名,可以在pom.xml
文件中更改:
1 | <build> |
compose.yaml
文件如下:
1 | services: |
- 点击
compose.yaml
文件右上角的Synchronize with the Services tool window
按钮
就可以在Services标签页查看。
- 执行
docker-compose up
或在Service标签页中,选择Compose进行deploy。
- 查看部署情况,容器全部正在运行,并且会自动为这些容器创建一个bridge网络用于互通。
- 远程调试项目。
创建一个Remote JVM Debug
配置。
一般来说,Java项目的调试端口都选择5005。确定好调试的Host
和port
,与compose.yaml
中的配置保持一致。其实可以看到,填写Host
和Port
,下面的Command line arguments for remote JVM
中会生成相应的命令行参数,其实是和compose.yaml
中的配置一致的。
配置无误的话,开启Debug时,Debug标签页会显示如下内容:
编排 Spring Boot 项目
以部署SpringBoot项目为例,希望部署SpringBoot项目的同时,部署一个MySQL服务器,一个Redis服务器。
- 查看是否已安装Docker-Compose
1 | docker compose version |
- 在IDEA中配置Docker-Compose:
在IDEA中编写
Dockerfile
和compose.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.jarcompose.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
35services: # 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修改项目的
application.yaml
配置文件,与compose.yaml
文件相对应。点击
compose.yaml
文件右上角的Synchronize with the Services tool window
按钮就可以在Services标签页查看。
执行
docker-compose up
或在Service标签页中,选择Compose进行deploy。查看部署情况,容器全部正在运行,并且会自动为这些容器创建一个bridge网络用于互通。
问题
docker run hello-world失败
参考如下,docker的镜像源一定要添加能ping通的。
docker下载镜像超时问题:Get https://registry-1.docker.io/v2/_myydan的博客-CSDN博客