skip to content

Chef

Ruby-based configuration management with cookbooks and recipes, converging a node toward the resources you declared. Interviewers ask about it mostly where it is still running, and want the resource/provider model rather than DSL syntax.

on this pageshow

questions

4

In a Chef recipe, `package 'nginx'` is a resource declaration rather than a shell command. What does Chef actually do with that declaration, and how does it stay idempotent across repeated runs?

level: juniorimportance: must knowfreq 72%

answer

  1. declaration, not a command
  2. desired state on a list
  3. provider inspects before it acts
  4. no difference means no action
  5. execute is the exception — guard it

basics

~20 s

Chef adds the resource to the run's resource collection with a desired state. During converge, the matching provider inspects the node, compares current state to desired, and acts only when they differ — so a second run changes nothing.

solid answer

~50 s

A resource in a Chef recipe is a *declaration of desired state*, not an instruction to run something. `package 'nginx'` says "nginx should be installed"; it does not say "run apt-get install". Chef pairs every resource with a **provider** — the platform-specific code that knows how to inspect and change that kind of thing (apt on Debian, yum/dnf on RHEL). At converge time the provider loads the *current* state of that specific object from the node, compares it against the properties you declared, and takes action only if they differ. That test-and-repair step is where idempotence comes from: it lives in the provider, not in your recipe. The one place you have to supply it yourself is `execute` and `bash`, which run a command Chef cannot inspect — there you add a guard like `not_if`, `only_if`, or the `creates` property so the command is skipped when the work is already done.

code

ruby · 20 lines
ruby
package 'nginx' do
  action :install
end

template '/etc/nginx/nginx.conf' do
  source 'nginx.conf.erb'
  owner 'root'
  group 'root'
  mode '0644'
  notifies :restart, 'service[nginx]', :delayed
end

service 'nginx' do
  action [:enable, :start]
end

execute 'extract-app' do
  command 'tar -xzf /tmp/app.tgz -C /opt/app'
  creates '/opt/app/bin/app'
end

go deeper

for a junior

Be ready to say that a Chef resource declares a desired end state and that Chef only acts when the node does not already match it. Name two or three built-in resources such as package, file, template and service.

for a middle

Explain the resource-and-provider split: the provider loads current state, compares it to the declared properties, and marks the resource updated only on a real change. Know why execute needs not_if, only_if or creates.

for a senior

Show you use converge output as a signal — a healthy fleet reports zero resource updates on a repeat run, and a resource that updates every time is a defect worth chasing. Explain how notification timers keep a run from bouncing services repeatedly.

for a principal

Own the standard: when teams reach for execute, decide whether a shared custom resource should exist instead, so the test-and-repair logic is written once and reviewed rather than copy-pasted as shell guards across every cookbook.

