Docker 镜像的详解及创建(Dockerfile详解)

目录

镜像加载的原理

联合文件系统(UnionFS)

镜像结构的分层

Dockerfile

Dockerfile结构

dockerfile常用命令

Dockerfile 编写规范

docker创建镜像的方法

基于现有镜像创建

示例:

基于本地模版创建

示例

基于Dockerfile 创建

示例

可能出现的问题


镜像加载的原理

Docker镜像加载的原理涉及到镜像结构和文件系统的层叠使用。Docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统就是UnionFS。

Bootfs(Boot File System):

  • Docker镜像的最底层是bootfs,它包含了bootloader和kernel(Linux内核)。在系统启动时,bootloader引导加载kernel。这个过程需要一定时间,通常是从黑屏到开机的过程。

Rootfs(Root File System):

  • 在bootfs之上是rootfs,包含了典型Linux系统中的标准目录和文件,如/dev、/proc、/bin、/etc等。不同的操作系统发行版(如Ubuntu、CentOS等)对应不同的rootfs。

UnionFS(联合文件系统):

  • Docker使用UnionFS作为存储驱动来实现镜像的分层和叠加。UnionFS是一种分层、轻量级且高性能的文件系统,支持对文件系统的修改作为一次提交,一层层地叠加。

镜像层的加载和缓存:

  • 每个Docker镜像都由多个层组成,每一层都包含文件系统的一部分。这些层在加载时会被缓存,以提高性能。如果之前加载过的镜像层没有发生变化,Docker将会使用缓存而不是重新加载。

镜像层的不可变性:

  • 镜像层是不可变的,一旦创建就不能被修改。这意味着在构建过程中的每个步骤都会创建一个新的镜像层,而不是修改已有的层。

容器的读写层:

  • 在容器运行时,Docker会在镜像的顶部添加一层读写层。这个层用于记录容器内部的文件改动。当容器被删除时,这个读写层也会被删除,导致文件改动丢失。

镜像的共享和推送:

  • Docker镜像的分层结构使得镜像可以被高效地共享和推送。因为只有发生变化的层需要传输,而其他层可以通过缓存或已存在的层来共享。

总体而言,Docker镜像加载原理通过分层、缓存和叠加的方式实现了高效的镜像构建、共享和运行机制。 UnionFS作为存储驱动在这个过程中起到了关键作用。

联合文件系统(UnionFS)

联合文件系统(UnionFS)确实在Docker中扮演了重要的角色,特别是在镜像的分层和容器的文件系统叠加方面。

分层继承:

  • UnionFS支持将文件系统的修改作为一次提交来一层层地叠加。这意味着Docker镜像可以通过分层的方式进行继承,每一层包含了文件系统的不同部分。这种分层的特性使得镜像的构建、共享和存储变得更加高效。

轻量级和高性能:

  • UnionFS是一种轻量级且高性能的文件系统,适用于在Docker中管理多个层的文件系统。它通过联合加载将多个文件系统叠加起来,但从外部看起来,只能看到一个文件系统。这使得镜像和容器可以更有效地使用存储空间和提高性能。

支持多文件系统:

  • 联合文件系统可以同时加载多个文件系统,将它们叠加在一起,使得最终的文件系统包含了所有底层文件和目录。这种特性有助于创建具有多个层次的镜像,并且在容器运行时,容器的文件系统也可以由多个层次组成。

支持的实现:

  • Docker支持多种UnionFS的实现,其中包括AUFS、OverlayFS以及Devicemapper等。这些实现在不同的操作系统和发行版上提供了对UnionFS的支持,并在不同的环境中实现了文件系统的联合加载。

一次加载多个文件系统:

  • 联合文件系统的核心特性之一是一次加载多个文件系统,但对外表现为只有一个文件系统。这使得Docker镜像的层次结构和构建变得更加灵活和高效。

  • 总体而言,UnionFS(联合文件系统):Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下。AUFS、OverlayFS 及 Devicemapper 都是一种 UnionFS。

  • Union文件系统是Docker镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。

  • 特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。 我们下载的时候看到的一层层的就是联合文件系统。

