第一节 Creating My First Chef Cookbook
Cookbooks are one of the key components in Chef.
They describe the desired state of your nodes, and allow Chef to push out the changes needed to achieve this state. Creating a cookbook can seem like an arduous task at first, given the sheer number of options provided and areas to configure, so in this guide we will walk through the creation of one of the first things people often learn to configure:
A LAMP stack.
1、Bootstap a new machine as new node
https://docs.chef.io/install_bootstrap.html
$ knife bootstrap 123.45.6.789 -x username -P password --sudo
$ knife bootstrap 192.168.33.14 -x vagrant -P vagrant --sudo
ERROR: You must pass a node name with -N when bootstrapping with user credentials
$ knife bootstrap 192.168.33.14 -x vagrant -P vagrant --sudo --use-sudo-password --node-name node2
Creating new client for node2
Creating new node for node2
Connecting to 192.168.33.14
192.168.33.14 -----> Existing Chef installation detected
192.168.33.14 Starting the first Chef Client run...
192.168.33.14 YAML safe loading is not available. Please upgrade psych to a version that supports safe loading (>= 2.0).
192.168.33.14 Starting Chef Client, version 11.8.2
192.168.33.14
192.168.33.14 ================================================================================
192.168.33.14 Chef encountered an error attempting to load the node data for "node2"
192.168.33.14 ================================================================================
192.168.33.14
192.168.33.14
192.168.33.14 Networking Error:
192.168.33.14 -----------------
192.168.33.14 Error connecting to https://chefserver/organizations/devops-jxi/nodes/node2 - getaddrinfo: Name or service not known
192.168.33.14
192.168.33.14 Your chef_server_url may be misconfigured, or the network could be down.
192.168.33.14
192.168.33.14
192.168.33.14
192.168.33.14 Relevant Config Settings:
192.168.33.14 -------------------------
192.168.33.14 chef_server_url "https://chefserver/organizations/devops-jxi"
192.168.33.14
192.168.33.14
192.168.33.14
192.168.33.14 [2018-08-17T08:48:43+00:00] FATAL: Stacktrace dumped to /var/chef/cache/chef-stacktrace.out
192.168.33.14 Chef Client failed. 0 resources updated
192.168.33.14 [2018-08-17T08:48:43+00:00] ERROR: Error connecting to https://chefserver/organizations/devops-jxi/nodes/node2 - getaddrinfo: Name or service not known
192.168.33.14 [2018-08-17T08:48:43+00:00] FATAL: Chef::Exceptions::ChildConvergeError: Chef run process exited unsuccessfully (exit code 1)
On the new node machine 192.168.33.14
$ sudo vi /etc/hosts
192.168.33.19 chefserver chefserver
Try it again
$ knife bootstrap 192.168.33.14 -x vagrant -P vagrant --sudo --use-sudo-password --node-name node2
Node node2 exists, overwrite it? (Y/N) y
Client node2 exists, overwrite it? (Y/N) y
Creating new client for node2
Creating new node for node2
Connecting to 192.168.33.14
192.168.33.14 -----> Existing Chef installation detected
192.168.33.14 Starting the first Chef Client run...
192.168.33.14 YAML safe loading is not available. Please upgrade psych to a version that supports safe loading (>= 2.0).
192.168.33.14 Starting Chef Client, version 11.8.2
192.168.33.14 resolving cookbooks for run list: []
192.168.33.14 Synchronizing Cookbooks:
192.168.33.14 Compiling Cookbooks...
192.168.33.14 [2018-08-17T08:50:28+00:00] WARN: Node node2 has an empty run list.
192.168.33.14 Converging 0 resources
192.168.33.14 Chef Client finished, 0 resources updated
$ knife client list
devops-jxi-validator
node1
node2
$ knife client show node2
admin: false
chef_type: client
name: node2
validator: false
2、Create the Cookbook
1.From your workstation, move to your cookbooks
directory in chef-repo
:
cd chef-repo/cookbooks
2.Create the cookbook. In this instance the cookbook is titled lamp_stack
:
chef generate cookbook lamp_stack
3.List the files located in the newly-created cookbook to see that a number of directories and files have been created:
$ cd lamp_stack/
$ ls
Berksfile CHANGELOG.md chefignore LICENSE metadata.rb README.md recipes spec test
3、default.rb
The default.rb
file in recipes
contains the “default” recipe resources.
Because each section of the LAMP stack (Apache, MySQL, and PHP) will have its own recipe, the default.rb
file is used to prepare your servers.
From within your lamp_stack
directory, navigate to the recipes folder:
cd recipes
Open default.rb
and add the Ruby command below, which will run system updates:
execute "update-upgrade" do
command "sudo apt-get update && sudo apt-get upgrade -y"
action :run
end
https://docs.chef.io/resource_execute.html
Recipes are comprised of a series of resources. In this case, the execute resource is used, which calls for a command to be executed once. The apt-get update && apt-get upgrade -y
commands are defined in the command
section, and the action
is set to :run
the commands.
This is one of the simpler Chef recipes to write, and a good way to start out. Any other startup procedures that you deem important can be added to the file by mimicking the above code pattern.
3.To test the recipe, add the LAMP stack cookbook to the Chef server:
$ sudo knife cookbook upload lamp_stack
Uploading lamp_stack [0.1.0]
Uploaded 1 cookbook.
4.Test that the recipe has been added to the chef server:
knife cookbook lists
5.Add the recipe to your chosen node’s run list, replacing nodename
with your node’s name:
knife node run_list add node2 "recipe[lamp_stack]"
node2:
run_list: recipe[lamp_stack]
Because this is the default recipe, the recipe name does not need to be defined after lamp_stack
cookbook in the code above.
6.Access your chosen node and run the chef-client
:
4、Apache
4-1 Install and Enable
1.In your Chef workstation, Create a new file under the ~/chef-repo/cookbooks/lamp_stack/recipes
directory called apache.rb
. This will contain all of your Apache configuration information.
2.Open the file, and define the package resource
to install Apache:
package "apache2" do
action :install
end
Again, this is a very basic recipe. The package resource calls to a package (apache2
). This value must be a legitimate package name. The action is install because Apache is being installed in this step. There is no need for additional values to run the install.
3.Set Apache to enable and start at reboot. In the same file, add the additional lines of code:
service "apache2" do
action [:enable, :start]
end
This uses the service resource, which calls on the Apache service. The enable
action enables it upon startup, and start
starts Apache.
Save and close the apache.rb
file.
4.To test the Apache recipe, update the LAMP Stack recipe on the server:
sudo knife cookbook upload lamp_stack
5.Add the recipe to a node’s run-list, replacing nodename
with your chosen node’s name:
knife node run_list add node2 "recipe[lamp_stack::apache]"
node2:
run_list:
recipe[lamp_stack]
recipe[lamp_stack::apache]
Because this is not the default.rb
recipe, the recipe name, apache
, must be appended to the recipe value.
6.From that node, run chef-client
:
sudo chef-client
YAML safe loading is not available. Please upgrade psych to a version that supports safe loading (>= 2.0).
Starting Chef Client, version 11.8.2
resolving cookbooks for run list: ["lamp_stack", "lamp_stack::apache"]
Synchronizing Cookbooks:
- lamp_stack
Compiling Cookbooks...
Converging 3 resources
Recipe: lamp_stack::default
* execute[update-upgrade] action run
- execute sudo apt-get update && sudo apt-get upgrade -y
Recipe: lamp_stack::apache
* package[apache2] action install
- install version 2.4.7-1ubuntu4.20 of package apache2
* service[apache2] action enable
- enable service service[apache2]
* service[apache2] action start (up to date)
Chef Client finished, 3 resources updated
If the recipe fails due to a syntax error, Chef will note it during the output.
7.After a successful chef-client
run, check to see if Apache is running:
$ service apache2 status
* apache2 is running
It should say that apache2
is running.
5、Configure Virtual Hosts
1.Because multiple websites may need to be configured, use Chef’s attributes feature to define certain aspects of the virtual hosts file(s). The ChefDK has a built-in command to generate the attributes directory and default.rb
file within a cookbook. Replace ~/chef-repo/cookbooks/lamp_stack
with your cookbook’s path:
$ chef generate attribute ~/chef-repo/cookbooks/lamp_stack default
* directory[/home/vagrant/chef-repo/cookbooks/lamp_stack/attributes] action create
- create new directory /home/vagrant/chef-repo/cookbooks/lamp_stack/attributes
- restore selinux security context
* template[/home/vagrant/chef-repo/cookbooks/lamp_stack/attributes/default.rb] action create
- create new file /home/vagrant/chef-repo/cookbooks/lamp_stack/attributes/default.rb
- update content in file /home/vagrant/chef-repo/cookbooks/lamp_stack/attributes/default.rb from none to e3b0c4
(diff output suppressed by config)
- restore selinux security context
2.Within the new default.rb
, create the default values of the cookbook:
~/chef-repo/cookbooks/lamp_stack/attributes/default.rb
default["lamp_stack"]["sites"]["example.com"] = { "port" => 80, "servername" => "cheftest.com", "serveradmin" => "cheftest@example.com" }
The prefix default
defines that these are the normal values to be used in the lamp_stack
where the site cheftest.com
will be called upon. This can be seen as a hierarchy: Under the cookbook itself are the site(s), which are then defined by their URL.
The following values in the array (defined by curly brackets ({}
)) are the values that will be used to configure the virtual hosts file. Apache will be set to listen on port 80 and use the listed values for its server name, and administrator email.
$ cd ~/chef-repo/cookbooks/lamp_stack/attributes/default.rb
default["lamp_stack"]["sites"]["example.com"] = { "port" => 80, "servername" => "example.com", "serveradmin" => "webmaster@example.com" }
default["lamp_stack"]["sites"]["example.org"] = { "port" => 80, "servername" => "example.org", "serveradmin" => "webmaster@example.org" }
3.Return to your apache.rb
file under recipes
to call the attributes that were just defined. Do this with the node
resource:
#Install & enable Apache
package "apache2" do
action :install
end
service "apache2" do
action [:enable, :start]
end
# Virtual Hosts Files
node["lamp_stack"]["sites"].each do |sitename, data|
end
This calls in the values under ["lamp_stack"]["sites"]
.
Code added to this block will be generated for each value, which is defined by the word sitename
.
The data value calls the values that are listed in the array of each sitename
attribute.
4.Within the node
resource, define a document root. This root will be used to define the public HTML files, and any log files that will be generated:
node["lamp_stack"]["sites"].each do |sitename, data|
document_root = "/var/www/html/#{sitename}"
end
5.However, this does not create the directory itself. To do so, the directory
resource should be used, with a true
recursive value so all directories leading up to the sitename
will be created. A permissions value of 0755
allows for the file owner to have full access to the directory, while group and regular users will have read and execute privileges:
$ vi ~/chef-repo/cookbooks/lamp_stack/apache.rb
node["lamp_stack"]["sites"].each do |sitename, data|
document_root = "/var/www/html/#{sitename}"
directory document_root do
mode "0755"
recursive true
end
end
6.The template feature will be used to generate the needed virtual hosts files. Within the chef-repo
directory run the chef generate template
command with the path to your cookbook and template file name defined:
$ chef generate template ~/chef-repo/cookbooks/lamp_stack virtualhosts
Recipe: code_generator::template
* directory[/home/vagrant/chef-repo/cookbooks/lamp_stack/templates] action create
- create new directory /home/vagrant/chef-repo/cookbooks/lamp_stack/templates
- restore selinux security context
* template[/home/vagrant/chef-repo/cookbooks/lamp_stack/templates/virtualhosts.erb] action create
- create new file /home/vagrant/chef-repo/cookbooks/lamp_stack/templates/virtualhosts.erb
- update content in file /home/vagrant/chef-repo/cookbooks/lamp_stack/templates/virtualhosts.erb from none to e3b0c4
(diff output suppressed by config)
- restore selinux security context
7.Open and edit the virtualhosts.erb
file. Instead of writing in the true values for each VirtualHost parameter, use Ruby variables. Ruby variables are identified by the <%= @variable_name %>
syntax. The variable names you use will need to be defined in the recipe file:
$ vi ~/chef-repo/cookbooks/lamp_stack/templates/default/virtualhosts.erb
<VirtualHost *:<%= @port %>>
ServerAdmin <%= @serveradmin %>
ServerName <%= @servername %>
ServerAlias www.<%= @servername %>
DocumentRoot <%= @document_root %>/public_html
ErrorLog <%= @document_root %>/logs/error.log
<Directory <%= @document_root %>/public_html>
Require all granted
</Directory>
</VirtualHost>
Some variables should look familiar. They were created in Step 2, when naming default attributes.
$ vi ~/chef-repo/cookbooks/lamp_stack/recipes/apache.rb
#Virtual Hosts Files
node["lamp_stack"]["sites"].each do |sitename, data|
document_root = "/var/www/html/#{sitename}"
directory document_root do
mode "0755"
recursive true
end
template "/etc/apache2/sites-available/#{sitename}.conf" do
source "virtualhosts.erb"
mode "0644"
variables(
:document_root => document_root,
:port => data["port"],
:serveradmin => data["serveradmin"],
:servername => data["servername"]
)
end
end
The name of the template resource should be the location where the virtual host file is placed on the nodes. The source
is the name of the template file. Mode 0644
gives the file owner read and write privileges, and everyone else read privileges. The values defined in the variables section are taken from the attributes file, and they are the same values that are called upon in the template.
9.The sites now need to be enabled in Apache, and the server restarted. This should only occur if there are changes to the virtual hosts, so the notifies
value should be added to the template
resource. notifies
tells Chef when things have changed, and only then runs the commands:
template "/etc/apache2/sites-available/#{sitename}.conf" do
source "virtualhosts.erb"
mode "0644"
variables(
:document_root => document_root,
:port => data["port"],
:serveradmin => data["serveradmin"],
:servername => data["servername"]
)
notifies :restart, "service[apache2]"
end
The notifies
command names the :action
to be committed, then the resource, and resource name in square brackets.
10.notifies
can also call on execute
commands, which will run a2ensiteand
enable the sites we’ve made virtual hosts files for. Add the following execute
command above the template
resource code to create the a2ensite
script:
$vi ~/chef-repo/cookbooks/lamp_stack/recipes/apache.rb
# [...]
directory document_root do
mode "0755"
recursive true
end
execute "enable-sites" do
command "a2ensite #{sitename}"
action :nothing
end
template "/etc/apache2/sites-available/#{sitename}.conf" do
# [...]
The action :nothing
directive means the resource will wait to be called on. Add a new notifies
line above the previoues notifies
line to the template
resource code to use it:
$ vi ~/chef-repo/cookbooks/lamp_stack/recipes/apache.rb
# [...]
template "/etc/apache2/sites-available/#{sitename}.conf" do
# [...]
notifies :run, "execute[enable-sites]"
notifies :restart, "service[apache2]"
end
# [...]
11.The paths referenced in the virtual hosts files need to be created. Once more, this is done with the directory
resource, and should be added before the final end
tag:
$ vi ~/chef-repo/cookbooks/lamp_stack/recipes/apache.rb
# [...]
node["lamp_stack"]["sites"].each do |sitename, data|
# [...]
directory "/var/www/html/#{sitename}/public_html" do
action :create
end
directory "/var/www/html/#{sitename}/logs" do
action :create
end
end
11、Apache Configuration
With the virtual hosts files configured and your website enabled, configure Apache to efficiently run on your servers. Do this by enabling and configuring a multi-processing module (MPM), and editing apache2.conf.
The MPMs are all located in the mods_available
directory of Apache. In this example the event MPM will be used, located at /etc/apache2/mods-available/mpm_event.conf
. If we were planning on deploying to nodes of varying size we would create a template file to replace the original, which would allow for more customization of specific variables. In this instance, a cookbook file will be used to edit the file.
Cookbook files are static documents that are run against the document in the same locale on your servers. If any changes are made, the cookbook file makes a backup of the original file and replaces it with the new one.
1.To create a cookbook file navigate to files/default
from your cookbook’s main directory. If the directories do not already exist, create them:
mkdir -p ~/chef-repo/cookbooks/lamp_stack/files/default/
cd ~/chef-repo/cookbooks/lamp_stack/files/default/
vi mpm_event.conf
2.Create a file called mpm_event.conf
and copy the MPM event configuration into it, changing any needed values:
<IfModule mpm_event_module>
StartServers 2
MinSpareThreads 6
MaxSpareThreads 12
ThreadLimit 64
ThreadsPerChild 25
MaxRequestWorkers 25
MaxConnectionsPerChild 3000
</IfModule>
3.Return to apache.rb
, and use the cookbook_file
resource to call the file we just created. Because the MPM will need to be enabled, we’ll use the notifies
command again, this time to execute a2enmod mpm_event
. Add the execute
and cookbook_file
resources to the apache.rb
file prior to the final end
tag:
# [...]
node["lamp_stack"]["sites"].each do |sitename, data|
# [...]
execute "enable-event" do
command "a2enmod mpm_event"
action :nothing
end
cookbook_file "/etc/apache2/mods-available/mpm_event.conf" do
source "mpm_event.conf"
mode "0644"
notifies :run, "execute[enable-event]"
end
end
4.Within the apache2.conf
the KeepAlive
value should be set to off
, which is the only change made within the file. This can be altered through templates or cookbook files, although in this instance a simple sed
command will be used, paired with the execute
resource. Update apache.rb
with the new execute
resource:
# [...]
directory "/var/www/html/#{sitename}/logs" do
action :create
end
execute "keepalive" do
command "sed -i 's/KeepAlive On/KeepAlive Off/g' /etc/apache2/apache2.conf"
action :run
end
execute "enable-event" do
# [...]
Your apache.rb
is now complete.
$ vi
package "apache2" do
action :install
end
service "apache2" do
action [:enable, :start]
end
#Virtual Hosts Files
node["lamp_stack"]["sites"].each do |sitename, data|
document_root = "/var/www/html/#{sitename}"
directory document_root do
mode "0755"
recursive true
end
execute "enable-sites" do
command "a2ensite #{sitename}"
action :nothing
end
template "/etc/apache2/sites-available/#{sitename}.conf" do
source "virtualhosts.erb"
mode "0644"
variables(
:document_root => document_root,
:port => data["port"],
:serveradmin => data["serveradmin"],
:servername => data["servername"]
)
notifies :run, "execute[enable-sites]"
notifies :restart, "service[apache2]"
end
directory "/var/www/html/#{sitename}/public_html" do
action :create
end
directory "/var/www/html/#{sitename}/logs" do
action :create
end
execute "keepalive" do
command "sed -i 's/KeepAlive On/KeepAlive Off/g' /etc/apache2/apache2.conf"
action :run
end
execute "enable-event" do
command "a2enmod mpm_event"
action :nothing
end
cookbook_file "/etc/apache2/mods-available/mpm_event.conf" do
source "mpm_event.conf"
mode "0644"
notifies :run, "execute[enable-event]"
end
end
$ sudo knife cookbook upload lamp_stack
Uploading lamp_stack [0.1.0]
Uploaded 1 cookbook.
$ knife node run_list add node2 "recipe[lamp_stack]"
node2:
run_list:
recipe[lamp_stack]
recipe[lamp_stack::apache]
12、MySQL
12-1 Download the MySQL
1.The Chef Supermarket has an OpsCode-maintained MySQL cookbook that sets up MySQL lightweight resources/providers (LWRPs) to be used. From the workstation, download and install the cookbook:
knife cookbook site install mysql
This will also install any and all dependencies required to use the cookbook. These dependencies include the smf
and yum-mysql-community
cookbooks, which in turn depend on the rbac
and yum
cookbooks.
2.From the main directory of your LAMP stack cookbook, open the metadata.rb
file and add a dependency to the MySQL cookbook:
$ vi ~/chef-repo/cookbooks/lamp_stack/metadata.rb
depends 'mysql', '~> 8.5.1'
3.Upload these cookbooks to the server:
knife cookbook upload mysql --include-dependencies
sudo knife cookbook upload lamp_stack --force
12-2 Create and Encrypt Your MySQL Password
Chef contains a feature known as data bags
. Data bags store information, and can be encrypted to store passwords, and other sensitive data.
1.On the workstation, generate a secret key:
openssl rand -base64 512 > ~/chef-repo/.chef/encrypted_data_bag_secret
2.Upload this key to your node’s /etc/chef
directory, either manually by scp
(an example can be found in the Setting Up Chef guide), or through the use of a recipe and cookbook file.
3.On the workstation, create a mysql
data bag that will contain the file rtpass.json
for the root password:
knife data bag create mysql rtpass.json --secret-file ~/chef-repo/.chef/encrypted_data_bag_secret
Created data_bag[mysql]
ERROR: RuntimeError: Please set EDITOR environment variable. See https://docs.chef.io/knife_setup.html for details.
cd ../../.chef
vi knife.rb
knife[:editor] = "/usr/bin/vim"
You will be asked to edit the rtpass.json
file:
{
"id": "rtpass.json",
"password": "password123"
}
4.Confirm that the rtpass.json file was created:
$ knife data bag show mysql
rtpass.json
It should output rtpass.json
. To ensure that is it encrypted, run:
$ knife data bag show mysql rtpass.json
WARNING: Encrypted data bag detected, but no secret provided for decoding. Displaying encrypted data.
id: rtpass.json
password:
auth_tag: +EoLhhpYKrHtyrbyx9oC7g==
cipher: aes-256-gcm
encrypted_data: dON+BYY9+vu0JexTVwmiGftpftiv+glLIta7tvL0
iv: FrD0NUPg78t4GJCr
version: 3
12-3 Set Up MySQL
With the MySQL library downloaded and an encrypted root password prepared, you can now set up the recipe to download and configure MySQL
1.Open a new file in recipes
called mysql.rb
and define the data bag that will be used:
vi ~/chef-repo/cookbooks/lamp_stack/recipes/mysql.rb
mysqlpass = data_bag_item("mysql", "rtpass.json")
2.Thanks to the LWRPs provided through the MySQL cookbook, the initial installation and database creation for MySQL can be done in one resource:
mysqlpass = data_bag_item("mysql", "rtpass.json")
mysql_service "mysqldefault" do
version '5.7'
initial_root_password mysqlpass["password"]
action [:create, :start]
end
mysqldefault
is the name of the MySQL service for this container. The inital_root_password
calls to the value defined in the text above, while the action creates the database and starts the MySQL service.
sudo knife cookbook upload lamp_stack --force
knife node run_list add node2 "recipe[lamp_stack::mysql]"
node2:
run_list:
recipe[lamp_stack]
recipe[lamp_stack::apache]
recipe[lamp_stack::mysql]
When running MySQL from your nodes you will need to define the socket:
mysql -S /var/run/mysql-mysqldefault/mysqld.sock -p
13、PHP
1.Under the recipes directory, create a new php.rb
file. The commands below install PHP and all the required packages for working with Apache and MySQL:
$ vi php.rb
package "php" do
action :install
end
package "php-pear" do
action :install
end
package "php-mysql" do
action :install
end
2.For easy configuration, the php.ini
file will be created and used as a cookbook file, much like the MPM module above. You can either:
- Add the PHP recipe, run
chef-client
and copy the file from a node (located in/etc/php/7.0/cli/php.ini
), or: - Copy it from this chef-php.ini sample. The file should be moved to the
chef-repo/cookbooks/lamp_stack/files/default/
directory. This can also be turned into a template, if that better suits your configuration.
3.php.ini
is a large file. Search and edit the following values to best suit your Linodes. The values suggested below are for 2GB Linodes:
vi ~/chef-repo/cookbooks/lamp_stack/files/default/php.ini
max_execution_time = 30
memory_limit = 128M
error_reporting = E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR
display_errors = Off
log_errors = On
error_log = /var/log/php/error.log
max_input_time = 30
4.Return to php.rb
and append the cookbook_file
resource to the end of the recipe:
cookbook_file "/etc/php/7.0/cli/php.ini" do
source "php.ini"
mode "0644"
notifies :restart, "service[apache2]"
end
5.Because of the changes made to php.ini
, a /var/log/php
directory needs to be made and its ownership set to the Apache user. This is done through a notifies
command and execute resource, as done previously. Append these resources to the end of php.rb
:
execute "chownlog" do
command "chown www-data /var/log/php"
action :nothing
end
directory "/var/log/php" do
action :create
notifies :run, "execute[chownlog]"
end
The PHP recipe is now done!
6.Ensure that your Chef server contains the updated cookbook, and that your node’s run list is up-to-date. Replace nodename with your Chef node’s name:
$ sudo knife cookbook upload lamp_stack --force
$ knife node run_list add node2 "recipe[lamp_stack],recipe[lamp_stack::apache],recipe[lamp_stack::mysql],recipe[lamp_stack::php]"
node2:
run_list:
recipe[lamp_stack]
recipe[lamp_stack::apache]
recipe[lamp_stack::mysql]
recipe[lamp_stack::php]
You have just created a LAMP Stack cookbook. Through this guide, you should have learned to use the execute, package, service, node, directory, template, cookbook_file, and mysql_service resources within a recipe, as well as download and use LWRPs, create encrypted data bags, upload/update your cookbooks to the server, and use attributes, templates, and cookbook files, giving you a strong basis in Chef and cookbook creation for future projects.