Chrome で検索対象の言語を英語のみにして検索する

設定方法

  1. URL バーを右クリックして「検索エンジンを管理」のメニューを表示します。 f:id:kyagi:20220308054556p:plain

  2. メニューを以下のように修正します。検索エンジン名とキーワード(トリガーとなるキー)をお好みで指定します。

https://www.google.com/search?gl=us&hl=en&gws_rd=cr&pws=0&q=%s

f:id:kyagi:20220308054649p:plain

使用方法

URL バーで「e」+「スペース」をタイプすると検索エンジンの設定が上記で設定した「Google(e)」に切り替わり英語のみ検索対象になります。

f:id:kyagi:20220308054612p:plain

上記の設定で検索した場合(左)と普通に検索した場合(右) の比較

f:id:kyagi:20220308054630p:plain

aws-minikube: ELB とも連携できてフットワークが軽い AWS k8s 環境

k8s 環境として minikube, eks, aws-minikube を比較してみました(k3s は未経験)。個人的には aws-minikube がコスト面/機能面でよさそうに思えたので紹介です。

aws-minikube とは?

EC2 1 台で k8s 環境が整うツールです。Red Hat 社の Jakub Scholz 氏が 2017 年から開発しています。最新の k8s への対応も早いです。 terraform ベースの aws-minikube と kubeadm ベースの aws-kubernetes の 2 段揃えの構成です。

github.com

(日本ではあまり知られていないようです... 私は minikube で ELB も使えるような拡張がないかな... とぐぐっていた時にたまたま見つけました)

使用する AWS リソース

terrafrom apply すると my-minikube という EC2 と f:id:kyagi:20220221201110p:plain

my-minikube.<your.domain.here> という Route53 レコードが追加されます。 f:id:kyagi:20220221201142p:plain

Security Group も作成されます。以下では自宅 IP に変更していますがデフォルトは 0.0.0.0/0。6443 は k8s の API をたたくポートで専用の .kube/config (認証情報を含む設定ファイル) が構築時に発行されます。 f:id:kyagi:20220221202550p:plain

何が嬉しいのか

  • 1 台で k8s 環境がそろう
  • 使う ec2 は t2.medium でコストが安い。1 ヶ月 4377 円(= 0.0608 USD * 100 * 24 * 30)。これはデフォルトで t3.medium 2 台を使って EKS 代も徴収する EKS の 1/3。環境構築のスピードも早いのでそのたびに作って壊してもストレスがない。
  • 環境構築のスピードがはやい。EKS だと cluster 作成に 20 分くらいかかるけどこちらは 5 分ほど。
  • minikube の制約だったサービスの外部公開が簡単に可能。service を type:LoadBalancer で create/delete すると応じて ELB が作られる/削除される
  • いじる aws リソースが最小限(EC2, Route53, SecurityGroup)
  • terraform apply/destroy でまるごと消せる

想定する使い方

開発者ごとに t2.medium の EC2 1 台を支給し、手軽に k8s を試してもらって組織の k8s 力をあげる、といった使い方がよさそうです。

minkube, aws-minikube, eks の比較

候補 お手軽さ 時間 できること コスト 後始末
minikube
aws-minikube
eks

コスト内訳

候補 コスト構成 コスト/日 コスト/月
minikube ローカルの Mac で動かせば無料、EC2 の場合は EC2 料金(*1) 146円 4,377 円
aws-minikube EC2 料金(*1) 146円 4,377円
EKS EC2 料金(*2) + EKS 料金 440円 13,200円
  • *1 t2.medium で計算(vCPU が 2 つある EC2)
  • *2 t3.medium * 2 で計算(EKS がデフォルトで選択する)

使い方

*ほぼ README 通りですが AWS MarketPlace の centos AMI を subscribe する必要があります。無料です)

1. centos の AMI を subscribe する(これをしないと terraform apply 時に止まります)

https://aws.amazon.com/marketplace/pp/prodview-qkzypm3vjr45g

f:id:kyagi:20220221201412p:plain

2. 設定ファイルを書き換える

$ git diff
diff --git a/example.tfvars b/example.tfvars
index 177a1fe..a16914c 100644
--- a/example.tfvars
+++ b/example.tfvars
@@ -1,5 +1,5 @@
 # AWS region where should the Minikube be deployed
