第2天-Docker容器管理及镜像制作

10 篇文章 1 订阅

第2天 Docker容器管理及镜像制作

一、Docker容器管理

创建新容器但不启动
[root@qfedu.com ~]# docker create -it daocloud.io/library/centos:5 /bin/bash

创建并运行一个新Docker 容器
同一个镜像可以启动多个容器,每次执行run子命令都会运行一个全新的容器
[root@qfedu.com ~]# docker run -it --restart=always centos /bin/bash
   如果执行成功,说明CentOS 容器已经被启动,并且应该已经得到了 bash 提示符。
  -i  
   捕获标准输入输出
  -t  
   分配一个终端或控制台
  --restart=always  
   容器随docker engine自启动,因为在重启docker的时候默认容器都会被关闭  
   也适用于create选项    
  --rm
   默认情况下,每个容器在退出时,它的文件系统也会保存下来,这样一方面调试会方便些,因为你可以通过查看日志等方式来确定最终状态。另一方面,也可以保存容器所产生的数据。
    但是当你仅仅需要短暂的运行一个容器,并且这些数据不需要保存,你可能就希望Docker能在容器结束时自动清理其所产生的数据。这个时候就需要--rm参数了。注意:--rm 和 -d不能共用

  容器名称
  --name= Assign a name to the container   
          --为容器分配一个名字,如果没有指定,docker会自动分配一个随机名称
          是docker run子命令的参数

  可以通过三种方式调用容器命名:
  1)使用UUID长命名("f78375b1c487e03c9438c729345e54db9d20cfa2ac1fc3494b6eb60872e74778")
  2)使用UUID短Id("f78375b1c487")
  3)使用Name("evil_ptolemy") 

  这个UUID标识是由Docker deamon生成的。
  如果你在执行docker run时没有指定--name,那么deamon会自动生成一个随机字符串UUID。
  但是对于一个容器来说有个name会非常方便,当你需要连接其它容器时或者类似需要区分其它容器时,使用容
  器名称可以简化操作。无论容器运行在前台或者后台,这个名字都是有效的。

保存容器PID equivalent:
如果在使用Docker时有自动化的需求,你可以将containerID输出到指定的文件中(PIDfile),类似于某些应用程序将自身ID输出到文件中,方便后续脚本操作。
--cidfile="": Write the container ID to the file

断开与容器的连接,并且关闭容器:
root@d33c4e8c51f8 /#exit

如果只想断开和容器的连接而不关闭容器:
快捷键:ctrl+p+q

查看容器:
  只查看运行状态的容器:
    [root@qfedu.com ~]#docker ps
    [root@qfedu.com ~]#docker ps -a
    -a  查看所有容器
	
  只查看所有容器id:
		[root@qfedu.com ~]# docker ps -a -q

  列出最近一次启动的容器(了解)
		[root@qfedu.com ~]# docker ps -l  

 	查看容器详细信息
    inspect  Return low-level information on a container or image
    用于查看容器的配置信息,包含容器名、环境变量、运行命令、主机配置、网络配置和数据卷配置等。
    目标:
    查找某一个运行中容器的id,然后使用docker inspect命令查看容器的信息。

    提示:可以使用镜像id的前面部分,不需要完整的id。
    [root@qfedu.com ~]# docker inspect d95   //d95是我机器上运行的一个容器ID的前3个字符
    [
    {
       "Id": "d95a220a498e352cbfbc098c949fc528dbf5a5c911710b108ea3a9b4aa3a4761",
        "Created": "2017-07-08T03:59:16.18225183Z",
        "Path": "bash",
        "Args": [],
        "State": {
          "Status": "exited",
          "Running": false,
          "Paused": false,
          "Restarting": false,
          "OOMKilled": false,
          "Dead": false,
          "Pid": 0,

容器信息很多,这里只粘贴了一部分

比如:容器里在安装ip或ifconfig命令之前,查看网卡IP显示容器IP地址和端口号,如果输出是空的说明没有配置IP地址(不同的Docker容器可以通过此IP地址互相访问)

[root@qfedu.com ~]# docker inspect --format='{{.NetworkSettings.IPAddress}}'  容器id

列出所有绑定的端口

[root@qfedu.com ~]# docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}} {{$p}} {{(index $conf 0).HostPort}} {{end}}' $INSTANCE_ID
[root@qfedu.com ~]# docker inspect --format='{{range $p, 
$conf := .NetworkSettings.Ports}} {{$p}} -> {{(index $conf 0).HostPort}} {{end}}' 
b220fabf815a
22/tcp -> 20020

找出特殊的端口映射

比如找出容器里22端口所映射的docker本机的端口

[root@qfedu.com ~]# docker inspect --format='{{(index (index .NetworkSettings.Ports "22/tcp") 
0).HostPort}}' $INSTANCE_ID
[root@qfedu.com ~]# docker inspect --format='{{(index (index .NetworkSettings.Ports "22/
tcp") 0).HostPort}}' b220fabf815a
20020
启动容器
[root@qfedu.com ~]# docker start  name 

