CoreOS

       最近由于业务需要在折腾Docker,偶然发现了一个新事物,那就是CoreOS,其实之前也听过,只不过没关注,直到最近才看了一点相关知识,第一次在百度中看介绍的时候,发现都是千篇一律,也就那么几篇文章,介绍的也很简单,也不知道谁抄谁的。没办法只能去官网看了,所以先学习了一些理论铺垫铺垫,以下内容均为个人学习后的理解。

        经过学习我认为CoreOS其实就是:docker+systemd+etcd+update

            docker:是个容器,相信关注互联网前沿技术的同学都应该听过,不了解的看前面的文件补脑。

            systemd:一个颠覆性的东西,要替换linux的init的,。。。。

            etcd:一个分布式的K/V存储系统,在CoreOS中用来同步管理一些集群信息,根据zookeeper的灵感而来。

            update:就是指CoreOS系统的更新,CoreOS有两个root分区,一个主动一个被动,其中这部分要收钱滴。。。


       CoreOS被设计成一个现代的、最小化的系统来构建你的平台,CoreOS比一般的linux系统少用一半的内存。CoreOS提供完整的计算能力,它可以自动的扩展和管理。它可以让你像google那样管理大规模的机器。CoreOS被设计成安全的、一致的、可靠的系统,用来代替yum、apt等包管理系统,CoreOS在一个较高的抽象层次使用linux容器来管理你的服务,一个应用的代码和它所依赖的所有外部条件都包含在一个容器内部,这个容器可以运行在一个或者多个CoreOS机器上面。linux容器提供了和虚拟机相似的好处,但是应用却不用进入虚拟机里面,容器没有自己的内核或者hypervisor,CoreOS可以运行在多数平台上面,包括vagrant、EC2、qemu/kvm、vmvare、openstack等等。CoreOS的集群可以横跨多个不同的云平台。

       CoreOS利用docker运行服务,CoreOS主要的部分就包含docker,docker是一个linux容器引擎,在docker上运行你的应用,docker安装在CoreOS机器上,你应该为你的每一个应用构建一个容器,可以使用fleet和etcd来启动管理它们。CoreOS使用fleet来管理cluster,建议使用fleet来管理在CoreOS上面运行的docker容器,fleet可以把你的整个集群抽象显示成一个单一的系统,fleet通过配合systemd unit file来工作,用来调度容器运行在集群的哪台机器上面。fleet也可以用来部署高可用的服务,把这些服务部署在不同的机器上面。使用这样一个命令,fleet可以横跨整个集群启动多个容器,而不需要复杂的部署脚本。CoreOS使用etcd来发现服务,每个主机都为etcd提供了一个本地的endpoint,它用来服务发现和配置/读取配置文件,etcd是分布式的,所有的改变都会映射到整个集群上面。

        有了fleet,你可以通过一个单一的接口来管理你的整个CoreOS集群,它鼓励用户编写一些短小的应用和小的units文件,这样就会很容易的在集群中迁移。有了fleet,devops就可以把关注点放在运行在容器的服务上面,而不用担心每个机器上都运行了哪些容器。如果你的应用有5个容器,fleet保证他们持续的运行在集群的某些机器上,如果一个机器故障或者需要升级,容器会把那台故障的机器上的应用迁移到集群中的其他机器上。通过让容器运行的服务部署在不同的机器上来达到高可用,这样它就有了以下特性:

1    可以在集群中任意的机器上部署容器
2    分布式的服务横跨集群
3    维护N个服务,自动调度失败的服务
4    发现在集群中运行的机器
5    自动通过ssh到集群上运行job

下面主要了解systemd和etcd两个部分

systemd

        CoreOS使用systemd作为它分布式系统的核心,systemd支持很多linux发行版,CoreOS深深的整合了systemd的各个方面。之所以选择systemd有以下原因:

