Mesos+Zookeeper+Marathon的Docker管理平台部署记录(1)



随着"互联网+"时代的业务增长、变化速度及大规模计算的需求,廉价的、高可扩展的分布式x86集群已成为标准解决方案,如Google已经在几千万台服务器上部署分布式系统。Docker及其相关技术的出现和发展,又给大规模集群管理带来了新的想象空间。如何将二者进行有效地结合?下面将记录使用Mesos+Zookeeper+Marathon+Docker分布式部署Paas云平台环境,其中

1
2
3
4
1)Mesos:Mesos采用与Linux kernerl相同的机制,只是运行在不同的抽象层次上。Mesos kernel利用资源管理和调度的API在整个数据中心或云环境中运行和提供引用(例如,Hadoop,Spark,Kafaka,Elastic Search)。
2)Zookeeper:zooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、名字服务、分布式同步、组服务等。
3)Marathon:marathon是一个mesos框架,能够支持运行长服务,比如web应用等。是集群的分布式Init.d,能够原样运行任何Linux二进制发布版本,如Tomcat Play等等,可以集群的多进程管理。也是一种私有的Pass,实现服务的发现,为部署提供提供REST API服务,有授权和SSL、配置约束,通过HAProxy实现服务发现和负载平衡
4)Docker:Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。

一、Mesos是什么?

Mesos是Apache下的开源分布式资源管理框架,它被称为是分布式系统的内核。Mesos能够在同样的集群机器上运行多种分布式系统类型,更加动态有效率低共享资源。提供失败侦测,任务发布,任务跟踪,任务监控,低层次资源管理和细粒度的资源共享,可以扩展伸缩到数千个节点。Mesos已经被Twitter用来管理它们的数据中心。

Mesos中的基本术语解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1)Mesos-master:主要负责管理各个framework和slave,并将slave上的资源分配给各个framework
2)Mesos-slave:负责管理本节点上的各个mesos-task,比如:为各个executor分配资源
3)Framework:计算框架,如:Hadoop,Spark等,通过MesosSchedulerDiver接入Mesos
4)Executor:执行器,安装到mesos-slave上,用于启动计算框架中的task。
 
其中:
1)Mesos-master是整个系统的核心,负责管理接入mesos的各个framework(由frameworks_manager管理)和 slave(由slaves_manager管理),并将slave上的资源按照某种策略分配给framework(由独立插拔模块Allocator管 理)。
 
2)Mesos-slave负责接收并执行来自mesos-master的命令、管理节点上的mesos-task,并为各个task分配资源。 mesos-slave将自己的资源量发送给mesos-master,由mesos-master中的Allocator模块决定将资源分配给哪个 framework,
前考虑的资源有CPU和内存两种,也就是说,mesos-slave会将CPU个数和内存量发送给mesos-master,而用 户提交作业时,需要指定每个任务需要的CPU个数和内存量,这样,当任务运行时,mesos-slave会将任务放到包含固定资源
的linux container中运行,以达到资源隔离的效果。很明显,master存在单点故障问题,为此,mesos采用了zookeeper解决该问题。
 
3)Framework是指外部的计算框架,如Hadoop,Mesos等,这些计算框架可通过注册的方式接入mesos,以便mesos进行统一管理 和资源分配。Mesos要求可接入的框架必须有一个调度器模块,该调度器负责框架内部的任务调度。
当一个framework想要接入mesos时,需要修 改自己的调度器,以便向mesos注册,并获取mesos分配给自己的资源, 这样再由自己的调度器将这些资源分配给框架中的任务,也就是说,整个mesos系统采用了双层调度框架:
第一层,由mesos将资源分配给框架;第二层, 框架自己的调度器将资源分配给自己内部的任务。当前Mesos支持三种语言编写的调度器,分别是C++,java和python,为了向各种调度器提供统 一的接入方式,Mesos内部采用
C++实现了一个MesosSchedulerDriver(调度器驱动器),framework的调度器可调用该 driver中的接口与Mesos-master交互,完成一系列功能(如注册,资源分配等)。
 
4)Executor主要用于启动框架内部的task。由于不同的框架,启动task的接口或者方式不同,当一个新的框架要接入mesos时,需要编写 一个executor,告诉mesos如何启动该框架中的task。为了向各种框架提供统一的执行器编写方式,
Mesos内部采用C++实现了一个 MesosExecutorDiver(执行器驱动器),framework可通过该驱动器的相关接口告诉mesos启动task的方法。

先来看下Mesos的基础架构

首先Mesos是一个Master/Agent的架构方式,其中:

1
2
3
4
5
6
7
8
1)Master负责资源的统一管理跟任务的分发
2)Agent负责起停执行器,汇报主机资源、执行器状态等信息。
3)一般情况下,会启动3个以上Master,以确保高可用,Master的状态由Zookeeper维护。
4)Framework是Mesos上的调度框架,Marathon Hadoop Chonous都是比较常见的任务调度框架。
 
这样的架构给人的整体感受就清晰明朗了。另外:
1)每台机器上都会部署一个Mesos-Agent,Agent会把信息汇报给Master。
2)调度器scheduler向Mesos-Master请求资源,Mesos-Master把所有可用的资源都反馈给Scheduler,Scheduler根据自己的规则决定该部署到哪一台。大致就是这样一个流程。

Mesos总体架构图如下:

上图展示了mesos的重要组成部分:

1
2
3
4
5
6
1)mesoso由一个master进程管理运行着每个客户端节点的salve进程和跑任务的mesos计算框架。master进程通过计算框架可以很细致的管理cpu和内存等,从而提供资源。
每个资源提供与包含了一个清单(slave ID, resource1: amount1, resource2, amount2, …),master会根据现有的政府决定提供每个计算框架多少资源,例如公平分享或者根据优先级分享。
为了支持不同种的政策,master通过插件机制新增了一个allocation模块使之分配资源更简单方便。
  
2)一个计算框架运行在两个组件之上,一个是scheduler,他是master提供资源的注册中心,另一个是executor程序,用来发起在slave节点上运行计算框架的任务。
master决定给每个计算框架提供多少计算资源,计算框架的的调度去选择使用哪个资源。当一个计算框架接受了提供的资源,他会通过mesos的任务描述运行程序,mesos也会在相应的slave上发起任务。

从上面图中可以看到,Mesos有Framework(Framework里面有Scheduler), Master(Master里面有Allocator)、Agent、Executor、Task几部分组成。
这里面有两层的Scheduler,一层在Master里面,Allocator会将资源公平的分给每一个Framework,二层在Framework里面,Framework的Scheduler将资源按规则分配给Task。
Mesos的这几个角色在一个任务运行的生命周期中,相互关系如下:

Agent会将资源汇报给Master,Master会根据Allocator的策略将资源offer给Framework的Scheduler。Scheduler 可以accept这个资源,运行一个Task,Master将Task交给Agent,Agent交给Executor去真正的运行这个Task。

Mesos资源提供的例子

简单梳理下以上图中的流程步骤:

1
2
3
4
5
1)slave 1 报告给master他拥有4核cpu和4G剩余内存,matser调用allocation政策模块,告诉salve 1 计算框架1应该被提供可用的资源。
2)master给计算框架1发送一个在slave1上可用的资源描述。
3)计算框架的调度器回复给master运行在slave上两个任务的相关信息,任务1需使用2个cpu,内存1G,任务2需使用1个cpu,2G内存。
4)最后,master发送任务给slave,分配适当的给计算框架执行器,继续发起两个任务(图上虚线处),因为仍有1个cpu和1G内存未分配,allocation模块现在或许提供剩下的资源给计算框架2。
除此之外,当任务完成,新的资源成为空闲时,这个资源提供程序将会重复。

Mesos框架是一个在Mesos上运行分布式应用的应用程序,它有两个组件:

1
2
1)调度器 : 与Mesos交互,订阅资源,然后在mesos从服务器中加载任务。
2)执行器 : 从框架的环境变量 配置中获得信息,在mesos从服务器中运行任务。

下面看看其是如何实现资源调用?Mesos通过"resources offers" 分配资源,资源其实是当前可用资源的一个快照,调度器将使用这些资源在mesos从服务器上运行任务。
Mesos主从服务器调度资源的顺序图如下:

首先由Mesos主服务器查询可用资源给调度器,第二步调度器向主服务器发出加载任务,主服务器再传达给从服务器,从服务器向执行器命令加载任务执行,执行器执行任务以后,将状态反馈上报给从服务器,最终告知调度器 。
从服务器下管理多个执行器,每个执行器是一个容器,以前可以使用Linux容器LXC,现在使用Docker容器。

Mesos失败恢复和高可用性

1
2
Mesos主服务器使用Zookeeper进行服务选举和发现。它有一个注册器记录了所有运行任何和从服务器信息,使用MultiPaxos进行日志复制实现一致性。
Mesos有一个从服务器恢复机制,无论什么时候一个从服务器死机了,用户的任务还是能够继续运行,从服务器会将一些关键点信息如任务信息状态更新持久化到本地磁盘上,重新启动时可以从磁盘上恢复运行这些任务(类似Java中的钝化和唤醒)

二、 Zookeeper是什么?

ZooKeeper是用来给集群服务维护配置信息,域名服务,提供分布式同步和提供组服务。所有这些类型的服务都使用某种形式的分布式应用程序。ZooKeeper是一个分布式的,开放源码的协调服务,是的Chubby一个的实现,是Hadoop和Hbase的重要组件。

ZooKeeper角色

1
2
3
4
领导者(leader):领导者负责投票发起和决议,更新系统状态
跟随者(follwoer):follower用于接收客户请求并向客户端返回结果,在选主过程中参与投票
观察者:ObServer可以接受客户端连接,将写请求转发给leader节点,但ObServer不参加投票过程,只同步leader的状态,ObServer的目的是为了拓展系统,提高读取速度。
客户端:请求发起方

ZooKeeper的工作原理

1
2
3
4
5
6
Zookeeper的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。Zab协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。
为了保证事务的顺序一致性,zookeeper采用了递增的事务 id 号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加上了zxid。实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch,标识当前属于那个leader的统治时期。低32位用于递增计数。
每个Server在工作过程中有三种状态:
1)LOOKING:当前Server不知道leader是谁,正在搜寻
2)LEADING:当前Server即为选举出来的leader
3)FOLLOWING:leader已经选举出来,当前Server与之同步

ZooKeeper选主流程

1
2
3
4
5
6
7
8
9
当leader崩溃或者leader失去大多数的follower,这时候zk进入恢复模式,恢复模式需要重新选举出一个新的leader,让所有的Server都恢复到一个正确的状态。Zk的选举算法有两种:一种是基于basic paxos实现的,另外一种是基于fast paxos算法实现的。系统默认的选举算法为fast paxos。先介绍basic paxos流程:
1)选举线程由当前Server发起选举的线程担任,其主要功能是对投票结果进行统计,并选出推荐的Server;
2)选举线程首先向所有Server发起一次询问(包括自己);
3)选举线程收到回复后,验证是否是自己发起的询问(验证zxid是否一致),然后获取对方的 id (myid),并存储到当前询问对象列表中,最后获取对方提议的leader相关信息( id ,zxid),并将这些信息存储到当次选举的投票记录表中;
4)收到所有Server回复以后,就计算出zxid最大的那个Server,并将这个Server相关信息设置成下一次要投票的Server;
5)线程将当前zxid最大的Server设置为当前Server要推荐的Leader,如果此时获胜的Server获得n /2   + 1的Server票数, 设置当前推荐的leader为获胜的Server,将根据获胜的Server相关信息设置自己的状态,否则,继续这个过程,直到leader被选举出来。
 
