cockscomblog?

cockscomb on hatena blog

GitHub ActionsでDocker Buildするときのキャッシュテクニック

GitHub Actionsでdocker buildすることが多い。このときのキャッシュをどうするかという話題。

基本

GitHub Actionsでdocker buildしてAmazon ECRにdocker pushする、典型的な.github/workflow/docker-push-to-ecr.ymlはこういう感じ。

name: Push to Amazon ECR

on:
  push:
    branches: [ 'main' ]

jobs:

  docker:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - uses: docker/setup-buildx-action@v1

      - 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: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - uses: docker/metadata-action@v3
        id: meta
        with:
          images: |
            ${{ steps.login-ecr.outputs.registry }}/awesome-app 
          tags: |
            type=sha,prefix=

      - uses: docker/build-push-action@v2
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Docker周りはDocker公式のアクションを組み合わせて使って、BuildKitでやる。

キャッシュはdocker/build-push-action@v2cache-fromcache-toでやっていて、type=ghaとしてGitHub Actionsのキャッシュにのせている。

type=ghaは、BuildKitで使えるキャッシュの中でも手軽で使いやすい。GitHub Actionsのキャッシュはブランチと紐づいていて、現在のブランチとベースブランチのキャッシュが利用できるので、CIと組み合わせたときにわりと都合がいい。

Build Matrixとの組み合わせ

背景

去年、業務で利用しているMacがM1 Proのそれになって、我らがCTO id:motemenが用意しているmod_perl用Dockerイメージ(motemen/docker-mod_perl)にパッチを送り、arm64アーキテクチャのイメージをビルドしてもらうようにした。

変更は簡単で、docker/setup-qemu-actionQEMUのセットアップをして、docker/build-push-actionplatformsを指定した。ベースイメージの時点でマルチアーキテクチャに対応しているし、Dockerfileでアーキテクチャに依存したことをしていないので、これで十分。

マルチアーキテクチャになったのはよかったけど、GitHub Actionsがすごく遅くなってしまった。もともとキャッシュが切れていても5分30秒くらいで済んでいたのが、1時間以上かかるようになった。とはいえ、QEMUでCPUアーキテクチャをまたいでビルドすると遅いのは、それはそう。それはそうなんだけど、キャッシュの効きが悪いのも気になっていた。

f:id:cockscomb:20220215180147p:plain

add perl 5.34.0 · motemen/docker-mod_perl@1b6041d

こういうのをみると、Matrixでビルドしているうち「5.30.1」のみ9秒で終わっているが、残りは1時間近くかかっている。

f:id:cockscomb:20220215180222p:plain

Merge pull request #15 from motemen/modperl-2-0-12 · motemen/docker-mod_perl@ca11e85

さっきのブランチのベースになっているのはこちらで、「5.30.1」が一番最後に終わっていそうだった。

このことから、Matrixビルドでキャッシュが競合していて、後勝ちになっていることが類推された。

scopeを設定する

ということでBuildKitのghaキャッシュのドキュメントをもう一回見ると、scopeというのがある。これを使って、docker/build-push-action@v2cache-fromcache-toscopeを設定してみる。

          cache-from: type=gha,scope=perl-${{ matrix.perl_version }}
          cache-to: type=gha,mode=max,scope=perl-${{ matrix.perl_version }}

これでMatrixごとにキャッシュが分離された。

f:id:cockscomb:20220215175945p:plain

[DNM] test actions · motemen/docker-mod_perl@3b407c8

各バージョンが20秒前後になり、全体で1分弱になった。よかった。

まとめ

GitHub ActionsでBuildKitを使うときにtype=ghaのキャッシュが使える。ただしMatrixビルドなどで並列に複数のジョブが走ると、キャッシュが衝突してしまうが、scopeを設定することで回避できる。

いいマイクアームを買った

f:id:cockscomb:20211221102110p:plain

春にShure MV7というマイクを買って、快適に過ごしていた。

快適に過ごしていたけど、このとき適当なマイクアームを買っていたことで、気になるところがあった。

口から近い位置に設置できた方がいいので、適当な安いアームも買った。