关闭容器:
[root@qfedu.com ~]# docker stop  name
[root@qfedu.com ~]# docker kill   name    --强制终止容器

杀死所有running状态的容器
[root@qfedu.com ~]# docker kill $(docker ps  -q)  

stop和kill的区别

docker stop命令给容器中的进程发送SIGTERM信号,默认行为是会导致容器退出,当然,容器内程序可以捕获该信号并自行处理,例如可以选择忽略。而docker kill则是给容器的进程发送SIGKILL信号,该信号将会使容器必然退出。

删除容器
[root@qfedu.com ~]# docker rm 容器id或名称
要删除一个运行中的容器,添加 -f 参数

根据格式删除所有容器:
[root@qfedu.com ~]# docker rm $(docker ps -qf status=exited)

重启容器:
[root@qfedu.com ~]#docker restart name

暂停容器:
pause  --暂停容器内的所有进程,
  通过docker stats可以观察到此时的资源使用情况是固定不变的,通过docker logs -f也观察不到日志的进一步输出。

恢复容器:
unpause  --恢复容器内暂停的进程,与pause参数相对应
[root@qfedu.com ~]# docker start infallible_ramanujan  
这里的名字是状态里面NAMES列列出的名字,这种方式同样会让容器运行在后台

让容器运行在后台:
如果在docker run后面追加-d=true或者-d,那么容器将会运行在后台模式。此时所有I/O数据只能通过网络资源或者共享卷组来进行交互。因为容器不再监听你执行docker run的这个终端命令行窗口。但你可以通过执行

docker attach来重新附着到该容器的回话中。
注:
容器运行在后台模式下,是不能使用--rm选项的(老版本是这样,新版本已经可以同时生效) 
[root@qfedu.com ~]#docker run -d IMAGE[:TAG] 命令
[root@qfedu.com ~]#docker logs container_id  [root@qfedu.com ~]#打印该容器的输出
[root@qfedu.com ~]# docker run -it -d --name mytest docker.io/centos /bin/sh -c "while true; do echo hello world; sleep 2; done"
37738fe3d6f9ef26152cb25018df9528a89e7a07355493020e72f147a291cd17

[root@qfedu.com ~]# docker logs mytest
hello world
hello world

docker attach container_id #附加该容器的标准输出到当前命令行
[root@qfedu.com ~]# docker attach mytest
hello world
hello world
.......
此时,ctrl+d等同于exit命令,按ctrl+p+q可以退出到宿主机,而保持container仍然在运行

rename 
  Rename a container

stats   
  Display a live stream of container(s) resource usage statistics   
  --动态显示容器的资源消耗情况,包括:CPU、内存、网络I/O    

port   
  List port mappings or a specific mapping for the CONTAINER
   --输出容器端口与宿主机端口的映射情况
[root@qfedu.com ~]# docker port blog
   80/tcp -> 0.0.0.0:80
   容器blog的内部端口80映射到宿主机的80端口,这样可通过宿主机的80端口查看容器blog提供的服务

连接容器:   
方法1.attach
[root@qfedu.com ~]# docker attach 容器id  //前提是容器创建时必须指定了交互shell

