kakakakakku blog

Weekly Tech Blog: Keep on Learning!

簡単にできる!macOS で PDF を結合する Tips

macOS のデフォルト機能で簡単に「PDF を結合する」Tips を「3種類」紹介する.以下の3世代で動作確認をした.

  • macOS Mojave
  • macOS Catalina
  • macOS Big Sur

1.「プレビュー」アプリを使う

💡 特定のページを選択して結合したいときに!

PDF を「プレビュー」アプリで開いて「サムネイル一覧」からドラッグすれば,簡単に PDF を結合できる.特定のページを選択して結合することもできるし,全ページを結合する場合は PDF ファイル自体をドラッグすることもできる.またページの順番を変えることもできる.

support.apple.com

2.「Finder」アプリで「クイックアクション」を使う

💡 複数の PDF を選択して結合したいときに!

「Finder」アプリで複数の PDF を選択して「右クリック」「クイックアクション」「PDF を作成」で,簡単に PDF を結合できる.

support.apple.com

3.「ターミナル」アプリで join.py を実行する

💡 コマンドラインで複数の PDF を選択して結合したいときに!

2. で紹介した「クイックアクション」の仕組みは「Automator」アプリで /System/Library/Automator/Combine PDF Pages.action/ 直下にある Python ファイル join.py を実行している.よって「ターミナル」アプリ(もしくは iTerm2 など)で join.py を実行すれば,簡単に PDF を結合できる.--output オプションに結合する PDF 名を指定する.

$ '/System/Library/Automator/Combine PDF Pages.action/Contents/Resources/join.py' --output merged.pdf 1.pdf 2.pdf 3.pdf

まとめ

専用アプリをインストールする必要はなく,macOS のデフォルト機能で解決できる.PDF を結合したくなったときの参考になれば!

Docker Compose で複数コンテナの準備完了を TCP / HTTP で待機できる「dockerize」を試した

Docker Compose を使って複数コンテナを起動するときに,タイミングによっては接続エラーになってしまう場合がある.具体的な例としては「データベースコンテナ」の起動が遅いために「アプリケーションコンテナ」が接続エラーになってしまうことが挙げられる.

depends_on とは

Docker Compose には depends_on という設定項目があり,複数コンテナの「起動順序」を制御できるようになっている.しかし,あくまでこれは「起動順序」となり,必ずしも「準備完了」を保証するものではない.ドキュメントにも「開始 (start)」「準備完了 (ready)」は異なると書いてあり,depends_on を使っても接続エラーは発生する可能性がある.機能不足と考えるのではなく,アプリケーション側でリトライ機構を用意するなど,レジリエンス(回復性)を考慮する必要がある.

docs.docker.com

解決策になるツール

同じドキュメントを読むと depends_on と組み合わせて使う解決策として,アプリケーション側で接続確認をするべきと書いてある.具体的には以下の「4種類」のツールが紹介されている.RelayAndContainers は比較的新しく今年8月頃にドキュメントに追加されていた.

docs.docker.com

その中でも今回は「dockerize」を試す.「dockerize」には大きく「3種類」の機能があり,今回は3番目の「準備完了を待つ機能」を試す.なお,用語としてコンテナ化することを「Dockerized」と言うこともあり,個人的には「dockerize」という名前が認識しにくかったりする.

  • ✅ 機能 1 : 環境変数を埋め込んだ設定ファイルを自動生成するテンプレート機能
  • ✅ 機能 2 : 複数ログファイルを標準出力と標準エラーに流す機能
  • ✅ 機能 3 : メインプロセスを実行する前に TCP / HTTP / HTTPS / Unix Socket を使って「準備完了」を待つ機能

検証環境

今回は「dockerize」を試すために簡単な検証環境を構築した.大きく「3種類」のコンテナがある.AppAPIMySQL となり,構成図を載せておく.Python で実装した App では requests を使って API に HTTP リクエストをする.さらに mysql-connector-python を使って MySQL に接続する.API は Ruby の Sinatra で実装した.

f:id:kakku22:20201111110357p:plain