-aws_region = "eu-central-1"
+aws_region = "ap-northeast-1"

 # Name for role, policy and cloud formation stack (without DBG-DEV- prefix)
 cluster_name = "my-minikube"
@@ -8,13 +8,13 @@ cluster_name = "my-minikube"
 aws_instance_type = "t2.medium"

 # SSH key for the machine
-ssh_public_key = "~/.ssh/id_rsa.pub"
+ssh_public_key = "~/.ssh/id_rsa.ubuntu.com.amazonaws.info.myservice.dev.all.pub"

 # Subnet ID where the minikube should run
-aws_subnet_id = "subnet-8a3517f8"
+aws_subnet_id = "subnet-0a102dc87cd20932e"

 # DNS zone where the domain is placed
-hosted_zone = "my-domain.com"
+hosted_zone = "my.service.com"
 hosted_zone_private = false

 # AMI image to use (if empty or not defined, latest CentOS 7 will be used)

3. terraform apply する

f:id:kyagi:20220221201437p:plain

(... snip ...)

f:id:kyagi:20220221201452p:plain

4. EC2 がたちあがるので SSH ログインする

5. config ファイル作成して完了

[centos@ip-10-0-0-87 ~]$ mkdir .kube
[centos@ip-10-0-0-87 ~]$ cp kubeconfig ~/.kube/config
[centos@ip-10-0-0-87 ~]$ kubectl get svc --all-namespaces
NAMESPACE     NAME                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                  AGE
default       kubernetes                  ClusterIP   10.96.0.1        <none>        443/TCP                  11h
kube-system   dashboard-metrics-scraper   ClusterIP   10.103.238.218   <none>        8000/TCP                 11h
kube-system   kube-dns                    ClusterIP   10.96.0.10       <none>        53/UDP,53/TCP,9153/TCP   11h
kube-system   kubernetes-dashboard        ClusterIP   10.96.254.133    <none>        443/TCP                  11h
kube-system   metrics-server              ClusterIP   10.103.193.240   <none>        443/TCP                  11h

6. サービスを作って ELB で外部公開

[centos@ip-10-0-0-87 ~]$ kubectl create deployment kubia --image=kyagi/kubia --replicas=3 --port=8080
deployment.apps/kubia created

[centos@ip-10-0-0-87 ~]$ kubectl expose deployment kubia --port=8080 --target-port=8080 --type=LoadBalancer --name=kubia
service/kubia exposed

[centos@ip-10-0-0-87 ~]$ kubectl get svc
NAME         TYPE           CLUSTER-IP      EXTERNAL-IP                                                                    PORT(S)          AGE
kubernetes   ClusterIP      10.96.0.1       <none>                                                                         443/TCP          11h
kubia        LoadBalancer   10.97.188.164   a81e8fb4f947f444f9bae599cf38ca3a-2070947448.ap-northeast-1.elb.amazonaws.com   8080:31678/TCP   10s

[centos@ip-10-0-0-87 ~]$ kubectl get pods
NAME                     READY   STATUS    RESTARTS   AGE
kubia-7f585c778c-k4t5h   1/1     Running   0          10m
kubia-7f585c778c-nkzbr   1/1     Running   0          10m
kubia-7f585c778c-p77hr   1/1     Running   0          10m

(外部公開を確認) f:id:kyagi:20220221201516p:plain

(ELB が作られている) f:id:kyagi:20220221201537p:plain

Getting started with Amazon EKS (2) とコスト計算

前回 に引き続き、EKS でサンプルの nginx pod を外部公開するまでを実施しました。 docs.aws.amazon.com

Service の外部公開

Getting Started with Amazon EKS で紹介されているデプロイだと Type:ClusterIP でクラスタ内部への公開なので、これを type: LoadBalancer にして外部へ公開する YAML を作成しました。チュートリアルで紹介されている YAML との差分は以下になります。

$ diff -Nurp eks-sample-service.yaml eks-sample-service-loadbalancer.yaml
--- eks-sample-service.yaml 2022-02-20 20:16:54.000000000 +0900
+++ eks-sample-service-loadbalancer.yaml    2022-02-20 22:54:35.000000000 +0900
@@ -1,11 +1,12 @@
 apiVersion: v1
 kind: Service
 metadata:
