Friday, September 9, 2011

From zero to DrawBridge via Ubuntu Server, Juju and CloudFoundry in less than 10 minutes

** This is an updated post reflecting the renaming of the project formerly known as Ensemble and now known as Juju **

As Dustin mentioned in his blog posts here and here, we've been working with VMWare on a CloudFoundry deployment using Ubuntu Server and Juju.

Today, I would like to build on those posts and talk a bit about:
  • deploying a CloudFoundry server environment using Ubuntu Server and Juju
  • show how Juju charm can make it easy to horizontally scale your deployment
  • deploy a sample application


The short version ( not really that short after all )

Dependencies
sudo apt-get install bzr wget grep sed gawk

Juju
sudo apt-add-repository ppa:juju/pkgs
sudo apt-get update
sudo apt-get install juju

Charms and supporting scripts
bzr branch lp:~canonical-sig/+junk/cloudfoundry-server
bzr branch lp:~canonical-sig/+junk/cloudfoundry-server-dea
bzr branch lp:~canonical-sig/+junk/cf-mysql
bzr branch lp:~canonical-sig/+junk/cf-mongodb
bzr branch lp:~canonical-sig/+junk/cf-redis
bzr branch lp:~kirkland/+junk/drawbridge
bzr branch lp:~negronjl/+junk/ubuntu-latest-image
chmod +x ./ubuntu-latest-image
juju
echo "    default-image-id: `ubuntu-latest-image oneiric m1.large | grep Image | awk '{ print $3 }'`" >> ~/.juju/environments.yaml
echo "    default-instance-type: m1.large" >> ~/.juju/environments.yaml

Bootstrap
juju bootstrap

Deploy
juju deploy --repository . cloudfoundry-server
juju deploy --repository . cloudfoundry-server-dea
juju deploy --repository . cf-mysql
juju deploy --repository . cf-redis
juju deploy --repository . cf-mongodb

Relationships
juju add-relation cloudfoundry-server cloudfoundry-server-dea
juju add-relation cloudfoundry-server cf-mysql
juju add-relation cloudfoundry-server cf-mongodb
juju add-relation cloudfoundry-server cf-redis

Scale
juju add-unit cloudfoundry-server-dea
juju add-unit cf-mysql
juju add-unit cf-mongodb
juju add-unit cf-redis

DrawBridge
cd drawbridge
vmc
vmc target <your hostname here>
vmc add-user
vmc push

Take it all down
juju destroy-environment

The video version ( better than the not so short version above but not enough details )


The details ( pretty much all of them )

This deployment will be a bit different as we'll be deploying everything in Ubuntu 11.10 ( Oneiric ) and we'll be using large instances in Amazon EC2.  Let's get started...

Juju
sudo apt-add-repository ppa:juju/pkgs
sudo apt-get update
sudo apt-get install juju

Bazaar ( so we can download the charms )
sudo apt-get install bzr

CloudFoundry Juju charms
bzr branch lp:~canonical-sig/+junk/cloudfoundry-server
bzr branch lp:~canonical-sig/+junk/cloudfoundry-server-dea
bzr branch lp:~canonical-sig/+junk/cf-mysql
bzr branch lp:~canonical-sig/+junk/cf-mongodb
bzr branch lp:~canonical-sig/+junk/cf-redis

DrawBridge app ( Thanks Dustin )
bzr branch lp:~kirkland/+junk/drawbridge

Configure Juju
juju ( will create the configuration file if needed )

Get the latest Ubuntu Oneiric image from here or you can just use this little script that will do it for you:
#!/bin/bash