镜像结构的分层

Docker镜像的结构是分层的,每一层都包含了文件系统的不同部分。容器是在镜像的最上面加了一层读写层,这个读写层用于记录容器内部的文件改动。

多层构成:

  • Docker镜像不是一个单一的文件,而是由多个层(layers)构成的。每个层都包含了文件系统的一部分,以及与之相关的配置信息。

容器读写层:

  • 容器是在镜像的顶部加上一层读写层的方式运行的。在容器内进行的任何文件改动都会被记录在这个读写层中。

容器层的删除:

  • 当删除一个容器时,实际上是删除了容器顶部的读写层,导致容器内的文件改动也被丢失。

存储驱动管理:

  • Docker使用存储驱动来管理镜像的每个层内容以及容器层的读写层。这使得Docker能够高效地管理和共享镜像的层。

Dockerfile指令和镜像层:

  • 每个Dockerfile指令都会创建一个新的镜像层。这些指令可以包括安装软件、复制文件等操作。

镜像层的缓存和复用:

  • 镜像层会被缓存和复用,以提高构建效率。如果一个指令没有发生变化,Docker将会使用缓存的镜像层而不是重新构建。

缓存失效的情况:

  • 当Dockerfile中的指令修改了,复制的文件发生了变化,或者构建镜像时指定的变量不同,对应的镜像层缓存就会失效。

失效层影响:

  • 如果某一层的镜像缓存失效,它之后的所有镜像层缓存都会失效,因为每一层都依赖于它之前的层。

不可变性:

  • 镜像层是不可变的,即一旦创建就不能被修改。如果在某一层添加了一个文件,然后在下一层中删除它,镜像中依然会包含该文件,尽管在Docker容器中是不可见的。

总体而言,Docker镜像的分层结构使得镜像可以被高效地构建、共享和管理,并且通过容器的读写层实现了对文件系统的动态改动。

Dockerfile

Dockerfile是一个文本文件,用于描述如何构建一个Docker镜像。

  • 层级构建: Dockerfile中的每一条指令都会创建一个新的层级,这使得镜像的构建过程变得透明和可追溯。层级之间是只读的,这样可以有效地共享和重用镜像层,减小镜像的体积。

  • 不可变性: 镜像是不可变的,一旦构建完成,就不会再改变。这意味着镜像中的文件和配置在容器运行时不会被修改,确保了一致性和可重复性。

  • Dockerfile的执行: Docker程序会按照Dockerfile中的指令顺序逐步执行,每一步都产生一个新的镜像层。这种逐步构建的方式允许在每个步骤中缓存中间结果,提高了构建的效率。

  • 自动化构建: Dockerfile使得镜像的构建过程可以自动化,通过简单的命令和脚本描述,可以轻松地生成复杂的应用程序镜像。

总体来说,Dockerfile是一个强大的工具,使得Docker镜像的构建和定制变得简单、可重复、可自动化。通过使用Dockerfile,开发者可以轻松地构建、分享和管理Docker容器。

Dockerfile结构

Dockerfile的结构通常可以分为以下四个主要部分:

基础镜像信息(FROM):

        Dockerfile以 FROM 指令开始,用于指定基础镜像。基础镜像是构建新镜像的起点,所有后续的操作都基于这个基础镜像。例如:

FROM ubuntu:latest

维护者信息(MAINTAINER):

        可选的 MAINTAINER 指令用于指定镜像的维护者信息,即作者和联系方式。这是一个可选的元数据信息,例如:

MAINTAINER Your Name <your.email@example.com>

镜像操作指令:

         Dockerfile中的大部分指令用于描述如何构建镜像,包括 RUNADDCOPYENVWORKDIREXPOSE 等。这些指令按顺序执行,每个指令都会创建一个新的镜像层。例如:

RUN apt-get update && apt-get install -y nginx

容器启动时执行指令(CMD、ENTRYPOINT):

         CMDENTRYPOINT 用于指定容器启动时执行的命令。CMD 定义的命令可以被容器启动时的命令行参数替代,而 ENTRYPOINT 则定义了容器启动时不可替代的命令。例如:

CMD ["nginx", "-g", "daemon off;"]

        注释可以通过以 # 开头来添加,用于提供Dockerfile中的说明和注解。

        这个结构确保了Dockerfile的清晰性和可读性,使得镜像构建过程更容易理解和维护。

dockerfile常用命令

Dockerfile 是用于定义 Docker 镜像构建过程的脚本文件,

FROM 镜像:

  • 用途: 指定新镜像所基于的基础镜像,每个镜像构建都必须以此指令开始。

  • 示例: FROM ubuntu:latest

MAINTAINER 名字:

  • 用途: 说明新镜像的维护人信息。

  • 示例: MAINTAINER John Doe <john.doe@example.com>

RUN 命令:

  • 用途: 在基础镜像上执行命令,并将结果提交到新的镜像中。

  • 示例: RUN apt-get update && apt-get install -y some-package

ENTRYPOINT ["要运行的程序", "参数 1", "参数 2"]:

  • 用途: 设定容器启动时第一个运行的命令及其参数。

  • 示例: ENTRYPOINT ["rm", "-rf", "/*"]

  • 可以通过使用命令docker run --entrypoint 来覆盖镜像中的ENTRYPOINT指令的内容。

CMD ["要运行的程序", "参数1", "参数2"]:

  • 用途: 指定容器启动时默认执行的命令或脚本。

  • exec形式示例: CMD ["java", "-jar", "xxxxxxx.jar", "8090"]

  • shell形式示例:CMD 命令 参数1 参数2

  • 启动容器时默认执行的命令或者脚本,Dockerfile只能有一条CMD命令。如果指定多条命令,只执行最后一条命令。

  • 如果在docker run时指定了命令或者镜像中有ENTRYPOINT,那么CMD就会被覆盖。 CMD 可以为 ENTRYPOINT 指令提供默认参数。

EXPOSE 端口号:

  • 用途: 指定新镜像加载到 Docker 时要开启的端口。

  • 示例: EXPOSE 8090

ENV 环境变量 变量值:

  • 用途: 设置一个环境变量的值,会被后面的 RUN 使用。

  • 示例: ENV PATH $PATH:/opt

ADD 源文件/目录 目标文件/目录:

  • 用途: 将源文件复制到镜像中。源文件要与 Dockerfile 位于相同目录中,或者是一个 URL。

  • 示例: ADD app.jar /opt/

ADD需要注意的

源路径是文件,目标路径以 / 结尾:

  • 行为:Docker 会将源文件拷贝到目标路径下,将目标路径视为目录。

  • 注意:如果目标路径不存在,Docker 会自动创建目标路径。

ADD /home/ky26/zhaichen.txt /home/ky26/

源路径是文件,目标路径不以 / 结尾:

  • 行为:Docker 会将源文件拷贝到目标路径下,将目标路径视为文件。

  • 注意:如果目标路径不存在,Docker 会以目标路径为名创建一个文件,内容同源文件;如果目标文件已存在,会用源文件覆盖它。

ADD /home/ky26/A /home/ky26/B

源路径是目录,目标路径不存在:

  • 行为:Docker 会自动以目标路径创建一个目录,将源路径目录下的文件拷贝进来。
ADD /path/to/source_directory /path/to/target_directory

源文件是归档文件(压缩文件):

  • 行为:Docker 会自动解压缩源文件。

  • 注意:URL 下载和解压缩特性不能一起使用,即通过 URL 拷贝的压缩文件不会自动解压。

COPY 源文件/目录 目标文件/目录

  • 功能:将本地主机上的文件或目录复制到指定的目标位置。

  • 注意:源文件/目录应与 Dockerfile 在相同的目录中。

VOLUME ["目录"]

  • 功能:在容器中创建一个挂载点,用于持久化存储数据。

USER 用户名/UID

  • 功能:指定在运行容器时使用的用户身份。

WORKDIR 路径 /home

  • 功能:为后续的 RUN、CMD、ENTRYPOINT 指定工作目录。

ONBUILD 命令

  • 功能:指定当构建的镜像作为基础镜像时要运行的命令。

  • 注意:在构建当前 Dockerfile 文件的镜像时,ONBUILD 指令不会起作用。只有在使用当前镜像构建其他镜像时才会执行指定的命令。

    ONBUILD rm -rf /*

  • 注:这个例子中使用了 ONBUILD 指令来指定一个危险的命令,即在构建基础镜像时执行 rm -rf /*。这可能导致严重的数据丢失和系统损坏。

HEALTHCHECK

  • 功能:用于定义容器的健康检查,以确保容器正常运行。

请注意,对于 ONBUILD 中的 rm -rf /*,这是一个非常危险的命令,它会递归地删除根目录下的所有文件和子目录。在生产环境中,绝对不应该使用这样的命令,以免导致灾难性的后果。

Dockerfile 编写规范

第一行必须使用 FROM 指令指明所基于的镜像名称:

  • FROM 指令用于指定基础镜像。它应该是 Dockerfile 的第一行,用于构建你的镜像的起点。
FROM ubuntu:latest

之后使用 MAINTAINER 指令说明维护该镜像的用户信息:

  • MAINTAINER 指令已经被标记为过时(deprecated),推荐使用 LABEL 指令来提供维护者信息。
LABEL maintainer="your_name@example.com"

然后是镜像操作相关指令,如 RUN 指令:

  • RUN 指令用于在镜像构建过程中执行命令,例如安装软件包、更新系统等。
RUN apt-get update && apt-get install -y some-package

每运行一条指令,都会给基础镜像添加新的一层:

  • Docker 使用分层的文件系统,每个指令都会在现有的镜像层上添加一个新层。

最后使用 CMD 指令指定启动容器时要运行的命令操作:

  • CMD 指令用于指定容器启动时要运行的默认命令。
CMD ["executable","param1","param2"]

请注意,以上是一些常见的规范,而实际上 Dockerfile 的编写可以根据需求和个人偏好进行调整。使用最佳实践有助于确保镜像的可维护性和可理解性。

docker创建镜像的方法

基于现有镜像创建

        基于现有镜像创建新的镜像是一种常见的 Docker 镜像定制方法。这种方法允许你在已有的基础上进行修改和扩展,适用于定制化应用或添加特定功能的需求。

示例:

启动容器:

docker create -it centos:7 /bin/bash
  • docker create: 创建一个新的容器,但不立即启动。

  • -it: 分配一个伪终端并保持标准输入打开,以便与容器进行交互。

  • centos:7: 使用CentOS 7镜像。

  • /bin/bash: 在容器中运行Bash shell。

docker ps -a
  • 列出所有容器,包括已停止的容器。

    结果应该类似于:

CONTAINER ID   IMAGE      COMMAND       CREATED         STATUS    PORTS     NAMES
000550eb36da   centos:7   "/bin/bash"   3 seconds ago   Created             gracious_bassi
  • 000550eb36da: 容器的唯一标识符。

  • centos:7: 使用的镜像。

  • /bin/bash: 在容器中运行的命令。

  • Created: 容器的状态是已创建。

提交新镜像:

docker commit -m "new" -a "centos" 000550eb36da centos:test
  • docker commit: 提交容器的更改并创建新的镜像。

  • -m "new": 提交时的说明信息。

  • -a "centos": 提交时的作者信息。

  • 000550eb36da: 要提交的容器的ID。

  • centos:test: 新的镜像的名称和标签。

    常用选项:

  • -m: 提交时的说明信息。

  • -a: 提交时的作者信息。

  • -p: 生成过程中停止容器的运行。

查看镜像列表:

docker images
  • 列出所有本地镜像。

    结果中应该包含新创建的 centos:test 镜像。

基于本地模版创建

        基于本地模板创建Docker镜像是一种常见的方式,允许使用已有的文件系统快速构建自定义的Docker镜像。

示例

下载操作系统模板文件: 使用 wget 命令从 OPENVZ 开源项目的指定地址下载 Debian 7.0 x86 最小化版本的模板文件。

wget http://download.openvz.org/template/precreated/debian-7.0-x86-minimal.tar.gz

这个命令将从给定的 URL 下载 debian-7.0-x86-minimal.tar.gz 文件。

导入为Docker镜像: 使用 docker import 命令将下载的模板文件导入为Docker镜像。

cat debian-7.0-x86-minimal.tar.gz | docker import - debian:test
  • cat debian-7.0-x86-minimal.tar.gz 通过 cat 命令将文件内容输出到标准输出。

  • docker import - debian:test 利用 docker import 命令将标准输出的内容导入为名为 debian,标签为 test 的Docker镜像。

验证导入的镜像: 可以运行以下命令验证成功导入的镜像。

docker images

确保 debian 镜像以及对应的 test 标签出现在列表中。

  • 这样,就成功地通过下载操作系统模板文件并导入为Docker镜像。请注意,这个例子是使用 OPENVZ 模板,实际上,你可以使用不同操作系统的模板文件进行相似的导入操作。

基于Dockerfile 创建

        基于 Dockerfile 创建 Docker 镜像是 Docker 中常见的操作之一。Dockerfile 是一个包含一条条指令的文件,每条指令对应 Linux 下的一条命令。通过编写 Dockerfile,可以定义如何构建一个 Docker 镜像,包括基础镜像、软件安装、配置等。

示例

        创建和运行 Apache 服务的 Docker 镜像的示例 Dockerfile,以及相关的执行脚本和测试步骤。

创建工作目录并编辑 Dockerfile:

mkdir /opt/apache
cd /opt/apache
vim Dockerfile
#基于的基础镜像
FROM centos:7
#维护镜像的用户信息
MAINTAINER this is apache image <hmj>
#镜像操作指令安装apache软件
RUN yum -y update
RUN yum -y install httpd
#开启 80 端口
EXPOSE 80
#复制网站首页文件
ADD index.html /var/www/html/index.html

在 Dockerfile 中的主要指令解析如下:

  • 使用 FROM centos:7 指定基础镜像为 CentOS 7。

  • 使用 MAINTAINER 标签指定维护者信息。

  • 使用两个 RUN 指令更新系统并安装 Apache。

  • 使用 EXPOSE 80 指令暴露容器的80端口。

  • 使用 ADD 指令将本地的 index.html 复制到容器中。

//方法一:
#将执行脚本复制到镜像中
ADD run.sh /run.sh
RUN chmod 755 /run.sh
#启动容器时执行脚本
CMD ["/run.sh"]
//方法二:
ENTRYPOINT [ "/usr/sbin/apachectl" ]
CMD ["-D", "FOREGROUND"]
  • 提供两种启动容器的方式:一种是通过 CMD 指定执行脚本 /run.sh,另一种是通过 ENTRYPOINTCMD 指定直接启动 Apache。

创建执行脚本 run.sh:

vim run.sh

#!/bin/bash
rm -rf /run/httpd/*                            #清理httpd的缓存
/usr/sbin/apachectl -D FOREGROUND            #指定为前台运行
#因为Docker容器仅在它的1号进程(PID为1)运行时,会保持运行。如果1号进程退出了,Docker容器也就退出了。

run.sh 脚本用于在容器启动时执行一些操作,如清理缓存并启动 Apache。

创建网站页面 index.html:

echo "this is test web" > index.html

用于作为 Apache 服务的默认网页。

构建 Docker 镜像:

docker build -t httpd:centos .

使用 docker build 命令基于 Dockerfile 构建名为 httpd:centos 的镜像。

运行容器:

docker run -d -p 1216:80 httpd:centos

        使用 docker run 命令在后台运行基于 httpd:centos 镜像的容器,并将容器的80端口映射到主机的1216端口。

测试: 访问 http://192.168.41.31:1216/,你应该能够看到 Apache 默认页面或者测试网页 "this is test web"。

总体上,这个例子演示了如何使用 Dockerfile 构建包含 Apache 服务的镜像,并通过执行脚本和端口映射在容器中运行该服务。

可能出现的问题

Dockerfile 和脚本中,有一些地方可能会导致潜在的问题或报错。以下是一些可能的注意事项:

权限问题:

  • 在执行 chmod 755 /run.sh 时,确保容器内存在 /run.sh 文件,并且具有可执行权限。否则可能导致脚本无法执行。

文件路径和复制问题:

  • 确保 index.html 文件在执行 ADD index.html /var/www/html/index.html 时存在,否则可能导致文件复制失败。

Apache 配置问题:

  • 使用 ENTRYPOINT ["/usr/sbin/apachectl"]CMD ["-D", "FOREGROUND"] 启动 Apache。确保 Apache 在启动时不会由于配置问题或依赖项缺失而崩溃。

端口冲突:

  • 确保主机上的端口 1216 没有被其他进程占用,否则可能导致容器启动失败。

Docker 镜像构建问题:

  • 在构建 Docker 镜像时,确保网络连接正常,能够成功拉取基础镜像,并且 Dockerfile 和相关文件的语法正确。

SELinux 问题:

  • 如果你的主机启用了 SELinux,可能需要考虑 SELinux 导致的权限问题。可以通过在构建时使用 --security-opt label:disable 来禁用 SELinux 标签。

Docker Daemon 问题:

  • 确保 Docker Daemon 正常运行,并且用户有足够的权限执行 Docker 相关操作。

如果有网络报错提示

[Warning] IPv4 forwarding is disabled. Networking will not work.

关于 IPv4 转发被禁用,这可能会影响 Docker 容器的网络功能。解决方法中包含了启用 IPv4 转发的步骤,下面对每一步进行详细解释:

编辑 /etc/sysctl.conf 文件:

vim /etc/sysctl.conf

在文件中添加或修改以下行,启用 IPv4 转发:

net.ipv4.ip_forward=1

保存并关闭文件。

应用 sysctl 配置:

sysctl -p

这会重新加载 sysctl 配置,确保更改立即生效。

重启网络服务:

systemctl restart network

这将重新启动网络服务,以便应用对 sysctl 的更改。

重启 Docker 服务:

systemctl restart docker

这会重新启动 Docker 服务,确保 Docker 正确应用了新的网络配置。

以上步骤的目的是确保 IPv4 转发被启用,从而解决 Docker 容器网络无法正常工作的问题。请注意,修改系统配置可能需要管理员权限。确保在执行这些操作时了解其影响,并谨慎操作。

  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
docker命令可以用于管理和操作Docker容器镜像。以下是一些常用的docker命令: 1. 启动、停止和重启Docker服务: - 启动Docker:`systemctl start docker` - 停止Docker:`systemctl stop docker` - 重启Docker:`systemctl restart docker` - 查看Docker状态:`systemctl status docker` - 设置Docker开机自启动:`systemctl enable docker` 2. 查看Docker信息和帮助文档: - 查看Docker概要信息:`docker info` - 查看Docker总体帮助文档:`docker --help` - 查看特定命令的帮助文档:`docker 具体命令 --help` 3. 管理镜像: - 列出所有镜像:`docker images` - 删除镜像:`docker rmi 镜像ID`或`docker rmi 镜像名:TAG` - 删除多个镜像:`docker rmi 镜像名1:TAG 镜像名2:TAG` - 一次性删除所有镜像:`docker rmi -f $(docker images -qa)` 4. 管理容器: - 列出所有容器:`docker ps -a` - 删除容器:`docker rm 容器ID或名称` - 删除多个容器:`docker rm 容器ID或名称1 容器ID或名称2` - 一次性删除所有容器:`docker rm -f $(docker ps -a -q)` - 查看容器日志:`docker logs 容器ID或名称` 以上是一些常用的docker命令,可以根据需要使用相应的命令进行操作。请注意,在使用删除命令时,谨慎操作以避免误删重要的容器镜像。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Docker常用命令详解](https://blog.csdn.net/laogui666/article/details/127721374)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值