方法2.exec    
  通过exec命令可以创建两种任务:后台型任务和交互型任务
  交互型任务:
   [root@qfedu.com ~]# docker exec -it  容器id  /bin/bash
   root@68656158eb8e:/ ls    

  后台型任务:
	 [root@qfedu.com ~]# docker exec 容器id touch /testfile

监控容器的运行:
可以使用logs、top、events、wait这些子命令
  logs:
    使用logs命令查看守护式容器
    可以通过使用docker logs命令来查看容器的运行日志,其中--tail选项可以指定查看最后几条日志,而-t选项则可以对日志条目附加时间戳。使用-f选项可以跟踪日志的输出,直到手动停止。
    [root@qfedu.com ~]# docker logs   App_Container  //不同终端操作
    [root@qfedu.com ~]# docker logs -f App_Container

  top:
  显示一个运行的容器里面的进程信息
 		[root@qfedu.com ~]# docker top birdben/ubuntu:v1

  events   
    Get real time events from the server
    实时输出Docker服务器端的事件,包括容器的创建,启动,关闭等。
  	[root@qfedu.com ~]# docker start loving_meninsky
    	loving_meninsky
  	[root@qfedu.com ~]# docker events  //不同终端操作
      2017-07-08T16:39:23.177664994+08:00 network connect  
      df15746d60ffaad2d15db0854a696d6e49fdfcedc7cfd8504a8aac51a43de6d4 
      (container=50a0449d7729f94046baf0fe5a1ce2119742261bb3ce8c3c98f35c80458e3e7a, 
      name=bridge, type=bridge)
      2017-07-08T16:39:23.356162529+08:00 container start 
      50a0449d7729f94046baf0fe5a1ce2119742261bb3ce8c3c98f35c80458e3e7a (image=ubuntu, 
      name=loving_meninsky)

  wait(X)   
   Block until a container stops, then print its exit code  
    --捕捉容器停止时的退出码
   执行此命令后,该命令会"hang"在当前终端,直到容器停止,此时,会打印出容器的退出码
 		[root@qfedu.com ~]# docker wait 01d8aa  //不同终端操作
 		137

  diff
    查看容器内发生改变的文件,以elated_lovelace容器为例
 			root@68656158eb8e:/# touch c.txt

    用diff查看:
    包括文件的创建、删除和文件内容的改变都能看到 
			[root@qfedu.com ~]# docker diff  容器名称
    	A /c.txt

    C对应的文件内容的改变,A对应的均是文件或者目录的创建删除
    [root@qfedu.com ~]# docker diff 7287
        A /a.txt
        C /etc
        C /etc/passwd
        A /run
        A /run/secrets   

宿主机和容器之间相互Copy文件
cp的用法如下:
  docker cp [OPTIONS] CONTAINER:PATH LOCALPATH
  docker cp [OPTIONS] LOCALPATH CONTAINER:PATH
如:容器mysql中/usr/local/bin/存在docker-entrypoint.sh文件,可如下方式copy到宿主机
	[root@qfedu.com ~]# docker cp mysql:/usr/local/bin/docker-entrypoint.sh  /root

修改完毕后,将该文件重新copy回容器
	[root@qfedu.com ~]# docker cp /root/docker-entrypoint.sh mysql:/usr/local/bin/    

二、Docker容器镜像制作

1、容器文件系统打包

将容器的文件系统打包成tar文件,也就是把正在运行的容器直接导出为tar包的镜像文件

export Export a container’s filesystem as a tar archive

有两种方式(elated_lovelace为容器名)

第一种:
  [root@master ~][root@qfedu.com ~]# docker export -o elated_lovelace.tar elated_lovelace
第二种:
  [root@master ~][root@qfedu.com ~]# docker export 容器名称 > 镜像.tar

导入镜像归档文件到其他宿主机

import Import the contents from a tarball to create a filesystem image

  [root@qfedu.com ~]# docker import elated_lovelace.tar  elated_lovelace:v1

注意:

如果导入镜像时没有起名字,随后可以单独起名字(没有名字和tag),可以手动加tag

 [root@qfedu.com ~]# docker  tag  镜像ID  mycentos:7

2、通过容器创建本地镜像

背景:

​ 容器运行起来后,又在里面做了一些操作,并且要把操作结果保存到镜像里

