前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >puppet 定时执行的陷阱

puppet 定时执行的陷阱

原创
作者头像
zero000
发布2022-06-02 16:45:20
7950
发布2022-06-02 16:45:20
举报
文章被收录于专栏:程序员菜谱程序员菜谱

背景

一次偶然的机会,我尝试通过 puppet 利用 archive module 从 s3 中下载文件到指定的目录,结果掉坑了。

服务器中 puppet 设置了定时任务自动更新,并监控其运行状态;做法是在 /etc/cron.d 创建了对应的 cronjob 任务。

代码语言:shell
复制
$ cd /etc/cron.d
$ cat mypuppetjob 
3 23    * * *   root    /opt/puppetlabs/bin/puppet agent -t >/dev/null; echo $? | tee /tmp/puppet.status

一次用户需求,需要到 s3 下载文件到指定的服务器中。

为此,编写 puppet 代码,使用 archive module,最后手工执行 puppet 更新后,文件下载成功。

验证没问题后,接着尝试全量更新;主要方式是:更新 puppetsever,让所有 client 通过定时任务更新 puppet,达到全量更新的目的。

后续通过监控查看到,大部分服务更新后,/tmp/puppet.status 状态码返回是 6 而非正常的 2 或者 0,问题变得有点迷惑。

问题分析

目前已知情况

  1. puppet 代码配置应该没有问题,因为手工登录任意一个机器,使用 puppet 更新命令 sudo puppet agent -t 可以看到更新成功,而且文件也下载成功
  2. 自动化 puppet 更新有问题,登录有问题的服务器,看到自动化更新的返回码是 6,也有看到是 4 的,文件没有成功被下载。
  3. puppet 状态码代表 puppet 更新只有部分成功。
代码语言:shell
复制
$ sudo puppet agent --help

...

'--test' runs once in the foreground with verbose logging, then exits.
It also exits if it can't get a valid catalog. `--test` includes the '--detailed-exitcodes' option by default and exits with one of the following exit codes:

* 0: The run succeeded with no changes or failures; the system was already in the desired state.
* 1: The run failed, or wasn't attempted due to another run already in progress.
* 2: The run succeeded, and some resources were changed.
* 4: The run succeeded, and some resources failed.
* 6: The run succeeded, and included both changes and failures.
...

逐步分析

1. 代码写错了吗?

首先,感觉是 puppet 的代码编写有问题,毕竟整个 s3 相关下载的 class 是参照 archive module 的例子改写的

代码语言:text
复制
class abc::role::s3::global_so {
  file { "/opt/abcaccount":
        ensure => directory,
        mode   => "0755",
        owner  => 'tom',
        group  => 'tom',
  }
  abc::profile::s3::download {"lib_hello_1.0.0.tar.gz":
    ensure => present,
    s3_src_path        => "/abc_account/lib_hello_1.0.0.tar.gz",
    dst_path           => "/opt/abcaccount",
    exist_test_path    => "/opt/abcaccount/llib_hello_1.0.0.version",
    user               => 'tom',
    group              => 'tom',
    require            => File['/opt/imoaccount'],
  }
}

这里自定义的 resource 可能无法使用 require 保留字,查了一轮官方文档也没有找到答案,保守起见先把这里改了,改为在 File 中使用 before 声明依赖。

代码语言:text
复制
class abc::role::s3::global_so {
  file { "/opt/abcaccount":
        ensure => directory,
        mode   => "0755",
        owner  => 'tom',
        group  => 'tom',
        before => Imo::Profile::S3::Download["lib_hello_1.0.0.tar.gz"],

  }
  abc::profile::s3::download {"lib_hello_1.0.0.tar.gz":
    ensure => present,
    s3_src_path        => "/abc_account/lib_hello_1.0.0.tar.gz",
    dst_path           => "/opt/abcaccount",
    exist_test_path    => "/opt/abcaccount/llib_hello_1.0.0.version",
    user               => 'tom',
    group              => 'tom',
  }
}

通过测试发现,修改前后效果一样,手工执行成功,cron 自动执行失败。

即自动更新失败于代码编写没有关系。

2. cron 执行有问题?

第一感觉:cron 的任务不应该有问题。<u>毕竟,cron 任务已经使用两年多了,没有修改过,而这种固定思维也为后面艰难排错埋下了伏笔。</u>

