A Step-By-Step Guide to Test Chef Using Test Kitchen with Docker (用docker和kitchen 測試Chef Cookbook)

A Step-By-Step Guide to Test Chef Using Test Kitchen with Docker

 

  • Reference of this article: https://fuhton.com/Testing-Chef-with-Docker/, as I encountered many issues when setting up and testing, I decided to write my own manual.
  • The workstation used is an air-gapped rhel server, so the procedures become complicated, it would be much easier if your workstation has internet access.
  • A simple method has been added at the end of this article.
  • For more resources like this, please check https://medium.com/@yangpeng_tech

Prerequisite

A rhel server with internal yum repo configured, the following packages are also installed on this server.

  • Chef Workstation 1.0.11
  • Docker CE 18.09.6
  • Gems: kitchen-docker 2.9.0
$ chef --version
Chef Workstation version: 1.0.11
Chef Infra Client version: 15.2.20
Chef InSpec version: 4.10.4
Test Kitchen version: 2.2.5
Foodcritic version: 16.1.1
Cookstyle version: 0.72.0
$ docker version
Client:
 Version:           18.09.6
 API version:       1.39
 Go version:        go1.10.8
 Git commit:        481bc77156
 Built:             Sat May  4 02:34:58 2019
 OS/Arch:           linux/amd64
 Experimental:      false
Server: Docker Engine - Community
 Engine:
  Version:          18.09.6
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.8
  Git commit:       481bc77
  Built:            Sat May  4 02:02:43 2019
  OS/Arch:          linux/amd64
  Experimental:     false
# chef gem list kitchen-docker

*** LOCAL GEMS ***

kitchen-docker (2.9.0)

Setup and Test

1. Prepare cookbook

$ chef generate cookbook cookbook-test
$ cd cookbook-test
$ ls
CHANGELOG.md  chefignore  kitchen.yml  LICENSE  metadata.rb  Policyfile.rb  README.md  recipes  spec  test
$ mkdir -p files/default
$ mkdir -p test/recipes
$ cat > files/default/log.sh << EOF
#!/bin/bash

echo "this is the log.sh"
EOF
$ cat > recipes/default.rb << EOF
directory "/etc/testbook" do
  owner "root"
  group "root"
  mode "0755"
  action :create
end
cookbook_file "/etc/testbook/log.sh" do
  source "log.sh"
  group "root"
  owner "root"
  mode "0644"
end
EOF
$ cat > test/recipes/default_test.rb << EOF
describe directory("/etc/testbook") do
  it { should exist }
end

describe file("/etc/testbook/log.sh") do
  it { should exist }
  its('content') { should match /echo "this is the log.sh"/ }
end
EOF

2. Prepare Policyfile.rb

In earlier version of Chef Workstion, the Policyfile.rb will not be generated, instead, Berksfile will be generated. In that case, generate the policy and modify it with: chef generate policyfile. Then we run chef install to generate Policyfile.lock.json.

$ vi Policyfile.rb (comment the following line as we cannot connect to the internet and don't use external cookbooks for now)
default_source :supermarket
$ chef install

3. Prepare files required for container

  • CentOS-Base.repo: copied from workstation to container, used to run yum install later
  • install-chef-client.sh: self-made script, configure artifactory repo and install chef from there
  • other files: the keys and certs to make sure CentOS-Base.repo works in container
$ cd cookbook-test
$ mkdir file4container
$ cp /etc/yum.repos.d/redhat.repo CentOS-Base.repo
$ vi install-chef-client.sh <--yum install chef via internal rpm artifactory
$ cp /etc/pki/entitlement/* . <--keys and certs used in CentOS-Base.repo
$ cp /etc/rhsm/ca/katello-server-ca.pem .

4. Pull internal CentOS docker image

# docker pull dtr.lxc.example.com/centos/centos7
# docker images | grep -e centos7 -e TAG
REPOSITORY                                            TAG                 IMAGE ID            CREATED             SIZE
dtr.lxc.example.com/centos/centos7                  latest              5182e96772bf        12 months ago       200MB

5. Create Dockerfile

As our workstation is air gapped, we use our own Dockerfile instead. We need to change highlighted part below.

$ cat > Dockerfile << EOF
FROM dtr.lxc.example/centos/centos7:latest
COPY file4container/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo
COPY file4container/*.pem  /etc/pki/entitlement/
COPY file4container/katello-server-ca.pem /etc/rhsm/ca/katello-server-ca.pem
RUN yum clean all
EOF

6. Edit kitchen.yml

The file name was .kitchen.yml in older version of Chef Workstation.

The run_list described in this file will be ignored, so we don’t write it, kitchen instead uses the run list in Policyfile.rb.

---                                                                                                                            
driver:
  name: docker
  use_sudo: false
provisioner:
  name: chef_zero
  always_update_cookbooks: true
  client_rb:
    chef_license: accept
verifier:
  name: inspec
platforms:
  - name: centos-7
    driver_config:
      platform: centos
      dockerfile: ./Dockerfile
suites:
  - name: default
    verifier:
      inspec_tests:
        - test/recipes
    attributes:

7. docker build

We have to use docker command to help us to build a base image for our test.

Use -t option to make a tag for it.

# docker build . -t kitchen-image-base
...
Successfully built 206dbbd70d5c
Successfully tagged kitchen-image-base:latest

8. Run container and execute commands

The reason I need to execute command manually in container is the /etc/hosts gets reset each time when the container is run, and I need to update it to make sure it can solve the hostname specified in CentOS-Base.repo. If there is way to update the /etc/hosts in Dockerfile and maintain it unchanged, the procedure will be much easier. Pass the image ID in previous step to docker run command.

# docker run -it 206dbbd70d5c bash
# cat >> /etc/hosts << EOF
xx.xx.xx.xx example.local
EOF
# yum install -y sudo openssh-server openssh-clients which curl htop # ssh-keygen -A
# ls /etc/ssh/
# mkdir -p /var/run/sshd
# username=kitchen
# password=kitchen
# useradd -d /home/$username -m -s /bin/bash $username
# echo "$username:$password" | chpasswd
# echo "$username ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
# mkdir -p /home/$username/.ssh
# chown -R $username /home/$username/.ssh
# chmod 0700 /home/$username/.ssh
# touch /home/$username/.ssh/authorized_keys
# chown $username /home/$username/.ssh/authorized_keys
# chmod 0600 /home/$username/.ssh/authorized_keys
!! Do not exit for now as we haven't saved the status yet.

9. Commit current container as new image centos/kitchen-centos:version1

We will be using the new image in our updated Dockerfile later.

# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
f0335b209531        206dbbd70d5c        "bash"              7 minutes ago       Up 7 minutes                            condescending_margulis
# docker commit f0335b209531 centos/kitchen-centos:version1
# docker images centos/kitchen-centos
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
centos/kitchen-centos   version1            7af2ccd7e480        5 seconds ago       344MB

10. Update Dockerfile to below 

FROM centos/kitchen-centos:version1
COPY file4container/install-chef-client.sh /etc/tmp/install-chef-client.sh
RUN bash /etc/tmp/install-chef-client.sh
RUN echo '<%= IO.read(@public_key).strip %>' >> /home/<%= @username %>/.ssh/authorized_keys
RUN ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

11. kitchen create

This will create a .kitchen directory in your cookbook. It contains the definition of our VM, the connection keys to SSH in, and our logs.

# kitchen create
...
       Successfully built dcb2414cbb86
       6e50294dea47b6e7c9b6172f0c9d454b93bda80cf476f428e7de84dea16dce53
       0.0.0.0:32774
       Waiting for SSH service on localhost:32774, retrying in 3 seconds
       [SSH] Established
       Finished creating <default-centos-7> (1m19.93s).
-----> Kitchen is finished. (1m30.09s)
# kitchen list
Instance          Driver  Provisioner  Verifier  Transport  Last Action  Last Error
default-centos-7  Docker  ChefZero     Inspec    Ssh        Created      <None>

12. kitchen converge

To run the recipes in the VM (container), run kitchen converge.

# kitchen converge
Chef Infra Client finished, 2/2 resources updated in 04 seconds
       Downloading files from <default-centos-7>
       Finished converging <default-centos-7> (0m26.84s).
-----> Kitchen is finished. (0m35.72s)
# kitchen list
Instance          Driver  Provisioner  Verifier  Transport  Last Action  Last Error
default-centos-7  Docker  ChefZero     Inspec    Ssh        Converged    <None>

13. kitchen verify

To test the recipes, we can run kitchen verify to check. kitchen verify will also call kitchen create and kitchen converge, if those two have been run, it will only run the tests.

# kitchen verify
Target:  ssh://kitchen@localhost:32774

  Directory /etc/testbook
     ✔  should exist
  File /etc/testbook/log.sh
     ✔  should exist
     ✔  content should match /echo "this is the log.sh"/

Test Summary: 3 successful, 0 failures, 0 skipped
       Finished verifying <default-centos-7> (0m2.99s).
-----> Kitchen is finished. (0m12.25s)
# kitchen list
Instance          Driver  Provisioner  Verifier  Transport  Last Action  Last Error
default-centos-7  Docker  ChefZero     Inspec    Ssh        Verified     <None>

We can also ssh to the container and check the file manually:

# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                   NAMES
6e50294dea47        dcb2414cbb86        "/usr/sbin/sshd -D -…"   16 minutes ago      Up 16 minutes       0.0.0.0:32774->22/tcp   hopeful_lamarr
f0335b209531        206dbbd70d5c        "bash"                   43 minutes ago      Up 43 minutes                               condescending_margulis
# docker exec -it hopeful_lamarr bash
# ls -lh /etc/testbook/log.sh
-rw-r--r-- 1 root root 39 Aug 13 05:14 /etc/testbook/log.sh

14. kitchen destroy

To destroy the container we created, run kitchen destroy. It will stop the container. The images we created with docker build and docker commit will not be deleted. When we run kitchen create again, it will use the image created by last kitchen create. So we have to manually delete the image if we want a clean one for next kitchen create.

# kitchen destroy
       Finished destroying <default-centos-7> (0m2.50s).
-----> Kitchen is finished. (0m11.19s)
# kitchen list
Instance          Driver  Provisioner  Verifier  Transport  Last Action    Last Error
default-centos-7  Docker  ChefZero     Inspec    Ssh        <Not Created>  <None>
# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
# docker images
REPOSITORY   TAG    IMAGE ID      CREATED             SIZE
<none>       <none> 9a730be48b97  19 minutes ago      585MB

Simple Method

In the above processes, we had to use docker build and docker commit to help us save the current container as new image as we were not able to change the /etc/hosts in container, a workaround has been found in which we can use a single Dockerfile.

  1. Prepare cookbook — same as above
  2. Prepare policyfile.rb— same as above
  3. Prepare files required for container— same as above
  4. Pull internal CentOS docker image — same as above
  5. Create Docker file
FROM dtr.lxc.example/centos/centos7:latest
COPY file4container/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo
COPY file4container/<multipleline>.pem  /etc/pki/entitlement/<multipleline>.pem
COPY file4container/katello-server-ca.pem /etc/rhsm/ca/katello-server-ca.pem
RUN yum clean all
RUN echo xx.xx.xx.xx exmaple.local >> /etc/hosts; yum install -y sudo openssh-server openssh-clients which curl htop
RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
RUN ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key
RUN mkdir -p /var/run/sshd
RUN useradd -d /home/<%= @username %> -m -s /bin/bash <%= @username %>
RUN echo <%= "#{@username}:#{@password}" %> | chpasswd
RUN echo '<%= @username %> ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
RUN mkdir -p /home/<%= @username %>/.ssh
RUN chown -R <%= @username %> /home/<%= @username %>/.ssh
RUN chmod 0700 /home/<%= @username %>/.ssh
RUN touch /home/<%= @username %>/.ssh/authorized_keys
RUN chown <%= @username %> /home/<%= @username %>/.ssh/authorized_keys
RUN chmod 0600 /home/<%= @username %>/.ssh/authorized_keys
COPY file4container/install-chef-client.sh /etc/tmp/install-chef-client.sh
RUN bash /etc/tmp/install-chef-client.sh
RUN echo '<%= IO.read(@public_key).strip %>' >> /home/<%= @username %>/.ssh/authorized_keys
RUN ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

6. kitchen create

7. kitchen converge

8. kitchen verify

9. kitchen destroy

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章