方案:

​ 使用 docker commit 指令,把一个正在运行的容器,直接提交为一个镜像。commit 是提交的意思,类似告诉svn服务器我要生成一个新的版本。

例:在容器内部新建了一个文件

[root@qfedu.com ~]# docker exec -it 4ddf4638572d /bin/sh  
root@4ddf4638572d# touch test.txt
root@4ddf4638572d# exit
将这个新建的文件提交到镜像中保存
[root@qfedu.com ~]# docker commit 4ddf4638572d wing/helloworld:v2

例:

[root@qfedu.com ~]# docker commit -m "my images version1" -a "wing" 108a85b1ed99 daocloud.io/ubuntu:v2
  sha256:ffa8a185ee526a9b0d8772740231448a25855031f25c61c1b63077220469b057
  -m                   添加注释
  -a                   作者
  108a85b1ed99         容器环境id
  daocloud.io/ubuntu:v2    镜像名称:hub的名称/镜像名称:tag 
  -p,–pause=true        提交时暂停容器运行

Init 层的存在,是为了避免执行 docker commit 时,把 Docker 自己对 /etc/hosts 等文件做的修改,也一起提交掉。

3、镜像迁移

保存一台宿主机上的镜像为tar文件,然后可以导入到其他的宿主机上:

save Save an image(s) to a tar archive

将镜像打包,与下面的load命令相对应

[root@qfedu.com ~]# docker save -o nginx.tar nginx
[root@qfedu.com ~]# docker save > nginx.tar nginx

load Load an image from a tar archive or STDIN

与上面的save命令相对应,将上面sava命令打包的镜像通过load命令导入

[root@qfedu.com ~]# docker load < nginx.tar

注:

1.tar文件的名称和保存的镜像名称没有关系

2.导入的镜像如果没有名称,自己打tag起名字

4、通过Dockerfile创建镜像

虽然可以自己制作 rootfs(见’容器文件系统那些事儿’),但Docker 提供了一种更便捷的方式,叫作 Dockerfile

docker build命令用于根据给定的Dockerfile和上下文以构建Docker镜像。

docker build语法

[root@qfedu.com ~]# docker build [OPTIONS] <PATH | URL | ->

选项说明

–build-arg,设置构建时的变量

–no-cache,默认false。设置该选项,将不使用Build Cache构建镜像

–pull,默认false。设置该选项,总是尝试pull镜像的最新版本

–compress,默认false。设置该选项,将使用gzip压缩构建的上下文

–disable-content-trust,默认true。设置该选项,将对镜像进行验证

–file, -f,Dockerfile的完整路径,默认值为‘PATH/Dockerfile’

–isolation,默认–isolation=“default”,即Linux命名空间;其他还有process或hyperv

–label,为生成的镜像设置metadata

–squash,默认false。设置该选项,将新构建出的多个层压缩为一个新层,但是将无法在多个镜像之间共享新层;设置该选项,实际上是创建了新image,同时保留原有image。

–tag, -t,镜像的名字及tag,通常name:tag或者name格式;可以在一次构建中为一个镜像设置多个tag

–network,默认default。设置该选项,Set the networking mode for the RUN instructions during build

–quiet, -q ,默认false。设置该选项,Suppress the build output and print image ID on success

–force-rm,默认false。设置该选项,总是删除掉中间环节的容器

–rm,默认–rm=true,即整个构建过程成功后删除中间环节的容器

PATH | URL | -说明

给出命令执行的上下文。

docker build需要的各种文件必须放到上下文目录中

上下文可以是构建执行所在的本地路径,也可以是远程URL,如Git库、tarball或文本文件等。

如果是Git库,如https://github.com/docker/rootfs.git#container:docker,则隐含先执行git clone --depth 1 --recursive,到本地临时目录;然后再将该临时目录发送给构建进程。

构建镜像的进程中,可以通过ADD命令将上下文中的任何文件(注意文件必须在上下文中)加入到镜像中。

-表示通过STDIN给出Dockerfile或上下文。

示例:

  docker build - < Dockerfile 

说明:该构建过程只有Dockerfile,没有上下文

 docker build - < context.tar.gz

