Kitchen for fun and food

Luciano Adonis
devsChile
Published in
12 min readFeb 20, 2019

Un tutorial extenso sobre cómo crear un directorio usando Chef y hacer referencias a Bob Esponja en el proceso.

Al empezar a trabajar con Chef, no es la curva de aprendizaje lo que más puede costarte, es el ya tener un ambiente completo con muchos cookbooks y estos a su vez, con múltiples recetas dando vuelta y no saber por dónde partir.

Tus opciones para familiarizarte y probar cosas son:

  • Ambiente de producción: comenzando con pequeños cambios en producción, subiendo una que otra receta cruzando los dedos para no matar algo y si ocurre, aprender de ello.
  • Ambiente de pruebas realista: levantar tu propio Chef Server, levantar tus nodos, configurarlos y recién ahí poder crear un directorio. Lo cual muchos cursos te muestran.
  • Ambiente de pruebas local: utilizar Chef Solo para correr recetas de forma manual, dentro de alguna máquina virtual o en tu mismo equipo, lo cual es poco recomendable.

El contra de todas esas opciones es que no puedes aplicar pruebas simples rápidamente y tampoco replicar fácilmente un ambiente complejo. Pero existe una solución para aquellos que buscan poco compromiso para realizar un par de pruebas rápidas y esta es Kitchen.

Kitchen es la herramienta que tienes en consideración cuando empiezas a entender la importancia de los tests y/o la intención de emplear Test Driven Development (TDD), en el cual escribes los test primero, ves como todo explota y vas escribiendo código que pueda cumplir con los estándares puestos por los tests.

Imagen de KitchenCI

Dependiendo de ti, puede que a través del TDD te familiarices mejor con Chef, pero por lo general no ocurre de esa forma. Más aún cuando esta herramienta la emplean principalmente SysAdmins, los cuales no deberían estar ex-Centos de eso.

Considero fantásticas las facilidades que te entrega Kitchen para poder crear, manejar y destruir ambientes a través de múltiples proveedores cloud y tecnologías de virtualización. Pero estas facilidades deben ser utilizadas para un bien superior…

…la creación de un directorio.

Conceptos generales

Debido a que tan solo en los componentes de Chef hay bastante contenido del cual escribir, mencionaré brevemente algunos conceptos generales. Respecto a la definición de Chef, suerte:

  • Chef Server: permite almacenar datos de configuración como cookbooks, políticas de seguridad y metadata, la cual describe a cada uno de los nodos manejados por Chef-Client.
  • Chef Client: es el agente que corre localmente en los nodos pertenecientes a Chef. Cuando se ejecuta el chef-client, consulta al Chef Server para los detalles de configuración como recetas, templates y distribución de archivos. Luego este hace todo lo posible en los nodos para definir el estado esperado.
  • Cookbook: agrupa las configuraciones necesarias para automatizar la creación y/o mantención de un ambiente de infraestructura especifico.
  • Recipes: Es una colección de recursos definidos utilizando patrones como resource names, attribute-value pairs y actions, con el objetivo de configurar parte de un sistema. Estos deben ser almacenados dentro de un cookbook, el cual será utilizado por chef-client siempre y cuando este figure dentro de un run-list.

Dentro de lo que debes tener en consideración para definir de mejor forma el estado de tus servidores, lo mejor es que tengas en mente algunos de los recursos que puedes emplear.

  • Resources: se encuentran en las recetas, encargados de describir el estado deseado para una configuración especifica y los pasos necesarios para cumplirla.

La estructura base de un Resource es:

type 'name' do
attribute 'value'
action :type_of_action
end

Tipo de recurso, por ejemplo; package, template o service seguido de sus propiedades y acciones asociadas.

  • Package: instala un servicio.
  • Service: manejar el comportamiento de un servicio.
  • Directory: crear y manejar un directorio con sus permisos respectivos. Este es el enfoque del post. 🔥
  • File: especificas el contenido de un archivo a crear dentro de la ruta.
  • Cookbook_file: te permite crear un archivo utilizando uno ubicado en /files que se encuentra dentro.
  • Template: a partir de un archivo en /templates con al extensión .erb, se generan archivos en base a variables.

Si buscas extender las funcionalidades o aprovechar mejor aún Chef, puedes hacerlo con un poco de Ruby.

El conjunto de recursos definidos en recetas, describen las configuraciones para ambientes específicos.

Cocinando un directorio

