Docker学习笔记-高级篇

Docker网络原理

理解docker0

在这里插入图片描述
Linux宿主机可以ping通容器内部。
在这里插入图片描述

如上,每启动一个docker容器,docker就会给docker容器分配一个ip,我们只要安装了docker,就会有一个网卡docker0,它使用桥接模式,其使用的技术是veth-pair

每启动一个容器,在宿主机都会看到增加了一个vethxxxxx的网卡,且与容器内的网卡是配对的:
在这里插入图片描述
veth-pair就是一对虚拟网络设备接口,它们都是成对出现的。
veth-pair往往充当一个桥梁,连接各种虚拟网络设备。

容器之间可以ping通
在这里插入图片描述

docker0网络通信模型图

在这里插入图片描述

结论:如上图,默认情况下,容器tomcat01和tomcat02是共用一个路由器,即docker0
所有容器在不指定网络的情况下,都是docker0作为路由器的。docker0会给容器分配一个默认的可用IP。
Docker中所有的网络接口都是虚拟的。
容器移除后,对应的那对网卡也随之删除。

最后再附上一张更加通用的docker0网络通信模型图。
在这里插入图片描述

场景:当容器IP改变 -> 容器互联:--link

因为默认情况下,容器启动时,IP是由docker0动态分配的,所以当容器重启后,IP有可能会变。

举个例子,比如Springboot的项目里,会把数据库连接配置写在配置文件中,比如database url = jdbc://172.17.0.6:3306/xxx,如果MySQL的容器重启了,很有可能IP改变了。那这样项目部署就有问题了。

因此我们希望可以用名字来访问容器。

在这里插入图片描述
如何实现呢?

通过--link就能实现。【但这种方式已经不建议使用了!
新启动一个名为tomcat02的容器,然后指定--link
在这里插入图片描述
这样,tomcat02容器就可以通过tomcat01这个容器名与对应的容器进行网络通信了。但与此同时,反过来是不行的,即tomcat01容器还是无法通过tomcat02这个容器名与对应的容器进行网络通信。因此--link的结果是单向的,这也是它的一个缺点
在这里插入图片描述
docker inspect tomcat02查看tomcat02的配置,可以看到有Links项:
在这里插入图片描述
本质探究:在容器tomcat02/etc/hosts文件可以看到对应的IP-名称映射配置。
在这里插入图片描述
可以看到--link这种配置方式太笨了,所以现在已经不推荐使用了!
现在推荐使用的是自定义网络,不使用docker0。因为docker0有局限性, 比如不支持使用容器名去进行网络连接。

自定义网络

docker network

[root@VM-8-8-centos ~]# docker network --help

Usage:  docker network COMMAND

Manage networks

Commands:
  connect     Connect a container to a network
  create      Create a network
  disconnect  Disconnect a container from a network
  inspect     Display detailed information on one or more networks
  ls          List networks
  prune       Remove all unused networks
  rm          Remove one or more networks

Run 'docker network COMMAND --help' for more information on a command.

网络模式

  • bridge:桥接(默认。自己创建一般也使用桥接模式)
  • none:不配置网络
  • host:和宿主机共享网络
  • container:容器网络联通(用的少!局限性大!)

其实在启动容器时,是默认带有--net bridge 这个参数的,这个参数其实就是指使用docker0

实践:创建自定义网络

1、先清空现有的容器,可以看到之前的桥接网卡也被删除,只剩下原有的loeth0docker0
在这里插入图片描述
2、通过docker create创建自定义网络

[root@VM-8-8-centos ~]# docker network create --help

Usage:  docker network create [OPTIONS] NETWORK

Create a network

