一文带你全方位入门docker

1、Docker简介

Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上。Docker 是一个跨平台、可移植的解决方案。

一个完整的Docker有以下几个部分组成:

1)dockerClient客户端

2)Docker Daemon守护进程

3)Docker Image镜像

4)DockerContainer容器

Docker引擎Docker Engine是C/S架构,主要有以下部件组成:

服务器(Docker daemon):后台运行的Docker daemon进程。Daemon进程用于管理Docker对象,包括镜像(images)、容器(containers)、网络(networks)、数据卷(data volumes)。

REST接口:同daemon交互的REST API接口。

客户端(Docker client):命令行(CLI)交互客户端。客户端使用REST API接口同Docker daemon进行访问。

Docker服务的架构图如图所示。

运行一个Docker服务,组成包括Docker daemon服务器、Docker Client客户端、Docker Image镜像、Docker Registry库、Docker Contrainer容器,
在这里插入图片描述

  • 后台进程(dockerd)

    $ ps -ef | grep  dockerd
    root      4874     1  2 3月22 ?       00:26:29 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
    

Docker服务组成图:
在这里插入图片描述

2、安装docker

docker使用起来十分容易。

  1. 添加官方yum仓库

    [root@docker-node1 ~]#  yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
    
  2. 关闭防火墙和selinux

    [root@docker-node1 ~]# systemctl stop firewalld
    [root@docker-node1 ~]# setenforce 0
    [root@docker-node1 ~]# getenforce 
    Permissive
    
  3. 安装并启动docker

    [root@docker-node1 ~]# yum install docker-ce -y
    [root@docker-node1 ~]# systemctl start docker
    
  4. 查看docker version来验证是否安装成功

    [root@docker-node1 ~]# docker version
    Client: Docker Engine - Community
     Version:           19.03.8
     API version:       1.40
     Go version:        go1.12.17
     Git commit:        afacb8b
     Built:             Wed Mar 11 01:27:04 2020
     OS/Arch:           linux/amd64
     Experimental:      false
    
    Server: Docker Engine - Community
     Engine:
      Version:          19.03.8
      API version:      1.40 (minimum version 1.12)
      Go version:       go1.12.17
      Git commit:       afacb8b
      Built:            Wed Mar 11 01:25:42 2020
      OS/Arch:          linux/amd64
      Experimental:     false
     containerd:
      Version:          1.2.13
      GitCommit:        7ad184331fa3e55e52b890ea95e65ba581ae3429
     runc:
      Version:          1.0.0-rc10
      GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
     docker-init:
      Version:          0.18.0
      GitCommit:        fec3683
    
  5. 配置docker镜像加速

    [root@docker-node1 ~]# cat /etc/docker/daemon.json
    {
        "registry-mirrors": [
            "https://1nj0zren.mirror.aliyuncs.com",
            "https://docker.mirrors.ustc.edu.cn",
            "http://f1361db2.m.daocloud.io",
            "https://registry.docker-cn.com"
        ]
    }
    

3、Docker底层技术支持

Docker通过namespace实现了资源隔离,通过cgroups实现了资源限制,通过写时复制(copy-on-write)实现了高效的文件操作。

3.1、namespace资源隔离

想要实现一个资源隔离的容器,当我们进入一个容器内部的时候,最直观的感受就是根目录的改变,即文件系统被隔离了。接着为了在分布式的环境下进行通信和定位,容器必然要具有独立的IP、端口、路由等,自然需要网络的隔离。同时容器还需要一个独立的主机名以便在网络中标识自己。容器间通信的本质其实是进程间通信,所以我们还需要进程间隔离以及PID的隔离。当然一切也离不开用户,所以对用户和用户组的隔离就实现了用户权限的隔离。

