Contributors20 minutesIn the previous part, you used knife to update your node's configuration. Many Chef users start out by using knife to update their servers' configuration on demand. You can then move to a continuous delivery system, such as Chef Automate, to release changes more often and with greater safety. A continuous delivery system might run chef-client on your nodes when an explicit change is made to your configuration policy, for example, when a change is committed to your version control system.
In addition to running chef-client when your configuration policy changes, you may also want to run chef-client periodically. One reason is to help ensure that your servers are free from configuration drift.
In this part, you'll use the chef-client cookbook to set up your node to run chef-client periodically. During the process, you'll learn how to:
- use community cookbooks from Chef Supermarket.
- use Berkshelf to resolve cookbook dependencies.
- use a role to define your node's attributes and its run-list.
This is the final part of this module, so you'll also learn how to optionally clean up your environment by removing your cookbook and node from the Chef server.
1. Get the chef-client cookbook
In this part, you'll get the chef-client cookbook from Chef Supermarket and upload the chef-client cookbook and its dependent cookbooks to your Chef server.
There are multiple ways to set up chef-client to run on a regular interval. On Linux nodes, you might use a daemon, cron job, or service. On Windows, you might use a scheduled task.
You can of course manually set chef-client to run on a regular interval. Another way is to use the chef-client cookbook from Chef Supermarket. Earlier in this module, you obtained starter code from GitHub. Chef Supermarket is also a place for the community to share cookbooks. You'll work more with community cookbooks in future modules.
On Linux, the chef-client cookbook sets up chef-client to run as a service. On Windows, this cookbook sets up chef-client to run as a scheduled task. One benefit to using the chef-client cookbook is that it works on multiple platforms.
There are several ways to obtain cookbooks from Chef Supermarket. One way is to use the knife supermarket command. However, the chef-client cookbook has dependencies on other cookbooks (you'll learn more about cookbook dependencies in a later module), and knife supermarket does not resolve these dependencies for you.
Berkshelf is a tool that helps you resolve cookbook dependencies. Berkshelf can retrieve the cookbooks that your cookbook depends on and can upload your cookbooks to your Chef server. Berkshelf comes with Chef Workstation.
To get started, first ensure you're in the ~/learn-chef directory.
Next, you need to create a configuration file that tells Berkshelf which cookbooks you want and where they're located. From your ~/learn-chef directory, create a file named Berksfile and add these contents.
Editor: ~/learn-chef/Berksfile
1
2
| source 'https://supermarket.chef.io'
cookbook 'chef-client' |
Chef provides a public Chef Supermarket site at https://supermarket.chef.io. You can also manage your own private Chef Supermarket server. Your Berksfile specifies that you want the chef-client cookbook and to pull cookbooks from the public Chef Supermarket server.
The next step is to run berks install to download the chef-client cookbook and its dependencies.
Terminal: ~/learn-chef
$ | berks installResolving cookbook dependencies...Fetching cookbook index from https://supermarket.chef.io...Installing chef-client (10.0.4)Installing cron (5.1.0)Installing logrotate (2.2.0)Installing windows (4.2.2)
|
Berkshelf downloads the chef-client cookbook and its dependent cookbooks to the ~/.berkshelf/cookbooks directory.
Terminal: ~/learn-chef
$ | ls ~/.berkshelf/cookbookschef-client-10.0.4cron-5.1.0logrotate-2.2.0windows-4.2.2
|
Next, you need to upload the chef-client cookbook and its dependencies to your Chef server.
Previously, you ran knife cookbook upload to upload your learn_chef_apache2 cookbook to the Chef server. Remember that the chef-client cookbook has dependencies on other cookbooks, so you need a way to upload everything.
You could run knife cookbook upload to manually upload each cookbook. An easier way is to run berks upload. Like berks install, berks upload handles dependencies for you.
Run berks upload to upload the chef-client cookbook and its dependencies to Chef server.
Terminal: ~/learn-chef
$ | berks uploadUploaded chef-client (10.0.4) to: 'https://api.chef.io:443/organizations/learn-chef-2'Uploaded cron (5.1.0) to: 'https://api.chef.io:443/organizations/learn-chef-2'Uploaded logrotate (2.2.0) to: 'https://api.chef.io:443/organizations/learn-chef-2'Uploaded windows (4.2.2) to: 'https://api.chef.io:443/organizations/learn-chef-2'
|
2. Create a role
Now that the chef-client cookbook is on your Chef server, you need to update your node's run-list to use it. You also need to specify how often to run chef-client. In this part, you'll use a role to define both.
How often chef-client is run is controlled by two node attributes (source code):
node['chef_client']['interval'] – interval specifies the number of seconds between chef-client runs. The default value is 1,800 (30 minutes).node['chef_client']['splay'] – splay specifies a maximum random number of seconds that is added to the interval. Splay helps balance the load on the Chef server by ensuring that many chef-client runs are not occurring at the same interval. The default value is 300 (5 minutes).
By default, chef-client will run every 30—35 minutes on your node. In practice, the values you choose depend on your requirements. For learning purposes, you'll specify an interval of 5 minutes (300 seconds) and a splay of 1 minute (60 seconds), causing your node to check in every 5—6 minutes.
To update your node's run-list, you could use the knife node run_list set command. However, that does not set the appropriate node attributes.
To accomplish both tasks, you'll use a role. Roles enable you to focus on the function your node performs collectively rather than each of its individual components (its run-list, node attributes, and so on). For example, you might have a web server role, a database role, or a load balancer role. Here, you'll create a role named web to define your node's function as a web server.
Roles are stored as objects on the Chef server. To create a role, you can use the knife role create command. Another common way is to create a file (in JSON format) that describes your role and then run the knife role from file command to upload that file to the Chef server. The advantage of creating a file is that you can store that file in a version control system such as Git.
First, ensure you have a directory named ~/learn-chef/roles.
Terminal: ~/learn-chef
$ | mkdir ~/learn-chef/roles
|
Now add the following to a file named ~/learn-chef/roles/web.json.
Editor: ~/learn-chef/roles/web.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| {
"name": "web",
"description": "Web server role.",
"json_class": "Chef::Role",
"default_attributes": {
"chef_client": {
"interval": 300,
"splay": 60
}
},
"override_attributes": {
},
"chef_type": "role",
"run_list": ["recipe[chef-client::default]",
"recipe[chef-client::delete_validation]",
"recipe[learn_chef_apache2::default]"
],
"env_run_lists": {
}
} |
This file defines the web role. It sets the required interval and splay attributes and sets the run-list to contain the chef-client cookbook as well as the learn_chef_apache2 cookbook.
As a recommended practice, the run-list also contains the chef-client::delete_validation recipe (source code). This recipe deletes the validation certificate (for example, /etc/chef/validation.pem) from your node. This certificate is used during the bootstrap process to authorize the node to connect to the Chef server, and is no longer needed.
Next, run the following knife role from file command to upload your role to the Chef server.
Terminal: ~/learn-chef
$ | knife role from file roles/web.jsonUpdated Role web
|
As a verification step, you can run knife role list to view the roles on your Chef server.
You can also run knife role show web to view the role's details.
Terminal: ~/learn-chef
$ | knife role show webchef_type: roledefault_attributes: chef_client: interval: 300 splay: 60description: Web server role.env_run_lists:json_class: Chef::Rolename: weboverride_attributes:run_list: recipe[chef-client::default] recipe[chef-client::delete_validation] recipe[learn_chef_apache2::default]
|
The final step is to set your node's run-list. Run the following knife node run_list set command to do that.
Terminal: ~/learn-chef
$ | knife node run_list set node1-ubuntu "role[web]"node1-ubuntu: run_list: role[web]
|
As a verification step, you can run the knife node show command to view your node's run-list.
Terminal: ~/learn-chef
$ | knife node show node1-ubuntu --run-listnode1-ubuntu: run_list: role[web]
|
You're now ready to run chef-client on your node.
3. Run chef-client
As before, run knife ssh to trigger chef-client to run on your node. This time, replace the search query 'name:node1-ubuntu' with 'role:web'. If you had multiple nodes with the web role, chef-client would run on each of them.
This example shows key-based authentication. Choose the method you used earlier.
Terminal: ~/learn-chef
$ | knife ssh 'role:web' 'sudo chef-client' --ssh-user vagrant --identity-file ~/.ssh/private_key --attribute ipaddress192.168.145.131 Starting Chef Client, version 13.8.5192.168.145.131 resolving cookbooks for run list: ["chef-client::default", "chef-client::delete_validation", "learn_chef_apache2::default"]192.168.145.131 Synchronizing Cookbooks:192.168.145.131 - cron (5.1.0)192.168.145.131 - logrotate (2.2.0)192.168.145.131 - learn_chef_apache2 (0.3.1)192.168.145.131 - windows (4.2.2)192.168.145.131 - chef-client (10.0.4)192.168.145.131 Installing Cookbook Gems:192.168.145.131 Compiling Cookbooks...192.168.145.131 Converging 15 resources192.168.145.131 Recipe: chef-client::init_service192.168.145.131 * directory[/var/run/chef] action create192.168.145.131 - create new directory /var/run/chef192.168.145.131 - change owner from '' to 'root'192.168.145.131 - change group from '' to 'root'192.168.145.131 * directory[/var/cache/chef] action create192.168.145.131 - create new directory /var/cache/chef192.168.145.131 - change owner from '' to 'root'192.168.145.131 - change group from '' to 'root'192.168.145.131 * directory[/var/lib/chef] action create192.168.145.131 - create new directory /var/lib/chef192.168.145.131 - change owner from '' to 'root'192.168.145.131 - change group from '' to 'root'192.168.145.131 * directory[/var/log/chef] action create192.168.145.131 - create new directory /var/log/chef192.168.145.131 - change mode from '' to '0755'192.168.145.131 - change owner from '' to 'root'192.168.145.131 - change group from '' to 'root'192.168.145.131 * directory[/etc/chef] action create (up to date)192.168.145.131 * template[/etc/init.d/chef-client] action create192.168.145.131 - create new file /etc/init.d/chef-client192.168.145.131 - update content in file /etc/init.d/chef-client from none to fee506192.168.145.131 --- /etc/init.d/chef-client 2018-05-01 21:22:54.523227443 +0000192.168.145.131 +++ /etc/init.d/.chef-chef-client20180501-13798-1jb5lze 2018-05-01 21:22:54.523227443 +0000192.168.145.131 @@ -1 +1,204 @@192.168.145.131 +#! /bin/sh192.168.145.131 +### BEGIN INIT INFO192.168.145.131 +# Provides: chef-client192.168.145.131 +# Required-Start: $remote_fs $network192.168.145.131 +# Required-Stop: $remote_fs $network192.168.145.131 +# Default-Start: 2 3 4 5192.168.145.131 +# Default-Stop: 0 1 6192.168.145.131 +# Short-Description: Start a chef-client.192.168.145.131 +### END INIT INFO192.168.145.131 +#192.168.145.131 +# Copyright (c) 2009-2010 Chef Software, Inc, <legal@chef.io>192.168.145.131 +#192.168.145.131 +# chef-client Startup script for chef-client.192.168.145.131 +# chkconfig: - 99 02192.168.145.131 +# description: starts up chef-client in daemon mode.192.168.145.131 +192.168.145.131 +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin192.168.145.131 +DAEMON=/usr/bin/chef-client192.168.145.131 +NAME=chef-client192.168.145.131 +DESC=chef-client192.168.145.131 +PIDFILE=/var/run/chef/client.pid192.168.145.131 +192.168.145.131 +test -x $DAEMON || exit 1192.168.145.131 +192.168.145.131 +. /lib/lsb/init-functions192.168.145.131 +192.168.145.131 +[ -r /etc/default/$NAME ] && . /etc/default/$NAME192.168.145.131 +192.168.145.131 +if [ ! -d /var/run/chef ]; then192.168.145.131 + mkdir /var/run/chef192.168.145.131 +fi192.168.145.131 +192.168.145.131 +DAEMON_OPTS="-d -P $PIDFILE -c $CONFIG -i $INTERVAL -s $SPLAY "192.168.145.131 +192.168.145.131 +running_pid() {192.168.145.131 + pid=$1192.168.145.131 + name=$2192.168.145.131 + [ -z "$pid" ] && return 1192.168.145.131 + [ ! -d /proc/$pid ] && return 1192.168.145.131 + cat /proc/$pid/cmdline | tr '\000' ' ' | grep -q "$name"192.168.145.131 +}192.168.145.131 +192.168.145.131 +running() {192.168.145.131 + [ ! -f "$PIDFILE" ] && return 1192.168.145.131 + pid=`cat $PIDFILE`192.168.145.131 + running_pid $pid $DAEMON || return 1192.168.145.131 + return 0192.168.145.131 +}192.168.145.131 +192.168.145.131 +start_server() {192.168.145.131 + if [ -z "$DAEMONUSER" ] ; then192.168.145.131 + start_daemon -p $PIDFILE $DAEMON $DAEMON_OPTS192.168.145.131 + errcode=$?192.168.145.131 + else192.168.145.131 + start-stop-daemon --start --quiet --pidfile $PIDFILE \192.168.145.131 + --chuid $DAEMONUSER \192.168.145.131 + --exec $DAEMON -- $DAEMON_OPTS192.168.145.131 + errcode=$?192.168.145.131 + fi192.168.145.131 + return $errcode192.168.145.131 +}192.168.145.131 +192.168.145.131 +stop_server() {192.168.145.131 + if [ -z "$DAEMONUSER" ] ; then192.168.145.131 + killproc -p $PIDFILE $DAEMON192.168.145.131 + errcode=$?192.168.145.131 + else192.168.145.131 + start-stop-daemon --stop --quiet --pidfile $PIDFILE \192.168.145.131 + --user $DAEMONUSER \192.168.145.131 + --exec $DAEMON192.168.145.131 + errcode=$?192.168.145.131 + fi192.168.145.131 + return $errcode192.168.145.131 +}192.168.145.131 +192.168.145.131 +reload_server() {192.168.145.131 + if [ -z "$DAEMONUSER" ] ; then192.168.145.131 + killproc -p $PIDFILE $DAEMON -HUP192.168.145.131 + errcode=$?192.168.145.131 + else192.168.145.131 + start-stop-daemon --stop --signal HUP --quiet --pidfile $PIDFILE \192.168.145.131 + --user $DAEMONUSER \192.168.145.131 + --exec $DAEMON192.168.145.131 + errcode=$?192.168.145.131 + fi192.168.145.131 + return $errcode192.168.145.131 +}192.168.145.131 +192.168.145.131 +run_server() {192.168.145.131 + if [ -z "$DAEMONUSER" ] ; then192.168.145.131 + killproc -p $PIDFILE $DAEMON -USR1192.168.145.131 + errcode=$?192.168.145.131 + else192.168.145.131 + start-stop-daemon --stop --signal USR1 --quiet --pidfile $PIDFILE \192.168.145.131 + --user $DAEMONUSER \192.168.145.131 + --exec $DAEMON192.168.145.131 + errcode=$?192.168.145.131 + fi192.168.145.131 + return $errcode192.168.145.131 +}192.168.145.131 +192.168.145.131 +force_stop() {192.168.145.131 + [ ! -e "$PIDFILE" ] && return192.168.145.131 + if running ; then192.168.145.131 + /bin/kill -15 $pid192.168.145.131 + sleep "$DIETIME"s192.168.145.131 + if running ; then192.168.145.131 + /bin/kill -9 $pid192.168.145.131 + sleep "$DIETIME"s192.168.145.131 + if running ; then192.168.145.131 + echo "Cannot kill $NAME (pid=$pid)!"192.168.145.131 + exit 1192.168.145.131 + fi192.168.145.131 + fi192.168.145.131 + fi192.168.145.131 + rm -f $PIDFILE192.168.145.131 +}192.168.145.131 +192.168.145.131 +case "$1" in192.168.145.131 + start)192.168.145.131 + log_daemon_msg "Starting $DESC " "$NAME"192.168.145.131 + if running ; then192.168.145.131 + log_progress_msg "apparently already running"192.168.145.131 + log_end_msg 0192.168.145.131 + exit 0192.168.145.131 + fi192.168.145.131 + if start_server ; then192.168.145.131 + [ -n "$STARTTIME" ] && sleep $STARTTIME # Wait some time192.168.145.131 + if running ; then192.168.145.131 + log_end_msg 0192.168.145.131 + else192.168.145.131 + log_end_msg 1192.168.145.131 + fi192.168.145.131 + else192.168.145.131 + log_end_msg 1192.168.145.131 + fi192.168.145.131 + ;;192.168.145.131 + stop)192.168.145.131 + log_daemon_msg "Stopping $DESC" "$NAME"192.168.145.131 + if running ; then192.168.145.131 + errcode=0192.168.145.131 + stop_server || errcode=$?192.168.145.131 + log_end_msg $errcode192.168.145.131 + else192.168.145.131 + log_progress_msg "apparently not running"192.168.145.131 + log_end_msg 0192.168.145.131 + exit 0192.168.145.131 + fi192.168.145.131 + ;;192.168.145.131 + force-stop)192.168.145.131 + $0 stop192.168.145.131 + if running; then192.168.145.131 + log_daemon_msg "Stopping (force) $DESC" "$NAME"192.168.145.131 + errcode=0192.168.145.131 + force_stop || errcode=$?192.168.145.131 + log_end_msg $errcode192.168.145.131 + fi192.168.145.131 + ;;192.168.145.131 + restart|force-reload)192.168.145.131 + log_daemon_msg "Restarting $DESC" "$NAME"192.168.145.131 + errcode=0192.168.145.131 + stop_server || errcode=$?192.168.145.131 + [ -n "$DIETIME" ] && sleep $DIETIME192.168.145.131 + start_server || errcode=$?192.168.145.131 + [ -n "$STARTTIME" ] && sleep $STARTTIME192.168.145.131 + running || errcode=$?192.168.145.131 + log_end_msg $errcode192.168.145.131 + ;;192.168.145.131 + status)192.168.145.131 + log_daemon_msg "Checking status of $DESC" "$NAME"192.168.145.131 + if running ; then192.168.145.131 + log_progress_msg "running"192.168.145.131 + log_end_msg 0192.168.145.131 + else192.168.145.131 + log_progress_msg "apparently not running"192.168.145.131 + log_end_msg 1192.168.145.131 + exit 3192.168.145.131 + fi192.168.145.131 + ;;192.168.145.131 + reload)192.168.145.131 + if running; then192.168.145.131 + log_daemon_msg "Reloading $DESC" "$NAME"192.168.145.131 + errcode=0192.168.145.131 + reload_server || errcode=$?192.168.145.131 + log_end_msg $errcode192.168.145.131 + fi192.168.145.131 + ;;192.168.145.131 + run)192.168.145.131 + if running; then192.168.145.131 + log_daemon_msg "Triggering run of $DESC" "$NAME"192.168.145.131 + errcode=0192.168.145.131 + run_server || errcode=$?192.168.145.131 + log_end_msg $errcode192.168.145.131 + fi192.168.145.131 + ;;192.168.145.131 + *)192.168.145.131 + N=/etc/init.d/$NAME192.168.145.131 + echo "Usage: $N {start|stop|force-stop|restart|force-reload|status|run}" >&2192.168.145.131 + exit 1192.168.145.131 + ;;192.168.145.131 +esac192.168.145.131 +192.168.145.131 +exit 0192.168.145.131 - change mode from '' to '0755'192.168.145.131 * template[/etc/default/chef-client] action create192.168.145.131 - create new file /etc/default/chef-client192.168.145.131 - update content in file /etc/default/chef-client from none to d443f5192.168.145.131 --- /etc/default/chef-client 2018-05-01 21:22:54.543225505 +0000192.168.145.131 +++ /etc/default/.chef-chef-client20180501-13798-1uv9x0e 2018-05-01 21:22:54.543225505 +0000192.168.145.131 @@ -1 +1,4 @@192.168.145.131 +CONFIG=/etc/chef/client.rb192.168.145.131 +INTERVAL=300192.168.145.131 +SPLAY=60192.168.145.131 - change mode from '' to '0644'192.168.145.131 * service[chef-client] action enable192.168.145.131 - enable service service[chef-client]192.168.145.131 * service[chef-client] action start192.168.145.131 - start service service[chef-client]192.168.145.131 Recipe: chef-client::delete_validation192.168.145.131 * file[/etc/chef/validation.pem] action delete (up to date)192.168.145.131 Recipe: learn_chef_apache2::default192.168.145.131 * apt_update[Update the apt cache daily] action periodic (up to date)192.168.145.131 * apt_package[apache2] action install (up to date)192.168.145.131 * service[apache2] action enable (up to date)192.168.145.131 * service[apache2] action start (up to date)192.168.145.131 * group[web_admin] action create (up to date)192.168.145.131 * linux_user[web_admin] action create (up to date)192.168.145.131 * template[/var/www/html/index.html] action create (up to date)192.168.145.131 Recipe: chef-client::init_service192.168.145.131 * service[chef-client] action restart192.168.145.131 - restart service service[chef-client]192.168.145.131 192.168.145.131 Running handlers:192.168.145.131 Running handlers complete192.168.145.131 Chef Client finished, 9/18 resources updated in 12 seconds
|
You can see from the output that the chef-client cookbook set up chef-client as a service on your node.
You can run the knife status command to display a brief summary of the nodes on your Chef server, including the time of the most recent successful chef-client run.
Terminal: ~/learn-chef
$ | knife status 'role:web' --run-list1 minute ago, node1-ubuntu, ["role[web]"], ubuntu 14.04.
|
Every 5–6 minutes you'll see that your node performed a recent check-in with the Chef server and ran chef-client.
4. Next steps
Now that chef-client is set up to run every 5—6 minutes, now's a great time to experiment with your node. Here are some ideas to start with.
- Repeat the steps where you make a change to the
learn_chef_apache2 cookbook, bump its version in the metadata, and upload your changes to Chef server. Even a small change to the home page template, index.html.erb, is enough to practice the process. Watch your change appear in your web browser the next time chef-client runs. - Manually log in to your node and stop the Apache service or delete the home page,
/var/www/html/index.html. Refresh your browser window or run curl to see that your web server is down. What do you expect to happen the next time chef-client runs? - Write a cookbook that configures a piece of software that you use. See if there are any cookbooks on Chef Supermarket that you can use to get started.
If you're interested in managing your Chef server, check out the knife opc command. knife opc enables you to manage organizations and users in Chef server from your workstation.
How to clean up your environment
You can continue to experiment with your Chef server and your node. When you're done, you can perform these optional steps if you want to clean up your Chef server or you want to repeat the module from the beginning.
Delete the node from the Chef server
As you experiment, it's a good idea to delete information about your node from the Chef server when you no longer need it. That way, your Chef server contains only relevant information. In practice, it's up to you whether to delete node information when you retire a production system from service.
From your workstation, run these commands to delete the data about your node from the Chef server.
Terminal: ~/learn-chef
$ | knife node delete node1-ubuntu --yesDeleted node[node1-ubuntu]
|
Terminal: ~/learn-chef
$ | knife client delete node1-ubuntu --yesDeleted client[node1-ubuntu]
|
Chef makes a distinction between the nodes that are being managed and the clients that are authorized to make API calls to the Chef server. Therefore, you need to run knife node delete to remove the node's metadata from the Chef server and knife client delete to delete the entry (including the public part of the RSA key pair) from the Chef server's API client list.
Delete your cookbook from the Chef server
Here's how to delete the learn_chef_apache2 cookbook from the Chef server.
Terminal: ~/learn-chef
$ | knife cookbook delete learn_chef_apache2 --all --yesDeleted cookbook[learn_chef_apache2][0.3.1]Deleted cookbook[learn_chef_apache2][0.3.0]Deleted cookbook[learn_chef_apache2][0.2.0]Deleted cookbook[learn_chef_apache2][0.1.0]
|
If you omit the --all argument, you'll be prompted to select which version to delete. In practice, you might delete version '0.3.0' of the learn_chef_apache2 cookbook because you know it contains a configuration policy that will always fail.
Delete the role from the Chef server
Here's how to delete the web role from the Chef server.
Terminal: ~/learn-chef
$ | knife role delete web --yesDeleted role[web]
|
Delete the RSA private key from your node
During the bootstrap process, an RSA private key is generated on your node to enable your node to make API calls to the Chef server. The default location of this key is /etc/chef/client.pem on Linux systems.
If you plan to bootstrap your node a second time, for example, to practice the process, you'll need to log in to your node and delete the RSA private key file, like this.
Terminal: ~
$ | sudo rm /etc/chef/client.pem
|
Tear down your instance
Deleting a node from your Chef server removes any data about that node from the server – it doesn't automatically tear down the instance.
Don't forget to tear down any cloud instances or local virtual machines that you used to complete the module.
Conclusion
In this module, you brought up a Chef server, bootstrapped a node, and applied a basic web server configuration. You also practiced updating your cookbook, uploading it to the Chef server, and seeing the changes appear on your node. As a bonus, you resolved an error in your configuration and set up chef-client to run periodically.
To update your cookbook you used a template. A template enables you to write a single, general recipe that's customized for a particular node as the recipe runs. That means you don’t have to write a custom version of your recipe for every node.
You also ran knife ssh to update your node. knife ssh invokes the command you specify over an SSH connection on a node – in our case sudo chef-client. You didn't have to specify the run-list because you already set that up when you bootstrapped the node. Search enables you to run chef-client on multiple nodes at once. A role enables you define your node's behavior and attributes based on its function.
That's it for this module. When you're done experimenting, be sure to clean up your environment.
Be sure to check out Get started with Test Kitchen, where you'll learn how local development with Test Kitchen can help you iterate faster and correct mistakes earlier in the development process. With local development, you verify your cookbooks on local test instances that resemble production before you apply your work to a bootstrapped node.