通过流程分析我们可以得出:要使Leader获得多数Server的支持,则Server总数必须是奇数2n+1,且存活的Server的数目不得少于n+1.
每个Server启动后都会重复以上流程。在恢复模式下,如果是刚从崩溃状态恢复的或者刚启动的server还会从磁盘快照中恢复数据和会话信息,zk会记录事务日志并定期进行快照,方便在恢复时进行状态恢复。

选主的具体流程图如下所示:

fast paxos(算法优化)流程是在选举过程中,某Server首先向所有Server提议自己要成为leader,当其它Server收到提议以后,解决epoch和zxid的冲突,并接受对方的提议,然后向对方发送接受提议完成的消息,重复这个流程,最后一定能选举出Leader。其流程图如下所示:

ZooKeeper同步流程

1
2
3
4
5
6
选完leader以后,zookeeper就进入状态同步过程。
1)leader等待server连接;
2)Follower连接leader,将最大的zxid发送给leader;
3)Leader根据follower的zxid确定同步点;
4)完成同步后通知follower 已经成为uptodate状态;
5)Follower收到uptodate消息后,又可以重新接受client的请求进行服务了。

流程图如下所示:

ZooKeeper工作流程

1)Leader工作流程

1
2
3
4
5
6
Leader主要有三个功能:
1)恢复数据;
2)维持与Learner的心跳,接收Learner请求并判断Learner的请求消息类型;
3)Learner的消息类型主要有PING消息、REQUEST消息、ACK消息、REVALIDATE消息,根据不同的消息类型,进行不同的处理。
 
PING消息是指Learner的心跳信息;REQUEST消息是Follower发送的提议信息,包括写请求及同步请求;ACK消息是Follower的对提议的回复,超过半数的Follower通过,则commit该提议;REVALIDATE消息是用来延长SESSION有效时间。

Leader的工作流程简图如下所示,在实际实现中,流程要比下图复杂得多,启动了三个线程来实现功能。

2)Follower工作流程

1
2
3
4
5
6
7
8
9
10
11
12
Follower主要有四个功能:
1)向Leader发送请求(PING消息、REQUEST消息、ACK消息、REVALIDATE消息);
2)接收Leader消息并进行处理;
3)接收Client的请求,如果为写请求,发送给Leader进行投票;
4)返回Client结果。
Follower的消息循环处理如下几种来自Leader的消息:
1)PING消息: 心跳消息;
2)PROPOSAL消息:Leader发起的提案,要求Follower投票;
3)COMMIT消息:服务器端最新一次提案的信息;
4)UPTODATE消息:表明同步完成;
5)REVALIDATE消息:根据Leader的REVALIDATE结果,关闭待revalidate的session还是允许其接受消息;
6)SYNC消息:返回SYNC结果到客户端,这个消息最初由客户端发起,用来强制得到最新的更新。

Follower的工作流程简图如下所示,在实际实现中,Follower是通过5个线程来实现功能的。

三、搞懂marathon

Marathon是一个成熟的,轻量级的,扩展性很强的Apache Mesos的容器编排框架,它主要用来调度和运行常驻服务(long-running service),提供了友好的界面和Rest API来创建和管理应用。marathon是一个mesos框架,能够支持运行长服务,比如web应用等,它是集群的分布式Init.d,能够原样运行任何Linux二进制发布版本,如Tomcat Play等等,可以集群的多进程管理。也是一种私有的Pass,实现服务的发现,为部署提供提供REST API服务,有授权和SSL、配置约束,通过HAProxy实现服务发现和负载平衡。

这样,我们可以如同一台Linux主机一样管理数千台服务器,它们的对应原理如下图,使用Marathon类似Linux主机内的init Systemd等外壳管理,而Mesos则不只包含一个Linux核,可以调度数千台服务器的Linux核,实际是一个数据中心的内核:

Marathon中重要的概念介绍
1)Application是Marathon中一个重要的核心概念,它代表了一个长服务。

2)Application definition表示一个长服务的定义,规定了一个Application启动和运行时的所有行为。Marathon提供了两种方式让你来定义你的长服务,第一种通过Portal来定义,它方便终端用户的理解和使用,另一种是通过JSON格式的文件来定义,并通过RestAPI的方式来创建和管理这个Application,这种方式方便和第三方的系统进行集成,提供了再次的可编程接口。

3)Application instance表示一个Application的实例,也称作Mesos的一个task。Marathon可以为一个Application创建和管理多个实例,并可以动态的增大和减小某个Application实例的个数,并且通过Marathon-lb实现服务发现和负载均衡。

4)Application Group:Marathon可以把多个Application组织成一棵树的结构,Group称为这个树的树枝,Application称为这个树的叶子。同一个Group中的Application可以被Marathon统一管理。

5)Deployments:对Application或者Group的definition的一次修改的提交称为一次deployment。它包括创建,销毁,扩容缩容Application或者Group等。多个deployments可以同时进行,但是对于一个应用的deployments必须是串行的,如果前一个deployment没有结束就执行下一个deployment,那么它将会被拒绝。

--------------------------------------------------------------------------------------------------------------------------------------
四、Mesos+Zookeeper+Marathon的Docker管理平台部署过程记录

1)服务器架构

机器信息如下:

1
2
3
4
5
这里部署的机器为3个Mastser控制节点,3个Slave运行节点,其中:
zookeeper、Mesos-master、marathon运行在Master端;Mesos-slave和docker运行在Slave端;需要修改zk的内容来保证slave能够被master发现和管理
 
这里为了测试方便,全部采用centos7版本系统。当然,在实际生产环境中,也不一定非要要求master和slave端的服务器版本一致,不一样的版本系统也可以。
(当机器数量没有这么多比如只有两台机器的情况下,可以将一台机器即作为Mesos-Master也作为Mesos-Slave,另一台作为Mesos-Slave,也就是一主两从的结构)