-  name: eks-sample-linux-service
+  name: eks-sample-linux-service-loadbalancer
   namespace: eks-sample-app
   labels:
     app: eks-sample-linux-app
 spec:
+  type: LoadBalancer
   selector:
     app: eks-sample-linux-app
   ports:

Amazon EKS クラスターで実行されている Kubernetes Services を公開する

こちらを適用すると LoadBalancer として AWS 内部で ELB が作成されて外部からアクセスすることが可能になります。

f:id:kyagi:20220220230353p:plain f:id:kyagi:20220220230557p:plain

ダッシュボード設定

ダッシュボードの設定は以下 2 つを実施することになります。

docs.aws.amazon.com

docs.aws.amazon.com

f:id:kyagi:20220220225811p:plain

eks 固有の pod

kube-system namespace に aws-node という pod が 2 つあがっています。これは eks 固有の pod のようです。

f:id:kyagi:20220220234720p:plain

コスト計算

N.Virginia で試算すると

EC2(t3.medium * 2): 199.68 円= 0.0416USD * 100 * 24 * 2 EKS: 240 円= 0.1USD * 100 * 24

で合計 440円/日になります。月換算だと 1.3万円。これは EC2(t3.nano) を 1 ヶ月立てっぱなしにした 374円/月より高いです(374.4 = 0.0052 * 100 * 24 * 30)。さらにここに ELB が載ってきます。EKS での個人利用はちょっときついですね。

Ansible における抽象/具体モデルと誰が/何をするの考え方

Ansible の group と host, role と task の関係が 公式ドキュメント からだと個人的にわかりにくかったのでまとめた図です。頭の中でなんとなく理解していたことを図や表に起こしてみることで理解が促進されました(公式ドキュメントにこういった説明があれば助かりましたが... たとえば以下の定義だけだと他のエンティティとどう結びついているのがわかりづらい)

抽象/具体の 2 軸に分類して考える

Ansible の主要なエンティティは group, host, role, task, inventory です。それぞれをまず抽象と具体の 2 軸で分類すると以下になります。

  • 抽象的なエンティティ: group, role
  • 具体的なエンティティ: host, task

抽象的なエンティティである group, role は実際に存在するわけではなく「集合」としての概念です。例えば redisservers という group は redisservers という具体的な host を指しているわけではありません。redis01, redis02, redis03 という実在する具体的な host を集合として抽象化したのが group です。

同様に roles も実際に存在するわけではなく「集合」としての概念です。例えば commonPackges という role は commopnPackages という 具体的な task を指しているわけではありません。install git, install gcc という実在する具体的な task を集合として抽象化したのが role です。

「誰が」「何をする」の 2 軸を追加して考える

もう一歩考えを進めて、抽象/具体モデルにもうひとうの軸「誰が」「何をする」を追加してみます。こうすると、それぞれ抽象的なレベルと具体的なレベルそれぞれで「誰が」「何をする」がわかりやすくなります。そしてこの抽象と具体を結びつけているのが inventory です。

誰が 何をする
抽象 group role
具体 host task
抽象/具体の関連付けファイル inventory [groupname].yml
  • group の例) redisservers, webservers
  • host の例) redis01, redis02, redis03, web01, web02, web03
  • role の例) commonPackges, tuningKernelParams
  • task の例) install git, install gcc, modify sysctl.conf, load sysctl
  • inventory の例) development, staging, production
  • [groupname].yml の例) redisservers.yml, webservers.yml

f:id:kyagi:20220218222605p:plain

抽象化することで「個」ではなく「集合」を扱える

抽象レベルで記述することの最大の利点は結果的に「個(host, task)」ではなく「集合(group, role)」を扱うことにより記述量が少なくなることです。同様の考え方はデータベースでもオブジェクト志向プログラミングでもクラウドのリソース管理/設計でも見つけることができるはずです。

f:id:kyagi:20220218222620p:plain