マイクアームとしての用は足りているが、最高ではなかった。安いので、見た目が少し野暮ったい。特にバネが露出していることで、2歳の娘の格好の餌食となり、ビヨンビヨンやられる。ビヨンビヨンされるとアームを介してマイクが音を拾ってしまう。

もう少しマシなマイクアームを買ってもいいかと思ったが、普通に売っているマイクアームでは解決しない本質的な課題に気づきつつあった。

人体の構造上、口は目より下側にある。マイクは口の近くに配置したい。そして通常、マイクアームは上側からマイクをぶら下げる形になっている。

f:id:cockscomb:20211220233751j:plain

模式的に表すとこうです。わかりますか

おわかりだろうか。マイクアームは人間の視界を塞ぐのである。目とディスプレイの間にマイクアームが置かれることになってしまう。これはあまりスマートではないと思う。思いませんか?

Wave Mic Arm LP

ということで、ゲーム配信者向けのデバイスのブランド「Elgato」から、Wave Mic Arm LPが出ている。これだとマイクを上からぶら下げるのではなく、横から保持できる。

海外では以前から販売されていて目をつけていたのだが、ついに日本でも12月3日から発売になった。

Elgatoの機能性とデザイン性に優れた2種類のマイクアームとPS5などに対応のオーディオアダプタ「Chat Link Pro」の販売を開始

発売に数日気づかず、12月7日にAmazonで「一時的に在庫切れ; 入荷時期は未定です。」の表示になっていたが、注文できた。しばらく届かないかと思ったが、12月15日には届いた。

ということで設置しました。剛性も高く、ビヨンビヨンしないし、質感も高級感がある。ケーブルも収納できて、何もいうことがない。

Shure MV7にElgato Wave Mic Arm LP、これが新たなスタンダードだと思う。

macOSのメモからMarkdownを作るショートカット

macOS Montereyから、「メモ」アプリに「クイックメモ」という機能がついた。

インターネットブラウジングをしていて気になったことを書きつけていくのにちょうどよさそうなので、使ってみることにする。ある事柄に関連するリンクをひとつのメモに連ねていって、ちょっとテキストで補足を書いておく。

これをブログで手軽に共有できたら、なおいい。Markdown形式に変換したい。

メモの本文を取得する

メモの内容を取得するには、Apple Script的なやつを使う。ここではJXA (JavaScript for Automation)というので、JavaScriptっぽく書く。選択中のノートの本文を得るには、次のようにする。

const app = Application("Notes");
console.log(app.selection()[0].body());

これでノート本文がHTML形式で取得できる。便利だが、リッチな埋め込み形式にはならないのが玉に瑕で、リンクはURLだけになってしまう。とはいえ最終的には、はてなブログの埋め込み機能を使うのだから、まあいい。

ショートカットでMarkdownにする

macOS Montereyから、macOSにも「ショートカット」が搭載された。

これに先ほどのJXAをのせてしまう。最終的なショートカットは次の通り。

f:id:cockscomb:20211031231251p:plain
ショートカット

先ほどのJXAを関数にして、選択されたメモがない場合のハンドリングも加える。

function run(input, parameters) {
    const app = Application("Notes");
    const selections = app.selection();
    if (selections.length === 0) {
        return undefined;
    }
    return selections[0].body();
}

選択されたメモがない場合は「if文」を使ってショートカットを終わらせる。

HTMLからMarkdownへの変換は、「HTMLからリッチテキストを作成」してから「リッチテキストからマークダウンを作成」によって達成される。間にリッチテキストを挟むが、見出しやリストなどはうまく変換できる。

最後は「結果を表示」しているけど、「クリップボードにコピー」でもよさそう。

ショートカットは共有できるので、共有リンクを記載しておく。

これでやっていく

ということで試しにクイックメモを元にしてブログに書き出した。あっさりしてるけど、まあなんでも公開したらいい。

変換したMarkdownをそのまま公開できるほどではなく、マークアップを整えたりした。この辺がよくなるとさらにいいけど、メモから取り出せるHTMLの側にもっと情報が増えないと難しい。

AndroidのウィジェットをComposeのAPIで開発できるGlanceはアルファ版が開発中

