Masteries

技術的なことや仕事に関することを書いていきます.

GitHub Actionsでactions/cacheを使わずS3で必要なファイルをキャッシュする

papix.hatenablog.com

先日, 「GitHub Actionsの知見ご紹介」でactions/cacheが意図通り動作しないケースがある, ということをご紹介しました. 今回は, actions/cacheの代わりにS3を使ってキャッシュを実現する方法をご紹介します. ...まあ, ご紹介しますというか, まあ普通にawsコマンドを使って必要なファイルをS3に置いたり引っ張ってきたりするだけなのですが.

ここでは, 例としてcpanfile.snapshotを元にして, Perlのモジュールがインストールされるディレクトリ(local)をS3でキャッシュする例をご紹介します. Node.jsであれば, cpanfile.snapshotpackage.json, localnode_modulesと読み替えればOKです.

前提として, 適当なS3バケット(以下の例では, sample-cache-bucketという名前としました)を用意しておいてください. また, S3への読み書きが可能なIAMアカウントを用意して, そのAccess Key IDとSecret Access KeyをGitHubのSecrets(リポジトリのSettingsSecrets)で, それぞれAWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYという名前で登録しておく必要があります.

env:
  cache-bucket: sample-cache-bucket
  cache-directory: perl

jobs:
  prepare:
    runs-on: ubuntu-latest
    steps:

    - name: Configure AWS Credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ap-northeast-1

    - name: Fetch cached perl modules
      run: |
        if [[ $(aws s3 ls s3://${{ env.cache-bucket }}/${{ env.cache-directory }}/${{ hashFiles('cpanfile.snapshot') }}.tar.gz | wc -l) > 0 ]]; then
          aws s3 cp --no-progress s3://${{ env.cache-bucket }}/${{ env.cache-directory }}/${{ hashFiles('cpanfile.snapshot') }}.tar.gz perl-modules.tar.gz
          tar -zxf perl-modules.tar.gz
        else
          echo "Cache not found"
        fi

    # ここで必要に応じてライブラリのインストール(carton install, npm installなど)を実行する

    - name: Cache perl modules
      run: |
        if [[ $(aws s3 ls s3://${{ env.cache-bucket }}/${{ env.cache-directory }}/${{ hashFiles('cpanfile.snapshot') }}.tar.gz | wc -l) > 0 ]]; then
          echo "Cache already exists"
        else
          tar -zcf perl-modules.tar.gz local
          aws s3 cp --no-progress perl-modules.tar.gz s3://${{ env.cache-bucket }}/${{ env.cache-directory }}/${{ hashFiles('cpanfile.snapshot') }}.tar.gz
        fi

...少なくとも, runs-onubuntu-latestの場合, デフォルトでawsコマンドが/usr/local/bin/awsに用意されています. 2020年5月現在バージョンは1.18.37となっているようで, 最新の2系ではないのですが, 例なので今回はこれをそのままを使うことにしましょう(より新しいバージョンのawsコマンドが必要であれば, 別途インストールしてください).

後はaws-actions/configure-aws-credentials@v1を利用して適切にAWSアカウントの認証(ここでSecretsで設定したAWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYを利用します)をした上で, awsコマンドを使って S3からファイルを撮ってくる/ファイルを設置するだけです.

これで,

  • cpanfile.snapshotに対応するキャッシュがあれば...
    • S3からこれをダウンロードして利用する
    • 必要なライブラリは全てキャッシュから復元されているはずなので, ライブラリのインストールをしても差分なしですぐに完了する
  • cpanfile.snapshotが書き換えられるなどしており, 対応するキャッシュがないなら...
    • ライブラリのインストールを実行して, その結果を最後にS3にアップロードする
    • 2回目以降はキャッシュが利用されるようになる

...という挙動が実現できます.

S3を使えば, 前のエントリで紹介したactions/cacheのハマりポイントを意識する必要はありませんし(2〜3週間運用していますが, キャッシュの取得にミスした事例はほぼほぼなくなった), 必要があればAWSのWebコンソールなどを使って, キャッシュを削除したりできるので, 適切なS3バケットが用意できるのであればむしろこちらの方が便利なのでは...? という気がしています.

一方で, 当然ながら別途S3の料金が必要になるのはデメリットですね. なるべく料金を節約できるよう, ライフサイクルルールを設定するなどしつつ利用すると良さそうです.

「Kubernetes完全ガイド」読んだ

Kubernetes完全ガイド (impress top gear)

Kubernetes完全ガイド (impress top gear)

  • 作者:青山 真也
  • 発売日: 2018/09/21
  • メディア: 単行本(ソフトカバー)

Kubernetesの入り口として推薦してもらっていて, 調べた所Kindle Unlimitedで読めたのでここ最近ちまちまと読み進めていました.

感想としては, Kubernetesの概要というか, 雰囲気を掴む本としてとても良いと感じました. 特に, 巻末付録に「よくある質問とその答え」コーナーがあるのが良かったです. ここを見るだけで, 「Kubernetesでは◯◯が出来る」ということを一通り(薄く)抑えることが出来るので, それを元に興味を持った部分を読み進めるとか, 或いは業務に活かすといったことが出来そうと思います.

あとはKubernetesにおけるモニタリング(15章), ログの扱い(16章), CIやCD(17章)について述べられていたのも参考になりました. この辺りはまだ前時代的(?)な考えでやっているので, そのコンテナの時代(?)とのギャップを埋める第一歩になりました.

まあそうですね, という感じではありますが, 本を読むだけだと雰囲気は掴めるものの実際に技術として身につく(?)感じは少ないので, この本を入り口にしてGKEとか使って実際に手を動かしてみる... という感じでやっていってみたいと思いました.

PerlでTSVをパースする

小ネタです, 忘備録として残しておきます.

metacpan.org

PerlにはText::CSVという, CSV(Comma Separated Value)をパースするモジュールがあります. 一方で, TSV(Tab Separated Value)をパースするモジュールは, パッと見た感じいいのがなさそうでした.

解決策は以下の3つがありそうです:

split など駆使して手動でパースする

一番素朴なのは, こういう感じで split などを使って, タブを切り分ける方法でしょう($line が1行のTSVとします).

my @fields = split /\t/, $line;

...とはいえ素朴ですし, エッジケース(あるのか?)に対応していないかも... という気がするので, なるべくよしなにモジュールの力を借りたいと思うかもしれません. そういう時は...

Text::CSV_XS::TSV を使う

metacpan.org

結局のところ, CSVとTSVは似ている部分が多いので, 適切な設定をすればText::CSVでもTSVをパースできます. 例えばText::CSV_XS::TSVというCPANモジュールがあり, これはTSVを適切にパースできる設定が有効になったText::CSV_XSを提供する... というモノです.

Text::CSVのオプションを変更する

さて, Text::CSV_XS::TSVのドキュメントを鑑みると, もしText::CSVでTSVをパースしたいのであれば,, 次のように設定してあげると良さそうです:

my $tsv = Text::CSV->new({
    sep_char => "\t",
    quote_char => undef,
    escape_char => undef,
});

...多分, これが一番ベターだと思います.

とはいえ, Text::CSVはコアモジュールではないので, コアモジュール縛りで行くならsplit を駆使する手を採用するということもあるでしょう. TMTOWTDIということで, その時々で最適な選択肢を選んでTSVをパースしていけると良さそうです.

GitHub Actionsの知見ご紹介

今更ですが, ここ最近ちまちまとGitHub Actionsをしています.

github.co.jp

個人的にはこういうのイジるの大好きなので, 新しいおもちゃをもらった子供のようにはしゃいでいます. 今回は, その中で知った知見などを雑多にご紹介します.

Pull Requestでコケた時にRe-run jobsするとactions/cacheアクションが正常に動作しない

GitHub Actionsには, 依存ファイル(例えばPerlならlocalとか, Node.jsならnode_modulesとか...)をキャッシュする, actions/cacheアクションが公式から提供されています.

github.com

このアクションを使っていて, かつon: pull_request のようにしてPull Requestをフックとしてワークフローを実行するとき, Re-run jobs(再実行)を実行すると, 次のような警告の文言が出て正常に動作しない問題が存在しています.

[warning]No scopes with read permission were found on the request.

これはあくまでwarning(警告)なので, キャッシュが取得できなかったものとしてワークフローの処理は継続します. そのため, 「actions/cacheでキャッシュがあれば, 必ず取得する」ことを前提とするワークフローを作っていると, 意図しない挙動になる可能性があります.

詳しくは次のIssueに記載されています.

github.com

2020年4月13日現在, この問題は開発陣に認識されてはいるものの, まだ修正は完了していないようです.

github.com

回避策としては... 空commitをpushすれば動くっちゃあ動くのですが, それはどうなのか... という感じですね. 一応 on: push としてpushをフックとすれば問題なくRe-run jobsできる... という話を聞きましたが, 自分はまだ試していません. 他の手としては, S3などにファイルを設置して, それを引いてくるなどの作戦もありそうです.

actions/cacheアクションは時折キャッシュの取得に失敗することがある

またもやactions/cacheアクションの話題です. タイトルにある通りで, actions/cacheアクションは時折次のような警告を表示してキャッシュが存在していたとしてもキャッシュの取得に失敗することがあります:

[warning]connect ETIMEDOUT xxx.xxx.xxx.xxx:443

github.com

これもwarning(警告)なので, キャッシュが取得できなかったものとしてワークフローの処理は継続します.

この問題も開発陣には認識はされていて, 原因はGitHub Actionsのランナーとキャッシュを保存するクラウドストレージの間のネットワークの問題とのことです. 自分たちの環境では, 1つのワークフローから一度に複数のジョブを走らせて, その全てで同じキャッシュを(actions/cacheを使って)引く... ということをしているので, こういった事象が起こりやすいのかもしれません.

また, 上記のIssueには, 「キャッシュアクションはベストエフォートで, もし失敗した時はそれ以降のステップでキャッシュされていたはずのコンテンツを再生成可能であると想定している(Please note, however, that the cache action is "best effort" and assumes that if it fails, the subsequent steps can recreate the cached content.)」と書かれているので, actions/cacheアクションを利用するときはその点考慮してワークフローを作る必要がありそうです.

Pull Requestでactions/checkoutを使うとmerge commitになる

こちらもまた公式が提供するアクションとして, Gitリポジトリをcheckoutするためのactions/checkoutアクションが存在します.

github.com

Pull Requestでactions/checkoutを使ってGitリポジトリを取得すると, HEAD がmergeコミットになっています. このままワークフローの中で新しいbranchを作ってcommitしてpushしてPull Requestを作ると, 差分に次のようなmergeコミットが含まれてしまいます.

赤線部がmergeコミット

ちなみにGitHub Actionsのワークフロー内でPull Requestを作ったりといったGitHubの操作をしたい時は, actions/github-scriptを使うのが便利そうです.

github.com

...で, この問題についてはきちんとREADME.mdを見ましょう, という話で, Checkout pull request HEAD commit instead of merge commit という項に解決策が書かれています:

- uses: actions/checkout@v2
  with:
    ref: ${{ github.event.pull_request.head.sha }}

README.mdのUsageには, ref以外に指定可能なオプションの解説があるので, 「actions/checkoutアクションでこういうこと出来ないかな...?」と思ったら改めてREADME.mdを一通り眺めると良さそうです.

まとめ

備忘録がてら, GitHub Actionsをしている時に気づいた事, ちょっと躓いたポイントなどをまとめてみました. ここ2週間くらいGitHub Actionsに熱中していますが, 公式が提供する各種アクションが便利(actions/cacheはちょっとハマりポイントが多かったですが...)ですし, GitHubと良い意味で密結合していて便利なので, 引き続きいろいろやっていって知見を共有できればと思っています.

Git submodule 向き先 変更

最近いろいろあってGitのsubmoduleの向き先を変更する機会があったのですが, 2回やって2回ともやり方を忘れていてググる羽目になったのでメモ.

qiita.com

基本的にはこちらの記事に掲載されている手順を踏めばよくて,

  • .gitmodules の向き先を書き換える
  • git submodule sync する

...でよさそう. 以上忘備録でした.