抽象レベルで記述したことを具体レベルまで落とし込んでいくと以下のようになります。

  • 誰が

    • host: redis01(具体) は group: redisservers(抽象) に属している
  • 何をする

    • task: yum install redis(具体) は role: redis(抽象) に属している
    • task: yum install git(具体) は role: commonPackages(抽象) に属している

よって「redis01」は「yum install redis」「yum install git」を行える「(... 省略 ...)」を行う(「誰が」「何をする」)

参考情報

docs.ansible.com

Node.js のテストフレームワークとデバッガ

テストフレームワーク Jest

shamshir に細かなバグを見つけたので少しの間、我流のプリントデバッグをしていたのですが、継続的なメンテナンスを考えてテストフレームワークを導入することにしました。参考にしている「ハンズオン Node.js」の「8 章 ユニットテストとデバッグ」には 2 種類のツールが紹介されています。ひとつは Mocha + Chai + Sinon.JS + Istanbul の組み合わせで、もう一つが Jest です。フレームワーク、アサーション、テストダブル、カバレッジ全てカバーしているフルスタックであることと、後発ゆえに設計の洗練されてきているのでは期待を込めて Jest を使用することにしました。

jestjs.io

ドキュメントを少し読んだだけですが、導入もしやすいし使い勝手もよさそうです。テストだけでなくカバレッジもこなせるのがいいですね。

f:id:kyagi:20220215230455p:plain

f:id:kyagi:20220215230551p:plain

今となってはスタンドアローンと Github Actions 両方で起動できるようにしておいてよかったと思います。スタンドアローンで環境変数で認証できるようにしておいたおかげでテストフレームワークの Setup にそのまま使えます(Jest での実装は beforeAll)。Github Actions だけにしていたら認証の部分を別途書かなくてはいけないところでした。

Setup and Teardown · Jest

デバッガ inspect

inspect を追加するだけで GDB ライクにデバッグが可能になります。ブレイクポイントを N 行目に設定するには sb(N) です。変数の値を見るには p ではなく debugger 内で repl を起動する必要があります。node --inspect-brk を使う場合はブラウザの deveoper tool でデバッグができます。

nodejs.org

f:id:kyagi:20220215231438p:plain

Getting started with Amazon EKS の地雷処理

2 年ぶりに Kubernetes を使います。以前、仕事で AWS とオンプレそれぞれにクラスタを構築して運用していたもの、構築部分は k8s プロフェッショナルなエンジニアが身近にいたせいでおんぶにだっこの状態でした。過去に Kubernetes The Hard Way を AWS で実施する をしたものの、2 年も経つとほとんど忘れてしまっています。今回は EKS を使います。

まずは動作を確認するために AWS が提供している Getting Started with Amazon EKS に則って進めてみました。基本的にはドキュメントの通りでしたが、AWS 側の都合で以下 2 点の修正が必要でしたのでワークアラウンドを残しておきます。

地雷1. NAT Gateway が作成できない AZ が存在する

Region に ap-northeast-1(Tokyo) を指定すると NAT Gateway を作成できずに Cloudformation がエラーになりました(個人的な経験だと法人だと a は使用できますが個人だと b と c しか使用できないので a 側にリソースを作成できないのはアカウントに依存するのかもしれません)

f:id:kyagi:20220206180557p:plain

ワークアラウンドとしては単純に ap-northeast-1 以外のリージョンを選びました。最終的には ap-northeast-1 のどの AZ が問題なのかを明確にしたほうがいいですが、試用目的であればその限りではありません。今回は代わりに us-east-1(N.Virginia) を選びました。

地雷2. 選択した Region によっては Cloudformation で指定している AZ(a と b) でリソースに空きがない場合がある

us-east-1 を選択してクラスタを作ろうとしたのですが、AZ a にリソースの空きがないのが理由で AWS マネージメントコンソールがエラーになりました。

f:id:kyagi:20220206181304p:plain

ワークアラウンドとしては a 以外の AZ を選びました。みんな a を使っているのかもしれないので、なるべく混んでいなそうな(?) e と f を選びます。AWS 側が提供している Cloudformation の YAML ファイル をローカルに DL して以下のように修正します。