为了直观的理解,简单的画了一张架构图:

2)配置mesos-master(3台master节点都要操作)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
1)关闭防火墙和selinux
关闭selinux
[root@master-1 ~] # vim /etc/sysconfig/selinux  
SELINUX=disabled
[root@master-1 ~] # setenforce 0     #临时关闭。reboot重启机器后,使得上面配置生效,就永久关闭selinux了
[root@master-1 ~] # getenforce
Permissive
  
关闭iptables防火墙。
如果不关闭,需要开启2181,5050,8080端口
[root@master-1 ~] # vim /etc/sysconfig/iptables
.......
-A INPUT -p tcp -m state --state NEW -m tcp --dport 2181 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 5050 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 8080 -j ACCEPT
[root@master-1  local ] # systemctl restart iptables.service
     
2)添加mesos的yum源
[root@master-1 ~] # rpm -Uvh http://repos.mesosphere.io/el/7/noarch/RPMS/mesosphere-el-repo-7-1.noarch.rpm
     
3)安装mesos,marathon,zookeeper
[root@master-1 ~] # yum install -y java-1.8.0-openjdk-devel java-1.8.0-openjdk       #安装依赖的JDK环境
[root@master-1 ~] # yum -y install mesos marathon mesosphere-zookeeper
     
4)配置zookeeper
设置文件 /var/lib/zookeeper/myid 为当前mesos master节点的 id id 必须为1-255之中的整数.
     
master-1机器设置 id "1"
[root@master-1 ~] # echo 1 > /var/lib/zookeeper/myid
     
master-2机器设置 id "2"
[root@master-2 ~] # echo 2 > /var/lib/zookeeper/myid
     
master-3机器设置 id "3"
[root@master-3 ~] # echo 3 > /var/lib/zookeeper/myid
     
[root@master-1 ~] # cp /etc/zookeeper/conf/zoo.cfg /etc/zookeeper/conf/zoo.cfg.bak
[root@master-1 ~] # vim /etc/zookeeper/conf/zoo.cfg
maxClientCnxns=50                      #单个客户端与单台服务器之间的连接数的限制,是ip级别的,默认是50,如果设置为0,那么表明不作任何限制。请注意这个限制的使用范围,仅仅是单台客户端机器与单台ZK服务器之间的连接数限制,不是针对指定客户端IP,也不是ZK集群的连接数限制,也不是单台ZK对所有客户端的连接数限制。
tickTime=2000                          #Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime 时间就会发送一个心跳
initLimit=10                           #Zookeeper的Leader 接受客户端(Follower)初始化连接时最长能忍受多少个心跳时间间隔数。当已经超过 5个心跳的时间(也就是tickTime)长度后 Zookeeper 服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是 5*2000=10 秒
syncLimit=5                            #表示 Leader 与 Follower 之间发送消息时请求和应答时间长度,最长不能超过多少个tickTime 的时间长度,总的时间长度就是 2*2000=4 秒
dataDir= /var/lib/zookeeper              #zookeeper数据文件存放目录
clientPort=2181                        #客户端连接端口
server.1=182.48.115.233:2888:3888      #数字1,2,3表示这个是第几号服务器(是上面myid文件里对应的数字);中间的是master主节点的ip地址
server.2=182.48.115.235:2888:3888      #第一个端口2888(这个端口可以自己定义)表示的是这个服务器与集群中的 Leader 服务器交换信息的端口
server.3=182.48.115.236:2888:3888      #第二个端口3888表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。
        
[root@master-1 ~] # vim /etc/mesos/zk             #完全替换原来内容
zk: //182 .48.115.233:2181,182.48.115.235:2181,182.48.115.236:2181 /mesos
     
设置文件 /etc/master-/quorum 内容为一个大于(master节点数除以2)的整数。即采用四舍五入,比如这里有3个master节点,那么3 /2 =1.5,四舍五入为2
[root@master-1 ~] # echo 2 >/etc/mesos-master/quorum
[root@master-1 ~] # cat /etc/mesos-master/quorum
2
     
5)配置mesos和marathon
主机名和ip要在hosts中写入,最好不要使用localhost,否则会出现slave不能识别,以及marathon任务下发不正常等现象。
     
master-1机器
[root@master-1 ~] # mkdir -p /etc/marathon/conf
[root@master-1 ~] # echo 182.48.115.233  > /etc/mesos-master/hostname
[root@master-1 ~] # echo 182.48.115.233 > /etc/marathon/conf/hostname
[root@master-1 ~] # hostnamectl --static set-hostname master-1.com
[root@master-1 ~] # echo "182.48.115.233 master-1 master-1.com" >/etc/hosts
[root@master-1 ~] # cat /etc/hosts
182.48.115.233 master-1 master-1.com
     
master-2机器
[root@master-2 ~] # mkdir -p /etc/marathon/conf
[root@master-2 ~] # echo 182.48.115.235  > /etc/mesos-master/hostname
[root@master-2 ~] # echo 182.48.115.235 > /etc/marathon/conf/hostname
[root@master-2 ~] # hostnamectl --static set-hostname master-2.com
[root@master-2 ~] # echo "182.48.115.235 master-2 master-2.com" >/etc/hosts
[root@master-2 ~] # cat /etc/hosts
182.48.115.235 master-2 master-2.com
     
master-3机器
[root@master-3 ~] # mkdir -p /etc/marathon/conf
[root@master-3 ~] # echo 182.48.115.236  > /etc/mesos-master/hostname
[root@master-3 ~] # echo 182.48.115.236 > /etc/marathon/conf/hostname
[root@master-3 ~] # hostnamectl --static set-hostname master-3.com
[root@master-3 ~] # echo "182.48.115.236 master-3 master-3.com" >/etc/hosts
[root@master-3 ~] # cat /etc/hosts
182.48.115.236 master-3 master-3.com
     
