GitHub Actionsでsshコマンド経由でアプリをデプロイする。

hypnotoadで動いているアプリについて、GitHubからWebhook経由でデプロイしていたのですが、GitHub Actionsを導入するとWebhookを廃止できるので、Actionsに乗り換えました。

Before

  1. (開発マシン)リポジトリにpush
  2. (デプロイ先サーバ)webhookが動く
  3. (デプロイ先サーバ)更新されたリポジトリをwebhook経由でgit pull
  4. (デプロイ先サーバ)hook経由でhypnotoadにhot deploy

After

  1. (開発マシン)リポジトリにpush
  2. (GitHub)Actionsが動く
  3. (デプロイ先サーバ)更新されたリポジトリをActions経由でgit pull
  4. (デプロイ先サーバ)Actions経由でhypnotoadにhot deploy

どうやってActionsで実現するか

hot deployするのにデプロイ先サーバ上でhypnotoadコマンドを実行する必要があるため、Actionsで立ち上がったDockerコンテナから、ssh経由でhypnotoadコマンドを実行します。やりたいことは以下の記事と大体一緒です。
GitHub Actions で快適なデプロイを実現する(rsync・laravel) – 自主的20%るぅる

sshするのにわざわざechoしたりchmodすると、それだけでstepを消費して保守しづらくなるため、sshを使うためのActionsを導入します。使い方が直感的でわかりやすいため、以下の記事のActionsを使用しました。
GitHub ActionsでSSHを使う – Qiita
KNOWN_HOSTSはオプションですが、どのみちsshコマンド実行時にfingerprintの確認があったり中間者攻撃されたら困るしで、実質設定必須です。
私はsshのポート番号とユーザ名もSecretsに登録しています。

他にも色々なActionsがあるので、自分にあったものを探すか作るかするといいと思います。
GitHub Marketplace · Actions to improve your workflow

実際に書いたActions

on:
  push:
    branches:
      - master
name: Deploy to retrorocket.biz
jobs:
  publish:
    name: Deploy to retrorocket.biz
    runs-on: ubuntu-latest
    steps:
    - name: Install SSH key
      uses: shimataro/ssh-key-action@v1
      with:
        private-key: ${{ secrets.SSH_KEY }}
        public-key: ${{ secrets.SSH_KEY_PUBLIC }}
        known-hosts: ${{ secrets.KNOWN_HOSTS }}
    - name: Git pull
      run: ssh -p ${{ secrets.SSH_PORT }} ${{ secrets.SSH_USER }}@retrorocket.biz 'cd /アプリのディレクトリまでのパス; and git pull'
    - name: Hypnotoad hot deploy
      run: ssh -p ${{ secrets.SSH_PORT }} ${{ secrets.SSH_USER }}@retrorocket.biz '/usr/local/bin/hypnotoad /アプリまでのパス'

Secretsに登録した値は以下のようにWorkflowのログからも値が見えなくなるので、より安全にCI/CDが運用できます。

ssh -p *** ***@retrorocket.biz '/usr/local/bin/hypnotoad /アプリまでのパス'

おかげさまでWebhookを無くして運用ができるようになりました。今まではWebhookのスクリプト自体を別リポジトリでバージョン管理する必要があって嫌だったのと、Webhookを動作させるためのリソースが必要だったのですが、Actionsだとリポジトリに紐付けて管理ができるので素晴らしいですね。

以下の記事のように、「秘密鍵を扱うActionに第三者が作ったものを採用していいのか」という問題がありますが、その点はActionsに限らずすべてのOSSやソフトウェアに言えることなので、セキュリティと利便性を天秤にかけて運用すれば良いと思っています。
Github Actions でハマった点と解決方法 – kawasin73のブログ

どうでもいいけどSyntaxHighlighter Evolved、yml対応してないんですね…これ書いたらymlのbrush追加しておきます。 追加しました。

追記(2020/01/21)

コメントを頂いて改善できそうだったため改善しました。

on:
  push:
    branches:
      - master