1    性能:systemd引导特别快,我们的目标是把它保持在1s以下    
2    日志:systemd使用现代的日志格式,例如JSON,查询索引等等
3    socket激活:

        systemd有着丰富的语法来描述服务,可以描述一个特定服务的属性,你的服务可以明确的表达对软件还是硬件的依赖,服务相对于这些依赖项的顺序来启动,并识别冲突的服务,docker容器变得更容易管理,你可以指定容器是否自动重启和定时重启,服务文件是普通的配置文件,他们很容易编辑和保存在版本控制系统中。按需生成服务文件也很容易,如果你掌握了systemd的编程方式,你甚至可以不用手动控制服务的启动和停止。systemd是一个init系统,它在启动、停止、管理进程活动时提供了许多有用的特性,在CoreOS的世界中,你几乎完全使用systemd来管理你的docker容器。

        systemd包含两部分:unit和target。unit是一个配置文件,用来描述你想运行的进程信息,正常情况下是docker run之类的命令。target是一个分组机制,允许systemd在同一时刻启动进程组。这个过程发生在每一个进程的启动过程中。

        systemd是CoreOS启动的第一个进程,它会读取不同的targets,然后启动里面指定的进程。你可以通过multi-user.target和target进行交互,它含有所有容器的unit文件。实际上每个target收集每个unit文件的软连接,这个在unit文件中的WantedBy=multi-user.target来指定,执行systemctl enable foo.service命令,则会在multi-user.target里面创建unit的软连接。

    unit文件

        在CoreOS中,unit文件存放在/etc/systemd/system目录下,让我们先创建一个hello.service文件:

[Unit] 
Description=My Service 
After=docker.service 
Requires=docker.service 

[Service] 
ExecStart=/usr/bin/docker run busybox /bin/sh -c "while true; do echo Hello World; sleep 1; done" 

[Install] 
WantedBy=multi-user.target
        这个配置文件给我们展示了一些了配置信息,After=docker.service和Requires=docker.service指令的意思是这个unit文件会在docker.service active之后启动,ExecStart=指令允许你指定一些你想在unit启动后运行命令,WantedBy=指定那个target是该unit文件的一部分。现在我们来启动一个新的unit,在启动unit之前,我们需要先创建软连接,然后启动unit。

systemctl enable /etc/systemd/system/hello.service
systemctl start hello.service
        为了检查unit的启动情况,你可以通过docker ps来查看容器的运行情况,也可以通过journalctl命令来读取unit的输出信息:

journalctl -f -u hello.service
    高级unit文件 

        systemd在unit文件中提供了一些内建的功能函数,下面是发生在一个unit生命周期里有用的指令参数:

ExecStartPre    在ExecStart之前执行
ExecStart    在unit中执行的主要命令
ExecStartPost    在ExecStart执行完毕后执行
ExecReload    当通过systemctl指令来reload这个foo.service时执行的命令
ExecStop    当通过systemctl指令来stop这个foo.service时执行的命令或者这个unit已经被认为是失败的时候执行的命令
ExecStopPost    在ExecStop执行完毕后执行
RestartSec    restart一个服务的时候sleep的时间
            更多的systemd指令参考: systemd.servive 

                                           systemd.unit

    现在让我们根据只几个简单的指令创建一个新的unit文件:(里面的etcd服务后面介绍)

[Unit] 
Description=My Advanced Service 
After=etcd.service 
After=docker.service 

[Service] 
ExecStart=/bin/bash -c '/usr/bin/docker start -a apache || /usr/bin/docker run --name apache -p 80:80 CoreOS/apache /usr/sbin/apache2ctl -D FOREGROUND' 
ExecStartPost=/usr/bin/etcdctl set /domains/example.com/10.10.10.123:8081 running 
ExecStop=/usr/bin/docker stop apache 
ExecStopPost=/usr/bin/etcdctl rm /domains/example.com/10.10.10.123:8081 

[Install] 
WantedBy=multi-user.target
        unit变量,在上面的配置文件中,我们硬编码了一些IP地址,这样是不利于横向扩展的,systemd有一些内建的变量可以帮助我们:

%n    unit的全名
%m    机器ID
%b    类似于%m,但是这个值是随机的,每次boot时都会改变
%H    主机名
        实例化unit,systemd是基于软连接的,当你使用容器的时候,下面有几个有趣的小技巧,如果你在一个unit文件中创建多个软连接,下面这些变量可以帮助你:

%p    prefix名称
%i    实例名称
    systemctl命令行工具

        systemctl是systemd的用户接口,在一台机器上的所有进程的启动和管理都是systemd来控制的,包括docker容器的启动,下面我们看看systemctl命令的用法,你必须子啊CoreOS上运行以下命令:

            查看容器的状态,例如如果你在unit文件中配置了多个Exec指令,你想知道哪一步执行失败了。

systemctl status custom-registry.service

            列出所有unit文件的状态,列出所有运行的进程可能比较多,但是你可以通过管道过滤出你要的信息。

systemctl list-units | grep .service

            启动、停止、杀死、重启一个服务

systemctl start apache.service
systemctl stop apache.service
systemctl kill apache.service
systemctl restart apache.service
    读取system的log

        journalctl 命令是你读取单台机器上的journal/logging的接口,fleetctl journal指令会读取通过fleet启动的容器的journal。所有文件和docker容器会插入数据到systemd的journal中。以下命令可以读取journal:

            读取整个journalctl

journalctl

            读取某一个服务的整个journal,加入--tunnel参数,可以远程读取

journalctl -u apache.service
fleetctl --tunnel 10.10.10.10 journal apache.service

            读取最后一次启动以来的boot日志

journalctl --boot

            实时查看

journalctl  -f
journalctl -u apache.service -f
    使用fleet来运行systemd units

        fleet显示集群信息,作为一个分布式的init系统运行在每台机器上。units被提交到fleet,然后根据fleet-specific的配置文件编码unit文件,units文件被调度到集群中的机器上面。fleet提供一个直接的通道到systemd。这意味着你的unit文件可以利用systemd的高级特性,比如:两个unit文件可以声明他们必须停留在同一台机器上,如果机器运行这两个unit失败了,它会在集群中其他合适的主机上启动这两个units。

            利用fleet启动容器

                fleet通过在集群层面控制systemd,在集群层面管理集群。为了在集群中运行你的服务,你必须提交合规则的systemd units。本文假设你在CoreOS本地运行fleetctl命令,他是CoreOS集群的一部分,当然你也可以远程控制你的集群。在集群中运行一个容器:在集群中运行一个单个的容器是很容易的,你只需要提供一个合规则的unit文件,不需要Install模块了,我们创建一个unit文件,myapp.service

[Unit] 
Description=MyApp 
After=docker.service 
Requires=docker.service 

[Service] 
ExecStart=/usr/bin/docker run busybox /bin/sh -c "while true; do echo Hello World; sleep 1; done"
                然后在集群中启动这个容器

fleetctl start myapp.service

             列出所有在集群中的units,查看当前的状态

fleetctl list-units

             你也可以查看集群中有哪些机器

fleetctl list-machines
            运行一个高可用的服务:

                使用CoreOS的好处是,你可以让你的服务运行在一个高可用的模式下,我们来部署两个容器运行apache web,首先我们创建两份unit文件:apache.1.service和apache.2.service

[Unit] 
Description=My Apache Frontend 
After=docker.service 
Requires=docker.service 

[Service] 
ExecStart=/usr/bin/docker run -rm --name apache -p 80:80 CoreOS/apache /usr/sbin/apache2ctl -D FOREGROUND 
ExecStop=/usr/bin/docker rm -f apache 

[X-Fleet] 
X-Conflicts=apache.*.service

                其中X-Conflicts参数告诉fleet,这两个服务不能运行在同一台机器上,我们要高可用。关于fleet unit的参数参见: fleet unit 

                我们来启动这两个units,看看他们是否在不同的机器上:

fleetctl start apache.*
fleetctl list-units

                根据以上步骤,我们已经把容器建立好了,现在我们怎么把流量导入呢?最好的方案是运行一个sidekick容器,它执行一些其他的和我们主要应用容器相关的功能。常见的sidekick容器用来发现服务和控制外部服务,例如负载均衡。 

          运行一个简单的sidekick

                以服务发现为例:unit盲目的宣布我们的容器已经开始运行,我们创建两个unit文件:apache-discovery.1.service和apache-discovery.2.service,注意修改配置文件中apache.1.service和apache2变量。

[Unit] 
Description=Announce Apache1 
BindsTo=apache.1.service 

