Skip to main content

Build

O build faz referencia aos sources podendo ter provisionados que irão criar o artefato, por exemplo uma imagem, e os pos processadores que irão manipular resolver o que fazer com o artefato.

Sources

O build precisa de pelo menos um source bloco source definido. Este bloco pode ser referenciado várias vezes dentro do build para gerar vários construtores diferentes e os comandos do provisioner serão executados dentro de cada um dos construtores selecionados.

Sabendo isso, é de boa prática declarar nos blocos source valores que não mudam e as mudanças declarar dentro da referencia ao source no build como no exemplo que vou mostrar.

O exemplo abaixo ilustra o source do tipo null que não é realmente um construtor, apenas configura uma conexão SSH e executa os provisionadores. Ele pode ser usado para depurar provisionadores sem incorrer em altos tempos de espera. Não cria nenhum tipo de imagem ou artefato.

Null

Uma boa prática é definir o name no build para aparecer na saída.


# Comunicator é obrigatório pode ser ssh winrm ou none no caso de null
# Dependendo do source source ssh deve ser definido por exemplo; no caso da aws e no caso da azure ssh é o padrão uma vez que todas as máquinas tem o user azure.
variable "pass" {
description = "My password"
type = string
# usando função para pegar minha variável exportada
default = env("MY_PASS")
}

source "null" "first-example" {
communicator = "none"
}

# Pode ter vários sources do mesmo tipo, mas nunca com o mesmo nome
source "null" "second-example" {
ssh_host= "127.0.0.1"
ssh_username = "david"
ssh_password = var.pass
# O argumento abaixo é necessário mas eu vou setar ele lá no bloco build
# Se esse argumento for setado aqui não é possível setar lá.
# ssh_password = "xxxxxx"
}