AndroidウィジェットをComposeのAPIで作る方法がAndroid Dev Summitで紹介された。RemoteViewにうまく対応する。

Glanceと呼ばれていて、コードは公開されている。

しかしまだアルファ版も出ていない。

アルファリリースに向かって開発中らしい。

Xcode 13.2とSwift Concurrencyのバックデプロイメント、iPadOS向けのSwiftUIでのアプリ開発をサポートしたSwift Playgrounds、macOSでのSharePlayとUniversal Controlはこの秋にリリースされる

この秋のうちにSharePlayとUniversal ControlをサポートするmacOS Montereyがリリースされる。バージョン番号はおそらく12.1になるだろう。

Available later this fall macOS Monterey - Apple

同じタイミングでmacOS Monterey 12.1のSDKを含んだXcode 13.2がリリースされるはずだ。このバージョンのXcodeからSwift Concurrencyのバックデプロイメントが可能になる。

Xcode 13.2 beta includes SDKs for iOS 15.2, iPadOS 15.2, tvOS 15.2, watchOS 8.3, and macOS Monterey 12.1. Apple Developer Documentation

You can now use Swift Concurrency in applications that deploy to macOS 10.15, iOS 13, tvOS 13, and watchOS 6 or newer. Apple Developer Documentation

Xcode 13.2はSwift Playgrounds 4の新しいプロジェクト形式をサポートする。これはiPadOS向けのSwiftUIでのアプリ開発をサポートしたSwift Playgroundsが今年中にリリースされるはずであることと整合する。

Xcode 13.2 beta includes support for app projects created with Swift Playgrounds 4. Apple Developer Documentation

* Available late 2021. iPadOS 15 - Apple

WEB+DB PRESS Vol.125の特集「GraphQL完全ガイド」を執筆しました

f:id:cockscomb:20211017224108p:plain 今週、10月23日(土)に発売されるWEB+DB PRESS Vol.125に掲載される、特集記事「GraphQL完全ガイド」を執筆しました。よろしくおねがいします。

桃栗三年、GraphQL 6年

原稿を書く過程で、知っているはずのことでも改めて調べなおしたりする。特に歴史みたいなのが好きで、GraphQLは2015年6月に発表されて、2018年に安定版になって、みたいなのをずっと調べてしまう。GraphQLってなんかすごい最近っぽく感じていたけど、発表されてからもう6年経つらしい。

ちなみにjQuery 1.0は2006年8月にリリースされていて、Reactは2013年5月に公開されたらしい。6年というのはだいたいそれくらい。

6年で、GraphQLはよく普及した。Facebookはもちろん、GitHubTwitterNetflixも、GraphQLを使っている。GitHubの新しいプロジェクトでも、普通にGraphQL APIを使っている

GraphQL、使っていますか

筆者は2018年からGraphQLを使っている。仕事で隙あらばGraphQLを導入した。サーバー側もクライアント側も何度も作った。そこで得た知見をこのブログに書いたりもした。

なんでもそうだけど、慣れてしまえばなんでもないが、最初は取っ付きにくい。GraphQLもそういうところがある。ということで、なるべく網羅的に説明するようにした。GraphQLをまだ使ったことがなかったり、本格的に取り組んだ経験がない場合に、特に役に立つようなつもりで書いた。

お買い求めください

見本誌が届いたので知っているのだけど、どの記事もおもしろい。例え僅かな可能性としてGraphQLに興味がない場合でも、WEB+DB PRESS Vol.125を買わない理由にはならない。

筆者はよくGihyo Digital Publishingを使う。リフローのできるEPubと、プロが丁寧にレイアウトしたPDFのどちらも入手できて、便利だ。

Amazonで買っても書店で買ってもいいと思う。全部便利だと思う。

雑誌の原稿を書くのは、前回iOS 14に関する特集を同僚らと書いて以来の二度目だが、そうそう慣れるものでもなく、必死だった。編集の皆さまにもお世話になり、また同僚や家族の助力も得て、ようやくというところ。

ということで、何卒よろしくお願いいたします。

こちらからは以上です。

GitHubの新しいプロジェクトを使ってみている

