Linux实例使用云助手与在ECS实例本地执行命令的差异

云助手是专为云服务器ECS打造的原生自动化运维工具,可以免密码、免登录、无需使用跳板机执行命令。本文介绍Linux实例使用云助手与在ECS实例操作系统本地执行命令的差异。

云助手概述

云助手Agent作为一个服务运行在ECS实例中,直接与阿里云相关服务端通信,获取并执行命令后上报结果,云助手Agent与用户使用SSH等方式远程连接到ECS实例上执行命令的通信链路不同,因此使用云助手与登录ECS实例执行命令的结果可能存在差异。关于云助手的更多信息,请参见云助手概述

环境变量存在差异

云助手执行命令的环境变量和ECS实例本地执行中的环境变量存在差异。

可能原因

使用远程连接软件(例如PuTTY、Xshell等)连接Linux实例或通过ECS控制台的VNC远程连接登录实例后,默认进入交互式登录的Shell环境。在这个环境中,Shell解释器(如/bin/bash)会加载环境配置和初始化相关的启动文件,例如/etc/profile~/.bash_profile~/.bashrc等。这些启动文件中通常会包含部分环境变量,例如HOSTNAME,您可以使用export命令查看环境中的变量信息,示例如下:

[root@localhost ~]# export
declare -x HISTCONTROL="ignoredups"
declare -x HISTSIZE="1000"
declare -x HOME="/root"
declare -x HOSTNAME="ALiYun-ASSIST-TEST"
declare -x LANG="zh_CN.UTF-8"
declare -x LESSOPEN="||/usr/bin/lesspipe.sh %s"
declare -x LOGNAME="root"
declare -x LS_COLORS="rs=0:di=38;5;27:*"
declare -x MAIL="/var/spool/mail/root"
declare -x OLDPWD="/etc/profile.d"
declare -x PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin"
declare -x PWD="/root"
declare -x SHELL="/bin/bash"
declare -x SHLVL="1"
declare -x SSH_CLIENT="42.120.XX.XX 34192 22"
declare -x SSH_CONNECTION="42.120.XX.XX 34192 192.168.0.47 22"
declare -x SSH_TTY="/dev/pts/1"
declare -x TERM="xterm-256color"
declare -x USER="root"
declare -x XDG_RUNTIME_DIR="/run/user/0"
declare -x XDG_SESSION_ID="520"

但云助手在通过Shell执行命令时,启动的Shell环境不是交互式登录的环境,不会加载/etc/profile~/.bash_profile~/.bashrc等启动文件,因此,云助手无法获得和使用包含在这些启动文件中定义的环境变量。您可以通过云助手执行export命令查看当前环境可用的变量,示例如下:

export HOME="/root"
export INVOCATION_ID="0828f2e5b64b4a96****"
export JOURNAL_STREAM="8:23203"
export LANG="en_US.UTF-8"
export OLDPWD
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
export PWD="/root"
export SHLVL="2"
export TERM="vt220"
export _="/usr/local/share/aliyun-assist/2.2.3.221/../work/script/t-xx.sh"

所以,当您使用云助手执行命令时,如果待执行的命令中需要使用云助手中没有的环境变量,或者所执行的命令和程序依赖云助手中没有的环境变量,则会导致命令执行失败或者执行结果不符合预期。

解决方案

您可以在云助手中加载待执行的命令或执行命令调用的程序需要的启动文件,然后补充缺少的相关变量后,重新执行命令,操作步骤如下:

  1. 使用云助手检查命令或程序使用的变量是否存在。

    云助手创建命令的操作,请参见创建命令

    根据Shell环境中的判断命令和变量不存在时的替换逻辑,检查需要使用的变量不存在并进行相应处理的命令模板如下:

    if [ -z "${<依赖的变量名称>+x}" ]; then
      <依赖变量不存在时的处理动作...>
    fi

    $TERM变量不存在,则通过云助手执行时将会输出$TERM is not set when script executed via cloud assistant为例,云助手命令如下:

    #!/bin/sh
    if [ -z "${TERM+x}" ]; then
      echo "\$TERM is not set when script executed via cloud assistant"
    else
      echo "\$TERM is set to '${NOT_SET_VARAIABLE}'"
    fi

    如果命令回显为已设置的$TERM is not set when script executed via cloud assistant,则$TERM变量不存在。

  2. 在命令中加载环境配置和初始化相关的启动文件(例如/etc/profile~/.bash_profile~/.bashrc等文件)。

    • 方法一:在命令中显式加载启动文件。

      在Shell脚本的前面通过 .命令或者Bash解释器特有的source命令加载指定的环境配置和初始化相关的启动文件,无需单独处理每个不存在的变量,示例如下:

      • .命令:

        #!/bin/sh
        . /etc/profile
      • source命令

        #!/bin/bash
        source $HOME/.bashrc
    • 方法二:在命令开头的#!语句中,使用参数指定Shell解释器加载启动文件。

      说明

      如果您的命令的输出内容下一步将会被进行解析和处理,则不推荐本方法。

      如果不希望在命令前面写入过多的加载启动文件的语句,而是希望启用Shell解释器的交互模式和登录模式,由Shell解释器自身来加载所依赖的启动文件,那么可以使用Shell解释器的-i参数启用交互模式、使用-l参数启用登录模式。两个参数合并为-il,可通过类似于SSH登录的方式启用Shell解释器的交互登录模式,命令如下:

      #!/bin/sh -il

      如果使用Bash解释器,则可以在命令开头指定#!/bin/bash -il,具体参数请参考所使用的Shell解释器的文档。但是,指定-i参数启用Shell交互模式时,Shell解释器会希望并检测标准输入stdin为一个交互式的终端输入设备,从而提供任务控制等交互式功能。但云助手在执行命令时标准输入stdin为空设备/dev/null,因此Shell解释器会检测失败并报告相应的错误,常见错误示例如下:

      sh: cannot set terminal process group (-1): Inappropriate ioctl for device
      sh: no job control in this shell

      /usr/local/share/aliyun-assist/work/script/t-xx.sh: 0: /usr/local/share/aliyun-assist/work/script/t-hz02vmfqb9vxxq8.sh: can't access tty; job control turned off
      stdin: is not a tty

