import remainder of content

This commit is contained in:
Adam Wiggins
2011-06-03 02:35:09 -07:00
parent a1cd422e0a
commit 975d4ef264
11 changed files with 188 additions and 10 deletions
+10
View File
@@ -0,0 +1,10 @@
## XI. Admin processes
### One-off admin/management tasks
The [process formation](#) represents the array of processes that are used to do the app's regular business (such as handling web requests) as it runs. But the app's developers will often wish to do one-off administrative or maintenance tasks for the app, such as:
* Running database migrations (e.g. `manage.py syncdb` in Django, `rake db:migrate` in Rails).
* Running a console (also known as a REPL shell) to run arbitrary code or inspect the app's models against the live database. Most languages provide a REPL by running the interpreter without any arguments (e.g. `python` or `node`) or in some cases have a separate command (e.g. `irb` for Ruby, `rails console` for Rails).
* Run one-time scripts committed into the app's repo (e.g. `python scripts/fix_bad_records.py`).
Twelve-factor apps prefer languages which provide a REPL shell out of the box, and which make it easy to run one-off scripts. In a local deploy, developers invoke one-off admin processes by a direct shell command inside the app's checkout directory. In a production deploy, developers can use ssh or other remote command execution mechanism provided by that deploy's execution environment to run such a process.
+8
View File
@@ -0,0 +1,8 @@
## IV. Backing Services
### Treat backing services as attached resources
An *backing service* is any service the app accesses over the network as part of its normal operation. Examples include SQL or NoSQL databases (such as MySQL or CouchDB), messaging/queueing systems (such as RabbitMQ or Beanstalkd), SMTP servers (such as Sendmail or Postfix), and caching systems (such as Memcached).
Backing services are traditionally considered "local" and part of the app. In addition to these locally-managed services, the app may also have services provided and managed by third parties. Examples include SMTP services (such as Sendgrid or Postmark), metrics-gathering services (such as New Relic or Loggly), binary asset services (such as Amazon S3 or Rackspace Cloudfiles), and even API-accessible consumer services (such as Twitter, Google Maps, or Last.fm).
**The twelve-factor app makes no differentiation between local and third party services**. To the app, both are backing services, accesed via a URL or other locator/credentials stored in the [config](#). A deploy of the twelve-factor app should be able to swap out a local MySQL database with one running as a managed service (such as [Amazon RDS](http://aws.amazon.com/rds/)) without any changes to the app's code. Likewise, or a local SMTP server could be swapped with a managed service (such as [Sendgrid](http://sendgrid.com/)) without code changes. In both cases, only the resource handle in the config needs to change.
+20
View File
@@ -0,0 +1,20 @@
## V. Build, release, run
### Separate build and run stages
The *build* stage is a transform which converts a [code repo](#) into an executable bundle with no external dependencies, suitable for immediate execution in the execution environment. This bundle is known as a *build* (noun). The build stage typically checks out a fresh copy of the code at a commit specified by the release process, fetches and vendors dependencies, and compile binaries or assets. One well-known example of a build stage is creating a JAR file for a Java (or other JVM-based) language.
The *run* stage (commonly also referred to as "at runtime") takes the bundle produced by the build stage and executes it in the execution environment. This typically happens by fetching and expanding the executable bundle, setting up the environment with the [config](#), and then launching one or more of the app [processes](#).
In a traditional server-based hosting environment, it's easy to muddle together the build and run stages. For example, one might create the fresh checkout and install dependencies, but then tweak the code in-place on the disk.
The twelve-factor app uses strict separation between the build and run stages. It is impossible to make changes to the code at runtime, since there is no way to propagate those changes back to the build stage (or to other runtimes). Config is only accessible at runtime, since config can change without triggering a build.
Builds only happen as the result of a developer-initiated action: deploying new code. Runtime execution, however, can often happen automatically in cases such as a server reboot, or a process restarting after it crashes. Therefore the run stage should be kept to as few moving parts as possible, since problems that prevent an app from running can cause it to break in the middle of the night when no developers are around. The build stage can be more complex, since errors are always in the foreground for a developer who is actively engaged, and a failed build will abort the release process and avoid disrupting the running application.
### Release = build + config
A *release* is a combination of a build (an executable bundle generated in the [build stage](#)) and a [config](#), a set of environment variables to determine runtime behavior.
New builds always trigger new releases (since the build has been updated). Config changes also trigger a new release (since the config has been updated). A release requires restarting all running [processes](#) in order to bring all processes onto the new release.
Deployment tools typically offer release management tools, most notably the ability to roll back to a previous release. For example, the [Capistrano](https://github.com/capistrano/capistrano/wiki) deployment tool stores releases in a subdirectory named `releases`, where the current release is a symlink to the current release directory; and offers a `rollback` command.
+15
View File
@@ -0,0 +1,15 @@
## VIII. Concurrency
### Scale up via the process model
Any computer program, once run, is represented by one or more processes. Web apps have taken a variety of process-execution forms. For example, PHP processes run as child processes of Apache, started on demand as needed by request volume. Java processes take the opposite approach, with the JVM providing one massive uberprocess that grabs more system resources (CPU and memory) as needed, with concurrency managed internally via threads. In both cases, the running process(es) are only minimally visible to the developers of the app.
In the twelve-factor app, processes are a first class citizen. Processes in the twelve-factor app take strong cues from [the unix process model for running service daemons](http://adam.heroku.com/past/2011/5/9/applying_the_unix_process_model_to_web_apps/). Using this model, the developer can architect their app to handle diverse workloads by assigning each type of work to a *process type*. For example, HTTP requests may be handled by a web process, and long-running background tasks handled by a worker process.
The process model truly shines when it comes time to scale up. The share-nothing, horizontally partitionable nature of twelve-factor app processes means that adding more concurrency is a simple and reliable operation. The array of process types and number of processes of each type is known as the *process formation*:
<img src="http://s3.amazonaws.com/adamheroku_blog/process_diagram.png" style="border: 1px solid #777" />
Twelve-factor app proceses [should never daemonize](http://dustin.github.com/2010/02/28/running-processes.html) or write PID files. They count on the operating system's process manager (Upstart, launchd, or a distributed process manager on a cloud platform) to manage STDIN/STDOUT, restarts after crashes, and restarts requested by the user (such as when deploying new code).
Concurrency of process types should never be referenced or managed directly by the app, since it varies between deploys. The process model offers a clean way for the underlying platform to scale up and distributed the app across many physical machines, without needing to modify the code of the app. Attempting to manage process concurrency at the operating system level from the app (tools such as `mongrel_cluster` for Ruby, or command line switches such as `--concurrency` passed to Python's `celeryd`) are violations of the twelve-factor system.
+66
View File
@@ -0,0 +1,66 @@
## IX. Dev/prod parity
### Parity between development and production
Historically, there has been a big gap between development (a developer making live edits to a local deploy of the app) and production (a running deploy of the app accessed by end users). This is a gap in time: a developer may work on code that doesn't go to production for days, weeks, or even months. It's a gap in personnel: developers write code, ops engineers deploy it. And it's a gap in environments: a developer may be using Nginx, SQLite, and OS X, while the production deploy uses Apache, MySQL, and Linux.
The twelve-factor app is designed for continuous deployment. The gap in time between writing code and deploying it is small: a developer may write code and have it deployed hours or even minutes later. The gap in personnel is small: developers who wrote code should be closely involved in deploying it and watching its behavior in production. So it follows that the gap between development and production environments must also be made small.
<table>
<tr>
<th></th>
<th>Traditional app</th>
<th>Twelve-factor app</th>
</tr>
<tr>
<th>Time between deploys</th>
<td>Weeks</td>
<td>Hours</td>
</tr>
<tr>
<th>Code authors vs code deployers</th>
<td>Different</td>
<td>Same</td>
</tr>
<tr>
<th>Dev vs production environments</th>
<td>Different</td>
<td>Same</td>
</tr>
</table>
[Backing services](#), such as the app's database, queueing system, or cache, is one area where dev/prod parity is important. Many languages offer libraries which simplify access to the backing service. Some examples are in the table below.
<table>
<tr>
<th>Type</th>
<th>Language</th>
<th>Library</th>
<th>Adapters</th>
</tr>
<tr>
<td>Database</td>
<td>Ruby/Rails</td>
<td>ActiveRecord</td>
<td>MySQL, PostgreSQL, SQLite</td>
</tr>
<tr>
<td>Queue</td>
<td>Python/Django</td>
<td>Celery</td>
<td>RabbitMQ, Beanstalkd, Redis</td>
</tr>
<tr>
<td>Cache</td>
<td>Ruby/Rails</td>
<td>ActiveSupport::Cache</td>
<td>Memory, filesystem, Memcached</td>
</tr>
</table>
Developers find great appeal in using a lightweight backing service in their local environments, while a more serious and robust backing service will be used in production. For example, using SQLite locally and PostgreSQL in production; or local process memory for caching in development and Memcached in production.
The twelve-factor app developer resists the urge to use different backing services between development and production, even when adapters theoretically abstract away any differences in backing services. Otherwise, tiny incompatibilities crop up, causing code that worked and passed tests in development or staging to fail in production. These types of errors, though infrequent, are maddening to debug, and create friction that disincentives continuos deployment. The cost of this frustration and the subsequent dampening of continous deployment is extremely high over time.
The appeal of lightweight local services is also much less when you consider that modern backing services such as Memcached, PostgreSQL, and RabbitMQ are not difficult to install and run thanks to modern packaging systems, such as [Homebrew](http://mxcl.github.com/homebrew/) and [apt-get](https://help.ubuntu.com/community/AptGet/Howto). The cost of installing and using one of these is low compared to the high benefit of dev/prod parity and continous deployment.
Adapters to different backing services are still useful, because they make porting to new backing services relatively painless. But all deploys of the app (developer environments, staging, production) should be using the same type and version of each of the backing services.
+12
View File
@@ -0,0 +1,12 @@
## X. Logs
### Logs are event streams
Logs provide oversight and introspection of the behavior of a running app. Logs often appear as files on disk, but this is only one possible output format, not their true form.
Logs for a twelve-factor app are the aggregated, time-ordered output streams from all of the app's running processes and backing services. The logs have no fixed beginning or end, and can be thought of as a [stream of events](http://adam.heroku.com/past/2011/4/1/logs_are_streams_not_files/). This is typically represented as a text format with one event per line (though backtraces from exceptions may span multiple lines).
A twelve-factor app never concerns itself with routing or storage of its output stream. It should not attempt to write to or manage logfiles. Instead, each running process writes its stream, unbuffered, to standard output (STDOUT). During local development, the developer will view this stream in the foreground of their terminal to observe the app's behavior.
In deployment, each process' stream will be captured by the execution environment, collated together with all other streams from the app, and routed to one or more final destinations for viewing and long-term archival. These archival destinations are not visible to or configurable by the app, and instead are completely managed by the execution environment.
The event stream for an app can be routed to a file, or watched via realtime tail in a terminal. Most significantly, the stream can be sent to into an log indexing and analysis system such as Splunk, or a general-purpose data warehousing system such as Hadoop/Hive. These systems allow for great power and flexibility with introspecting the app's behavior over time: from finding specific events in the past, to large-scale graphing of trends (such as requests per minute), to active alerting according to user-defined heuristics (such as an alert when the quantity of errors per minute exceeds a certain threshold).
+12
View File
@@ -0,0 +1,12 @@
## VII. Port binding
### Services exported via port binding
Historically, web apps are often executed inside some kind of runtime container. For example, PHP apps might run as a module inside Apache, or Java apps might run inside Tomcat.
A twelve-factor app is completely self-contained and does not rely on runtime injection of a webserver into the execution environment to create a web-facing service. The web app exports HTTP as a service by binding to a port, and listening to requests coming in on that port.
In a local development environment, the developer visits a service URL like `http://localhost:5000/` to access the service exported by their app. In deployment, a routing layer handles routing requests from a public-facing hostname to the port-bound web processes.
This is typically implemented by using [dependency declaration](#) to add a webserver library to the app, such as [Tornado](http://www.tornadoweb.org/) for Python, [Thin](http://code.macournoyer.com/thin/) for Ruby, or [Jetty](http://jetty.codehaus.org/jetty/) for Java and other JVM-based languages. But this happens completely inside the app's code space, and the app's developers could chose to accept raw TCP requests and parse the HTTP themselves. Either one looks the same to the execution environment: the contract is binding to a port to serve requests, leaving the implementation details of what webserver is being used up to the web framework and/or the app's developers.
HTTP is not the only service that can be exported by port binding. Nearly any kind of server software, from ejabberd (speaking XMPP) to Redis (speaking the Redis protocol), can be run in the process-binds-to-a-port-to-receive-requests model.
+27
View File
@@ -0,0 +1,27 @@
## VI. Processes
### Stateless, disposable processes handle application logic
The business logic of an app happens in the app code. This code gets executed in the execution environment as one or more *processes*. the simplest case, the code is a stand-alone script, the execution environment is a developer's local laptop and an installed language runtime, and the process is launched via a simple command line: for example, `python my_script.py`. On the other end of the spectrum, a production deploy of a sophisticated app may use many [process types, instantiated into zero or more running processes](#).
Processes are stateless and share-nothing. The filesystem sitting beneath the process is either read-only (attempting a write will produce an error), or completely ephemeral (when the process terminates, all state written to disk will be discarded). Anything that needs to persist must be stored in a stateful [backing service](#), typically a [database](#).
The process memory space and filesystem can potentially be used as a brief, single-transacton cache. For example, downloading a large file, operating on it, and storing the results of the operation in the database. The twelve-factor app never assumes that anything cached in memory or on disk will be available on a future request or job - with many processes of each type running, chances are high that that a future request will be served by a different process. Even with a single process, a restart (triggered by code deploy, config change, or the executing environment deciding to relocate the process to a different physical location) will wipe all state.
Asset packagers (such as [Jammit](http://documentcloud.github.com/jammit/)) or [django-assetpackager](http://code.google.com/p/django-assetpackager/)) use the filesystem as a cache for compiled assets. A twelve-factor app is best served by doing this compiling during the [build stage](#) rather than at runtime.
Some web systems rely on "sticky sessions" - that is, caching user session data in memory of the app's process and expecting future requests from the same visitor to be routed to the same process. Sticky sessions are a violation of twelve-factor and should never be used or relied upon. Session state is a good candidate for a datastore that offers time-expiration, such as Memcached or Redis.
### Processes are disposable
Processes should be architected to be stopped and started at a moment's notice, faciliting fast elastic scaling and rapid deployment of code or config changes. This is a key element in ensuring robustness of a production deployment.
Processes should be prepared to shut down gracefully at any time on receiving a [SIGTERM](http://en.wikipedia.org/wiki/SIGTERM) signal from the process manager. The process will be granted a brief window (perhaps ten seconds) to shut down, after which it will be force-killed.
For a web process, graceful shutdown is achieved by ceasing to listen on the service port (thereby refusing any new requests), allowing any current requests to finish, and then exiting. Implicit in this model is that HTTP requests are short (no more than a few seconds), or in the case of long polling, the client is written to seamlessly attempt a reconnect in the case of losing the long-poll connection.
For a worker process, graceful shutdown is achieved by returning the current job to the work queue. For example, on RabbitMQ the worker can send a `NACK`; on Beanstalkd, the job is returned to the queue automatically whenever a worker disconnects. Lock-based systems such as Delayed Job need to be sure to release their lock on the job record. Implicit in this model is that all jobs are [reentrant](http://en.wikipedia.org/wiki/Reentrant_%28subroutine%29), which typically is achieved by wrapping the results in a transaction, or making the operation [idempotent](http://en.wikipedia.org/wiki/Idempotence).
Developers should attempt to keep process startup time low. Ideally, it should only take a few seconds from the time the launch command is executed until the process is up and ready to receive requests or jobs. But even a large app should try to keep its startup to to no more than 30 seconds. Briefer startup time provides more agility for deploys, code changes, scaling up; moreover, it helps robustness, because the process manager can more easily move processes to new physical machines when warranted.
Processes should also be robust against sudden death, in the case of a failure in the underlying hardware. While this is a much less common occurence than a graceful shutdown with `SIGTERM`, it can still happen. Use of a robust queueing backend (such as Beanstalkd) that returns jobs to the queue when clients disconnect or time out, can make all the difference here. Either way, a twelve-factor app is architected to handle unexpected, non-graceful terminations. Taking this idea to its logical conclusion may even result in a [crash-only](http://lwn.net/Articles/191059/) [design](http://couchdb.apache.org/docs/overview.html).
+9 -9
View File
@@ -7,29 +7,29 @@ The Twelve Factors
## [II. Dependencies](/dependencies)
### Explicit dependency declaration and isolation
## [III. Config]
## [III. Config](/config)
### Store config in the environment
## IV. Backing Services
## [IV. Backing Services](/backing-services)
### Treat backing services as attached resources
## V. Build, release, run
## [V. Build, release, run](/build-release-run)
### Separate build and run stages
## VI. Processes
## [VI. Processes](/processes)
### Stateless, disposable processes handle application logic
## VII. Port binding
## [VII. Port binding](/port-binding)
### Services exported via port binding
## VIII. Concurrency
## [VIII. Concurrency](/concurrency)
### Scale up via the process model
## IX. Dev/prod parity
## [IX. Dev/prod parity](/dev-prod-parity)
### Parity between development and production
## X. Logs
## [X. Logs](/logs)
### Logs are event streams
## XI. Admin processes
## [XI. Admin processes](/admin-processes)
### One-off admin/management tasks
+8
View File
@@ -110,6 +110,14 @@ code {
padding: 1pt 3pt;
}
table {
padding-left: 32pt;
}
td, th {
padding-right: 12pt;
padding-bottom: 4pt;
}
footer {
color: #444;
font-size: 12pt;
+1 -1
View File
@@ -5,7 +5,7 @@ get '/' do
erb :home
end
TOC = %w(repo dependencies config)
TOC = %w(repo dependencies config backing-services build-release-run processes port-binding concurrency dev-prod-parity logs admin-processes)
get '/:factor' do |factor|
@factor = factor