说明:其中Dockerfile位于context.tar.gz的根路径

可以同时设置多个tag

  [root@qfedu.com ~]# docker build -t champagne/bbauto:latest -t champagne/bbauto:v2.1 . 

创建镜像所在的文件夹和Dockerfile文件

mkdir dockerfile-test 
cd dockerfile-test 
touch Dockerfile 

在Dockerfile文件中写入指令,每一条指令都会更新镜像的信息例如:

[root@qfedu.com ~]# vim Dockerfile 
[root@qfedu.com ~]# This is a comment 
FROM  daocloud.io/library/centos:7 
MAINTAINER wing wing@qfedu.com
RUN  命令1;命令2;命令3
RUN  命令

格式说明

​ 每行命令都是以 INSTRUCTION statement 形式,就是命令+ 清单的模式。命令要大写,"#"是注解。

​ FROM 命令是告诉docker 我们的镜像什么。

​ MAINTAINER 是描述 镜像的创建人。

​ RUN 命令是在镜像内部执行。就是说他后面的命令应该是针对镜像可以运行的命令。

创建镜像

  [root@qfedu.com ~]# docker build  -t  centos:v2 . 
     docker build  是docker创建镜像的命令 
     -t  镜像名称  
     "." 上下文目录,如果不用-f指定Dockerfile文件的位置和名称,默认会从上下文目录也就是这里指定的当前目录查找 

2.4、创建完成后,用这个镜像运行容器

[root@qfedu.com ~]# docker run -it centos:v2 /bin/bash

–build-arg应用示例

# cat Dockerfile 
FROM daocloud.io/library/nginx:1.7.9
MAINTAINER wing wing@qq.com
ARG TEST
ARG TEST1
RUN echo $TEST > /a.txt
RUN echo $TEST1 > /b.txt

# docker build --build-arg TEST=123 --build-arg TEST1=456 -t nginx:v11 . 

5、Dockerfile实例01:容器化Python的Flask应用

目标

​ 用 Docker 部署一个用 Python 编写的 Web 应用。

应用代码部分

​ 代码功能:如果当前环境中有"NAME"这个环境变量,就把它打印在"Hello"后,否则就打印"Hello world",最后再打印出当前环境的 hostname。

[root@qfedu.com ~]# mkdir python_app
[root@qfedu.com ~]# cd python_app
[root@qfedu.com ~]# vim app.py
from flask import Flask
import socket
import os
app = Flask(__name__)
@app.route('/')
def hello():
  html = "<h3>Hello {name}!</h3>" \
      "<b>Hostname:</b> {hostname}<br/>"      
  return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname())
if __name__ == "__main__":
  app.run(host='0.0.0.0', port=80)

应用依赖

​ 定义在同目录下的 requirements.txt 文件里,内容如下:

[root@qfedu.com ~]# vim requirements.txt
Flask

编写Dockerfile

[root@qfedu.com ~]# vim Dockerfile
FROM python:2.7-slim
WORKDIR /app
ADD  .  /app
RUN pip install --trusted-host pypi.python.org -r requirements.txt
EXPOSE 80
ENV NAME World
CMD ["python", "app.py"]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H1aBckV6-1605786957295)(assets/image-20200722153133293.png)]

Dockerfile文件说明

FROM python:2.7-slim

​ 使用官方提供的 Python 开发镜像作为基础镜像

​ 指定"python:2.7-slim"这个官方维护的基础镜像,从而免去安装 Python 等语言环境的操作。否则,这一段就得这么写了:

​ FROM ubuntu:latest

​ RUN apt-get update -y

​ RUN apt-get install -y python-pip python-dev build-essential

WORKDIR /app

​ 将工作目录切换为 /app,意思是在这一句之后,Dockerfile 后面的操作都以这一句指定的 /app 目录作为当前目录。

ADD . /app

将当前目录下的所有内容复制到 /app 下,Dockerfile 里的原语并不都是指对容器内部的操作。比如 ADD,指的是把当前目录(即 Dockerfile 所在的目录)里的文件,复制到指定容器内的目录当中。

RUN pip install --trusted-host pypi.python.org -r requirements.txt