6种namespace隔离:

  • UTS namespace:提供了主机名与域名的隔离,这样每个Docker容器就可以拥有独立的主机名和域名。
  • IPC namespace:进程间通信(IPC)涉及的IPC资源包括常见的信号量、消息队列和共享内存,申请IPC资源就申请了一个全局唯一的32位ID,所以IPC namespace中实际上包含了系统IPC标识符以及实现POSIX消息队列的文件系统。在同一个IPC namespace下的进程彼此可见,不同namespace下的进程则互相不可见。
  • PID namespace:PID的隔离,它对进程PID重新标号,即两个不同namespace下的进程可以具有相同的PID。

内核为所有的PID namespace维护了一个树状结构,最顶层的是系统初始时创建的,被称为root namespace。它创建的新PID namespace被称为child namespace,而原先的PID namespace就是新创建的PID namespace的 parent namespace。通过这种方式,不同PID namespace会形成一个层级体系。所属的父节点可以看到子节点中的进程,并可以通过信号等方式对子节点中的进程产生影响。而子节点却不能看到父节点PID namespace中的任何内容。

  • Network namespace:主要提供了网络资源的隔离。

包括网络设备、IPv4和IPv6协议栈、IP路由表、防火墙、/proc/net目录、/sys/class/net目录、套接字(socket)等。一个物理的网络设备最多存在于一个network namespace中,可以通过veth pair (虚拟网络设备对:有两端、类似管道,数据从一端传入另一端接收,反之亦然)在不同的network namespace间创建通道,以达到通信的目的。

  • Mount namespace:通过隔离文件系统挂载点对隔离文件系统提供支持,进程在创建mount namespace时会把当前的文件结构赋给新的namespace。新namespace中的所有mount操作都只影响自身的文件系统,对外界不会产生任何影响。
  • User namespace:主要隔离了安全相关的标识符(identifier)和属性(attribute),包括用户ID、用户组ID、root目录、key(指密钥)以及特殊权限。

一个普通用户的进程通过clone()创建的新进程在新user namespace中可以拥有不同的用户和用户组。这意味着一个进程在容器外属于一个没有特殊权限的普通用户,但是它创建的容器进程却属于拥有所有权限的超级用户。

在启动Docker daemon的时候指定–userns-remap 那么当用户运行容器时,容器内部的root用户并不等于宿主机内的root用户,而是映射到宿主机上的普通用户。

​ 实际上,Linux内核实现namespace的一个主要目的,就是实现轻量级虚拟化(容器)服务。在同一个namespace下的进程可以感知彼此的变化,而对外界的进程一无所知。这样就可以让容器中的进程产生错觉,仿佛自己置身于一个独立的系统环境中,以达到独立和隔离的目的。

用户可以在/proc/[PID]/ns文件下看到指向不同namespace号的文件,[4026531839]就是namespace号

$ ls -al /proc/$$/ns
总用量 0
dr-x--x--x. 2 root root 0 4月  12 18:41 .
dr-xr-xr-x. 9 root root 0 4月  12 18:01 ..
lrwxrwxrwx. 1 root root 0 4月  12 18:41 ipc -> ipc:[4026531839]
lrwxrwxrwx. 1 root root 0 4月  12 18:41 mnt -> mnt:[4026531840]
lrwxrwxrwx. 1 root root 0 4月  12 18:41 net -> net:[4026531956]
lrwxrwxrwx. 1 root root 0 4月  12 18:41 pid -> pid:[4026531836]
lrwxrwxrwx. 1 root root 0 4月  12 18:41 user -> user:[4026531837]
lrwxrwxrwx. 1 root root 0 4月  12 18:41 uts -> uts:[4026531838]

如果两个进程指向的namespace编号相同,说明在同一个namespace下。

3.2、cgroups (control groups)

​ cgroups是Linux内核提供的一种机制,这种机制可以根据需求把一系列系统任务及其子任务集合到按资源划分等级的不同组内,从而为系统资源管理提供一个统一的结果。

​ cgroups可以限制、记录任务组所使用的物理资源(包括CPU、Memory、IO等)。

