Posts for: #Packaging

ordered map という適当な粒度の課題

1時に寝て4時に起きて CPAP 治療忘れて寝ていたことに気付いて、装着してまた寝て6時半に起きた。

今日の運動は背筋をした。統計を 運動の記録 にまとめる。

orderedmap ライブラリ

json のオブジェクトの属性を意図した順番に並び替えたいという要件があって 適当なライブラリをみつけられなくて 最終的には自分で実装した。そのときに参考にしたモジュールではネストした配列やオブジェクトの順番を維持できないことに気付いて、どうせ実装するなら json の marshal/unmarshal の処理を完璧に実装しようと思って、再度フルスクラッチで作り直した。要は python の OrderedDict のようなものの go 版になる。

orderedmap というキーワードで検索するといくつもオレオレの実装がみつかる。次の3つを参考にしながら、私も自分の思う「ぼくのかんがえたさいきょうの orderedmap」 を作ってみた。

実際に作ったものが次になる。

インストール

$ go get -u github.com/kazamori/orderedmap

go のモジュールは GOPROXY protocol を介してプロキシサーバーから取得される。デフォルトでは proxy.golang.org が設定されている。

$ go env | grep GOPROXY
GOPROXY='https://proxy.golang.org,direct'

プロキシサーバーではモジュールがキャッシュされているため、利用する側でリビジョンを更新しようとしても短い間隔だとすぐには反映されない。プロキシサーバーに存在しないモジュールのリビジョンを取得したい場合は GOPROXY の環境変数でダイレクトモードに変更する。

$ GOPROXY=direct go get -u github.com/kazamori/orderedmap

generics 対応して汎用の map を扱えるのがちょっとお気に入りなところ。他人が作ったライブラリを使うことはできるけど、プログラミング勉強として自分で実装してみるのもよい気はする。ちょうど kyoto.go のイベントがあってそのトークに orderedmap について話して来ようと思う。

情報共有とメンバー課金の過ち

1時に寝て4時に起きて5時に起きて7時に起きた。明け方からうまく眠れなくなった。

clang の互換性

openldap 2.5 向けに ldap の overlay モジュールのビルド環境を作っていた。これまでは 2.4 向けのモジュールのみを提供していた。2.5 もそろそろやろうということで先週末からビルド環境の構築に着手していた。rpm のパッケージングの作業をしていて、openldap 2.5 のサーバーのビルドをしていると次のエラーが発生した。

configure:21011: checking for pthread_detach with <pthread.h>
configure:21033: clang -o conftest -O2 -g3 -fstack-protector -fPIE -D_REENTRANT -D_THREAD_SAFE -DOPENLDAP_FD_SETSIZE=16384 -DLDAP_CONNECTIONLESS -DSLAPD_META_CLIENT_PR -D_GNU_SOURCE -Wl,-z,relro -Wl,--as-needed  -Wl,-z,now -specs=/usr/lib/rpm/redhat/redhat-hardened-ld -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1  conftest.c    >&5
clang-15: warning: argument unused during compilation: '-specs=/usr/lib/rpm/redhat/redhat-hardened-ld' [-Wunused-command-line-argument]
clang-15: warning: argument unused during compilation: '-specs=/usr/lib/rpm/redhat/redhat-annobin-cc1' [-Wunused-command-line-argument]
conftest.c:118:16: error: incompatible pointer to integer conversion passing 'void *' to parameter of type 'pthread_t' (aka 'unsigned long') [-Wint-conversion]
pthread_detach(NULL);
               ^~~~
/usr/lib64/clang/15.0.7/include/stddef.h:89:16: note: expanded from macro 'NULL'
#  define NULL ((void*)0)
               ^~~~~~~~~~
/usr/include/pthread.h:269:38: note: passing argument to parameter '__th' here
extern int pthread_detach (pthread_t __th) __THROW;
                                     ^
1 error generated.

エラーメッセージを調べていると、どうやら clang 15 に pthread_detach がないといったものらしい。clang 14 のときはビルドできたという。他の oss でも clang のバージョン違いでビルドできないといったことは発生しているらしい。有識者によると、次の修正が clang15 対応らしい。

それ以外はとくに問題なく、ビルドできてモジュールそのものの動作も確認した。あとは rpm のパッケージングと gitlab ci/cd でビルドしたモジュールで動くかどうかの検証だけ。