[root@master-1 ~] # cp  /etc/mesos/zk   /etc/marathon/conf/master
[root@master-1 ~] # cp  /etc/mesos/zk   /etc/marathon/conf/zk
[root@master-1 ~] # sed -i  's|mesos|marathon|g'   /etc/marathon/conf/zk
     
6)启动mesos,marathon,zookeeper
[root@master-1 ~] # systemctl enable zookeeper && systemctl enable mesos-master && systemctl enable marathon
[root@master-1 ~] # systemctl start zookeeper && systemctl start mesos-master && systemctl start marathon
[root@master-1 ~] # systemctl disable mesos-slave
     
查看进程状态
[root@master-1 ~] # systemctl status zookeeper
[root@master-1 ~] # systemctl status mesos-master
[root@master-1 ~] # systemctl status marathon
     
[root@master-1 ~] # lsof -i:2181
[root@master-1 ~] # lsof -i:5050
[root@master-1 ~] # lsof -i:8080
     
7)检查配置
配置过程中出错,为了方便检查对比各master配置,直接执行如下命令。
[root@master-1 ~] # cat /var/lib/zookeeper/myid && tail -6 /etc/zookeeper/conf/zoo.cfg && cat /etc/mesos/zk && cat /etc/mesos-master/quorum && cat /etc/mesos-master/hostname&& cat /etc/marathon/conf/hostname&& cat /etc/marathon/conf/master&&cat /etc/marathon/conf/zk
1
syncLimit=5
dataDir= /var/lib/zookeeper
clientPort=2181
server.1=182.48.115.233:2888:3888
server.2=182.48.115.235:2888:3888
server.3=182.48.115.236:2888:3888
zk: //182 .48.115.233:2181,182.48.115.235:2181,182.48.115.236:2181 /mesos
2
182.48.115.233
182.48.115.233
zk: //182 .48.115.233:2181,182.48.115.235:2181,182.48.115.236:2181 /mesos
zk: //182 .48.115.233:2181,182.48.115.235:2181,182.48.115.236:2181 /marathon
    
--------------------------------------------------------------------------------------------------------
温馨提示:
1) 还可以创建  /etc/mesos-master/cluster   文件,写入集群的别名。
2)主机名最好不要轻易更换,否则会导致mesos启动失败!更换主机名后,最好彻底卸载并删除源数据,然后重新部署
# yum remove mesos marathon mesosphere-zookeeper
# rm -rf /etc/mesos*
# rm -rf /etc/marathon*
# rm -rf /var/lib/zookeeper*
# rm -rf /etc/zookeeper*
# rm -rf /var/lib/mesos*      #源数据目录
# yum -y install mesos marathon mesosphere-zookeeper
3)以上操作后,master节点机不能 ping 通外网,是因为DNS解析文件被改变了,执行下面命令即可:
[root@master-1  local ] # echo "nameserver 8.8.8.8" >> /etc/resolv.conf
[root@master-1  local ] # ping www.baidu.com
PING www.a.shifen.com (14.215.177.38) 56(84) bytes of data.
64 bytes from 14.215.177.38: icmp_seq=1 ttl=53  time =38.3 ms
................

3)配置slave节点(3台slave节点都要操作)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
1)关闭selinux和iptables防火墙
关闭selinux
[root@slave-1 ~] # vim /etc/sysconfig/selinux  
SELINUX=disabled
[root@slave-1 ~] # setenforce 0     #临时关闭。reboot重启机器后,使得上面配置生效,就永久关闭selinux了
[root@slave-1 ~] # getenforce
Permissive
  
关闭iptables防火墙。
如果不关闭,需要开启5051端口
[root@slave-1 ~] # vim /etc/sysconfig/iptables
.......
-A INPUT -p tcp -m state --state NEW -m tcp --dport 5051 -j ACCEPTT
[root@slave-1 ~] # systemctl restart iptables.service
        
2)安装docker,安装后启动docker
[root@slave-1 ~] # yum install -y docker
[root@slave-1 ~] # systemctl enable docker
[root@slave-1 ~] # systemctl start docker
         
拉取镜像(三台slave节点机都需要下载镜像,因为在marathon界面里创建docker容器,是随机在slave节点机上读取镜像创建的)
[root@slave-1 ~] # docker pull nginx
[root@slave-1 ~] # docker pull tomcat
[root@slave-1 ~] # docker images
REPOSITORY                   TAG                 IMAGE ID            CREATED             SIZE
docker.io /tomcat               latest              08f8166740f8        4 days ago          366.7 MB
docker.io /nginx                latest              46102226f2fd        2 weeks ago         109.4 MB
        
3)添加mesos的yum源
[root@slave-1 ~] # rpm -Uvh http://repos.mesosphere.io/el/7/noarch/RPMS/mesosphere-el-repo-7-1.noarch.rpm
        
4)安装mesos
[root@slave-1 ~] # yum -y install mesos
        
5)配置master信息
        
slave-1机器
[root@slave-1 ~] # echo 182.48.115.237 > /etc/mesos-slave/hostname
[root@slave-1 ~] # hostnamectl --static set-hostname slave-1.com
[root@slave-1 ~] # echo "182.48.115.237 slave-1 slave-1.com" >/etc/hosts
[root@slave-1 ~] # cat /etc/hosts
182.48.115.237 slave-1 slave-1.com
         
slave-2机器
[root@slave-2 ~] # echo 182.48.115.238 > /etc/mesos-slave/hostname
[root@slave-2 ~] # hostnamectl --static set-hostname slave-2.com
[root@slave-2 ~] # echo "182.48.115.238 slave-2 slave-2.com" >/etc/hosts
[root@slave-2 ~] # cat /etc/hosts
182.48.115.238 slave-2 slave-2.com
        
