Local usage of Chef with knife-zero
Sometimes you don’t need or want a dedicated server for configuration management with Chef, and this is
completely possible, you can use a knife plugin called knife-zero
to simulate a local server with
Chef local mode and work everything within a repository.
This can also be called a push based
system instead of the default pull based
chef server. I’m not
going to address the differences, pros and cons because that’s not the scope of this article. I’m assuming
you already know these and is interested in doing this with Chef.
How this works:
-
Install the
chef-workstation
tools, including theknife
CLI utility; -
Install the
knife
plugin namedknife-zero
; -
You create a local repository (i.e. using git) containing the basic chef structure;
-
Instead of using
knife
to do things like: upload cookbooks, data bags, that will interact with a chef server, you useknife-zero
and it will automatically run a local, temporary chef-server with all content from the repository.
In this article, I’m also assuming that you have basic knowledge about configuration management and Chef, and
I’ll focus on the technical aspect of setting up knife-zero
and using it.
Installing tools
Here we’ll use the official tools provided by Chef. It’s also good to mention that after the Chef trademark usage was changed to a more commercial approach, there’s a community behind CINC which stands for CINC is not Chef that provides the same tools I’ll use here. So if you’re inside an commercial companny that is not looking for a commercial/paid product, use it. It’s the same thing!
-
Download and install the chef-workstation package
This package includes all the necessary tools to work locally with chef: an embedded Ruby interpreter, Ruby gems for
chef
,chef-client
,chef-zero
,knife
,ohai
,cookstyle
,foodcritic
,kitchen
, and various other tools.In my case, I’m using Fedora Linux 40, so the equivalent chef-workstation version would be Red Hat Enterprise Linux 8.x or 9.x. You can check the package’s latest version on the [https://docs.chef.io/release_notes_workstation/](release notes) page.
Following the instructions, I download and installed the package:
wget https://packages.chef.io/files/stable/chef-workstation/24.4.1064/el/9/chef-workstation-24.4.1064-1.el9.x86_64.rpm sudo dnf localinstall chef-workstation-24.4.1064-1.el9.x86_64.rpm
-
Install the
knife-zero
Ruby gem using the following command:chef gem install knife-zero
This will run the
gem install
command but inside the embedded Ruby interpreter from the chef-workstation, and for the user you’re running (we’re not using sudo here). You can check all gems installed on this embedded ruby with the command:chef gem list
Notice that
knife-zero
should be on the list! -
Test if
knife-zero
is working by running the command:knife zero
The command should return:
** ZERO COMMANDS ** knife zero apply QUERY (options) knife zero bootstrap [SSH_USER@]FQDN (options) knife zero chef_client QUERY (options) | It's same as converge knife zero converge QUERY (options) knife zero diagnose # show configuration from file
That’s it! All you need should be installed and ready to be used.
Create and configure a repository
In this article I’ll create a repository from scratch. But you can also use any already-created Chef repository and configure it to use knife-zero.
Create the base repository by running:
chef generate repo chef-repo
cd chef-repo
This creates a skeleton reposity at directory chef-repo
. We’ll also create a dummy cookbook to test it later:
cd cookbooks
chef generate cookbook testing
And the following content on chef-repo/cookbooks/testing/recipes/default.rb
:
file '/tmp/testing' do
owner 'root'
group 'root'
mode '0640'
content "this was created by chef for #{node['hostname']}"
end
Back to the repository’s root directory, let’s create a specially crafted knife configuration to always use
a Chef local server and knife-zero
. Create a chef-repo/config.rb
file with this content:
# configuration file for local mode chef
local_mode true
chef_zero.enabled true
config_log_level :error
node_name 'admin'
client_key File.expand_path(__dir__) + '/.chef/admin.pem'
cookbook_path [
File.expand_path(__dir__) + '/cookbooks',
]
knife[:chef_repo_path] = File.expand_path(__dir__)
knife[:ssh_user] = 'rocky'
knife[:ssh_identity_file] = '~/.ssh/id_ed25519'
knife[:ssh_timeout] = 10
knife[:use_sudo] = true
knife[:listen] = true
knife[:editor] = '/usr/bin/vim'
knife[:log_level] = 'error'
knife[:ssh_attribute] = 'knife_zero.host'
knife[:secret_file] = File.expand_path(__dir__) + '/.chef/encrypted_data_bag_secret'
knife[:encrypted_data_bag_secret] = File.expand_path(__dir__) + '/.chef/encrypted_data_bag_secret'
knife[:allowed_automatic_attributes] = %w(
fqdn
os
os_version
hostname
ipaddress
roles
recipes
ipaddress
platform
platform_version
cloud
cloud_v2
chef_packages
ohai_time
)
silence_deprecation_warnings %w(chef-18)
Some notes about this configuration:
-
Replace
knife[:ssh_user] = 'rocky'
with the SSH username you use to connect to servers. In this case I’m using therocky
username for the Rocky Linux distribution. This can also be replaced while running the knife CLI later. -
Replace
knife[:ssh_identity_file] = '~/.ssh/id_ed25519'
with the SSH key you use to connect to servers. This key should be valid for the username (in this example,rocky
) by allowing it inauthorized_keys
on the server.
Create the admin client. Inside the chef-repo
directory, run:
mkdir .chef
knife client create admin -f .chef/admin.pem
# a text editor will be shown, change the "admin" value to "true", save and exit
chmod 0640 .chef/admin.pem
Create the encrypted data bag secret. Inside the chef-repo
directory, run:
openssl rand -base64 512 | tr -d '\r\n' > .chef/encrypted_data_bag_secret
chmod 0640 .chef/encrypted_data_bag_secret
…And we’re ready to roll with everything configured.
Bootstrap new nodes
With the tools, repository and configuration ready, now it’s time to bootstrap a new node with Chef and use it. To do
this, you should have access to a server using the configured SSH username and key (in this article, rocky
and
~/.ssh/id_ed25519
. This remote user also needs to gain root using sudo in the new node.
With the IP address of the server you want to bootstrap, run:
knife zero bootstrap <ip> --node-name my-new-server.example.com -r 'recipe[testing]'
This command will:
-
Run a Chef local server with the contents of the local repository;
-
Make a bridge between this server and the new node;
-
Install the Chef and its tools (i.e.
chef-client
) inside the node; -
Run a
chef-client
with the specifiedrecipe[testing]
runlist. This is the testing cookbook we wrote earlier.
Sample output (yeah, in this sample I used Ubuntu :P):
Connecting to 192.168.124.101 using ssh
WARNING: Performing legacy client registration with the validation key at ...
WARNING: Remove the key file or remove the 'validation_key' configuration option from your config.rb (knife.rb) to use more secure user credentials for client registration.
Bootstrapping 192.168.124.101
[192.168.124.101] -----> Installing Chef Omnibus (stable/18)
downloading https://omnitruck.chef.io/chef/install.sh
to file /tmp/install.sh.4232/install.sh
trying wget...
[192.168.124.101] ubuntu 22.04 x86_64
Getting information for chef stable 18 for ubuntu...
downloading https://omnitruck.chef.io/stable/chef/metadata?v=18&p=ubuntu&pv=22.04&m=x86_64
to file /tmp/install.sh.4236/metadata.txt
[192.168.124.101] trying wget...
[192.168.124.101] sha1 f6037852e758977da9d23d87206348522a7506a0
sha256 57fd4c44ab37d9e311cbdd9dceeca73dc1ebf342b6cfb4e75632f0b7fcd4336d
url https://packages.chef.io/files/stable/chef/18.4.12/ubuntu/22.04/chef_18.4.12-1_amd64.deb
version 18.4.12
[192.168.124.101]
[192.168.124.101] downloaded metadata file looks valid...
[192.168.124.101] downloading https://packages.chef.io/files/stable/chef/18.4.12/ubuntu/22.04/chef_18.4.12-1_amd64.deb
to file /tmp/install.sh.4236/chef_18.4.12-1_amd64.deb
[192.168.124.101] trying wget...
[192.168.124.101] Comparing checksum with sha256sum...
[192.168.124.101] Installing chef 18
installing with dpkg...
[192.168.124.101] Selecting previously unselected package chef.
[192.168.124.101] (Reading database ... 64405 files and directories currently installed.)
[192.168.124.101] Preparing to unpack .../chef_18.4.12-1_amd64.deb ...
[192.168.124.101] Unpacking chef (18.4.12-1) ...
[192.168.124.101] Setting up chef (18.4.12-1) ...
[192.168.124.101] Thank you for installing Chef Infra Client! For help getting started visit https://learn.chef.io
[192.168.124.101] Starting the first Chef Infra Client Client run...
[192.168.124.101] +---------------------------------------------+
✔ 2 product licenses accepted.
+---------------------------------------------+
[192.168.124.101] Chef Infra Client, version 18.4.12
[192.168.124.101] Patents: https://www.chef.io/patents
[192.168.124.101] Infra Phase starting
[192.168.124.101] Creating a new client identity for my-new-server.example.com using the validator key.
[192.168.124.101] Resolving cookbooks for run list: ["testing"]
[192.168.124.101] Synchronizing cookbooks:
[192.168.124.101] - testing (0.1.0)
[192.168.124.101] Installing cookbook gem dependencies:
Compiling cookbooks...
[192.168.124.101] Loading Chef InSpec profile files:
[192.168.124.101] Loading Chef InSpec input files:
[192.168.124.101] Loading Chef InSpec waiver files:
[192.168.124.101] Converging 1 resources
[192.168.124.101] Recipe: testing::default
[192.168.124.101] * file[/tmp/testing] action create
[192.168.124.101] - create new file /tmp/testing
[192.168.124.101] - update content in file /tmp/testing from none to 7f7e25
[192.168.124.101] --- /tmp/testing 2024-06-01 13:03:50.901599562 +0000
[192.168.124.101] +++ /tmp/.chef-testing20240601-4323-slc8kl 2024-06-01 13:03:50.901599562 +0000
[192.168.124.101] @@ -1 +1,2 @@
[192.168.124.101] +this was created by chef for pgsql
[192.168.124.101] - change mode from '' to '0640'
[192.168.124.101] - change owner from '' to 'root'
[192.168.124.101] - change group from '' to 'root'
[192.168.124.101] Running handlers:
[192.168.124.101] Running handlers complete
[192.168.124.101] Infra Phase complete, 1/1 resources updated in 02 seconds
If we look in the server, our testing
recipe created the file /tmp/testing
:
cat /tmp/testing
Output:
this was created by chef for my-new-server
After the bootstrap is done, you don’t need to bootstrap again. The node was also created in the local repository with these two files:
clients/my-new-server.example.com.json
- the public key for this nodenodes/my-new-server.example.com.json
- the inventory for this node (generated by chef andohai
)
You can also use any knife
command in the repository root directory to manage this node, example:
knife node list
Output:
my-new-server.example.com
knife node show my-new-server.example.com
Output:
Node Name: my-new-server.example.com
Environment: _default
FQDN: my-new-server
IP: 192.168.124.101
Run List: recipe[testing]
Roles:
Recipes: testing, testing::default
Platform: ubuntu 22.04
Tags:
Managing nodes
Since we don’t need to bootrap the node, we can run the same recipe using knife zero converge
, like this:
knife zero converge 'name:my-new-server.example.com'
This will do almost the same thing as bootstraping, but it will not need to install the chef-client since it’s already installed.
The name:my-new-server.example.com
is a search string. In this example, I’m telling to run chef-client only on
the server named my-new-server.example.com
, but I can use any other search string:
# run on all nodes
knife zero converge 'name:*'
# run only on ubuntu servers:
knife zero converge 'platform:ubuntu'
# run only on production environment, centos servers:
knife zero converge 'chef_environment:production AND platform:centos'
# run only on servers running the webserver cookbook:
knife zero converge 'recipes:testing'
# and so on...
To check the search syntax, consult this documentation for knife search.
Conclusion and references
And that’s it! You can use chef and knife as you use in any other environment. You won’t need to always do
a knife cookbook upload
because every time you run knife zero
, it will read the current repository and
serve from that. That’s no upstream server to upload anything to.
Also, there’s no catch in writing cookbooks. Everything will just run as if you have a server. You don’t need to
programatically check for parameters like when you use chef-solo
or dry-run
. It will work out-of-the-box.
References: