软件的特点,就在于一切都是可以快速进行大量复制,从而降低海量重复使用的成本。
Docker 是什么
有时候,我们需要在不同的机器中安装同样的代码运行环境,例如:Windows 跑起一个 SpringBoot 项目需要准备的基本环境。可以说,这些操作是相当的繁琐。
在容器技术诞生以前,我们一般使用虚拟机来解决多机器配置环境问题,例如: CentOS7 从安装到维护的全过程。但虚拟机本身占用系统资源较多,并且启动速度很慢。
在这些需求的驱使下,便诞生了容器技术,包括当前讨论的 Docker,它能够为程序的运行提供一个虚拟化的环境,同时又极小化地占用宿主系统的资源。
Docker 的安装
-
Windows 用户
请参考 Windows10 家庭版安装 Docker Desktop 。安装后需要以管理员身份运行软件方可正常使用。
-
Linux/Unix 用户
请参考 CentOS7 从安装到维护的全过程 - 安装 Docker 。如果由于版本原因无法安装成功,则请参考官方安装教程 Install | Docker Docs 。系统用户需要具有 sudo 权限方可正常使用(可以把用户加入 Docker 用户组)。
命令技巧
由于 docker 命令通常基于 Bash Shell 或 Powershell Core 执行,因此我们可以利用宿主环境实现批量操作:
bash
# 停止所有容器
# Bash Shell 以及 PowerShell v5.1+ 支持
docker stop $(docker ps -aq)
# 删除所有容器
# Bash Shell 以及 PowerShell v5.1+ 支持
docker rm $(docker ps -aq)
您可能不理解上述 Docker 窗口命令的用途与意义,接下来就介绍 Docker 的基本命令。
Docker 基本命令
下面会简要描述 docker 基本命令的使用,主要涉及应用程序开发时所用到的命令。
-
docker version
bash# 查看当前 docker 版本 docker version # 查看某个 docker 命令的使用方式 docker <Command> --help
-
docker search
bash# (从 hub 上)查找镜像文件 # <Keyword> - 模糊的文件名 # -f key=value - 提供过滤功能,如: # 点星的最少数量 stars=5 # 是否为官方镜像 is-official=true docker search [OPTIONS] <Keyword>
由于不可描述之原因,docker 官方已经禁止大陆访问其服务器,因此,搜索镜像时应当采用
${镜像源域名}/${搜索关键字}
的模式,这里假设您的 registry-mirrors 配置了 docker.1ms.run :bashdocker search docker.1ms.run/<Keyword>
-
docker image
操作镜像文件。
bash# 列出本地某个镜像文件的基本信息 # <ImageName|ImageId> - 如果不传,则表示列出本地所有的镜像文件 docker image ls [OPTIONS] <ImageName|ImageId> # 删除一个或多个镜像文件 # -f - 加上该参数表示不再走询问流程便执行删除 docker image rm [OPTIONS] <ImageName|ImageId>... # 从 hub 上拉取某个镜像文件到本地 # 其中官方的 "GroupName/" 即 library/ 是默认组,可省略 # Tag 是版本号,如 0.0.1 这样一个数字,省略 ":<Tag>" 则默认为最新版本 docker image pull [OPTIONS] <GroupName>/<ImageName|ImageId[:<Tag>]> # 查看某个镜像(如何)构建的历史记录 docker image history [OPTIONS] <ImageName|ImageId> # 查看某个镜像的(详细)元信息 docker image inspect [OPTIONS] <ImageName|ImageId> # 从 tar 包中加载镜像文件 # -i <TarArchive> - 指定 tar 包,如 abc.tar docker image load [OPTIONS] -i <TarArchive> # 从标准输入中加载镜像文件 # < <STDIN> - 输入流或标准文件,如 abc.tar.gz docker image load [OPTIONS] < <STDIN> # 将镜像文件打包为 tar 包 # <TarArchive> - 例如 abc.tar docker image save [OPTIONS] <ImageName|ImageId> > <TarArchive> # 将镜像文件打包为 tar 包 # -o <TarArchive> - 指定输出的 tar 包文件名,如 abc.tar docker image save [OPTIONS] <ImageName|ImageId> # 利用管道符使用 gzip 打包镜像文件到 gz 包,如 abc.tar.gz docker image save [OPTIONS] <ImageName|ImageId> | gzip > <GzArchive>
-
docker container
操作容器文件。该系列命令中绝大部分可以省略
container
一词,例如docker container run
可写成docker run
。bash# docker container run = docker container create + docker container start # 运行某个 image 镜像文件,(每次)生成一个容器实例 # 如果本地不存在该 image,则自动从 hub 上先拉取 # -d - 后台启动 # -e - 指定参数(具体需要看镜像本身需要什么参数),例如 -e MYSQL_ROOT_PASSWORD=123456 # --name <ContainerName> - 指定容器名词 # -v <VolumeName|/path/to/host/system>:</path/to/container/directory> - 将名为 VolumeName 的卷 # 或宿主系统的路径 /path/to/host/system 挂载到容器目录 /Directory 。 # 卷默认位于 /var/lib/docker/volumes/<volume-name> 。 # 如果卷未创建,则 docker 会自动将 /Directory 的内容复制一份到卷目录中。 # 宿主系统如果装在虚拟机,路径最好不要是虚拟机的共享文件夹(CentOS9 以上不支持)。 # -p <LocalPort>:[ContainerPort] - 容器的端口 ContainerPort 映射到本机端口 LocalPort # -i - 保持容器的标准输入流打开 # -t - 分配一个伪终端(容器的 Shell 映射到当前窗口的 Shell),结合 -i 开发者就可以在当前窗口与容器交互 # --network <NetworkName> - 指定容器间通信的(已创建好的)网络 # --restart no|always - 是否在容器错误退出时自动重启 # <Command> - 容器启动后内部第一个执行的命令,例如 /bin/bash - 启动 Bash,保证用户可使用 Shell docker container run [OPTIONS] <GroupName>/<ImageName|ImageId[:<Tag>]> [<Command>] # 使用某个镜像创建容器实例,但不启动该容器 docker container create [OPTIONS] <ImageName> # 启动某个已生成或已终止的容器实例 docker container start [OPTIONS] <ContainerName|ContainerID> # 手动终止某个容器实例,正在进行中的操作会丢失 docker container kill [OPTIONS] <ContainerName|ContainerID> # 手动终止某个容器实例,程序可以自行进行收尾清理工作 docker container stop [OPTIONS] <ContainerName|ContainerID> # 列出当前正在运行的容器 # 这个命令比较特殊,它的别名是 docker ps # -a - 列出所有已生成的容器实例,包括终止运行的实例 docker container ls [OPTIONS] # 移除所有已终止的容器文件 # -f - 加上该参数表示不再出现询问流程 docker container prune [OPTIONS] # 移除终止运行后的某个容器文件 docker container rm [OPTIONS] <ContainerName|ContainerID> # 实时显示容器的系统资源占用情况 # <ContainerName|ContainerID> - 某个容器实例的名称或 ID,如果不带则表示全部正在运行的容器 docker container stats [OPTIONS] [<ContainerName|ContainerID>] # 进入一个正在运行中的容器实例 # -it - docker run 命令运行容器时没有使用该参数,则当前就必须使用该参数 # <Command> - 通常是 /bin/bash,以便在容器的 Shell 中进一步执行命令 docker container exec [OPTIONS] <ContainerName|ContainerID> <Command> # 将容器打包成一个新镜像 # -m <UpdateMessage> - 提交的信息,类似于 git commit 的提交信息 # <ImageName[:Tag]> - 镜像的名称与版本。镜像名称与版本均可选填,不填则默认为容器名称 docker container commit [OPTIONS] <ContainerName|ContainerID> <ImageName[:Tag]> # 查看容器实例里面的 Shell 的标准输出 # -it - docker run 命令运行容器时没有使用该参数,则当前就必须使用该参数 docker container logs [OPTIONS] <ContainerName|ContainerID> # 将容器实例里面的文件拷贝到本机(或者把本地文件拷贝到容器中) # <Src>、<Dest> - 它们可以是 <ContainerName|ContainerID>:</path/to/container>/ # 或 </path/to/local> # 或 - (表示标准输入流或标准输出流,从而能够使用管道符进行处理) # 如果 <Src> 是一个目录,则只会复制该目录中的内容到目的目录 docker container cp [OPTIONS] <Src> <Dest>
-
docker volume
操作卷(持久化数据)。卷默认位于 /var/lib/docker/volumes/ 中,每一个卷就是一个文件夹,宿主系统的卷目录与 Docker 容器内部对应目录互相同步文件的增删改。
bash# 创建一个卷 # <VolumeName> - 一个独一无二的卷名,如果不指定,则使用一个随机长字符串作为卷名 docker volume create [OPTIONS] <VolumeName> # 查看一个或多个卷的(详细)元信息 # --format <GoTemplate>|'json' - 打印的格式,默认是 'json'。也可以是 Go 模板 docker volume inspect [OPTIONS] <VolumeName>... # 列出所有卷 # -f key=value - 提供过滤功能,如: # 虚悬的卷 dangling=true # 卷的驱动 driver=local # 卷创建时定义的标签 label=<key> 或 label=<key>=<value> # 卷的名称 name=<VolumeName> # -q - 仅显示卷名 docker volume ls [OPTIONS] # 移除所有没被容器文件关联的匿名(未命名)卷 # -a - 移除所有没被容器文件关联的卷,而不仅仅是未命名的 # -f - 加上该参数表示不再出现询问流程 # --filter key=value - 提供过滤功能,如: # 卷创建时定义的标签 label=<key> 或 label=<key>=<value> docker volume prune [OPTIONS] # 移除一个或多个卷,但不能移除被容器文件使用中的卷 # -f - 加上该参数表示不再出现询问流程 docker volume rm [OPTIONS] <VolumeName>...
-
docker network
操作网络。正常情况下,docker 会为每一个运行中的容器分配一个唯一的 IP 地址,容器可以根据该 IP 地址进行通信。但这 IP 地址不是固定的。而 docker network 可以实现根据容器的名称来作为容器之间的通信域名,从而方便容器之间的通信。
bash# 创建一个网络 # -d - 指定网络驱动,默认值为 bridge # --subnet - 指定子网掩码,例如 192.168.0.0/16 或 172.28.0.0/16 ,16 在这里指多少位 # --gateway - IPv4 或 IPv6 的网关 # --internal - 限制外部访问本网络 # --ipv4 - 是否允许 IPv4 地址分配,默认值为 true # --ipv6 - 是否允许 IPv4 地址分配 docker network create [OPTIONS] <NetworkName> # 连接一个容器到某个网络上 # --ip - 指定该容器的 IPv4 地址 # --ip6 - 指定该容器的 IPv6 地址 docker network connnect [OPTIONS] <NetWorkName> <ContainerName|ContainerID> # 从某个网络上断开一个容器 # -f,--force - 强制断开 docker network disconnnect [OPTIONS] <NetWorkName> <ContainerName|ContainerID> # 显示一个或多个网络的详细信息 # -f 'json'|<GoTemplate> - 输出格式,可选 'json' 或 Go 模板 # -v - 更多的信息 docker network inspect [OPTIONS] <NetWorkName>... # 列出当前 Docker 的全部网络 # -f,--filter - 提供过滤条件,如 -f driver=bridge # -q - 输出结果仅显示网络 ID docker network ls [OPTIONS] # 删除所有未被使用的网络 # --filter - 过滤条件,如 --filter until=<Timestamp> # -f - 加上该参数表示不再出现询问流程 docker system prune [OPTIONS] # 删除一个或多个网络 # -f - 当网络不存在时不报错 docker network rm [OPTIONS] <NetWorkName>...
-
docker system
管理 docker 整个系统。主要用于查看 docker 文件的统计数据,以及对系统资源的消耗情况。
bash# 简要显示磁盘占用情况 # -v - 显示更详细的统计信息,包括每一个镜像文件、每一个容器文件、每一个卷的相关信息 docker system df [OPTIONS] # 打印 docker 内部的各种(容器的、镜像的、插件的、卷的、网络的、守护进程的...)事件 # 最多返回 256 个 # -f 'key=value' - 过滤条件 # key 可以是 container、image、plugin、network、daemon ... # 而 value 则是相应的 Name 或 ID # key 也可以是 type # 而 value 则是 container、image、plugin、network、daemon ... # --format <GoTemplate>|'json' - 打印的格式,默认是系统格式。GoTemplate 即 Go 语言的模板格式 # --since <Timestamp> - 只展示从某个时刻开始的事件 # --until <Timestamp> - 只展示直到某个时刻为止的事件 docker system events [OPTIONS] # 显示 docker 的安装信息以及当前统计状态 docker system info [OPTIONS] # 删除所有未被使用的容器、镜像、网络、卷等 # -f - 加上该参数表示不再出现询问流程 docker system prune [OPTIONS]
-
docker buildx
使用 BuildKit 扩展构建(镜像)的功能。
bash# (使用 BuildKit) 开始一次(镜像)构建。简写:docker build # -f <FilePath> - 指定 Dockerfile。默认就是当前目录中的全名为 Dockerfile 的文件 # -t <ImageName>:<Tag> - 指定生成的镜像名以及标签,建议同时定义版本以及 latest # <Context> - 构建镜像时的上下文目录,可以是 PATH | URL | - 。一般会使用 `.` docker buildx build [OPTIONS] <Context>
上述命令提到了 Dockerfile,它具体有哪些指令请参考下一节。
Dockerfile
Dockerfile 文件的全名就叫 Dockerfile ,通常放置于项目的根目录中。它的作用是集成一系列的指令将本地应用程序及文件构建为一个镜像文件:docker build [-f Dockerfile]
。
一个镜像,至少会包含 基础环境 、软件包 、启动命令 三项。
-
FROM
指定一个软件运行的基础镜像,即后续指令的运行环境。所以 Dockerfile 的第一行有效指令(非
ARG
指令)必须是FORM
。bash# <platform> 可以是 `linux/amd64`,`linux/arm64`,或 `windows/amd64` FORM [--platform=<platform>] <image[:<tag>]> [AS <name>]
-
RUN
构建 image 文件阶段执行该关键字后面的命令。一个 Dockerfile 可以有多个
RUN
命令。RUN
后接命令支持 shell 格式以及 exec 格式:shell 格式:
<command> param1 param2
,command 即系统内置的应用。exec 格式:
["executable","param1","param2"]
,其中 executable 包括所有 command 。bash# 打包 image 过程中才会执行 RUN [OPTIONS] <command>|["executable"]
-
CMD
容器启动完成后执行该命令。一个 Dockerfile 只能有一个
CMD
命令。docker container run
后面指定的命令可以覆盖CMD
指令。CMD
后接命令也支持 shell 格式以及 exec 格式。bash# 容器启动后才会执行 CMD <command>|["executable"] # CMD ["exec_cmd", "p1_cmd"] exec_cmd p1_cmd # CMD exec_cmd p1_cmd /bin/sh -c exec_cmd p1_cmd
-
LABEL
自定义标签。用于为镜像文件添加元数据。使用
docker image inspect <image>
可以查看到定义好的标签。bash# <key> 可以用双引号包裹或不包裹。<value> 必须用双引号包裹 LABEL <key>=<value> [<key>=<value>...]
-
EXPOSE
指定暴露端口。用于告知 Docker 当前容器运行时监听了哪些端口。可以指定
<protocol>
即 TCP (默认) 或 UDP 。执行docker run -p <system-port>:<container-port> <image>
中的 -p 可以覆盖EXPOSE
的设置。bashEXPOSE <port> [<port>/<protocol>...]
-
ENV
设置环境变量(键值对)。设置的变量可以被后续的指令直接使用,也可以在镜像运行时使用。如果值中含有空格等转移字符,可以使用双引号包裹整个值,也可以使用
\
转义符来修饰待转义字符。bashENV <key>=<value> [<key>=<value>...] # 使用已声明的变量 RUN echo $key
-
ADD
添加文件到镜像内部。
ADD
后接地址支持 shell 格式以及 exec 格式:shell 格式:
<src> ... <dest>
。exec 格式:
["<src>", ..., "<dest>"]
。其中,src 是指当前宿主系统中的 目录、文件、压缩包,或 网络地址、 GIT 仓库地址,可以有多个。dest 是镜像中的目录或文件,只能有一个,添加的文件默认 uid 和 gid 为 0。dest 目录或文件,都是相对于构建上下文(不能使用 ../ 这样的路径跳出构建上下文)。不同的 src 类型的操作如下:
- 目录 - 将源目录中的内容(不包括该目录)复制至目标目录。
- 文件 - 如果目标地址是目录(以 / 结尾),则将文件复制至目标目录;否则,复制并重命名为目标文件。
- 压缩包 - 支持 tar、gzip、bzip2、xz,自动解压至目标目录,目标目录的内部结构就是压缩包的内部结构。
- 网络 URL - 下载该文件到目标目录,并且不会对下载的压缩包解压。
- GIT 仓库地址 - 相当于在目标目录中执行了
git clone
命令。
bash# shell 格式 ADD [OPTIONS] <src> ... <dest> # exec 格式 ADD [OPTIONS] ["<src>", ... "<dest>"]
-
COPY
复制系统文件到镜像内部。相比较于
ADD
,当遇到不需要自动解压的情况,优先使用COPY
。COPY
后接地址也支持 shell 格式以及 exec 格式。bash# shell 格式 COPY [OPTIONS] <src> ... <dest> # exec 格式 COPY [OPTIONS] ["<src>", ... "<dest>"]
-
ENTRYPOINT
容器固定启动命令。通常用于将容器作为可执行程序使用。可以使用
docker run --entrypoint
命令覆盖ENTRYPOINT
后接的命令。ENTRYPOINT
后接命令支持 shell 格式以及 exec 格式:shell 格式:
<command> param1 param2
,command 即系统内置的应用。exec 格式:
["executable","param1","param2"]
,其中 executable 包括所有 command 。bashENTRYPOINT <command>|["executable"]
Dockerfile 文件应当至少指定一个
CMD
或ENTRYPOINT
命令。如果开发者希望容器被当成一个可执行文件,就使用ENTRYPOINT
。如果使用了ENTRYPOINT
,CMD
则应当用来为ENTRYPOINT
提供默认参数。如果不使用ENTRYPOINT
,则CMD
应当提供一个完整的执行命令来作为容器启动后的默认命令。如果 Dockerfile 中含有完整的
CMD
以及ENTRYPOINT
命令(即既有可执行命令,也有参数),那么优先级为:-
shell 格式的
ENTRYPOINT
命令会覆盖掉CMD
命令; -
exec 格式的
ENTRYPOINT
命令会与CMD
命令合并成一条命令。bash# ENTRYPOINT ["exec_entry", "p1_entry"] # CMD ["exec_cmd", "p1_cmd"] exec_entry p1_entry exec_cmd p1_cmd # ENTRYPOINT ["exec_entry", "p1_entry"] # CMD exec_cmd p1_cmd exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd
-
-
VOLUME
指定数据卷。用于挂载外部主机目录或容器,通常用于 Docker 与主机共享目录。
VOLUME
后接地址支持 shell 格式以及 exec 格式:shell 格式:
addr1 ... addrN
。exec 格式:
["addr1", ..., "addrN"]
。bash# 挂载多个主机目录 VOLUME ["/data", "/var/log"]
为了容器的可移植性,Dockerfile 不应当声明
VOLUME
挂载主机目录。指定挂载点应当交给容器的创建时刻或运行时刻。 -
USER
指定用户及用户组。用于指定
ENTRYPOINT
以及CMD
命令的运行用户(用户组)。如果容器在 Windows 中运行,则该用户必须预先创建好:RUN net user /add UserName
。bashUSER <user>[:<group>] # 或 USER <UID>[:<GID>]
-
WORKDIR
指定默认工作目录(为
RUN
、CMD
、ENTRYPOINT
、COPY
、ADD
指令)。如果被指定的目录不存在,Docker 则会自动创建该目录。如果不指定,则默认的目录为/
(实际上,默认的工作目录会被基础镜像所指定),所以建议开发者明确指定工作目录。bashWORKDIR /path/to/workdir
WORKDIR
在 Dockerfile 中可以多次使用。如果后一个指定目录是相对目录,则它是前一个工作目录的相对目录。 -
ARG
获取构建参数(为
docker build
命令)。该参数只在构建时生效,不会被打包到生成的镜像中。bash# 仅从指令 docker build --build-arg <VAR_KEY>=<VAR_VALUE> 获取参数值 ARG VAR_KEY # 指定参数的默认值(如果构建命令不指定该参数) ARG VAR_KEY=VAR_DEFAULT_VALUE # 使用方式:${VAR_KEY} FROM base:${VAR_KEY}
-
示例
DockerfileFROM openjdk:8-jdk-alpine COPY demo.jar /app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/app.jar"]
学习了 Dockerfile,结合之前掌握的 Docker 窗口命令,我们就能构建一个完整的 Docker 镜像了。但是,现实的开发往往需要我们构建多个模块(例如前端 web 和后端 backend)。而我们希望能一键构建多个 Docker 镜像,以及批量管理容器。因此,我们有必要学习 Docker Compose。
Docker Compose
Docker Compose 默认使用 compose.yaml
或 compose.yml
或 docker-compose.yaml
或 docker-compose.yml
来实现批量的容器操作。
-
操作命令
-
上线
bash# 启动 compose.yaml 文件中指定的全部容器(服务) # --env-file <PathToEnv> - .env 文件所处位置 docker compose [OPTIONS] up -d
-
下线
bash# 关闭 Docker 中所有关于 compose.yaml 文件中指定的容器(服务) docker compose [OPTIONS] down
-
启动
bash# 重启 compose.yaml 文件中指定的部分容器(服务),不能是首次启动 docker compose [OPTIONS] start x1 x2 x3
-
停止
bash# 停止 compose.yaml 文件中指定的部分容器(服务) docker compose [OPTIONS] start x1 x2 x3
-
扩容
bash# 将容器(服务) x1 启动共 3 份 docker compose scale x1=3
-
-
基本语法
yml# 名称 name: <ProjectName> # 服务 service: # docker run -d -p 3306:3306 \ # -e MYSQL_ROOT_PASSWORD=123456 \ # -e MYSQL_DATABASE=dbname \ # -v mysqldata:/var/lib/mysql \ # -v /app/myconf:/etc/mysql/conf.d \ # --restart always \ # --name db # --network projectnet # mysql:8.0 # # 服务(容器)名称 db: # 生成容器名称,不指定时默认为 <项目名>_<服务名>_<序号>,其中序号从 1 开始,到 scale 个结束 container_name: my-db # 所依赖镜像 image: mysql:8.0 # 监听端口 (外部端口:容器端口) ports: - "3306:3306" # 环境变量 environment: # 在本 compose.yml 文件的同级目录中创建 .env 文件,并填写以下两行常量声明: # DB_PASSWORD=123456 # DB_NAME=a_db_name # 那么,本 compose.yml 中便可引用这些声明的常量 - MYSQL_ROOT_PASSWORD=${DB_PASSWORD} - MYSQL_DATABASE=${DB_NAME} # 关联卷 (卷名或外部地址:容器内部地址) volumes: - mysqldata:/var/lib/mysql - /app/myconf:/etc/mysql/conf.d # 是否自动重启 restart: always # 关联网络 networks: - projectnet # docker run -d -p 8080:80 \ # -v webpage:/var/www/html \ # --restart always \ # --name web \ # --network projectnet \ # frontend-demo:1.0.0 # # 服务(容器)名称 web: # 所依赖镜像 image: frontend-demo:1.0.0 # 监听端口 (外部端口:容器端口) ports: - "8080:80" # 环境变量 environment: # 在本 compose.yml 文件的同级目录中创建 .env 文件,并填写以下两行常量声明: # APP_ALLOW_EMBED=true # APP_PM2_INSTANCES=3 # 那么,本 compose.yml 中便可引用这些声明的常量 # # 报错:"APP_API_URL not defined" API_URL: ${APP_API_URL?APP_API_URL not defined in env} # 默认值为 false ALLOW_EMBED: ${APP_ALLOW_EMBED:-false} # 默认值为 2 PM2_INSTANCES: ${APP_PM2_INSTANCES:-2} # 关联卷 (卷名或外部地址:容器内部地址) volumes: - webpage:/var/www/html # 是否自动重启 restart: always # 关联网络 networks: - projectnet # 依赖服务 depends_on: - db # 网络 networks: # 声明网络名 projectnet: # 卷 volumes: # 声明卷名 mysqldata: webpage: # 配置 configs: # 密钥 secrets:
小结
本文是 Docker 的语法入门篇。非常简单,后续有机会就把通用的 compose.yaml
文档贴出,以便大家能够懒人式地使用 Docker。