ironfan 3.1.3 → 3.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/notes/style_guide.md CHANGED
@@ -1,41 +1,82 @@
1
1
  # Ironfan + Chef Style Guide
2
2
 
3
- **NOTE**: NEEDS UPDATE. will be current by time of launch but the below may differ from what you see in practice.
3
+ ------------------------------------------------------------------------
4
4
 
5
- ## Cookbooks
5
+ ### System+Component define Names
6
6
 
7
- Ordinary cookbooks describe a single system, consisting of one or more components. For example, the `redis` cookbook has a `server` component (with a daemon and moving parts), and a `client` component (which is static).
7
+ Name things uniformly for their system and component. For the ganglia master,
8
+
9
+ * attributes: `node[:ganglia][:master]`
10
+ * recipe: `ganglia::master`
11
+ * role: `ganglia_master`
12
+ * directories: `ganglia/master` (if specific to component), `ganglia` (if not).
13
+ - for example: `/var/log/ganglia/master`
14
+
15
+ ### Component names
16
+
17
+ * `agent.rb`
18
+ * `worker.rb`
19
+ * `datanode.rb`
20
+ * `webnode.rb`
8
21
 
9
- You should crisply separate cookbook-wide concerns from component concerns. The server's attributes live in `node[:redis][:server]`, it is installed by the `redis::server` cookbook, and so forth.
10
-
11
- You should also separate system configuration from multi-system integration. Cookbooks should provide hooks that are neighborly but not exhibitionist, and otherwise mind their own business. The `hadoop_cluster` cookbook describes hadoop, the `pig` cookbook pig, and the `zookeeper` cookbook zookeeper. The job of tying those components together (copying zookeeper jars into the pig home dir, or the port+addr of hadoop daemons) should be isolated.
12
22
 
13
23
  ### Recipes
14
24
 
15
- * Naming:
16
- - `foo/recipes/default.rb` -- information shared by anyone using foo, including support packages, directories
17
- - `foo/recipes/client.rb` -- configure me as a foo client
18
- - `foo/recipes/server.rb` -- configure me as a foo server
19
- - `foo/recipes/ec2_conf` -- cloud-specific settings
25
+ Recipes partition these things:
26
+
27
+ * shared functionality between components
28
+ * proper event order
29
+ * optional or platform-specific functionality
30
+
31
+ * Within the foo cookbook, name your recipes like this:
32
+ - `default.rb` -- information shared by anyone using foo, including support packages, users and directories.
33
+ - `user.rb` -- define daemon users. Called 'user' even if there is more than one. It's OK to move this into the default cookbook.
34
+ - `install_from_X.rb` -- install packages (`install_from_package`), versioned tarballs (`install_from_release`). It's OK to move this into `default.rb`.
35
+ - `deploy.rb` -- use this when doing sha-versioned deploys.
36
+ - `plugins.rb` -- install additional plugins or support code. If you have separate plugins, name them `git_plugin`, `rspec_plugin`, etc.
37
+ - `server.rb` -- define the foo server process. Similarly, `agent`, `worker`, etc -- see component naming above.
38
+ - `client.rb` -- install libraries to *use* the foo service.
39
+ - `config_files.rb` -- discover other components, write final configuration to disk
40
+ - `finalize.rb` -- final cleanup
41
+
42
+ * Do not repeat the cookbook name in a recipe title: `ganglia::master`, not `ganglia::ganglia_master`.
43
+ * Use only `[a-z0-9_]` for cookbook and component names. Do not use capital letters or hyphens.
44
+ * Keep names short and descriptive (preferably 15 characters or less, or it jacks with the Chef webui).
45
+
20
46
  * Always include a `default.rb` recipe, even if it is blank.