メンバー課金による過ち

昨日 SuperGoodMeetings をさわってみた ときにチーム管理の機能があって、任意のユーザーを招待するのは無制限で課金されないと書いてあった。「なるほどね。」とピンと来てコパイロツトの中の人に次のような所感を共有してみた。

招待可能ユーザー数を無制限にしているのはよい視点だと私は思います。メンバー課金にすると、経費を削減するために共有アカウントを利用したり、あまり使わない人にはアカウントを作らないようになって情報共有の側面から望ましくない状態になる。一昔前のオンプレ時代は業務に使うシステムのアカウントは全社員がもっていて当たり前だったのが、クラウドサービスを使うようになってメンバー課金の経費削減から全社員がもたないようになりつつある (とくに中小企業) のは、情報共有の視点から過去よりも悪化しているという問題意識を私はもっています。

コパイロツトさんもまったく同じ課題意識をもっていてメンバー課金しない料金体系にしているとのこと。鶏と卵みたいな話しだけど、組織には情報共有のためにアカウントのお金をケチんなと言いたいし、クラウドサービスの会社も料金体系を1人ずつじゃなくて、30人、100人、1000人といったある程度の階段でいいんじゃない?とか思ったりする。メンバー課金じゃないクラウドサービスとして basecamp や backlog などがある。

git コマンドでアーカイブ

2時に寝て7時に起きた。昨日も遅かったので0時ぐらいに晩ご飯を食べてうまく眠れなかった。体調が悪かったので今日は早めにお仕事を終えて帰って寝てた。

rpm パッケージングのためのアーカイブ

プロダクトは docker compose を使ってデプロイするので docker-compose.yml と関連する設定などのサンプルファイルをパッケージングして rpm として提供する。ビルドは必要なく、初期は数ファイルだったので rpm の SOURCES ディレクトリに直接配置して個別に SourceXx と指定してパッケージングしていた。設定のサンプルファイルが増えてくると1つずつ指定するのが面倒になってきてアーカイブすることにした。rpm を作るための Makefile で次のように git コマンドからアーカイブを作ることができる。このやり方のメリットの1つは git でアーカイブすることでリポジトリにコミットされているものだけが使われるため、対象ディレクトリに中間ファイルなどが散らかっていても無視してくれて都合がよい。

VERSION          = 1.0.0
SRC_PREFIX       = my-product-$(VERSION)
SRC_ARCHIVE      = $(SRC_PREFIX).tar.bz2

SOURCES/$(SRC_ARCHIVE):
	git -C ../my-src archive HEAD --prefix $(SRC_PREFIX)/ -o $(SRC_ARCHIVE)
	mv ../my-src/$(SRC_ARCHIVE) $@

make したときに my-product-1.0.0.tar.bz のようなアーカイブが rpm パッケージングするときの SOURCES 配下に置かれる。そして rpm の spec ファイルでこのアーカイブを Source0 として指定して %prep で %setup マクロを呼び出すと展開される。

Source0: my-product-%{version}.tar.bz2
...
%prep
%setup

たったこれだけで spec ファイルの Source 管理をシンプルにできて保守コストが下がるのでうまいやり方だなと学びになった。

rpm のパッケージングを作り直す

1時に寝て何度か起きてあまり眠れなかった。昨日は遅くに帰ってきて晩ご飯を遅くに食べたので寝ていて吐き気がしてうまく眠れなかった。寝る前に食べることはできないみたい。

rpm パッケージング再び

ビルドができるようになったモジュール を rpm でパッケージングする。rpm でのビルドもできる状態で渡してもらえたので私が開発したモジュールを追加してパッケージングを修正する。rpm のパッケージングを行うのも5年ぶりといったところ。コンテナに慣れてしまって rpm を使うことはもうないと思っていたけれど、まだまだ現役であることを実感する。spec ファイルは普通に読めるので既存の設定や、他の rpm パッケージの spec ファイルの記述などもみながら、自分のモジュールで必要な設定を追加していく。久しぶりだったわりには順調に作業が進捗して2-3時間もやっていて追加の修正をして、実際にインストールして動作確認もできた。

rpm のマクロを確認する。

$ rpm --eval "%{_libdir}"
/usr/lib64