命令执行结果差异

使用云助手执行ulimit -n命令时,输出结果为1024;但是在ECS实例本地执行ulimit -n时,输出结果为65535,云助手执行命令和ECS实例本地执行结果不一致。

可能原因

Linux公共镜像中的/etc/security/limits.conf资源限制文件用于设置系统登录用户的资源限制,在ECS实例本地执行cat /etc/security/limits.conf命令,可以打开的文件描述符最大数量为65535。因此,在ECS实例本地执行ulimit -n命令时,输出结果为65535。

[root@localhost ~]# cat /etc/security/limits.conf

root soft nofile 65535
root hard nofile 65535
* soft nofile 65535
* hard nofile 65535

[root@localhost ~]# ulimit -n
65535

通过云助手执行ulimit -n命令时,/etc/security/limits.conf资源限制文件对系统服务无限制效果。云助手服务进程由运行在系统服务上的init引导启动,该进程打开的文件描述符最大数量为1024。因此,云助手执行ulimit -n时,输出结果为1024。

在ECS实例本地执行cat /proc/云助手进程PID/limits命令,可以查看云助手服务进程的资源限制情况。

[root@localhost ~]# cat /proc/$(pidof aliyun-service)/limits
Limit                     Soft Limit           Hard Limit           Units
Max cpu time              unlimited            unlimited            seconds
Max file size             unlimited            unlimited            bytes
Max data size             unlimited            unlimited            bytes
Max stack size            8388608              unlimited            bytes
Max core file size        unlimited            unlimited            bytes
Max resident set          unlimited            unlimited            bytes
Max processes             30546                30546                processes
Max open files            1024                 262144               files
Max locked memory         65536                65536                bytes
Max address space         unlimited            unlimited            bytes
Max file locks            unlimited            unlimited            locks
Max pending signals       30546                30546                signals
Max msgqueue size         819200               819200               bytes
Max nice priority         0                    0
Max realtime priority     0                    0
Max realtime timeout      unlimited            unlimited            us

解决方案

在ECS实例上将云助手服务的资源限制设置为65535,设置方式与系统启动模式有关。

查询ECS实例系统启动模式

  1. 远程连接Linux实例。

    具体操作,请参见连接方式概述

  2. 执行如下命令,查看系统启动模式。

    • systemd

      strings $(which init) |grep "systemd"

      如果命令回显中输出systemd相关信息,则系统启动模式为systemd。

    • upstart

      strings $(which init) | grep "upstart"

      如果命令回显中输出upstart相关信息,则系统启动模式为upstart。

    • sysvinit

      strings $(which init) | grep "sysvinit"

      如果命令回显中输出sysvinit相关信息,则系统启动模式为sysvinit。

设置云助手服务资源限制

根据系统启动模式不同,设置云助手服务资源限制的操作方法也不同。