cgroups特点:

  • cgroups提供了一个 cgroup 虚拟文件系统,作为进行分组管理和各子系统设置的用户接口。要使用 cgroup,必须挂载 cgroup 文件系统。通过挂载选项指定使用哪个子系统。
  • cgroups的组织管理单元可以细粒度到线程级别,用户可以创建和销毁cgroup,从而实现资源再分配
  • 所有资源管理的功能都以子系统的方式实现,接口统一。
  • 子任务创建之初与父任务处于同一个cgroup的控制组。

cgroups功能:

  • 资源限制:cgroup可以对任务使用的资源总额进行限制。如设定应用运行时使用内存的上限,一旦超过这个配额就会发出OOM提示(Out of Memory)。
  • 优先级分配:通过分配的CPU时间片数量及磁盘IO带宽大小。
  • 资源统计:cgroups可以统计系统的资源使用量。
  • 任务控制:cgroups可以对任务执行挂起、恢复等操作。

cgroups术语:

  • task(任务):task表示系统的一个进程或线程。

  • cgroup:cgroups中的资源控制都以cgroup为单位实现。cgroup表示按某种资源控制标准划分而成的任务组,包含一个或多个子系统。一个task可以加入某个cgroup,也可以从某个cgroup迁移到另一个cgroup。

  • subsystem(子系统):cgroups中的subsystem就是一个资源调度控制器,每种子系统独立控制一种资源。

    blkio -- 这个子系统为块设备设定输入/输出限制,比如物理设备(磁盘,固态硬盘,USB 等等)。
    cpu -- 这个子系统使用调度程序提供对 CPU 的 cgroup 任务访问。
    cpuacct -- 这个子系统自动生成 cgroup 中任务所使用的 CPU 报告。
    cpuset -- 这个子系统为 cgroup 中的任务分配独立 CPU(在多核系统)和内存节点。
    devices -- 这个子系统可允许或者拒绝 cgroup 中的任务访问设备。
    freezer -- 这个子系统挂起或者恢复 cgroup 中的任务。
    memory -- 这个子系统设定 cgroup 中任务使用的内存限制,并自动生成由那些任务使用的内存资源报告。
    net_cls -- 这个子系统使用等级识别符(classid)标记网络数据包,可允许 Linux 流量控制程序(tc)识别从具体 cgroup 中生成的数据包。
    ns -- 名称空间子系统。
    
  • hierarchy(层级):层级由一系列cgroup以一个树状结构排列而成,每个层级通过绑定对应的subsystem进行资源控制。层级中的cgroup节点可以包含0个或多个子节点。子节点继承父节点挂载的子系统。整个操作系统可以有多个层级。

基本规则:

  • 同一个层级可以附加一个或多个子系统。
  • 一个子系统可以附加到多个层级,当且仅当目标层级只有唯一一个子系统时。
  • 系统每次新建一个层级时,该系统上的所有task默认加入这个新建层级的初始化cgroup。这个cgroup也称为root cgroup。一个task不能存在于同一个层级的不同cgroup中,可以存在于不同层级中的cgroup中。
  • task在创建子task时,默认与原task在同一个cgroup中,子task允许移动到不同的cgroup中。

4、Docker 镜像

4.1、什么是docker image?

docker image是一个只读模板,用于创建Docker容器,docker image是文件和meta data的集合(root filesystem),image采用分层架构,每一层都可以添加删除文件commit成为一个新的image,不同的image可以共享相同的layer,image本身是只读的。

Docker 镜像含有启动容器所需要的文件系统及其内容,因此,其用于创建并启动docker容器。

  • docker 采用分层构建机制,最底层为bootfs(aufs/btrfs/overlay2等文件系统。),其之上为rootfs(etc,bin…)
    • bootfs:用于引导文件系统,包括bootloader和kernel,容器启动完成后会被卸载以节约资源。
    • rootfs:位于bootfs之上,表现为docker容器的根文件系统。
      • 传统模式中,系统启动之时,内核挂载rootfs时会首先将其挂载为"只读"模式,完整性自检完成后将其重新挂载为读写模式。
      • docker中,rootfs由内核挂载为"只读模式",而后通过“联合挂载(union mount)”技术额外挂载一个"读写"层。这样读写层位于Docker容器文件系统的最顶层,它下面联合挂载多个只读层。