[Service] 
ExecStart=/bin/sh -c "while true; do etcdctl set /services/website/apache1 '{ \"host\": \"%H\", \"port\": 80, \"version\": \"52c7248a14\" }' --ttl 60;sleep 45;done" ExecStop=/usr/bin/etcdctl rm /services/website/apache1 

[X-Fleet]  
X-ConditionMachineOf=apache.1.service
                 这个unit文件插入了几个有趣的指令,利用BindsTo指令连接到apache.x.service unit上,当apache unit停止了,这个unit也会停止,并且删除我们在etcd中的/services/website目录,TTL 60的意思是如果我们的机器突然故障,60秒后在目录中删除unit文件。第二个有趣的是%H,这个变量是systemd内建的,代表运行unit的主机名。第三个是X-ConditionMachineOf指令,该指令使当前unit运行在和apache.1.service的同一台机器上。让我们启动unit,然后查看是否和apache service运行在同一台机器上: 

fleetctl start apache-discovery.1.service
fleetctl list-units
                  检查服务发现是否正常工作 

etcdctl ls /services/ --recursive
etcdctl get /services/website/apache1

    fleet客户端的使用

        fleet提供了一个CLI命令叫fleetctl,和systemctl类似,当用户和整个集群交互或者断开systemd的实例时,都会用到该命令。fleetctl包含在CoreOS发行版中,因此你可以像ssh那样远程到你的CoreOS上执行fleetctl命令。fleetctl需要直接和在fleet机器上配置的etcd集群通讯,,可以使用--endpoint标记覆盖默认的连接,默认是连接本机的http://127.0.0.1:4001。其实--endpoint标记也可以通过FLEETCTL_ENDPOINT环境变量来设置。

fleetctl --endpoint http://<IP:PORT> list-units

        从外部机器执行fleetctl

            如果你更喜欢从外部机器执行fleetctl,--tunnel标记可以帮组你利用ssh和你的fleet集群通讯,--tunnel也可以用FLEETCTL_TUNNEL环境变量替代

fleetctl --tunnel <IP[:PORT]> list-units

            如果你同时使用了--endpoint和--tunnel两个标记,则所有的etcd请求都会通过ssh隧道传输,因此--endpoint标记的地址必须可以路由到server上。

        和units交互

            列出fleet集群中的所有units

fleetctl list-units

            把units文件push到集群,给fleet集群提交unit文件,并不会导致他们被调度到host上,可以通过fleetctl list-units命令来查看,但是并不会报告他们的状态