Options:
      --attachable           Enable manual container attachment
      --aux-address map      Auxiliary IPv4 or IPv6 addresses used by Network driver (default
                             map[])
      --config-from string   The network from which to copy the configuration
      --config-only          Create a configuration only network
  -d, --driver string        Driver to manage the Network (default "bridge")
      --gateway strings      IPv4 or IPv6 Gateway for the master subnet
      --ingress              Create swarm routing-mesh network
      --internal             Restrict external access to the network
      --ip-range strings     Allocate container ip from a sub-range
      --ipam-driver string   IP Address Management Driver (default "default")
      --ipam-opt map         Set IPAM driver specific options (default map[])
      --ipv6                 Enable IPv6 networking
      --label list           Set metadata on a network
  -o, --opt map              Set driver specific options (default map[])
      --scope string         Control the network's scope
      --subnet strings       Subnet in CIDR format that represents a network segment

在这里插入图片描述
3、创建两个容器tomcat-net-01tomcat-net-02,并使用--net指定网络为自定义网络mynet01。可以看到,使用自定义网络的容器间可以通过容器名来进行网络连接:
在这里插入图片描述
这样带来的好处就是:
(1) 在自定义网络中,Docker帮我们维护了IP<->容器名之间的对应关系;
(2) 不同的集群可使用不同的网络(比如一个Redis集群和一个MySQL集群),可保证不同集群之间的网络隔离,安全性。
在这里插入图片描述
PS:当然,上面这种网络隔离情况也是可以打通的。下面来说说网络连通。

网络连通

docker network connect //一个容器连接另一个网络

[root@VM-8-8-centos ~]# docker network connect --help

Usage:  docker network connect [OPTIONS] NETWORK CONTAINER

Connect a container to a network

Options:
      --alias strings           Add network-scoped alias for the container
      --driver-opt strings      driver options for the network
      --ip string               IPv4 address (e.g., 172.30.100.104)
      --ip6 string              IPv6 address (e.g., 2001:db8::33)
      --link list               Add link to another container
      --link-local-ip strings   Add a link-local address for the container

首先处于不同网段的容器之间是无法ping通的,测试如下:
在这里插入图片描述
要连通的话,只能通过容器去连接另一个网络,如下图:
在这里插入图片描述

实践:打通 tomcat01 - mynet01

docker network connect mynet01 tomcat01

打通前:
在这里插入图片描述
打通后,将tomcat01放到了mynet的网络下,可以看到tomcat01容器多了一张网卡eth1
在这里插入图片描述
在这里插入图片描述
打通后,tomcat01容器就可以与mynet01网络里的容器互相ping通了。
在这里插入图片描述

实战:部署Redis集群

创建集群的脚本:create-redis-cluster.sh

#!/bin/bash

# 创建用于redis集群的自定义网络
docker network create redis --subnet 172.38.0.0/16

# 创建6个redis容器
for port in $(seq 1 6); \
do \
mkdir -p /mydata/redis/node-${port}/conf
touch /mydata/redis/node-${port}/conf/redis.conf
cat <<EOF > /mydata/redis/node-${port}/conf/redis.conf
port 6379
bind 0.0.0.0
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.38.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
appendonly yes
EOF

docker run -p 637${port}:6379 -p 1637${port}:16379 --name redis-${port} \
-v /mydata/redis/node-${port}/data:/data \
-v /mydata/redis/node-${port}/conf/redis.conf:/etc/redis/redis.conf \
-itd --network redis --ip 172.38.0.1${port} redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf; \

done


# 创建集群
docker exec -it redis-1 redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1

执行脚本:
在这里插入图片描述
在这里插入图片描述
查看集群信息:
在这里插入图片描述
测试集群,可以看到集群正常工作,到此Redis集群部署完成。可以看到通过docker来部署redis集群非常简单。
在这里插入图片描述

IDEA整合Docker

实战:Springboot微服务打包Docker镜像

  • 1、构建Springboot项目
  • 2、打包应用
  • 3、编写Dockerfile
  • 4、构建镜像
  • 5、发布运行

构建并打包Springboot项目

在这里插入图片描述

编写Dockerfile

FROM java:8

COPY *.jar /app.jar

