About Environments
The _default
environment
Every Chef Infra Server organization must have at least one environment.
Each organization starts out with a single _default
environment. The
_default
environment can’t be modified in any way. Nodes, roles,
run-lists, cookbooks (and cookbook versions), and attributes specific to
an organization can only be associated with a custom environment.
Additional environments can be created to reflect each organization’s
patterns and workflow. For example, creating production
, staging
,
testing
, and development
environments.
Environment attributes
Note
Attributes can be defined in an environment and then used to override the default attributes in a cookbook. When an environment is applied during a Chef Infra Client run, environment attributes are compared to the attributes that are already present on the node. When the environment attributes take precedence over the default attributes, Chef Infra Client applies those new settings and values during a Chef Infra Client run.
Environment attributes can be set to either default
attribute level or
an override
attribute level.
Environment attribute types
There are two types of attributes that can be used with environments:
Attribute Type | Description |
---|---|
default | A default attribute is automatically reset at the start of every Chef
Infra Client run and has the lowest attribute precedence. Use default
attributes as often as possible in cookbooks. |
override | An override attribute is automatically reset at the start of every
Chef Infra Client run and has a higher attribute precedence than
default , force_default , and normal attributes. An override
attribute is most often specified in a recipe, but can be specified in
an attribute file, for a role, and/or for an environment. A cookbook
should be authored so that it uses override attributes only when
required. |
Pinning Cookbooks in environments
Cookbook versions can be pinned in each environment, which allows you to control the rollout of new cookbook releases through successive testing environments before releasing new cookbook versions into production environments. See the environment format examples below for the cookbook pinning syntax.
Environment formats
Environments may be stored on disk (any in source control) in two formats:
- As Ruby ( a file that ends with
.rb
); this format isn’t available when running Chef Infra Client in local mode - As JSON (a file that ends with
.json
)
Chef language
Each environment is defined as a Ruby file (a file that ends with
.rb
). Each environment file should contain the following
domain-specific attributes:
Setting | Description |
---|---|
| A version constraint for a single cookbook. For example: or: or: |
| A version constraint for a group of cookbooks. For example: |
| Optional. A set of attributes to be applied to all nodes, assuming the node doesn't already have a value for the attribute. This is useful for setting global defaults that can then be overridden for specific nodes. If more than one role attempts to set a default value for the same attribute, the last role applied is the role to set the attribute value. When nested attributes are present, they're preserved. For example, to specify that a node that has the attribute |
| A description of the functionality that's covered. For example: |
| A unique name within the organization. Each name must be made up of letters (uppercase and lowercase), numbers, underscores, and hyphens: [A-Z][a-z][0-9] and [_-]. Spaces aren't allowed. For example: |
| Optional. A set of attributes to be applied to all nodes, even if the node already has a value for an attribute. This is useful for ensuring that certain attributes always have specific values. If more than one role attempts to set an override value for the same attribute, the last role applied wins. When nested attributes are present, they're preserved. For example: The parameters in a Ruby file are actually Ruby method calls, so parentheses can be used to provide clarity when specifying numerous or deeply-nested attributes. For example: or: |
A Ruby file for each non-default environment must exist in the
environments/
subdirectory of the chef-repo. (If the chef-repo does
not have this subdirectory, then it should be created.) The complete
environment has the following syntax:
name 'environment_name'
description 'environment_description'
cookbook OR cookbook_versions 'cookbook' OR 'cookbook' => 'cookbook_version'
default_attributes 'node' => { 'attribute' => [ 'value', 'value', 'etc.' ] }
override_attributes 'node' => { 'attribute' => [ 'value', 'value', 'etc.' ] }
where both default and override attributes are optional and either a
cookbook or cookbook versions (one or more) are specified. For example,
an environment named dev
that uses the couchdb
cookbook (version
11.0.0 or higher) that listens on ports 80 and 443:
name 'dev'
description 'The development environment'
cookbook_versions 'couchdb' => '= 11.0.0'
default_attributes 'apache2' => { 'listen_ports' => %w(80 443) }
Or (using the same scenario) to specify a version constraint for only one cookbook:
cookbook 'couchdb', '= 11.0.0'
More than one cookbook version can be specified:
cookbook_versions({
'couchdb' => '= 11.0.0',
'my_rails_app' => '~> 1.2.0'
})
Attributes are optional and can be set at the default and override levels. These will be processed according to attribute precedence. An environment attribute will be applied to all nodes within the environment, except in places where it’s overridden by an attribute with higher precedence. For example:
default_attributes 'apache2' => { 'listen_ports' => %w(80 443) }
will have all nodes in the environment (node[:apache2][:listen_ports]
)
set to '80'
and '443'
unless they were overridden by an attribute
with higher precedence. For example:
override_attributes 'apache2' => { 'listen_ports' => %w(80 443) }
JSON
The JSON format for environments maps directly to the domain-specific
Ruby format: the same settings, attributes, and values, and a similar
structure and organization, just formatted as JSON. When an environment
is defined as JSON the file that contains that data must be defined as a
file that ends with .json
. For example:
{
"name": "dev",
"default_attributes": {
"apache2": {
"listen_ports": [
"80",
"443"
]
}
},
"json_class": "Chef::Environment",
"description": "",
"cookbook_versions": {
"couchdb": "= 11.0.0"
},
"chef_type": "environment"
}
The JSON format has two additional settings:
Setting | Description |
---|---|
chef_type | Always set this to environment . Use this setting for any custom process that consumes environment objects outside of Ruby. |
json_class | Always set this to Chef::Environment . Chef Infra Client uses this setting to automatically inflate an environment object. If objects are being rebuilt outside of Ruby, ignore it. |
Create environments
An environment can be created in four different ways:
- Create a Ruby file in the environments sub-directory of the chef-repo and then push it to the Chef Infra Server
- Create a JSON file directly in the chef-repo and then push it to the Chef Infra Server
- Using knife
- Using the Chef Infra Server REST API
Once an environment exists on the Chef Infra Server, a node can be
associated with that environment using the chef_environment
method.
Manage environments
Once created, an environment can be managed in several ways:
- By using knife and passing the
-E ENVIRONMENT_NAME
option withknife cookbook upload
- By using Ruby or JSON files that are stored in a version source
control system. These files are pushed to the Chef Infra Server
using the
knife environment
subcommand and thefrom file
argument. This approach allows environment data to be dynamically generated. This approach won’t work unless these files are defined in the proper format—Ruby file end with.rb
; JSON files end with.json
.
These workflows are mutually exclusive: only the most recent environment changes will be kept on the Chef Infra Server, regardless of the source of those changes. All previous changes are overwritten when environment data is updated.
The settings for environments can be modified and environments can be integrated into the larger infrastructure by associating them with nodes and by using recipes to call specific environment settings.
Find environment from recipe
Use the following syntax to find the current environment from a recipe:
node.environment
or:
node.chef_environment
Save in a data bag
Values that are stored in a data bag are global to the organization and are available to any environment. There are two main strategies that can be used to store environment data within a data bag: by using a top-level key that corresponds to the environment or by using separate items for each environment.
A data bag that’s storing a top-level key for an environment might look something like this:
{
"id": "some_data_bag_item",
"production" : {
// Hash with all your data here
},
"testing" : {
// Hash with all your data here
}
}
When using the data bag in a recipe, that data can be accessed from a recipe using code similar to:
bag_item[node.chef_environment]['some_other_key']
The other approach is to use separate items for each environment. Depending on the amount of data, it may all fit nicely within a single item. If this is the case, then creating different items for each environment may be a simple approach to providing values within a data bag for each environment. However, this approach is more time-consuming and may not scale to large environments or when the data must be stored in many data bag items.
Override attributes in roles
Environment attributes that are used with roles can be overridden. Typically, this is done by using attribute precedence, but sometimes it may be necessary to ensure that specific attributes are used based on the presence of specific environments. This type of scenario is best addressed in using a recipe that relies on a top-level key that’s stored in a data bag.
For example, to retrieve a value from a data bag based on a specific environment:
mything = data_bag_item('things', 'mything')
attribute_i_want = mything[node.chef_environment]
Set for a node
A node is considered to be associated with an environment when the
chef_environment
attribute is set. The chef_environment
attribute
can’t be set with normal or override attributes (in a role)
because it’s actually a method. An environment may be set explicitly
using the following methods:
By using the
knife edit
andknife exec
subcommandsBy editing the
environment
configuration details in the client.rb file, and then usingknife bootstrap -e environment_name
to bootstrap the changes to the specified environmentNote
After the environment has been set using bootstrap, the environment is set in the client.rb file and may not be modified using theedit
argument of theknife node
subcommand.By setting the
environment
configuration entry in the client.rb file ; when Chef Infra Client runs, it will pick up the value and then set thechef_environment
attribute of the node
Move nodes
Use the knife exec
subcommand to move nodes between environments, such
as from a “dev” to a “production” environment. For example:
knife exec -E 'nodes.transform("chef_environment:dev") { |n| n.chef_environment("production") }'
Search environments
When searching, an environment is an attribute. This allows search results to be limited to a specified environment by using Boolean operators and extra search terms. For example, to use knife to search for all of the servers running CentOS in an environment named “QA”, enter the following:
knife search node "chef_environment:QA AND platform:centos"
Or, to include the same search in a recipe, use a code block similar to:
qa_nodes = search(:node, 'chef_environment:QA')
qa_nodes.each do |qa_node|
# Do useful work specific to qa nodes only
end
Environments in chef-solo
An environment is defined using JSON or the Ruby DSL. chef-solo will
look for environments in /var/chef/environments
, but this location can
be modified by changing the setting for environment_path
in solo.rb.
For example, the following setting in solo.rb:
environment_path '/var/chef-solo/environments'
Environment data looks like the following in JSON:
{
"name": "dev",
"default_attributes": {
"apache2": {
"listen_ports": [
"80",
"443"
]
}
},
"json_class": "Chef::Environment",
"description": "",
"cookbook_versions": {
"couchdb": "= 11.0.0"
},
"chef_type": "environment"
}
and like the following in the Ruby DSL:
name 'environment_name'
description 'environment_description'
cookbook OR cookbook_versions 'cookbook' OR 'cookbook' => 'cookbook_version'
default_attributes 'node' => { 'attribute' => %w(value value etc.) }
override_attributes 'node' => { 'attribute' => %w(value value etc.) }