その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

Jenkins + AWS CodeBuildという構成をやめました

かつて Kyoto.なんか #4 で発表した話題ですけれども:

moznion.hatenadiary.com

これはもうやってません!!!(正確に言うと運用している組織内ではリタイアメントの段階に入っています)
今はCodeBuildを単体で使っています.


かつての AWS CodeBuild は

  • ビルド結果の通知が貧弱
  • Trigger が貧弱 (pull-requestに引っ掛けてビルドタスクを回す,みたいな機能が微妙だった)
  • ビルド履歴の一覧性が貧弱

という感じだったので,その不便さを補うために Jenkins を挟んで運用していましたが,今やこれらのペインポイントはほぼ解決しており*1 CodeBuild を単体で使っても充分に快適な開発体験が得られます.
むしろ今となっては Jenkins という部品を間に挟んでしまうとシステムの複雑度が上がってとっつきにくくなってしまいますし,なにより故障部品が増えるのであまり好ましい状況ではありません.強いこだわりや理由がなければ CodeBuild を単体で使うと良いと思います.

今考えると,昨年 fujiwara さんが発表していた「隙間家具」のような感じで Jenkins を活用していたと言えそうですね (結果的に円満に隙間家具ことJenkinsを外せたので).
speakerdeck.com


というわけで現在は Jenkins + CodeBuild という構成ではなく,CodeBuild 単体で使うようにしております,というご連絡でした.こういう情報はしっかりアップデートしておかないといけないなという気持ち (新たに使う人が出てくるかもなんで).

一時期の開発を支えてくれた Jenkins に感謝です.

*1:通知はもう少し細かい設定をしたくなることがありますが

GitHub Packagesにホストされたprivate packageをsbtから使う

表題のとおりです.
GitHubのオフィシャルドキュメントを読むとmavenで使う方法gradleで使う方法は紹介されているのですがsbtで使う方法が調べてもシュッと出てこなかったのでメモとして記す次第.


基本的には以下のようにmavenやgradleと同様にbuild.sbtに対して設定してやるとよろしい.

libraryDependencies += "com.example.your" % "package" % "0.0.1"

resolvers += "GitHub your-package" at "https://maven.pkg.github.com/example/your-package"

credentials += Credentials("GitHub Package Registry", "maven.pkg.github.com", "", sys.env.get("GITHUB_REGISTRY_TOKEN").orNull)

libraryDependenciesは普通にインストールしたいprivate packageを指定し,併せてresolversにはそのpackageに至る宛先を追加します (この時,例における"GitHub your-package"は自分の好きなわかりやすい名前をつけて良い./example/your-packageについてはpackageの属する/org/repoを指定する).

credentialsについては例の通り,第4引数になんらかの形でGitHubトークン文字列を与えると良いです.この際のGitHubトークンの詳細についてはこちらを参照ください: https://docs.github.com/en/packages/publishing-and-managing-packages/about-github-packages#about-tokens
注意としては,Credentialsの第1引数および第2引数は例のとおりである必要があるということです.第2引数については「まあそうでしょう」という感じですが,第1引数を自由気ままに設定するとうまく動きません.これはこの文字列を内部的にrealmとして利用しているためです.

