Tuesday, April 7, 2015

Murano: How to prepare an image for Kubernetes

Before I am going into details about an image preparation let me explain what is Kubernetes.  The long definition of Kubernetes can be found here: https://github.com/GoogleCloudPlatform/kubernetes/blob/master/DESIGN.md. I will just quote the first lines which defines in high level what Kubernetes is.
"Kubernetes is a system for managing containerized applications across multiple hosts, providing basic mechanisms for deployment, maintenance, and scaling of applications. Its APIs are intended to serve as the foundation for an open ecosystem of tools, automation systems, and higher-level API layers."[1]

Murano does support Kubernetes as a Murano application. It is possible to quickly roll out a Kubernetes cluster on top of OpenStack, making sure that it is properly configured and integrated to OpenStack infrastructure. More detailed information can be found here: https://www.mirantis.com/blog/integrating-openstack-and-kubernetes-with-murano/.

Lets return to the topic and discuss an image preparation for Kubernetes. Image preparation is automated by using OpenStack Disk Image Builder tool. There is a DIB element provided by Murano team to build Kubernetes image on Ubuntu: https://github.com/stackforge/murano/tree/master/contrib/elements/kubernetes 

File /install.d/57-kubernetes contains all necessary steps to pre-install all necessary component for Kubernetes cluster:
  • ETCD - a distributed /etc service used in CoreOS and required by Kubernetes to store its configuration
  • Go - Go language compiler to build Kubernetes and ETCD from sources
  • Kubernetes - the latest version of Kubernetes source code
  • Flannel - a GRE based overlay network service to connect Docker containers in Kubernetes VMs
When Murano creates a new Kubernetes cluster it executes several shell scripts on each VM. The shell scripts to configure Kubernetes can be found here: https://github.com/stackforge/murano-apps/tree/master/kubernetes/io.murano.apps.docker.kubernetes.KubernetesCluster/Resources/scripts

Workflow with the logic and step-by-step installation flow can be found in Murano workflow definition for Kubernetes: https://github.com/stackforge/murano-apps/blob/master/kubernetes/io.murano.apps.docker.kubernetes.KubernetesCluster/Classes/KubernetesCluster.yaml


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

[1] https://github.com/GoogleCloudPlatform/kubernetes/blob/master/DESIGN.md

Monday, April 6, 2015

Murano: How to find network ids for automatically created networks

Murano deploys the specific network topology for a Murano environment by default. Murano creates a new private network then adds a new subnet and finally attaches this new network to the router.

If someone is writing an application, which wants to add some  Heat resources into the stack it might be a requirement to pass some network ids to the resource.

As networking is usually hidden from an user in the depth of Core library and it is not so obvious how to get these ids.

Here is a way I do this in my apps:

- $net: $.environment.defaultNetworks.environment
- If: $net != null
  Then:
     - $.environment.reporter.report($this, '[VMCluster]Using existing external network')
- $netRef: $net._getNetworkName() 
- $subnetRef: $net._getSubnetName()
- $extNetId: $net._getExternalNetId()

[Note: in Icehouse version there is no underscore in the name of these functions]