name: Deploy to retrorocket.biz
jobs:
  publish:
    name: Deploy to retrorocket.biz
    runs-on: ubuntu-latest
    steps:
    - name: Install SSH key
      uses: shimataro/ssh-key-action@v1.6.1
      with:
        private-key: ${{ secrets.SSH_KEY }}
        known-hosts: ${{ secrets.KNOWN_HOSTS }}
        config: ${{ secrets.CONFIG }} # ssh target だけで接続できるように設定
    - name: Git pull
      run: ssh target 'git -C /path/to/app pull'
    - name: Hypnotoad hot deploy
      run: ssh target '/usr/local/bin/hypnotoad /path/to/app'
# configの内容
Host target
    HostName    retrorocket.biz
    User    hoge
    Port    12345

/etc/passwdでログインシェルの設定を誤った場合ssh -tでは復旧できない。

遠方にある物理サーバーのrootユーザーが逝った話 - タケハタのブログ

これ読んだぼく「ssh -tで /bin/bash/usr/sbin/sudo /usr/bin/vi /etc/passwd すればよかったのでは」

検証

Webarena Indigo使ってるのでIndigoに適当なインスタンス(CentOS 7.5)を用意して実験しました。タイトルにオチを書きましたがssh -tでは復旧できません。

ログインシェルが正常の場合

Raspbian Busterから/etc/passwdの書き換えを試みます。

-SSH- raspberrypiˇpi ~ » ssh -i "private_key.txt" -t -t centos@140.227.xxx.xxx "/usr/bin/sudo /usr/bin/vi /etc/passwd"

~~~ viの編集画面
centos:x:1000:1000::/home/centos:/bin/bash
~~~

Connection to 140.227.xxx.xxx closed.

書き換えできました。

ログインシェルが異常の場合

-SSH- raspberrypiˇpi ~ » ssh -i "private_key.txt" -t -t centos@140.227.xxx.xxx "/usr/bin/sudo /usr/bin/vi /etc/passwd"

centos@140.227.xxx.xxx: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).

だめでした。

[B! サーバ] 遠方にある物理サーバーのrootユーザーが逝った話 - タケハタのブログ

