Build
El build hace referencia a los sources pudiendo tener provisionadores que crearán el artefacto, por ejemplo una imagen, y los post-procesadores que manipularán o resolverán qué hacer con el artefacto.
Sources
El build necesita al menos un bloque source definido. Este bloque puede ser referenciado varias veces dentro del build para generar varios constructores diferentes y los comandos del provisioner serán ejecutados dentro de cada uno de los constructores seleccionados.
Sabiendo esto, es buena práctica declarar en los bloques source valores que no cambian y los cambios declararlos dentro de la referencia al source en el build como en el ejemplo que voy a mostrar.
El ejemplo a continuación ilustra el source del tipo null que no es realmente un constructor, solo configura una conexión SSH y ejecuta los provisionadores. Puede ser usado para depurar provisionadores sin incurrir en altos tiempos de espera. No crea ningún tipo de imagen o artefacto.
Una buena práctica es definir el name en el build para que aparezca en la salida.
# Communicator es obligatorio puede ser ssh, winrm o none en el caso de null
# Dependiendo del source, ssh debe ser definido por ejemplo; en el caso de AWS y en el caso de Azure ssh es el estándar una vez que todas las máquinas tienen el usuario azure.
variable "pass" {
description = "Mi contraseña"
type = string
# Usando función para obtener mi variable exportada
default = env("MY_PASS")
}
source "null" "first-example" {
communicator = "none"
}
# Puede tener varios sources del mismo tipo, pero nunca con el mismo nombre
source "null" "second-example" {
ssh_host= "127.0.0.1"
ssh_username = "david"
ssh_password = var.pass
# El argumento a continuación es necesario pero lo voy a establecer allá en el bloque build
# Si este argumento se establece aquí no es posible establecerlo allá.
# ssh_password = "xxxxxx"
}
build {
name = "MiNullBuild"
# Usando el primer constructor y agregando la variable name
source "null.first-example" {
name = "consul"
}
# Usando el primer constructor y agregando una variable extra
source "null.first-example" {
name = "nomad"
}
source "null.second-example" {
name = "vault"
}
sources = ["null.first-example","null.second-example"]
# Es posible referenciar name y type de los sources de la siguiente manera
# No es posible referenciar otras variables como othername usando source.othername
provisioner "shell-local" {
inline = ["echo ${source.name} and ${source.type} TEST PROVISIONER"
]
}
post-processor "shell-local" {
inline = ["echo ${source.name} OR ${source.type} TEST POST-PROCESSOR"]
}
}
Para ejecutar el build a continuación es necesario habilitar ssh en tu máquina para permitir autenticación por contraseña e iniciar el servicio sshd.
Revisa el proyecto y haz tus pruebas en el ejemplo ejecutando el comando dentro de la carpeta.
~/gitlab/personal/study-packer/examples/build main ⇡3 !3 ?1
❯ packer build .
MiNullBuild.null.first-example: output will be in this color.
MiNullBuild.null.second-example: output will be in this color.
MiNullBuild.null.consul: output will be in this color.
MiNullBuild.null.nomad: output will be in this color.
MiNullBuild.null.vault: output will be in this color.
==> MiNullBuild.null.vault: Using SSH communicator to connect: 127.0.0.1
==> MiNullBuild.null.second-example: Using SSH communicator to connect: 127.0.0.1
==> MiNullBuild.null.vault: Waiting for SSH to become available...
==> MiNullBuild.null.second-example: Waiting for SSH to become available...
==> MiNullBuild.null.nomad: Running local shell script: /tmp/packer-shell4018821226
==> MiNullBuild.null.consul: Running local shell script: /tmp/packer-shell24449155
==> MiNullBuild.null.first-example: Running local shell script: /tmp/packer-shell590834333
MiNullBuild.null.nomad: nomad and null TEST PROVISIONER
MiNullBuild.null.consul: consul and null TEST PROVISIONER
==> MiNullBuild.null.nomad: Running post-processor: (type shell-local)
==> MiNullBuild.null.consul: Running post-processor: (type shell-local)
MiNullBuild.null.first-example: first-example and null TEST PROVISIONER
==> MiNullBuild.null.first-example: Running post-processor: (type shell-local)
==> MiNullBuild.null.nomad (shell-local): Running local shell script: /tmp/packer-shell661443636
==> MiNullBuild.null.consul (shell-local): Running local shell script: /tmp/packer-shell3268706950
==> MiNullBuild.null.first-example (shell-local): Running local shell script: /tmp/packer-shell3568713064
MiNullBuild.null.nomad (shell-local): nomad OR null TEST POST-PROCESSOR
Build 'MiNullBuild.null.nomad' finished after 12 milliseconds 418 microseconds.
MiNullBuild.null.consul (shell-local): consul OR null TEST POST-PROCESSOR
Build 'MiNullBuild.null.consul' finished after 12 milliseconds 632 microseconds.
MiNullBuild.null.first-example (shell-local): first-example OR null TEST POST-PROCESSOR
Build 'MiNullBuild.null.first-example' finished after 14 milliseconds 121 microseconds.
==> MiNullBuild.null.vault: Connected to SSH!
==> MiNullBuild.null.second-example: Connected to SSH!
==> MiNullBuild.null.vault: Running local shell script: /tmp/packer-shell1580315204
==> MiNullBuild.null.second-example: Running local shell script: /tmp/packer-shell2396870170
MiNullBuild.null.vault: vault and null TEST PROVISIONER
==> MiNullBuild.null.vault: Running post-processor: (type shell-local)
MiNullBuild.null.second-example: second-example and null TEST PROVISIONER
==> MiNullBuild.null.second-example: Running post-processor: (type shell-local)
==> MiNullBuild.null.vault (shell-local): Running local shell script: /tmp/packer-shell1814621561
==> MiNullBuild.null.second-example (shell-local): Running local shell script: /tmp/packer-shell4080843186
MiNullBuild.null.vault (shell-local): vault OR null TEST POST-PROCESSOR
Build 'MiNullBuild.null.vault' finished after 165 milliseconds 197 microseconds.
MiNullBuild.null.second-example (shell-local): second-example OR null TEST POST-PROCESSOR
Build 'MiNullBuild.null.second-example' finished after 167 milliseconds 229 microseconds.
==> Wait completed after 167 milliseconds 336 microseconds
==> Builds finished. The artifacts of successful builds are:
--> MiNullBuild.null.nomad: Did not export anything. This is the null builder
--> MiNullBuild.null.nomad: Did not export anything. This is the null builder
--> MiNullBuild.null.consul: Did not export anything. This is the null builder
--> MiNullBuild.null.consul: Did not export anything. This is the null builder
--> MiNullBuild.null.first-example: Did not export anything. This is the null builder
--> MiNullBuild.null.first-example: Did not export anything. This is the null builder
--> MiNullBuild.null.vault: Did not export anything. This is the null builder
--> MiNullBuild.null.vault: Did not export anything. This is the null builder
--> MiNullBuild.null.second-example: Did not export anything. This is the null builder
--> MiNullBuild.null.second-example: Did not export anything. This is the null builder
❯ packer build .
roles.null.first-example: output will be in this color.
roles.null.second-example: output will be in this color.
roles.null.consul: output will be in this color.
roles.null.nomad: output will be in this color.
roles.null.vault: output will be in this color.
==> roles.null.second-example: Running local shell script: /tmp/packer-shell2204105492
==> roles.null.nomad: Running local shell script: /tmp/packer-shell3063727616
==> roles.null.consul: Running local shell script: /tmp/packer-shell2232404743
==> roles.null.vault: Running local shell script: /tmp/packer-shell607664062
==> roles.null.first-example: Running local shell script: /tmp/packer-shell3215393418
roles.null.consul: consul and null
roles.null.nomad: nomad and null
roles.null.second-example: second-example and null
roles.null.first-example: first-example and null
Build 'roles.null.nomad' finished after 52 milliseconds 323 microseconds.
Build 'roles.null.second-example' finished after 53 milliseconds 750 microseconds.
Build 'roles.null.consul' finished after 53 milliseconds 860 microseconds.
Build 'roles.null.first-example' finished after 54 milliseconds 339 microseconds.
roles.null.vault: vault and null
Build 'roles.null.vault' finished after 65 milliseconds 939 microseconds.
==> Wait completed after 66 milliseconds 290 microseconds
==> Builds finished. The artifacts of successful builds are:
--> roles.null.nomad: Did not export anything. This is the null builder
--> roles.null.second-example: Did not export anything. This is the null builder
--> roles.null.consul: Did not export anything. This is the null builder
--> roles.null.first-example: Did not export anything. This is the null builder
--> roles.null.vault: Did not export anything. This is the null builder
Para que el build pueda acceder a la máquina existen dos formas, o declarar ssh en el caso de Linux o winrm en el caso de Windows. Generalmente se usa ssh. Si no existe un comunicador definido, la mayoría de los pasos del build no funcionarán.
Los plugins extienden los tipos de sources y provisioners.
Provisioners
Los provisioners trabajan sobre la instancia disponibilizada por los sources y después del boot instalan y configuran la imagen de la máquina después del boot.
Todos los provisionadores tienen parámetros comunes
-
pause_before(duración) - Duerme por un período antes de la ejecución.
-
max_retries(int) - Máximo de veces que el provisionador intentará nuevamente en caso de fallo. El estándar es cero (0).
-
only(array of string) - Ejecute el provisionador solo para los provisioners listados por nombre.
-
override(object) - Sustituya el constructor con configuraciones diferentes para un constructor específico
Provisioners Nativos
-
file: Este provisionador se utiliza para cargar un archivo dentro de las máquinas disponibilizadas por los sources. Solo es posible hacer upload a directorio que el usuario utilizado en el source para hacer ssh tenga permiso. Una buena práctica es hacer para dentro de /tmp y después con otro provisionador manipular los archivos.build {
provisioner "file" {
# Las rutas para los archivos o directorios locales para cargar en la máquina
sources = ["app.tar.gz"]
# La ruta hacia donde el archivo será cargado en la máquina
destination = "/tmp/app.tar.gz"
# Si se establece download traerá archivo de la vm hacia dentro de la máquina local. El estándar es upload.
direction = "upload"
}
}Se usa direction como download muchas veces para traer algo en el post processor.
-
shell: Este provisionador ejecutará scripts shell. Es el provisionador principal. Es con él que se manipulan los files que fueron cargados en la vm. Una buena práctica en mi opinión es siempre referenciar scripts en archivos en lugar de mostrar comando a comando. Esto evita contaminación en el código y organiza mejor las cosas. Por eso en el proyecto base existe una carpeta solo para scripts. Una de las ventajas es siempre definir el shell que se usa cuando se tiene el script. Atención en execute_command que es un atributo de este provisionador.- execute_command(string) - El comando a usar para ejecutar el script. Por defecto, es
chmod +x {{ .Path }}; {{ .Vars }} {{ .Path }}. Si el usuario define un archivo donde están las variables de entorno, el comando por defecto seráchmod +x {{.Path}}; . {{.EnvVarFile}} && {{.Path}}existen tres variables extra disponibles:- Path es la ruta a ser ejecutada
- Vars es la lista de environment_vars si está definida
- EnvVarFile es la ruta al archivo que contiene env vars, si use_env_var_file es true
build {
provisioner "shell" {
environment_vars = [
"MY_TEST=david",
"MY_TEST2=puziol"
]
# Ejemplo de cómo ejecutar con sudo
# "echo 'packer' | sudo -S sh -c '{{ .Vars }} {{ .Path }}'"
# "echo 'packer' | sudo -S env {{ .Vars }} {{ .Path }}"
execute_command = "{{ .Vars }} /bin/bash '{{ .Path }}'"
scripts = [
"./scripts/script1.sh",
"./scripts/script2.sh",
"./scripts/script3.sh"
]
}
} - execute_command(string) - El comando a usar para ejecutar el script. Por defecto, es
-
shell-local: ejecutará un script en la máquina en que Packer está siendo ejecutado. Los parámetros son los mismos del provisionador shell. Bastante usado durante el post-processor para ejecutar comandos.
Vale mencionar que existen algunas envs que son por defecto.
- PACKER_BUILD_NAME: usado para diferenciar el nombre entre las compilaciones sino todas usarán el mismo name del build.
- PACKER_BUILDER_TYPE: define el tipo del constructor que fue usado para crear la máquina que va a ejecutar el script.
- PACKER_HTTP_ADDR: Solo se usa si el constructor provee un servidor http para transferencia de archivo
NO SIRVE HABLAR DE TODO LO QUE EXISTE, NECESITAS SABER QUE EXISTE, Y CUANDO LO NECESITES USAR LO ESTUDIAS. ESTOS SON LOS PRINCIPALES.
¿Cómo reenviar tu agente ssh local a la máquina?
provisioner "shell" {
inline = [
"sudo apt-get install -y git",
"ssh-keyscan github.com >> ~/.ssh/known_hosts",
"git clone [email protected]:exampleorg/myprivaterepo.git"
]
}
Sin embargo, usar el plugin de git facilitaría las cosas y garantizaría mayor seguridad.
Post-processors
Es lo último a ser ejecutado, después de los constructores y provisionadores. Son opcionales y usados para resolver qué hacer con los artefactos.
- Hacer upload
- Reempaquetar
- Checksum
- Manifiestos
- Cargar en un bucket S3
- etc