release="$1"
size="$2"
[ -z "$release" ] && release="oneiric"
[ -z "$size" ] && size="t1.micro"
echo "Release: ${release}"
echo "Size: ${size}"
result=`wget -q -O - http://uec-images.ubuntu.com/$release/current/ | grep -m1 "$size.*us-east" | sed -e "s/^.*<tt>//" -e "s/ <\/tt>.*$//" -e 's/${EC2_KEYPAIR_US_EAST_1}//'`
image_id=`echo $result | awk '{ print $2 }'`
echo "Image ID: ${image_id}"
Copy the above code and save it somewhere in your computer ( I named mine ubuntu-latest-image and saved it in ~/bin so it's in my PATH ).  The script depends on wget, grep, sed and awk.  All available in the repositories and more than likely already installed in your system.  Just in case you don't have them installed, run sudo apt-get install wget grep sed awk.


Execute the script as follows:
  • ubuntu-latest-image oneiric m1.large
It should return something like the following:
Release: oneiric
Size: m1.large
Image ID: ami-d131f2b8

We now need to modify our default juju configuration so we can deploy large (m1.large) oneiric ( ami-d131f2b8 ) instances.

Edit juju's configuration file (~/.juju/environments.yaml) and make it look something like this:
environments:  sample:    type: ec2    access-key: --removed--    secret-key: --removed--    control-bucket: --removed--    admin-secret: --removed--    default-image-id: ami-d131f2b8    default-instance-type: m1.large
The highlighted parts are the important ones, you should already have the other lines in your configuration.  

Bootstrap Juju:
juju bootstrap

Make sure the environment has been bootstrapped by running:
juju status 

The output should look similar the this:
2011-09-09 21:30:48,363 INFO Connecting to environment.
machines:
  0: {dns-name: ec2-184-73-104-84.compute-1.amazonaws.com, instance-id: i-58f72138}
services: {}
2011-09-09 21:30:50,925 INFO 'status' command finished successfully

== DynDNS ==
If you want your CloudFoundry server to be accessible ( and usable ) remotely, you'll need to have a DNS entry that also creates a wildcard record.  If you have a DynDNS account, you can enter your hostname and credentials right into this charm.  Upon deployment, the configuration will be done for you.  Edit cloudfoundry-server/hooks/install and change the following lines:
USE_DYNDNS="true" <---- make sure it is set to "true"
DYNDNS_USERNAME="dyndnsusername"  <----- Your DynDNS username
DYNDNS_PASSWORD="dyndnspassword" <----- Your DynDNS password
DYNDNS_HOSTNAME="cf-host.dyndns.org" <--- The DyDNS host you created for this.

Deploy the cloudfoundry-server charm by typing the following:
ensmelbe deploy --repository . cloudfoundry-server

After a few minutes, the output of juju status should look similar to this:
2011-09-09 21:43:38,203 INFO Connecting to environment.
machines:
  0: {dns-name: ec2-184-73-104-84.compute-1.amazonaws.com, instance-id: i-58f72138}
  1: {dns-name: ec2-50-17-173-69.compute-1.amazonaws.com, instance-id: i-c6f224a6}
services:
  cloudfoundry-server:
    charm: local:cloudfoundry-server-26
    relations: {}
    units:
      cloudfoundry-server/0:
        machine: 1
        relations: {}
        state: started

In order to be able to connect to the cloudfoundry-server, we need to tell juju to expose (open) the ports specified in the charm ( 80, 443 and 4222 in this case ).  Let's do that:
juju expose cloudfoundry-server

juju status should now look similar to this:
2011-09-09 21:46:01,008 INFO Connecting to environment.
machines:
  0: {dns-name: ec2-184-73-104-84.compute-1.amazonaws.com, instance-id: i-58f72138}
  1: {dns-name: ec2-50-17-173-69.compute-1.amazonaws.com, instance-id: i-c6f224a6}
services:
  cloudfoundry-server:
    exposed: true
    charm: local:cloudfoundry-server-26
    relations: {}
    units:
      cloudfoundry-server/0:
        machine: 1
        open-ports: [80/tcp, 443/tcp, 4222/tcp]
        relations: {}
        state: started
2011-09-09 21:46:05,037 INFO 'status' command finished successfully

We should now have ports 80, 443 and 4222 open to the world.

In order to connect to our CloudFoundry server, we need to install ruby-vmc ( available in Ubuntu 11.10 ).  Let's install it by typing:
sudo apt-get install ruby-vmc

Once installed, connect to your CloudFoundry server by typing:
vmc target api.<your dns entry here>
ie:  vmc target api.cf-host.dyndns.org

Create a user:
vmc add-user --email <some email address> --passwd <some password>
ie: vmc add-user --email 'test@example.com' --passwd 'test'

Deploy DrawBridge
cd drawbridge
vmc push
Would you like to deploy from the current directory? [Yn]: Y
Application Name: drawbridge
Application Deployed URL: 'drawbridge.cf-host.dyndns.org'? 
Detected a Node.js Application, is this correct? [Yn]: Y
Memory Reservation [Default:64M] (64M, 128M, 256M, 512M, 1G or 2G) 
Creating Application: OK
Would you like to bind any services to 'drawbridge'? [yN]: y
The following system services are available::
1. mongodb
2. mysql
3. redis
Please select one you wish to provision: 2
Specify the name of the service [mysql-ce0c8]: 
Creating Service: OK
Binding Service: OK
Uploading Application:
  Checking for available resources: OK
  Processing resources: OK
  Packing application: OK
  Uploading (16M): OK   
Push Status: OK
Staging Application: OK                                                         
Starting Application: OK  
cd ..                                                      

Open your browser to URL you selected when pushing the app ( in this example: http://drawbridge.cf-host.dyndns.org )




That's all there is to that!!  You have deployed CloudFoundry server via Ubuntu Server and Juju and, DrawBridge via vmc and CloudFoundry.

But wait!!!  There's more!.

A single cloudfoundry-server is probably not your idea of scalability.  Let's deploy a few more scalable components.  
Let's add an extra DEA, MySQL node, MongoDB node and Redis node.
cd <directory where your charms are>
juju deploy --repository . cloudfoundry-server-dea
juju deploy --repository . cf-mysql
juju deploy --repository . cf-mongodb
juju deploy --repository . cf-redis

.... and connect them all together with the cloudfoundry-server
juju add-relation cloudfoundry-server cloudfoundry-server-dea
juju add-relation cloudfoundry-server cf-mysql
juju add-relation cloudfoundry-server cf-mongodb
juju add-relation cloudfoundry-server cf-redis

After a few minutes, run juju status again.... It should look something similar to this:
2011-09-09 22:36:40,991 INFO Connecting to environment.
machines:
  0: {dns-name: ec2-184-73-104-84.compute-1.amazonaws.com, instance-id: i-58f72138}
  1: {dns-name: ec2-50-17-173-69.compute-1.amazonaws.com, instance-id: i-c6f224a6}
  2: {dns-name: ec2-107-20-82-210.compute-1.amazonaws.com, instance-id: i-7615c316}
  3: {dns-name: ec2-107-20-98-149.compute-1.amazonaws.com, instance-id: i-4e15c32e}
  4: {dns-name: ec2-184-72-209-9.compute-1.amazonaws.com, instance-id: i-5815c338}
  5: {dns-name: ec2-50-19-63-40.compute-1.amazonaws.com, instance-id: i-0015c360}
services:
  cf-mongodb:
    charm: local:cf-mongodb-1
    relations: {cf-server: cloudfoundry-server, mongodb-cluster: cf-mongodb}
    units:
      cf-mongodb/0:
        machine: 4
        relations:
          cf-server: {state: up}
          mongodb-cluster: {state: up}
        state: started
  cf-mysql:
    charm: local:cf-mysql-1
    relations: {cf-server: cloudfoundry-server, mysql-cluster: cf-mysql}
    units:
      cf-mysql/0:
        machine: 3
        relations:
          cf-server: {state: up}
          mysql-cluster: {state: up}
        state: started
  cf-redis:
    charm: local:cf-redis-1
    relations: {cf-server: cloudfoundry-server, redis-cluster: cf-redis}
    units:
      cf-redis/0:
        machine: 5
        relations:
          cf-server: {state: up}
          redis-cluster: {state: up}
        state: started
  cloudfoundry-server:
    exposed: true
    charm: local:cloudfoundry-server-26
    relations: {cf-server: cf-mysql}
    units:
      cloudfoundry-server/0:
        machine: 1
        open-ports: [80/tcp, 443/tcp, 4222/tcp]
        relations:
          cf-server: {state: up}
        state: started
  cloudfoundry-server-dea:
    charm: local:cloudfoundry-server-dea-26
    relations: {cf-dea-cluster: cloudfoundry-server-dea, cf-server: cloudfoundry-server}
    units:
      cloudfoundry-server-dea/0:
        machine: 2
        relations:
          cf-dea-cluster: {state: up}
          cf-server: {state: up}
        state: started
2011-09-09 22:36:57,701 INFO 'status' command finished successfully

It looks a bit different now doesn't it?  :)

Here's what we just did:
  • added a new DEA
  • added a new MySQL
  • added a new MongoDB
  • added a new Redis
This version of the deployment looks a lot better.

But wait!!!  There's more!.

The newly deployed units can "grow" the deployment as needed.  For example:
  • juju add-unit cf-mysql
  • juju add-unit cf-mongodb
  • juju add-unit cf-redis
  • juju add-unit cloudfoundry-server-dea
Each one of the above commands will add another unit of the existing deployed ones thus, horizontally scaling our deployment.  Go ahead and add a few units and, run juju status after so you can get a better idea of how all of these services are orchestrated.

You may have noticed that I haven't gone into any details as to how these charms work ( especially if you have read any of my previous posts ).  The idea I am trying to convey here is that with Ubuntu Server and Juju you really don't have to know how CloudFoundry needs to be installed and configured in order to be able to deploy and scale it.  Juju charms neatly encapsulate all of the necessary knowledge so you don't have to.  That is not to say that you shouldn't or are not able to. These charms are available for download, review, contributions and feedback ( <--- the emphasis means that I would really like comments, contributions and general feedback )


I look forward to your comments/questions and general feedback.  Let me know what you think.


-Juan
http://blog.xtremeghost.com

4 comments:

  1. I get this error in /var/log/juju/machine-agent.log on the remote instance after executing juju deploy --repository . cloudfoundry-server

    2011-09-29 13:29:37,340: juju.agents.machine@DEBUG: Starting service unit: cloudfoundry-server/0 ...
    2011-09-29 13:29:37,340: juju.agents.machine@INFO: Machine agent started id:1 deploy: provider:'ec2'
    2011-09-29 13:29:37,356: juju.agents.machine@ERROR: Error starting unit: cloudfoundry-server/0
    Traceback (most recent call last):
    File "/usr/lib/pymodules/python2.7/juju/agents/machine.py", line 126, in watch_service_units
    yield self.start_service_unit(unit_name)
    File "/usr/lib/python2.7/dist-packages/twisted/internet/defer.py", line 1018, in _inlineCallbacks
    result = result.throwExceptionIntoGenerator(g)
    File "/usr/lib/python2.7/dist-packages/twisted/python/failure.py", line 350, in throwExceptionIntoGenerator
    return g.throw(self.type, self.value, self.tb)
    File "/usr/lib/pymodules/python2.7/juju/agents/machine.py", line 143, in start_service_unit
    charm_id)
    File "/usr/lib/python2.7/dist-packages/twisted/internet/defer.py", line 1018, in _inlineCallbacks
    result = result.throwExceptionIntoGenerator(g)
    File "/usr/lib/python2.7/dist-packages/twisted/python/failure.py", line 350, in throwExceptionIntoGenerator
    return g.throw(self.type, self.value, self.tb)
    File "/usr/lib/pymodules/python2.7/juju/state/charm.py", line 54, in get_charm_state
    raise CharmStateNotFound(charm_id)
    CharmStateNotFound: Charm 'local:cloudfoundry-server-26' was not found

    Would you please help me about why the remote instance can't get the charms? although my current working directory was correct before creating the instance.

    Thank you

    ReplyDelete
  2. Hi Muhammad:

    Thanks for the comment. Recently, ensemble went through a rename ( It's now juju - juju.ubuntu.com ) among other changes. I will be revising the formulas ( now called charms ) to reflect the changes.

    When the changes are done, I will update all of the necessary posts, branches, formulas ( charms ).

    This should be done within a week or so.

    Thanks,

    Juan

    ReplyDelete
  3. Hi Juan,

    Thank you for your great post !
    Have you had time to continue your work? (Juju and charms ?)

    ReplyDelete
  4. I second vchav's commnets and his question. Great work. Will you be able to return to working on Juju charms for Cloud Foundry? It looks like work on those charms has gone dormant, or am I wrong? I hope so.

    ReplyDelete