CMD ["--server.port=8080"]

EXPOSE 8080

ENTRYPOINT ["java","-jar","/app.jar"]

构建镜像

在这里插入图片描述

发布运行

在这里插入图片描述
在这里插入图片描述

Docker Compose

简介

以前交付、启动容器的步骤:
(1) 定义Dockerfile;
(2) docker build;
(3) docker run;
只适用单个容器的情况。

现在的微服务项目一般都有多个容器,且存在依赖关系,假设有100个容器,那用上面的方法来管理容器就非常低效了。

所以就有了Docker Compose。它可以轻松高效的管理多个容器(批量编排)。

官方介绍如下:
Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration. To learn more about all the features of Compose, see the list of features.
 
Compose works in all environments: production, staging, development, testing, as well as CI workflows. You can learn more about each case in Common Use Cases.
 
Using Compose is basically a three-step process:

  1. Define your app’s environment with a Dockerfile so it can be reproduced anywhere.

 
2. Define the services that make up your app in docker-compose.yml so they can be run together in an isolated environment.
 
3. Run docker compose up and the Docker compose command starts and runs your entire app. You can alternatively run docker-compose up using the docker-compose binary.

Docker Compose 是Docker官方的一个开源项目,并不是在Docker项目里的。要另外安装才有:
在这里插入图片描述
docker-compose.yml示例:

version: "3.9"  # optional since v1.27.0
services:
  web:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - .:/code
      - logvolume01:/var/log
    links:
      - redis
  redis:
    image: redis
volumes:
  logvolume01: {}

在Docker Compose的语境下,有以下几个概念:
(1) 服务Services:指的是单个的容器/应用。(web、redis、mysql…)
(2) 项目Projects:一组关联的容器(比如一个博客项目,它包含了web、mysql、redis、nginx等容器)。

Docker Compose 安装

参考官方文档:
https://docs.docker.com/compose/install/

如果是国内的服务器环境,建议替换成get.daocloud.io这个源,下载速度会飞快。

//1、下载
curl -L "https://get.daocloud.io/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

//2、赋予执行权限
chmod u+x /usr/local/bin/docker-compose

//3、创建软链接
ls -l /usr/bin/docker-compose

在这里插入图片描述

Docker Compose 初体验

按照官方文档的get started进行实践:https://docs.docker.com/compose/gettingstarted/

Step 1: Setup

Define the application dependencies.

  • 1、Create a directory for the project:
 $ mkdir composetest
 $ cd composetest
  • 2、Create a file called app.py in your project directory and paste this in:
import time

import redis
from flask import Flask

app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)

def get_hit_count():
    retries = 5
    while True:
        try:
            return cache.incr('hits')
        except redis.exceptions.ConnectionError as exc:
            if retries == 0:
                raise exc
            retries -= 1
            time.sleep(0.5)

@app.route('/')
def hello():
    count = get_hit_count()
    return 'Hello World! I have been seen {} times.\n'.format(count)
  • 3、Create another file called requirements.txt in your project directory and paste this in:
flask
redis

Step 2: Create a Dockerfile

目的:将应用打包成镜像

In this step, you write a Dockerfile that builds a Docker image. The image contains all the dependencies the Python application requires, including Python itself.

In your project directory, create a file named Dockerfile and paste the following:

由于测试时用的是国内的服务器,所以在Dockerfile中加入了国内的源,包括alpine linux 和 pypi的,否则下载会很慢。

#syntax=docker/dockerfile:1
FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
RUN apk update && apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip config set global.index-url https://mirrors.ustc.edu.cn/pypi/web/simple
RUN pip install -r requirements.txt
EXPOSE 5000
COPY . .
CMD ["flask", "run"]

Step 3: Define services in a Compose file

目的:定义整个项目服务需要的环境,该示例则是包括web、redis。

Create a file called docker-compose.yml in your project directory and paste the following:

version: "3.9"
services:
  web:
    build: .
    ports:
      - "5000:5000"
  redis:
    image: "redis:alpine"
  • Web service
    The web service uses an image that’s built from the Dockerfile in the current directory. It then binds the container and the host machine to the exposed port, 5000. This example service uses the default port for the Flask web server, 5000.

  • Redis service
    The redis service uses a public Redis image pulled from the Docker Hub registry.

Step 4: Build and run your app with Compose

  • 1、From your project directory, start up your application by running docker-compose up.
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    会自动创建单独的自定义网络,意味着处于该网络的容器之间可以通过容器名来互相连接(比如ping):
    在这里插入图片描述
    一共3个镜像:
    在这里插入图片描述

  • 2、在浏览器中访问:
    在这里插入图片描述

Step 5: Edit the Compose file to add a bind mount

修改docker-compose.yml文件,针对web服务的容器添加数据卷挂载。

version: "3.9"
services:
  web:
    build: .
    volumes:
      - .:/code
    environment:
      FLASK_ENV: development
    ports:
      - "5000:5000"
  redis:
    image: "redis:alpine"

将主机上的项目目录(当前目录)挂载到容器内的/code目录,这样就可以在本地修改代码而无需重建镜像。另外,FLASK_ENV环境变量表示让Flask 在开发模式下运行,这样可以在代码变化时自动重新加载代码(这种模式只应在开发中使用)。

Step 6: Re-build and run the app with Compose

docker-compose up -d重新构建,并启动容器。

Step 7: Update the application

修改app.py中返回的字符串:

@app.route('/')
def hello():
    count = get_hit_count()
    return 'Hello from Docker! I have been seen {} times.\n'.format(count)

保存后,刷新浏览器:
在这里插入图片描述

PS:当自己定义的Dockerfile有变化,该镜像需要重新生成再启动容器的话,用docker-compose up --build--build参数会先重新构建镜像。
如果镜像不用重新生成,则用docker-compose up即可。

Docker Compose配置(yaml)编写规则

这是 docker-compose.yml 的核心。
需要的时候查阅官方文档即可:https://docs.docker.com/compose/compose-file/compose-file-v3/
官方的一些示例:https://docs.docker.com/samples/wordpress/

Docker Swarm(相当于简化版k8s)

Docker Compose 属于单机部署,实际上企业生产环境多是用的集群部署的方式。

环境

四台Centos7(CentOS Linux release 7.7.1908 (Core))虚拟机。
四台都安装docker环境。

PS:四台同时安装docker环境的小技巧,也是很多优秀的shell终端管理工具都有的功能,比如xshell、iTerm2。这里以iTerm2为例:
在这里插入图片描述

工作模式

  • 管理节点(Manager node)
  • 工作节点(Worker node)
    在这里插入图片描述

搭建集群

[root@centos7-docker-1 ~]# docker swarm --help

Usage:  docker swarm COMMAND

Manage Swarm

Commands:
  ca          Display and rotate the root CA
  init        Initialize a swarm
  join        Join a swarm as a node and/or manager
  join-token  Manage join tokens
  leave       Leave the swarm
  unlock      Unlock swarm
  unlock-key  Manage the unlock key
  update      Update the swarm

Run 'docker swarm COMMAND --help' for more information on a command.

1、初始化集群

docker swarm init --advertise-addr <IP>   //IP一般是私网IP

在这里插入图片描述
2、添加节点

docker swarm join --token <TOKEN> <管理节点的IP:PORT>

在这里插入图片描述

如果要添加管理节点,则需要先在已有的管理节点上执行docker swarm join-token manager来获取管理节点的加入凭证(join-token),如下:
在这里插入图片描述
添加完毕后,可以在管理节点上使用docker node ls查看所有节点:
在这里插入图片描述
3、提升/降级节点

//提升节点
docker node promote --help

Usage:  docker node promote NODE [NODE...]

Promote one or more nodes to manager in the swarm


