PHPerKaigiが好きな理由 #phperkaigi

PHPの名を冠していて、それでいていろいろな技術に対して門戸を開いているところが好きです。

(ほぼ)元PHPerであるからなのですが、なんとなくPHPに関われている気がして嬉しいのです。

他のコミュニティや勉強会やカンファレンスも好きなところはたくさんありますし、他にも幅広い技術の話題を取り上げてくれるカンファレンスはあるので(PHPカンファレンス福岡とか)、「PHPerKaigiだけ」というわけではないのですが、少なくとも私がPHPerKaigiの好きなところの1つです。

あと、程よい距離感も好きです。

forteeの充実した機能も好きです。

運営の人の動きや頑張りが(たまたまですが)私のTwitter TLで見ることができるのも好きです。

今年はオンライン/オフラインのハイブリッド開催です。私はオンラインでの参加になりますが、今回も楽しみです。

phperkaigi.jp

今回もタイムテーブルを見る限り、多少のPHPの軸足がありつつも発表内容の幅は広そうです。

forteeの機能によってプロポーザルに星をつけたセッションには、そのままタイムテーブルに☆マークがついているので便利です。

あと、オンラインの人たちの野良懇親会とかもあれば参加したいなあと思っています。

まだ若干チケットはあるみたいなので、参加してみんなで楽しみましょう。

データベースにクエリを投げて結果をプリントするライブラリqpを作った

データベースを伴うテストを書いていて、何故かテスト結果が安定しない事象に出くわして「なんでだ?????」と混乱した結果、データベースの状況をprintデバッグをしたくなって作りました*1

github.com

使い方は

package main

import (
    "database/sql"
    "log"

    "github.com/k1LoW/qp"
    _ "github.com/mattn/go-sqlite3"
)

