Shell
类型:shell
Packer 的 shell
配置器使得 Packer 可以使用 shell 脚本构建的机器。 Shell 配置器是在计算机上安装和配置软件的最简单方法。
基础用法
HCL2:
provisioner "shell" {
inline = ["echo foo"]
}
Json:
{
"type": "shell",
"inline": ["echo foo"]
}
配置参数
下面列出了可用配置选项的参考。唯一必须的元素是 inline
或 script
。每个其他选项都是可选的。
以下参数必须选一配置:
inline
(array of string)- 这是一组要执行的命令。这些命令由换行符连接起来并变成一个文件,因此它们都在相同的上下文中执行。这使得您可以在一个命令中更改目录,并在下一个命令中使用目录中的内容,依此类推。内联脚本是在机器内完成简单任务的最简单方法。script
(string) - 要上传到机器并执行的脚本的路径。此路径可以是绝对路径或相对路径。如果是相对路径,就是相对于Packer执行时的工作目录。scripts
(array of string)- 要执行的脚本数组。脚本将按照指定的顺序上传和执行。每个脚本都是独立执行的,因此一个脚本中的变量等状态不会传递到下一个脚本。
可选参数:
binary
(boolean) - 如果为true
,则指定脚本是二进制文件,因此 Packer 不应将 Windows 行结尾转换为 Unix 行结尾(如果有的话)。默认为false
。valid_exit_codes
(整数列表)- 脚本的有效返回值。默认情况下全部是 0。env
(map(string))- 在execute_command
之前注入的键/值对映射。 Packer 也会默认将一些环境变量注入到环境中,这将在下面的部分中介绍。重复的env
设置会覆盖environment_vars
中的设置。environment_vars
([]string)- 在execute_command
之前注入的键/值对数组。格式应为key=value
。 Packer 也会默认将一些环境变量注入到环境中,这将在下面的部分中介绍。env_var_format
(string) - 当我们解析您提供的environment_vars
时,该参数会为我们提供一个字符串模板以确保我们正确设置环境变量。默认值为"%s='%s' "
。与use_env_var_file
结合使用时,默认值为"export %s='%s'\n"
use_env_var_file
(boolean) - 如果为true
,Packer 会将您的环境变量写入临时文件并从该文件获取它们,而不是在我们的execute_command
中内联声明它们。默认的execute_command
将是chmod +x { {.Path} }; . { {.EnvVarFile} } && { {.Path} }
。在大多数情况下,此选项是不必要的,但如果您在自定义execute_command
中有额外的引号,那么这可能是正确执行脚本所必需的。默认值:false
。execute_command
(string)- 用于执行脚本的命令。 默认值为chmod +x { { .Path } }; { { .Vars } } { { .Path } }
,除非 用户设置了"use_env_var_file": true
- 在这种情况下,默认execute_command
是chmod +x { {.Path} }; . { {.EnvVarFile} } && { {.Path} }
。 该值是一个模板引擎。因此,您可能在此字段中使用用户变量和模板函数。此外,还有三个可用的额外变量:Path
是要运行的脚本的路径Vars
是environment_vars
的列表(如果已配置)。EnvVarFile
是包含环境变量的文件的路径,如果use_env_var_file
是true
expect_disconnect
(boolean) - 默认为false
。当为true
时,允许服务器断开与 Packer 的连接而不抛出错误。如果您重新启动 SSH 服务器或重新启动主机,则连接可能会断开。inline_shebang
(string) - 运行inline
指定的命令时使用的 shebang 值。默认值为/bin/sh -e
。如果您不使用inline
,则此配置无效。重要提示:如果您对此进行自定义,请务必包含类似-e
标志的内容,否则个别步骤失败不会引发配置器失败。remote_folder
(string) - 上传的脚本将保存在机器上的文件夹。默认值为/tmp
。remote_file
(string) - 上传的脚本在机器上的文件名。默认值为script_nnn.sh
。remote_path
(string) - 上传脚本在远程机器上的完整路径。默认值是remote_folder/remote_file
,如果设置此选项将覆盖remote_folder
和remote_file
。skip_clean
(boolean) - 如果为true
,则指定上传到系统的脚本不会被 Packer 删除。默认值为false
(从系统中清理脚本)。start_retry_timeout
(string) - 尝试启动远程进程的时间量。默认情况下,默认值为5m
或 5 分钟。存在此设置是为了应对 SSH 可能重新启动的时间,例如系统重新启动。如果重新启动需要更长的时间,请将此设置为更高的值。pause_after
(string) - 配置 shell 脚本后等待的时间,如果前面的所有步骤都成功,则暂停。
所有配置器共有的参数:
pause_before
(duration) - 执行前休眠一段时间。max_retries
(int) - provisioner 在失败的情况下重试的最大次数。默认为零 (0)。零表示不会重试错误。only
(array of string) - 只运行列表中指定的的配置器程序。override
(object) - 使用不同设置覆盖指定配置器的配置,例如:
HCL2:
source "null" "example1" {
communicator = "none"
}
source "null" "example2" {
communicator = "none"
}
build {
sources = ["source.null.example1", "source.null.example2"]
provisioner "shell-local" {
inline = ["echo not overridden"]
override = {
example1 = {
inline = ["echo yes overridden"]
}
}
}
}
Json:
{
"builders": [
{
"type": "null",
"name": "example1",
"communicator": "none"
},
{
"type": "null",
"name": "example2",
"communicator": "none"
}
],
"provisioners": [
{
"type": "shell-local",
"inline": ["echo not overridden"],
"override": {
"example1": {
"inline": ["echo yes overridden"]
}
}
}
]
}
timeout
(duration)- 如果配置器完成时间超过配置值(例如1h10m1s
或10m
),则配置器将超时并失败。
执行命令的例子
对于许多新用户来说,execute_command
令人费解。但是,它提供了一个重要的功能:自定义命令的执行方式。最常见的用例是处理 sudo 密码提示
。如果您使用非 POSIX shell,例如 FreeBSD 上的 tcsh
,您可能还需要自定义该参数。
Sudo 的例子
某些操作系统默认为非根用户。例如,如果您以 ubuntu
身份登录并且使用密码 packer
执行 sudo
,那么您需要将 execute_command
更改为:
"echo 'packer' | sudo -S sh -c '{{ .Vars }} {{ .Path }}'"
-S
标志告诉 sudo
从 stdin 读取密码,在本例中,它是通过管道输入了 packer
这个值。
如果您的环境变量包含空格或单引号,则上述示例将不起作用;这种情况下尝试删除单引号:
"echo 'packer' | sudo -S env {{ .Vars }} {{ .Path }}"
通过这样配置 execute_command
,您的脚本可以以 root 权限运行,而不必担心密码提示。
FreeBSD 的例子
FreeBSD 的默认 shell 是 tcsh
,它偏离了 POSIX 语义。为了让 Packer 程序传递环境变量,您需要将 execute_command
更改为:
chmod +x {{ .Path }}; env {{ .Vars }} {{ .Path }}
注意在 { { .Vars } }
之前添加了 env
。
默认的环境变量
除了能够使用 environment_vars
配置自定义环境变量外,配置器还会自动定义某些常用的环境变量:
PACKER_BUILD_NAME
值为 Packer 正在运行的构建的名称。这在 Packer 进行多个构建并且您希望将它们彼此能有所区分时最有用。PACKER_BUILDER_TYPE
是用于创建运行脚本的机器的构建器类型。如果您只想在使用特定构建器构建的系统上运行脚本的特定部分,这将很有用。PACKER_HTTP_ADDR
如果使用为文件传输提供 HTTP 服务器的构建器(例如hyperv
、parallels
、qemu
、virtualbox
和vmware
),这将被设置为 HTTP 服务器地址。您可以在配置器中使用此地址通过 HTTP 下载大文件。如果您在使用默认文件配置器时遇到较慢的速度,这可能很有用。使用winrm
通信器的file
配置器可能会遇到这些类型的困难。
处理重启
配置器有时需要重启,通常是在更新操作系统时。 Packer 的 shell
配置器可以处理重启的情况。
Packer 通过重试执行脚本一段时间直到超时失败来处理此问题。这将允许机器有时间启动并准备好运行脚本。 配置器等待的时间是使用 start_retry_timeout
配置的,默认为几分钟。
有时,当执行诸如 reboot
之类的命令时,shell 脚本将返回并且 Packer 将在 SSH 实际退出并重新启动机器之前开始执行下一个命令。为此,使用 pause_before
让 Packer 在执行下一个脚本之前等待:
HCL2:
provisioner "shell" {
script = "script.sh"
pause_before = "10s"
timeout = "10s"
}
Json:
{
"type": "shell",
"script": "script.sh",
"pause_before": "10s",
"timeout": "10s"
}
某些操作系统配置无法在重启时正确终止所有网络连接,导致配置器在主机重启后挂起。在这种情况下,请确保在重启时或在 shell 脚本中关闭网络接口。例如,在 Gentoo 上:
/etc/init.d/net.eth0 stop
SSH Agent Forwarding
一些配置器需要从 Packer 实例中连接到远程 SSH 服务器。下面的示例是使用客户端上的 openssh 从私有 git 存储库中提取代码。确保您正在运行 ssh-agent
并使用 ssh-add /path/to/key
将您的 git repo SSH 密钥添加到其中。当 Packer 实例需要访问 SSH 密钥时,代理会将请求转发回您的 ssh-agent
。
注意:当通过 git 进行配置时,您应该将 git 服务器密钥添加到 ~/.ssh/known_hosts
文件中,否则 git 命令可能会挂起等待输入。这可以通过文件配置器复制文件(更安全)或使用 ssh-keyscan
填充文件(不太安全)来完成。后者访问 github 的一个例子是:
HCL2:
provisioner "shell" {
inline = [
"sudo apt-get install -y git",
"ssh-keyscan github.com >> ~/.ssh/known_hosts",
"git clone git@github.com:exampleorg/myprivaterepo.git"
]
}
Json:
{
"type": "shell",
"inline": [
"sudo apt-get install -y git",
"ssh-keyscan github.com >> ~/.ssh/known_hosts",
"git clone git@github.com:exampleorg/myprivaterepo.git"
]
}
故障排查
我的 shell 脚本在 Ubuntu 上不能正常工作!
- 在 Ubuntu 上,
/bin/sh shell
是 dash。如果您的脚本中包含特定于 bash) 的命令,请将#!/bin/bash -e
放在脚本的顶部。可以在 Ubuntu wiki 的 DashAsBinSh 页面上找到 dash 和 bash 之间的区别。
当我登录时,我的 shell 可以工作,但 shell 配置器失败了
- 请参阅上面的提示。更有可能的是,您的登录 shell 使用
/bin/bash
,而配置程序使用/bin/sh
。
使用 apt-get
或 yum
时安装程序挂起
- 确保在命令中添加
-y
以防止它要求用户输入才能继续执行。
我如何知道我的 shell 脚本在做什么?
- 将
-x
标志添加到脚本顶部的 shebang (#!/bin/sh -x
) 将在脚本语句执行时回显脚本语句。
我的构建并不总是一样
- 一些发行版在其他核心服务之前启动 SSH 守护进程,这会产生竞争条件。您的第一个配置器可以阻塞配置直至 SSH 守护进程完全启动后再运行。
HCL2:
provisioner "shell" {
inline = ["sleep 10"]
}
Json:
{
"type": "shell",
"inline": ["sleep 10"]
}
环境变量中使用双引号
Packer 会为您管理双引号,因此您不必担心。以下是 Packer 模板输入的示例以及您应该期望得到的内容:
HCL2:
provisioner "shell" {
environment_vars = [
"FOO=foo",
"BAR=bar's",
"BAZ=baz=baz",
"QUX==qux",
"FOOBAR=foo bar",
"FOOBARBAZ='foo bar baz'",
"QUX2=\"qux\""
]
inline = [
"echo \"FOO is $FOO\"",
"echo \"BAR is $BAR\"",
"echo \"BAZ is $BAZ\"",
"echo \"QUX is $QUX\"",
"echo \"FOOBAR is $FOOBAR\"",
"echo \"FOOBARBAZ is $FOOBARBAZ\"",
"echo \"QUX2 is $QUX2\""
]
}
Json:
"provisioners": [
{
"type": "shell",
"environment_vars": ["FOO=foo",
"BAR=bar's",
"BAZ=baz=baz",
"QUX==qux",
"FOOBAR=foo bar",
"FOOBARBAZ='foo bar baz'",
"QUX2=\"qux\""],
"inline": ["echo \"FOO is $FOO\"",
"echo \"BAR is $BAR\"",
"echo \"BAZ is $BAZ\"",
"echo \"QUX is $QUX\"",
"echo \"FOOBAR is $FOOBAR\"",
"echo \"FOOBARBAZ is $FOOBARBAZ\"",
"echo \"QUX2 is $QUX2\""]
}
]
输出:
docker: FOO is foo
docker: BAR is bar's
docker: BAZ is baz=baz
docker: QUX is =qux
docker: FOOBAR is foo bar
docker: FOOBARBAZ is 'foo bar baz'
docker: QUX2 is "qux"