あるサーバーサービスを systemd 経由で実行させる。内部的に環境変数を使っている。systemd の EnvironmentFile で環境変数を設定したファイルへのパスを指定できる。例えば、次のように EnvironmentFile にパスを設定する。

[Service]
Type=simple
EnvironmentFile=/opt/path/to/my.env
ExecStart=/opt/path/to/bin/my-service
KillMode=process
StartLimitBurst=2
Restart=on-abnormal
User=ldap
Group=ldap

この環境変数にはパスワードのような機密情報も含むので rpm の %files で root 権限でのみ読めるようにアクセス制限を設定する。systemd 自体は root 権限で動くので環境変数の設定は root が行って my-service は ldap のユーザー/グループ権限で動く。

%attr(600,root,root) %config(noreplace) %{_sysconfdir}/path/to/my.env

ローカルにコンテナレジストリを構築する

出張する日は寝ないで資料を作ったりバグ修正したりして始発の新幹線の中で寝てた。寝てなくて疲れているせいか、新幹線で寝るのに慣れたのか、わりと2-3時間ぐっすり新幹線で眠れるようになってきた。普通にベッドで寝ても3時間ぐらいしか眠れないので睡眠時間はあまり変わらない。

docker registry の構築

先日の調査 の続き。Deploy a registry server に書いてあることを実際にローカルで検証した。

tls の自己証明書の作成。subjectAltName という設定をするように書いてある。

$ openssl req -newkey rsa:4096 -nodes -sha256 -keyout certs/domain.key -addext "subjectAltName = DNS:myhost.mydomain.example.com" -x509 -days 365 -out certs/domain.crt

basic 認証のための htpasswd の設定。htpasswd とか懐かしいなと思いながら実行した。

$ docker run --entrypoint htpasswd httpd:2 -Bbn user1 secret1 >> dot_htpasswd
$ docker run --entrypoint htpasswd httpd:2 -Bbn user2 secret2 >> dot_htpasswd

docker 社が提供する oss な docker registry サーバーを使って起動する。

$ mkdir /mnt/registry  # docker image を永続化する場所
$ sudo docker run -d \
  --restart=always \
  --name registry \
  -v "$(pwd)"/auth:/auth \
  -e "REGISTRY_AUTH=htpasswd" \
  -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
  -e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/dot_htpasswd" \
  -v "$(pwd)"/certs:/certs \
  -e "REGISTRY_HTTP_ADDR=0.0.0.0:443" \
  -e "REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt" \
  -e "REGISTRY_HTTP_TLS_KEY=/certs/domain.key" \
  -p 8443:443 \
  -v /mnt/registry:/var/lib/registry \
  registry:2

これで basic 認証付きで https で通信できる docker registry サーバーができた。

外部のマシンから dokcer login しようとすると次のようなエラーが発生する。

$ docker login myhost.mydomain.example.com:8443
Username: user2
Password: ***
Error response from daemon: Get "https://myhost.mydomain.example.com:8443/v2/": x509: certificate signed by unknown authority

Test an insecure registry によると、自己証明書を使って外部からアクセスできるようにするためには docker client 側にさっき作った domain.crt をコピーする必要がある。

linux だとこんな設定。

$ cp domain.crt /etc/docker/certs.d/myhost.mydomain.example.com:8443/ca.crt 

Docker Desktop for Mac を使っている場合はこんな感じ。

> security add-trusted-cert -d -r trustRoot -k ~/Library/Keychains/login.keychain path/to/certs/domain.crt

これで外部からも docker login して任意の docker image を push/pull できるようになる。docker registry サーバーは Let’s Encrypt をサポートしているそうなので How It Works を参照して設定すればよいと書いてあった。

mdbook の初期設定

mdbook は新しい rust のバージョンだとビルドできなかったりするので rustup を使ってローカルに rustc をインストールするのがよいかもしれない。プラグインとしては mdbook-mermaid を使う。

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
$ ~/.cargo/bin/rustc --version
$ cargo install mdbook mdbook-mermaid

mdbook-mermaid の設定も簡単でドキュメントルート配下に mermaid の js ファイルを配置すると動いた。

$ vi book.toml
[preprocessor.mermaid]
command = "mdbook-mermaid"

[output.html]
additional-js = ["mermaid.min.js", "mermaid-init.js"]

コンテナレジストリをプライベートに運用する