次に docker-compose.yml は以下のようになる. App から APIMySQLdepends_on を設定している.

version: '3.8'
services:
  app:
    build: ./app/.
    depends_on:
      - api
      - mysql
    command: [ 'python', '/app/app.py' ]
  api:
    build: ./api/.
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'

そして App のサンプル実装は以下とした.API に HTTP リクエストをしたら ✅ API と表示する.MySQL に接続できたら ✅ MySQL と表示する.プロダクションで使うなら backoff など,Python ライブラリでリトライ機構も用意しておくと良さそう.

import mysql.connector
import requests

response = requests.get('http://api')

if response.status_code == requests.codes.ok:
    print('✅ API')

conn = mysql.connector.connect(
    host='mysql',
    user='root'
)

if conn.is_connected():
    print('✅ MySQL')

「dockerize」を使わずに Docker ComposeApp を実行すると,以下のように「Connection refused」で接続エラーになる.今回はこの検証環境に「dockerize」を追加して改善していく.

app_1    | Traceback (most recent call last):
app_1    | ConnectionRefusedError: [Errno 111] Connection refused

(中略)

app_1    | Traceback (most recent call last):
app_1    | mysql.connector.errors.InterfaceError: 2003: Can't connect to MySQL server on 'mysql:3306' (111 Connection refused)

dockerize をインストール

「dockerize」は GitHub Releases に .tar.gz で公開されている.Dockerfile の中で .tar.gz を展開して使うこともできるし,Apline Linux ならインストール済の Docker イメージも jwilder/dockerize として公開されている.

今回は以下のように Dockerfile の中で .tar.gz を展開して,dockerize コマンドを使えるようにしている.

FROM python:3.8-alpine

RUN apk add --no-cache openssl

ENV DOCKERIZE_VERSION v0.6.1
RUN wget https://github.com/jwilder/dockerize/releases/download/${DOCKERIZE_VERSION}/dockerize-alpine-linux-amd64-${DOCKERIZE_VERSION}.tar.gz \
    && tar -C /usr/local/bin -xzvf dockerize-alpine-linux-amd64-${DOCKERIZE_VERSION}.tar.gz \
    && rm dockerize-alpine-linux-amd64-${DOCKERIZE_VERSION}.tar.gz

COPY . /app

RUN pip install -r /app/requirements.txt

CMD ["python", "/app/app.py"]

docker-compose.yml に dockerize を追加する

「dockerize」は,基本的に docker-compose.ymlentrypoint もしくは command をラップして使う.Docker Compose のドキュメントにも書いてある通り,docker-compose.ymlentrypoint を指定すると,Dockerfile 側の CMD は無視されるため,最終的に以下のように書いた.entrypoint に指定している dockerize コマンドに以下のオプションを追加している.

  • --timeout 20s : タイムアウト設定(デフォルトは10秒)
  • --wait http://api : API への HTTP 接続を待機する
  • --wait tcp://mysql:3306 : MySQL への TCP 接続を待機する
version: '3.8'
services:
  app:
    build: ./app/.
    depends_on:
      - api
      - mysql
    entrypoint: [ 'dockerize', '--timeout', '20s', '--wait', 'http://api', '--wait', 'tcp://mysql:3306' ]
    command: [ 'python', '/app/app.py' ]
  api:
    build: ./api/.
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'

dockerize を使って App を実行する

今度は「dockerize」を使って Docker ComposeApp を実行すると,以下のようになる.追加した「dockerize」によって,APIMySQL「準備完了」を待てるようになった.便利!

app_1    | 2020/11/10 01:00:00 Waiting for: http://api
app_1    | 2020/11/10 01:00:00 Waiting for: tcp://mysql:3306

(中略)

app_1    | 2020/11/10 01:00:00 Received 200 from http://api
app_1    | 2020/11/10 01:00:01 Connected to tcp://mysql:3306

(中略)

app_1    | ✅ API
app_1    | ✅ MySQL
app_1    | 2020/11/10 01:00:02 Command finished successfully.

まとめ