odz ログインシェル間違えただけなら ssh -t root@server /bin/bash とかで行けるのでわ。/行けなかった。opensshは直接コマンドを実行する場合もログインシェルのチェックはしてファイルない場合は認証エラーになる(ソースで確認

なるほど、じゃあだめだ。
できるんじゃないか?と思ったのですが、予想が外れていたので実際に確認してよかったです。これに限らず、実際にトラブルが起こってしまった時に「できると思ってたけどできませんでした」だと目も当てられないので、疑問に思ったら実行してみるのは大事ですね。

ポケモンの相性が覚えられないのでダメージ倍率計算するLINE Botを作った。

表題の通り。ポケモン剣プレイしてたのですが、あまりに覚えられなさすぎてチャンピオンにボコボコにされたので作りました。

相性ととくせいを教えるBot

防御側のタイプを入力すると、攻撃側の相性を返却します。

本当は8世代のポケモンの名前を入力してダメージ倍率を出したかったのですが、使ってるポケモン図鑑のjsonが8世代対応してなかったので対応待ちです。(2020/08/16追記)使用しているpokemon.jsonが更新停止してしまったので、別のjsonを使用してポケモンの名前で検索できるようにしました。詳細はREADME.mdを参照してください。ついでにとくせいも返却しています。

相性覚えられなさすぎてポケモンGOでロケット団のリーダーにも全然勝てなかったのですが、これでどうにかなりそうです。

以下ソースコード。Mojolicious::Liteというか、Perlが使いにくいのでPythonあたりで作ったほうが良かったかもしれないですね。
retrorocket/poke-weak: ポケモンの相性チェック用LINE Bot

使用VPSをWebARENA VPSクラウドからIndigoに変更しました。

使用しているVPSを
VPSクラウド 初期料金無料・月額360円~|VPS(仮想専用サーバー)はWebARENA
から
Indigo 初期料金無料・従量課金制|VPS(仮想専用サーバー)はWebARENA
に変更しました。

ベンチマーク比較

WebArena VPSクラウド(Indigo/SuitePro含)の料金/特徴/評判/性能 | VPS比較 2019年版 を見たのですが、掲載されているVPSクラウド2GBプランのCPUスコア(2317)が実感よりもかなり高く、疑問があったので、VPSクラウド(ゾーン1)とIndigoの2GBプランでUnixBenchを実行しました。

VPSクラウド

========================================================================
   BYTE UNIX Benchmarks (Version 5.1.3)

   System: xxxxxx: GNU/Linux
   OS: GNU/Linux -- 3.10.0-1062.4.1.el7.x86_64 -- #1 SMP Fri Oct 18 17:15:30 UTC 2019
   Machine: x86_64 (x86_64)
   Language: en_US.utf8 (charmap="UTF-8", collate="UTF-8")
   CPU 0: Intel Xeon E312xx (Sandy Bridge) (4400.0 bogomips)
          x86-64, MMX, Physical Address Ext, SYSENTER/SYSEXIT, SYSCALL/SYSRET
   CPU 1: Intel Xeon E312xx (Sandy Bridge) (4400.0 bogomips)
          x86-64, MMX, Physical Address Ext, SYSENTER/SYSEXIT, SYSCALL/SYSRET
   19:30:54 up 19 days,  6:37,  1 user,  load average: 0.01, 0.04, 0.05; runlevel

------------------------------------------------------------------------
Benchmark Run: 日 11月 10 2019 19:30:54 - 19:58:58
2 CPUs in system; running 1 parallel copy of tests

Dhrystone 2 using register variables       25924548.6 lps   (10.0 s, 7 samples)
Double-Precision Whetstone                     3925.3 MWIPS (9.8 s, 7 samples)
Execl Throughput                               1977.6 lps   (30.0 s, 2 samples)
File Copy 1024 bufsize 2000 maxblocks        310129.8 KBps  (30.0 s, 2 samples)
File Copy 256 bufsize 500 maxblocks           81236.5 KBps  (30.0 s, 2 samples)
File Copy 4096 bufsize 8000 maxblocks        983120.4 KBps  (30.0 s, 2 samples)
Pipe Throughput                              424206.6 lps   (10.0 s, 7 samples)
Pipe-based Context Switching                 120468.4 lps   (10.0 s, 7 samples)
Process Creation                               6587.9 lps   (30.0 s, 2 samples)
Shell Scripts (1 concurrent)                   4479.8 lpm   (60.0 s, 2 samples)
Shell Scripts (8 concurrent)                    904.1 lpm   (60.0 s, 2 samples)
System Call Overhead                         368301.9 lps   (10.0 s, 7 samples)

System Benchmarks Index Values               BASELINE       RESULT    INDEX
Dhrystone 2 using register variables         116700.0   25924548.6   2221.5
Double-Precision Whetstone                       55.0       3925.3    713.7
Execl Throughput                                 43.0       1977.6    459.9
File Copy 1024 bufsize 2000 maxblocks          3960.0     310129.8    783.2
File Copy 256 bufsize 500 maxblocks            1655.0      81236.5    490.9
File Copy 4096 bufsize 8000 maxblocks          5800.0     983120.4   1695.0
Pipe Throughput                               12440.0     424206.6    341.0
Pipe-based Context Switching                   4000.0     120468.4    301.2
Process Creation                                126.0       6587.9    522.9
Shell Scripts (1 concurrent)                     42.4       4479.8   1056.6
Shell Scripts (8 concurrent)                      6.0        904.1   1506.8
System Call Overhead                          15000.0     368301.9    245.5
                                                                   ========
System Benchmarks Index Score                                         681.1

------------------------------------------------------------------------
Benchmark Run: 日 11月 10 2019 19:58:58 - 20:27:41
2 CPUs in system; running 2 parallel copies of tests

Dhrystone 2 using register variables       51601555.8 lps   (10.0 s, 7 samples)
Double-Precision Whetstone                     7813.5 MWIPS (9.8 s, 7 samples)
Execl Throughput                               4113.1 lps   (30.0 s, 2 samples)
File Copy 1024 bufsize 2000 maxblocks        580218.7 KBps  (30.0 s, 2 samples)
File Copy 256 bufsize 500 maxblocks          152260.9 KBps  (30.0 s, 2 samples)
File Copy 4096 bufsize 8000 maxblocks       1825531.3 KBps  (30.0 s, 2 samples)
Pipe Throughput                              860609.2 lps   (10.0 s, 7 samples)
Pipe-based Context Switching                 238577.6 lps   (10.0 s, 7 samples)
Process Creation                              14930.7 lps   (30.0 s, 2 samples)
Shell Scripts (1 concurrent)                   6450.5 lpm   (60.0 s, 2 samples)
Shell Scripts (8 concurrent)                    940.4 lpm   (60.0 s, 2 samples)
System Call Overhead                         693496.6 lps   (10.0 s, 7 samples)

System Benchmarks Index Values               BASELINE       RESULT    INDEX
Dhrystone 2 using register variables         116700.0   51601555.8   4421.7
Double-Precision Whetstone                       55.0       7813.5   1420.6
Execl Throughput                                 43.0       4113.1    956.5
File Copy 1024 bufsize 2000 maxblocks          3960.0     580218.7   1465.2
File Copy 256 bufsize 500 maxblocks            1655.0     152260.9    920.0
File Copy 4096 bufsize 8000 maxblocks          5800.0    1825531.3   3147.5
Pipe Throughput                               12440.0     860609.2    691.8
Pipe-based Context Switching                   4000.0     238577.6    596.4
Process Creation                                126.0      14930.7   1185.0
Shell Scripts (1 concurrent)                     42.4       6450.5   1521.3
Shell Scripts (8 concurrent)                      6.0        940.4   1567.3
System Call Overhead                          15000.0     693496.6    462.3
                                                                   ========
System Benchmarks Index Score                                        1244.0

Indigo

========================================================================
   BYTE UNIX Benchmarks (Version 5.1.3)

   System: xxxxxx: GNU/Linux
   OS: GNU/Linux -- 3.10.0-1062.4.1.el7.x86_64 -- #1 SMP Fri Oct 18 17:15:30 UTC 2019
   Machine: x86_64 (x86_64)
   Language: en_US.utf8 (charmap="UTF-8", collate="UTF-8")
   CPU 0: Intel Xeon E312xx (Sandy Bridge) (4400.0 bogomips)
          Hyper-Threading, x86-64, MMX, Physical Address Ext, SYSENTER/SYSEXIT, SYSCALL/SYSRET
   CPU 1: Intel Xeon E312xx (Sandy Bridge) (4400.0 bogomips)
          Hyper-Threading, x86-64, MMX, Physical Address Ext, SYSENTER/SYSEXIT, SYSCALL/SYSRET
   10:56:17 up 1 min,  1 user,  load average: 0.28, 0.14, 0.05; runlevel 3

------------------------------------------------------------------------
Benchmark Run: Sun Nov 10 2019 10:56:17 - 11:24:27
2 CPUs in system; running 1 parallel copy of tests

Dhrystone 2 using register variables       31197605.1 lps   (10.0 s, 7 samples)
Double-Precision Whetstone                     4522.7 MWIPS (10.2 s, 7 samples)
Execl Throughput                               1894.8 lps   (29.6 s, 2 samples)
File Copy 1024 bufsize 2000 maxblocks        271307.0 KBps  (30.0 s, 2 samples)
File Copy 256 bufsize 500 maxblocks           69907.7 KBps  (30.0 s, 2 samples)
File Copy 4096 bufsize 8000 maxblocks       1063671.5 KBps  (30.0 s, 2 samples)
Pipe Throughput                              456484.5 lps   (10.0 s, 7 samples)
Pipe-based Context Switching                 135959.2 lps   (10.0 s, 7 samples)
Process Creation                               8357.8 lps   (30.0 s, 2 samples)
Shell Scripts (1 concurrent)                   4547.2 lpm   (60.0 s, 2 samples)
Shell Scripts (8 concurrent)                   1011.5 lpm   (60.0 s, 2 samples)
System Call Overhead                         434753.8 lps   (10.0 s, 7 samples)

System Benchmarks Index Values               BASELINE       RESULT    INDEX
Dhrystone 2 using register variables         116700.0   31197605.1   2673.3
Double-Precision Whetstone                       55.0       4522.7    822.3
Execl Throughput                                 43.0       1894.8    440.7
File Copy 1024 bufsize 2000 maxblocks          3960.0     271307.0    685.1
File Copy 256 bufsize 500 maxblocks            1655.0      69907.7    422.4
File Copy 4096 bufsize 8000 maxblocks          5800.0    1063671.5   1833.9
Pipe Throughput                               12440.0     456484.5    366.9
Pipe-based Context Switching                   4000.0     135959.2    339.9
Process Creation                                126.0       8357.8    663.3
Shell Scripts (1 concurrent)                     42.4       4547.2   1072.5
Shell Scripts (8 concurrent)                      6.0       1011.5   1685.8
System Call Overhead                          15000.0     434753.8    289.8
                                                                   ========
System Benchmarks Index Score                                         728.3

------------------------------------------------------------------------
Benchmark Run: Sun Nov 10 2019 11:24:27 - 11:52:30
2 CPUs in system; running 2 parallel copies of tests

Dhrystone 2 using register variables       60039821.0 lps   (10.0 s, 7 samples)
Double-Precision Whetstone                     9182.8 MWIPS (9.7 s, 7 samples)
Execl Throughput                               4603.8 lps   (29.8 s, 2 samples)
File Copy 1024 bufsize 2000 maxblocks        634465.0 KBps  (30.0 s, 2 samples)
File Copy 256 bufsize 500 maxblocks          165369.1 KBps  (30.0 s, 2 samples)
File Copy 4096 bufsize 8000 maxblocks       2085180.7 KBps  (30.0 s, 2 samples)
Pipe Throughput                              912044.0 lps   (10.0 s, 7 samples)
Pipe-based Context Switching                 274306.0 lps   (10.0 s, 7 samples)
Process Creation                              17890.6 lps   (30.0 s, 2 samples)
Shell Scripts (1 concurrent)                   7618.4 lpm   (60.0 s, 2 samples)
Shell Scripts (8 concurrent)                   1057.0 lpm   (60.1 s, 2 samples)
System Call Overhead                         833999.5 lps   (10.0 s, 7 samples)

System Benchmarks Index Values               BASELINE       RESULT    INDEX
Dhrystone 2 using register variables         116700.0   60039821.0   5144.8
Double-Precision Whetstone                       55.0       9182.8   1669.6
Execl Throughput                                 43.0       4603.8   1070.7
File Copy 1024 bufsize 2000 maxblocks          3960.0     634465.0   1602.2
File Copy 256 bufsize 500 maxblocks            1655.0     165369.1    999.2
File Copy 4096 bufsize 8000 maxblocks          5800.0    2085180.7   3595.1
Pipe Throughput                               12440.0     912044.0    733.2
Pipe-based Context Switching                   4000.0     274306.0    685.8
Process Creation                                126.0      17890.6   1419.9
Shell Scripts (1 concurrent)                     42.4       7618.4   1796.8
Shell Scripts (8 concurrent)                      6.0       1057.0   1761.6
System Call Overhead                          15000.0     833999.5    556.0
                                                                   ========
System Benchmarks Index Score                                        1418.7

やはりVPS比較に掲載されてるunixbenchのスコアはおかしい気がします(ゾーンごとに違うのかもしれないのですが、これほど差が出るかはわかりません)。こういうのは自分で計測したほうがよさそうですね。
VPSクラウド と Indigo のベンチマーク比較結果 - Qiita の記事のスコアは私が計った結果に近いです。ディスクI/OはVPSクラウドのほうが圧倒的に性能が良いです。というよりもIndigoがめちゃくちゃ遅いです。

どちらを選ぶべきか

DNS逆引きが任意のFQDNに設定できない時点でどちらも変わらないので、安いほうにしましたが、すでにVPSクラウドを使っている人が無理して乗り換える理由はないと思います。VPSクラウドのほうがロードバランサとかあるしディスクI/Oは速いので。
ただ、課金が従量制なのはかなり魅力的だと思います。少ししか使わない人はIndigoのほうがコスパが良いです。
ちなみにお金払っても逆引きは任意のFQDNに設定できません。詐欺でしょこんなん。
DNS(有料オプション) | Indigo | VPS(仮想専用サーバー)はWebARENA

逆引きは予め設定済みであり、お客さま独自ドメインでの設定への変更はできません。インスタンスのIPアドレスでの逆引き設定はできません。

Indigoのだめなところ

  1. ログインできない(ssh鍵認証できない)インスタンスが払い出される場合がある
    一回ログインできずにインスタンスを作り直しました(作り直したらログインできた)。いくらなんでもこれはない。

  2. ログインユーザのIDがわかりにくい
    CentOSの場合、centosが初期IDなのですが、書いてるページが見つけにくすぎる。
    インスタンスへのログイン方法 | Indigo | VPS(仮想専用サーバー)はWebARENA
    こんなんわからんわ。

  3. ブラウザコンソールがあるが、ログイン用のパスワードがわからない
    初期ユーザのログインパスワード未だにわからないんだけど、ssh死んだ時どうすればいいのこれ。

  4. コンパネの動作があやしい
    インスタンス停止したのに実は停止してなかったりあやしさがすごい。インスタンス操作用のAPIを使ったほうが確実かもしれません。
    WebARENA Indigo API

  5. サービス開始日をごまかした
    本当は2019/10/23にサービスが開始されたのですが、プレスリリースとかサービスページとかまるごと全部削除された後10/28開始だったことにされました。VPSクラウドの解約ルールが「月末7営業日前までに申請された場合、当月末日の解約となります。月末6営業日前から月末までに申請された場合、翌月末日の解約となります。」なので、「VPSクラウドの当月解約を防ぐためだったんじゃないの?」とか、「かなりやばげなバグを見つけたんじゃないの?」とか、いらん疑いをかけてしまいますね。

総評

いろいろやばそうだけどとりあえず使ってみます。

CentOS 7でfail2banが動作しない。

postfix-sasl向けにfail2banを導入していたのですが、failedは検知してくれてるのに全然Banできてなかったので原因を調べました。私の環境ではfirewalldとipsetを使ってBanしています。
/etc/fail2ban/jail.local はこんな感じ。

[DEFAULT]
bantime = -1
findtime = 600
maxretry = 5
backend = systemd
banaction = firewallcmd-ipset

[sshd]
enabled = true

[postfix-sasl]
enabled = true
logpath = /var/log/mail/maillog

[sshd-ddos]
enabled = true

/var/log/fail2ban.logには特に情報がありませんでした。
DEFAULTのbanactionにfirewallcmd-ipsetを設定しているので、firewallcmd-ipsetの動作を見直せば動くかなぁ。

/etc/fail2ban/action.d/firewallcmd-ipset.confはこんな感じ。

[Definition]

actionstart = ipset create fail2ban-<name> hash:ip timeout <bantime>
              firewall-cmd --direct --add-rule ipv4 filter <chain> 0 -p <protocol> -m multiport --dports <port> -m set --match-set fail2ban-<name> src -j <blocktype>

actionstop = firewall-cmd --direct --remove-rule ipv4 filter <chain> 0 -p <protocol> -m multiport --dports <port> -m set --match-set fail2ban-<name> src -j <blocktype>
             ipset flush fail2ban-<name>
             ipset destroy fail2ban-<name>

actionban = ipset add fail2ban-<name> <ip> timeout <bantime> -exist

actionunban = ipset del fail2ban-<name> <ip> -exist

firewall-cmd --reloadがないですね。そりゃ動かんわ。(2019/10/30追記)ドキュメント読む限り、reloadがないのが原因ではなかったのですが、じゃあなんで動かなかったのかがわからない…。(参考:Documentation - HowTo - Reload firewalld | firewalld)bantimeをデフォルト値にしても動作しませんでした。

/etc/fail2ban/action.d/firewallcmd-ipset.localで設定を上書きします。bantimeいらないなぁとか色々考えた結果以下で運用することにしました。

[Definition]
actionstart = ipset create fail2ban-<name> hash:ip
              firewall-cmd --permanent --direct --add-rule ipv4 filter <chain> 0 -m set --match-set fail2ban-<name> src -j <blocktype>
              firewall-cmd --reload

actionstop = firewall-cmd --permanent --direct --remove-rule ipv4 filter <chain> 0 -m set --match-set fail2ban-<name> src -j <blocktype>
             firewall-cmd --reload
             ipset flush fail2ban-<name>
             ipset destroy fail2ban-<name>

actionban = ipset add fail2ban-<name> <ip> -exist

[Init]
blocktype = DROP

無事Banされるようになりました。

Status for the jail: postfix-sasl
|- Filter
|  |- Currently failed: 0
|  |- Total failed:     0
|  `- Journal matches:  _SYSTEMD_UNIT=postfix.service
`- Actions
   |- Currently banned: 9
   |- Total banned:     9
   `- Banned IP list:   大体中国