新しいものが好きなので、GitHubの新しいプロジェクトで仕事をしている。まだベータ版だが、少し前に会社のアカウントで有効になったので、所属するチームで大喜びで使い始めた。

もともとしばらくAsanaを使っていて、Asanaはタスクに依存関係がつけられたりして気に入っている。とはいえ仕事の大部分はGitHub上のリポジトリで行うのだから、GitHub上で完結するなら試してみたいわけである。

スプリント

ということでとりあえず仕掛かりのIssueなどをプロジェクトに入れていたのだけど、なんかまだしっくりきていなかった。ドキュメントにはベストプラクティスとかもあるけど、そんなにおもしろいことは書いていない。Single source of truth志向なのはいいと思う。

しっくりこなかったことの一つは、スプリントの表現がうまくいかない感じがしたからだった。Single source of truthということでリポジトリマイルストーンを使う感じかと思ったが、しかし扱っているリポジトリは残念ながら複数あって、それぞれにマイルストーンを設定してまわりたくはない。

と思っていたら今日になっていいのが出た。

このiteration field typeというのが最高。これで何もかも上手くいった。

GitHubの新しいプロジェクトは、今まさにバリバリ開発している感じでおもしろい。

自動化

プロジェクトにIssueとかPull Requestを追加するところは、けっこう面倒に感じる。ということで自動化を試す。

Automating projects (beta) - GitHub Docs

複数のチームに関連する特別なリポジトリで、特定のラベルがついたIssueを、自分のチームのプロジェクトに追加する、というのをやってみた。次のようなGitHub Actionsを設定した。

name: Projects
on:
  issues:
    types:
      - labeled
jobs:
  projects:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/github-script@v5
        with:
          github-token: ${{ secrets.GITHUB_TOKEN_FOR_PROJECTS }}
          script: |
            const script = require('./.github/workflows/lib/projects.js');
            await script({ github, context, core }, {
              '特定のラベル': 1,
            });

「特定のラベル」がつけられたIssueを「#1」のプロジェクトに追加する、というマッピングが書いてある。シークレットからPersonal Access Tokenをもらって使っている。このトークンの権限は、プロジェクトを操作するために org:write が必要であるほか、Issueの情報を取得するために repo:read も必要だった。

.github/workflows/lib/projects.js は次の通り。

module.exports = async ({ github, context, core }, projectMapping) => {
  const headers = {
    'GraphQL-Features': 'projects_next_graphql',
  };

  const projectNumber = projectMapping[context.payload.label.name];
  if (projectNumber === undefined) {
    return;
  }

  const project = await github.graphql(
    `query($organization: String!, $projectNumber: Int!) {
      organization(login: $organization) {
        projectNext(number: $projectNumber) {
          id
        }
      }
    }`,
    {
      headers,
      organization: context.repo.owner,
      projectNumber,
    }
  );
  const projectId = project.organization.projectNext.id;

  const item = await github.graphql(
    `mutation($projectId: ID!, $contentId: ID!) {
      addProjectNextItem(input: { projectId: $projectId, contentId: $contentId }) {
        projectNextItem {
          id
        }
      }
    }`,
    {
      headers,
      projectId,
      contentId: context.payload.issue.node_id,
    }
  );
  core.exportVariable('itemId', item.addProjectNextItem.projectNextItem.id);
};

ドキュメントでは gh コマンドを使っているが、値を引き回すのが面倒なので、actions/github-script@v5 を使ってJavaScriptで書いた。スクリプトを別なファイルに置いているのはエディタで書きやすいから。でも別なファイルに分けると actions/checkout@v2 が必要になって、もったいない気もしている……。

actions/github-script@v5 を使うとOctokitが使えるのだけど、ヘッダの扱いがよくわからず、Octokitのコードを読む羽目になった。

ラベルが外されたらプロジェクトから取り除くのもやりたかったのだけど、プロジェクト内のアイテムから該当のものを探すのが簡単でなかったので、いったん諦めた。

ということで

いろいろタイミングが合ったので、GitHubの新しいプロジェクトを使ってみている。まだ開発中という感じだけど、その分だけ将来に期待できていいと思う。