Docker Composedepends_on では「準備完了」を保証できない点を解決するツールとして「dockerize」を試した.「dockerize」には大きく「3種類」の機能があり,今回は3番目の「準備完了を待つ機能」を試した.App から APIMySQL「準備完了」を待てるようになった.今後同じような課題に遭遇したら検討しようと思う.とは言え,あくまで重要なのは「アプリケーション側でリトライ機構を用意すること」なので,忘れないように!なお,検証環境の設定などは全て GitHub に置いておいた.

github.com

よく使う "hub" コマンドを調べて "gh" コマンドに移行した

最近まで GitHub リポジトリを操作するときに ghq コマンドと hub コマンドを使っていた.リポジトリを git clone したり ~/ghq 直下のディレクトリに移動する場合に ghq コマンドと peco を使って,その他の操作は hub コマンドと git コマンドを使っていた.

GitHub CLI : gh コマンド

hub コマンドは今年3月からアップデートされてなく,今後は根本的に作り直された GitHub CLI に移行していく流れを感じる.GitHub CLI(gh コマンド)にも興味はあったけど,hub コマンドは便利だし,2014年頃から使っていて指に馴染んでいるし,様子を見ていた.ただ今年9月に GitHub CLI v1.0 がリリースされたこともあり,意を決して hub コマンドから gh コマンドに移行することにした.

cli.github.com

当然ながら gh コマンドは hub コマンドを完全に置き換えるものではなく,併用もあり得る.詳しくは以下の gh-vs-hub.md 参照で!

よく使う hub コマンド🏆

gh コマンドに移行するために Mac で history コマンドを実行して「よく使う hub コマンド🏆」を調べた.結果としては以下に載せた「4種類」のコマンドをよく使っていた.特に hub openhub checkout ${pr-url} は個人的には必須だった!

hub opengh open

リポジトリをブラウザで開く場合,GitHub CLI だと gh repo view --web コマンドで実現できる.もともと hub browse は入力しにくく hub open に alias を設定していたため,今回は gh alias set コマンドを使って gh open に alias を設定した.便利!

$ hub browse

$ gh repo view --web

$ gh alias set open 'repo view --web'
- Adding alias for open: repo view --web
✓ Added alias.

$ gh open

hub checkout ${pr-url}gh pr checkout ${pr-number}

プルリクエストをレビューするためにブランチを切り替える場合,GitHub CLI だと gh pr checkout ${pr-number} コマンドで実現できる.hub コマンドでは「プルリクエスト URL」を指定していたけど,gh コマンドでは「プルリクエスト番号」になる.今までの癖を直して「プルリクエスト URL」の末尾にある「プルリクエスト番号」をコピーすれば良く,許容範囲とした.慣れていく!

$ hub checkout https://github.com/kakakakakku/xxx/pull/100

$ gh pr checkout 100

hub pr listgh pr list

プルリクエスト一覧を確認する場合,GitHub CLI でも特に変化はなく gh pr list コマンドで実現できる.同様に Issue の場合も gh issue list で実現できる.問題なし!

$ hub pr list

$ gh pr list

$ hub issue

$ gh issue list

hub gist creategh gist create

作業中にログファイルなどを雑に Gist にアップロードしている.基本的に公開することはなく,Secret(プライベート)で作り,不要になったら削除している.そのときに hub gist create コマンドを使っていた.ファイルを指定することもできるし,echo と連携して Gist URL を発行するためだけに使うこともある.GitHub CLI でも特に変化はなく gh gist create コマンドで実現できる.問題なし!

$ hub gist create hello.txt

$ echo 'aaa' | hub gist create

$ gh gist create hello.txt

$ echo 'aaa' | gh gist create

まとめ

hub コマンドから gh コマンドに移行した.コマンド履歴から「よく使う hub コマンド🏆」を調べて,多少パラメータに変化はあるものの問題なく使えることを確認できた.実は hub sync コマンドや hub ci-status コマンドなど,gh コマンドではサポートされていないものもあったけど(hub ci-statusgh pr checks は異なる),最近はほとんど使ってなく,問題ないと判断した.