4.2、Docker Image主要特点

4.2.1、分层

Docker镜像采用分层的方式构建,不同镜像之间可以共享镜像层。

4.2.2、写时复制(copy-on-write)

写入时复制是一种共享和复制文件的策略,可最大程度地提高效率。容器启动时并不需要单独复制一份文件,而是将所有镜像层以只读的方式挂载到一个挂载点,再在最上层添加一个读写层。当文件发生改变时,会把变化的内容写到读写层中,只读层内容不改变并被隐藏。

4.2.3、内容寻址

Docker使用了内容寻址存储(content-addressable storage)的机制,根据文件内容来索引镜像和镜像层。每一层会计算一个hash值,并以此作为唯一标识。

4.2.4、联合挂载

联合挂载技术可以在一个挂载点同时挂载多个文件系统,将挂载点的原目录与被挂载内容进行整合,最终可见的文件系统包含整合之后的各层的文件和目录。
在这里插入图片描述

5.3、从docker hub 搜索 image https://hub.docker.com/

以mysql为例
在这里插入图片描述

5.4、Image 相关概念

5.4.1、registry

Registry用以保存Docker镜像,其中还包括镜像层次结构和关于镜像的元数据。

​ Docker Hub是Docker公司提供的互联网公共镜像仓库。用户可以构建自己本地的镜像仓库,国内有些公司也构建了镜像仓库。包括阿里云、新浪等。
​ 一个 Docker Registry 节点中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。
​ 一般而言,一个仓库包含的是同一个软件的不同版本的镜像,而标签则用于对应于软件的的不同版本。可以通过 <仓库名>:<标签> 的格式来指定具体是哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。
​ 以 Ubuntu 镜像 为例,ubuntu 是仓库的名字,其内包含有不同的版本标签,如,14.04, 16.04。可以通过 ubuntu:14.04,或者 ubuntu:16.04 来具体指定所需哪个版本的镜像。如果忽略了标签,比如ubuntu,那将视为 ubuntu:latest。

registry分类

  • Sponsor Registry:第三方Registry,仅供客户和Docker社区使用
  • Mirror Registry:第三方Registry,只让客户使用
  • Vendor Registry:由发布Docker镜像的供应商提供的Registry
  • Private Registry:通过设有防火墙和额外的安全层的私有实体提供的registry
5.4.2、repository