$ wget https://amazon-eks.s3.us-west-2.amazonaws.com/cloudformation/2020-10-29/amazon-eks-vpc-private-subnets.yaml
$ cp amazon-eks-vpc-private-subnets.yaml amazon-eks-vpc-private-subnets-modified.yaml

$ ruby -e "puts ('a'..'z').to_a.find_index('a')"
0
$ ruby -e "puts ('a'..'z').to_a.find_index('b')"
1
$ ruby -e "puts ('a'..'z').to_a.find_index('e')"
4
$ ruby -e "puts ('a'..'z').to_a.find_index('f')"
5

$ vi amazon-eks-vpc-private-subnets-modified.yaml
$ diff -Nurp amazon-eks-vpc-private-subnets.yaml amazon-eks-vpc-private-subnets-modified.yaml
--- amazon-eks-vpc-private-subnets.yaml 2020-10-30 07:36:35.000000000 +0900
+++ amazon-eks-vpc-private-subnets-modified.yaml    2022-02-06 15:19:24.000000000 +0900
@@ -168,7 +168,7 @@ Resources:
       MapPublicIpOnLaunch: true
       AvailabilityZone:
         Fn::Select:
-        - '0'
+        - '4'
         - Fn::GetAZs:
             Ref: AWS::Region
       CidrBlock:
@@ -189,7 +189,7 @@ Resources:
       MapPublicIpOnLaunch: true
       AvailabilityZone:
         Fn::Select:
-        - '1'
+        - '5'
         - Fn::GetAZs:
             Ref: AWS::Region
       CidrBlock:
@@ -209,7 +209,7 @@ Resources:
     Properties:
       AvailabilityZone:
         Fn::Select:
-        - '0'
+        - '4'
         - Fn::GetAZs:
             Ref: AWS::Region
       CidrBlock:
@@ -229,7 +229,7 @@ Resources:
     Properties:
       AvailabilityZone:
         Fn::Select:
-        - '1'
+        - '5'
         - Fn::GetAZs:
             Ref: AWS::Region
       CidrBlock:

同時に aws cloudformation のパラメータも修正します(--template-url https://... ではなく --template-body file://... で修正したファイルに置き換える)

$ diff -Nurp a.txt b.txt
--- a.txt   2022-02-06 15:34:18.000000000 +0900
+++ b.txt   2022-02-06 15:33:28.000000000 +0900
@@ -1 +1 @@
-aws cloudformation create-stack --region ap-northeast-1 --stack-name my-eks-vpc-stack --template-url https://amazon-eks.s3.us-west-2.amazonaws.com/cloudformation/2020-10-29/amazon-eks-vpc-private-subnets.yaml
+aws cloudformation create-stack --region us-east-1 --stack-name my-eks-vpc-stack --template-body file://amazon-eks-vpc-private-subnets-modified.yaml

あとはドキュメント通りに進んで、クラスタが作成できました。最終的にはひとつひとつの AWS リソースの構成を自分で設定していく予定ですが、動作確認のためにはこれでよしとします。個人で試用している場合、料金も気になります。立ち上げたいサービスの Docker イメージの準備をしてからクラスタを作成し、動作確認後にクラスタと Cloudformation のスタックを削除する、といった流れがよさそうです。

その他、自分の Mac にインストールしている AWS ツールが若干古かったようで下記の設定をする必要がありました。事前に確認しておくといいかもしれません。

事前準備

参考情報

EKSでの認証認可 〜aws-iam-authenticatorとIRSAのしくみ〜 - もうずっといなかぐらし

Github Sponsors に登録するには

大きな流れ

  • Introduction (5,000 characters) と Short bio (250 characters) を書く(自己紹介と略歴)
  • Sponsor tiers や Goals を決める($1/月で 10 人が目標、スポンサーになってくれた方への特典など)
  • Github と連携する Stripe のアカウントを作成し、銀行口座を入力する(運転免許証やパスポートといった顔画像認識処理あり)
  • DocuSign を使用して W-8BEN フォーム(米国源泉税に対する受益者の非居住証明書) に入力し署名する

参考: W-8BEN https://www.irs.gov/pub/irs-pdf/fw8ben.pdf

提出から審査完了までの時間

私の場合は提出から審査完了まで 5 分でした。むしろ提出する前段階で Introduction などの英文を書いていたり Stripe で本人確認をしたり、DocuSign でフォームを埋めたりしている時間のほうが長く、1 時間ほどかかりました。日本時間の深夜に申請しましたが、時差を考えると西海岸で人間が働いているには少し早すぎる時間です。人間が処理しているようなメッセージが表示されたように記憶していますがあやふやです(Our staff will be checking ...)。どちらかというと審査処理もある程度自動化が進んでいて自動化で判断ができない場合に人間が処理している可能性のほうが高そうです。

f:id:kyagi:20220204224008p:plain

オンラインでの本人確認プロセスが一般化してきている

W-8BEN のフォームの入力は 7 年前に US に銀行口座を作成して以来でしたが、当時は同フォームを紙で送ったのに、今は DocuSign を利用してオンライン上で署名が可能なことに時代を感じました。DocuSign は仕事においても US の取引相手と契約書を締結するのに使用しています(日本ではクラウドサインのほうが有名でしょうか)。本人確認のための顔画像認識処理は Coincheck のアカウントを作った場合とほぼ同様のように見受けられました。オンラインでの本人確認プロセスが一般化してきていることを実感しました。フレームワークにライブラリとして実装される日も遠くなさそうです(もうあるのかな?)。

Shamshir が Github Actions に対応しました

従来のスタンドアローンでの起動に加えて、Github Actions からも起動できるようになりました。よろしければお使いください。

https://github.com/kyagi/shamshir

f:id:kyagi:20220204204204p:plain f:id:kyagi:20220204204219p:plain

        uses: kyagi/shamshir@v1

トリガーのイベントとして「PR のレビューや修正」だと数が多くて Github Actions の実行時間の制限が心配かもしれません。その場合、cron 起動にして「平日の 9-19時(JST=UTC+9)に 毎時 0, 30 分の 2 回ずつ」であれば 1 レポジトリ20 回/日なので大丈夫だと思います。

on:
  pull_request_review:
    types: [submitted, edited, dismissed]
  pull_request:
    types: [edited, labeld, unlabeled]
on:
  schedule:
    - cron: '0,30 0-10 * * 1-5'

以下は振り返りになります。

独自 DSL である Github Actions の理解が難しい

Github API Reference をみてスタンドアローンの js を書くのはスムーズに行きました。一方 Github Actions のドキュメントを読んで YAML を書くのは恐ろしく手間でした。これは背景知識の差によるものです。あらかじめ Rest API とはどういうものかを知っているかからはじめるのと、独自 DSL を スクラッチから学ぶ学習コストの違いです。Github Actions を「使う」側ではなく「作る」側は全くの経験だったのでのドキュメントを読んでも「つまり、どういうことだってばよ?」で エラーメッセージを読んでトライ & エラーを延々と繰り返すしかありませんでした。

トライ&エラーの一例を挙げます。例えば、action.yml で「使いますよ」宣言したログを

outputs:
  log:
    description: 'log'

octkit ライブラリを使用して js 内ではこうやって使用して

core.setOutput('log', files)

Github Actions の Web 画面に出すために workflow の yml でこう書かなければいけない

 - run: echo "${{ steps.id.outputs.log }}"

こういったところでつまづきまくりました。要は Github 側でスポーンさせたコンテナに対してどうやって入出力をつなぐか、の仕組みをつくって YAML で提供してくれているのだと思いますがそれぞれがどう連携して動いているか、がわかりづらかったです。ただ慣れればササッとかけるかもしれません。これは利便性と柔軟性のトレードオフですね。

Github Actions を動かすのが面倒(使うのは楽)

動かすために、ダミーレポジトリをつくってダミーの PR でダミーのコミット(Update README) を延々と繰り返すしかありませんでした。PR を 30 個ほど、README に 1 文字加えるだけのコミットを 100 回ほど繰り替えしたでしょうか...

スタンドアローンと Github Actions の両方をサポートする、という設計が無理めだったかも

振り返るとスタンドアローンと github actions 両方やろうとしたのが間違いだったかもしれません。最初はスタンドアローンで作っていたのを友人が「Github Actions で動かせば制限時間内ならタダなのにサーバ代乙 (^Д^) pgr」と煽ってきたため、カッとなって「え!! おなじ動作を Github Actions で!? ... (両方)出来らあっ!! ヽ(・Д・)ノ」と作り込みをはじめてしまいました。結局、認証の違い(personal access token or ${{secret.GITHUB}} )からくる octokit の差分とパラメータの取得処理の違いをどうやって吸収するかに苦しみました。Github Actions 側のコンテナに独自の環境変数や /etc/system-release 的なファイルがあって、「ここは Github 様のコンテナ内ですぞ」が js 内から判別できれば楽だったのですがそんな美味い話があるわけもなく…

結局コンストラクタ内で分岐させましたが、共通メソッドを用意した親クラスを用意して継承 or コンポジションを使った方がよかったかもしれません(v2 の予定が出てくればリファクタリングしたい)。今後拡張する場合、肥大化しそうなのはスタンドアローン側ではなく Github Actions 側のはず。

DSL でのプログラミング(?) について

個人的に触っている Kubernetes も CloudFormation も Ansible も YAML で用意された箱に納めるデータだけ用意する形なので「リソースの定義だけ準備しろ」が Ops における最近形式なのかもしれません。その場合、各コンポーネントがどう連携しているか理解することが重要になってきます。しかし YAML を書くには、フレームワークが提供するコンポーネントをしっかり理解しないといけないので、初動までの学習コストはどうしても高くなるように思えます。Github API Refereces を見てスタンドアローンの js を自由に書いて好きなサーバで動かすのか、Github Actions の DSL に従って Github というプラットフォームで統合された形で動かすのがいいのか... どちらも一長一短です。

「Github Actions おじさん」は副業 or 新規事業になるか

Github API を利用してスタンドアローンのスクリプトを作るのは楽でしたが、それを Github Actions 上で動かすためにカスタムの Github Action を作るのは独自 DSL の学習コストや実行環境の点で、想像以上に大変でした。

ここまで大変なら「Github Actions おじさん」がビジネスになるのではないか、作るのは面倒なので誰もやりたがらない反面、使うのは便利だからこれでビジネスができないか、という議論を友人としたところ、すでに普及したプラットフォームであり、ネットワーク効果が高いので専門職として成り立つ気はする、との結論になりました。

PR だけではなく、commit, issue, packages など、ほぼすべての github イベントに対してアクションを実行できるので用途は幅広く、拡張性も高いので「ウチの Github レポジトリでこういうことをしたい」会社はそれなりにあるのではないでしょうか。スタートアップに提供する場合、彼らは本業のプロダクト開発を効率化できますし、大企業に提供する場合、彼らは複数レポジトリに一括して恩恵を享受できます。使用者が kyagi/shamshir@ref で使い分けられるのも、開発者(私)にとってはブランチごとにクライアントを管理できるのでよさそうです。

まとめ

オレはようやくのぼりはじめたばかりだからな。このはてしなく遠い Github Actions 坂をよ…(未完)。

次回作(v2)にご期待ください。( •ᴗ•)੭

追記

Github Sponsors

使ってみて気に入りましたら Github Sponsor で支援していただけると嬉しいです ( ᵕᴗᵕ ) 支援していただいた方/組織は機能開発の優先リクエストを開発者(私)に送ることができます。 github.com

営業メッセージ

もしこの Github Actions が気に入った場合は Github Sponsors から支援していただけると嬉しいです。$1/月でスポンサーのバッジを、 $5/月で開発における優先リクエストを受け付けています。

現状、Shamshir は「Approved の数」を条件として「ひとつの」ラベルをつけたり/はがしたりしていますが、より複雑な条件や複数のラベル、ユーザーのロールに応じての動作も開発可能です。

Github Actions はとても便利な反面、そのドキュメントはお世辞にも読みやすいものとは言えず、独自の DSL とワークフローがさらにプログラマの理解を難しくしています。また、ドキュメントは基本的に全て英語で書かれており、非ネイティブの開発者は読み解くのに時間がかかります。

わずかなコスト、わずかな時間であなたの開発チームにあったカスタム Github Actions の開発ができたらどうでしょうか? あなたも私もハッピーです。ご連絡、お待ちしています :-)