slave-3机器
[root@slave-3 ~] # echo 182.48.115.239 > /etc/mesos-slave/hostname
[root@slave-3 ~] # hostnamectl --static set-hostname slave-3.com
[root@slave-3 ~] # echo "182.48.115.239 slave-3 slave-3.com" >/etc/hosts
[root@slave-3 ~] # cat /etc/hosts
182.48.115.239 slave-3 slave-3.com
        
[root@slave-1 ~] # vim /etc/mesos/zk
zk: //182 .48.115.233:2181,182.48.115.235:2181,182.48.115.236:2181 /mesos
    
配置marathon调用mesos运行docker容器
[root@slave-1 ~] # echo 'docker,mesos' > /etc/mesos-slave/containerizers
        
6)启动slave(要保证mesos-slave启动后,读取的zk信息是那三个mesos-master的连接信息,否则mesos访问页面里就不会出现这个slave节点信息。 ps   -ef| grep   mesos-slave)
[root@slave-1 ~] # systemctl start mesos-slave && systemctl enable mesos-slave
[root@slave-1 ~] # systemctl disable mesos-master
        
-------------------------------------------------------------------------------------------------------
温馨提示:
1)以上操作后,master节点机不能 ping 通外网,是因为DNS解析文件被改变了,执行下面命令即可:
[root@slave-1 ~] #echo "nameserver 8.8.8.8" >> /etc/resolv.conf
[root@slave-1 ~] # ping www.baidu.com
PING www.a.shifen.com (14.215.177.38) 56(84) bytes of data.
64 bytes from 14.215.177.38: icmp_seq=1 ttl=53  time =38.3 ms
.......
       
2)如果mesos-slave启动失败,可以如下检查:
[root@slave-1 ~] #  journalctl -f -u  mesos-slave        #journalctl -f -u  mesos-master可以检查master端的
   
mesos-slave启动失败(比如主机名改变导致),可以删除源数据,卸载干净,然后重新安装部署
# yum remove mesos
# rm -rf /etc/mesos*
# rm -rf /var/lib/mesos*      #源数据目录是/var/lib/mesos/meta
# yum install mesos

4)访问web管理页面
访问mesos的管理页面,即访问http://master_ip:5050 
注意:master_ip是这3个master中的任意一个就行,经过zookeeper选主,会自动跳到了leader的页面,如图mesos的leader为182.48.115.236(即master3被选为leader master)

在Frameworks中已经能够识别marathon,此时marathon的leader为182.48.115.236.
注意:mesos和marathon都是有zookeeper来选举leader,但是选主过程彼此独立,就是mesos的leader和marathon的leader可以不一样。如图这里二者通过zookeeper选出的leader是同一台机器(即都是master3:182.48.115.236)

点击"Agents",发现已经能够识别出三个slave。
注意:这里访问mesos显示的是"Agents"选项,老版本的mesos显示的是”Salve“选项

点击上面3个slave中的任意一个,也能看出它的master是182.48.115.236

访问marathon的管理页面,http://master_ip:8080
这里的master_ip就是在上面访问mesos页面Frameworks中识别出的marathon,即http://182.48.115.236:8080
或者直接点击mesos访问页面Frameworks中识别出的marathon也可以。

或者点击下图标红的marathon(即zookeeper选出的主marathon),可以出现marathon的管理界面

5)通过Mesos调度,使用marathon来创建容器
比如创建一个nginx镜像的Docker容器,Marathon启动时会读取/etc/mesos/zk配置文件,Marathon通过Zookeeper来找到Mesos Master。
Marathon有自己的REST API,我们通过API的方式来创建一个Nginx的Docker容器。

首先创建一个json文件(这个要在master节点机器上创建,任意一台master节点机上都可以):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[root@master-1 ~] # vim nginx.json         #nginx的docker镜像要提前创建或下载
{
 
"id" : "nginx" ,
 
"cpus" :0.2,
 
"mem" :32.0,
 
"instances" : 1,
 
"constraints" : [[ "hostname" "UNIQUE" , "" ]],
 
"container" : {
 
"type" : "DOCKER" ,
 
"docker" : {
 
"image" "docker.io/nginx" ,                                               #这个nginx镜像是在slave节点机上通过"docker images"查看到的,镜像名不能写错
 
"network" "BRIDGE" ,
 
"portMappings" : [
 
{ "containerPort" : 80,  "hostPort" : 0, "servicePort" : 0,  "protocol" "tcp"   }
 
]
 
   }
 
     }
 
       }

接着使用curl的方式调用。注意上面的nginx.json文件是在/root路径下的(注意下面命令中json文件路径)。

1
[root@master-1 ~] # curl -X POST http://182.48.115.233:8080/v2/apps -d @/root/nginx.json -H "Content-type: application/json"

登陆marathon界面查看是在哪一台slave机器上创建的docker容器实例(这个是随机的),点击"running"。(如果容器创建失败,即非"running"状态,可以尝试重启slave节点的docker服务)

如上截图中可知,这个nginx容器是在slave3节点机(182.48.115.239)上创建的(注意:如果slave3节点机宕机或docker服务重启,那么这个nginx容器就会自动漂移到其他的slave节点机上;另外,通过上面方式创建好的容器,在单个slave节点机上删除后,容器也会自动转移到其他slave节点机器上,这样就实现了在slave节点出现故障时容器自动转移的高可用功能)可以登陆slave3机器查看所创建的容器,如下可知:访问Docker随机启动的端口是31782

1
2
3
[root@slave-3 ~] # docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                   NAMES
b35fff88051e        docker.io /nginx       "nginx -g 'daemon off"     14 minutes ago      Up 14 minutes       0.0.0.0:31782->80 /tcp     mesos-6bc17bcc-433c-425e-b85e-232ffa42fe4f-S5.f1a121be-2e79-4856-bd5c-576db3497fba

