通过环境传递 bash 代码(对于 docker-compose)

Passing bash code through the environment (for docker-compose)

我正在尝试处理 docker-compose 容器启动顺序中的一个已知问题,所以我以一个简单的解决方案结束,有一个脚本来检查环境是否准备好启动命令。

就是这个想法,为容器定义了一些环境变量:

在那个例子中,我在启动我的应用程序之前等待 elasticSearch 响应,这取决于 ES 数据来启动。

以下脚本将是我在 docker 容器上的入口点。

导出的 ENV 变量

WAIT_COMMAND="$(curl --write-out %{http_code} --silent --output /dev/null http://elastic:9200/_cat/health?h=st) == 200"
LOOPS=3
START_CMD="python my_script_depending_on_elastic.py"
SLEEP=2

有了上面的 ENV 变量,脚本应该等到 ES 请求 return 代码 200

#!/bin/bash

is_ready() {
    eval $WAIT_COMMAND
}

# wait until is ready
i=0
while ! is_ready; do
    i=`expr $i + 1`
    if [ $i -ge $LOOPS ]; then
        echo "$(date) - still not ready, giving up"
        exit 1
    fi
    echo "$(date) - waiting to be ready"
    sleep $SLEEP
done

#start the script
exec $START_CMD

问题是代码在 is_ready 函数行中不起作用,它不是 return 布尔值,而是尝试执行 200作为命令

# ./wait_elastic.sh 
./wait_elastic.sh: line 9: 200: command not found
Fri Jul  3 18:26:43 UTC 2015 - waiting to be ready
./wait_elastic.sh: line 9: 200: command not found
Fri Jul  3 18:26:45 UTC 2015 - waiting to be ready
./wait_elastic.sh: line 9: 200: command not found
Fri Jul  3 18:26:47 UTC 2015 - still not ready, giving up

如何测试curl响应是否正常?

如何将逻辑测试命令指定为:

WAIT_COMMAND="$(curl --write-out %{http_code} --silent --output /dev/null http://elastic:9200/_cat/health?h=st) == 200"

以及如何评价它:

is_ready() {
    eval $WAIT_COMMAND
}

?

不要使用 eval。相反,只需获取状态

status=$(curl --write-out %{http_code} --silent --output /dev/null http://elastic:9200/_cat/health?h=st)"

那就直接测试吧

is_ready () {
    [[ $status -eq 200 ]]
}

如果要存储代码,请不要使用标量变量——使用函数;这就是他们的目的。

如果要通过环境变量传递代码,那么答案是导出函数:

wait_command() {
  [ $(curl --write-out %{http_code} --silent --output /dev/null http://elastic:9200/_cat/health?h=st) = 200 ]
}
export -f wait_command

...创建一个如下所示的环境变量(在现代,post-official-shellshock-patch bash):

BASH_FUNC_wait_command%%=() {  [ $(curl --write-out %{http_code} --silent --output /dev/null http://elastic:9200/_cat/health?h=st) = 200 ]

...你可以 运行 像这样,没有 eval:

wait_command

再次重申,更清楚一点:导出的函数只是具有特殊名称和值的环境变量。如果您创建一个名称以 BASH_FUNC_ 开头并以 %% 结尾并且其值以 () { 开头的环境变量,您只是创建了一个导出函数,它将可用于 运行 在 shell 中以它在环境中启动。


因此,在您的 .yml 文件中,您可以使用如下内容:

environment: {BASH_FUNC_wait_command%%: '() {  [ $(curl --write-out %{http_code} --silent
    --output /dev/null http://elastic:9200/_cat/health?h=st) = 200 ]'}

这个答案是不好的做法。请考虑一种不涉及 eval 的方法。

wait_command='[ $(curl --write-out %{http_code} --silent --output /dev/null http://elastic:9200/_cat/health?h=st) = 200 ]'
is_ready() {
  eval "$wait_command"
}

与原始代码的差异:

  • 实际上在正在评估的环境变量中包含 test 命令同义词 [(之前的版本不合法 bash 比较语法)。
  • ==(在 POSIX [ 中不是有效运算符)切换到 =(POSIX 允许)。参见 the standards document for the test command
  • 在赋值时从双引号切换到单引号,这样 curl 在调用 eval 之前不是 运行。
  • 引用传递给 eval 的内容以防止在代码到达 eval 命令之前发生字符串拆分和 glob 扩展。

docker-compose 的上下文中实现它可能如下所示:

app:
    command: docker/wait
    environment:
        - wait_command=[ $(curl --write-out %{http_code} --silent --output /dev/null http://elastic:9200/_cat/health?h=st) = 200 ]
        - wait_loops=10
        - wait_sleep=30

...使用如下脚本:

#!/bin/bash
s_ready() { eval "$wait_command"; }

# wait until is ready
i=0
while ! is_ready; do
    if (( ++i >= wait_loops )); then
        echo "$(date) - still not ready, giving up"
        exit 1
    fi
    echo "$(date) - waiting to be ready"
    sleep $wait_sleep
done