Chef has been around a long time as is definitely a key player in deployment suites.  What started out as OpsCode over a decade ago is now part of Progress Software.  They have a series of integrated suites that bundle their products of Desktop, Infra, Inspec, Compliance, Habitat and Automate.  

Let's get into Chef with a focus on integration into Azure DevOps. We'll explore locally, containerized and chef in Kubernetes.

Setup

First we need to setup workstation for local development.  We can use brew cask install chef-workstation on the mac or just install with dpkg on WSL

$ wget https://packages.chef.io/files/stable/chef-workstation/21.1.233/ubuntu/20.04/chef-workstation_21.1.233-1_amd64.deb
Will not apply HSTS. The HSTS database must be a regular and non-world-writable file.
ERROR: could not open HSTS store at '/home/builder/.wget-hsts'. HSTS will be disabled.
--2021-02-07 11:43:13--  https://packages.chef.io/files/stable/chef-workstation/21.1.233/ubuntu/20.04/chef-workstation_21.1.233-1_amd64.deb
Resolving packages.chef.io (packages.chef.io)... 151.101.186.110
Connecting to packages.chef.io (packages.chef.io)|151.101.186.110|:443... connected.HTTP request sent, awaiting response... 200 OK
Length: 167400768 (160M) [application/x-debian-package]
Saving to: ‘chef-workstation_21.1.233-1_amd64.deb’

chef-workstation_21.1.233-1_a 100%[=================================================>] 159.65M  39.1MB/s    in 3.9s

2021-02-07 11:43:20 (41.3 MB/s) - ‘chef-workstation_21.1.233-1_amd64.deb’ saved [167400768/167400768]

Then we can install it:

builder@DESKTOP-JBA79RT:~/Workspaces/Chef$ sudo dpkg -i chef-workstation_21.1.233-1_amd64.deb
[sudo] password for builder:
Selecting previously unselected package chef-workstation.
(Reading database ... 119363 files and directories currently installed.)
Preparing to unpack chef-workstation_21.1.233-1_amd64.deb ...
Unpacking chef-workstation (21.1.233-1) ...
Setting up chef-workstation (21.1.233-1) ...

The Chef Workstation App is available for you to try.

Launch the App by running 'chef-workstation-app'.
The App will then be available in the system tray.

Thank you for installing Chef Workstation!
You can find some tips on getting started at https://docs.chef.io/workstation/

Verification

builder@DESKTOP-JBA79RT:~/Workspaces/Chef$ chef -v
Chef Workstation version: 21.1.233
Chef Infra Client version: 16.9.20
Chef InSpec version: 4.24.32
Chef CLI version: 3.0.35
Chef Habitat version: 1.6.181
Test Kitchen version: 2.9.0
Cookstyle version: 7.5.3

Creating a Cookbook

Chef loves the whole chef/kitchen concept so expect a lot of food related terms here.  But "Chef" runs "Cookbooks" which do things.. So we first need to create a cookbook:

We can create our first using ‘chef generate’

builder@DESKTOP-JBA79RT:~/Workspaces/Chef$ chef generate cookbook first_cookbook
+---------------------------------------------+
            Chef License Acceptance

Before you can continue, 3 product licenses
must be accepted. View the license at
https://www.chef.io/end-user-license-agreement/

Licenses that need accepting:
  * Chef Workstation
  * Chef Infra Client
  * Chef InSpec

Do you accept the 3 product licenses (yes/no)?

> yes

Persisting 3 product licenses...
✔ 3 product licenses persisted.

+---------------------------------------------+
Generating cookbook first_cookbook
- Ensuring correct cookbook content
- Committing cookbook files to git

Your cookbook is ready. Type `cd first_cookbook` to enter it.

There are several commands you can run to get started locally developing and testing your cookbook.
Type `delivery local --help` to see a full list of local testing commands.

Why not start by writing an InSpec test? Tests for the default recipe are stored at:

test/integration/default/default_test.rb

If you'd prefer to dive right in, the default recipe can be found at:

recipes/default.rb

note: older instructions may use "knife" to create cookbooks.  From what i can tell they've moved the "cookbook create" to a variety of binaries.  

Let’s use VSCode to view the created files: builder@DESKTOP-JBA79RT:~/Workspaces/Chef$ code .

The default.rb is the blank recipe we can start to fill in.

Let’s just create a text file:

#
# Cookbook:: first_cookbook
# Recipe:: default
#
# Copyright:: 2021, The Authors, All Rights Reserved.
 
