While the Pallet site has comprehensive API documentation, there is only one quick start that gets you going with EC2. While I enjoy EC2 as much as the next person, the use case I was working on called for a more local development environment. As luck would have it, Pallet has a library called VMFest, which is an abstraction over Oracle's VirtualBox. Later, we would also pickup their Docker support, but that's a story for another day. The nice thing about VMFest is you can use VirtualBox as a cloud provider. On a reasonable piece of hardware, this means you can spin up virtual machines in 5-7 minutes, and they're full VMs that you can control. Last but not least, I wanted Ubuntu and Java on these VMs as a solid base example to work from. The following is a walk through of how to get started with Pallet, use VMFest as a good starter compute service, and install your first package - Java.
Pallet-java-example, the tutorial repo, is in Github.
Prerequisites:
- Oracle VirtualBox version 2.4 or later
- Leiningen, e.g. brew install leinginen
- Something to edit Clojure with, I recommend Light Table
- Follow the VirtualBox setup in the README (refer to Pallet-VMFEST Readme for issues)
With these requirements in hand, let's look at how you create this environment. i started with the Pallet lein plugin for creating an example project.
lein new pallet example
With the environment in place, I then proceeded to modify the project.clj file to include the VMFest dependencies, and the Pallet Java crate. In pallet, a crate is a collection of functions that are grouped together as a reusable unit, much like a Chef recipe.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defproject pallet-java-example "0.1.0-SNAPSHOT" | |
:description "Example Pallet project for show casing how to get a VM with Java up and running" | |
:dependencies [[org.clojure/clojure "1.5.1"] | |
[com.palletops/pallet "0.8.0-RC.3"] | |
[com.palletops/pallet-vmfest "0.3.0-beta.2"] | |
[com.palletops/java-crate "0.8.0-beta.5"] | |
[org.clojars.tbatchelli/vboxjws "4.2.4"] | |
[org.slf4j/slf4j-api "1.6.1"] | |
[ch.qos.logback/logback-core "1.0.0"] | |
[ch.qos.logback/logback-classic "1.0.0"]] | |
:profiles {:dev | |
{:dependencies | |
[[com.palletops/pallet "0.8.0-RC.3" | |
:classifier "tests"]] | |
:plugins | |
[[com.palletops/pallet-lein "0.8.0-alpha.1"]]} | |
:leiningen/reply | |
{:dependencies [[org.slf4j/jcl-over-slf4j "1.7.2"]] | |
:exclusions [commons-logging]}} | |
:local-repo-classpath true | |
:main pallet-java-example.main | |
:repositories | |
{"sonatype" "https://oss.sonatype.org/content/repositories/releases/"}) |
You will notice that we use the Virtual Box web service. There is also a local COM interface, which I presume is higher performing but is not portable across all environments. For this environment, a few more miliseconds to talk to the VirtualBox isn't a big deal, so we'll go portability for ease of setup on different environments. One important note, you cannot have both vbox dependencies in your configuration, as they use classpath loading and clash with eachother.
The next step is to look at what the lein pallet plugin generated for us. The good news is that this is almost everything we need. Let's take a look at the edited file:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns pallet-java-example.groups.pallet-java-example | |
"Node defintions for pallet-java-example" | |
(:require | |
[pallet.compute :refer [instantiate-provider]] | |
[pallet.api :refer [group-spec server-spec node-spec plan-fn converge lift]] | |
[pallet.compute :refer [images nodes]] | |
[pallet.configure :refer [compute-service]] | |
[pallet.crate.java :as java] | |
[pallet.crate.automated-admin-user :refer [automated-admin-user]] | |
[pallet.actions :as pact] | |
[pallet.compute.vmfest :as vmf])) | |
;; A refer to hold the provider | |
;; There are several ways to configure providers, such as a local .pallet/config.clj file | |
;; Here is an example where we can be very explicit about what we want, and even develop | |
;; a solution that changes the cloud provider to suit our needs - e.g. some code goes to EC2, | |
;; sometimes we use VMFest (Oracle VirtualBox) for development, etc | |
(def provider (ref {})) | |
;; the pallet compute provider, such as EC2, VMFest, Docker, etc can be manipulated | |
;; programmatically, as done here, or configured centrally such as via a local ~/.pallet/config.clj file | |
;; there is also a lein plugin for working with them as lein tasks | |
(defn init-vmfest [] | |
"Initializes the vmfest cloud provider" | |
(dosync | |
(ref-set provider | |
(hash-map :vmfest | |
(instantiate-provider "vmfest" :vbox-comm :ws))))) | |
;; Note image specification, such as RAM amount can be specified here | |
(def default-node-spec | |
(node-spec | |
:image {:image-id :ubuntu-13.04-64bit} | |
:hardware {:min-cores 1})) | |
;; here is where the automated-admin-user, a representation of your current | |
;; user ID executing the program, and SSH key used to bootstrap the target VM | |
;; this enables seamless ssh to the VM | |
(def | |
^{:doc "Defines the type of node will run on"} | |
base-server | |
(server-spec | |
:phases | |
{:bootstrap (plan-fn (automated-admin-user))})) | |
;; typically a server spec is where all of the application specific configuration | |
;; will take place, such as moving files, unzipping them, etc | |
(def | |
^{:doc "Define a server spec"} | |
example-server | |
(server-spec | |
:phases | |
{:configure (plan-fn | |
(pact/file "/tmp/phase-configure-basenode" :owner "vmfest", :group "vmfest", :mode "0644") | |
;; add more functions here, usually using things out of the pallet.actions namespace | |
)})) | |
;; The group is what is used by converge or lift to spin up VMs and move configuration on to them | |
;; Each of servers will be executed in the order of phases (bootstrap, configure, install) | |
;; So per the above, base-server has a bootstrap with automated-admin-user which will be done first | |
;; next, the java server-spec will execute during the install phase | |
;; lastly, the example-server configure phase will create the remote file, finishing up the converge | |
(def | |
^{:doc "Defines a example group spec that can be passed to converge or lift."} | |
example-group | |
(group-spec | |
"example-group" | |
:extends [base-server example-server (java/server-spec {:vendor :oracle :version "7"})] | |
:node-spec default-node-spec)) |
In pallet terms, a node is an instance of a software stack running on a compute service, i.e. a VM in our instance with all of the software installed. You'll see that we have a node-spec, some server-specs, and a group-spec. Pallet provides flexibility in defining profiles of what you want the node (e.g. machine level parameters), sever (most of your software), and the group (your cluster). These are all then converged or lifted together (i.e. deployed). These layers of configuration are applied in order of phases: bootstrap, install, configure.
Some important notes here
- In the base server, we define {:bootstrap (plan-fn (automated-admin-user))}, which is telling pallet that during the bootstrap phase of the node, execute the function for automated-admin user. For the EC2 tutorial, you provide Pallet with your EC2 credentials to solve the chicken-in-egg problem of how do you login to create the first user. Pallet-vmfest solves this with a sudo user and sudo password, stored in the .meta file in ./vmfest/models. If this inforation is not present, the SSH fails and therefore pallet executions fail.
- In the group-spec, you see that we extend java/server-spec. This is where the crate is defined and used. In the end, it's just more functions, and if you configure them into a group-spec, they will run.
Now we're just about ready to create our cluster, but first we need to get our Ubuntu base image, and then pull it all together.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns pallet-java-example.main | |
(:use [clojure.tools.cli :only (cli)] | |
[clojure.pprint]) | |
(:gen-class :main true) | |
(:require | |
[pallet.api :refer [group-spec server-spec node-spec plan-fn converge lift]] | |
[pallet.compute :refer [nodes images]] | |
[clojure.pprint :refer [pprint]] | |
[pallet.compute.vmfest :refer [add-image]] | |
[pallet.node :refer [group-name primary-ip]] | |
[pallet.phase :as pphase] | |
[pallet.actions :as pact] | |
[clojure.java.io :as io] | |
[pallet-java-example.groups.pallet-java-example :as pvm] | |
[clojure.java.shell :as csh])) | |
;; Simple example showing creatin of a compute service provider, via vmfest | |
;; and the converge of it | |
;; Note you'll have to shut down the VM manually in this example | |
(defn -main [& args] | |
(do | |
(pvm/init-vmfest) | |
(when-not (contains? (images (:vmfest @pvm/provider)) :ubuntu-13.04-64bit) | |
(println "Adding default ubuntu VM") | |
(add-image (:vmfest @pvm/provider) "https://s3.amazonaws.com/vmfest-images/ubuntu-13.04-64bit.vdi.gz")) | |
(println "Spinning up example-group virtual machine") | |
(converge {pvm/example-group 1} :compute (:vmfest @pvm/provider)) | |
(println "Completed VM creation") | |
(println (nodes (:vmfest @pvm/provider))) | |
(System/exit 0))) | |
There are a couple things going on here:
- add-image is from vmfest, and adds an image and a .meta file to your ~/.vmfest/models directory
- converge is the main function you'll use to bring up and down a cluster - converge to a server count, and converge to 0 to destroy it
- nodes is a function that prints out your current nodes, also from vmfest
This should help folks get started with Pallet, gives you an introduction to a PaaS running on your local server, and is a fun way to apply Clojure to a domain. Big thanks go out to Hugo Duncan and Antoni Batechilli, they both are very helpful to all who join #pallet and get started.
To recap the links:
- Pallet-java-example repo, i.e. the source for this article
- Pallet official website
- VMFest and Pallet-VMFest
- Oracle VirtualBox
- LightTable
Enjoy!