hub コマンドもアンインストールした!今までありがとう!

$ hub
zsh: command not found: hub

Secret を暗号化して Git 管理を可能にする「Sealed Secrets」を試した

Kubernetes で設定値や機密情報を管理する場合,ConfigMapSecret が代表的な選択肢として挙げられる.しかし Secret は暗号化ではなく Base64 でエンコードをする仕様になっているため,Git リポジトリで直接マニフェストを管理できないという懸念点がある.ドキュメントにも Base64 エンコーディングは「平文と同様であると判断すべき (Base64 encoding is not an encryption method and is considered the same as plain text.)」と書いてある.

kubernetes.io

Sealed Secrets とは

今回は Secret の暗号化ソリューションとして「Kubernetes 完全ガイド 第2版」「第13章」に載っていた「Sealed Secrets」を試す.「Sealed Secrets」を使うと,公開鍵暗号方式で Secret を暗号化できるため,Git リポジトリで管理できるようになる.仕組みとしては Kubernetes クラスターにデプロイをした Sealed Secret Controller によって復号されて Secret になる.よって,最終的には「Base64 でエンコードされた Secret」になり,設定変更なしで Pod から参照できる.「Sealed Secrets」を開発した Bitnami の記事に載っている構成図を見るとイメージしやすいと思う.

github.com

f:id:kakku22:20201030235212p:plain
Bitnami Engineering: Sealed Secrets: Protecting your passwords before they reach Kubernetes から引用

検証環境

今回は Docker Desktop for Mac "Edge" を使って,以下の Kubernetes 環境で試す.

$ kubectl version --short
Client Version: v1.19.2
Server Version: v1.19.2

なお,10/19 にリリースされた v2.4.2.0 から Kubernetes 1.19.2 が使えるようになった!

docs.docker.com

インストール(クライアント側)

今回は Homebrewkubeseal コマンドをインストールする.10/29 にリリースされた「Sealed Secrets」の最新バージョン v0.13.1 を使う.実は最初に試したときはまだ v0.12.6 だったこともあり,記事のセルフレビューも兼ねて v0.13.1 でやり直した.

$ brew install kubeseal

$ kubeseal --version
kubeseal version: v0.13.1

インストール(Kubernetes クラスター側)

次に Kubernetes クラスターに Sealed Secret Controller をインストールする.インストール方法は KustomizeHelm ChartOperator Framework など,いろいろ用意されているけど,今回は GitHub Releases に載っている方法で直接インストールする.kubectl apply でマニフェストを適用したログを確認すると,Sealed Secret ControllerDeployment などがデプロイされている.

$ kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.13.1/controller.yaml
Warning: rbac.authorization.k8s.io/v1beta1 Role is deprecated in v1.17+, unavailable in v1.22+; use rbac.authorization.k8s.io/v1 Role
role.rbac.authorization.k8s.io/sealed-secrets-key-admin created
Warning: rbac.authorization.k8s.io/v1beta1 ClusterRoleBinding is deprecated in v1.17+, unavailable in v1.22+; use rbac.authorization.k8s.io/v1 ClusterRoleBinding
clusterrolebinding.rbac.authorization.k8s.io/sealed-secrets-controller created
Warning: rbac.authorization.k8s.io/v1beta1 ClusterRole is deprecated in v1.17+, unavailable in v1.22+; use rbac.authorization.k8s.io/v1 ClusterRole
clusterrole.rbac.authorization.k8s.io/secrets-unsealer created
service/sealed-secrets-controller created
Warning: rbac.authorization.k8s.io/v1beta1 RoleBinding is deprecated in v1.17+, unavailable in v1.22+; use rbac.authorization.k8s.io/v1 RoleBinding
rolebinding.rbac.authorization.k8s.io/sealed-secrets-service-proxier created
role.rbac.authorization.k8s.io/sealed-secrets-service-proxier created
rolebinding.rbac.authorization.k8s.io/sealed-secrets-controller created
serviceaccount/sealed-secrets-controller created
deployment.apps/sealed-secrets-controller created
Warning: apiextensions.k8s.io/v1beta1 CustomResourceDefinition is deprecated in v1.16+, unavailable in v1.22+; use apiextensions.k8s.io/v1 CustomResourceDefinition
customresourcedefinition.apiextensions.k8s.io/sealedsecrets.bitnami.com created