func main() {
    db, err := sql.Open("sqlite3", "path/to/db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    qp.Print(db, "SELECT * FROM users WHERE username = 'alice'")
}

みたいにqp.Print()*sql.DB と実行したいクエリを渡すと、

$ go run main.go
+----+----------+----------+-------------------+---------------------+---------+
| id | username | password |       email       |       created       | updated |
+----+----------+----------+-------------------+---------------------+---------+
|  1 | alice    | passw0rd | alice@example.com | 2017-12-05 00:00:00 | <nil>   |
+----+----------+----------+-------------------+---------------------+---------+
(1 row)

のように結果がでるシンプルなものです。SELECT だけ結果をだしますが、他のクエリでもデータベースに投げるようにしています。

私のように混乱しないとなかなか必要になることはないと思いますが、「Goのコードからデータベースにクエリをカジュアルに投げてデバッグしたい」というときがあれば使ってみてください。

*1:原因は、単純にgo test ./... がパッケージ単位で並列に実行されるのに対して別のパッケージで同じテストデータベースにクエリを投げていたからでした...

ファイルの一部の文字列を差し替えするためのコマンド/パッケージ repin を作った

継続的ドキュメンテーション関連です。

README.mdなどのドキュメントを運用していると、そのドキュメントの一部を(CIなどで)自動で差し替えたいことがあります。

例えば xxx help コマンドの出力をもって機能一覧にしているようなREADMEだと、機能が追加されるたびに xxx help の実行結果でREADMEを差し替える必要があります。

ちょっと前に作った dirmap も出力結果をREADMEに貼っておきたい系のツールです。

k1low.hatenablog.com

そういう「文字列の差し替え」というのはsedでもある程度可能ですが、エスケープが難しかったり複数行の置換もなかなか覚えられないので専用ツールを書きました。

github.com

repin

例えば

# Hello

```console
```

みたいなMarkdownファイルがあったとして、$ echo hello world! という文字列を入れたいとき、GNU sedだと

$ cat README.md | sed -z 's/```console.*```/```console\n$ echo hello world!\n```/'

と書けば

# Hello

```console
$ echo hello world!
```

と出力されます。

これを repin で書くと

$ repin README.md -k '```console' -k '```' -r '$ echo hello world!'

となります。多少直感的になります。

また、置換文字列を標準入力で受け取れるので、

$ repin --help | repin README.md -k '```console' -k '```'

とすれば

# Hello

```console
repin is a tool to replace strings between keyword pair.

Usage:
  repin [FILE] [flags]

Flags:
  -h, --help              help for repin
  -i, --in-place          edit file in place
  -k, --keyword strings   keywords to use as a delimiter. If 1 keyword is specified, it will be used as the start and end delimiters; if 2 keywords are specified, they will be used as th\
e start and end delimiters, respectively.
  -N, --no-newline        disable appending newlines
      --raw-keywords      do not convert \n or \t of the entered keywords
  -r, --replace string    replace file path or string
  -v, --version           version for repin

```

と、複数行の help コマンドの出力を差し込んだMarkdownが得られます(これをGNU sedですることが私には難しかった)。

パッケージとしての利用

repinはパッケージとしての利用もできるようにしています*1

pkg.go.dev

というわけで

小さいコマンド/パッケージの紹介でした

*1:これは、 https://github.com/k1LoW/ndiag に組み込んでドキュメンテーション生成の仕組みをもう少しシンプルにしたいという思惑があるのですがまだ取りかかれていません

Colima on ARM Macでaarch64とx86_64のDocker環境を切り替えて使う

Colimaにはプロファイル( profile )という概念があり、--profile によってVM環境を切り替えることができます。

また --arch オプションでVMのイメージを切り替えることも可能です。

上記を利用してaarch64とx86_64のDocker環境を両方準備してみます。

方針

ARM Macなので基本的には aarch64(ARM)をデフォルトにします

profile ISA
default aarch64
x64 x86_64

各環境を作成

まずaarch64の環境を作成します。

$ colima start --cpu 8 --memory 8 --disk 128 --mount '~/src/:w' --mount '~/tmp:w' --dns 8.8.8.8

次にx86_64の環境を x64 というプロファイル名をつけて作成します。

$ colima start --profile x64 --arch x86_64 --cpu 8 --memory 8 --disk 128 --mount '~/src/:w' --mount '~/tmp:w' --dns 8.8.8.8

この時点で以下のようになっています

$ colima list
PROFILE    STATUS     ARCH       CPUS    MEMORY    DISK
default    Running    aarch64    8       8GiB      128GiB
x64        Running    x86_64     8       8GiB      128GiB

ColimaはLimaのラッパーなので limactl でも確認してみます。

$ limactl list
NAME          STATUS     SSH                ARCH       CPUS    MEMORY    DISK      DIR
colima        Running    127.0.0.1:52650    aarch64    8       8GiB      128GiB    /Users/k1low/.lima/colima
colima-x64    Running    127.0.0.1:58188    x86_64     8       8GiB      128GiB    /Users/k1low/.lima/colima-x64

docker context list をみるとホストのdockerがどのDockerのエンドポイントを向いているのかがわかります。

$ docker context list
NAME            DESCRIPTION                               DOCKER ENDPOINT                               KUBERNETES ENDPOINT   ORCHESTRATOR
colima          colima                                    unix:///Users/k1low/.colima/docker.sock
colima-x64 *    colima [profile=x64]                      unix:///Users/k1low/.colima-x64/docker.sock
default         Current DOCKER_HOST based configuration   unix:///var/run/docker.sock                                         swarm
desktop-linux                                             unix:///Users/k1low/.docker/run/docker.sock

colima-x64* がついているので、今は x86_64 環境を向いています。

contextの切り替えは docker context use [NAME] で切り替え可能です。

これで、Colima on ARM Macでaarch64とx86_64のDocker環境を切り替えて使えるようになりました。

--profile=x64 の環境ではx86_64のDockerイメージしかない mysql:5.7 なども動きます。

ベンチマーク

せっかくなのでベンチマークも取ってみます。

aarch64 環境

$ docker context use colima
colima
Current context is now "colima"
$ docker container run -it --rm --name debian-bullseye debian:bullseye /bin/bash
Unable to find image 'debian:bullseye' locally
bullseye: Pulling from library/debian
c7869242ae9a: Pull complete
Digest: sha256:10b622c6cf6daa0a295be74c0e412ed20e10f91ae4c6f3ce6ff0c9c04f77cbf6
Status: Downloaded newer image for debian:bullseye
root@faabf0fd5dcf:/# apt -y update && apt -y install sysbench
[...]
root@faabf0fd5dcf:/# uname -a
Linux faabf0fd5dcf 5.10.93-0-virt #1-Alpine SMP Thu, 27 Jan 2022 09:34:38 +0000 aarch64 GNU/Linux
root@faabf0fd5dcf:/# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
NAME="Debian GNU/Linux"
VERSION_ID="11"
VERSION="11 (bullseye)"
VERSION_CODENAME=bullseye
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
root@faabf0fd5dcf:/# sysbench cpu --cpu-max-prime=10000 --threads=8 --time=30 run
sysbench 1.0.20 (using system LuaJIT 2.1.0-beta3)

Running the test with following options:
Number of threads: 8
Initializing random number generator from current time


Prime numbers limit: 10000

Initializing worker threads...

Threads started!

CPU speed:
    events per second: 64490.28

General statistics:
    total time:                          30.0003s
    total number of events:              1934753

Latency (ms):
         min:                                    0.09
         avg:                                    0.12
         max:                                   21.01
         95th percentile:                        0.46
         sum:                               239149.80

Threads fairness:
    events (avg/stddev):           241844.1250/1938.40
    execution time (avg/stddev):   29.8937/0.01

root@faabf0fd5dcf:/#

x86_64 環境

$ docker context use colima-x64
colima-x64
Current context is now "colima-x64"
$ docker container run -it --rm --name debian-bullseye debian:bullseye /bin/bash
[...]
root@be6276f3da26:/# uname -a
Linux be6276f3da26 5.10.93-0-virt #1-Alpine SMP Thu, 27 Jan 2022 09:34:38 +0000 x86_64 GNU/Linux
root@be6276f3da26:/# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
NAME="Debian GNU/Linux"
VERSION_ID="11"
VERSION="11 (bullseye)"
VERSION_CODENAME=bullseye
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
root@be6276f3da26:/# sysbench cpu --cpu-max-prime=10000 --threads=8 --time=30 run
sysbench 1.0.20 (using system LuaJIT 2.1.0-beta3)

Running the test with following options:
Number of threads: 8
Initializing random number generator from current time


Prime numbers limit: 10000

Initializing worker threads...

Threads started!

CPU speed:
    events per second:  1077.72

General statistics:
    total time:                          30.0053s
    total number of events:              32340

Latency (ms):
         min:                                    3.38
         avg:                                    7.41
         max:                                   37.02
         95th percentile:                       13.22
         sum:                               239622.11

Threads fairness:
    events (avg/stddev):           4042.5000/46.19
    execution time (avg/stddev):   29.9528/0.01

root@be6276f3da26:/#

x86_64 on ARM はやはり遅いか......

参考

blog.amedama.jp

awsdoに ~/.aws/(config|credentials)の設定情報がなくてもAssumeRoleできるようにするためのオプションと、AssumeRoleしたロールでAWSコンソールにログインするオプションを追加した

1年以上前からの久しぶりのアップデートです。

k1low.hatenablog.com

--role-arn --source-profile

複数のAWSアカウントを横断して作業することがあり、AssumeRoleのための設定を~/.aws/(config|credentials)に書くのすら面倒になってきたので、設定なしでAssumeRoleができるようにするためのオプション --role-arn--source-profile を追加しました。

委任元のアカウントAのロール arn:aws:iam::AAAAAAAAAAAAAA:role/example-role を委任先のアカウントBに委任できるようにしている場合、

委任先のアカウントBの設定が ~/.aws/(config|credentials) にある場合は( profile-b )、

$ awsdo --role-arn=arn:aws:iam::AAAAAAAAAAAAAA:role/example-role --source-profile=profile-b -- aws ec2 describe-instance-status

と書けばいいし、アカウントBの設定がなくてもクレデンシャルがあれば

$ env AWS_ACCESS_KEY_ID=BBBBBbbbBBbBb AWS_SECRET_ACCESS_KEY=bbbbBBBbBBBBBBBB awsdo --role-arn=arn:aws:iam::AAAAAAAAAAAAAA:role/example-role -- aws ec2 describe-instance-status

と書けばAssumeRoleをした上でコマンドを実行できます。

設定を書かなくてもAssumeRoleできるということは、シェルスクリプトの一部だったりとかCIで実行したりなどがやりやすくなります。

例えばGitHub Actionsだと以下のようなワークフローでOIDC Connectして認証しながらさらにAssumeRoleしてコマンド実行ができたりします(未検証)。

name: AWS example workflow
on:
  push
permissions:
  id-token: write
  contents: read
jobs:
  assumeRole:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT }}:role/example-role
          aws-region: ${{ secrets.AWS_REGION }}
      - name: Run as ${{ secrets.AWS_ACCOUNT }}
        run: |
          aws sts get-caller-identity
      - name: Setup awsdo
        run: |
          export AWSDO_VERSION=X.X.X
          curl -L https://git.io/dpkg-i-from-url | bash -s -- https://github.com/k1LoW/awsdo/releases/download/v$AWSDO_VERSION/awsdo_$AWSDO_VERSION-1_amd64.deb
      - name: Run as ${{ secrets.AWS_ANOTHER_ACCOUNT }} using awsdo
        run: |
          awsdo --role-arn=arn:aws:iam::${{ secrets.AWS_ANOTHER_ACCOUNT }}:role/another-example-role -- aws sts get-caller-identity