访问所创建的nginx容器。(marathon ui界面里创建的docker容器映射到宿主机的访问端口默认都是随机分配的(BRIDGE模式))、可以自己制作应用的docker镜像,或者自定义构建容器并提交为新镜像(自己设定应用容器结构),然后根据自己制作的镜像在Marathon上创建应用。

接着访问mesos页面,可以看到"Active Tasks"有刚才创建的nginx任务了。(注意:只有当mesos访问界面"Active Tasks"里有容器创建任务时,才说明此容器真正创建成功了)

删除marathon创建的docker实例。如下图,点击"Destory"即可删除。

然后登陆slave3机器,发现在服务器上,这个容器只是被关闭了(docker ps -a),可以选择删除。如果再次在机器上启动这个nginx容器,那么在marathon上是不会显示的。注意:在节点机器上手动创建的docker容器,这些容器信息是不会在marathon和mesos里展示的。

1
2
3
4
5
6
7
[root@slave-3 ~] # docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
[root@slave-3 ~] # docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                          PORTS               NAMES
b35fff88051e        docker.io /nginx       "nginx -g 'daemon off"     30 minutes ago      Exited (0) About a minute ago                       mesos-6bc17bcc-433c-425e-b85e-232ffa42fe4f-S5.f1a121be-2e79-4856-bd5c-576db3497fba
[root@slave-3 ~] # docker rm b35fff88051e
b35fff88051e

如上在marathon界面里"Destory"删除对应的application后,在mesos界面的"Active Tasks"里的对应任务也会删除

另外要注意:在marathon界面里通过调用mesos创建docker容器,只能创建应用容器(Application),如nginx、tomcat、mysql、redis等,需要映射端口,这里是映射的是宿主机的随机端口不能创建基本centos,ubuntu的系统容器!

----------------------------------------------------------------------------------------------------------------------------------------
可以直接在marathon界面里手动创建docker应用容器:

首先点击marathon界面里右上角的"Create Application"

然后填写创建容器的配置信息,如下图,可以点击"New Application"创建页面右上角的"JSON Mode"模式,将上面创建nginx容器的json文件复制到这里

这样,就可以直接创建一个docker应用容器了。(复制写好的json文件到这里后,可以再次关闭"JSON Mode"模式,然后对比下所选用的配置)
下面我关闭"JSON mode"模式,手动选择配置信息去创建tomcat容器:

Marathon还可以对App应用实现手动扩缩的功能,选择"Scale Application"进行快速扩容。如下图,对上面已创建的tomcat应用容器进行扩展到2个Task(注意:这里有3个slave节点,那么扩展的Task实例最好是2个,3/2=1.5,即2个Instances;如果扩展多个Task,会发现多余的创建失败,这时候可以点击"Configuration"修改,修改成2个)

------------------------------------------------------------------------------------------------------

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
当然也可以手动编写json文件进行创建
[root@master-1 ~] # cat tomcat.json
{
  
"id" : "tomcat" ,
  
"cpus" :1,
  
"mem" :128,
  
"instances" : 1,
  
"constraints" : [[ "hostname" "UNIQUE" , "" ]],
  
"container" : {
  
"type" : "DOCKER" ,
  
"docker" : {
  
"image" "docker.io/tomcat" ,                                            
  
"network" "BRIDGE" ,
  
"portMappings" : [
  
{ "containerPort" : 8080,  "hostPort" : 0, "servicePort" : 0,  "protocol" "tcp"   }
  
]
  
   }
  
     }
  
       }
 
[root@master-1 ~] # curl -X POST http://182.48.115.233:8080/v2/apps -d @/root/tomcat.json -H "Content-type: application/json"
 
 
也可以将上面的tomcat.json文件内容直接复制到marathon创建应用容器的 "JSON Mode" 模式里,然后直接点击创建

查看容器创建的日志,可以在marathon界面里下载,也可以到mesos页面里查看或下载。如下图:

 点击下面的日志"stderr"和"stdout"就会下载到本地。

也可以到mesos页面查看或下载。点击下面mesos页面对应容器任务后面的"Sandbox"

---------------------------------------------------------------------------------------------------------------------------------------
具体配置可以参考marathon官方文档https://mesosphere.github.io/marathon/docs/persistent-volumes.html
里面有关于json文件的配置

--------------------------------------------------marathon创建应用使用volumes---------------------------------------------------
在marathon界面里创建应用,可以使用volumes,即映射容器目录到宿主机上,JSON文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
   "id" "nginx" ,
   "cmd" : null,
   "cpus" : 1,
   "mem" : 128,
   "disk" : 0,
   "instances" : 1,
   "container" : {
     "docker" : {
       "image" "docker.io/nginx" ,
       "network" "BRIDGE" ,
       "portMappings" : [
         {
           "containerPort" : 80,
           "protocol" "tcp" ,
           "name" : null,
           "labels" : null
         }
       ]
     },
     "type" "DOCKER" ,
     "volumes" : [
       {
         "containerPath" "/usr/share/nginx/html" ,
         "hostPath" "/opt/web/www" ,
         "mode" "RW"
       }
     ]
   }
}

在marathon界面里创建nginx容器应用时,将上面的JSON文件复制到"JSON Mode"模式下,然后创建即可!非"JSON Mode"模式下手动填写,Volumes选项填写如下

点击"Configuration",就可以看到容器创建的配置信息,并可以"Edit"修改

注意事项:

1
2
3
4
5
6
7
8
9
1)映射到宿主机的目录 /opt/web/www 要在每个slave节点机器上都要创建,并且里面的文件要在每个slave节点机上都有,因为容器重启后会在各个slave节点之间随机漂移。
 
2)上面映射的是nginx容器应用的站点目录,默认创建后, /usr/share/nginx/html 是空的( /opt/web/www 目录也是空的),所以容器默认访问会出现403报错。
    只需在slave节点的 /opt/web/www 目录下编写html文件(如index.html)即可访问。
 
