murano是如何集成docker?提供了怎样的接口以辅助用户快捷的编写package呢?这个得利于murano自身的架构,murano的package提供了自定义lib的功能,即根据自己的需要,根据murano package的定义规则,自己拓展一个即可。关于murano package的解析请移步murano实践之package分析
DockerInterfacesLibrary 中提供了对于一个已正确安装docker环境的虚拟机操作docker的接口,首先看下package中的manifest.yaml
见:murano-apps/manifest.yaml
Format: 1.0
Type: Library
FullName: io.murano.apps.docker.Interfaces
Name: Docker Interface Library
Description: |
The library provides all necessary interface for Docker
and Kubernetes applications
Author: 'Mirantis, Inc'
Tags: [Docker, Kubernetes]
Classes:
io.murano.apps.docker.DockerApplication: DockerApplication.yaml
io.murano.apps.docker.DockerContainer: DockerContainer.yaml
io.murano.apps.docker.DockerContainerHost: DockerContainerHost.yaml
io.murano.apps.docker.DockerHelpers: DockerHelpers.yaml
io.murano.apps.docker.DockerHostVolume: DockerHostVolume.yaml
io.murano.apps.docker.DockerTempVolume: DockerTempVolume.yaml
io.murano.apps.docker.DockerVolume: DockerVolume.yaml
io.murano.apps.docker.ApplicationPort: ApplicationPort.yaml
重点在Classes中,目前这里面主要封装了
这8个docker相关的接口,我们这里以DockerApplication为例做一个分析,在Classes文件包中找到DockerApplication.yaml
提供了deploy
、destroy
、getConnectionTo
等方法。以deploy
为例,显示了虚拟机使用docker安装application的步骤
deploy:
Body:
- $.host.deploy()
- $container: $.getContainer()
- $repr: $._getContainerRepresentation($container)
- If: $.getAttr(container, null) != $repr
Then:
- $.onInstallationStart()
- Try:
- $.applicationEndpoints: $.host.hostContainer($container)
- $.setAttr(container, $repr)
Catch:
- As: e
Do:
- $formatString: 'Error: {0}'
- $._environment.reporter.report_error($, $formatString.format($e.message))
- Rethrow:
Else:
- $.onInstallationFinish()
首先$.host.deploy()
,这个host
为DockerStandaloneHost
详见DockerStandaloneHost,具有方法deploy
,此处调用的也是deploy方法DockerStandaloneHost
的deploy
方法如下:
deploy:
Body:
- If: not $.getAttr(deployed, false)
Then:
- $._environment.reporter.report($this, 'Create VM for Docker Server')
- $.instance.deploy()
- $resources: new(sys:Resources)
- $template: $resources.yaml('StartDocker.template')
- $.instance.agent.call($template, $resources)
- If: $.dockerRegistry != null and $.dockerRegistry != ''
Then:
- $._environment.reporter.report($this, 'Configuring Docker registry')
- $template: $resources.yaml('SetupDockerRegistry.template').bind(dict(
dockerRegistry => $.dockerRegistry
))
- $.instance.agent.call($template, $resources)
- $._environment.reporter.report($this, 'Docker Server is up and running')
- $.setAttr(deployed, true)
从
- $.instance.deploy()
- $resources: new(sys:Resources)
- $template: $resources.yaml('StartDocker.template')
- $.instance.agent.call($template, $resources)
这四步看出,需要拿到StartDocker.template
交给LinuxMuranoInstance
,StartDocker.template
中的内容如下:
startDocker.template
FormatVersion: 2.0.0
Version: 1.0.0
Name: Start docker service
Body: |
startDocker()
Scripts:
startDocker:
Type: Application
Version: 1.0.0
EntryPoint: startDocker.sh
Options:
captureStdout: false
captureStderr: false
verifyExitcode: false
这个template的EntryPoint是startDocker.sh
,内容:
#!/bin/bash
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
service docker start
一句话,启动docker服务,所以$.host.deploy()
其实就是用了启动虚拟机上的docker服务;根据上面的方法,
- $container: $.getContainer()
- $repr: $._getContainerRepresentation($container)
这两步获取container的基础信息
再看- $.host.hostContainer(container)
对应的方法如下:
hostContainer:
Arguments:
- container:
Contract: $.class(DockerContainer).notNull()
Body:
- $.deploy()
- $.deleteContainer($container.name)
- $portBindings: {}
- $newEndpoints: []
- $._pullImage(image => $container.image)
- For: applicationPort
In: $container.ports
Do:
- If: $applicationPort.scope != host
Then:
- $hostPort: $._acquirePort($applicationPort, $container.name)
- $containerPort: $._getPortSpec($applicationPort)
- $portBindings[$hostPort]: $containerPort
- If: $applicationPort.scope = public
Then:
- $rule:
- ToPort: $hostPort
FromPort: $hostPort
IpProtocol: toLower($applicationPort.protocol)
External: true
- $._environment.securityGroupManager.addGroupIngress($rule)
- $record:
port: $hostPort
address: $.instance.ipAddresses[0]
scope: cloud
containerPort: $applicationPort.port
portScope: $applicationPort.scope
protocol: $applicationPort.protocol
applicationName: $container.name
- $newEndpoints: $newEndpoints + list($record)
- If: $applicationPort.scope = public and $.instance.floatingIpAddress != null
Then:
- $record.address: $.instance.floatingIpAddress
- $record.scope: public
- $newEndpoints: $newEndpoints + list($record)
- $volumeMap: {}
- For: path
In: $container.volumes
Do:
- $volume: $container.volumes.get($path)
- If: $volume.getType() = HostDir
Then:
- $hostDir: $volume.getParameters()
- $volumeMap[$hostDir]: $path
- $._environment.reporter.report($this, 'Adding Docker Application')
- $resources: new(sys:Resources)
- $template: $resources.yaml('RunContainer.template').bind(dict(
appName => $container.name,
image => $container.image,
env => $container.env,
portMap => $portBindings,
volumeMap => $volumeMap,
commands => $container.commands
))
- $._removeApplicationEndpoints($container.name)
- $privateIp: $.instance.agent.call($template, $resources)
- $record:
port: $applicationPort.port
address: $privateIp
scope: host
containerPort: $applicationPort.port
portScope: $applicationPort.scope
protocol: $applicationPort.protocol
applicationName: $container.name
- $newEndpoints: $newEndpoints + list($record)
- $._environment.stack.push()
- If: not $container.name in $.containers
Then:
- $.containers: $.containers + list($container.name)
- $.applicationEndpoints: $.applicationEndpoints + $newEndpoints
- Return: $.getEndpoints($container.name)
前面几步,判断该container是否存在,如果存在,删除- $._pullImage(image => $container.image)
这一步获取$container.image。中间到- $volumeMap: {}
之前的这一段就是为了组装portBindings和newEndpoints,接着组装volumeMap,完事儿之后呢获取RunContainer.template
。- $privateIp: $.instance.agent.call($template, $resources)
到这一步了自然就是告诉虚拟机,你该调用RunContainer.template
工作啦,RunContainer.template
中其实归根到底就是一段拼装好的执行docker run
命令的脚本。
从上面的分析可以看出来,DockerInterfacesLibrary
事实上是依赖DockerStandaloneHost
与 LinuxMuranoInstance
的这两个对象事实需要创建虚拟机的镜像中包含了murano-agent服务和docker服务的,DockerStandaloneHost
中封装了StandaloneHost对docker的一些操作,如docker run
、service docker start
等,LinuxMuranoInstance
中封装的是获取组装好的模版执行等功能。
Namespaces:
=: io.murano.apps.docker
std: io.murano
Name: DockerTomcat
Extends: DockerApplication
Properties:
name:
Contract: $.string().notNull()
publish:
Contract: $.bool().notNull()
Default: true
password:
Contract: $.string().notNull()
Methods:
initialize:
Body:
- $._environment: $.find(std:Environment).require()
- $._scope: switch($.publish, $ => public, not $ => internal)
getContainer:
Body:
Return:
name: $.name
image: 'tutum/tomcat'
env:
TOMCAT_PASS: $.password
ports:
- port: 8080
scope: $._scope
onInstallationStart:
Body:
- $._environment.reporter.report($this, 'Installing Tomcat')
onInstallationFinish:
Body:
- If: $.publish
Then:
- $endpoints: $.applicationEndpoints.where($.scope = $this._scope).
select(format('http://{0}:{1}', $.address, $.port))
- $._environment.reporter.report($this, 'Tomcat {0} is available at {1}'.format($.name, join(', ', $endpoints)))
Else:
- $._environment.reporter.report($this, 'Tomcat {0} has deployed but is not accessible from outside'.format($.name))
Extends: DockerApplication
直接表明,继承了DockerApplication,也就是上面分析的DockerApplication实例,此处只需要实现getContainer
和 onInstallationFinish
方法即可,deploy
会自动从DockerApplication继承,deploy
方法完成tomcat的具体安装