--login

AssumeRoleしたロールでAWSマネジメントコンソール(Web)にログインできるオプションです。

$ AWS_PROFILE=myaws awsdo --login

もともと aws-vault にある機能で、便利だったので実装を参考にして awsdo にも追加しました。

というわけで

どんどんAssumeRoleしていきます。

github-script-ruby@v2でRubyのバージョンを指定できるようにした

github-script-rubyが何かについてはペパボのテックブログに書きましたので是非ご覧ください。

tech.pepabo.com

github-script-rubyは、簡単にいうと actions/github-scriptRuby版です。

今回は、github-script-ruby独自の機能としてRubyのバージョンを指定できるようにしました。

具体的には ruby-version:Rubyのバージョンを書くとそのバージョンで動きます。 簡単なサンプルとしては以下のような感じです。

- name: 'Hello Ruby version'
  uses: k1LoW/github-script-ruby@v2
  with:
    script: |
      repo = "#{context.repo.owner}/#{context.repo.repo}"
      number = context.issue.number
      comment = "Hello using Ruby v#{RUBY_VERSION}"
      github.add_comment(repo, number, comment)
    ruby-version: 2.7.5

あまり古いバージョンのRubyで動かすことにモチベーションはないですが、現状2.4.10までは動いています(コードを対応させればもう少し古いバージョンでも動くと思います)。