0時に寝て7時に起きた。晩ご飯を食べきれなくて調子悪いと思っていたら夜も吐き気と胃酸で気分が悪くてあまり眠れなかった。

外部向けコンテナレジストリ

いまお仕事で作っているアプリケーションは docker image としてパッケージングしている。エンドユーザーがこのアプリケーションを使うためには docker pull できるためにインターネットを経由してアクセスできる必要がある。普段はイントラネットのコンテナレジストリに push/pull して運用しているのを、外部のエンドユーザー向けにアクセスできるコンテナレジストリ (リポジトリ) を構築しないといけない。パブリックなリポジトリとして提供するのであれば、docker hubGitHub Container registry などを無料で利用できる。しかし、プライベートなリポジトリで運用しようとするとその選択肢は狭まってしまう。おそらく他社のサービスを使うのであれば、実際の運用を考慮するといくらか費用がかかるだろう。

仮に docker image が使うストレージを5GiB、インターネットへの outbound なデータ転送を30GiB/月で見積もってみた。docker hub だと利用量によって課金されないのでその後に利用増加が前提であればよさそうにみえる。

  • github (従量課金)

  • aws (従量課金)

    • region=tokyo: $3.92/month, $47.04/year
  • docker hub (容量無制限)

別の選択肢として自前でコンテナレジストリを運用するという方法もある。docker registry サーバーは oss として公開されていて docker image の push/pull をするだけのサーバーならすぐに構築できる。ベーシック認証に近い v1 の認証でよければ htpasswd を使ってアカウント管理できる。

ドメインと tls の証明書と外部からアクセスできるサーバーがあれば、自前で運用するのもそう大変ではないと思う。実際にこれらの運用コストと他サービスの利用料金とを比べて選択することになるだろう。

gitlab packages api の使い方

0時に寝て何度か起きて6時半に起きた。先週より少し早く起きれるようになってきた。

gitlab ci/cd で別プロジェクト (リポジトリ) の成果物を取得する

gitlab では複数のパッケージリポジトリに対応しているが、それらに当てはまらない汎用の成果物向けに GitLab Generic Packages Repository というものがある。zip でもバイナリファイルでも何でも置くためのリポジトリと言える。但し、同じパッケージ名でバージョン管理するといった作りにはなっていなくて、同じパッケージ名でアップロードしても別のパッケージ id が割り当てられて管理される。他バージョンとの紐付け自体はできているので、おそらく歴史的経緯でそういう仕様なのだと思う。そのために、あるパッケージの最新のバージョンを取得したいときは作成日の降順でソートして最初のパッケージを取得するといったコードを書かないといけない。それは Packages API を駆使して簡単なスクリプトを書くことになる。

もう1つ分からないことにトークンの使い分けがある。なるべく ci/cd での処理は GitLab CI/CD job token を使いたいところだが、どうも Packages API の呼び出しはできなくて別途プロジェクトでアクセストークンを作成して呼び出すようにしている。これはもしかしたら別の設定で CI/CD job token でも呼び出しできるかもしれない。rest api への呼び出し権限そのものがないのかもしれない。

最終的には次のようなスクリプトで任意のプロジェクトの generic リポジトリの最新の成果物を取得できた。

rm -rf ${TARGET_DIR}
mkdir -p ${TARGET_DIR}
for project in $PROJECTS
do
  prj=$(echo "$project" | jq -Rr @uri)
  base="${CI_API_V4_URL}/projects/${prj}"
  pkg=$(curl -s -H "PRIVATE-TOKEN: $PROJECT_ACCESS_TOKEN" "${base}/packages?order_by=created_at&sort=desc&per_page=1" | jq '.[0]')
  pkg_id=$(echo $pkg | jq -r .id)
  pkg_name=$(echo $pkg | jq -r .name)
  pkg_version=$(echo $pkg | jq -r .version)
  file_names=$(curl -s -H "PRIVATE-TOKEN: $PROJECT_ACCESS_TOKEN" "${base}/packages/${pkg_id}/package_files" | jq -r '.[].file_name')
  for file_name in $file_names
  do
    dw_endpoint="${base}/packages/generic/${pkg_name}/${pkg_version}/${file_name}"
    curl -s -H "PRIVATE-TOKEN: $PROJECT_ACCESS_TOKEN" "${dw_endpoint}" -o "${TARGET_DIR}/${file_name}"
  done
done
find ${TARGET_DIR} -type f