$ kubectl get deployments.apps -n kube-system sealed-secrets-controller
NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
sealed-secrets-controller   1/1     1            1           50s

ちなみに Sealed Secret Controller をインストールする前に kubeseal コマンドを使ったらエラーになった.

$ kubeseal -o yaml < kakakakakku-secret.yaml
error: cannot fetch certificate: services "sealed-secrets-controller" not found

Secret を暗号化する

まず,単純な Secret マニフェストとして kakakakakku-secret.yaml を作った.

apiVersion: v1
kind: Secret
metadata:
  name: kakakakakku-secret
type: Opaque
data:
  username: YWRtaW4=
  password: cGFzc3dvcmQ=

値は Base64 でエンコードされているため,base64 --decode コマンドを使えば簡単にデコードできる.今回の値はあくまでサンプルとして adminpassword にした.

$ echo 'YWRtaW4=' | base64 --decode
admin

$ echo 'cGFzc3dvcmQ=' | base64 --decode
password

次に kubeseal コマンドを使って kakakakakku-secret.yamlkakakakakku-sealed-secret.yaml として暗号化する.

$ kubeseal -o yaml < kakakakakku-secret.yaml > kakakakakku-sealed-secret.yaml

まず,kindSecret から SealedSecret に変わった.また spec.encryptedData には暗号化された usernamepassword が設定されている(以下の例では xxxxx で埋めた).Sealed Secret Controller によって管理された公開鍵で暗号化されているため,鍵を安全に管理しているという前提においては復号されることはなく,Git リポジトリで管理できる.

$ cat kakakakakku-sealed-secret.yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  creationTimestamp: null
  name: kakakakakku-secret
  namespace: default
spec:
  encryptedData:
    password: xxxxx(実際にはもっと長い)
    username: xxxxx(実際にはもっと長い)
  template:
    metadata:
      creationTimestamp: null
      name: kakakakakku-secret
      namespace: default
    type: Opaque

公開鍵 と 秘密鍵

「Sealed Secrets」では Kubernetes クラスターの中に Secret として「公開鍵」「秘密鍵」を管理している.以下のように kube-system Namespace の中にある sealed-secrets-xxxxxxxx Secret を取得すると「公開鍵(Base64 エンコード)」「秘密鍵(Base64 エンコード)」を確認できる.同じく今回も xxxxx で埋めた.GitHub の README.md を読むと,バックアップを取る場合は kubectl get secret の結果を YAML で出力しておくと書いてあった.

$ kubectl get secrets -n kube-system sealed-secrets-xxxxxxxx -o json | jq '.data'
{
  "tls.crt": "xxxxx(実際にはもっと長い)",
  "tls.key": "xxxxx(実際にはもっと長い)"
}

また kubeseal --fetch-cert コマンドを使えば「公開鍵」を取得できる.

$ kubeseal --fetch-cert > mycert.pem

暗号化した Secret を適用する

さっそく「Sealed Secrets」で暗号化した kakakakakku-sealed-secret.yamlkubectl で適用する.すると SealedSecret リソースだけではなく Secret リソースも適用されている!内部的には Sealed Secret Controller によって復号されて Secret まで適用されている.

$ kubectl apply -f kakakakakku-sealed-secret.yaml
sealedsecret.bitnami.com/kakakakakku-secret created

$ kubectl get sealedsecret kakakakakku-secret
NAME                 AGE
kakakakakku-secret   20s

$ kubectl get secrets kakakakakku-secret
NAME                 TYPE     DATA   AGE
kakakakakku-secret   Opaque   2      30s

結果的に Secret は Base64 でエンコードされているため,値も確認できる.