systemd启动模式

  • 设置所有服务资源限制

    1. 远程连接Linux实例。

      具体操作,请参见连接方式概述

    2. 执行如下命令,修改/etc/systemd/system.conf配置文件,在Manager参数中增加DefaultLimitNOFILE=65535字段。

      vim /etc/systemd/system.conf
      
      [Manager]
      DefaultLimitNOFILE=65535
    3. 执行如下命令,重新运行systemd管理器并重新读取系统配置文件。

      systemctl  daemon-reexec
    4. 执行如下命令,重启云助手服务。

      systemctl restart aliyun.service

      云助手服务重启完毕,云助手服务的资源限制被设置为65535。

  • 设置云助手服务资源限制

    1. 远程连接Linux实例。

      具体操作,请参见连接方式概述

    2. 执行如下命令,在云助手服务的service配置文件Service参数中增加LimitNOFILE=65535字段。

      [root@localhost ~]# vim /etc/systemd/system/aliyun.service
      ....
      [Service]
      LimitNOFILE=65535
      ....
    3. 执行如下命令,重新加载系统配置文件。

      [root@localhost ~]# systemctl daemon-reload
    4. 执行如下命令,重启云助手服务。

      [root@localhost ~]# systemctl restart aliyun

      云助手服务重启完毕,云助手服务的资源限制被设置为65535。

非systemd启动模式

  • 修改云助手的启动脚本

    1. 远程连接Linux实例。

      具体操作,请参见连接方式概述

    2. 执行如下命令,在云助手执行进程前添加ulimit -n 65535字段。

       [root@localhost ~]# vim /etc/init.d/aliyun-service
      
      ..................
       start() {
          if [ -f $service_lock ]; then
              ps aux | grep "$start_cmd" | grep -v grep 1>/dev/null 2>&1
              if [ $? -eq 0 ]; then
                  echo "$aliyun_service_name is already started!" 1>/dev/null 2>&1
                  return 0
              else
                  rm -rf $service_lock
              fi
          fi
          ulimit -n 65535
          $start_cmd
      
          ### Create the lock file ###
          touch $service_lock
      }
      ....................
    3. 执行如下命令,重启云助手服务。

      [root@localhost ~]# /etc/init.d/aliyun-service restart

      云助手服务重启完毕,云助手服务的资源限制被设置为65535。

  • 修改云助手的配置文件

    说明

    仅upstart启动模式的系统支持修改云助手的配置文件来设置资源限制。

    1. 远程连接Linux实例。

      具体操作,请参见连接方式概述

    2. 执行如下命令,修改云助手的配置文件中的limit nofile字段为65535。

      [root@localhost ~]# cat /etc/init/aliyun-service.conf
      description     "Aliyun Assist Tool"
      author          "aliyun.com"
      
      start on runlevel [2345]
      stop on runlevel [!2345]
      
      limit nofile 65535 65535
      
      respawn
      
      exec /usr/sbin/aliyun-service
      post-stop exec sleep 1
      
      [root@localhost ~]# service aliyun-service restart
    3. 执行如下命令,重启云助手服务。

      [root@localhost ~]# service aliyun-service restart

      云助手服务重启完毕,云助手服务的资源限制被设置为65535。

执行结果输出完整度差异

相比较于ECS实例本地执行命令,使用云助手执行命令输出的结果不完整。

可能原因

云助手执行命令输出结果大小限制为16 KB,因此,当命令执行结果的文件大小超过16 KB时,使用云助手执行命令输出的结果会不完整。

解决方案

建议您将云助手命令的输出结果保存到本地日志或存储到OSS中。

命令执行是否报错差异

使用云助手执行命令时显示失败,但在ECS实例本地执行时却没有报错。

可能原因

  • 可能原因1:

    使用云助手执行和ECS实例本地执行命令,脚本的退出码均是最后一条命令的返回值,如果该值非零,系统就会提示失败。有的命令在ECS实例本地执行虽然退出码非零,但执行结果中无明显报错,用户会以为命令是执行成功的,此时,使用云助手执行命令就会提示失败。

  • 可能原因2:

    云助手执行命令时,会将命令保存成脚本,然后通过sh -c "./script"方式执行命令。ECS实例本地执行命令时,使用Bash解释器执行。由于解释器差异,可能存在使用云助手执行命令失败,但ECS实例本地执行正常的情况。例如Sh解释器不支持进程替换<(cmd)语法而Bash解释器支持,此时,使用云助手执行命令就会提示执行失败。

解决方案

使用云助手执行命令前,在命令首行增加#!/bin/bash命令,指定使用Bash解释器。

针对特定实例执行命令是否正常差异

在Debian、Ubuntu和UOS实例上使用云助手执行出现语法错误,但Debian、Ubuntu和UOS实例的本地执行脚本却正常。

可能原因

在Debian、Ubuntu和UOS实例中,当用户交互式登录实例后,默认分配的Bash环境是/bin/bash,该环境的默认配置被定义在/etc/adduser.conf中。当在非交互式登录环境后,执行脚本且在脚本中未定义解释器时,默认是用/bin/sh执行。

在Debian、Ubuntu和UOS实例中,/bin/sh被链接到了dash,因此使用云助手执行命令可能会因为语法不兼容导致执行失败,例如执行Bash特有的source命令或function关键字定义函数等,更多信息,请参见Shell

解决方案

使用云助手执行命令前,在命令首行增加#!/bin/bash命令,指定使用Bash解释器。