repository具有某个功能的Docker镜像的所有迭代版本构成的镜像组,简单来说,repository是镜像的集合。Docker Hub中有两种类型仓库,用户仓库(由用户自己创建,由用户名与repository名两部分组成,中间以’/'隔开,username/repository_name),顶层仓库(由docker公司维护,一般只包含repository名的部分,如centos)

5.4.3、manifest

manifest(描述文件)主要存在于registry中作为Docker镜像的元数据文件,在pull、push、save和load中作为镜像结构额基础信息的描述文件。在镜像被pull或者load到Docker宿主机时,manifest被转化为本地的镜像配置文件config。

5.4.4、image和layer

image是用来存储一组镜像相关的元数据信息,主要包括镜像的架构、镜像默认配置信息、构建镜像的容器配置信息、包含所有镜像信息的rootfs。Docker利用rootfs中的diff_id计算出内容寻址的索索引来获取layer的信息,进而获取每一个镜像层的文件内容。

layer是一个Docker用来管理镜像层的中间概念,镜像由镜像层组成,单个镜像层可以被多个镜像共享。

5.5、Docker image常用操作

[root@docker-node1 ~]# docker image -h
Flag shorthand -h has been deprecated, please use --help
Usage:  docker image COMMAND
Manage images
Commands:
  build       Build an image from a Dockerfile
  history     Show the history of an image
  import      Import the contents from a tarball to create a filesystem image
  inspect     Display detailed information on one or more images
  load        Load an image from a tar archive or STDIN
  ls          List images
  prune       Remove unused images
  pull        Pull an image or a repository from a registry
  push        Push an image or a repository to a registry
  rm          Remove one or more images
  save        Save one or more images to a tar archive (streamed to STDOUT by default)
  tag         Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
  1. 列出本地image,docker image ls 或者 docker images

    [root@docker-node1 ~]# docker image ls
    REPOSITORY                      TAG                 IMAGE ID            CREATED             SIZE
    redis                           latest              f0453552d7f2        9 days ago          98.2MB
    
  2. pull一个mysql镜像

    [root@docker-node1 ~]# docker pull mysql
    [root@docker-node1 ~]# docker image ls mysql
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    mysql               latest              9b51d9275906        2 weeks ago         547MB
    
  3. 通过docker save导出mysql镜像到mysql.tar.gz文件

    [root@docker-node1 ~]# docker save mysql -o mysql.tar.gz
    [root@docker-node1 ~]# ls mysql.tar.gz 
    mysql.tar.gz
    
  4. 从文件导入镜像

    [root@docker-node1 ~]# docker image load -i mysql.tar.gz 
    Loaded image: mysql:latest
    
  5. 给镜像添加tag docker image tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]

    [root@docker-node1 ~]# docker image tag mysql:latest mysql:test
    [root@docker-node1 ~]# docker image ls mysql
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    mysql               latest              9b51d9275906        2 weeks ago         547MB
    mysql               test                9b51d9275906        2 weeks ago         547MB
    
  6. docker image rm 删除一个image

    [root@docker-node1 ~]# docker image rm mysql:test
    Untagged: mysql:test
    

5、Docker Container

5.1、什么是Container?

是一个镜像的运行实例。container是通过image创建。container包含image+可读写层。

5.2、官方怎么说?

immage在运行时成为container。container是打包代码及其所有依赖项的标准软件单元,因此应用程序可以从一个计算环境快速可靠地运行到另一个计算环境。 Docker容器镜像是一个轻量级的,独立的,可执行的软件软件包,其中包含运行应用程序所需的一切:代码,运行时,系统工具,系统库和设置。

5.3、docker container常用操作