//降级节点
[root@centos7-docker-4 ~]# docker node demote --help

Usage:  docker node demote NODE [NODE...]

Demote one or more nodes from manager in the swarm

centos7-docker-4提升为管理节点:
在这里插入图片描述
这样呢,就有了两个管理节点,两个工作节点。
在这里插入图片描述

Raft协议

当 Docker Engine 以 swarm 模式运行时,管理器节点实现 Raft 一致性算法来管理全局集群状态。
Docker swarm 模式之所以使用一致性算法,是为了确保集群中负责管理和调度任务的所有管理器节点都存储相同的一致状态。
 
在整个集群中具有相同的一致状态意味着在发生故障时,任何 Manager 节点都可以接收任务并将服务恢复到稳定状态。例如,如果集群中负责调度任务的 Leader Manager 意外死亡,任何其他 Manager 都可以接手调度的任务并重新平衡任务以匹配所需的状态。
 
Raft 最多可以容忍 (N-1)/2 次失败,并且需要多数或法定人数 (N/2)+1 名成员就提议给集群的值达成一致。这意味着在运行 Raft 的 5 个 Manager 的集群中,如果 3 个节点不可用,系统将无法处理更多请求以安排额外的任务。现有任务继续运行,但如果管理器集不健康,则调度程序无法重新平衡任务以应对故障。

官方文档:https://docs.docker.com/engine/swarm/raft/

测试
使centos7-docker-3也成为管理节点管理,此时共有3个管理节点(1、3和4):
在这里插入图片描述
此时如果centos7-docker-1挂了(使用systemctl stop docker.servicesystemctl stop docker.socket停止docker进程来模拟):
在这里插入图片描述
centos7-docker-3centos7-docker-4这两个管理节点依旧正常,故集群还是可以正常工作的。
在这里插入图片描述
此时,如果centos7-docker-3也挂了,管理节点只剩下一个,此时管理节点就无法正常工作了,整个集群也就异常了:
在这里插入图片描述
以上测试也验证了Raft一致性的一些失败/故障次数<=(N-1)/2 原则(N表示管理节点数)。

创建弹性服务

在集群里面:
容器 => 服务 => 副本

比如一个redis服务=>10个副本(同时开启10个redis容器)

1、启动服务

docker run //容器启动:不具备扩缩容器的能力。
docker sevice //启动服务:局别扩缩容器、滚动更新的能力。

服务:在集群中的任意节点都可以访问。服务可以有多个副本,动态扩缩用。

# docker service --help

Usage:  docker service COMMAND

Manage services

Commands:
  create      Create a new service
  inspect     Display detailed information on one or more services
  logs        Fetch the logs of a service or task
  ls          List services
  ps          List the tasks of one or more services
  rm          Remove one or more services
  rollback    Revert changes to a service's configuration
  scale       Scale one or multiple replicated services
  update      Update a service

Run 'docker service COMMAND --help' for more information on a command.

在这里插入图片描述
虽然只有centos7-docker-4这个节点上运行了nginx容器,但可通过任意节点的8888端口访问nginx服务,可以看出其实集群就是一个整体。
在这里插入图片描述
如果把docker-4节点停止,很快其他的节点又会把容器运行起来。
在这里插入图片描述

2、对集群服务进行扩缩容

在上面第一步,创建了集群nginx服务后,可以看到nginx服务只有一个副本:
在这里插入图片描述
当站点访问量增大后,服务器压力增大,就有了扩容的需求。

  • 扩容

更新服务的副本数:

docker service update --replicas <REPLICAS_NUM> <SERVICE>

在这里插入图片描述

  • 缩容
    在这里插入图片描述
    也可以使用docker service scale命令进行动态扩缩容。
docker service scale --help

Usage:  docker service scale SERVICE=REPLICAS [SERVICE=REPLICAS...]

Scale one or multiple replicated services