さて、どのように実現したのかというと ruby/setup-ruby の仕組みを利用させてもらうことで実現しています。

ruby/setup-ruby はどのように ruby-version: の機能を実現しているか

ruby/setup-rubyのREADME.mdに以下のように書かれています。

This action downloads a prebuilt ruby and adds it to the PATH.

The prebuilt releases are generated by ruby-builder and on Windows by RubyInstaller2. mingw and mswin builds are generated by ruby-loco. ruby-head is generated by ruby-dev-builder, jruby-head is generated by jruby-dev-builder, truffleruby-head is generated by truffleruby-dev-builder and truffleruby+graalvm is generated by graalvm-ce-dev-builds. The full list of available Ruby versions can be seen in ruby-builder-versions.js for Ubuntu and macOS and in windows-versions.js for Windows.

つまり、あらかじめruby/ruby-builder をはじめとする各リポジトリでプレビルドしたRubyをReleasesのAssetとして置いておき、ruby/setup-ruby側では ruby-version: で指定されたバージョンのRubyをダウンロード・展開し設置するという形で「任意のバージョンのRubyのセットアップ」を実現しています*1

github-script-ruby@v2 でも ruby-version: の機能を実現する

github-script-ruby@v2 でもruby/setup-rubyで使用しているAssetを使用させてもらっています。

まず、さまざまな処理系やバージョンにする必要はないと考えて「MRIのみ」かつ「パッチバージョンまで指定する」という制約をつけています。 そうすると対象となるのは ruby/ruby-builder で管理されているプレビルドRubyだけになります。