可以使用docker container Command的格式。

  1. docker run运行一个名称为web容器,映射本地80端口到容器80端口

    [root@docker-node1 ~]# docker container run -dit --name web -p 80:80 httpd
    c8e06ae7d71f13ae0a9f8d63777efdda3fdce93c476ab931dd99ec419e98e8db
    参数介绍:
    -d:后台运行并返回container ID  命令执行后显示的c8xxxx就是此参数的效果
    -i:打开STDIN,用于控制台交互
    -t:分配一个tty,可以用来登陆
    --name:指定容器名称
    -p:映射端口-p[本地port]:[容器port]
    --rm:容器退出时自动删除container
    
  2. docker ps查看启动的容器,与所有容器。

    [root@docker-node1 ~]# docker ps -a
    CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS                NAMES
    c8e06ae7d71f        httpd               "httpd-foreground"   4 minutes ago       Up 4 minutes        0.0.0.0:80->80/tcp   web
    
    -a参数可以显示所有容器,包括退出的容器
    -q参数可以只返回容器ID
    [root@docker-node1 ~]# docker ps -q
    c8e06ae7d71f
    
  3. docker exec进入容器,常用组合-it参数

    docker exec [OPTIONS] CONTAINER COMMAND [ARG…]

    [root@docker-node1 ~]# docker exec -it web /bin/bash
    root@c8e06ae7d71f:/usr/local/apache2# 
    

    不进入容器,让容器执行命令并返回结果docker exec [容器名称或ID] [执行的命令]

    [root@docker-node1 ~]# docker exec  web ls /var/log/
    alternatives.log
    apt
    btmp
    dpkg.log
    faillog
    lastlog
    wtmp
    
  4. docker inspect 显示容器的详细信息

    [root@docker-node1 ~]# docker inspect web | more
    [
        {
            "Id": "c8e06ae7d71f13ae0a9f8d63777efdda3fdce93c476ab931dd99ec419e98e8db",
            "Created": "2020-03-25T07:46:08.626233176Z",
            "Path": "httpd-foreground",
            "Args": [],
            "State": {
                "Status": "running",
                "Running": true,
                "Paused": false,
                "Restarting": false,
                "OOMKilled": false,
                "Dead": false,
                "Pid": 19811,
                "ExitCode": 0,
                "Error": "",
                "StartedAt": "2020-03-25T07:46:09.434595879Z",
                "FinishedAt": "0001-01-01T00:00:00Z"
            },
    
  5. docker logs 容器ID/容器Name 显示启动日志

    [root@docker-node1 ~]# docker logs web
    AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message
    AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message
    [Wed Mar 25 08:03:12.376930 2020] [mpm_event:notice] [pid 1:tid 139689087566976] AH00489: Apache/2.4.41 (Unix) configured -- resuming normal operations
    [Wed Mar 25 08:03:12.377423 2020] [core:notice] [pid 1:tid 139689087566976] AH00094: Command line: 'httpd -D FOREGROUND'
    
  6. docker cp命令,拷贝容器内的文件到宿主机,或者拷贝宿主机文件到容器内

    拷贝web容器httpd.conf到本地
    [root@docker-node1 ~]# docker cp web:/usr/local/apache2/conf/httpd.conf .
    [root@docker-node1 ~]# ls httpd.conf 
    httpd.conf
    
  7. docker rm 删除一个容器,默认不允许删除正在运行的容器,需要先停止容器,一般可以加上-f参数,强制执行

    [root@docker-node1 ~]# docker rm -f web
    web
    [root@docker-node1 ~]# docker ps -a
    CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
    

6、搭建私有仓库,registry与harbor

前面说到了镜像的registry,这里介绍下私有仓库的搭建。

8.1、registry搭建

准备两台linux,一台做为registry私有仓库

registry端配置,在安装好docker环境的基础上执行

[root@registry ~]# docker run -d -p 5000:5000 --restart always --name registry registry:2
查看启动的registry容器
[root@registry ~]# docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
43a7858c9be7        registry:2          "/entrypoint.sh /etc…"   5 seconds ago       Up 3 seconds        0.0.0.0:5000->5000/tcp   registry
此时registry端启动了5000端口

docker-node1指定私有仓库地址

在/etc/docker/daemon.json中指定registry的ip 地址及端口
[root@docker-node1 ~]# cat /etc/docker/daemon.json
{
    "insecure-registries": ["10.10.128.178:5000"]  #registry的ip:port
}
然后我们重新加载配置并重启docker
[root@docker-node1 ~]# systemctl daemon-reload
[root@docker-node1 ~]# systemctl restart docker

修改一个本地镜像格式
例如将centos-httpd tag为v1的镜像格式修改为10.10.128.178:5000/centos-httpd:v1
[root@docker-node1 ~]# docker tag centos-httpd:v1 10.10.128.178:5000/centos-httpd:v1

通过docker push 推送镜像docker push registryip:port/imagename:tag
[root@docker-node1 ~]# docker push 10.10.128.178:5000/centos-httpd:v1
The push refers to repository [10.10.128.178:5000/centos-httpd]
083db8a1c5d9: Pushed 
9f8b5faabf43: Pushed 
77b174a6a187: Pushed 
v1: digest: sha256:b3ec77096374c7f964f615da701683906d12b7a86f343d73c887484c609b31fc size: 948

验证是否推送成功,官方提供了介绍https://docs.docker.com/registry/spec/api/#listing-repositories

[root@docker-node1 ~]# curl 10.10.128.178:5000/v2/_catalog
{"repositories":["centos-httpd"]}

8.2 、docker harbor搭建

什么是harbor?

Harbor is an open source container image registry that secures images with role-based access control, scans images for vulnerabilities, and signs images as trusted. As

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值