上期,我们介绍了DC/OS应用市场里面的开源项目universe,并且介绍了universe的应用文件内容和创建过程,当用户创建了属于自己的universe repository后,如何使用这些repository呢?
这期,我们从开始介绍DC/OS的cosmos来逐层剖析应用仓库的管理。在DC/OS系统中,应用仓库管理的后端是通过cosmos提供的服务完成的。首先,我们先简单介绍一下cosmos。
Cosmos是DCOS的一个开源项目(github地址在https://github.com/dcos/cosmos),t通过对外提供REST API,完成了对universe仓库的管理以及应用服务的安装部署,在DC/OS系统中,cosmos是通过systemd管理起来的系统服务,用户可以通过系统服务管理命令systemctl来控制cosmos的启停。
下面是Cosmos的系统启动文件,DCOS的系统安装完成后,默认放在/etc/systemd/system/dcos-cosmos.service下面:
[Unit]
Description=Package Service: DC/OS Packaging API
After=dcos-mesos-master.service
After=dcos-gen-resolvconf.service
[Service]
Restart=always
StartLimitInterval=0
RestartSec=15
EnvironmentFile=/opt/mesosphere/environment
EnvironmentFile=-/var/lib/dcos/environment.proxy
ExecStartPre=/bin/ping -c1 ready.spartan
ExecStartPre=/opt/mesosphere/bin/exhibitor_wait.py
## CoreOS
ExecStartPre=-/usr/bin/mkdir -p /var/lib/cosmos
## Ubuntu
ExecStartPre=-/bin/mkdir -p /var/lib/cosmos
ExecStart=/opt/mesosphere/bin/java -Xmx2G -classpath /opt/mesosphere/packages/cosmos--25d98ad8c31c73550a40c8e1022c08f2e53976c4/lib/:/opt/mesosphere/packages/cosmos--25d98ad8c31c73550a40c8e1022c08f2e53976c4/usr/cosmos.jar com.simontuffs.onejar.Boot
Cosmos的核心代码是用scala实现的,通过cosmos在启动后对外提供了REST API, DCOS的UI通过调用Cosmos开放出来的REST API来完成相应的工作。
Cosmos通过twitter 开源出来的finagle,来完成创建了一个http server, 在服务启动过程中,分别注册了adminrouter, marathon,mesos以及zookeeper这四大家族的client,其中,其中adminrouter是dcos的开源项目之一(https://github.com/dcos/adminrouter)想要更细一些了解的同学可以参考github,其目的就是做了反向代理,能client通过一个url + port的方式访问到底层的各种服务;那marathon的client上期我们大概介绍过,是DC/OS的服务都是托管在Marathon上面,所以这里需要一个marathon的client来通过调用marathon相应的API来完成应用的创建;mesos上期也大概提了下,在DC/OS的应用市场里,除了一部分能跑在marathon上的应用外,还有一些应用是需要跑在mesos上来作为mesos的framework的,所以没有client不行啊,剩下的zookeeper就是用来存放用户在dcos ui上添加的universe的repository的。
下面看看启动参数:
Cosmos编译完是个jar包,启动的时候你的环境需要有java,目前后面可以配置的参数有:
好了,能指定的参数都在这里了,在写这个的时候,不小心更新了下code(其实好久都没有更新过了),看到社区又加了两个环境变量进来了,这里顺带介绍一下:
这两个环境变量是和zookeeper的authentication有关的,分别是ZOOKEEPER_USER和ZOOKEEPER_SECRET。当然,你export了cosmos肯定会认得,不信摘一点Cosmos的代码让你看看:
val boot = ar map { adminRouter =>
val zkUri = zookeeperUri()
logger.info("Using {} for the ZooKeeper connection", zkUri)
val marathonPackageRunner = new MarathonPackageRunner(adminRouter)
val zkClient = zookeeper.Clients.createAndInitialize(
zkUri, sys.env.get("ZOOKEEPER_USER").zip(sys.env.get("ZOOKEEPER_SECRET")).headOption
)
onExit {
zkClient.close()
}
好了,说了这么多,总算能启动服务了吧:
root@mesos8:~# java -Xmx2G -classpath /root/hchen/cosmos-server_2.11-0.1.6-SNAPSHOT-one-jar.jar com.simontuffs.onejar.Boot -com.mesosphere.cosmos.dcosUri=http://9.111.255.40 -com.mesosphere.cosmos.zookeeperUri=zk://127.0.0.1:2181/cosmos -com.mesosphere.cosmos.kubernetesUri=http://0.0.0.0:8888 -com.mesosphere.cosmos.dataDir=/var/lib/cosmos
我平常就这么启动的,当然,你可以通过systemctl dcos-cosmos start/stop来控制cosmos服务的启停。
好了,服务也起来了,下面就看看使用了。
在DC/OS的GUI上面,用户可以在system -> Overview -> Repositories页面中查询,添加及删除universe 的repositories.
我们先了解一下仓库的添加。
DCOS系统安装好后,默认会有一个mesosphere官方提供的default repository, cosmos服务在启动时,通过读取zookeeper里面的数据来决定是否需要添加default repository, 当然,刚安装好的环境里面zookeeper里面肯定没有东西,所以cosmos服务在第一次启动的时候,default repository就会被添加进去。
private[this] val DefaultRepos: List[PackageRepository] = DefaultRepositories().getOrElse(Nil)
override def read(): Future[List[PackageRepository]] = {
Stat.timeFuture(stats.stat("read")) {
readFromZooKeeper.flatMap {
case Some((_, bytes)) =>
Future(decodeData(bytes))
case None =>
create(DefaultRepos)
}
}
}
其中,default repository的文件是hard code的一个default-repositories.json文件,文件默认放在cosmos的lib目录下:
[root@hcmesos3 ~]#cat /opt/mesosphere/packages/cosmos--25d98ad8c31c73550a40c8e1022c08f2e53976c4/lib/default-repositories.json
[
{
"name": "Universe-1.7",
"uri": "https://universe.mesosphere.com/repo-1.7"
},
{
"name": "Universe",
"uri": "https://universe.mesosphere.com/repo"
}
]
当然,用户也可以添加自己的universe repository.
当用户进入页面开始添加一个universe的repositories的时候,universe会去调用CosmosPackageAction.js::addRepository来开始添加一个repository.
addRepository: function (name, uri, index) {
RequestUtil.json({
contentType: getContentType('repository.add', 'request', 'v1'),
headers: {Accept: getContentType('repository.add', 'response', 'v1')},
method: 'POST',
url: `${Config.rootUrl}${Config.cosmosAPIPrefix}/repository/add`,
data: JSON.stringify({name, uri, index}),
timeout: REQUEST_TIMEOUT,
success: function (response) {
AppDispatcher.handleServerAction({
type: REQUEST_COSMOS_REPOSITORY_ADD_SUCCESS,
data: response,
name,
uri
});
},
error: function (xhr) {
AppDispatcher.handleServerAction({
type: REQUEST_COSMOS_REPOSITORY_ADD_ERROR,
data: RequestUtil.getErrorFromXHR(xhr),
name,
uri
});
}
});
},
前端dcos UI的请求会以POST的方式发送给 cosmos受理repository/add的请求,并且等待cosmos的response. 根据universe请求的URL, Cosmos会调用PackageRepositoryAddHandler::sourceStorage.add来完成操作,如下所示:
private[cosmos] final class PackageRepositoryAddHandler(
sourcesStorage: PackageSourcesStorage
) extends EndpointHandler[PackageRepositoryAddRequest, PackageRepositoryAddResponse] {
override def apply(request: PackageRepositoryAddRequest)(implicit
session: RequestSession
): Future[PackageRepositoryAddResponse] = {
request.uri.scheme match {
case Some("http") | Some("https") =>
sourcesStorage.add(
request.index,
PackageRepository(request.name, request.uri)
) map { sources =>
PackageRepositoryAddResponse(sources)
}
case _ => throw UnsupportedRepositoryUri(request.uri)
}
}
其中sourcesStorage是个zooKeeperStorage的对象,添加的repositories通过ZooKeeperStorage对数据进行加密然后放到zookeeper上面(默认在zookeeper的/cosmos/package下面),下面是存储在zookeeper里面repository的信息。
WatchedEvent state:SyncConnected type:None path:null
[zk: localhost:2181(CONNECTED) 0] get /cosmos/package/repositories
{"metadata":{"Content-Type":"application/vnd.dcos.package.repository.repo-list+json;charset=utf-8;version=v1"},"data":"W3sibmFtZSI6ImhjaGVudGVzdCIsInVyaSI6Imh0dHA6Ly9tZXNvczEuZW5nLnBsYXRmb3JtbGFiLmlibS5jb20vYXBwX3N0b3JlL2N3Yy1hcHBzdG9yZS56aXAifV0="}
cZxid = 0x55
ctime = Thu Jun 02 04:25:49 EDT 2016
mZxid = 0x300
mtime = Sun Jul 10 23:01:31 EDT 2016
pZxid = 0x55
cversion = 0
dataVersion = 51
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 249
numChildren = 0
顺便提一下,从上面的代码我们不难发现,目前universe的repository支持http和https两种协议。
除了对repository name和URL进行加密并且写到zookeeper里面以外,还会获取repository所提供的压缩包并且解压放到/var/lib/cosmos下(也就是启动的时候指定的dataDir),里面就解压后的package index以及各种应用软件包的描述文件,后面介绍的应用包的信息展示,安装等等都会用到这个里面的文件。
创建完成后,cosmos会返回一个PackageRepositoryAddResponse给dcos UI, 当dcos UI发现repository add 成功的response,页面成功返回。
仓库添加成功后,dcos ui都会重新调用fetchRepositories的api来刷新当前的页面来返回最新的universe repository, 下面我们来就来看看这个api的调用:
当用户点击页面的时候,通过调用CosmosPackageAction.js::fetchRepositories去POST一个http request ,等待cosmos受理/repository/list的response, 代码如下:
fetchRepositories: function (type) {
RequestUtil.json({
contentType: getContentType('repository.list', 'request', 'v1'),
headers: {Accept: getContentType('repository.list', 'response', 'v1')},
method: 'POST',
url: `${Config.rootUrl}${Config.cosmosAPIPrefix}/repository/list`,
data: JSON.stringify({type}),
timeout: REQUEST_TIMEOUT,
success: function (response) {
AppDispatcher.handleServerAction({
type: REQUEST_COSMOS_REPOSITORIES_LIST_SUCCESS,
data: response.repositories
});
},
error: function (xhr) {
AppDispatcher.handleServerAction({
type: REQUEST_COSMOS_REPOSITORIES_LIST_ERROR,
data: RequestUtil.getErrorFromXHR(xhr)
});
}
});
},
cosmos收到universe发过来的request后,通过调用PackageRepositoryListHandler::sourcesStorage.read()来获取当前的repository列表:
private[cosmos] final class PackageRepositoryListHandler(
sourcesStorage: PackageSourcesStorage
) extends EndpointHandler[PackageRepositoryListRequest, PackageRepositoryListResponse] {
override def apply(req: PackageRepositoryListRequest)(implicit
session: RequestSession
): Future[PackageRepositoryListResponse] = {
sourcesStorage.read().map(PackageRepositoryListResponse(_))
}
}
返回的PackageRepositoryListResponse是一个序列化的packageRepository类型,每一个packageRepository里面都有一个repositrory名字以及和其所对应的URI.
Universe收到cosmos的response后,RepositoriesTables.js会把这些数据展示到GUI上面。
用户除了能查看当前的仓库,创建新的仓库以外,当然还能删除已有的仓库。
在DCOS的GUI 上面,每一个仓库列表后面,都有一个remove的操作, 点击remove按钮,就会弹出对话框来确认参数操作:
点击Remove Repository, 操作开始。
同样的,dcos ui会调用CosmosPackageAction.js::deleteRepository的api来删除当前选择的universe repository, 请求会以post的方式发送给cosmos等待受理/repository/delete的response.
deleteRepository: function (name, uri) {
RequestUtil.json({
contentType: getContentType('repository.delete', 'request', 'v1'),
headers: {Accept: getContentType('repository.delete', 'response', 'v1')},
method: 'POST',
url: `${Config.rootUrl}${Config.cosmosAPIPrefix}/repository/delete`,
data: JSON.stringify({name, uri}),
timeout: REQUEST_TIMEOUT,
success: function (response) {
AppDispatcher.handleServerAction({
type: REQUEST_COSMOS_REPOSITORY_DELETE_SUCCESS,
data: response,
name,
uri
});
},
error: function (xhr) {
AppDispatcher.handleServerAction({
type: REQUEST_COSMOS_REPOSITORY_DELETE_ERROR,
data: RequestUtil.getErrorFromXHR(xhr),
name,
uri
});
}
});
}
cosmos收到universe发过来的request后,通过调用PackageRepositoryDeleteHandler::sourcesStorage.delete()来删除这个universe repository:
private[cosmos] final class PackageRepositoryDeleteHandler(
sourcesStorage: PackageSourcesStorage
) extends EndpointHandler[PackageRepositoryDeleteRequest, PackageRepositoryDeleteResponse] {
import PackageRepositoryDeleteHandler._
override def apply(request: PackageRepositoryDeleteRequest)(implicit
session: RequestSession
): Future[PackageRepositoryDeleteResponse] = {
val nameOrUri = optionsToIor(request.name, request.uri).getOrElse(throw RepoNameOrUriMissing())
sourcesStorage.delete(nameOrUri).map { sources =>
PackageRepositoryDeleteResponse(sources)
}
}
删除完成后返回一个PackageRepository的List给dcos UI, 这个也就是最新的当前DC/OS系统中存在的universe repository了。
以上就是cosmos在DC/OS系统中对应用仓库的管理,包括仓库的查询,创建,删除这三部分,后期我们还会介绍软件包的应用部署等等。
作者简介:陈晖,供职于西安IBM, 主要从事云计算方面工作,涉及OpenStack、Mesos等开源项目,在资源管理 ,资源调度等方面有比较丰富的经验 。
责编:魏伟,投稿请联系[email protected],欢迎加入CSDN Container技术专家交流群,和大牛一起交流技术和经验,微信搜索“k15751091376”,由专人拉入。