Jenkinsからserverspecを実行しようとしたら、ポート確認で失敗した話

目的

serverspecの手動実行時のチェック結果と、Jenkinsから実行したチェック結果に差異が生じました。
まったく同じサーバー、ユーザー、コマンドで実施したが結果が違ったため原因を調査する。

前提条件

バージョン
・rake:13.0.1
ruby:2.7.0p0
・Jenkins:2.220

AWS上にJenkisサーバ、webサーバーをそれぞれ準備
・公開鍵を登録し、特定のユーザーのみパスワード無しで、sshログインできる
・2サーバ間のセキュリティは全アクセスを許可済み

serverspecのチェック内容

require 'spec_helper'

describe package('nginx') do
  it { should be_installed }
end

describe service('nginx') do
  it { should be_enabled }
  it { should be_running }
end

describe port(80) do
  it { should be_listening }
end

describe file('/usr/share/nginx/html/index.html') do
  it { should be_file }
  it { should exist }
  its(:content) { should match /^Hello, development ansible!!$/ }
end

詳細事象

wedサーバー上でrake specを実行したところ、以下のように0 failuresで実行できました。

/home/ec2-user/.rbenv/versions/2.7.0/bin/ruby -I/home/ec2-user/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-support-3.9.2/lib:/home/ec2-user/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-core-3.9.1/lib /home/ec2-user/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-core-3.9.1/exe/rspec --pattern spec/localhost/\*_spec.rb

Package "nginx"
  is expected to be installed

Service "nginx"
  is expected to be enabled
  is expected to be running

Port "80"
  is expected to be listening

File "/usr/share/nginx/html/index.html"
  is expected to be file
  is expected to exist
  content
    is expected to match /^Hello, development ansible!!$/

Finished in 0.09714 seconds (files took 0.55084 seconds to load)
7 examples, 0 failures

しかし、Jenkinsで同じユーザーでログインし、rake specを実行したところ以下の結果になりました。

[SSH] executing...
/home/ansible/.rbenv/versions/2.7.0/bin/ruby -I/home/ansible/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-support-3.9.2/lib:/home/ansible/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-core-3.9.1/lib /home/ansible/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-core-3.9.1/exe/rspec --pattern spec/localhost/\*_spec.rb failed
/home/ansible/.rbenv/versions/2.7.0/bin/ruby -I/home/ansible/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-support-3.9.2/lib:/home/ansible/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-core-3.9.1/lib /home/ansible/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-core-3.9.1/exe/rspec --pattern spec/localhost/\*_spec.rb

Package "nginx"
  is expected to be installed

Service "nginx"
  is expected to be enabled
  is expected to be running

Port "80"
  is expected to be listening (FAILED - 1)

File "/usr/share/nginx/html/index.html"
  is expected to be file
  is expected to exist
  content
    is expected to match /^Hello, production ansible!!$/

Failures:

  1) Port "80" is expected to be listening
     On host `localhost'
     Failure/Error: it { should be_listening }
       expected Port "80" to be listening
       /bin/sh -c ss\ -tunl\ \|\ grep\ -E\ --\ :80\\\ 
       
     # ./spec/localhost/web_spec.rb:13:in `block (2 levels) in <top (required)>'

Finished in 0.11224 seconds (files took 0.54982 seconds to load)
7 examples, 1 failure

Failed examples:

rspec ./spec/localhost/web_spec.rb:13 # Port "80" is expected to be listening


[SSH] completed
[SSH] exit-status: 1

まったく同じコマンドを実行したはずですが、1 failureになってしまいました。

どうも80番ポートのチェックで失敗しているように見えます。

切り分け

実施コマンドの確認

エラーメッセージを確認すると以下のメッセージが表示されていることから、 ssコマンドが実行されていることがわかりました。

/bin/sh -c ss\ -tunl\ \|\ grep\ -E\ --\ :80\\\ 

webサーバーの対象ユーザーからssコマンドを実行してみます。

$ ss -tunl
 Netid    State      Recv-Q     Send-Q                             Local Address:Port           Peer Address:Port     
 udp      UNCONN     0          0                                        0.0.0.0:725                 0.0.0.0:*        
 udp      UNCONN     0          0                                      127.0.0.1:323                 0.0.0.0:*        
 udp      UNCONN     0          0                                        0.0.0.0:68                  0.0.0.0:*        
 udp      UNCONN     0          0                                        0.0.0.0:111                 0.0.0.0:*        
 udp      UNCONN     0          0                                           [::]:725                    [::]:*        
 udp      UNCONN     0          0                                          [::1]:323                    [::]:*        
 udp      UNCONN     0          0                [fe80::4db:f6ff:fee3:8fec]%eth0:546                    [::]:*        
 udp      UNCONN     0          0                                           [::]:111                    [::]:*        
 tcp      LISTEN     0          128                                      0.0.0.0:80                  0.0.0.0:*        
 tcp      LISTEN     0          128                                      0.0.0.0:22                  0.0.0.0:*        
 tcp      LISTEN     0          100                                    127.0.0.1:25                  0.0.0.0:*        
 tcp      LISTEN     0          128                                      0.0.0.0:111                 0.0.0.0:*        
 tcp      LISTEN     0          128                                         [::]:80                     [::]:*        
 tcp      LISTEN     0          128                                         [::]:22                     [::]:*        
 tcp      LISTEN     0          128                                         [::]:111                    [::]:*        