Antes de poder crear tu receta para crear un directorio, tendrás que instalar un par de cosas:

  • Chef DK: contiene el set de herramientas recomendadas, lo cual incluye Chef y Test Kitchen. Tropical Freeze 🍌
  • Vagrant: otra maravilla de Hashicorp, esta te permite construir y manejar máquinas virtuales. Cuenta con integración para Virtual Box.
  • Virtual Box: si tengo que justificar el porque Virtual Box, teniendo opciones como utilizar Docker de lleno, es debido a que en macOS, iniciarlo implica hacer un par de clicks. Lo cual son segundos que suman.

Puedes usar otra combinación, solo implica hacer algunas variaciones en el .kitchen.yaml, nada grave.

Creando un libro de cocina 📖

Dentro de un directorio que te acomode, ya sea algo como kitchen o cookbooks ejecuta lo siguiente:

chef generate cookbook demoCookbook

Este comando creará la estructura para el cookbook demoCookbook.

/recipes
/spec
/test
.gitignore
.kitchen.yml
Berksfile
CHANGELOG.md
chefignore
LICENSE
metadata.rb
README.md

Archivos y directorios que no se tocarán para esta demo:

  • spec/ — directorio para pruebas unitarias.
  • test/ — directorio para pruebas de integración.
  • .gitignore — lista de archivos que serán ignorados por git.
  • Berksfile — administrador de dependencias para Cookbooks.
  • CHANGELOG.md — información sobre cambios y mejoras.
  • chefignore — lista de archivos que serán ignorados por chef.
  • LICENSE — Licencia de uso.
  • metadata.rb información relacionada al cookbook, definición de dependencias y otras hierbas.
  • README.md léelo, sé que te sorprenderá.

Para este ejemplo solo necesitaremos de los siguientes archivos:

  • .kitchen.yaml — definiciones para crear los entornos en los cuales se provisonará chef y las recetas a ejecutar.
  • recipes/default.rb — receta por defecto, la cual ya se encuentra en la lista dentro del .kitchen.yaml.
  • attributes/default.rb — carpeta y archivos no generados con el comando que se explicará a su debido momento.

Kitchen 🍽

Se basa en un archivo llamado .kitchen.yaml que especifica:

  • Drivers: define el software que creará las plataformas para pruebas. Por ejemplo: Vagrant, Docker y EC2.
  • Provisioner: especifica como el Chef Client será emulado, las opciones mas comunes pueden ser Chef Solo el cual ejecuta recetas localmente y Chef Zero que te levanta un Chef Server ligero durante la ejecución, permitiéndote testear Cookbooks que requieran, por ejemplo, realizar consultas de información de nodos y data bags. Más adelante va la explicación del tipo de información con Ohai.
  • Verifier: la aplicación que ejecutará las pruebas.
  • Plataformas: sistemas operativos, por ejemplo: Ubuntu o CentOS.
  • Suites: definición de distintas plataformas que deben especificar el listado de recetas a ejecutar. Cada una de estas definida por “name” incluyendo el “runlist” correspondiente.

Para facilitar su uso, el archivo quedará lo mas minimalista posible:

---
driver:
name: vagrant

provisioner:
name: chef_zero
always_update_cookbooks: true

platforms:
- name: centos-7

suites:
- name: default
run_list:
- recipe[demoCookbook::default]

Las variaciones principales son que he sacado lo siguiente del generado por defecto:

# platforms:
- name: ubuntu-16.04
# verifier:
name: inspec
# suites:
verifier:
inspec_tests:
- test/integration/default
attributes:

Removí de plataformas ubuntu-16.04 ya que solamente queremos que levante un ambiente en CentOS 7. Respecto a Suites, las pruebas de integración y atributos, por el momento no.

🔗 Mas info: kitchen.yml

Crear un directorio 🍴

Dentro de recipes/default.rb copia lo siguiente:

directory '/etc/demo/' do
owner 'root'
group 'root'
mode '0755'
action :create
end

Dentro del .kitchen.yaml la lista ejecución de recetas sobre el nodo esta definida de la siguiente formarecipe[demoCookbook::default] la cual apunta a default.rb.

Con el contenido agregado previamente defines que /etc/demo pertenecerá al grupo root, su dueño será root, sus permisos ‘0755’ cuyo 0 no afecta, así que puede escribirse como ‘755’ y la acción de este recurso será ser creado.

Lo que resta son solo 3 comandos:

  • kitchen create: creará la máquina virtual en este caso, un CentOS 7.
  • kitchen converge: ejecutará la receta sobre la máquina virtual creada, a modo de chef-client.
  • kitchen login: facilita el login al ambiente creado.

Ejecuta kitchen create; kitchen converge, debe ser visible que todo fue ejecutado correctamente. Pese a que este nodo creado tiene el nombre “default”, notarás que se le agrego una extensión con el tipo de SO, lo cual es de esperar ya que el enfoque es tener en conocimiento las distintas plataformas en que ha sido ejecutadas las pruebas.

