SNS

GitHub ActionsのデプロイをSCPからrsync --deleteに変えた(ハマりどころと実行回数の節約)

投稿日:
|

このサイトは、GitHub Actionsでビルドした静的ファイルをXserverにアップロードしています。デプロイがしばらく失敗したままになっていたのをきっかけに、SCPからrsyncに変更し、ついでに実行回数も絞りました。途中でハマったところも含めて残します。

1. まずSCPのデプロイが失敗していた

appleboy/scp-action のログには次のように出ていました。

ssh.ParsePrivateKey: ssh: no key found
ssh: handshake failed: ssh: unable to authenticate, attempted methods [none], no supported methods remain

Secretsに登録していた秘密鍵が、鍵として読み込めない状態でした。手元で使っている鍵をそのまま登録し直すこともできますが、Actions専用の鍵ペアを作ってサーバーの authorized_keys に追加しました。万一漏れても、その鍵だけを無効にすればよく、手元のSSHには影響しません。

$ ssh-keygen -t ed25519 -N "" -C "github-actions" -f ./id_actions
$ gh secret set SFTP_KEY < ./id_actions

2. strip_componentsを付けないと、out/ディレクトリごと置かれる

鍵を直したらデプロイは成功しましたが、ファイルがドキュメントルート直下ではなく public_html/out/ に置かれていました。source: "./out/*" と書いても、out/ というパスが付いたままアップロードされるためです。

source: "./out/*"
strip_components: 1 # out/ ディレクトリごとではなく中身を target 直下に置く

本番の表示に影響はありませんでしたが、https://example.com/out/ にサイトのコピーが公開された状態になっていたので、すぐに削除しました。

3. rsync --deleteに変更

SCPは上書きするだけで、削除したページや古いビルドファイルがサーバーに残り続けます。そこで、rsyncの --delete で out/ とサーバーを同期する形にしました。

注意点は、サーバー上でだけ管理しているものまで消してしまうことです。このサイトでは、同じドキュメントルートの中にWordPress(サブドメイン用のフォルダ)を置いているので、除外リストをファイルにまとめました。

/cms.example.com/
/.well-known/
.DS_Store

--exclude で指定したパスは、--delete でも削除されません。手元からのデプロイ(npm run deploy)とActionsで同じファイルを使っています。

- name: Deploy to Xserver via rsync
  env:
    SSH_KEY: ${{ secrets.SFTP_KEY }}
    SSH_HOST: ${{ secrets.SFTP_HOST }}
    SSH_PORT: ${{ secrets.SFTP_PORT }}
    SSH_USER: ${{ secrets.SFTP_USER }}
    TARGET_DIR: ${{ secrets.SFTP_TARGET_DIR }}
  run: |
    mkdir -p ~/.ssh
    printf '%s\n' "$SSH_KEY" > ~/.ssh/deploy_key
    chmod 600 ~/.ssh/deploy_key
    ssh-keyscan -p "$SSH_PORT" "$SSH_HOST" >> ~/.ssh/known_hosts 2>/dev/null
    rsync -az --delete --exclude-from=.rsync-exclude \
      -e "ssh -p $SSH_PORT -i ~/.ssh/deploy_key" \
      out/ "$SSH_USER@$SSH_HOST:$TARGET_DIR/"

--delete は消すファイルを間違えると取り返しがつかないので、初回は必ず -n(dry-run)で削除されるファイルを確認しました。

$ rsync -avzn --delete --exclude-from=.rsync-exclude out/ server:~/public_html/ | grep '^deleting'

実際に、dry-runで想定外の削除(作業途中の画像の差し替え)が見つかり、デプロイを止めて確認できました。

4. 実行回数を絞る

privateリポジトリの場合、GitHub Freeでは毎月の無料枠(分数)が決まっています。READMEを直しただけで本番デプロイが走るのは無駄なので、サイトの出力に関係するファイルが変わったときだけ動くようにしました。

on:
  push:
    branches:
      - main
    paths:
      - 'src/**'
      - 'public/**'
      - 'content/**'
      - 'package.json'
      - 'package-lock.json'
      - 'next.config.mjs'
      # ビルド時に生成されるサイトマップの更新だけではデプロイしない
      - '!public/sitemap*.xml'
  workflow_dispatch:

# 連続で push したときは、古い実行を取り消して最新だけを実行する
concurrency:
  group: deploy
  cancel-in-progress: true

あわせて actions/setup-node の cache: npm と npm ci で、インストールの時間も短くしています。

まとめ

  • Actions用のSSH鍵は専用のものを作る
  • scp-action で out/* を送るなら strip_components: 1
  • rsync --delete は除外リストとdry-runをセットで
  • paths と concurrency で無駄な実行を減らす

CONTACT

サイト訪問ありがとうございます。
お仕事のご依頼、ご相談がありましたらお気軽にご連絡ください。
*は必須項目となります。

お名前 *
メールアドレス *
会社・組織名
お問い合わせ内容 *
0/300

当サイトはreCAPTCHAによって保護されており、Google のプライバシーポリシーおよび利用規約が適用されます。
当サイトのプライバシーポリシーをご一読の上ご連絡ください。