Options:
  -d, --detach   Exit immediately instead of waiting for the service to converge

在这里插入图片描述
docker service updatedocker service scale 作用是一样的,都是用来动态扩缩容。

3、移除服务

docker service rm <SERVICE>

在这里插入图片描述

PS:docker swarm并不难,只要会搭建集群,启动服务、动态管理容器即可。docker swarm比k8s简单多了,k8s功能更加强大,但也比较复杂。

概念总结

详见官方文档:https://docs.docker.com/engine/swarm/key-concepts/

  • Swarm

负责集群的管理和编排。
docker可以初始化一个swarm集群,其他的docker节点可以加入。

  • Node

即docker节点。多个节点组成一个网络集群。
docker swarm的节点分为manager节点和worker节点。

-Service

服务,可以在管理节点或工作节点运行。【核心】
在复制服务模型中,swarm管理器根据你设置的副本数在节点之间分配特定数量的副本任务。
对于全局服务,swarm 在集群中的每个可用节点上为该服务运行一项任务。

服务副本和全局服务:
在这里插入图片描述

调整service以什么方式运行
--mode

--mode     Service mode (replicated, global, replicated-job, or global-job) (default "replicated")

docker service create --mode replicated --name mytomcat tomcat:7
docker service create --mode global --name allnx alpine ping www.baidu.com

//场景的话,比如日志收集:
每个节点有自己的日志收集器,过滤。把所有日志最终再传给日志中心,服务监控,状态性能。

-Task

一个任务就是指一个容器和该容器内运行的命令(因为前面也学习过,docker容器就是分层的,每次执行的变更都是新的一层)。其实就是指服务的一个副本。它是swarm的原子调度单元。
管理节点根据服务设置的副本数将任务分配给工作节点。一旦任务被分配到一个节点,它就不能移动到另一个节点。

Service和Task可结合下面两张图去理解。
在这里插入图片描述
在这里插入图片描述

一些扩展

网络模式:

  • overlay
  • ingress:特殊的overlay网络,具有负载均衡的功能。
    在这里插入图片描述
    在这里插入图片描述

Docker其他的一些命令

这部分的话,作为了解即可,因为用的很少了,基本都是用K8s了。

Docker Stack

//单机部署
docker-compose  up -d wordpress.yml
docker service create   //一次只能部署一个服务
//集群部署
docker stack deploy wordpress.yml

以在 Swarm 集群中部署 WordPress 为例:

version: "3"

services:
  wordpress:
    image: wordpress
    ports:
      - 80:80
    networks:
      - overlay
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
    deploy:
      mode: replicated
      replicas: 3

  db:
    image: mysql
    networks:
       - overlay
    volumes:
      - db-data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: somewordpress
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
    deploy:
      placement:
        constraints: [node.role == manager]

  visualizer:
    image: dockersamples/visualizer:stable
    ports:
      - "8080:8080"
    stop_grace_period: 1m30s
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
    deploy:
      placement:
        constraints: [node.role == manager]

volumes:
  db-data:
networks:
  overlay:

在 Swarm 集群管理节点新建该文件,其中的 visualizer 服务提供一个可视化页面,我们可以从浏览器中很直观的查看集群中各个服务的运行节点。
在 Swarm 集群中使用 docker-compose.yml 我们用 docker stack 命令:

  • 使用docker stack deploy部署服务,其中 -c 参数指定 compose 文件名:
$ docker stack deploy -c docker-compose.yml wordpress

集群部署后,可通过浏览器访问wordpress、visualizer:
在这里插入图片描述
在这里插入图片描述
使用docker stack rm删除服务:
在这里插入图片描述

Docker Secret

参考:https://yeasy.gitbook.io/docker_practice/swarm_mode/secret

Docker Config

参考:https://yeasy.gitbook.io/docker_practice/swarm_mode/config

滚动升级

参考:https://yeasy.gitbook.io/docker_practice/swarm_mode/rolling_update

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值