De ir todo bien, el output luego de kitchen converge, sería:

# Outputresolving cookbooks for run list: ["demoCookbook2::default"]
Synchronizing Cookbooks:
- demoCookbook2 (0.1.0)
Installing Cookbook Gems:
Compiling Cookbooks...
Converging 1 resources
Recipe: demoCookbook2::default
* directory[/etc/demo/] action create
- create new directory /etc/demo/
- change mode from '' to '0755'
- change owner from '' to 'root'
- change group from '' to 'root'
- restore selinux security context

⭐️ TIP: si tienes problemas con la creación de la máquina, en macOS probablemente se le esté bloqueando el acceso y en Windows, la virtualización no se encuentre habilitada.

Utilizando kitchen login, cambia a root con sudo -s y dirígete a la carpeta ubicada en /etc/demo. Felicitaciones tienes una carpeta 👏.

Pero no es todo, podemos extender el post otros 4 minutos, que tal ahora ¿5 carpetas?

En vez de repetir 5 veces el recurso, usa un poco de Ruby. Editando el mismo archivo en recipes/default.rb:

%w(demo0 demo1 demo2 demo3 demo4).each do |dir|
directory "/etc/#{dir}" do
owner 'root'
group 'root'
mode '0755'
action :create
end
end

Explicando lo anterior, %w defines un array de palabras, en este caso los nombres de cada directorio, .each itera sobre cada una de estas palabras y dir corresponde al valor que se utilizará en cada iteración.

Resumiéndolo, por cada palabra dentro del array, repetirá lo que se encuentra dentro del primer do y el ultimo end, que corresponde a:

directory "/etc/#{dir}" do
owner 'root'
group 'root'
mode '0755'
action :create
end

¿Algo cambió desde la primera versión?

Así es, antes el directorio estaba definido como '/etc/demo', mientras que la nueva versión utiliza comillas dobles y #{dir}.

En Ruby, para agregar una variable dentro de un string se deben utilizar comillas dobles en vez de simples y esta debe ir dentro de los brackets de #{}.

Al volver a ejecutar kitchen converge, tendrás tus 5 directorios tan esperados.

Crear un directorio con un par de variables

Volvamos al punto inicial, digamos que necesitas que tu directorio, pueda ser definido como una variable, en caso de que se te antoje alterar el nombre sin tener que meter mas mano.

directory '/etc/demo/' do
owner 'root'
group 'root'
mode '0755'
action :create
end

Cuando defines una variable ve desde lo más general siendo por ejemplo el nombre de una aplicación o servicio, hasta un atributo especifico. Cosa de que después no te topes al editar atributos, aunque eso ya es un poco mas avanzado. El ejemplo de la documentación de Attributes:

node.default['apache']['dir']          = '/etc/apache2'
node.default['apache']['listen_ports'] = [ '80','443' ]

Para efectos prácticos la definición será la siguiente:

node.default['demo']['path'] = '/etc/demo'

Dependiendo de la versión que uses de Chef o Kitchen, puede considerar el node. como implícito, permitiéndote omitirlo. Pero en este caso con la versión 2.6.0 debe ir escrito cómo:node.default['demo']['path']

Para utilizar esta variable en la receta, debes escribirla de esta forma: node['demo']['path']

node.default['demo']['path'] = '/etc/demo'directory node['demo']['path'] do
owner 'root'
group 'root'
mode '0755'
action :create
end

Otra forma de hacerlo, es definirla con una variable mas corta:

node.default['demo']['path'] = '/etc/demo'dir = node['demo']['path']directory dir do
owner 'root'
group 'root'
mode '0755'
action :create
end

¿Dónde lo agrego?

Usualmente va en el archivo de atributos, attributes/default.rb, perfectamente puedes agregarlo en el mismo archivo donde tienes el tus definiciones, tanto por tema de orden y debido a que la precedencia de atributos juega un papel fundamental a la hora de sobreescribir una variable en particular.

Si te preguntabas porque node.default['demo']['path'] lleva un “default”, es debido a que este es uno de los tipos de atributos, el cual es de los mas bajos y fáciles de sobreescribir.

En todos los años por venir, en tus momentos mas privados, recordarás la siguiente imagen, cuya primera columna representa el tipo de atributo y la primera fila la ubicación de la variable:

Chef Attributes.

El número 15 es bastante llamativo, corresponde a los atributos de nivel automático, recolectados por Ohai, esto evita que los atributos sean sobrescritos después de que chef-client aplica las configuraciones.

Chef Client es el agente que corre localmente en los nodos pertenecientes a Chef. Cuando se ejecuta chef-client, consulta al Chef Server para los detalles de configuración como recetas, templates y distribución de archivos. Luego este hace todo lo posible en los nodos para definir el estado esperado.