defaultNetwroks property of Environment contains a networking information for created networks.  Actual implementation of networking could be found here (https://github.com/stackforge/murano/blob/master/meta/io.murano/Classes/resources/NeutronNetwork.yaml) [Note that it should be moved soon to guthub.com/openstack/murano]


In the example above I got all necassary information for my networks:
  • $netRef: will contain a Heat resource name for the network so that in embeded Heat template I can just use {get_resource: $netRef }
  • $subnetRef: will contain a Heat resource name for subnet resource. Could be used as a reference in Embeded Heat template as {get_resource: $subnetRef }
  • $extNetId: will contain an id of external network attached to a router. It is a network where application will allocate floating IPs. You might need this network id for LBaaS Pool resource.

On Juno version of OpenStack and Murano it will be necessary to explicitly cast network to NeutronNetwork like in the example below:

- $netRef: $net.cast('io.murano.resources.NeutronNetwork')._getNetworkName()
- $subnetRef: $net.cast('io.murano.resources.NeutronNetwork')._getSubnetName()
- $extNetId: $net.cast('io.murano.resources.NeutronNetwork')._getExternalNetId()

That is it about networks ids.
Enjoy!

Friday, April 3, 2015

Murano: Configuring Security Groups for an application

Close every door to me
Hide all the world from me
Bar all the windows
And shut out the light


(Andrew Lloyd-Webber)


Security groups are the key component of application security on a shared cloud environment. Each Murano application definition most probably contains security group rules to open or close ports which are exposed by an application.

There are two ways to configure security groups in a workflows. The first way is to use Murano security manager code which wraps all internals of Heat template handling and expose simple functions.

Here is an example of how it could be done:

- $securityGroupIngress:
    - ToPort: 65535       
      FromPort: 1       
      IpProtocol: tcp
      External: false
    - ToPort: 80 
      FromPort: 80 
      IpProtocol: tcp
      External: true
    - ToPort: 443 
      FromPort: 443       
      IpProtocol: tcp
      External: true
- $.environment.securityGroupManager.addGroupIngress($securityGroupIngress)

In this example the application will open all tcp ports (1-65535) between VMs who are participating in the same group (Note this line)

External: false


So these ports will be open only for internal communication between VMs.
In contrary, ports 80 and 443 are opened for external access from the outside (internet if you use floating IPs).

SecurityManager uses Heat updates to update security rules inside stack but it does not initiates these updates. Usually this update happens when you have an instance creation as a next step. If you are openning ports after your VMs are provisioned you can initiate stack update by adding:

- $.environment.stack.push()

Another way to do security groups is to embed Heat resources directly int he workflow and use stack updates to push security group resources into the stack.


And Heat resources are documented here: http://docs.openstack.org/developer/heat/template_guide/openstack.html#OS::Neutron::SecurityGroup


 
Enjoy!

Friday, March 27, 2015

Murano: Sending files to a VM

Murano can manage your software on top of VM. If there is no underlying software orchestration under Murano, it is possible to use Murano itself for delivering files to the VM.
It is possible to do as a part of Execution plan template which usually reside in Resources folder of Murano application package. Here is an example of execution plan.

FormatVersion: 2.0.0
Version: 1.0.0
Name: Install Kubernetes Minion

Parameters:
  master: $master


Body: |
  return install(args.master).stdout

Scripts:
  install:
    Type: Application
    Version: 1.0.0
    EntryPoint: install.sh
    Files: 
      - minion.conf
    Options:
      captureStdout: true
      captureStderr: true


In the Scripts section there is a node Files where it is possible to define a list of files which should be delivered to the VM and placed in the same directory where entry point script will be placed. In the package these files should be stored in "Resources/scripts" folder.
Murano uses MQ to deliver execution plans to the VM and binary files should be base64 encoded to be successfully delivered. It is possible to enforce base64 encoding of the files by putting triangle brackets around a file name.

Files: 
  - minion.conf
  - <myjava.jar>

All files will be accessible from the same directory where entry point shell script is placed. You can work with these files from shell script like that:
cp -f minion.conf /etc/salt/minion

Happy weekends!



Murano: Filtering complex structures

Where is this stuff?

Sometime you need to deal with complex structures which are passed as a parameter to your Murano workflow.  Lets take the following example:
There is a Docker container based application which exposes some ports to the host and some ports are used for internal linking between containers. Ports are stored in a list of complex objects like:
ports:
 - port: 3306   scope: host
   applicationPort: 3306
 - port: 22   scope: local
   applicationPort: 22

In application Murano workflow I want to select all ports which should be exposed to a host system and add proper parameters to docker run command line. It is possible to use For loop to iterate over $.ports list and check each component individually like the following:

- For: port
  In: $.ports
  Do:    - If: $port.scope = 'host'
      Then:
        - $.doSomethingWithIt($port)


But there is more elegant solution with using list.where(condition) construction:
- $.ports.where($.scope = 'host').select($this.doSomethingWithIt($))

Please remember that the meaning of "$" sign is overloaded in such syntax. I described this in the previous post in details: http://muranohints.blogspot.com/2015/03/murano-couple-words-about-take-and-skip.html

You can also use this function in For loops for filtering lists on the fly:
- For: port
    In: $.ports.where($.scope in list(public, host))
    Do:      - $.doSomethingWithIt($port)


Enjoy!

Thursday, March 26, 2015

Murano: Sequential or parallel execution

Select, or pselect, that is the question -


Yeah, sometime application developer has almost Hamlet level questions. Murano engine can execute workflows in parallel mode which is actually a default behavior.  If there are several application configured in a Murano environment all of them will deploy them-self in parallel if there is no special dependency handling in the workflow.

Another example is a cluster creation. When cluster has a list of instances which are all identical it makes sense to provision them simultaneously. Sometime there is a use case when you need to process instances one by one, for example in order to register slave on the master node.

There are two functions in Murano which handles both use cases.
List.select($.deploy()) will generate a sequence of executions of deploy method of each element in the list. It is equivalent to the for loop:
- For: inst
  In: $.instances.take($.currentCount)
  Do:    - $inst.deploy()

In contrary, List.pselect($.deploy()) will initiate a parallel execution of deploy() method on each element of the list.

- $.instances.take($.currentCount).pselect($.deploy())

Please, note that "$" has overloaded usage here. If you use select or pselect then a "$" will represent the element of a list.  If you want to call a method of the current class which will take list element as a parameter you will have to use full form of $this rather then $ like in example below:

- $.instances.take($.currentCount).pselect($this.configureKubeMinion($))
 In this example current class ($this) method configureKubernetesMinion() will be called for each element of the list instances with the element passed as an argument of the method configureKubernetesMinion($)

Enjoy !

Wednesday, March 25, 2015

Murano: Couple words about take and skip functions

Almighty little functions

 There is a list type in Murano. It behaves as classical array so that it is possible to have an access to the each element of the list via index. In contrary to Python, there are no slices via indexes in Murano and sometime you really miss them.

Fortunately in Juno release new functions were introduced: list.take(N) and list.skip(N). These little functions make your life easier when you need to work with lists in Murano. Their definition can be found here: https://github.com/stackforge/murano/blob/master/murano/engine/system/yaql_functions.py#L305-L312

As you see, this is just a wrapper around itertool.islice() function which does all the work for you.

These functions take and skip does exactly what you expect from them. List.take(N) returns a list which contains first N elements of the original list. List.skip(N) returns a list of elements starting from N-th element of the original list till the end. And, of course, it is possible to combine these functions to have something like that list.skip(N).take(M) which returns a list which contains elements [N...N+M] of the original list.

Currently our clustering code uses these functions. Lets imagine that you have a cluster with maximum number of instances equal 10. It means that there is a list "instances" which contains 10 elements. Sometime you need to create only few of these cluster instances so that initially after the first deployment you will have only 2 instances running. That is exactly work for take function. instances.take($.currentCount).pselect($.deploy) will initiate deployment only for initial number $.currentCount of instances.

Happy coding!