首先,我们需要确认 cron job 它是在特定时间被执行了,通过查看 /var/log/cron.log 可以看到,定时任务执行没有问题(想想也知道没有问题,因为我们修改的是 puppet 逻辑代码,这个不会影响 cron 的自动执行 :) )

接着,想到 puppet 更新时可以开启 debug 模式,因此对 cron 任务打开 debug,输出到指定文件中:

代码语言:shell
复制
$ cd /etc/cron.d
$ cat mypuppetjob 
3 23    * * *   root    /opt/puppetlabs/bin/puppet agent -t --debug >/tmp/puppet-debug.log; echo $? | tee /tmp/puppet.status

查看 debug 信息:

代码语言:txt
复制
Debug: Caching connection for https://puppet:8140
Debug: Abc::Profile::S3::Download[lib_hello_1.0.0.tar.gz]: Resource is being skipped, unscheduling all events
Debug: Class[Abc::Role::S3::Global_so]: Resource is being skipped, unscheduling all events
Debug: Stage[main]: Resource is being skipped, unscheduling all events
Debug: Finishing transaction 49174140
Debug: Storing state
Debug: Pruned old state cache entries in 0.00 seconds
Debug: Stored state in 0.16 seconds
Notice: Applied catalog in 31.53 seconds

这里可以看到,Debug: Abc::Profile::S3::Download[lib_hello_1.0.0.tar.gz]: Resource is being skipped, unscheduling all events ,貌似是 download 这个 resource 没有被执行,重新走读代码,对一些可能有问题的点做了修改,发现问题依然存在。

所以,可能问题不在代码上。

由于没有其他办法,只能手工运行 sudo puppet agent -t --debug 命令,看看两者 debug 信息会有什么差异。

代码语言:txt
复制
Debug: Caching connection for https://puppet:8140
Debug: Executing: '/usr/local/bin/aws s3 cp s3://abc_backend_lib/abc_account/lib_hello_1.0.0.tar.gz /tmp/lib_hello_1.0.0.tar.gz_20220531-3980-ynce3l --endpoint-url https://abc.com/ --profile abcrs'
Debug: Archive extracting /tmp/lib_hello_1.0.0.tar.gz in /opt/abcaccount: tar xzf /tmp/lib_hello_1.0.0.tar.gz
Debug: Executing with uid=amy gid=amy: 'tar xzf /tmp/lib_hello_1.0.0.tar.gz'
Debug: Cleanup archive /tmp/lib_hello_1.0.0.tar.gz
Notice: /Stage[main]/Abc::Role::S3::Global_so/Abc::Profile::S3::Download[lib_hello_1.0.0.tar.gz]/Archive[lib_hello_1.0.0.tar.gz]/ensure: download archive from s3://abc_backend_lib/abc_account/lib_hello_1.0.0.tar.gz to /tmp/lib_hello_1.0.0.tar.gz and extracted in /opt/abcaccount with cleanup (corrective)
Debug: /Stage[main]/Abc::Role::S3::Global_so/Abc::Profile::S3::Download[lib_hello_1.0.0.tar.gz]/Archive[lib_hello_1.0.0.tar.gz]: The container Abc::Profile::S3::Download[lib_hello_1.0.0.tar.gz] will propagate my refresh event
Debug: Abc::Profile::S3::Download[lib_hello_1.0.0.tar.gz]: The container Class[Abc::Role::S3::Global_so] will propagate my refresh event
Debug: Class[Abc::Role::S3::Global_so]: The container Stage[main] will propagate my refresh event
Debug: Finishing transaction 43366640
Debug: Storing state
Debug: Pruned old state cache entries in 0.00 seconds
Debug: Stored state in 0.21 seconds
Notice: Applied catalog in 41.37 seconds

可以看到,download 相关的 resource 被执行了。

这里的主要差异点在于:cron job 的 debug 信息报了 Debug: Abc::Profile::S3::Download[lib_hello_1.0.0.tar.gz]: Resource is being skipped, unscheduling all events 的错误,而手工在 shell 执行时没有;通过搜索引擎查询相关信息,但没有找到这个错误具体原因详细的解释。

正当我继续苦恼换着搜索引擎查询解决方案时,我突然想到了一个问题。回头仔细看正确执行的代码信息,Debug: Executing: '/usr/local/bin/aws s3 cp s3://abc_backend_lib/abc_account/lib_hello_1.0.0.tar.gz /tmp/lib_hello_1.0.0.tar.gz_20220531-3980-ynce3l --endpoint-url https://abc.com/ --profile abcrs',这里错误原因就是这个 s3 命令没有被执行,会不会是 /usr/local/bin/aws 不存在?不对,这些配置已经提前设置好了,确认过有问题的机器都是有对应项 bin 执行文件的。