また,Credentialsについてはbuild.sbtに直接書くのではなく,別ファイルに書き出しておくという方法もあると思います (ref: https://www.scala-sbt.org/1.x/docs/Using-Sonatype.html#step+3%3A+Credentials).この辺は状況やお好みに応じてという感じでしょう.


以上です.GitHub Packages,便利ですね.

いい感じにPHP Runtimeのdebパッケージを作る

PHPのエッジなバージョン (例えば今の時点だと 8.0.0alpha3) を使いたい場合,多くの場合はソースコードからビルドする必要があります.その他にもランタイムのオプションをいじりたいだとか,そういった様々な理由からPHPソースコードからビルドしなければならないことがあります.あるでしょう.

ソースコードからビルドするのはまあ良いんですが,そのビルド成果物 (つまりPHPランタイム) のデプロイ対象が多い場合に都度都度その環境でビルドをするのは時間がかかってしまいますし,計算機リソースの無駄遣いです.
というわけでDebianUbuntuを利用している場合だとdebパッケージを一度こさえて,それをばら撒くようにすると経済的です.

以下はミニマムな例:

$ curl -LO https://downloads.php.net/~carusogabriel/php-8.0.0alpha3.tar.gz
$ tar zxf php-8.0.0alpha3.tar.gz
$ cd php-8.0.0alpha3
$ ./configure
$ make
$ INSTALL_ROOT="/path/to/php-dist" make install

というふうにしておき, /path/to/php-dist/DEBIAN/control

Package: php8
Maintainer: moznion <moznion@gmail.com>
Architecture: amd64
Version: 8.0.0alpha3
Description: php 8 binary

というようなcontrolファイルを置いて,

fakeroot dpkg-deb --build /path/to/php-dist/ .

というふうにしておくと良い感じでdebパッケージが作成されます.


特記事項としては

  • ./configure の際に --prefix を付与してはならない
  • --prefix の代わりに make install の際に INSTALL_ROOT 環境変数を付加する

というあたりです.

というのも ./configure--prefix を付けてしまうと,build / install された成果物がそのprefixの内容に依存してしまうため,ビルドした環境の構成 (特にpath) とdebパッケージの展開先の環境構成が異なるときにうまく動作しなくなる場合があります.
例えば,./configure --prefix=/tmp/dist として makemake install すると,成果物は /tmp/dist 以下に展開されます.その状態でdebパッケージを作成し,そのパッケージを利用して別の環境にインストールすると,モノ自体は /usr/local 以下にインストールされますが,インストールされたモノたちは /tmp/dist に依存しています (例えば php.ini の場所は /tmp/dist/usr/local/lib/php.ini にあることが期待されてしまう).びみょう.

というわけで --prefix を渡さないようにしておきつつ (つまりデフォルトの /usr/local が利用される),しかしそのまま make install するとそのまま /usr/local 以下にモノがインストールされてしまいdebパッケージを作成するには具合が悪いので,INSTALL_ROOT 環境変数によって「インストール先だけを変える」ことにより,そのディレクトリに対してdebパッケージを作るといい感じに作成できます (つまりこの場合だと php.ini の場所は /usr/local/lib/php.ini になる).

良かったですね.

The Perl Foundationに寄付した

perl,やはり心のふるさとという感じがある……(流石に全盛期と比較して手のスピードは落ちたけど
大きな問題は他の人にとってふるさとではないということです

とりたてて特別な何かがあったわけではなく,久しくPerlを書いていなかったなか,たまたまPerlXML::XPathを使ってXMLをどやこやするスクリプトを書いたところ上記のような気持ちになったので「ふるさと納税」と称してPerlの総本山ことThe Perl Foundationに寄付したという経緯です.

www.perlfoundation.org

寄付はこちらからできます.PayPalを選ぶと日本からの寄付は未対応である旨が表示されるので,ここは一丁勇気を出して "Online credit card payment" すると良いでしょう.

心のふるさとに寄付をするというのもなかなかオツなものですね.

Javaの非同期アプリケーションのflame graph profileを取る

netty を使うような非同期 Java のアプリケーション (例えば Play2 Web アプリ) の flame graph profile を取るという話題です.色々な方法が考えられますが,jvm-profiling-tools/async-profiler を利用するのが最も手っ取り早そうな感じがしたので,その方法を示します.

github.com

使い方はいたって簡単で,Releases から任意のバージョンのアーカイブを取ってきて,

./profiler.sh -d 60 -f /path/to/flame_graph.svg $JAVA_APP_PID

として実行するだけで 60 秒間プロファイリングし,その結果の flame graph を取得することが可能です.

f:id:moznion:20200620234509p:plain

便利ですね.

tinygo 向けの JSON marshaler: go-json-ice を書いた

English article is here: Released go-json-ice: a code generator of JSON marshaler for tinygo - moznion's tech blog


tinygo では encoding/json を import するとコンパイルできなくなるという問題があり *1,なんらかの struct を JSON に marshal したい時に使える de facto な方法が無いように見えました.これに関しては例えば以下のような issue が立っています:

github.com

github.com

つまり tinygo 上で任意の struct を JSON にしたい時は「手で気を付けてシリアル化する」しか方法がなかったわけですが,まあそれだと何かと不便だったので表題の通り json-ice という encoding/json に依存しない JSON marshaler のコードジェネレータを作りました.

github.com

挙動としては,事前に marshaling 対象となる struct (の json カスタム struct タグ) を解釈して JSON に marshal するコードを吐き出す,という至ってシンプルなものとなります.似たような挙動をする先行実装に mailru/easyjson などがありますが,これらは内部的に encoding/json に依存しているようで,今回の用途にはマッチしませんでした.


例えば以下のような struct を marshal したい時には go:generate と一緒にコードを書いておくと

//go:generate json-ice --type=AwesomeStruct
type AwesomeStruct struct {
	Foo string `json:"foo"`
	Bar string `json:"bar,omitempty"`
}

MarshalAwesomeStructAsJSON(s *AwesomeStruct) ([]byte, error) というコードが生成されるので,それを利用して struct を JSON に marshal することが可能です:

marshaled, err := MarshalAwesomeStructAsJSON(&AwesomeStruct{
	Foo: "buz",
	Bar: "",
})
if err != nil {
    log.Fatal(err)
}
fmt.Printf("%s\n", marshaled) // => {"foo":"buz"}

これにより実行時に reflection を使って動的に marshaling する必要がなくなるので,tinygo でも JSON marshaling が簡単に行えるようになります.また,動的な reflection の代わりに事前計算するので当然の結果ですがパフォーマンスも少し良くなります *2
もちろんその副作用として interface{} な値を持つ struct については動的な型の解決ができないため marshaling ができません.Marshaling するためには静的に型の解決ができる必要があります.

また tinygo は wasm を吐き出す機能も有しており,この wasm が実行時に import するモジュールは「元のコードが何に依存しているか」によって変化してきます.この実行時の依存をできる限りミニマムにしたい (例えばブラウザランタイム以外の強力な sandbox 環境で wasm を動かすというユースケースが考えられる) という動機があったので,生成コードが依存するパッケージは可能な限り最小限にとどめました.結果的に現時点では strconv にのみ依存するようになっています.ミニマル!


そんな感じのライブラリです.どうぞご利用ください!
もちろん tinygo ではなく通常の go の処理系でも利用できますが,それについてはもっと良い先行実装 (それこそ easyjson とか) があると思うので,そちらの利用の検討をおすすめします.


なお,この実装は JSON の marshaling のみをサポートするものですが,逆に tinygo で JSON unmarshaling するにはどうすればよいかと言うと,buger/jsonparser を利用すれば良いように思いました.

> It does not rely on encoding/json, reflection or interface{}, the only real package dependency is bytes.

github.com




余談ですが

//go:generate json-ice --type=DeepStruct
type DeepStruct struct {
	Deep []map[string]map[string]map[string]map[string]string `json:"deep"`
}

のような深く,再帰的(?)な型についてもちゃんとしたコード生成が可能です:

given := &DeepStruct{
	Deep: []map[string]map[string]map[string]map[string]string{
		{
			"foo": {
				"bar": {
					"buz": {
						"qux": "foobar",
					},
				},
			},
		},
		{
			"foofoo": {
				"barbar": {
					"buzbuz": {
						"quxqux": "foobarfoobar",
					},
				},
			},
		},
	},
}

marshaled, err := MarshalDeepStructAsJSON(given)
if err != nil {
	log.Fatal(err)
}

log.Printf("[debug] %s", marshaled) // => {"deep":[{"foo":{"bar":{"buz":{"qux":"foobar"}}}},{"foofoo":{"barbar":{"buzbuz":{"quxqux":"foobarfoobar"}}}}]}

生成コードはこんな感じ

import "github.com/moznion/go-json-ice/serializer"

func MarshalDeepStructAsJSON(s *DeepStruct) ([]byte, error) {
	buff := make([]byte, 1, 54)
	buff[0] = '{'
	if s.Deep == nil {
		buff = append(buff, "\"deep\":null,"...)
	} else {
		buff = append(buff, "\"deep\":"...)
		buff = append(buff, '[')
		for _, v := range s.Deep {
			if v == nil {
				buff = append(buff, "null"...)
			} else {
				buff = append(buff, '{')
				for mapKey, mapValue := range v {
					buff = serializer.AppendSerializedString(buff, mapKey)
					buff = append(buff, ':')
					if mapValue == nil {
						buff = append(buff, "null"...)
					} else {
						buff = append(buff, '{')
						for mapKey, mapValue := range mapValue {
							buff = serializer.AppendSerializedString(buff, mapKey)
							buff = append(buff, ':')
							if mapValue == nil {
								buff = append(buff, "null"...)
							} else {
								buff = append(buff, '{')
								for mapKey, mapValue := range mapValue {
									buff = serializer.AppendSerializedString(buff, mapKey)
									buff = append(buff, ':')
									if mapValue == nil {
										buff = append(buff, "null"...)
									} else {
										buff = append(buff, '{')
										for mapKey, mapValue := range mapValue {
											buff = serializer.AppendSerializedString(buff, mapKey)
											buff = append(buff, ':')
											buff = serializer.AppendSerializedString(buff, mapValue)
											buff = append(buff, ',')
										}
										if buff[len(buff)-1] == ',' {
											buff[len(buff)-1] = '}'
										} else {
											buff = append(buff, '}')
										}

									}
									buff = append(buff, ',')
								}
								if buff[len(buff)-1] == ',' {
									buff[len(buff)-1] = '}'
								} else {
									buff = append(buff, '}')
								}

							}
							buff = append(buff, ',')
						}
						if buff[len(buff)-1] == ',' {
							buff[len(buff)-1] = '}'
						} else {
							buff = append(buff, '}')
						}

					}
					buff = append(buff, ',')
				}
				if buff[len(buff)-1] == ',' {
					buff[len(buff)-1] = '}'
				} else {
					buff = append(buff, '}')
				}

			}
			buff = append(buff, ',')
		}
		if buff[len(buff)-1] == ',' {
			buff[len(buff)-1] = ']'
		} else {
			buff = append(buff, ']')
		}

		buff = append(buff, ',')
	}
	if buff[len(buff)-1] == ',' {
		buff[len(buff)-1] = '}'
	} else {
		buff = append(buff, '}')
	}
	return buff, nil
}

td-agent-gem で `google-protobuf requires Ruby version < 2.8.dev, >= 2.5.` みたいなエラーが出るって時

td-agent-gem を利用している時に google-protobuf requires Ruby version < 2.8.dev, >= 2.5. というエラーがでて困るという事がありました.

support.treasuredata.com

td-agent の ChangeLog を見た感じ,この記事を書いている時点での td-agent の最新バージョン v3.7.1 にバンドルされている ruby のバージョンは 2.4.10 のようなので,このエラーメッセージの内容は正しそうです.


そもそもなぜこのようなことが起きたかというと,awslabs/aws-fluent-plugin-kinesis を td-agent-gem でインストールしようとした際に

ERROR:  Error installing fluent-plugin-kinesis:
        google-protobuf requires Ruby version < 2.8.dev, >= 2.5.

というような内容が出てしまったのでした.

ちょっと調べてみると

github.com

という pull-request がすでに作られており,このdiff を見た感じ google-protobuf のバージョン指定が甘かったため,google-protobuf のマイナーバージョンが 3.12.0 に上がったタイミングで非互換が出てしまっていたということがわかりました *1.以下の protocolbuffers/protobuf に対する pull-request に拠るもののようです:

github.com


aws-fluent-plugin-kinesis に出ている pull-request がマージされないとどうしようもない感じがするのですが,そう待ってはおれんので以下のように「先に google-protobuf:3.11.4 をインストールしてしまう」ワークアラウンドを書いて現状は乗り切っています.

$ td-agent-gem install google-protobuf:3.11.4 --no-ri --no-rdoc
$ td-agent-gem install fluent-plugin-kinesis --no-ri --no-rdoc


以上です.はやく pull-request がマージされる (あるいは td-agent にバンドルされている ruby のバージョンが上がる)と良いですね.


[追記]

github.com

マージされたようです.3.2.2 以降のバージョンの aws-fluent-plugin-kinesis を利用すればこの問題は解決できそうです.

*1:他にも atlassian/fluent-plugin-kinesis-aggregation でも同じような問題が起きていた様子