## What a resource is A Chef recipe is Ruby, but the lines that matter are not doing work — they are describing an outcome. Each of these is a **resource**: a named object with a type, a name, a set of properties, and an action. ```ruby package 'nginx' do action :install end template '/etc/nginx/nginx.conf' do source 'nginx.conf.erb' owner 'root' mode '0644' notifies :restart, 'service[nginx]', :delayed end service 'nginx' do action [:enable, :start] end ``` Read aloud, that says: nginx should be installed; this config file should exist with this content and these permissions; the nginx service should be enabled and running. Nothing in it says *how*. It never mentions `apt-get`, `systemctl`, or a file-writing command. ## The resource collection When Chef Infra Client evaluates a recipe, it does not execute those resources on the spot. It appends each one to an ordered list called the **resource collection**. Only after the whole run list has been evaluated does Chef walk that collection and act — that second stage is called **converge**. Order in the collection is the order the resources were declared, which is why recipe order still matters even though the resources themselves are declarative. ## Resource and provider Every resource type is backed by one or more **providers**. The resource is the *what*; the provider is the *how*, and it is platform-aware. On a Debian node the `package` resource resolves to the apt provider; on a RHEL node, to the dnf or yum provider. Your recipe does not change. This split is the reason a single cookbook can target several operating systems, and it is the main thing an interviewer is testing when they ask "what is the resource/provider model?" A provider implements two responsibilities: 1. **Load current state** — ask the node what is actually true right now. For `package`, query the package database for the installed version. For `file`, stat the path and hash the content. For `service`, ask the init system whether the unit is enabled and running. 2. **Take action if needed** — compare the loaded current state against the declared properties, and only if they diverge, perform the change and mark the resource **updated**. ## Where idempotence comes from Idempotence means running the same recipe twice produces the same result as running it once, and the second run reports no changes. In Chef, that property is supplied by the providers, not by conditionals you write. You do not write: ```ruby # Not how Chef is meant to be written unless node['packages'].include?('nginx') # ... end ``` You declare the resource and let the provider decide. This is also what makes a run's output meaningful: Chef reports how many resources were updated, and a converged, healthy fleet should show zero updates on a repeat run. A recipe that reports changes on every run is a bug, and interviewers use exactly that symptom as a probe. ## The exception: resources Chef cannot inspect The `execute`, `bash`, `script` and similar resources hand Chef an opaque command. Chef has no way to inspect what that command would do, so it has no current state to compare against — by default it runs the command every converge. You restore idempotence with a **guard**: ```ruby execute 'extract-app' do command 'tar -xzf /tmp/app.tgz -C /opt/app' creates '/opt/app/bin/app' # skip if this path already exists end execute 'load-schema' do command '/usr/local/bin/load-schema.sh' not_if '/usr/local/bin/schema-present.sh' end ``` `not_if` skips the resource when its command succeeds; `only_if` runs it only when its command succeeds; `creates` skips when the named path exists. Both guards also accept a Ruby block instead of a string. Getting this right is the difference between a cookbook that converges cleanly and one that restarts a service on every 30-minute agent run. ## Notifications Because the provider knows whether it actually changed anything, resources can react to each other. `notifies :restart, 'service[nginx]', :delayed` fires only when the template resource was *updated* — so nginx restarts when the config genuinely changed and not otherwise. `:delayed` queues the restart to the end of the run so several config changes collapse into one restart; `:immediately` runs it as soon as the notifying resource finishes. `subscribes` expresses the same link from the receiving side, which is useful when the resource you depend on lives in a cookbook you do not own. ## Custom resources When the built-in set is not enough, you write a **custom resource** — a file under a cookbook's `resources/` directory that declares `property` definitions and `action` blocks. Inside those action blocks you declare more resources, so your abstraction inherits the same test-and-repair behaviour. This is the modern replacement for the older LWRP/HWRP style, and it is how a team turns "install our internal agent" into a single line that other cookbooks call.

  • A colleague's cookbook reports the same `execute` resource as updated on every single run. What is wrong and how do you fix it?
    `execute` runs an opaque command, so Chef has no current state to compare and runs it every converge. Add a guard: `creates` pointing at the artefact the command produces, or `not_if`/`only_if` with a cheap test command or Ruby block. If the work maps onto a real resource type — a file, a package, an archive — prefer that resource over `execute` entirely, since its provider already supplies the check.
  • How does Chef decide which provider backs a `package` resource on a given machine?
    Ohai collects node data at the start of the run, including platform and platform family. Chef's provider resolution uses that, plus each provider's `provides` declaration, to pick the platform-appropriate implementation — apt on Debian and Ubuntu, dnf or yum on RHEL family, and so on. You can force a specific one with the `provider` property, but needing to is usually a sign the platform detection or the cookbook's supported platforms are wrong.
  • Why does `notifies` with `:delayed` matter when several resources change the same service's configuration?
    A notification only fires when the notifying resource was actually updated, and `:delayed` queues the action to the end of the converge, de-duplicating it. Five template resources all notifying a restart produce one restart at the end of the run rather than five mid-run bounces. `:immediately` is for the cases where later resources genuinely need the new state in place, such as reloading a service before a subsequent resource talks to it.

A resource is a shopping list, not a route through the shop. You write "milk"; the provider is the shopper who checks the fridge first and only buys it if there is none.

saying these in an interview costs you the question

  • Says a resource executes at the line where it appears in the recipe
  • Claims you make Chef idempotent by wrapping resources in Ruby if statements
  • Assumes execute and bash resources are idempotent out of the box
  • Thinks Chef compares against a stored state file rather than inspecting the node
  • Cannot separate the resource (what) from the provider (how)

context

open as a page

Chef Infra Client executes a run in two phases, compile and converge. What happens in each, and why does plain Ruby written inside a recipe so often run earlier than its author expected?

level: middleimportance: should knowfreq 58%

basics

~20 s

Compile evaluates every recipe as Ruby and builds an ordered resource collection; converge then walks that collection and lets each provider act. Bare Ruby in a recipe runs during compile — before any resource has taken effect — so it sees the node's pre-run state.

open as a page

A Chef cookbook lists `depends` entries in its metadata.rb. How do those dependency versions get resolved and pinned for a node, and what did Policyfiles change compared with Berkshelf plus environment version constraints?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Berkshelf resolves the metadata.rb constraints into a Berksfile.lock and uploads the cookbooks, while the Chef Infra Server pins versions per environment and expands the run list at run time. Policyfiles replace both with one artefact that locks the run list and every cookbook version together.

open as a page

You inherit a fleet of long-lived Linux servers configured by a large set of Chef cookbooks, maintained by a team fluent in Ruby. How do you decide whether to keep converging with chef-client or move that configuration somewhere else?

level: principalimportance: nice to knowfreq 28%

basics

~20 s

Judge the cookbooks, not the tool's reputation: whether they are declarative and idempotent, whether anyone left can debug a compile-versus-converge bug, and whether the Chef Infra Server plus agent estate is worth operating. Working, quiet cookbooks are a poor migration candidate.

open as a page