Github で PR の APPROVED 数に応じてラベル(releasable) をつけたり外したりする

f:id:kyagi:20220131212856p:plain
Dismiss stale pull request approvals when new commits are pushed

Github の Pull Request のレビュー後、APPROVED の数に応じてラベルをつける運用を自動化します。APPROVED 後にコミットがあった場合、自動的に APPROVED が取り消される運用(画像) での運用を想定しています。*1

f:id:kyagi:20220131220133p:plain
shamshir

任意の数の APPROVED を取得した PR の集合を A とし、releasable ラベルが付与されている PR の集合を B とした場合、動作は以下になります。

  • 差集合 A - B に対して releasable ラベルを付ける
  • 差集合 B - A に対して releasable ラベルを剥がす

差集合 B -A は主に APPROVED 後にコミットすることで発生します ((Github のブランチ設定で Dismiss stale pull request approvals when new commits are pushed 設定をしている場合)))

レポジトリ

https://github.com/kyagi/shamshir

実行

$ node shamshir.js --owner kyagi --repo awesome-project --label releasable --quorum 2

ログ

$ cat combined.log
{"level":"info","message":"Shamshir started.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:22"}
{"level":"info","message":"Shamshir got pulls: 2602,2598,2596,2575,2573,2557,2553,2551,2540,2539,2481,2478,2295,2281,1981,1951,1685","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:22"}
{"level":"info","message":"Shamshir added releasable label to pull/2598.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:24"}
{"level":"info","message":"Shamshir added releasable label to pull/2596.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:24"}
{"level":"info","message":"Shamshir added releasable label to pull/2575.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:25"}
{"level":"info","message":"Shamshir removed releasable label from pull/2573.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:26"}
{"level":"info","message":"Shamshir added releasable label to pull/2551.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:28"}
{"level":"info","message":"Shamshir added releasable label to pull/2540.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:29"}
{"level":"info","message":"Shamshir added releasable label to pull/2539.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:30"}
{"level":"info","message":"Shamshir added releasable label to pull/2478.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:31"}
{"level":"info","message":"Shamshir added releasable label to pull/2295.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:32"}
{"level":"info","message":"Shamshir added releasable label to pull/1951.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:34"}
{"level":"info","message":"Shamshir finished.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:34"}