3)marathon里创建好的容器应用,你可以在对应的slave机器上登陆容器内修改,但是这个容器应用一旦在marathon界面里restart,那么你之前的修改就没有了。
    因为重启应用,就是再次使用初始镜像进行构建了。
 
4)可以自己制作应用镜像,在镜像里设定好应用的配置文件;或者将自己创建的容器提交为新的镜像。然后在marathon界面里根据自己定义的镜像创建应用。

---------------------------------------------------marathon创建应用指定访问端口-----------------------------------------------

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
默认情况下,marathon创建的应用访问端口是随机分配的,因为hostPort默认配置的是0,具体看下面说明:
 
marathon创建应用后,会涉及到三个端口:containerPort:、hostPort、servicePort,其中:
1)containerPort:用来指定容器内部的一个端口。当使用BRIDGE或USER网络模式连接Docker容器时,必须将这个属性设置为port mapping的一部分。
 
2)hostPort:用来指定绑定到主机上的一个端口。当使用BRIDGE或USER网络模式,你可以指定一个port mapping将一个主机端口映射到容器端口。在HOST网络模式下,默认的请求端口就是主机的端口。
请注意,主机端口只可以通过环境变量提供给一个任务。
 
3)servicePort:当您在Marathon上(不管是通过REST API或界面)创建一个新的应用程序,你可以指定一个或多个服务端口给它。
可以指定所有有效的端口号为服务端口,也可以用0表示Marathon应该自动分配的可用服务端口给应用程序使用。如果你选择自己的服务端口,你必须自己确保,这个端口在所有应用程序中是唯一的。
 
portMapping:在Docker BRIDGE模式下,在容器外部可被访问到的端口都需要做端口映射。端口映射是一个包含host port, container port, service port和协议的元组。可以为Marathon应用指定多个端口映射; 未指定hostPort,则其默认值为0(意味着Marathon将随机分配一个)。在Docker USER模式下,hostPort的语义为稍有点变化:USER模式不需要指定hostPort,如果未指定Marathon不会自动分配一个随机的。这允许在USER网络模式下部署容器,包括containerPort和发现信息,但不暴露主机网络上的这些端口(意味着将不消耗主机端口资源)。
 
marathon创建应用的网络模式介绍:
1)BRIDGE网络模式:指定Docker应用程序使用BRIDGE网络模式。在这种模式下,容器端口(容器内部的端口)被映射到主机端口(主机上的端口)。在这种模式下,应用程序被绑定到容器内的指定端口,容器的端口被绑定到主机上的指定端口。
 
2)USER网络模式:指定Docker应用程序使用USER网络模式。在这种模式下,容器端口(容器内部的端口)被映射到主机端口(主机上的端口)。在这种模式下,应用程序被绑定到容器内的指定端口,容器的端口被绑定到主机上的指定端口。在与“用户自定义”Docker网络集成时,USER网络模式将会非常有用。在Mesos世界,这种网络通常是通过使用与Mesos CNI网络隔离的 CNI 插件访问。
 
3)HOST网络模式:该种模式在Marathon应用为非容器化而其它应用为容器化的情况下使用。在这种模式下,应用程序直接绑定到主机上的一个或多个端口。

如下JSON文件内容,注意一下:如果hostport端口指定了,那么serviceport端口也要指定(最好使用大端口),否则会导致应用容器创建失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
{
  
"id" : "nginx" ,
  
"cpus" :0.2,
  
"mem" :32.0,
  
"instances" : 1,
  
"constraints" : [[ "hostname" "UNIQUE" , "" ]],
  
"container" : {
  
"type" : "DOCKER" ,
  
"docker" : {
  
"image" "docker.io/nginx" ,                                            
  
"network" "BRIDGE" ,
  
"portMappings" : [
  
{ "containerPort" : 80,  "hostPort" : 31030, "servicePort" : 33180,  "protocol" "tcp"   }
  
]
  
   }
  
     }
  
       }

在marathon界面里创建应用,将上面的JSON文件内容复制到"JSON Mode"模式下。构建成功后,就会发现该应用容器的访问端口就是上面自己定义的31030端口了(如下)

---------------------------------------------------其他-----------------------------------------------------------------------------

在marathon中,应用是一个完整的概念。每个应用是一个典型的长运行的服务,这个服务有很多实例,并且是运行在多个slave节点机上。下面通过一个小示例说明下:

一个内嵌的shell脚步
如下通过内嵌的shell脚步,编写一个简单的app,即:
打印Hello world到slave节点的/mnt/test文件中,然后sleep 5秒,周而复始。可以使用下面的应用定义文件(json格式)来描述应用(注意:cmd是要执行的命令。它的值会以/bin/sh -c ${cmd}的方式执行。)

1
2
3
4
5
6
7
{
     "id" "basic-0" ,
     "cmd" "while [ true ] ; do echo 'Hello hello' >> /mnt/test ; sleep 5 ; done" ,
     "cpus" : 0.1,
     "mem" : 10.0,
     "instances" : 1
}

在marathon界面里添加应用,采用"JSON Mode "模式,如下:

不采用"JSON Mode"模式,即将上面的json文件内容粘贴进去后,去掉右上方的"JSON Mode"模式,也就是只配置"General"选向,其他选项都不配置。注意:marathon里的应用是一个长运行服务,所以shell脚本里要配置长运行动作。

 然后到182.48.115.239这台slave节点机上检查,发现每隔5秒钟,就会输出"hello world"到/mnt/test文件中。如果这台节点机出现故障,就会输出到其他节点机上。

1
2
3
4
5
6
7
8
9
[root@slave-3 mnt] # cat /mnt/test
Hello hello
Hello hello
Hello hello
Hello hello
Hello hello
Hello hello
Hello hello
.......
***************当你发现自己的才华撑不起野心时,就请安静下来学习吧***************

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值