github-script-ruby@v2をstepで実行するタイミングで ruby-version: が指定されていれば ruby/ruby-builder からプレビルドRubyをダウンロードして設置して使用します。 また、bundlerが標準添付されていないバージョンの場合はbundlerもインストールします。

https://github.com/k1LoW/github-script-ruby/blob/6852aaffa26d2b760ff60c79b7086ef9a248a918/scripts/entrypoint.sh#L9-L23

これだけで簡単な ruby-version: の機能が実現できました。

その他にも

v2で、gemfile-path: (Gemfileのパスを指定する)や command:github-script-rubyの環境で任意のコマンドを実行する)なども追加しました。

これで、stepだけで独立した任意のバージョンのRuby環境を手に入れることができます。

(必要かどうかはおいておいて)例えば、

- name: 'Capistrano deploy'
  uses: k1LoW/github-script-ruby@v2
  with:
    gemfile-path: path/to/Gemfile
    command: bundle exec cap deploy --gemfile=path/to/Gemfile
    ruby-version: 2.7.5

みたいなこともできたりします。

というわけで、v1からあった gemfile:(Gemfileを書ける) や pre-command:github-script-rubyの環境で任意のコマンドを実行する)に続いて、さらに元ネタであったactions/github-scriptからさらに逸脱してきた感じです。

何か面白いユースケースを思いついたら是非教えてください。

*1:実際にはダウンロード以外にもキャッシュを使ったりruby-buildでビルドしたりなどをしています

2021年の振り返りと2022年の抱負

2021年の振り返り

2021年も2020年から引き続き内に籠った年だった気がします。

また、厄年だったことは全く関係ないのですが「エンジニアとしてこの先生き残るには」ということを考えることが多かったと思います。

私はこれまであまり将来を深く考えることはあまりなかったのですが、子供の著しい成長を間近で見ていると「ところで私は成長できているのだろうか」という現状に対する思いと「今後子供が十分に成長するまでサポートし続けることができるだろうか」という将来に対する思いとがごちゃ混ぜになって「うっ」となることが何回かありました。世の親をやっている人々はすごい。

一方で「自分が何が好きなのか」は言語化できました。

私はいろいろ好きだし大抵のものには意義を見出せる質なのですが、特に「開発者のための開発」が好きです。

その言語化を得て冷静に振り返ってみると「結局自分がやり続けたこととか成功体験の積み重ねの結果の"好き"なんだなあ」とも思いました。

会社では全方位でいろいろやらせてもらえる(結果は求められる)ので、引き続きやっていきたいです。

2021年の抱負は「インプットができるようにする」でした。

結果はどうだったかというと「できてないな」というのが正直な感想です。

「手持ちのカードが増えない」という事実は結構自分の心を乱している要因にもなっています。せめて強いカードを持っていればいいのですが難しい。

増えていない原因も自分なので、これはもう自分で動き出す以外は方法はないです。がんばれ。

OSS

今年はGitHub関係のものが多かった印象があります。GitHub Actionsは本当に良いですね。

今年最も力を入れたOSSは何かと問われたら octocov だと思います。

個人的にはtblsに近いレベルで「うまく設計がまとまったな」という感覚があります。うまくまとまると「小さな世界」を作れた感覚があって好きです。これを求めて懲りずにツールを作っている面もありそう。

octocovは2022年ももう少しだけやり残した実装を続けると思います。そしたらまたアイデアが降ってくるまでは手を離すことができそうです。

発表

継続的ドキュメンテーション関係の発表が多かったです。もう個人的には研究課題みたいになっています。もしくは趣味。

2022年の抱負

今年は「挑戦する」です。1年が終わった時に「今年はこれをやったな〜」と言いたい。

コンフォートゾーンから抜けるのが本当に苦手なので(むしろ嫌がる)、なんでもいいので、失敗でもいいので「やったな〜」を成し遂げたい。

チャンス(特に自身の感情の高まり)を逃さずに2022年もコードを書いていきたいです。

あ、あと分割キーボードを今年こそ手に入れたい。

今年もよろしくお願いします。