fleetctl submit examples/hello.service
fleetctl submit examples/*

            从集群中移除units

fleetctl destroy hello.service

            查看unit的内容

cat examples/hello.service

            启动、停止units

fleetctl start hello.service
fleetctl stop hello.service

            查看unit的状态

fleetctl status hello.service

            查询unit的log

fleetctl journal hello.service
        查询集群信息

            列举集群中的主机

fleetctl list-machines

            ssh自动连接到主机

fleetctl ssh ‘machine ID’
fleetctl ssh --unit hello.service

    使用fleet实战部署服务

        制作docker镜像

            1    CoreOS/subgun

FROM stackbrew/ubuntu:precise
RUN apt-get install -y --allow-unauthenticated ca-certificates
ADD bin/subgun /bin/
EXPOSE 8080
ENV SUBGUN_LISTEN 127.0.0.1:8080
CMD /bin/subgun

            2    CoreOS/elb-presence

FROM stackbrew/ubuntu:precise
RUN apt-get update
RUN apt-get install -y python-requests python-boto
ADD bin/elb-presence /bin/elb-presence
CMD /bin/elb-presence

        服务文件

            1    subgun-http.1.service

[Unit]
Description=subgun
[Service]
ExecStart=/usr/bin/docker run -rm -name subgun-1 -e SUBGUN_LISTEN=127.0.0.1:8080 -e SUBGUN_LISTS=recv@sandbox2398.mailgun.org -e SUBGUN_API_KEY=key-779ru4cibbnhfa1qp7a3apyvwkls7ny7 -p 8080:8080 CoreOS/subgun
ExecStop=/usr/bin/docker kill subgun-1
[X-Fleet]
X-Conflicts=subgun-http.*.service

            2    subgun-presence.1.service

[Unit]
Description=subgun presence service
BindsTo=subgun-http.1.service
[Service]
ExecStart=/usr/bin/docker run -rm -name subgun-presence-1 -e AWS_ACCESS_KEY=AKIAIBC5MW3ONCW6J2XQ -e AWS_SECRET_KEY=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 -e AWS_REGION=us-east-1 -e ELB_NAME=fleet-lb CoreOS/elb-presence
ExecStop=/usr/bin/docker kill subgun-presence-1
[X-Fleet]
X-ConditionMachineOf=subgun-http.1.service

        部署服务

            1    提交

fleetctl submit subgun-presence.*.service subgun-http.*.service
fleetctl list-units

            2    启动

fleetctl start subgun-presence.*.service subgun-http.*.service
fleetctl list-units




etcd

    下面了解CoreOS的另一个主要部分etcd,etcd是一个高可用的K/V存储系统,用来存储共享的配置文件和服务发现。它为CoreOS集群提供了主要的功能,etcd客户端运行在集群中的每一台机器上,在网络出现问题或者当前master故障的时候,etcd会优雅的进行master重新选举。你的应用可以从etcd中读写数据,例如存储数据连接信息、cache、特性标记等等。etcd有哪些特性呢:

1    简单,curl访问,基于http返回JSON格式的数据
        2    客户端可以通过openssl认证
        3    每个实例1000/s的写速度
        4    使用raft协议进行正确的分发
        5    key支持TTL
        6    支持原子操作
        7

     etcd的API接口信息: api 

    从etcd中读写数据

        1    etcd的基于http的api很容易使用,你可以使用etcdctl或者curl来访问,注意-L参数是curl命令需要使用的。登陆一台CoreOS机器,设置message=hello

$ etcdctl set /message Hello
Hello

$ curl -L -X PUT http://127.0.0.1:4001/v2/keys/message -d value="Hello"
{"action":"set","node":{"key":"/message","value":"Hello","modifiedIndex":4,"createdIndex":4}}
                可以看到etcdctl明显比curl要简单,现在从message中读取相应的值

$ etcdctl get /message
Hello

$ curl -L http://127.0.0.1:4001/v2/keys/message {"action":"get","node":{"key":"/message","value":"Hello","modifiedIndex":4,"createdIndex":4}}
                如果你有多台CoreOS,你可以通过ssh登陆到其他CoreOS机器上,然后重写相同的key的值,例如删除一个key:

$ etcdctl rm /message

$ curl -L -X DELETE http://127.0.0.1:4001/v2/keys/message {"action":"delete","node":{"key":"/message","modifiedIndex":19,"createdIndex":4}}

                从容器内部进行读写

                    在一个容器内部读写etcd,你必须使用通过ip address show查看你可以使用哪个地址,一般使用docker@,这个接口地址一般是172.17.42.1

                代理

                    我们假设我们在代理后面启动了一个服务,该服务由多个容器构成,我们可以通过etcd宣布这些容器时首先创建一个目录,使每个容器写一个key到这个目录中,并且可以通过代理查看整个目录,我们跳过创建容器的过程,直接开始创建目录:

                    1    创建目录,目录会被自动的创建,当一个key被自动提及的时候

$ etcdctl mkdir /foo-service
Cannot print key [/foo-service: Is a directory] $ etcdctl set /foo-service/container1 localhost:1111
localhost:1111
                            第一步创建 目录不成功,但是当第二部目录后面带一个key的时候,再创建就会自动创建相关的目录

$ curl -L -X PUT http://127.0.0.1:4001/v2/keys/foo-service/container1 -d value="localhost:1111" {"action":"set","node":{"key":"/foo-service/container1","value":"localhost:1111","modifiedIndex":17,"createdIndex":17}}
                        从文件目录查看里面的内容

$ etcdctl ls /foo-service
/foo-service/container1

$ curl -L http://127.0.0.1:4001/v2/keys/foo-service {"action":"get","node":{"key":"/foo-service","dir":true,"nodes":[{"key":"/foo-service/container1","value":"localhost:1111","modifiedIndex":17,"createdIndex":17}],"modifiedIndex":17,"createdIndex":17}}

                    2    监视目录

                            监控foo-service目录的变化,在shell打开一个CoreOS,在这个窗口开启目录监控,再另外一个窗口,增加另外一个key:container2,value:localhost:2222到目录中,这个命令不会有任何输出,除非key有变化,许多事件可以触发一个变化,例如增删或者key过期等等。 

$ etcdctl watch /foo-service --recursive

$ curl -L http://127.0.0.1:4001/v2/keys/foo-service?wait=true\&recursive=true
                             在另外一个窗口,创建一个key

$ etcdctl set /foo-service/container2 localhost:2222
localhost:2222

$ curl -L -X PUT http://127.0.0.1:4001/v2/keys/foo-service/container2 -d value="localhost:2222" {"action":"set","node":{"key":"/foo-service/container2","value":"localhost:2222","modifiedIndex":23,"createdIndex":23}}
                            在第一个窗口,你应该看到key改变的消息,在实际应用中,这个会触发重新配置 

$ etcdctl watch /foo-service --recursive
localhost:2222

$ curl -L http://127.0.0.1:4001/v2/keys/foo-service?wait=true\&recursive=true {"action":"set","node":{"key":"/foo-service/container2","value":"localhost:2222","modifiedIndex":23,"createdIndex":23}}

                    3    test和set

                            etcd可以用作一个集中的协调服务并提供testandset功能作为服务的构建块,你必须提供原始值和新的值,如果前面的值匹配当前的值,则操作会成功

$ etcdctl set /message "Hi" --swap-with-value "Hello" Hi

$ curl -L -X PUT http://127.0.0.1:4001/v2/keys/message?prevValue=Hello -d value=Hi {"action":"compareAndSwap","node":{"key":"/message","value":"Hi","modifiedIndex":28,"createdIndex":27}}

                    4    TTL


                            你可以设置一个key的ttl,以秒为单位

$ etcdctl set /foo "Expiring Soon" --ttl 20
Expiring Soon
                            curl会返回一个绝对的时间戳,key什么时候过期。

$ curl -L -X PUT http://127.0.0.1:4001/v2/keys/foo?ttl=20 -d value=bar {"action":"set","node":{"key":"/foo","value":"bar","expiration":"2014-02-10T19:54:49.357382223Z","ttl":20,"modifiedIndex":31,"createdIndex":31}}
                            如果请求的值过期,则会返回一个100的错误码 

$ etcdctl get /foo
Error: 100: Key not found (/foo) [32]

$ curl -L http://127.0.0.1:4001/v2/keys/foo {"errorCode":100,"message":"Key not found","cause":"/foo","index":32}

        2    通过HTTPS读写etcd

                参考:etcd https

        

update

    CoreOS的升级策略,我们相信经常的、可靠地升级是一个很好的安全策略。不幸的是,目前已有的配置管理工具在比较大的集群中经常会遇到不一致的状态,这样使得在一个大的集群中更新变得很复杂,CoreOS通过划分每个实体的复杂性进行最小化更新,通常需要更新的是:操作系统、应用代码、配置文件。

        1    操作系统

                CoreOS通过使用FastPatch来进行一致性的更新,CoreOS有两个root分区,是主动/被动模式的。我们更新整个操作系统,而不是更新包。release channel用来提供健康平衡能力和新特性。为了进一步的控制,更新策略可以在整个集群中配置。

        2    应用代码

            运行部署你的代码在容器中,每个应用的包都包含了它运行所需的所有依赖,这会消除依赖上的冲突和测试用例。容器可以从dev->test->prod环境,容器隔离意味着你可以独立的更新每个应用。所以即使一个差的代码也会影响在同一台机器上的其他应用

        3    配置文件

                分布式的平台一般都包含很多配置值,cache设置、数据库地址、防火墙规则、速率限制等等都存储在某个地方,传统式我们使用chef或者puppet来更新这些值,但是,在你执行单一的配置变化之前你不能审计成百上千的机器的状态。What if you triggered a library upgrade on a machine that was missed on the last run?为了解决这个问题,CoreOS使用etcd提供了分布式的配置,一个单一的配置值可以被原子的改变,并且只有监听这些变化的应用会受到影响。每个应用可以决定是否理会相关配置值的改变,它的逻辑可以独立于其他应用程序的更新。





转载于:https://my.oschina.net/guol/blog/283473

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值