bin 文件存在,但执行失败,我再想到的,是可能环境变量出问题了。

我尝试在 cron job 中添加 PATH 环境变量:

代码语言:txt
复制
 $ cat mypuppetjob
 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
3 23    * * *   root    /opt/puppetlabs/bin/puppet agent -t --debug >/tmp/puppet-debug.log; echo $? | tee /tmp/puppet.status

调整任务时间使其执行,OK,问题解决了~

原因分析

默认情况下,cron 设置了很多默认变量,例如 SHELL 默认为 /bin/sh,PATH 默认为 /usr/bin:/bin

通过 man 5 crontab,我们可以看到

crontab 有默认的环境变量

Several environment variables are set up automatically by the cron(8) daemon. SHELL is set to /bin/sh, and LOGNAME and HOME are set from the /etc/passwd line of the crontab's owner. PATH is set to "/usr/bin:/bin". HOME, SHELL, and PATH may be overridden by settings in the crontab; LOGNAME is the user that the job is running from, and may not be changed.

若想变更默认的 PATH 环境变量,需要在 crontab file 里面声明

On the Debian GNU/Linux system, cron supports the pam_env module, and loads the environment specified by /etc/environment and /etc/security/pam_env.conf. It also reads locale information from /etc/default/locale. However, the PAM settings do NOT override the settings described above nor any settings in the crontab file itself. Note in particular that if you want a PATH other than "/usr/bin:/bin", you will need to set it in the crontab file.

当在 /etc/cron.d 配置 puppet 定时执行时,实际 puppet 命令也受上述命令的影响;其 fork 出来的子进程若想执行类似 /usr/local/bin/aws 的命令,并且命令写成相对路径的格式 aws <aws_command>,这时候命令执行就会失败,因为在环境变量中无法找到 aws 这个 bin 文件。

测试

获取 /etc/cron.d 下,root 用户 cron job 默认的 PATH 环境变量

代码语言:shell
复制
$ cat /etc/cron.d/get_path 
33 23    * * *   root   echo $PATH > /tmp/test_path_1.log 
$ cat /tmp/test_path_1.log 
/usr/bin:/bin

获取当前用户下,crontab 的环境变量 PATH 的信息

代码语言:shell
复制
$ crontab -l |grep -v "^#"
42 23    * * *   echo $PATH > /tmp/test_path_2.log
$ cat /tmp/test_path_2.log 
/usr/bin:/bin

测试的环境是 Linux Ubuntu 16.04,结果可以知道,无论 crontab 还是 /etc/cron.d ,其任务都是使用了默认的 PATH 环境变量

参照这里有更详细的解释

解决方案

参照这里可知,linux 的全局环境变量依赖 /etc/environment/etc/profile,查看 /etc/environment 的内容

代码语言:shell
复制
$ cat /etc/environment 
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin"

environment 的已经罗列了大部分常用的 PATH。因此,如无特殊需要,可以直接使用environment 中的 PATH 作为 crontab file 的默认 PATH

最终修改方案如下:

代码语言:shell
复制
$ cat mypuppetjob
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
3 23    * * *   root    /opt/puppetlabs/bin/puppet agent -t >/dev/null; echo $? | tee /tmp/puppet.status

所有 puppet 自动更新任务执行无异常,返回码为 2 。

参考链接

  1. https://unix.stackexchange.com/questions/479522/how-does-cron-set-the-environment-variables-in-etc-cron-d-and-etc-cron-d
  2. https://superuser.com/questions/664169/what-is-the-difference-between-etc-environment-and-etc-profile
  3. https://puppet.com/docs/puppet/6/lang_defined_types.html
  4. https://puppet.com/docs/puppet/7/lang_relationships.html#lang_rel_require
  5. https://groups.google.com/g/puppet-users/c/mEaP_PciA_Y/m/bOWi6qTcGwAJ?pli=1

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 问题分析
    • 目前已知情况
      • 逐步分析
        • 1. 代码写错了吗?
        • 2. cron 执行有问题?
      • 原因分析
        • 测试
    • 解决方案
    • 参考链接
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档