$ kubectl get secrets kakakakakku-secret -o json | jq  -r '.data.username'
YWRtaW4=
$ kubectl get secrets kakakakakku-secret -o json | jq  -r '.data.username' | base64 --decode
admin%

$ kubectl get secrets kakakakakku-secret -o json | jq  -r '.data.password'
cGFzc3dvcmQ=
$ kubectl get secrets kakakakakku-secret -o json | jq  -r '.data.password' | base64 --decode
password%

Sealed Secret Controller のログを確認すると SealedSecret unsealed successfully と出力されていた.

$ kubectl logs -n kube-system sealed-secrets-controller-xxxxxxxxxx-xxxxx
controller version: v0.13.1
2020/10/30 15:00:00 Starting sealed-secrets controller version: v0.13.1
2020/10/30 15:00:00 Searching for existing private keys

(中略)

2020/10/30 15:30:00 Event(v1.ObjectReference{Kind:"SealedSecret", Namespace:"default", Name:"kakakakakku-secret", UID:"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", APIVersion:"bitnami.com/v1alpha1", ResourceVersion:"4381", FieldPath:""}): type: 'Normal' reason: 'Unsealed' SealedSecret unsealed successfully

リソース削除

最後にリソースを削除しておく.

$ kubectl delete -f kakakakakku-sealed-secret.yaml

$ kubectl delete -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.13.1/controller.yaml

Kubeseal extension for VS Code

関連ツールとして VS Code でも拡張機能「Kubeseal extension for VS Code」を使えば「Sealed Secrets」を使った Secret の暗号化ができる.既に紹介した kubeseal --fetch-cert コマンドを使って「公開鍵」を取得して,VS CodeShift + Command + P(コマンドパレット)Seal Kubernetes Secret - FileclusterWide公開鍵 と操作をすると以下のように右側に暗号化されたマニフェストが自動生成される.便利なのかは判断できないけど,試してみた!

f:id:kakku22:20201031005735p:plain

marketplace.visualstudio.com

まとめ

今回は「Sealed Secrets」を試した.Sealed Secret Controller によって暗号化されたマニフェストから Base64 でエンコードされた Secret に複合される仕組みは現場でも使えそう.GitHub の README.md を読むと他にも「ローテーション機能」もあった.継続的に運用していくための機能などはまた別途試そうと思う.

関連記事

kakakakakku.hatenablog.com

Mac でターミナル操作をレコーディングしてシュッと公開できる「asciinema」

Mac で「ターミナル操作」をレコーディングしたくなる場面がある.ブログに載せたり,GitHub リポジトリの README.md に載せたり,プレゼンテーションでデモを行う場合のバックアップ用途として使ったりする.今までは Terminalizerttyrec + ttygif を使っていたけど,今回は別の選択肢として「asciinema」を試す.発音は [as-kee-nuh-muh] だから「アスキーヌーマ」とか?簡単に紹介すると「asciinema」を使うと「ターミナル操作」をレコーディングして,サービス側にアップロードできる.そしてブログに載せることもできる.

asciinema.org

f:id:kakku22:20201018094006p:plain
GitHub - asciinema/asciinema-logo: Official asciinema logo より引用

インストール 📹

asciinema コマンドは Homebrew で簡単にインストールできる.Homebrew だけじゃなく,pip3 でもインストールできるし,yumapt-get でもインストールできる.Docker を使って docker run コマンドで実行することもできる.今回インストールした最新バージョン v2.0.2 は2019年9月にリリースされているため,直近1年ほどはアップデートされていない?

$ brew install asciinema

$ asciinema --version
asciinema 2.0.2

asciinema.org

ログイン 📹

「asciinema」はサービス登録をしなくても使える.しかし,レコーディングデータは「7日間」で自動的にアーカイブされてしまうため,ブログに載せたりすることを考えると使いにくいと思う.今回はまずサービス登録(メール認証)をしてから,asciinema auth コマンドを実行してアカウント連携をしておく.これで準備完了!

$ asciinema auth
Open the following URL in a web browser to link your install ID with your asciinema.org user account:

https://asciinema.org/connect/00000000-1111-2222-3333-444444444444

This will associate all recordings uploaded from this machine (past and future ones) to your account, and allow you to manage them (change title/theme, delete) at asciinema.org.

レコーディング 📹

asciinema rec コマンドを実行すると,新しく /bin/zsh などのシェルプロセスが立ち上がり,レコーディング状態になる.適当にコマンドを実行して,最終的に ctrl-d でプロセスを落とせば,レコーディングは完了する.

レコーディング後に enterctrl-c を入力するように求められる.挙動の差は以下に簡単にまとめた.なお,asciinema にアップロードをした場合,private とは言え URL を直接指定すればアクセスできるため「限定共有」のような仕組みになる.

  • enter : asciinemapriavate 状態でアップロードする
  • ctrl-c : ローカルファイル (.cast) に出力する
$ asciinema rec
asciinema: recording asciicast to /var/folders/xx/yyy/T/zzz-ascii.cast
asciinema: press <ctrl-d> or type "exit" when you're done

(中略)

asciinema: recording finished
asciinema: press <enter> to upload to asciinema.org, <ctrl-c> to save locally
asciinema: asciicast saved to /var/folders/xx/yyy/T/zzz-ascii.cast

以下に echokubectlcowsay を実行した(本当に適当な👻)サンプルをレコーディングした.asciinema にアップロードすれば,Markdown 形式でブログに載せることもできる.ターミナルデザインはレコーディング時のものではなく,以下から選べるようになっている.今回は Tango を選んだ.

  • Default (asciinema)
  • asciinema
  • Tango
  • Solarized Dark
  • Solarized Light
  • Monokai

asciicast

ローカルファイル (.cast) 📹

レコーディング完了時に ctrl-c「ローカルファイル (.cast)」を出力した場合,asciinema play コマンドを使えばレコーディングを再生できる.本当にコマンドを再実行するわけではなく,実行結果がそのまま表示される仕組みになっている.よって .cast ファイルを一部修正することもできる.そして「ローカルファイル (.cast)」の仕様は GitHub に公開されていて,現在は asciicast file format v2 が使われている.実際に .cast ファイルを読むのは難しいけど,仕様が公開されているのは良いことだと思う.

$ asciinema play zzz-ascii.cast
 ~ echo 'hello'
hello
 ~ k get nodes
NAME             STATUS   ROLES    AGE   VERSION
docker-desktop   Ready    master   13d   v1.18.8
 ~ cowsay kakakakakku
 _____________
< kakakakakku >
 -------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

設定ファイル 📹

「asciinema」には「設定ファイル機能 (Configuration file)」もある.api(API)record(レコーディング)play(再生) の挙動を細かく設定できる.設定ファイルは ${HOME}/.config/asciinema/config に置く.特に recordidle_time_limit はデフォルト off なので,設定しておくと便利.レコーディング時にコマンドを実行せずに静止している時間が続いた場合に指定した秒数に詰めてくれるため,変に間延びしたレコーディングにならない.asciinema rec コマンドのオプションに --idle-time-limit を設定することもできるけど,デフォルトで入れておくのが良さそう.

[record]

idle_time_limit = 2

asciinema.org

GIF 📹

レコーディングデータを GIF に変換したいこともある.GitHub に公開されているツール asciicast2gif を使えば実現できそうだけど,実際に試したところ,うまく変換できなかった.Docker も npm も試した.GitHub で2018年頃からアップデートされていないことを考慮すると,asciicast file format v2 に対応していなさそうだった.README.md に載っている拡張子も .cast ではなく demo.json になっていた.

github.com

まとめ 📹

Mac で「ターミナル操作」をレコーディングできる「asciinema」を試した.簡単に使えるし,サービス側にアップロードできるのも便利だった.しかし asciinema コマンドは直近1年ほどはアップデートされてなく,関連ツールの asciicast2gifasciinema-player もうまく動かなかった.asciinema Blog も接続できず,今後も継続的に使えるのかは判断が難しいところ.

asciinema.org