21
- * *DO NOT* install daemons via the default cookbook, even if that's currently the only thing it does. Remember, a node that is a client -- or refers to any current or future component of the system -- will include the default recipe.
22
- * Do not repeat the cookbook name in a recipe title: `hbase:master`, not `hbase:hbase_master`; `zookeeper:server`, not `zookeeper:zookeeper_server`.
23
- * Use only `[a-z0-9_]` for cookbook and component names. Do not use capital letters or dashes. Keep names to fewer than 15 characters.
47
+ * *DO NOT* use the default cookbook to install daemons or do anything interesting at all, even if that's currently the only thing the recipe does. I want to be able to refer to the attributes in the apache cookbook without launching the apache service. Think of it like a C header file.
48
+
49
+ A `client` is also passive -- it lets me *use* the system without requiring that I run it. This means the client recipe should *never* launch a process (chef_client` and `nfs_client` components are allowed exceptions).
24
50
 
25
51
  ### Cookbook Dependencies
26
52
 
27
53
  * Dependencies should be announced in metadata.rb, of course.
28
- * *DO* remember to explicitly `include_recipe` for system resources -- `runit`, `java`, `provides_service`, `thrift` and `apt`.
29
- * *DO NOT* use `include_recipe` unless putting it in the role would be utterly un-interesting. You *want* the run to break unless it's explicitly included the role.
30
- - *yes*: `java`, `ruby`, `provides_service`, etc.
31
- - *no*: `zookeeper:client`, `nfs:server`, or anything that will start a daemon
54
+ * Explicitly `include_recipe` for system resources -- `runit`, `java`, `silverware`, `thrift` and `apt`.
55
+ - never
56
+ * *DO NOT* use `include_recipe` unless putting it in the role would be utterly un-interesting. You *want* the run to break unless it's explicitly included in the role.
57
+ - *yes*: `java`, `ruby`, `announces`, etc.
58
+ - *no*: `zookeeper::client`, `nfs::server`, or anything that will start a daemon
32
59
  Remember: ordinary cookbooks describe systems, roles and integration cookbooks coordinate them.
33
60
  * `include_recipe` statements should only appear in recipes that are entry points. Recipes that are not meant to be called directly should assume their dependencies have been met.
34
61
  * If a recipe is meant to be the primary entrypoint, it *should* include default, and it should do so explicitly: `include_recipe 'foo::default'` (not just 'foo').
35
62
 
63
+ Crisply separate cookbook-wide concerns from component concerns.
64
+
65
+ Separate system configuration from multi-system integration. Cookbooks should provide hooks that are neighborly but not exhibitionist, and otherwise mind their own business.
66
+
36
67
  ### Templates
37
68
 
38
- * *DO NOT* use node[:foo] in your templates except in rare circumstances. Instead, say `variables :foo => node[:foo]`; this lets folks use that cookbook from elsewhere.
69
+ *DO NOT* refer to attributes directly on the node (`node[:foo]`). This prevents people from using those templates outside the cookbook. Instead:
70
+
71
+ ```ruby
72
+ # in recipe
73
+ template 'fooconf.yml' do
74
+ variables :foo => node[:foo]
75
+ end
76
+
77
+ # in template
78
+ @node[:log_dir]
79
+ ```
39
80
 
40
81
  ### Attributes
41
82
 
@@ -43,42 +84,44 @@ You should also separate system configuration from multi-system integration. Coo
43
84
  * Attributes shared by all components sit at cookbook level, and are always named for the cookbook: `node[:hadoop][:log_dir]` (since it is shared by all its components).
44
85
  * Component-specific attributes sit at component level (`node[:cookbook_name][:component_name]`): eg `node[:hadoop][:namenode][:service_state]`. Do not use a prefix (NO: `node[:hadoop][:namenode_handler_count]`)
45
86
 
87
+ * Refer to node attributes by symbol, never by method:
88
+ - `node[:ganglia][:log_dir]`, not `node.ganglia.log_dir` or `node['ganglia']['log_dir']
89
+
46
90
  #### Attribute Files
47
91
 
48
- * The main attribute file should be named `attributes/default.rb`.
92
+ * The main attribute file should be named `attributes/default.rb`. Do not name the file after the cookbook, or anything else.
49
93
  * If there are a sizeable number of tunable attributes (hadoop, cassandra), place them in `attributes/tuneables.rb`.
50
- * ?? Place integration attribute *hooks* in `attributes/integration.rb` ??
51
94
 
52
- * Be generic when you're *simple and alone*, descriptive when you're not.
53
- - If a component has only one log file, call it 'log_file': `node[:foo][:server][:log_file]` and in general do not use a prefix.
54
- - If a component has more than one log_file, *always* use a prefix: `node[:foo][:server][:dashboard_log_file]` and `node[:foo][:server][:gc_log_file]`.
95
+ ## Name Attributes for their aspects
55
96
 
56
- * If you don't have exactly the semantics and datatype of the convention, don't use the convention. That is, don't use `:port` and give it a comma-separated string, or `:addr` and give it an email address.
57
- * (*this advice will change as we figure out integration rules*: use `foo_client` when you are a client of a service: so [:rails][:mysql_client][:host] to specify the hostname of your mysql server.)
58
-
59
- ## Attribute Names
97
+ Attributes should be named for their aspect: `port`, `log`, etc. Use generic names if there is only one attribute for an aspect, prefixed names if there are many:
98
+ - For a component that only opens one port: `node[:foo][:server][:port]`
99
+ - More than one port, use a prefix: `node[:foo][:server][:dash_port]` and `node[:foo][:server][:rpc_port]`.
60
100
 
61
- ### Universal Aspects
101
+ Sometimes the conventions below are inappropriate. All we ask is in those cases that you *not* use the special magic name. For example, don't use `:port` and give it a comma-separated string; name it something else, like `:port_list`.
102
+
103
+ Here are specific conventions:
62
104
 
63
105
  ### File and Dir Aspects
64
106
 
65
- A *file* is the full directory and basename for a file. A *dir* is a directory whose contents correspond to a single concern. A *root* is a prefix not intended to be used directly -- it will be decorated with suffixes to form dirs and files. A *basename* is only the leaf part of a file reference. Don't use the terms 'path' or 'filename'.
107
+ A *file* is the full directory and basename for a file. A *dir* is a directory whose contents correspond to a single concern. A *prefix* not intended to be used directly -- it will be decorated with suffixes to form dirs and files. A *basename* is only the leaf part of a file reference. Don't use the terms 'path' or 'filename'.
66
108
 
67
- Ignore the temptation to make a one-true-home-for-my-system, or to fight the package maintainer's choices.
109
+ Ignore the temptation to make a one-true-home-for-my-system, or to fight the package maintainer's choices. (FIXME: Rewrite to encourage OS-correct naming schemas.)
110
+ - a sandbox holding dir, pid, log, ...
68
111
 
69
112
  #### Application
70
113
 
114
+ * **prefix**: A container with directories bin, lib, share, src, to use according to convention
115
+ - default: `/usr/local`.
71
116
  * **home_dir**: Logical location for the cookbook's system code.
72
- - default: typically, leave it up to the package maintainer. Otherwise, `:prefix_root/share/:cookbook` should be a symlink to the `install_dir` (see below).
117
+ - default: typically, leave it up to the package maintainer. Otherwise, `:prefix/share/:cookbook` should be a symlink to the `install_dir` (see below).
73
118
  - instead of: `xx_home` / `dir` alone / `install_dir`
74
- * **prefix_root**: A container with directories bin, lib, share, src, to use according to convention
75
- - default: `/usr/local`.
76
119
  * **install_dir**: The cookbook's system code, in case the home dir is a pointer to potential alternates.
77
- - default: `:prefix_root/share/:cookbook-:version` ( you don't need the directory after the cookbook runs, use `:prefix_root/share/:cookbook-:version` instead, eg `/usr/local/src/tokyo_tyrant-xx.xx`)
120
+ - default: `:prefix/share/:cookbook-:version` ( you don't need the directory after the cookbook runs, use `:prefix/share/:cookbook-:version` instead, eg `/usr/local/src/tokyo_tyrant-xx.xx`)
78
121
  - Make `home_dir` a symlink to this directory (eg home_dir `/usr/local/share/elasticsearch` links to install_dir `/usr/local/share/elasticsearch-0.17.8`).
79
122
  * **src_dir**: holds the compressed tarball, its expanded contents, and the compiled files when installing from source. Use this when you will run `make install` or equivalent and use the files elsewhere.
80
- - default: `:prefix_root/src/:system_name-:version`, eg `/usr/local/src/pig-0.9.tar.gz`
81
- - do not: expand the tarball to `:prefix_root/src/(whatever)` if it will actually be used from there; instead, use the `install_dir` convention described above. (As a guideline, I should be able to blow away `/usr/local/src` and everything still works).
123
+ - default: `:prefix/src/:system_name-:version`, eg `/usr/local/src/pig-0.9.tar.gz`
124
+ - do not: expand the tarball to `:prefix/src/(whatever)` if it will actually be used from there; instead, use the `install_dir` convention described above. (As a guideline, I should be able to blow away `/usr/local/src` and everything still works).
82
125
  * **deploy_dir**: deployed code that follows the capistrano convention. See more about deploy variables below.
83
126
  - the `:deploy_dir/shared` directory holds common files
84
127
  - releases are checked out to `:deploy_dir/releases/{sha}`
@@ -138,7 +181,7 @@ Ignore the temptation to make a one-true-home-for-my-system, or to fight the pac
138
181
  * **release_url**: URL for the release.
139
182
  - instead of: install_url, package_url, being careless about partial vs whole URLs
140
183
  * **release_file**: Where to put the release.
141
- - default: `:prefix_root/src/system_name-version.ext`, eg `/usr/local/src/elasticsearch-0.17.8.tar.bz2`.
184
+ - default: `:prefix/src/system_name-version.ext`, eg `/usr/local/src/elasticsearch-0.17.8.tar.bz2`.
142
185
  - do not use `/tmp` -- let me decide when to blow it away (and make it easy to be idempotent).
143
186
  - do not use a non-versioned URL or file name.
144
187
  * **release_file_sha** or **release_file_md5** fingerprint
@@ -147,8 +190,7 @@ Ignore the temptation to make a one-true-home-for-my-system, or to fight the pac
147
190
 
148
191
  * **plugins**: array of system-specific plugins
149
192
 
150
- use `deploy_{}` for anything that would be true whatever SCM you're using; use
151
- `git_{}` (and so forth) where specific to that repo.
193
+ use `deploy_{}` for anything that would be true whatever SCM you're using; use `git_{}` (and so forth) where specific to that repo.
152
194
 
153
195
  * **deploy_env** production / staging / etc
154
196
  * **deploy_strategy**
@@ -160,7 +202,7 @@ use `deploy_{}` for anything that would be true whatever SCM you're using; use
160
202
  * **git_revision**: SHA or branch
161
203
  - instead of: `deploy_revision`
162
204
 
163
- * **apt/{repo_name}** Options for adding a cookbook's apt repo.
205
+ * **apt/(repo_name)** Options for adding a cookbook's apt repo.
164
206
  - Note that this is filed under *apt*, not the cookbook.
165
207
  - Use the best name for the repo, which is not necessarily the cookbook's name: eg `apt/cloudera/{...}`, which is shared by hadoop, flume, pig, and so on.
166
208
  - `apt/{repo_name}/url` -- eg `http://archive.cloudera.com/debian`
@@ -190,7 +232,7 @@ use `deploy_{}` for anything that would be true whatever SCM you're using; use
190
232
 
191
233
  * **XX_heap_max**, **xx_heap_min**, **java_heap_eden**
192
234
  * **java_home**
193
- * AVOID **java_opts** if possible: assemble it in your recipe from intelligible attribute names.
235
+ * AVOID batch declaration of options (e.g. **java_opts**) if possible: assemble it in your recipe from intelligible attribute names.
194
236
 
195
237
  ### Nitpicks
196
238
 
@@ -214,19 +256,12 @@ If your app does any of the following,
214
256
  * **exports** -- jars or libs that other programs may wish to incorporate
215
257
  * **consumes** -- placed there by any call to `discover`.
216
258
 
217
- ### Dummy aspects
218
-
219
- Integration cookbooks that announce as
220
-
221
- * Elastic Load Balancers
222
-
223
-
224
259
  ## Clusters
225
260
 
226
261
  * Describe physical configuration:
227
262
  - machine size, number of instances per facet, etc
228
263
  - external assets (elastic IP, ebs volumes)
229
- * Describe high-level assembly of systems via roles: `hadoop_namenode`, `nfs_client`, `flume_agent`, etc.
264
+ * Describe high-level assembly of systems via roles: `hadoop_namenode`, `nfs_client`, `ganglia_agent`, etc.
230
265
  * Describe important modifications, such as `ironfan::system_internals`, mounts ebs volumes, etc
231
266
  * Describe override attributes:
232
267
  - `heap size`, rvm versions, etc.
@@ -247,5 +282,19 @@ roles shouldn't assemble systems. The contents of the infochimps_chef/roles/plat
247
282
 
248
283
  * Deprecated:
249
284
  - Cluster and facet roles (`roles/gibbon_cluster.rb`, `roles/gibbon_namenode.rb`, etc) go away
250
- - roles should be service-oriented: `hadoop_master` considered harmful, you should explicitly enumerate the services
285
+ - Roles should be service-oriented: `hadoop_master` considered harmful, you should explicitly enumerate the services
286
+
287
+
288
+ ### Facets should be (nearly) identical
289
+
290
+ Within a facet, keep your servers almost entirely identical. For example, servers in a MySQL facet would their index to set shard order and to claim the right attached volumes. However, it would be a mistake to have one server within a facet be a master process and the rest be worker processes -- just define different facets for each.
291
+
292
+ ### Pedantic Distinctions:
293
+
294
+ Separate the following terms:
295
+
296
+ * A *machine* is a concrete thing that runs your code -- it might be a VM or raw metal, but it has CPUs and fans and a finite lifetime. It has a unique name tied to its physical presence -- something like 'i-123abcd' or 'rack 4 server 7'.
297
+ * A *chef node* is the code object that, together with the chef-client process, configures a machine. In ironfan, the chef node is strictly slave to the server description and the measured attributes of the machine.
298
+ * A *server description* gives the high-level specification the machine should acheive. This includes the roles, recipes and attributes given to the chef node; the physical characteristics of the machine ('8 cores, 7GB ram, AWS cloud'); and its relation to the rest of the system (george cluster, webnode facet, index 3).
251
299
 
300
+ In particular, we try to be careful to always call a Chef node a 'chef node' (never just 'node'). Try processing graph nodes in a flume node feeding a node.js decorator on a cloud node define by a chef node. No(de) way.
@@ -1,5 +1,12 @@
1
1
  ## Tips and Notes
2
2
 
3
+ ### Gems
4
+
5
+ knife cluster ssh bonobo-worker-2 'sudo gem update --system'
6
+ knife cluster ssh bonobo-worker-2 'sudo true ; for foo in /usr/lib/ruby/gems/1.9.2-p290/specifications/* ; do sudo sed -i.bak "s!000000000Z!!" $foo ; done'
7
+ knife cluster ssh bonobo-worker-2 'sudo true ; for foo in /usr/lib/ruby/site_ruby/*/rubygems/deprecate.rb ; do sudo sed -i.bak "s!@skip ||= false!true!" $foo ; done'
8
+
9
+
3
10
  ### EC2 Notes Instance attributes: `disable_api_termination` and `delete_on_termination`
4
11
 
5
12
  To set `delete_on_termination` to 'true' after the fact, run the following (modify the instance and volume to suit):
@@ -81,3 +88,5 @@ Your service is probably installed but removed from runit's purview; check the `
81
88
  * directory `/etc/sv/foo`, containing file `run` and dirs `log` and `supervise`
82
89
  * `/etc/init.d/foo` is symlinked to `/usr/bin/sv`
83
90
  * `/etc/servics/foo` is symlinked tp `/etc/sv/foo`
91
+
92
+
@@ -0,0 +1,168 @@
1
+ FIXME: Repurpose general structure to demonstrate a Hadoop cluster.
2
+
3
+ ## Walkthrough: Hadoop Cluster
4
+
5
+ Here's a very simple cluster:
6
+
7
+ ```ruby
8
+ Ironfan.cluster 'hadoop_demo' do
9
+ cloud(:ec2) do
10
+ flavor 't1.micro'
11
+ end
12
+
13
+ role :base_role
14
+ role :chef_client
15
+ role :ssh
16
+
17
+ # The database server
18
+ facet :dbnode do
19
+ instances 1
20
+ role :mysql_server
21
+
22
+ cloud do
23
+ flavor 'm1.large'
24
+ backing 'ebs'
25
+ end
26
+ end
27
+
28
+ # A throwaway facet for development.
29
+ facet :webnode do
30
+ instances 2
31
+ role :nginx_server
32
+ role :awesome_webapp
33
+ end
34
+ end
35
+ ```
36
+
37
+ This code defines a cluster named hadoop_demo. A cluster is a group of servers united around a common purpose, in this case to serve a scalable web application.
38
+
39
+ The hadoop_demo cluster has two 'facets' -- dbnode and webnode. A facet is a subgroup of interchangeable servers that provide a logical set of systems: in this case, the systems that store the website's data and those that render it.
40
+
41
+ The dbnode facet has one server, which will be named `hadoop_demo-dbnode-0`; the webnode facet has two servers, `hadoop_demo-webnode-0` and `hadoop_demo-webnode-1`.
42
+
43
+ Each server inherits the appropriate behaviors from its facet and cluster. All the servers in this cluster have the `base_role`, `chef_client` and `ssh` roles. The dbnode machines additionally house a MySQL server, while the webnodes have an nginx reverse proxy for the custom `hadoop_demo_webapp`.
44
+
45
+ As you can see, the dbnode facet asks for a different flavor of machine (`m1.large`) than the cluster default (`t1.micro`). Settings in the facet override those in the server, and settings in the server override those of its facet. You economically describe only what's significant about each machine.
46
+
47
+ ### Cluster-level tools
48
+
49
+ ```
50
+ $ knife cluster show hadoop_demo
51
+
52
+ +---------------------+-------+------------+-------------+--------------+---------------+-----------------+----------+--------------+------------+------------+
53
+ | Name | Chef? | InstanceID | State | Public IP | Private IP | Created At | Flavor | Image | AZ | SSH Key |
54
+ +---------------------+-------+------------+-------------+--------------+---------------+-----------------+----------+--------------+------------+------------+
55
+ | hadoop_demo-dbnode-0 | yes | i-43c60e20 | running | 107.22.6.104 | 10.88.112.201 | 20111029-204156 | t1.micro | ami-cef405a7 | us-east-1a | hadoop_demo |
56
+ | hadoop_demo-webnode-0 | yes | i-1233aef1 | running | 102.99.3.123 | 10.88.112.123 | 20111029-204156 | t1.micro | ami-cef405a7 | us-east-1a | hadoop_demo |
57
+ | hadoop_demo-webnode-1 | yes | i-0986423b | not running | | | | | | | |
58
+ +---------------------+-------+------------+-------------+--------------+---------------+-----------------+----------+--------------+------------+------------+
59
+
60
+ ```
61
+
62
+ The commands available are:
63
+
64
+ * list -- lists known clusters
65
+ * show -- show the named servers
66
+ * launch -- launch server
67
+ * bootstrap
68
+ * sync
69
+ * ssh
70
+ * start/stop
71
+ * kill
72
+ * kick -- trigger a chef-client run on each named machine, tailing the logs until the run completes
73
+
74
+
75
+ ### Advanced clusters remain simple
76
+
77
+ Let's say that app is truly awesome, and the features and demand increases. This cluster adds an [ElasticSearch server](http://elasticsearch.org) for searching, a haproxy loadbalancer, and spreads the webnodes across two availability zones.
78
+
79
+ ```ruby
80
+ Ironfan.cluster 'hadoop_demo' do
81
+ cloud(:ec2) do
82
+ image_name "maverick"
83
+ flavor "t1.micro"
84
+ availability_zones ['us-east-1a']
85
+ end
86
+
87
+ # The database server
88
+ facet :dbnode do
89
+ instances 1
90
+ role :mysql_server
91
+ cloud do
92
+ flavor 'm1.large'
93
+ backing 'ebs'
94
+ end
95
+
96
+ volume(:data) do
97
+ size 20
98
+ keep true
99
+ device '/dev/sdi'
100
+ mount_point '/data'
101
+ snapshot_id 'snap-a10234f'
102
+ attachable :ebs
103
+ end
104
+ end
105
+
106
+ facet :webnode do
107
+ instances 6
108
+ cloud.availability_zones ['us-east-1a', 'us-east-1b']
109
+
110
+ role :nginx_server
111
+ role :awesome_webapp
112
+ role :elasticsearch_client
113
+
114
+ volume(:server_logs) do
115
+ size 5
116
+ keep true
117
+ device '/dev/sdi'
118
+ mount_point '/server_logs'
119
+ snapshot_id 'snap-d9c1edb1'
120
+ end
121
+ end
122
+
123
+ facet :esnode do
124
+ instances 1
125
+ role "elasticsearch_data_esnode"
126
+ role "elasticsearch_http_esnode"
127
+ cloud.flavor "m1.large"
128
+ end
129
+
130
+ facet :loadbalancer do
131
+ instances 1
132
+ role "haproxy"
133
+ cloud.flavor "m1.xlarge"
134
+ elastic_ip "128.69.69.23"
135
+ end
136
+
137
+ cluster_role.override_attributes({
138
+ :elasticsearch => {
139
+ :version => '0.17.8',
140
+ },
141
+ })
142
+ end
143
+ ```
144
+
145
+ The facets are described and scale independently. If you'd like to add more webnodes, just increase the instance count. If a machine misbehaves, just terminate it. Running `knife cluster launch hadoop_demo webnode` will note which machines are missing, and launch and configure them appropriately.
146
+
147
+ Ironfan speaks naturally to both Chef and your cloud provider. The esnode's `cluster_role.override_attributes` statement will be synchronized to the chef server, pinning the elasticsearch version across the server and clients. Your chef roles should focus on specific subsystems; the cluster file lets you see the architecture as a whole.
148
+
149
+ With these simple settings, if you have already [set up chef's knife to launch cloud servers](http://wiki.opscode.com/display/chef/Launch+Cloud+Instances+with+Knife), typing `knife cluster launch hadoop_demo --bootstrap` will (using Amazon EC2 as an example):
150
+
151
+ * Synchronize to the chef server:
152
+ - create chef roles on the server for the cluster and each facet.
153
+ - apply role directives (eg the homebase's `default_attributes` declaration).
154
+ - create a node for each machine
155
+ - apply the runlist to each node
156
+ * Set up security isolation:
157
+ - uses a keypair (login ssh key) isolated to that cluster
158
+ - Recognizes the `ssh` role, and add a security group `ssh` that by default opens port 22.
159
+ - Recognize the `nfs_server` role, and adds security groups `nfs_server` and `nfs_client`
160
+ - Authorizes the `nfs_server` to accept connections from all `nfs_client`s. Machines in other clusters that you mark as `nfs_client`s can connect to the NFS server, but are not automatically granted any other access to the machines in this cluster. Ironfan's opinionated behavior is about more than saving you effort -- tying this behavior to the chef role means you can't screw it up.
161
+ * Launches the machines in parallel:
162
+ - using the image name and the availability zone, it determines the appropriate region, image ID, and other implied behavior.
163
+ - passes a JSON-encoded user_data hash specifying the machine's chef `node_name` and client key. An appropriately-configured machine image will need no further bootstrapping -- it will connect to the chef server with the appropriate identity and proceed completely unattended.
164
+ * Syncronizes to the cloud provider:
165
+ - Applies EC2 tags to the machine, making your console intelligible: ![AWS Console screenshot](https://github.com/infochimps-labs/ironfan/raw/version_3/notes/aws_console_screenshot.jpg)
166
+ - Connects external (EBS) volumes, if any, to the correct mount point -- it uses (and applies) tags to the volumes, so they know which machine to adhere to. If you've manually added volumes, just make sure they're defined correctly in your cluster file and run `knife cluster sync {cluster_name}`; it will paint them with the correct tags.
167
+ - Associates an elastic IP, if any, to the machine
168
+ * Bootstraps the machine using knife bootstrap
@@ -0,0 +1,166 @@
1
+ ## Walkthrough: Web Cluster
2
+
3
+ Here's a very simple cluster:
4
+
5
+ ```ruby
6
+ Ironfan.cluster 'web_demo' do
7
+ cloud(:ec2) do
8
+ flavor 't1.micro'
9
+ end
10
+
11
+ role :base_role
12
+ role :chef_client
13
+ role :ssh
14
+
15
+ # The database server
16
+ facet :dbnode do
17
+ instances 1
18
+ role :mysql_server
19
+
20
+ cloud do
21
+ flavor 'm1.large'
22
+ backing 'ebs'
23
+ end
24
+ end
25
+
26
+ # A throwaway facet for development.
27
+ facet :webnode do
28
+ instances 2
29
+ role :nginx_server
30
+ role :awesome_webapp
31
+ end
32
+ end
33
+ ```
34
+
35
+ This code defines a cluster named web_demo. A cluster is a group of servers united around a common purpose, in this case to serve a scalable web application.
36
+
37
+ The web_demo cluster has two 'facets' -- dbnode and webnode. A facet is a subgroup of interchangeable servers that provide a logical set of systems: in this case, the systems that store the website's data and those that render it.
38
+
39
+ The dbnode facet has one server, which will be named `web_demo-dbnode-0`; the webnode facet has two servers, `web_demo-webnode-0` and `web_demo-webnode-1`.
40
+
41
+ Each server inherits the appropriate behaviors from its facet and cluster. All the servers in this cluster have the `base_role`, `chef_client` and `ssh` roles. The dbnode machines additionally house a MySQL server, while the webnodes have an nginx reverse proxy for the custom `web_demo_webapp`.
42
+
43
+ As you can see, the dbnode facet asks for a different flavor of machine (`m1.large`) than the cluster default (`t1.micro`). Settings in the facet override those in the server, and settings in the server override those of its facet. You economically describe only what's significant about each machine.
44
+
45
+ ### Cluster-level tools
46
+
47
+ ```
48
+ $ knife cluster show web_demo
49
+
50
+ +---------------------+-------+------------+-------------+--------------+---------------+-----------------+----------+--------------+------------+------------+
51
+ | Name | Chef? | InstanceID | State | Public IP | Private IP | Created At | Flavor | Image | AZ | SSH Key |
52
+ +---------------------+-------+------------+-------------+--------------+---------------+-----------------+----------+--------------+------------+------------+
53
+ | web_demo-dbnode-0 | yes | i-43c60e20 | running | 107.22.6.104 | 10.88.112.201 | 20111029-204156 | t1.micro | ami-cef405a7 | us-east-1a | web_demo |
54
+ | web_demo-webnode-0 | yes | i-1233aef1 | running | 102.99.3.123 | 10.88.112.123 | 20111029-204156 | t1.micro | ami-cef405a7 | us-east-1a | web_demo |
55
+ | web_demo-webnode-1 | yes | i-0986423b | not running | | | | | | | |
56
+ +---------------------+-------+------------+-------------+--------------+---------------+-----------------+----------+--------------+------------+------------+
57
+
58
+ ```
59
+
60
+ The commands available are:
61
+
62
+ * list -- lists known clusters
63
+ * show -- show the named servers
64
+ * launch -- launch server
65
+ * bootstrap
66
+ * sync
67
+ * ssh
68
+ * start/stop
69
+ * kill
70
+ * kick -- trigger a chef-client run on each named machine, tailing the logs until the run completes
71
+
72
+
73
+ ### Advanced clusters remain simple
74
+
75
+ Let's say that app is truly awesome, and the features and demand increases. This cluster adds an [ElasticSearch server](http://elasticsearch.org) for searching, a haproxy loadbalancer, and spreads the webnodes across two availability zones.
76
+
77
+ ```ruby
78
+ Ironfan.cluster 'web_demo' do
79
+ cloud(:ec2) do
80
+ image_name "maverick"
81
+ flavor "t1.micro"
82
+ availability_zones ['us-east-1a']
83
+ end
84
+
85
+ # The database server
86
+ facet :dbnode do
87
+ instances 1
88
+ role :mysql_server
89
+ cloud do
90
+ flavor 'm1.large'
91
+ backing 'ebs'
92
+ end
93
+
94
+ volume(:data) do
95
+ size 20
96
+ keep true
97
+ device '/dev/sdi'
98
+ mount_point '/data'
99
+ snapshot_id 'snap-a10234f'
100
+ attachable :ebs
101
+ end
102
+ end
103
+
104
+ facet :webnode do
105
+ instances 6
106
+ cloud.availability_zones ['us-east-1a', 'us-east-1b']
107
+
108
+ role :nginx_server
109
+ role :awesome_webapp
110
+ role :elasticsearch_client
111
+
112
+ volume(:server_logs) do
113
+ size 5
114
+ keep true
115
+ device '/dev/sdi'
116
+ mount_point '/server_logs'
117
+ snapshot_id 'snap-d9c1edb1'
118
+ end
119
+ end
120
+
121
+ facet :esnode do
122
+ instances 1
123
+ role "elasticsearch_data_esnode"
124
+ role "elasticsearch_http_esnode"
125
+ cloud.flavor "m1.large"
126
+ end
127
+
128
+ facet :loadbalancer do
129
+ instances 1
130
+ role "haproxy"
131
+ cloud.flavor "m1.xlarge"
132
+ elastic_ip "128.69.69.23"
133
+ end
134
+
135
+ cluster_role.override_attributes({
136
+ :elasticsearch => {
137
+ :version => '0.17.8',
138
+ },
139
+ })
140
+ end
141
+ ```
142
+
143
+ The facets are described and scale independently. If you'd like to add more webnodes, just increase the instance count. If a machine misbehaves, just terminate it. Running `knife cluster launch web_demo webnode` will note which machines are missing, and launch and configure them appropriately.
144
+
145
+ Ironfan speaks naturally to both Chef and your cloud provider. The esnode's `cluster_role.override_attributes` statement will be synchronized to the chef server, pinning the elasticsearch version across the server and clients. Your chef roles should focus on specific subsystems; the cluster file lets you see the architecture as a whole.
146
+
147
+ With these simple settings, if you have already [set up chef's knife to launch cloud servers](http://wiki.opscode.com/display/chef/Launch+Cloud+Instances+with+Knife), typing `knife cluster launch web_demo --bootstrap` will (using Amazon EC2 as an example):
148
+
149
+ * Synchronize to the chef server:
150
+ - create chef roles on the server for the cluster and each facet.
151
+ - apply role directives (eg the homebase's `default_attributes` declaration).
152
+ - create a node for each machine
153
+ - apply the runlist to each node
154
+ * Set up security isolation:
155
+ - uses a keypair (login ssh key) isolated to that cluster
156
+ - Recognizes the `ssh` role, and add a security group `ssh` that by default opens port 22.
157
+ - Recognize the `nfs_server` role, and adds security groups `nfs_server` and `nfs_client`
158
+ - Authorizes the `nfs_server` to accept connections from all `nfs_client`s. Machines in other clusters that you mark as `nfs_client`s can connect to the NFS server, but are not automatically granted any other access to the machines in this cluster. Ironfan's opinionated behavior is about more than saving you effort -- tying this behavior to the chef role means you can't screw it up.
159
+ * Launches the machines in parallel:
160
+ - using the image name and the availability zone, it determines the appropriate region, image ID, and other implied behavior.
161
+ - passes a JSON-encoded user_data hash specifying the machine's chef `node_name` and client key. An appropriately-configured machine image will need no further bootstrapping -- it will connect to the chef server with the appropriate identity and proceed completely unattended.
162
+ * Syncronizes to the cloud provider:
163
+ - Applies EC2 tags to the machine, making your console intelligible: ![AWS Console screenshot](https://github.com/infochimps-labs/ironfan/wiki/aws_servers.jpg)
164
+ - Connects external (EBS) volumes, if any, to the correct mount point -- it uses (and applies) tags to the volumes, so they know which machine to adhere to. If you've manually added volumes, just make sure they're defined correctly in your cluster file and run `knife cluster sync {cluster_name}`; it will paint them with the correct tags.
165
+ - Associates an elastic IP, if any, to the machine
166
+ * Bootstraps the machine using knife bootstrap