Github API (REST API) について

Github API では Issue と Pull Request でエンドポイントを共有しています("shared" actions for both features)。API Reference の Issue ページにはラベル処理がありますが Pulls ページにはラベル処理がありません。だからといって Pull Request にラベルをつけられないというわけではなく、Issue API のエンドポイントを使い key として pull_request を使用することで(shared) Pull Request に対してもラベル処理が可能です。

Note: GitHub's REST API v3 considers every pull request an issue, but not every issue is a pull request. For this reason, "Issues" endpoints may return both issues and pull requests in the response. You can identify pull requests by the pull_request key. Be aware that the id of a pull request returned from "Issues" endpoints will be an issue id. To find out the pull request id, use the "List pull requests" endpoint.

https://docs.github.com/en/rest/reference/issues

Every pull request is an issue, but not every issue is a pull request. For this reason, "shared" actions for both features, like manipulating assignees, labels and milestones, are provided within the Issues API. https://docs.github.com/en/rest/reference/pulls

トップの画像では集合の図を載せていますが、実装で集合演算は使用していません。JavaScript に組み込みの集合演算がないのと、1 ページ 30 個 程度の Pull Request が処理対象の場合、集合演算よりループで処理した方が簡単だからです。

運用方法について

EC2 や ECS で動かすスタンドアローン的な運用と Github Actions で動かす運用の 2 つを考えています(Github Actions は将来サポート予定)。それぞれの長所/短所を以下に挙げます。

スタンドアローン Github Actions
設計/実装 制約がなく自由度が高い Github Actions の方法に則る必要があり workflow/action 内でできることに制約がある
コスト 走らせるためのリソース(EC2 or ECS) にコストがかかる 条件内の実行時間とストレージ容量であればコストは無料
認証/認可 Personal Access Token が必要 入力として GITHUB_TOKEN が使える

参考情報

*1 Dismiss stale pull request approvals when new commits are pushed 設定をしている場合