使用 pip 命令安装这个应用所需要的依赖

EXPOSE 80 //允许外界访问容器的 80 端口

ENV NAME World //设置环境变量

CMD [“python”, “app.py”]

设置容器进程为:python app.py,即:这个 Python 应用的启动命令

这里app.py 的实际路径是 /app/app.py。CMD [“python”, “app.py”] 等价于 “docker run python app.py”。

在使用 Dockerfile 时,可能还会看到一个叫作 ENTRYPOINT 的原语。它和 CMD 都是 Docker 容器进程启动所必需的参数,完整执行格式是:“ENTRYPOINT CMD”。

但是,默认,Docker 会提供一个隐含的 ENTRYPOINT,即:/bin/sh -c。所以,在不指定 ENTRYPOINT 时,比如在这个例子里,实际上运行在容器里的完整进程是:/bin/sh -c “python app.py”,即 CMD 的内容就是 ENTRYPOINT 的参数。

基于以上原因,后面会统一称 Docker 容器的启动进程为 ENTRYPOINT,而不是 CMD。

现在目录结构:

[root@qfedu.com ~]# ls
Dockerfile  app.py  requirements.txt

构建镜像

[root@qfedu.com ~]# docker build -t helloworld .
-t  给这个镜像加一个 Tag

Dockerfile 中的每个原语执行后,都会生成一个对应的镜像层。即使原语本身并没有明显地修改文件的操作(比如,ENV 原语),它对应的层也会存在。只不过在外界看来,这个层是空的。

查看结果

[root@qfedu.com ~]# docker image ls
REPOSITORY       TAG         	 IMAGE ID
helloworld     	 latest        653287cdf998

启动容器

[root@qfedu.com ~]# docker run -p 4000:80 helloworld 
镜像名 helloworld 后面,什么都不用写,因为在 Dockerfile 中已经指定了 CMD。
否则,就得把进程的启动命令加在后面:
[root@qfedu.com ~]# docker run -p 4000:80 helloworld python app.py

查看容器

[root@qfedu.com ~]# docker ps
CONTAINER ID     IMAGE         COMMAND       		 CREATED
4ddf4638572d     helloworld    "python app.py"   10 seconds ago

进入容器

[root@qfedu.com ~]# docker exec -it b69 /bin/bash

访问容器内应用

[root@qfedu.com ~]# curl http://localhost:4000

Hello World!

Hostname: 4ddf4638572d

至此,已经使用容器完成了一个应用的开发与测试,如果现在想要把这个容器的镜像上传到 DockerHub 上分享给更多的人,首先要注册一个 Docker Hub 账号,然后使用 docker login 命令登录。

给容器镜像打tag起一个完整的名字:

[root@qfedu.com ~]# docker tag helloworld yanqiang20072008/helloworld:v1
yanqiang20072008为我在docker hub的用户名

推送镜像到Docker Hub

[root@qfedu.com ~]# docker push yanqiang20072008/helloworld:v1

6、Dockerfile实例02:制作Kubectl的镜像

注:本实例学完k8s之后才能做

制作镜像:wing/kubectl

[root@qfedu.com ~]#cat Dockerfile/kubectl/Dockerfile
FROM alpine
MAINTAINER wing <276267003@qq.com>
LABEL org.label-schema.vcs-ref=$VCS_REF \
   org.label-schema.vcs-url="https://github.com/vfarcic/kubectl" \
   org.label-schema.docker.dockerfile="/Dockerfile"
ENV KUBE_LATEST_VERSION="v1.13.0"
RUN apk add --update ca-certificates && \
  apk add --update -t deps curl && \
  curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_LATEST_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl && \
  chmod +x /usr/local/bin/kubectl && \
  apk del --purge deps && \
  rm /var/cache/apk/* 
CMD ["kubectl", "help"]

7、Dockerfile优化

编译一个简单的nginx成功以后发现好几百M。

1、RUN 命令要尽量写在一条里,每次 RUN 命令都是在之前的镜像上封装,只会增大不会减小

2、每次进行依赖安装后使用yum clean all清除缓存中的rpm头文件和包文件

3、选择比较小的基础镜像,比如:alpine

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值