当然実行できました。

次に、Jenkisサーバからsshコマンドにssコマンドを渡して実行してみます。

$ ssh <対象ユーザー>@<webサーバーIP> "ss -tunl" 
 bash: ss: command not found

失敗しました。。コマンドが存在しない???
コマンドの渡し方が間違っていたのか確認しましたが、あってそうです。

ssコマンドをフルパスで実行してみます。

$ which ss
/sbin/ss

$ ssh <対象ユーザー>@<webサーバーIP> "/sbin/ss -tunl"
 Netid    State      Recv-Q     Send-Q                             Local Address:Port           Peer Address:Port     
 udp      UNCONN     0          0                                        0.0.0.0:725                 0.0.0.0:*        
 udp      UNCONN     0          0                                      127.0.0.1:323                 0.0.0.0:*        
 udp      UNCONN     0          0                                        0.0.0.0:68                  0.0.0.0:*        
 udp      UNCONN     0          0                                        0.0.0.0:111                 0.0.0.0:*        
 udp      UNCONN     0          0                                           [::]:725                    [::]:*        
 udp      UNCONN     0          0                                          [::1]:323                    [::]:*        
 udp      UNCONN     0          0                [fe80::4db:f6ff:fee3:8fec]%eth0:546                    [::]:*        
 udp      UNCONN     0          0                                           [::]:111                    [::]:*        
 tcp      LISTEN     0          128                                      0.0.0.0:80                  0.0.0.0:*        
 tcp      LISTEN     0          128                                      0.0.0.0:22                  0.0.0.0:*        
 tcp      LISTEN     0          100                                    127.0.0.1:25                  0.0.0.0:*        
 tcp      LISTEN     0          128                                      0.0.0.0:111                 0.0.0.0:*        
 tcp      LISTEN     0          128                                         [::]:80                     [::]:*        
 tcp      LISTEN     0          128                                         [::]:22                     [::]:*        
 tcp      LISTEN     0          128                                         [::]:111                    [::]:*        

実行できました!
どうもパスが通っていないように感じますね。

普通にコマンド実行する場合とssh越しにコマンド実行する場合で、環境変数に差があるか確認してみます。

<通常実行時(webサーバ)>

$ env
 XDG_SESSION_ID=4
 HOSTNAME=
 SHELL=/bin/bash
 TERM=xterm
 HISTSIZE=1000
 USER=ansible
 LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
  SUDO_USER=ec2-user
  SUDO_UID=1000
  USERNAME=root
  PATH=/sbin:/bin:/usr/sbin:/usr/bin
  MAIL=/var/spool/mail/ec2-user
  PWD=/home/ec2-user
  LANG=en_US.UTF-8
  SHLVL=1
  SUDO_COMMAND=/bin/su ansible
  HOME=/home/ansible
  LOGNAME=ansible
  LESSOPEN=||/usr/bin/lesspipe.sh %s
  SUDO_GID=1000
  _=/bin/env

<ssh越しにenvコマンド実行時(Jenkinsサーバ)>

$ ssh <対象ユーザー>@<webサーバーIP> "env"
 XDG_SESSION_ID=44
 SHELL=/bin/bash
 SSH_CLIENT=
 USER=ansible
 MAIL=/var/mail/ansible
 PATH=/usr/local/bin:/usr/bin
 PWD=/home/ansible
 LANG=en_US.UTF-8
 SHLVL=1
 HOME=/home/ansible
 LOGNAME=ansible
 SSH_CONNECTION=
 LESSOPEN=||/usr/bin/lesspipe.sh %s
 XDG_RUNTIME_DIR=/run/user/1001
 _=/usr/bin/env

予想通り、PATHに差異があることがわかりました。

<通常実行時(webサーバ)>
PATH=/sbin:/bin:/usr/sbin:/usr/bin

<ssh越しにenvコマンド実行時(Jenkinsサーバ)>
PATH=/usr/local/bin:/usr/bin

解決

問題を解決すべく、ssh時の環境変数について調べたところ以下のブログが見つかりました。

ssh-env - ssh実行時に環境変数を設定/変更したい - spikelet days

上記ブログに記載してある以下の方法を試します。

  • sshdの設定変更 (PermitUserEnvironment=yes)、sshd再起動
  • ~/.ssh/environment に環境変数を書く (VAR=VAL 形式で一行一変数で)

まずは、webサーバの /etc/ssh/sshd_config を編集します。
109行目あたりにある PermitUserEnvironment noPermitUserEnvironment yes に変更します。

#変更前の設定内容を確認
$ sudo grep PermitUserEnvironment /etc/ssh/sshd_config
#PermitUserEnvironment no

#PermitUserEnvironmentをyesに変更
$ sudo vi /etc/ssh/sshd_config

#変更されていることを確認
$ sudo grep PermitUserEnvironment /etc/ssh/sshd_config
PermitUserEnvironment yes

