[Editor – The Chef cookbook referenced in this blog relies on the NGINX Plus Status and Upstream Conf modules (enabled by the status
and upstream_conf
directives). Those modules are replaced and deprecated by the NGINX Plus API in NGINX Plus Release 13 (R13) and later, and are not available in NGINX Plus R16 and later. For the solution to continue working, update the cookbook components that refer to the two deprecated modules.]
In an earlier blog post, we talked about using Ansible to install NGINX or NGINX Plus. As for many other types of software out there, there lots of alternatives when it comes to configuration management software. Along with Ansible, one of the most popular is Chef. Both tools have their fans, and there are plenty of articles that compare them. Here we’ll focus on showing how to use Chef to install and configure NGINX and NGINX Plus.
Opscode, the company behind Chef, provides an extensive collection of cookbooks that are easy to install onto your Chef server with a single command. Out of the box, the base cookbook for NGINX is a very powerful tool for installing and configuring NGINX. It can be rather overwhelming for newer Chef users, however, so in this post we’ll go over how to use it to install an NGINX Plus package and set up a basic configuration. (You can also use this cookbook to install NGINX via source code – with or without third‑party modules – but we will not be covering that process in this post.)
Editor – For information about using other DevOps automation tools with NGINX and NGINX Plus, check out these related blogs:
- Announcing a Unified Ansible Role for NGINX and NGINX Plus
- Deploying NGINX Plus for High Availability with Chef
- Autoscaling and Orchestration with NGINX Plus and Chef
- Installing NGINX and NGINX Plus with Puppet
This post assumes that you have a basic understanding of Chef, its configuration files (recipes, environments, roles, and so on) and its associated tools, mainly knife
. If you need to review, check out the tutorials and documentation at learn.chef.io.
Preparing Your Chef Environment
To help make it quick and easy to get a test environment up and running, we’re doing our work in a Chef Zero environment. Chef Zero is a simple, easy to install, in‑memory Chef server that is useful for testing chef‑client
tasks (similar to chef‑solo
), as well as tasks that require a full Chef server. For full installation instructions, see the Chef Zero homepage on GitHub.
Here is the process for installing chef‑zero
on a basic Ubuntu 14.04 installation and getting everything in place:
-
Use
apt
to install the prerequisite packages:~$ sudo apt-get install ruby-dev make git [sudo] password for username: Reading package lists... Done Building dependency tree Reading state information... Done ... Need to get 52.6 MB of archives. After this operation, 188 MB of additional disk space will be used. Do you want to continue? [Y/n] y Get:1 http://us.archive.ubuntu.com/ubuntu/ trusty-updates/main libasan0 amd64 4.8.4-2ubuntu1~14.04 [63.0 kB] ... Setting up ruby1.9.1 (1.9.3.484-2ubuntu1.2) ... Processing triggers for libc-bin (2.19-0ubuntu6.6) ...
-
Use
gem
to installchef‑zero
:~$ sudo gem install chef-zero Fetching: mixlib-log-1.6.0.gem (100%) Fetching: hashie-3.4.2.gem (100%) ... Successfully installed chef-zero-4.3.2 7 gems installed
-
Download and install the Chef package. We’re pulling the package directly from the Opscode servers instead of using
apt
, because the Ubuntu distributions have a rather outdated version of the Chef package. (The Chef package in the example might even be slightly outdated; to be sure you’re getting the latest package, check the Chef website.)Download the package:
~$ wget https://opscode-omnibus-packages.s3.amazonaws.com/ubuntu/10.04/x86_64/chef_12.4.3-1_amd64.deb --2015-10-19 11:33:44-- https://opscode-omnibus-packages.s3.amazonaws.com/ubuntu/10.04/x86_64/chef_12.4.3-1_amd64.deb Resolving opscode-omnibus-packages.s3.amazonaws.com (opscode-omnibus-packages.s3.amazonaws.com)... 54.231.2.233 Connecting to opscode-omnibus-packages.s3.amazonaws.com (opscode-omnibus-packages.s3.amazonaws.com)|54.231.2.233|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 38821266 (37M) [application/x-www-form-urlencoded] Saving to: 'chef_12.4.3-1_amd64.deb' 100%[========================================================================== =============================================================================== ===============>] 38,821,266 9.96MB/s in 4.6s 2015-10-19 11:33:50 (8.03 MB/s) - 'chef_12.4.3-1_amd64.deb' saved [38821266/38821266]
Install the package:
~$ sudo dpkg -i chef_12.4.3-1_amd64.deb Selecting previously unselected package chef. (Reading database ... 64482 files and directories currently installed.) Preparing to unpack chef_12.4.3-1_amd64.deb ... Unpacking chef (12.4.3-1) ... Setting up chef (12.4.3-1) ... Thank you for installing Chef!
-
Set two GitHub global variables so that you can clone repos. The values do not actually have to match an existing GitHub user, as we are only cloning public repositories, and here I’m using my name and email address. If you plan on cloning any private repos or pushing any commits, specify values that match your GitHub account.
~$ git config --global user.name "Damian Curry" ~$ git config --global user.email "damian.curry@nginx.com"
-
Clone the
chef‑zero
repo. The repo contains the necessary configuration files to get moving quickly, even though we usedgem
to install it. It also contains some cookbooks, node definitions, and environment definitions that we will not be using, but are good examples to have if you are new to Chef.~$ git clone https://github.com/chef/chef-zero.git Cloning into 'chef-zero'... remote: Counting objects: 3115, done. remote: Total 3115 (delta 0), reused 0 (delta 0), pack-reused 3115 Receiving objects: 100% (3115/3115), 548.87 KiB | 0 bytes/s, done. Resolving deltas: 100% (1727/1727), done. Checking connectivity... done.
-
Start the
chef‑zero
process that we’ll work with.By default, Chef Zero listens on port 8889, but the
knife
configuration file bundled with thechef‑zero
repo defaults to port 4000. To keep file changes to a minimum, we tell thechef‑zero
instance to listen on port 4000. We also include the‑d
flag to make Chef Zero run as a daemon. If you run into any issues, you can try running the process in the foreground for troubleshooting purposes.~$ sudo chef-zero -p 4000 -d
Downloading and Configuring the NGINX Cookbook
Now we install and configure the NGINX cookbook on our Chef server.
-
Change directory to the playground directory inside the repo we cloned as ~/chef-zero in the previous section. This enables
knife
commands to locate the knife.rb configuration file, which resides in the ~/chef-zero/playground/.chef subdirectory.~$ cd chef-zero/playground/
-
Download the cookbook and its requirements to your local system. (The term
install
in the command string is kind of misleading, because the command doesn’t actually install NGINX or anything else; instead it just downloads the files.)~/chef-zero/playground$ knife cookbook site install nginx Installing nginx to /home/username/chef-zero/playground/cookbooks Checking out the master branch. Creating pristine copy branch chef-vendor-nginx Downloading nginx from the cookbooks site at version 2.7.6 to /home/username/chef-zero/playground/cookbooks/nginx.tar.gz ... Cookbook yum version 3.8.1 successfully installed
-
For some reason, the
knife
cookbook
command we ran in the previous step downloads cookbook versions that are incompatible forrsyslog
. If we don’t fix it, the incompatibility will cause the following error later on when we upload the cookbooks in Step 4 of Bootstrapping and Preparing the Node for NGINX Plus Installation:# DO NOT RUN NOW: example to show error ~/chef-zero/playground/cookbooks$ knife cookbook upload * Uploading apache2 [1.0.0] Uploading apt [2.9.2] Uploading bluepill [2.4.1] ERROR: Cookbook bluepill depends on cookbooks which are not ERROR: currently being uploaded and cannot be found on the ERROR: server. ERROR: The missing cookbook(s) are: 'rsyslog' version '~> 2.0'
To avoid the error, run this command to pull an earlier version of the
rsyslog
cookbook:~/chef-zero/playground$ cd cookbooks ~/chef-zero/playground/cookbooks$ knife cookbook site install rsyslog 2.0.0 Installing rsyslog to /home/username/chef-zero/playground/cookbooks Checking out the master branch. Pristine copy branch (chef-vendor-rsyslog) exists, switching to it. ... Cookbook rsyslog version 2.0.0 successfully installed
-
Create a new recipe file for NGINX Plus installation, based on the existing recipe in the NGINX cookbook for installing the open source NGINX software from a prebuilt binary, package.rb. After we make a couple of changes to the NGINX cookbook, we’ll upload it to the Chef server. The new recipe, plus_package.rb, creates all of the necessary certificates and
apt
repos needed for access to the NGINX Plus package.Create the plus_package.rb file in the ~/chef-zero/playground/cookbooks/nginx/recipes directory with these contents:
# # Cookbook Name:: nginx # Recipe:: plus_package # Author:: Damian Curry <damian.curry@nginx.com> # # Copyright 2008-2013, Chef Software, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # include_recipe 'nginx::ohai_plugin' directory '/etc/ssl/nginx' do owner 'root' group 'root' mode '0755' action :create end file '/etc/ssl/nginx/nginx-repo.key' do owner 'root' group 'root' mode '0644' content node.attribute['nginx']['nginx_repo_key'] end file '/etc/ssl/nginx/nginx-repo.crt' do owner 'root' group 'root' mode '0644' content node.attribute['nginx']['nginx_repo_crt'] end # Ensure file does not exist (certificate for the NGINX Plus repository was # changed in June 2016) file '/etc/ssl/nginx/CA.crt' do action :delete end case node['platform_family'] when 'rhel' version_long = node[:platform_version] version = version_long[0] case version when '5' package 'openssl' do action :install end when '6', '7' package 'ca-certificates' do action :install end end remote_file "/etc/yum.repos.d/nginx-plus-#{version}.repo" do source "https://cs.nginx.com/static/files/nginx-plus-#{version}.repo" owner 'root' group 'root' mode 0700 end when 'debian' include_recipe 'apt::default' apt_repository 'nginx_plus' do uri 'https://plus-pkgs.nginx.com/ubuntu' distribution node['lsb']['codename'] components %w(nginx-plus) deb_src false key 'http://nginx.org/keys/nginx_signing.key' end end package node['nginx']['package_name'] do notifies :reload, 'ohai[reload_nginx]', :immediately not_if 'which nginx' end directory node['nginx']['dir'] do owner 'root' group node['root_group'] mode '0755' recursive true end directory node['nginx']['log_dir'] do mode node['nginx']['log_dir_perm'] owner node['nginx']['user'] action :create recursive true end directory File.dirname(node['nginx']['pid']) do owner 'root' group node['root_group'] mode '0755' recursive true end directory "#{node['nginx']['dir']}/conf.d" do owner 'root' group node['root_group'] mode '0755' end service 'nginx' do supports :status => true, :restart => true, :reload => true action :enable end include_recipe 'nginx::commons_script' template 'nginx.conf' do path "#{node['nginx']['dir']}/nginx.conf" source node['nginx']['conf_template'] cookbook node['nginx']['conf_cookbook'] owner 'root' group node['root_group'] mode '0644' notifies :reload, 'service[nginx]', :delayed end if node['nginx']['default_site_enabled'] == 'true' template "#{node['nginx']['dir']}/conf.d/default.conf" do source 'default-site.erb' owner 'root' group node['root_group'] mode '0644' notifies :reload, 'service[nginx]', :delayed end else file "#{node['nginx']['dir']}/conf.d/default.conf" do action :delete end end if node['nginx']['plus_status_enable'] == 'true' template 'nginx_plus_status' do path "#{node['nginx']['dir']}/conf.d/nginx_plus_status.conf" source 'nginx_plus_status.erb' owner 'root' group node['root_group'] mode '0644' notifies :reload, 'service[nginx]', :delayed end end
-
Create the template file, nginx_plus_status.erb, that is referenced in the last stanza of the plus_package.rb recipe. Put it in the ~/chef-zero/playground/cookbooks/nginx/templates/default directory with these contents:
upstream example { zone example 64k; server [::1]:8080; } server { listen <%= node['nginx']['plus_status_port'] -%>; status_zone status-page; access_log <%= node['nginx']['log_dir'] %>/status.access.log; location = /status.html { root /usr/share/nginx/html; } location = / { return 301 /status.html; } location /status { status; status_format json; access_log off; } location /upstream_conf { upstream_conf; } }
-
Create a role to use when installing NGINX Plus. Roles in Chef consist of four main lists:
run_list
– List of the recipes to run on a nodedefault_attributes
– List of attributes and the values for the recipes to set during deploymentenv_run_list
– (Not used in this example) List of recipes to run against the node based on which environment it is assigned to-
override_attributes
– (Not used in this example) List of attributes and the values for the recipes to set during deployment that will override the default attributes that are defined in some recipes
A role is a good way to define a baseline set of characteristics for a node with a specific function. You can then modify the role by adding more attributes to one or both of the
default_attributes
andoverride_attributes
lists on each individual node.Create the nginx_plus.rb role definition file with these contents and place it in the ~/chef-zero/playground/roles directory:
name "nginx_plus" description "An example role to install NGINX Plus" run_list "recipe[nginx]" default_attributes "nginx" => { "install_method" => "plus_package", "package_name" => "nginx-plus", "init_style" => "init", "default_site_enabled" => "true", "nginx_repo_key" => "-----BEGIN PRIVATE KEY-----nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCbYwum24BwEY8Ynnqc0 ... +BCnoMSzbvLWhZbpSrdmD9nOj1KkPcWn4ArSv6prlYItUwWbNtFLw/E=n-----END PRIVATE KEY-----", "nginx_repo_crt" => "-----BEGIN CERTIFICATE-----nMIIDrDCCApSgAwIBAgICBs8wDQYJKoZIhvcNAQEFBQAwXjELMAkGA1UEBhGTg ... X2XnbovinLlYPfdi7BhlXTI9u78+tqbo0YVsSBiDV49hcIA=n-----END CERTIFICATE-----" }
You will notice that I have added the contents of the nginx-repo.key and nginx-repo.crt files as the values of the
nginx_repo_key
andnginx_repo_crt
attributes. If you use the example as the basis for your nginx_plus.rb file, replace the values in the example with the key and certificate for your NGINX Plus subscription or trial. I prefer recording the key and certificate in attributes this way instead of recording them in files inside of the cookbook, because it makes things more portable, but you can use either method.It is also possible to define the
run_list
anddefault_attributes
for an individual host or an environment, inside either the node or environment definition, but I prefer to define them in a role. Then you can apply the role to any node where you want to install NGINX Plus, followed by another recipe that configures NGINX Plus for the specific function the node is to perform.Notes:
- You must define the
init_style
attribute asinit
, because its default value ofrunit
does not work with NGINX Plus. - When you copy in the key and certificate from your Subscriptions page at the NGINX Plus customer portal, each one is a block of about 20 lines of text with a hard linebreak at the end of each line. For Chef to parse the key and certificate properly, you must convert the blocks to a single line each by replacing every hard linebreak with the linebreak character,
n
(backslash‑n
). For brevity, the example shows the converted form of only the first and last couple lines from the original blocks.
Bootstrapping and Preparing the Node for NGINX Plus Installation
Next we need to make sure that our node has the correct role in its run list. You can set up the run list when bootstrapping the node or after. To keep the process as simple as possible and thus avoid errors, I prefer to set up the run list after the node has been bootstrapped. Also, I always like to have a local copy of the node definitions so I can modify them on disk and then push the changes to Chef, which lets me keep track of changes using Git.
-
Run this
knife
bootstrap
command to associate the node with the Chef environment without actually executing any run lists against it. Once the node has been associated with Chef, we will define its run list.In this example I am using the
‑N
flag to set the name of the node to chef-test. You can set any name you like; if you omit the‑N
flag, the name defaults to the IP address or FQDN provided as the last parameter to the command (127.0.0.1 in this example).~/chef-zero/playground/cookbooks$ cd .. ~/chef-zero/playground$ knife bootstrap -N chef-test -x username --sudo 127.0.0.1 Creating new client for chef-test Creating new node for chef-test Connecting to 127.0.0.1 username@127.0.0.1's password: 127.0.0.1 knife sudo password: Enter your password: 127.0.0.1 127.0.0.1 -----> Existing Chef installation detected ... 127.0.0.1 Chef Client finished, 0/0 resources updated in 1.122875381 seconds
-
Create a local copy of the node’s definition.
~/chef-zero/playground$ knife node show chef-test --format json > nodes/chef-test.json
-
Edit the local node definition, adding the
nginx_plus
role to its run list. Here is the definition after you have modified it:{ "name": "chef-test", "chef_environment": "_default", "run_list": [ "role[nginx_plus]" ] ´ "normal": { "tags": [ ] } }
-
With all of the required files in place, push them to the Chef server:
~/chef-zero/playground$ knife role from file roles/nginx_plus.rb Updated Role nginx_plus! ~/chef-zero/playground$ knife node from file nodes/chef-test.json Updated Node chef-test! ~/chef-zero/playground$ cd cookbooks ~/chef-zero/playground/cookbooks$ knife cookbook upload * Uploading apache2 [1.0.0] Uploading apt [2.8.2] ... Uploaded 12 cookbooks.
-
Run the
chef‑client
in the foreground so you can make sure everything works. The first time the cookbook runs, it can take a while as it needs to run anapt‑get
update
operation:~/chef-zero/playground/cookbooks$ sudo chef-client Starting Chef Client, version 12.4.3 resolving cookbooks for run list: ["nginx"] Synchronizing Cookbooks: - nginx - apt ... Compiling Cookbooks... Recipe: ohai::default ... Recipe: nginx::plus_package ... Running handlers: Running handlers complete Chef Client finished, 34/41 resources updated in 81.395229372 seconds
At this point NGINX Plus is installed with a very basic configuration, without any server
blocks defined in the http
block. This is not the default NGINX Plus configuration, but rather the default configuration from the official Chef NGINX cookbook. Here are the contents of the /etc/nginx/nginx.conf file:
user www-data;
worker_processes 1;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_requests 100;
keepalive_timeout 65;
gzip on;
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_proxied any;
gzip_vary off;
gzip_types text/plain text/css application/x-javascript text/xml
application/xml application/rss+xml
application/atom+xml text/javascript
application/javascript application/json text/mathml;
gzip_min_length 1000;
gzip_disable "MSIE [1-6].";
variables_hash_max_size 1024;
variables_hash_bucket_size 64;
server_names_hash_bucket_size 64;
types_hash_max_size 2048;
types_hash_bucket_size 64;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Because the basic default site has no virtual servers (server
configuration blocks) defined, when you access it you see the NGINX welcome page:
Modifying the Default Options for the NGINX Cookbook
Now, let’s change some of the config variables by defining more attributes in the configuration file for the NGINX Plus role (~/chef-zero/playgrounds/roles/nginx_plus.rb). To match the default configuration in NGINX Plus packages, we are disabling the default site, setting the worker_processes
directive to auto
, changing the username from www‑data
to nginx
, and turning off the gzip module. We’re also enabling the NGINX Plus live activity monitoring (status) dashboard and API and setting them to listen on port 8080.
Here is what the modified role looks like, with the changes highlighted in bold:
name "nginx_plus"
description "An example role to install NGINX Plus"
run_list "recipe[nginx]"
default_attributes "nginx" => { "install_method" => "plus_package",
"package_name" => "nginx-plus",
"init_style" => "init",
"default_site_enabled" => "false",
"worker_processes" => "auto",
"user" => "nginx",
"gzip" => "off",
"plus_status_enable" => "true",
"plus_status_port" => "8080",
"nginx_repo_key" => "-----BEGIN PRIVATE KEY-----nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCbYwum24BwEY8Ynnqc0 ... +BCnoMSzbvLWhZbpSrdmD9nOj1KkPcWn4ArSv6prlYItUwWbNtFLw/E=n-----END PRIVATE KEY-----",
"nginx_repo_crt" => "-----BEGIN CERTIFICATE-----nMIIDrDCCApSgAwIBAgICBs8wDQYJKoZIhvcNAQEFBQAwXjELMAkGA1UEBhGTg ... X2XnbovinLlYPfdi7BhlXTI9u78+tqbo0YVsSBiDV49hcIA=n-----END CERTIFICATE-----" }
There are many other NGINX configuration options that you can define as attributes in the same way. We aren’t covering them here, but they are all documented in the README.md that is bundled with the cookbook. Keep in mind that the different attributes were designed for the open source NGINX software, so not all of them apply to NGINX Plus.
Now we go ahead and upload the modified role and rerun Chef to see the changes. To avoid errors, we need to manually stop NGINX Plus before running the chef‑client
command.
~/chef-zero/playground$ knife role from file roles/nginx_plus.rb
Updated Role nginx_plus!
~/chef-zero/playground$ sudo service nginx stop
~/chef-zero/playground$ ps axu | grep nginx
username 10493 0.0 0.2 11748 2144 pts/0 S+ 17:24 0:00 grep --color=auto nginx
~/chef-zero/playground$ sudo chef-client
Starting Chef Client, version 12.4.3
resolving cookbooks for run list: ["nginx"]
Synchronizing Cookbooks:
- apt
-
...
Compiling Cookbooks...
...
Recipe: nginx::commons_conf
* template[nginx.conf] action create
...
Running handlers:
Running handlers complete
Chef Client finished, 5/30 resources updated in 3.877185478 seconds
At this point you can display the NGINX Plus dashboard by pointing a browser at port 8080 on your NGINX Plus server’s IP address. There’s not much information on the dashboard’s main, Server zones, or Upstreams tabs, because there are no running backend servers for it to monitor (the current entries are for the live activity monitoring module itself). So in the next section we’ll define a configuration of backend servers that takes advantage of some NGINX Plus features.
Creating a Cookbook for a DockerUI Deployment
The base NGINX cookbook does not include variables for creating a server
configuration block, so we need to create a basic recipe to create the site configuration file for NGINX Plus. As an example, I am going to configure NGINX Plus as a frontend for DockerUI
, adding SSL, basic htpasswd
authentication, and a custom log format that makes it easy to track which users are accessing DockerUI
.
-
The demo-deploy cookbook that we are creating depends on a couple of other cookbooks, which we need to download:
-
htpasswd – Creates and manages htaccess files. This is not an official Chef cookbook, but it is a good option. It depends on the Python cookbook, so we need to install that as well.
-
selfsigned_certificate – Creates and manages self‑signed certs.
Run these five commands to download the required files and verify they are in place:
$ cd ~/chef-zero/playground/cookbooks ~/chef-zero/playground/cookbooks$ git clone https://github.com/redguide/htpasswd.git Cloning into 'htpasswd'... remote: Counting objects: 234, done. remote: Total 234 (delta 0), reused 0 (delta 0), pack-reused 234 Receiving objects: 100% (234/234), 32.88 KiB | 0 bytes/s, done. Resolving deltas: 100% (96/96), done. Checking connectivity... done. ~/chef-zero/playground/cookbooks$ knife cookbook site install python Installing python to /home/username/chef-zero/playground/cookbooks Checking out the master branch. Creating pristine copy branch chef-vendor-python ... Uploaded 15 cookbooks. ~/chef-zero/playground/cookbooks$ git clone https://github.com/cgravier/selfsigned_certificate.git Cloning into 'selfsigned_certificate'... remote: Counting objects: 169, done. remote: Total 169 (delta 0), reused 0 (delta 0), pack-reused 169 Receiving objects: 100% (169/169), 105.59 KiB | 0 bytes/s, done. Resolving deltas: 100% (59/59), done. Checking connectivity... done. ~/chef-zero/playground/cookbooks$ ls apache2 apt bluepill build-essential htpasswd nginx ohai packagecloud php python rsyslog runit selfsigned_certificate yum yum-epel
-
-
Create the directory structure that Chef expects, as the first step in creating the new demo-deploy cookbook. This is a very basic cookbook at this time, so we only need to create directories for templates and recipes:
~/chef-zero/playground/cookbooks$ mkdir -p demo-deploy/templates/default ~/chef-zero/playground/cookbooks$ mkdir -p demo-deploy/recipes
-
Create three files that the demo-deploy cookbook needs:
- metadata.rb – Defines the details of the cookbook
- dockerui.rb – Controls the deployment
-
dockerui.conf.erb – Provides a template for the actual NGINX configuration file
We also create an empty file called default.rb just because Chef expects to find a file with that name.
Here are the contents of ~/chef-zero/playground/cookbooks/demo-deploy/metadata.rb:
name 'demo-deploy' maintainer_email 'damian.curry@nginx.com' license 'Apache 2.0' description 'Deploys NGINX Plus demos' version '0.0.1' recipe 'demo-deploy::dockerui', 'Installs dockerui demo' depends 'selfsigned_certificate', '~> 0.1.3' depends 'htpasswd', '~> 0.2.5'
Here are the contents of ~/chef-zero/playground/cookbooks/demo-deploy/recipes/dockerui.rb. Notice that in the third stanza we are using the htpasswd cookbook to add a user named dockerui with the password dockerui in the file /etc/nginx/htpassword. In a real world deployment, you would store the password in an encrypted data bag instead of as plain text.
# # Cookbook Name:: demo-deploy # Recipe:: dockerui # Author:: Damian Curry <damian.curry@nginx.com> # # Copyright 2008-2013, Chef Software, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # node.default['htpasswd']['built-in']['lang'] = ['ruby'] include_recipe 'htpasswd' include_recipe 'nginx' include_recipe 'selfsigned_certificate' htpasswd '/etc/nginx/htpassword' do user 'dockerui' password 'dockerui' end template '/etc/nginx/conf.d/dockerui.conf' do source 'dockerui.conf.erb' owner 'root' group 'root' mode '0644' notifies :reload, 'service[nginx]', :delayed end bash 'install docker.io' do user 'root' cwd '/root/' code 'curl -sSL https://get.docker.com/ | sh' not_if 'which docker' end bash ‘start docker container' do user 'root' cwd '/root/' code 'docker run -d -p 8970:9000 --privileged -v /var/run/docker.sock:/var/run/docker.sock dockerui/dockerui' not_if 'ps -ef | grep docker-proxy | grep -v grep' end
Here are the contents of ~/chef-zero/playground/cookbooks/demo-deploy/templates/default/dockerui.conf.erb:
log_format main "$status $ssl_client_verify/$remote_user@$remote_addr $request / $bytes_sent bytes ->$upstream_addr"; upstream dockerui { zone dockerui 64k; server <%= node['ipaddress'] %>:8970; } server { listen 80; return 301 https://$http_host; } server { listen 443 ssl; ssl_certificate /usr/var/ssl/certs/server.crt; ssl_certificate_key /usr/var/ssl/certs/server.key; access_log /var/log/nginx/access.log main; location / { auth_basic on; auth_basic_user_file /etc/nginx/htpassword; proxy_pass http://dockerui; health_check match=dockerui; proxy_set_header Host $host; proxy_buffering off; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } match dockerui { status 200; body ~ "dockerui"; }
-
Upload the cookbooks to Chef.
~/chef-zero/playground/cookbooks$ knife cookbook upload * Uploading apache2 [1.0.0] Uploading apt [2.8.2] ... Uploading yum-epel [0.6.3] Uploaded 16 cookbooks.
-
Modify the NGINX Plus role definition file (~/chef-zero/playground/roles/nginx_plus.rb) so that the run list includes the recipe for our
DockerUI
deployment. The text to add appears in bold:name "nginx_plus" description "An example role to install NGINX Plus" run_list "recipe[nginx]","recipe[demo-deploy::dockerui]" default_attributes "nginx" => { "install_method" => "plus_package", "package_name" => "nginx-plus", "init_style" => "init", "default_site_enabled" => "false", "worker_processes" => "auto", "user" => "nginx", "gzip" => "off", "plus_status_enable" => "true", "plus_status_port" => "8080", "nginx_repo_key" => "-----BEGIN PRIVATE KEY-----nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCbYwum24BwEY8Ynnqc0 ... +BCnoMSzbvLWhZbpSrdmD9nOj1KkPcWn4ArSv6prlYItUwWbNtFLw/E=n-----END PRIVATE KEY-----", "nginx_repo_crt" => "-----BEGIN CERTIFICATE-----nMIIDrDCCApSgAwIBAgICBs8wDQYJKoZIhvcNAQEFBQAwXjELMAkGA1UEBhGTg ... X2XnbovinLlYPfdi7BhlXTI9u78+tqbo0YVsSBiDV49hcIA=n-----END CERTIFICATE-----" }
-
Update the role:
~/chef/zero/playground/cookbooks$ cd ../ ~/chef-zero/playground$ knife role from file roles/nginx_plus.rb Updated Role nginx_plus!
-
Apply the updated role to our node:
~/chef-zero/playground$ sudo chef-client Starting Chef Client, version 12.4.3 resolving cookbooks for run list: ["nginx", "demo-deploy::dockerui"] Synchronizing Cookbooks: - demo-deploy - bluepill - apt - nginx - build-essential - packagecloud - runit - yum-epel - ohai - rsyslog - selfsigned_certificate - htpasswd - yum - python Compiling Cookbooks... ...
Now that DockerUI
is running and the NGINX Plus configurations are in place, when we refresh the NGINX Plus dashboard we see a server zone and upstream group for it.
If you send an HTTP request to the server’s IP address, NGINX Plus redirects it to an HTTPS URL and prompts for a username and password. When you provide the username and password defined in the dockerui.rb recipe, you are brought to the DockerUI
web interface. The first line of this snippet from the access log shows the redirect:
10.100.10.1 - - [11/Dec/2015:17:31:22 -0800] "GET / HTTP/1.1" 301 184 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.80 Safari/537.36"
200 NONE/dockerui@10.100.10.1 GET / HTTP/1.1 / 1711 bytes ->10.100.100.23:8970
200 NONE/dockerui@10.100.10.1 GET /vendor.css HTTP/1.1 / 127994 bytes ->10.100.100.23:8970
200 NONE/dockerui@10.100.10.1 GET /dockerui.css HTTP/1.1 / 1352 bytes ->10.100.100.23:8970
200 NONE/dockerui@10.100.10.1 GET /angular.js HTTP/1.1 / 209191 bytes ->10.100.100.23:8970
200 NONE/dockerui@10.100.10.1 GET /dockerui.js HTTP/1.1 / 96665 bytes ->10.100.100.23:8970
200 NONE/dockerui@10.100.10.1 GET /vendor.js HTTP/1.1 / 652438 bytes ->10.100.100.23:8970
200 NONE/dockerui@10.100.10.1 GET /dockerapi/version HTTP/1.1 / 345 bytes ->10.100.100.23:8970
200 NONE/dockerui@10.100.10.1 GET /dockerapi/containers/json?all=1 HTTP/1.1 / 564 bytes ->10.100.100.23:8970
200 NONE/dockerui@10.100.10.1 GET /dockerapi/images/json?all=0 HTTP/1.1 / 431 bytes ->10.100.100.23:8970
The snippet also shows the custom format for log entries that we defined in Creating a Cookbook for a DockerUI Deployment by including this log_format
directive in the dockerui.conf.erb file:
log_format main "$status $ssl_client_verify/$remote_user@$remote_addr $request / $bytes_sent bytes ->$upstream_addr";
The custom format adds the second field with the $remote_user
variable to report the username that made the request, making it easier to track which users are accessing the DockerUI web interface. Because we placed the log_format
directive in the server
block for HTTPS traffic, the format is used only for the lines in the snippet after the first one. The first line uses the default format for log entries because it records the redirect operation that happens in the server
block for HTTP traffic.
Summary
We’ve barely scratched the surface of what is possible with Chef, but I hope you have a better understanding of how you can implement Chef to deploy NGINX Plus. There are many more cookbooks and modules that can handle almost any administrative function that you need.
Editor – Check out these related blogs about other DevOps automation tools for NGINX and NGINX Plus:
- Announcing a Unified Ansible Role for NGINX and NGINX Plus
- Deploying NGINX Plus for High Availability with Chef
- Autoscaling and Orchestration with NGINX Plus and Chef
- Installing NGINX and NGINX Plus with Puppet
Ready to put what you’ve learned about Chef and NGINX Plus into practice? Start your free 30‑day trial of NGINX Plus or contact us today.