¿Qué es Ohai?

Es una herramienta que recolecta información sobre un sistema en particular, que le es entregada al Chef-Client para ser utilizada en conjunto a los cookbooks.

Se utiliza al comienzo de la ejecución de chef-client para determinar el estado de un sistema. Por lo cual, si te preguntabas de cómo sacaba Chef información sobre sistema operativo, red, memoria, disco, CPU, Host Names y muuuchas otras cosas útiles, Ohai es la respuesta.

Para acceder a esta información puedes utilizar Ohai que viene incluida en el Chef-Client.

$ ohai

La información que entrega ante las consultas con Ohai es en JSON. Para Chef-Client esta información es accesible mediante el node object, que también es un JSON que contiene la información obtenida por Ohai y otros atributos definidos por el usuario.

Por ejemplo, la consulta local con Ohai y la consulta utilizada dentro de una receta:

  • ohai fqdn == node['FQDN']
  • ohai ipaddress == node['ipaddress']
  • ohai platform == node['platform']

El ejemplo que me gusta sobre Ohai es el utilizado en el curso de Udemy “Chef fundamentals a recipe for automating infrastructure”, el cual es este:

file '/etc/motd' do
content "
This server is the property of Chef
HOSTNAME: #{node['hostname']}
IPADDRESS: #{node['ipaddress']}
CPU: #{node['cpu']['0']['mhz']}
MEMORY: #{node['memory']['total']}
"
end

Modifica el Message Of The Day con información especifica del nodo en que se ejecute Chef. Puedes crearla como una receta llamada motd.rb y agregarla al run list del .kitchen.yaml de la siguiente forma:

suites:
- name: default
run_list:
- recipe[demoCookbook::default]
- recipe[demoCookbook::motd]

🎁 Bonus: lo más común que puedes encontrar “enredado” es cuando se itera sobre los valores de Ohai. Usualmente utilizarás un map o select y en base al key, value obtendrás valores específicos.

Retomando el ejemplo, el archivo default.rb dentro de atributos, debería verse de la siguiente forma:

# attributes/default.rb#default['demo']['path'] = '/etc/demo'
node.default['demo']['path'] = '/etc/demo'

Mientras que la receta de esta forma:

# recipes/default.rbdir = node['demo']['path']directory dir do
owner 'root'
group 'root'
mode '0755'
action :create
end

También puedes definir atributos dentro de la definición del nodo en suites, pero por extensión, dejo en tus manos investigar más al respecto.

Al volver a ejecutar kitchen converge, detectará que no hubo cambios en las definiciones del recurso.

# OutputSynchronizing Cookbooks:
- demoCookbook2 (0.1.0)
Installing Cookbook Gems:
Compiling Cookbooks...
Converging 1 resources
Recipe: demoCookbook2::default
* directory[/etc/demo] action create (up to date)

Modifica la variable a "/etc/demo2" para que te cree un nuevo directorio.

# OutputSynchronizing Cookbooks:
- demoCookbook2 (0.1.0)
Installing Cookbook Gems:
Compiling Cookbooks...
Converging 1 resources
Recipe: demoCookbook2::default
* directory[/etc/demo2] action create
- create new directory /etc/demo2
- change mode from '' to '0755'
- change owner from '' to 'root'
- change group from '' to 'root'
- restore selinux security context

Crea un nuevo directorio, ya que pasa a ser una nueva definición de recurso. En cambio si la variable hubiese involucrase uno de los atributos internos (no recomendado), cambiando ‘0755’ a ‘0666’ solo se hubiese actualizado el directorio.

Palabras finales

De los cursos que me gustaría recomendar:

  • Chef fundamentals a recipe for automating infrastructure — Disponible en Udemy, con un enfoque bastante bueno para ir explorando y comprendiendo como trabajar con Chef a nivel de recetas y aborda bastante bien otros conceptos.
  • Basic Chef Fluency Badge Disponible en Linux Academy, se enfoca mas en el trabajar un ambiente con Chef Server y explicar la relación entre los distintos componentes, abordando los temas que entran en la certificación en Chef.

Sip, luego de una linda explicación de 10 minutos, más tarea para la casa.

Este post surge a partir de mi búsqueda del punto medio entre ambos cursos, permitiendo aprender sobre la marcha sin perder los elementos de un ambiente con Chef Server, ni sacrificar velocidad, por no decir “destruir todo el ambiente en cuanto se me diese la gana”

Por lo cual, si te quedó más claro y/o te ayudo a decidir que enfoque prefieres darle para familiarizarte, el post cumplió su objetivo. Ademas de hacer las referencias correspondientes a Bob Esponja.

--

--