次に、~/.ssh/environment に環境変数を書く (VAR=VAL 形式で一行一変数で) を実行します。

sshでログインしたいユーザーのホームディレクトリ配下にある ~/.ssh に移動し、
設定したい環境変数を記載した environment ファイルを作成します。

今回は、コマンドを実行するために PATH= の値を変更したいので、 PATH=/sbin:/bin:/usr/sbin:/usr/bin を記載します。

#ホームディレクトリは以下の.sshディレクトリに移動
$ cd ~/.ssh

#設定する環境変数(PATH=/sbin:/bin:/usr/sbin:/usr/bin)を記載
$ vi environment

#記載内容の確認
$ cat environment
PATH=/sbin:/bin:/usr/sbin:/usr/bin

これで、設定完了です。
試しに、Jenkinsサーバからsshコマンド越しにserverspecを実行してみます。

ssh <ユーザー名>@<IPアドレス> "ss -tunl"
NetidState  Recv-Q Send-Q                     Local Address:Port   Peer Address:Port
udp  UNCONN 0      0                                0.0.0.0:111         0.0.0.0:*
udp  UNCONN 0      0                                0.0.0.0:724         0.0.0.0:*
udp  UNCONN 0      0                              127.0.0.1:323         0.0.0.0:*
udp  UNCONN 0      0                                0.0.0.0:68          0.0.0.0:*
udp  UNCONN 0      0                                   [::]:111            [::]:*
udp  UNCONN 0      0                                   [::]:724            [::]:*
udp  UNCONN 0      0                                  [::1]:323            [::]:*
udp  UNCONN 0      0        [fe80::4db:f6ff:fee3:8fec]%eth0:546            [::]:*
tcp  LISTEN 0      128                              0.0.0.0:111         0.0.0.0:*
tcp  LISTEN 0      128                              0.0.0.0:80          0.0.0.0:*
tcp  LISTEN 0      128                              0.0.0.0:22          0.0.0.0:*
tcp  LISTEN 0      100                            127.0.0.1:25          0.0.0.0:*
tcp  LISTEN 0      128                                 [::]:111            [::]:*
tcp  LISTEN 0      128                                 [::]:80             [::]:*
tcp  LISTEN 0      128                                 [::]:22             [::]:*

実行できました!!

次は、いよいよJenkinsのGUIからjobを実行してみます。

Started by user root
Running as SYSTEM
Building in workspace /var/lib/jenkins/workspace/serverspec jobtest
[SSH] script:

cd /tmp/serverspec_sample
~/.rbenv/shims/rake spec

[SSH] executing...
/home/ansible/.rbenv/versions/2.7.0/bin/ruby -I/home/ansible/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-support-3.9.2/lib:/home/ansible/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-core-3.9.1/lib /home/ansible/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-core-3.9.1/exe/rspec --pattern spec/localhost/\*_spec.rb

Port "80"
  is expected to be listening

Package "nginx"
  is expected to be installed

Service "nginx"
  is expected to be enabled
  is expected to be running

Port "80"
  is expected to be listening

File "/usr/share/nginx/html/index.html"
  is expected to be file
  is expected to exist
  content
    is expected to match /^Hello, development ansible!!$/

Finished in 0.08618 seconds (files took 1.01 seconds to load)
8 examples, 0 failures


[SSH] completed
[SSH] exit-status: 0

Started calculate disk usage of build
Finished Calculation of disk usage of build in 0 seconds
Started calculate disk usage of workspace
Finished Calculation of disk usage of workspace in 0 seconds
Finished: SUCCESS

f:id:Tk24:20200325012403p:plain

成功しました!!

余談

今回色々調べている中で、以下のようにパイプを使用して、ssh越しにコマンドを実行する方法があるのを知りました。

echo `ss -tunl`  | ssh <ユーザー名>@<IPアドレス>

このコマンドだと、上記の解決を実施しなくてもコマンド実行できました。

これも環境変数が違うのかと思い、envコマンドを実行してみました。

$ echo "env"  | ssh ansible@172.31.23.243
Pseudo-terminal will not be allocated because stdin is not a terminal.
XDG_SESSION_ID=24
HOSTNAME=ansible-server.tk.com
SHELL=/bin/bash
HISTSIZE=1000
SSH_CLIENT=172.31.18.127 54540 22
USER=ansible
MAIL=/var/spool/mail/ansible
PATH=/home/ansible/.rbenv/shims:/home/ansible/.rbenv/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/home/ansible/.local/bin:/home/ansible/bin
PWD=/home/ansible
LANG=en_US.UTF-8
RBENV_SHELL=bash
HISTCONTROL=ignoredups
SHLVL=1
HOME=/home/ansible
LOGNAME=ansible
SSH_CONNECTION=172.31.18.127 54540 172.31.23.243 22
LESSOPEN=||/usr/bin/lesspipe.sh %s
XDG_RUNTIME_DIR=/run/user/1001
_=/bin/env

今までのどれとも違う環境変数が出力されました。。

少し調べてみましたが、結論が出ませんでした。 時間のある時に調べて、原因がわかり次第追記しようと思います。

以上です。