file "#{ENV['HOME']}/first.txt" do
  content 'This file was created by Chef Infra! Howdy Howdy'
end

Now if we just run this right here, we’ll get failures:

builder@DESKTOP-JBA79RT:~/Workspaces/Chef$ chef-client --local-mode --override-runlist first_cookbook
[2021-02-07T11:58:00-06:00] WARN: No config file found or specified on command line. Using command line options instead.
[2021-02-07T11:58:00-06:00] WARN: No cookbooks directory found at or above current directory.  Assuming /home/builder/Workspaces/Chef.
Starting Chef Infra Client, version 16.9.20
Patents: https://www.chef.io/patents

This is because the chef-client really wants to see a “cookbooks” dir first. This is easy to correct

builder@DESKTOP-JBA79RT:~/Workspaces/Chef$ mkdir cookbooks
builder@DESKTOP-JBA79RT:~/Workspaces/Chef$ mv first_cookbook cookbooks

Now when we run:

builder@DESKTOP-JBA79RT:~/Workspaces/Chef$ chef-client --local-mode --override-runlist first_cookbook
[2021-02-07T12:06:27-06:00] WARN: No config file found or specified on command line. Using command line options instead.
Starting Chef Infra Client, version 16.9.20
Patents: https://www.chef.io/patents
[2021-02-07T12:06:30-06:00] ERROR: shard_seed: Failed to get dmi property serial_number: is dmidecode installed?
[2021-02-07T12:06:30-06:00] WARN: Run List override has been provided.
[2021-02-07T12:06:30-06:00] WARN: Original Run List: []
[2021-02-07T12:06:30-06:00] WARN: Overridden Run List: [recipe[first_cookbook]]
resolving cookbooks for run list: ["first_cookbook"]
Synchronizing Cookbooks:
  - first_cookbook (0.1.0)
Installing Cookbook Gems:
Compiling Cookbooks...
Converging 1 resources
Recipe: first_cookbook::default
  * file[/home/builder/first.txt] action create
    - create new file /home/builder/first.txt
    - update content in file /home/builder/first.txt from none to 4a42a5
    --- /home/builder/first.txt 2021-02-07 12:06:30.790000000 -0600
    +++ /home/builder/.chef-first20210207-11645-1ftmwy4.txt     2021-02-07 12:06:30.790000000 -0600
    @@ -1 +1,2 @@
    +This file was created by Chef Infra! Howdy Howdy
[2021-02-07T12:06:30-06:00] WARN: Skipping final node save because override_runlist was given

Running handlers:
Running handlers complete
Chef Infra Client finished, 1/1 resources updated in 02 seconds

Verification

We can see the file was created

builder@DESKTOP-JBA79RT:~/Workspaces/Chef$ cat ~/first.txt
This file was created by Chef Infra! Howdy Howdybuilder@DESKTOP-JBA79RT:~/Workspaces/Chef$

Alternate Install

You can use apt to install Chef as well:

$ sudo apt update
$ sudo apt install chef-bin

$ chef-client -z
/usr/share/rubygems-integration/all/gems/chef-15.8.25/lib/chef/provider.rb:337: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/usr/share/rubygems-integration/all/gems/chef-15.8.25/lib/chef/node_map.rb:58: warning: The called method `set' is defined here
/usr/share/rubygems-integration/all/gems/chef-15.8.25/lib/chef/resource.rb:1379: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/usr/share/rubygems-integration/all/gems/chef-15.8.25/lib/chef/node_map.rb:58: warning: The called method `set' is defined here
/usr/share/rubygems-integration/all/gems/chef-15.8.25/lib/chef/platform/priority_map.rb:36: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/usr/share/rubygems-integration/all/gems/chef-15.8.25/lib/chef/node_map.rb:58: warning: The called method `set' is defined here
/usr/share/rubygems-integration/all/gems/chef-15.8.25/lib/chef/property.rb:546: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/usr/share/rubygems-integration/all/gems/chef-15.8.25/lib/chef/property.rb:121: warning: The called method `initialize' is defined here
[2021-02-08T20:42:01-06:00] WARN: No config file found or specified on command line. Using command line options instead.
[2021-02-08T20:42:01-06:00] WARN: No cookbooks directory found at or above current directory.  Assuming /home/builder.
Starting Cinc Client, version 15.8.25
resolving cookbooks for run list: []
Synchronizing Cookbooks:
Installing Cookbook Gems:
Compiling Cookbooks...
[2021-02-08T20:42:14-06:00] WARN: Node DESKTOP-72D2D9T.localdomain has an empty run list.
Converging 0 resources

Running handlers:
Running handlers complete
Cinc Client finished, 0/0 resources updated in 07 seconds

Containerized Chef

Let's use AKS to install Chef

First, create the resource group and service principal we will use

$ az group create idjaks04rg --location centralus

$ az ad sp create-for-rbac -n idjaks04sp --skip-assignment --output json > idjaks04sp.json
WARNING: Changing "idjaks04sp" to a valid URI of "http://idjaks04sp", which is the required format used for service principal names
WARNING: Found an existing application instance of "4d3b5455-8ab1-4dc3-954b-39daed1cd954". We will patch it
WARNING: The output includes credentials that you must protect. Be sure that you do not include these credentials in your code or check the credentials into your source control. For more information, see https://aka.ms/azadsp-cli

Then set the env vars we need

$ export SP_PASS=`cat idjaks04sp.json | jq -r .password`
$ export SP_ID=`cat idjaks04sp.json | jq -r .appId`

Create the Cluster

$ az aks create --resource-group idjaks04rg --name idjaks04 --location centralus --node-count 3 --enable-cluster-autoscaler --min-count 2 --max-count 4 --generate-ssh-keys --network-plugin azure --network-policy azure --service-principal $SP_ID --client-secret $SP_PASS
 - Running ..

Verify we can see it

$ az aks list -o table
Name      Location    ResourceGroup    KubernetesVersion    ProvisioningState    Fqdn
--------  ----------  ---------------  -------------------  -------------------  -----------------------------------------------------------
idjaks04  centralus   idjaks04rg       1.18.14              Succeeded            idjaks04-idjaks04rg-70b42e-f90aef18.hcp.centralus.azmk8s.io

and we can login and see nodes


$ (rm -f ~/.kube/config || true) && az aks get-credentials -n idjaks04 -g idjaks04rg --admin
Merged "idjaks04-admin" as current context in /home/builder/.kube/config
$ kubectl get nodes
NAME                                STATUS   ROLES   AGE    VERSION
aks-nodepool1-10338191-vmss000000   Ready    agent   2m4s   v1.18.14
aks-nodepool1-10338191-vmss000001   Ready    agent   2m4s   v1.18.14
aks-nodepool1-10338191-vmss000002   Ready    agent   2m4s   v1.18.14

Let’s create an interactive pod with $ kubectl run my-shell -i --tty --image ubuntu -- bash or we can use an inline YAML:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: ubuntu
  labels:
    app: ubuntu
spec:
  containers:
  - image: ubuntu
    command:
      - "sleep"
      - "604800"
    imagePullPolicy: IfNotPresent
    name: ubuntu
  restartPolicy: Always
EOF

Here i'll do the first

Setup container

Update it first

root@my-shell:/# apt-get update && apt-get upgrade -y

Then install non-interatictively

root@my-shell:/# echo chef chef/chef_server_url string "http://localhost:4000" | debconf-set-selections
root@my-shell:/# debconf-show chef
* chef/chef_server_url: http://localhost:4000

root@my-shell:/# DEBIAN_FRONTEND=noninteractive

Installing commercial binaries…

root@my-shell:/# apt install -y wget
root@my-shell:/# wget https://packages.chef.io/files/stable/chef-workstation/21.1.233/ubuntu/20.04/chef-workstation_21.1.233-1_amd64.deb
--2021-02-09 13:10:45--  https://packages.chef.io/files/stable/chef-workstation/21.1.233/ubuntu/20.04/chef-workstation_21.1.233-1_amd64.deb
Resolving packages.chef.io (packages.chef.io)... 151.101.186.110
Connecting to packages.chef.io (packages.chef.io)|151.101.186.110|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 167400768 (160M) [application/x-debian-package]
Saving to: 'chef-workstation_21.1.233-1_amd64.deb'

chef-workstation_21.1.233-1_amd64.deb 100%[======================================================================>] 159.65M   158MB/s    in 1.0s

2021-02-09 13:10:49 (158 MB/s) - 'chef-workstation_21.1.233-1_amd64.deb' saved [167400768/167400768]

Installing open-source:

$ apt install chef-bin

We can test, but it needs us to accept a license first :|

root@my-shell:~# chef-client -z
[2021-02-09T13:15:38+00:00] WARN: No config file found or specified on command line. Using command line options instead.
[2021-02-09T13:15:38+00:00] WARN: No cookbooks directory found at or above current directory.  Assuming /root.
+---------------------------------------------+
            Chef License Acceptance

Before you can continue, 2 product licenses
must be accepted. View the license at
https://www.chef.io/end-user-license-agreement/

Licenses that need accepting:
  * Chef Infra Client
  * Chef InSpec

Do you accept the 2 product licenses (yes/no)?

> yes

Persisting 2 product licenses...
✔ 2 product licenses persisted.

Note, if you used APT to install chef-bin instead of the wget above, you'll need to install chefdk

$ apt install -y curl
$ curl https://omnitruck.chef.io/install.sh | bash -s -- -P chefdk -c stable

Creating a demo

First run in zero (local mode)

root@my-shell:~# mkdir chefdemo
root@my-shell:~# cd chefdemo/

root@my-shell:~/chefdemo# chef-client -z
[2021-02-09T13:18:52+00:00] WARN: No config file found or specified on command line. Using command line options instead.
[2021-02-09T13:18:52+00:00] WARN: No cookbooks directory found at or above current directory.  Assuming /root/chefdemo.
Starting Chef Infra Client, version 16.9.20
Patents: https://www.chef.io/patents
[2021-02-09T13:18:53+00:00] WARN: Plugin Network: unable to detect ipaddress
[2021-02-09T13:18:53+00:00] ERROR: shard_seed: Failed to get dmi property serial_number: is dmidecode installed?
resolving cookbooks for run list: []
Synchronizing Cookbooks:
Installing Cookbook Gems:
Compiling Cookbooks...
[2021-02-09T13:18:53+00:00] WARN: Node my-shell has an empty run list.
Converging 0 resources

Running handlers:
Running handlers complete
Chef Infra Client finished, 0/0 resources updated in 00 seconds

we will see that created a node of this server

# cat nodes/my-shell.json | head -n 10
{
  "name": "my-shell",
  "normal": {
    "tags": [

    ]
  },
  "default": {
    "audit": {
      "inspec_backend_cache": true,

Next create a cookbook

root@my-shell:~/chefdemo# chef generate cookbook democookbook
Generating cookbook democookbook
- Ensuring correct cookbook content

Your cookbook is ready. Type `cd democookbook` to enter it.

There are several commands you can run to get started locally developing and testing your cookbook.
Type `delivery local --help` to see a full list of local testing commands.

Why not start by writing an InSpec test? Tests for the default recipe are stored at:

test/integration/default/default_test.rb

If you'd prefer to dive right in, the default recipe can be found at:

recipes/default.rb

We can see the cookbook is local, but for some odd reason, not in a "cookbooks" dir which we will need

root@my-shell:~/chefdemo# ls
democookbook  nodes
root@my-shell:~/chefdemo# mkdir cookbooks
root@my-shell:~/chefdemo# mv democookbook cookbooks/
root@my-shell:~/chefdemo# cd cookbooks/democookbook/
root@my-shell:~/chefdemo/cookbooks/democookbook# ls
CHANGELOG.md  LICENSE  Policyfile.rb  README.md  chefignore  kitchen.yml  metadata.rb  recipes  spec  test

Set the metadata.rb file


Let’s create a recipe to install Nginx


root@my-shell:~/chefdemo/cookbooks/democookbook/recipes# vi default.rb
root@my-shell:~/chefdemo/cookbooks/democookbook/recipes# cat default.rb
#
# Cookbook:: democookbook
# Recipe:: default
#
# Copyright:: 2021, The Authors, All Rights Reserved.
#

package "nginx"

And we can test it by running directly

root@my-shell:~/chefdemo# chef-client --local-mode cookbooks/democookbook/recipes/default.rb
[2021-02-09T13:36:45+00:00] WARN: No config file found or specified on command line. Using command line options instead.
Starting Chef Infra Client, version 16.9.20
Patents: https://www.chef.io/patents
[2021-02-09T13:36:46+00:00] WARN: Plugin Network: unable to detect ipaddress
[2021-02-09T13:36:46+00:00] ERROR: shard_seed: Failed to get dmi property serial_number: is dmidecode installed?
resolving cookbooks for run list: []
Synchronizing Cookbooks:
Installing Cookbook Gems:
Compiling Cookbooks...
[2021-02-09T13:36:46+00:00] WARN: Node my-shell has an empty run list.
Converging 1 resources
Recipe: @recipe_files::/root/chefdemo/democookbook/recipes/default.rb
  * apt_package[nginx] action install

    - install version 1.18.0-0ubuntu1 of package nginx

Running handlers:
Running handlers complete
Chef Infra Client finished, 1/1 resources updated in 13 seconds

we can also add this to our run list. first confirm the node name

# cat nodes/*.json | grep name | head -n1
  "name": "my-shell",

Then add it

# knife node run_list add -z my-shell democookbook mistake
WARNING: No knife configuration file found. See https://docs.chef.io/config_rb/ for details.
my-shell:
  run_list:
    recipe[democookbook]
    recipe[mistake]

Here i accidentally added two books (democookbook and mistake) when i just wanted one. we can remove one from the run list

root@my-shell:~/chefdemo# knife node run_list remove -z my-shell 'recipe[mistake]'
WARNING: No knife configuration file found. See https://docs.chef.io/config_rb/ for details.
my-shell:
  run_list: recipe[democookbook]

Then use chef client to install

root@my-shell:~/chefdemo# chef-client -z
[2021-02-09T13:43:52+00:00] WARN: No config file found or specified on command line. Using command line options instead.
Starting Chef Infra Client, version 16.9.20
Patents: https://www.chef.io/patents
[2021-02-09T13:43:53+00:00] ERROR: shard_seed: Failed to get dmi property serial_number: is dmidecode installed?
resolving cookbooks for run list: ["democookbook"]
Synchronizing Cookbooks:
  - democookbook (0.1.0)
Installing Cookbook Gems:
Compiling Cookbooks...
Converging 1 resources
Recipe: democookbook::default
  * apt_package[nginx] action install (up to date)

Running handlers:
Running handlers complete
Chef Infra Client finished, 0/1 resources updated in 01 seconds

Let's expand on this and create a user.  Before we do that let's see valid groups

root@my-shell:~/chefdemo# cat /etc/group | grep users
users:x:100:

and now create a recipe for users

root@my-shell:~/chefdemo# cat cookbooks/democookbook/recipes/createuser.rb
user "nginxuser" do
  comment "Nginx User"
  shell "/bin/bash"
  uid 4444
  gid 100
  home "/home/nginxuser"

  manage_home true
end

and we can test

root@my-shell:~/chefdemo# chef-client -z
[2021-02-09T13:53:54+00:00] WARN: No config file found or specified on command line. Using command line options instead.
Starting Chef Infra Client, version 16.9.20
Patents: https://www.chef.io/patents
[2021-02-09T13:53:55+00:00] ERROR: shard_seed: Failed to get dmi property serial_number: is dmidecode installed?
resolving cookbooks for run list: ["democookbook", "democookbook::createuser"]
Synchronizing Cookbooks:
  - democookbook (0.1.0)
Installing Cookbook Gems:
Compiling Cookbooks...
Converging 2 resources
Recipe: democookbook::default
  * apt_package[nginx] action install (up to date)
Recipe: democookbook::createuser
  * linux_user[nginxuser] action create
    - create user nginxuser

Running handlers:
Running handlers complete
Chef Infra Client finished, 1/2 resources updated in 01 seconds

Validate

root@my-shell:~/chefdemo# su - nginxuser
nginxuser@my-shell:~$ pwd
/home/nginxuser
nginxuser@my-shell:~$ exit

Let's expand this recipe to do a bit more

root@my-shell:~/chefdemo# cat cookbooks/democookbook/recipes/createuser.rb
user "nginxuser" do
  comment "Nginx User"
  shell "/bin/bash"
  uid 4444
  gid 100
  home "/home/nginxuser"

  manage_home true
end


group "webusers" do
  members "nginxuser"
  append true
  action :create
end

group "root" do
  members "nginxuser"
  append true
  action :manage
end

Here we see us create a group and then add to a group (manage). if you type create on an existing group, it will blow it away and re-create.

then run it

root@my-shell:~/chefdemo# chef-client -z
[2021-02-09T13:57:59+00:00] WARN: No config file found or specified on command line. Using command line options instead.
Starting Chef Infra Client, version 16.9.20
Patents: https://www.chef.io/patents
[2021-02-09T13:58:00+00:00] ERROR: shard_seed: Failed to get dmi property serial_number: is dmidecode installed?
resolving cookbooks for run list: ["democookbook", "democookbook::createuser"]
Synchronizing Cookbooks:
  - democookbook (0.1.0)
Installing Cookbook Gems:
Compiling Cookbooks...
Converging 4 resources
Recipe: democookbook::default
  * apt_package[nginx] action install (up to date)
Recipe: democookbook::createuser
  * linux_user[nginxuser] action create (up to date)
  * group[webusers] action create (up to date)
  * group[root] action manage
    - manage group root
    - add missing member(s): nginxuser

Running handlers:
Running handlers complete
Chef Infra Client finished, 1/4 resources updated in 01 seconds

Validate

root@my-shell:~/chefdemo# cat /etc/group | grep nginxuser
root:x:0:nginxuser
webusers:x:1000:nginxuser

Files

Lets look at the current nginx index file

# cat /var/www/html/index.nginx-debian.html
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>

Let’s make  a dir and put in a default index.html file….

root@my-shell:~/chefdemo/cookbooks/democookbook# mkdir -p templates/default
root@my-shell:~/chefdemo/cookbooks/democookbook# cat templates/default/index.html.erb
<HTML>
  <HEAD>
    <TITLE>Hello</TITLE>
  </HEAD>
  <BODY>
    <h1>Hello <%= @myVar %></h1>

    This Pod has <%= node["memory"]["total"] %> memory.


    <% if @debug > 0 %>
      Debug On
    <% else %>
      Debug Off
    <% end %>
  </BODY>
</HTML>

Setup the recipe to deploy it with variables

root@my-shell:~/chefdemo/cookbooks/democookbook# !v
vi recipes/managed_index.rb
root@my-shell:~/chefdemo/cookbooks/democookbook# cat recipes/managed_index.rb
template "/var/www/html/index.html" do
  source "index.html.erb"
  user "nginxuser"
  group "root"
  mode 0664
  variables({
    :debug => 1,
    :myVar => "My Variable"
  })
end

Now add the recipe

root@my-shell:~/chefdemo/cookbooks/democookbook# knife node run_list add -z my-shell democookbook::managed_index
WARNING: No knife configuration file found. See https://docs.chef.io/config_rb/ for details.
my-shell:
  run_list:
    recipe[democookbook]
    recipe[democookbook::createuser]
    recipe[democookbook::managed_index]

and install

root@my-shell:~/chefdemo/cookbooks/democookbook# chef-client -z
[2021-02-09T14:08:53+00:00] WARN: No config file found or specified on command line. Using command line options instead.
Starting Chef Infra Client, version 16.9.20
Patents: https://www.chef.io/patents
[2021-02-09T14:08:54+00:00] ERROR: shard_seed: Failed to get dmi property serial_number: is dmidecode installed?
resolving cookbooks for run list: ["democookbook", "democookbook::createuser", "democookbook::managed_index"]
Synchronizing Cookbooks:
  - democookbook (0.1.0)
Installing Cookbook Gems:
Compiling Cookbooks...
Converging 5 resources
Recipe: democookbook::default
  * apt_package[nginx] action install (up to date)
Recipe: democookbook::createuser
  * linux_user[nginxuser] action create (up to date)
  * group[webusers] action create (up to date)
  * group[root] action manage (up to date)
Recipe: democookbook::managed_index
  * template[/var/www/html/index.html] action create
    - create new file /var/www/html/index.html
    - update content in file /var/www/html/index.html from none to 99d0ab
    --- /var/www/html/index.html        2021-02-09 14:08:55.315727527 +0000
    +++ /var/www/html/.chef-index20210209-4768-3ek16s.html      2021-02-09 14:08:55.315727527 +0000
    @@ -1 +1,16 @@
    +<HTML>
    +  <HEAD>
    +    <TITLE>Hello</TITLE>
    +  </HEAD>
    +  <BODY>
    +    <h1>Hello My Variable</h1>
    +
    +    This Pod has 7120660kB memory.
    +
    +
    +      Debug On
    +  </BODY>
    +</HTML>
    +
    +
    - change mode from '' to '0664'
    - change owner from '' to 'nginxuser'
    - change group from '' to 'root'

Running handlers:
Running handlers complete
Chef Infra Client finished, 1/5 resources updated in 01 seconds

Validation

root@my-shell:~# wget http://localhost
--2021-02-09 10:32:54--  http://localhost/
Resolving localhost (localhost)... 127.0.0.1, ::1
Connecting to localhost (localhost)|127.0.0.1|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 163 [text/html]
Saving to: 'index.html'

index.html                       100%[=======================================================>]     163  --.-KB/s    in 0s

2021-02-09 10:32:54 (37.0 MB/s) - 'index.html' saved [163/163]

root@my-shell:~# cat index.html
<HTML>
  <HEAD>
    <TITLE>Hello</TITLE>
  </HEAD>
  <BODY>
    <h1>Hello My Variable</h1>

    This Pod has 7120660kB memory.


      Debug On
  </BODY>
</HTML>

Before we go.. We may wish to transfer our work as we did this in a shell session.. So when we exit, all of what we did above goes away.

root@my-shell:~# zip -r cookbook.zip cookbooks/
updating: cookbooks/ (stored 0%)
  adding: cookbooks/democookbook/ (stored 0%)
  adding: cookbooks/democookbook/templates/ (stored 0%)
  adding: cookbooks/democookbook/templates/default/ (stored 0%)
  adding: cookbooks/democookbook/templates/default/index.html.erb (deflated 36%)
  adding: cookbooks/democookbook/LICENSE (deflated 4%)
  adding: cookbooks/democookbook/metadata.rb (deflated 55%)
  adding: cookbooks/democookbook/test/ (stored 0%)
  adding: cookbooks/democookbook/test/integration/ (stored 0%)
  adding: cookbooks/democookbook/test/integration/default/ (stored 0%)
  adding: cookbooks/democookbook/test/integration/default/default_test.rb (deflated 42%)
  adding: cookbooks/democookbook/chefignore (deflated 46%)
  adding: cookbooks/democookbook/Policyfile.rb (deflated 41%)
  adding: cookbooks/democookbook/CHANGELOG.md (deflated 27%)
  adding: cookbooks/democookbook/.delivery/ (stored 0%)
  adding: cookbooks/democookbook/.delivery/project.toml (deflated 50%)
  adding: cookbooks/democookbook/README.md (deflated 12%)
  adding: cookbooks/democookbook/kitchen.yml (deflated 47%)
  adding: cookbooks/democookbook/spec/ (stored 0%)
  adding: cookbooks/democookbook/spec/unit/ (stored 0%)
  adding: cookbooks/democookbook/spec/unit/recipes/ (stored 0%)
  adding: cookbooks/democookbook/spec/unit/recipes/default_spec.rb (deflated 57%)
  adding: cookbooks/democookbook/spec/spec_helper.rb (deflated 24%)
  adding: cookbooks/democookbook/recipes/ (stored 0%)
  adding: cookbooks/democookbook/recipes/createuser.rb (deflated 47%)
  adding: cookbooks/democookbook/recipes/managed_index.rb (deflated 23%)
  adding: cookbooks/democookbook/recipes/default.rb (deflated 14%)

And in a different shell

builder@DESKTOP-JBA79RT:~$ kubectl cp my-shell:/root/cookbook.zip ./cookbook.zip
tar: Removing leading `/' from member names
builder@DESKTOP-JBA79RT:~$ unzip cookbook.zip
Archive:  cookbook.zip
   creating: cookbooks/
   creating: cookbooks/democookbook/
   creating: cookbooks/democookbook/templates/
   creating: cookbooks/democookbook/templates/default/
  inflating: cookbooks/democookbook/templates/default/index.html.erb
  inflating: cookbooks/democookbook/LICENSE
  inflating: cookbooks/democookbook/metadata.rb
   creating: cookbooks/democookbook/test/
   creating: cookbooks/democookbook/test/integration/
   creating: cookbooks/democookbook/test/integration/default/
  inflating: cookbooks/democookbook/test/integration/default/default_test.rb
  inflating: cookbooks/democookbook/chefignore
  inflating: cookbooks/democookbook/Policyfile.rb
  inflating: cookbooks/democookbook/CHANGELOG.md
   creating: cookbooks/democookbook/.delivery/
  inflating: cookbooks/democookbook/.delivery/project.toml
  inflating: cookbooks/democookbook/README.md
  inflating: cookbooks/democookbook/kitchen.yml
   creating: cookbooks/democookbook/spec/
   creating: cookbooks/democookbook/spec/unit/
   creating: cookbooks/democookbook/spec/unit/recipes/
  inflating: cookbooks/democookbook/spec/unit/recipes/default_spec.rb
  inflating: cookbooks/democookbook/spec/spec_helper.rb
   creating: cookbooks/democookbook/recipes/
  inflating: cookbooks/democookbook/recipes/createuser.rb
  inflating: cookbooks/democookbook/recipes/managed_index.rb
  inflating: cookbooks/democookbook/recipes/default.rb

again, when we exit the shell, we lose our work

root@my-shell:~/chefdemo/cookbooks/democookbook# exit
exit
Session ended, resume using 'kubectl attach my-shell -c my-shell -i -t' command when the pod is running
builder@DESKTOP-JBA79RT:~$ kubectl attach my-shell -c my-shell -i
If you don't see a command prompt, try pressing enter.
root@my-shell:/# history
    1  history

Integrating with Azure DevOps

Let's extract that zip into a new folder "dockerchef" and create a Dockerfile

$ cat dockerchef/Dockerfile
FROM zuazo/chef-local:ubuntu-16.04

# Copy the cookbook from the current working directory:
COPY ./cookbooks/democookbook /tmp/democookbook
# Download and install the cookbook and its dependencies in the cookbook path:
RUN berks vendor -b /tmp/democookbook/Berksfile $COOKBOOK_PATH
# Run Chef Client, runs in local mode by default:
RUN chef-client -r "recipe[apt],recipe[democookbook],recipe[democookbook::createuser],recipe[democookbook::managed_index]"

# CMD to run you application
CMD ["nginx", "-g", "daemon off;"]

We need to create a berksfile at this time too:

source 'https://supermarket.chef.io'
metadata
cookbook 'apt'

We can now build a docker image locally to test

$ docker build dockerchef
[+] Building 59.5s (9/9) FINISHED
 => [internal] load build definition from Dockerfile                                                                              0.1s
 => => transferring dockerfile: 38B                                                                                               0.0s
 => [internal] load .dockerignore                                                                                                 0.1s
 => => transferring context: 2B                                                                                                   0.0s
 => [internal] load metadata for docker.io/zuazo/chef-local:ubuntu-16.04                                                          0.6s
 => [internal] load build context                                                                                                 0.0s
 => => transferring context: 1.67kB                                                                                               0.0s
 => CACHED [1/4] FROM docker.io/zuazo/chef-local:ubuntu-16.04@sha256:3d453c2e47782abf29e200795d7bfa72cf28d58b4f4e94bf55eb0c409f0  0.0s
 => [2/4] COPY ./cookbooks/democookbook /tmp/democookbook                                                                         0.1s
 => [3/4] RUN berks vendor -b /tmp/democookbook/Berksfile /tmp/chef/cookbooks                                                     4.8s
 => [4/4] RUN chef-client -r "recipe[apt],recipe[democookbook],recipe[democookbook::createuser],recipe[democookbook::managed_in  53.3s
 => exporting to image                                                                                                            0.5s
 => => exporting layers                                                                                                           0.4s
 => => writing image sha256:a558b64165a839ca4455e87c2cb842ddfdaff222d086b4d809291193985b66f1                                      0.0s

To build and deploy to IBM CR i'll add a step to build and push


        - task: Docker@2
          inputs:
            containerRegistry: 'IBMCR'
            repository: 'freshbrewedcr/my-chef-demo'
            command: 'buildAndPush'
            Dockerfile: 'dockerchef/Dockerfile'
            buildContext: 'dockerchef'
            tags: |
              $(Build.BuildId)
              latest

And when run, we see it pushed

Then in my deployment.yaml i added

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: azdo-chefdemo-deployment
  namespace: #{ENV-AKS-NAMESPACE}#
  labels: 
    app: azdo-chefdemo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: azdo-chefdemo
  template:
    metadata:
      labels:
        app: azdo-chefdemo
    spec:
      containers:
      - name: azdo-agent
        image: us.icr.io/freshbrewedcr/my-chef-demo:latest

I uploaded then created a PR

When merged, i saw it deploy

And i can see it come up in standuptime:

$ kubectl get deployments -n standuptime
NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
azdo-newagent-deployment   1/1     1            1           40d
azdo-chefdemo-deployment   0/1     1            0           97s


$ kubectl get pods -n standuptime
NAME                                        READY   STATUS    RESTARTS   AGE
azdo-newagent-deployment-5b6b489cb6-tcqh9   1/1     Running   0          11d
azdo-chefdemo-deployment-568fb9f75f-7l5v5   1/1     Running   0          114s

Verification

Doing a port-forward we can see it running

Summary

In this introduction we used Chef locally, in a container and then in Kubernetes.  We tied it to a self running Dockerfile and proved it ran 3 recipes in a cookbook we made.

Our next steps would be to check out Test Kitchen and the linting tools available such as Cookstyle.

Chef has a number of commercial offerings that range from $72/node to $350/node depending on features and options.  That said, the basic open source chef does provide some decent abilities as a deployment platform.