build {
name = "MeuNullBuild"
# usando o primeiro construtor e adicionando a variável name
source "null.first-example" {
name = "consul"
}
# usando o primeiro construtor e adicionando uma variável extra
source "null.first-example" {
name = "nomad"
}
source "null.second-example" {
name = "vault"
}

sources = ["null.first-example","null.second-example"]

# É possível referenciar name e type dos sources da seguinte maneira
# Não é possível referenciar outras variáveis 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 executar o build abaixo e é necessário que habilitar o ssh na sua máquina para permitir autenticação por senha e iniciar o serviço do sshd. Confira o projeto e faça seus testes no exemplo rodando o comando dentro da pasta.

~/gitlab/personal/study-packer/examples/build main ⇡3 !3 ?1
❯ packer build .
MeuNullBuild.null.first-example: output will be in this color.
MeuNullBuild.null.second-example: output will be in this color.
MeuNullBuild.null.consul: output will be in this color.
MeuNullBuild.null.nomad: output will be in this color.
MeuNullBuild.null.vault: output will be in this color.

==> MeuNullBuild.null.vault: Using SSH communicator to connect: 127.0.0.1
==> MeuNullBuild.null.second-example: Using SSH communicator to connect: 127.0.0.1
==> MeuNullBuild.null.vault: Waiting for SSH to become available...
==> MeuNullBuild.null.second-example: Waiting for SSH to become available...
==> MeuNullBuild.null.nomad: Running local shell script: /tmp/packer-shell4018821226
==> MeuNullBuild.null.consul: Running local shell script: /tmp/packer-shell24449155
==> MeuNullBuild.null.first-example: Running local shell script: /tmp/packer-shell590834333
MeuNullBuild.null.nomad: nomad and null TEST PROVISIONER
MeuNullBuild.null.consul: consul and null TEST PROVISIONER
==> MeuNullBuild.null.nomad: Running post-processor: (type shell-local)
==> MeuNullBuild.null.consul: Running post-processor: (type shell-local)
MeuNullBuild.null.first-example: first-example and null TEST PROVISIONER
==> MeuNullBuild.null.first-example: Running post-processor: (type shell-local)
==> MeuNullBuild.null.nomad (shell-local): Running local shell script: /tmp/packer-shell661443636
==> MeuNullBuild.null.consul (shell-local): Running local shell script: /tmp/packer-shell3268706950
==> MeuNullBuild.null.first-example (shell-local): Running local shell script: /tmp/packer-shell3568713064
MeuNullBuild.null.nomad (shell-local): nomad OR null TEST POST-PROCESSOR
Build 'MeuNullBuild.null.nomad' finished after 12 milliseconds 418 microseconds.
MeuNullBuild.null.consul (shell-local): consul OR null TEST POST-PROCESSOR
Build 'MeuNullBuild.null.consul' finished after 12 milliseconds 632 microseconds.
MeuNullBuild.null.first-example (shell-local): first-example OR null TEST POST-PROCESSOR
Build 'MeuNullBuild.null.first-example' finished after 14 milliseconds 121 microseconds.
==> MeuNullBuild.null.vault: Connected to SSH!
==> MeuNullBuild.null.second-example: Connected to SSH!
==> MeuNullBuild.null.vault: Running local shell script: /tmp/packer-shell1580315204
==> MeuNullBuild.null.second-example: Running local shell script: /tmp/packer-shell2396870170
MeuNullBuild.null.vault: vault and null TEST PROVISIONER
==> MeuNullBuild.null.vault: Running post-processor: (type shell-local)
MeuNullBuild.null.second-example: second-example and null TEST PROVISIONER
==> MeuNullBuild.null.second-example: Running post-processor: (type shell-local)
==> MeuNullBuild.null.vault (shell-local): Running local shell script: /tmp/packer-shell1814621561
==> MeuNullBuild.null.second-example (shell-local): Running local shell script: /tmp/packer-shell4080843186
MeuNullBuild.null.vault (shell-local): vault OR null TEST POST-PROCESSOR
Build 'MeuNullBuild.null.vault' finished after 165 milliseconds 197 microseconds.
MeuNullBuild.null.second-example (shell-local): second-example OR null TEST POST-PROCESSOR
Build 'MeuNullBuild.null.second-example' finished after 167 milliseconds 229 microseconds.

==> Wait completed after 167 milliseconds 336 microseconds

==> Builds finished. The artifacts of successful builds are:
--> MeuNullBuild.null.nomad: Did not export anything. This is the null builder
--> MeuNullBuild.null.nomad: Did not export anything. This is the null builder
--> MeuNullBuild.null.consul: Did not export anything. This is the null builder
--> MeuNullBuild.null.consul: Did not export anything. This is the null builder
--> MeuNullBuild.null.first-example: Did not export anything. This is the null builder
--> MeuNullBuild.null.first-example: Did not export anything. This is the null builder
--> MeuNullBuild.null.vault: Did not export anything. This is the null builder
--> MeuNullBuild.null.vault: Did not export anything. This is the null builder
--> MeuNullBuild.null.second-example: Did not export anything. This is the null builder
--> MeuNullBuild.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 builde

Para o build poder acessar a máquina existem duas formas, ou declarar ter ssh no caso do Linux ou winrm no caso do windows. Geralmente usa-se ssh. Se não existir um comunicador definido, a maioria dos passos do build não funcionarão.

Os plugins estendem os tipos dos sources e provisioners.

Provisioners

Os provisioners usam trabalham em cima da instancia disponibilizada pelos sources e após o boot instala e configura a imagem da máquina após o boot.

Todos os provisionadores tem parâmetros comuns

  • pause_before(duração) - Dorme por um período antes da execução.

  • max_retries(int) - Máximo de vezes que o provisionador tentará novamente em caso de falha. O padrão é zero (0).

  • only(array of string) - Execute o provisionador apenas para os provisioners listados pelo nome.

  • override(object) - Substitua o construtor com configurações diferentes para um construtor específico

Provisioners Nativos

  • file: Este provisionador é usado para fazer upload de um arquivo para dentro da máquinas disponibilizada pelos sources. Somente é possível fazer upload para diretório que o user utilizado no source para fazer o ssh tenha permissão. Uma boa prática é fazer para dentro de /tmp e depois com outro provisionador manipular os arquivos.

    build {
    provisioner "file" {
    # Os caminhos para os arquivos ou diretórios local para carregar na máquina
    sources = ["app.tar.gz"]
    # O caminho para onde o arquivo será carregado na máquina
    destination = "/tmp/app.tar.gz"
    # Se for setado download irá puxar arquivo da vm para dentro da máquina local. O padrão é upload.
    direction = "upload"
    }
    }

    Usa-se o direction como download muitas vezes para puxar algo no post processor.

  • shell: Este provisionador irá executar shell scripts. É o principal provisionador. É com ele que se manipula os files que foram carregados na vm. Uma boa prática na minha opinião é sempre referenciar scripts em arquivos ao invés de mostrar comando a comando. Isso evita poluição no código e organiza melhor as coisas. Por isso no projeto base existe uma pasta só para scripts. Uma das vantagens é sempre definir o shell que é usado quando se tem o script. Atenção no execute_command que é um atributo deste provisionador.

    • execute_command(string) - O comando a ser usado para executar o script. Por padrão, é chmod +x {{ .Path }}; {{ .Vars }} {{ .Path }}. Se o usuário definir um arquivo onde estão as variáveis de ambiente o padrão do comando será chmod +x {{.Path}}; . {{.EnvVarFile}} && {{.Path}} existem três variáveis ​​extras disponíveis:
      • Path é o caminho para ser executado
      • Vars é a lista de environment_vars se estiver definida
      • EnvVarFile é o caminho para o arquivo que contém env vars, se use_env_var_file for true
      build {
    provisioner "shell" {
    environment_vars = [
    "MY_TEST=david",
    "MY_TEST2=puziol"
    ]
    # Exemplo de como excutar com 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"
    ]
    }
    }
  • shell-local: executará um script na máquina em que o Packer está sendo executado. Os parâmetros são os mesmos do provisionador shell. Bastante usado durante o post-processor para executar comandos.

Vale mencionar que existem algumas envs que são default.

  • PACKER_BUILD_NAME : usado para diferenciar o nome entre as compilações senão todas usaram o mesmo name do build.
  • PACKER_BUILDER_TYPE : define o tipo do construtor que foi usado para criar a máquina que vai executar o script.
  • PACKER_HTTP_ADDR: Só é usado caso o construtor forneça um http server para transferência de arquivo

NÃO ADIANTA FALAR DE TUDO QUE EXISTE, VOCÊ PRECISA SABER QUE EXISTE, E QUANDO VC PRECISAR USAR VC ESTUDA. ESSES SÃO OS PRINCIPAIS.

Como encaminhar seu agente ssh local para a 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"
]
}

Porém existe usar o plugin do git facilitaria as coisas e garantiria maior segurança.

Post-processors

É a ultima coisa a ser executado, após os construtores e provisionadores. São opcionais e usados para resolver o que fazer com os artefatos.

  • Fazer upload
  • Reempacotar
  • Checksum
  • Manifestos
  • Carregar em um bucket s3
  • etc